Assembly Language Core for Xojo

Hi Everyone,

While ALE was fun it was limited in that the assembly commands could only be executed in a sandboxed engine, so what if you wanted to use the assembly commands directly in your Xojo code with no engine in between, well that is what AssemblyCore is for. It currently has about 30 assembly commands implemented and I haven’t had time to document it but if you want to try it out it is available.

AssemblyCore is a set of Xojo wrappers to allow the use of assembly commands, the module is completely unlocked so you can see how it all works, it is actually insanely simple. I’d like to continue maintaining it so if you have a suggestion then pass it onto me for inclusion. It’s free to use but a small donation to help development would be appreciated but not required.

The assembly instructions can be used anywhere in your Xojo code, modifying your own variables or run on background threads or in anyway you desire. There are all the standard x86 registers and a stack should you want to pass parameters the assembly language way.

var a as Integer
mov 100, a

Of course there are some limitations as Xojo does not have the concept of labels or jump commands, but we can come pretty close.

var a, b as Integer
mov 100, a
mov 50, b
cmp a,b
if je then yourMethod

What about Assembly language loops, without labels it seems impossible but it’s not that hard.

mov 100, ECX
while jnz
//your code
loop_
wend

You can even pass parameters in the assembly language way

push 100
yourMethod
var a as Integer
pop a
//Do something in your method

Have fun and let me know what you think. Just import the module into your app then away you go. The next step, other than continued improvements, is possibly calling direct OS functions, but we shall see.

Regards

TC

2 Likes

Hi Everyone,

I just uploaded a version with some basic documentation of the instructions. Currently only Integer parameters are supported on the instructions, registers and stack, as is generally the case with asm but I’ll look at creating overloaded methods to handle floating points if needed. I feel that if you need to do massive floating point modifications then you’d probably do that within your own code.

You may notice when you look at the instructions that they all have just a single line of code. Asm gets it speed from doing very simple things, but by chaining these simple instructions together you can do complex things.

Anyway have fun.

TC

2 Likes

Hi there,

I just uploaded version 1.1 with a few bug fixes and now with full floating point support on all methods where it was needed. This means that the registers and stack are now also floating point. I think this was really an oversight on my part, floating point is really needed.

Methods now support the following parameter combinations:

  • Integer to Integer
  • Integer to Double
  • Double to Double
  • Double to Integer

This seems to cover most requirements. I haven’t found any other situation yet, but I’ll add it when I do.

Regards

TC

2 Likes

Hi Everyone,

Here is a little example of using AssemblyCore in Xojo to convert a string to uppercase. I know Xojo has a method for doing this but this is a good get your head around assembly example. You’ll need to download version 1.2 for this. Version 1.2 auto updates register ESP with the stack size information.

Okay now using some labels which I didn’t know about… Thanks @Robert_Weaver

Regards

TC

//Let do a uppercase string example
Var inString As String = "Hello world"
Var outString As String = ""
Var sPointer As Integer = 0

//Lets push all the items onto the stack for modification
For Each char As String In inString.Characters
  push char.Asc
Next

//Lets store the size of the stack in ECX so we can loop through
//all the string characters
mov ESP, ECX

//Now we'll loop through all the character and convert them to uppercase
While jnz
  //Confirm characters are in the lower case range then convert to uppercase
  cmp 97, stack(ecx)
  If jl Then GoTo notLowerCase
  cmp 122, stack(ecx)
  If jg Then GoTo notLowerCase
  
  //Upper Case the Character
  r0 = stack(ecx)
  sub_ 32, r0
  stack(ecx) = r0
  
  notLowerCase:
  
  loop_
Wend

//Now we'll store the uppercase string
Len inString, ECX
While jnz
  outString = outString + Chr(stack(sPointer))
  inc sPointer
  loop_
Wend

//Now lets erase all the information from the stack to
//Clean it up for the next task
Len inString, ECX
While jnz
  pop
  loop_
Wend

System.DebugLog(outString)
System.DebugLog(CStr(ESP))

Xojo does in fact have a goto instruction and labels. I used this to advantage when I coded a PIC microcontroller emulator. Similar to what you have done, all of the PIC instruction codes were coded as Xojo methods. This allowed the writing of PIC assembly language code directly in the Xojo code editor and then running it. There’s a short description of the project, which I submitted in the summer 2018 Just Code Challenge. Here’s a link, and it has a link for downloading the project.

Here is example assembly code that makes use of Xojo’s goto instruction and labels:

  'Volder's CORDIC rectangular to polar routine in PIC assembly language.
  'It is assumed that inputs are 12 bit positive integers 0..4095.
  'Hence the vector is in the first quadrant.
  
  
  'The following XOJO housekeeping code sets up the various PIC registers.
  dim xH,xL,yH,yL,angleH,angleL,index,count,tL,tH,vL,vH,kScale As UInt8
  'dim deltaSum As Double
  'Lookup table values with angles in degrees. Tables probably have more entries than necessary for 16 bit resolution
  dim LU_AngleInt() As Integer = array(26,14,7,3,1,0,0,0,0,0,0,0,0,0)
  dim LU_AngleFrac() As Integer = Array(144,9,32,147,202,229,114,57,28,14,7,4,2,1)
  
  
  dim nTable As UInt8 = UBound(LU_AngleFrac)
  
  'Initialize x and y variables
  xH=x\256 'High byte of x coordinate
  xL=x mod 256 'Low byte of x coordinate
  yH=y\256 'High byte of y coordinate
  yL=y mod 256 'Low byte of y coordinate
  
  '*************************************
  
  'Start of ASM code
  
  'CORDIC ATAN2 function
  '
  'On entry, the 12 bit (0..4095) values of x and y should be in xL,xH,yL,yH
  'The upper 4 bits of xH and yH must be Zero.
  'On completion, the angle (in degrees) is in AngleH and AngleL
  'The integer part is in AngleH, and the fractional part is in AngleL
  'The radius (magnitude) is in yH,yL (integer), and xH (fraction).
  
  
  clrf angleH
  clrf angleL
  'Check for all Zero input combinations
  movf xL,w
  iorwf xH,w
  iorwf yL,w
  iorwf yH,w
  btfsc status,z
  if noSkip then Return 999 'This is an error code indicating that both x an y are zero
  'check if y=0
  movf yH,w
  iorwf yL,w
  btfsc status,z
  if noSkip then Return 0 'Angle is zero if y=0
  'check for x=0
  movlw 90
  movwf angleH
  movf xH,w
  iorwf xL,w
  btfsc status,z
  if noSkip then Return 90 'Angle is 90 degrees if x=0
  movlw 45
  movwf angleH
  
  ' Scale up x and y to improve resolution
  movlw 15
  movwf kScale
  
  ScaleXY:
  
  decf kScale,f
  clc
  rlf xL,f 'shift xH and yH left
  rlf xH,f
  clc
  rlf yL,f
  rlf yH,f
  movf xH,w
  iorwf yH,w
  andlw &hF0
  btfsc status,z
  if noSkip then goto ScaleXY
  
  'xH and yH are now normalized
  'initialize calc loop
  'calc x and y initial values
  'save old x, as it will be overwritten
  movf xH,w 
  movwf vH
  movf xL,w
  movwf vL
  'calc x=x+y
  addwf yL,w 
  movwf xL
  btfsc status,c
  incf xH,f
  movf yH,w
  addwf xH,f
  'calc y=y-x
  movf vL,w
  subwf yL,f 
  btfss status,c
  decf yH,f
  movf vH,w
  subwf yH,f
  clrf index 'Loop counter and lookup table index
  
  MainLoop:
  
  'shift x and y values
  movf index,w
  movwf count
  'calc x y shift
  movf yH,w
  movwf tH
  movf yL,w
  movwf tL
  movf xH,w
  movwf vH
  movf xL,w
  movwf vL
  btfss yH,7 'check for negative y
  if noSkip then goto ShiftLoop
  comf tH,f
  comf tL,f
  incf tL,f
  btfsc status,z
  incf tH,f
  
  ShiftLoop:
  
  clc
  rrf vH,f
  rrf vL,f
  clc
  rrf tH,f
  rrf tL,f
  clc
  decfsz count,f
  if noSkip then goto ShiftLoop
  
  'calc new xH and yH
  btfsc yH,7
  if noSkip then goto NewXYcalc
  'y=y+/-yH>>index
  comf vH,f
  comf vL,f
  incf vL,f
  btfsc status,z
  incf vH,f
  
  NewXYcalc:
  
  movf vL,w
  addwf yL,f
  btfsc status,c
  incf yH,f
  movf vH,w
  addwf yH,f
  'x=x+y>>index
  movf tL,w
  addwf xL,f 
  btfsc status,c
  incf xH,f
  movf tH,w
  addwf xH,f
  ' Sum Angle from lookup table
  movf index,w
  call_Lkp LU_AngleFrac
  movwf tL
  movf index,w
  call_Lkp LU_AngleInt
  movwf tH
  btfss yH,7
  if noSkip then goto AddSum
  comf tH,f
  comf tL,f
  incf tL,f
  btfsc status,z
  incf tH,f
  
  AddSum:
  
  movf tL,w
  addwf angleL,f
  btfsc status,c
  incf angleH,f
  movf tH,w
  addwf angleH,f
  'if result is Zero, then we are done
  incf index,f
  movlw nTable+1
  subwf index,w
  btfss status,z
  if noSkip then goto MainLoop
  '
  'The result of the preceding steps is to rotate the vector to the x axis. The rotated angle
  'is the required polar angle, and the final x value is proportional to the required magnitude.
  'The magnitude will be high by a factor of 1.6467743... which is the product of the cosine values
  'in the lookup table. to get the true magnitude value we must scale it back by multiplying by
  '1/1.6467743 or 0.607247753539552. Since we only need 16 bit precision, we can pick the simplest
  'fixed shift and add routine that gives a sufficiently precise result. Multiplying by 17/28
  'gives 0.6071428571 which is in error by 0.0172% which is probably good enough. However, we can
  'improve the accuracy by truncating the summation after 4 terms and adding the final term twice.
  'This gives a correction of 0.60725402832 for an error of 0.001%
  
  'We use a repeating fraction multiply algorithm with parameters m=17, n=5, p=3 (for 17/28 factor)
  'First multiply by m, then shift right n bits, then repeatedly shift right p bits and add to the
  'n shifted value.
  
  'start by multiplying by 17 (actually 17/4, in order to scale values to a safe range;
  'the factor 4 will be removed later)
  
  clc
  rrf xH,w
  movwf tH
  rrf xL,w
  movwf tL
  movlw 3
  movwf count
  
  MpyLoop1:
  
  clc 'bcf status,c
  rrf tH,f
  rrf tL,f
  decfsz count,f
  if noSkip then goto MpyLoop1
  movf xL,w
  addwf tL,f
  btfsc status,c
  incf tH,f
  movf xH,w
  addwf tH,f
  
  movf tH,w 'copy back from t to x
  movwf xH
  movf tL,w
  movwf xL
  'end of 17/4 scale
  
  'repeating shift/add loop
  movlw 4
  movwf index
  
  MpyLoop2:
  
  movlw 3
  movwf count
  
  MpyLoop3:
  
  clc 'bcf status,c
  rrf tH,f
  rrf tL,f
  decfsz count,f
  if noSkip then goto MpyLoop3
  movf tL,w
  addwf xL,f
  btfsc status,c
  incf xH,f
  movf tH,w
  addwf xH,f
  decfsz index,f
  if noSkip then goto MpyLoop2
  
  movf tL,w
  addwf xL,f
  btfsc status,c
  incf xH,f
  movf tH,w
  addwf xH,f
  
  'Finally shift x into y regs
  clrf yH
  clrf yL
  
  ScaleLoop:
  
  clc
  rlf xL,f
  rlf xH,f
  rlf yL,f
  rlf yH,f
  decfsz kScale,f
  if noSkip then goto ScaleLoop
  nop
  
  AllDone:
  nop
  
  'End of asm code.
  '****************************************
  
  'The magnitude value is converted to a type double, and stored as a property.
  magnitudeCORDIC=(yH*256+yL+xH/255)
  
  'The angle is converted to a type double, and stored as a property.
  angleCORDIC = angleL/256+angleH
  
  return 1 'Error code Indicates valid result

This worked well in my situation, because these PIC processors have no conditional jump instructions, just conditional skips. So all I had to do for compatibility is have the skip instruction set or clear a flag bit, and then the following line that has the goto instruction is simply prefixed with “If noskip then”

1 Like

Cool thanks, I’ll look into that…

TC

Yeah, goto’s and labels are a tightly guarded secret, to prevent people from using them for anything except ancient legacy code compatibility. :wink:
Actually, they are in the Xojo docs. At least, they were there the last time I checked.

1 Like

Yes, thanks so much, helps with ASM format so much. I found them in the documentation but they’re not obvious. I think I’ll still do my loops the same way as I like the indenting in the while statement, though I could always use a label and a conditional jump instruction instead.

Many, many thanks.

TC

Hi Everyone,

I’ve started to add some of the extended x86 instructions into the module such as:

  • POPSC to pop a single string character off the stack and add it to the end of a string
  • PUSHS to push all the character values of a string onto the stack
  • PUSHSC to push a specified character value of a string onto the stack

All the conditional check instructions can now be pointed at a different register rather than just EAX as default, which was sorely needed. I’ve also added more floating point support to many methods which were lacking it.

I’ll upload a new version in a couple of days after some further testing, but here’s a little example of how much the changes and extra instructions have reduced the needed code. The following tiny example reverses the characters in a string. Loop_ always counts backward so we are grabbing the characters in the reverse order. Pop also always grabs and removes the last item which was added to the stack.

TC

//Push all the characters of the string onto the stack for modification
pushs inString

//Now we'll store the reversed string,
Len inString, ECX
While jnz(ecx)
  popsc outString
  loop_
Wend

This is all very cool but does it actually offer a performance improvement over Xojo code? What is its use case?

1 Like

Hi Garry,

ALE with it’s highly optimised engine running compiled asm object code is certainly comparable to Xojo but it is completely sandboxed and not really available to intersperse with your Xojo code. AssemblyCore is a series of Xojo wrappers providing assembly syntax and will generally run at Xojo speed but will never out perform it. It can also be seamlessly interspersed with your Xojo code, which is something that others desired.

I could call external OS functions directly but again they would be no quicker as they have no context to each other so they wouldn’t perform like a compiled asm application, at best I suspect with the overhead of constantly calling external methods it would be slower then coding in Xojo directly. It might be a little dangerous as well. :slight_smile:

I designed AssemblyCore because I enjoy coding in ASM but I also like to have a nice editor and a quick compile and runtime environment which Xojo provides, I have also used Xojo and its predecessors for many years so it’s comfortable. I guess the use case is that I can code in ASM and Xojo to get the result I desire and others may also do the same, based on the number of downloads, or they could use it purely as a tool to learn the concepts of assembly language or hopefully discover other ways of doing things.

I find it enjoyable and I hope others will as well. It’s always fun to learn new stuff, just not who the new Bond is going to be… :slight_smile:

Kind regards

TC

3 Likes

Thats reason enogh I think, not everything needs to have a grand puprose. Somethimes one need to do what youre passionate about

2 Likes

Thanks,

It’s certainly what drives me forward… :grinning:

Kind Regards

TC

1 Like