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.
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.
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.
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”
Yeah, goto’s and labels are a tightly guarded secret, to prevent people from using them for anything except ancient legacy code compatibility.
Actually, they are in the Xojo docs. At least, they were there the last time I checked.
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.
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
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.
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…