L1VM - assembly VOL I

Here I will show how to read the assembly output of my compiler Brackets. A program can be build in the l1vm directory:

./build.sh prog/hello

This creates the bytecode in “prog/hello.l1obj” and in the root directory: “out.l1asm” and “out.l1dbg”. Here is “prog/hello.l1com” in Brackets:

// hello.l1com
// Brackets - Hello world!
//
#include <intr.l1h>
(main func)
	(set int64 1 zero 0)
	(set int64 1 x 23)
	(set int64 1 y 42)
	(set int64 1 a 0)
	(set string 13 hello "Hello world!")
	// print string
	print_s (hello)
	print_n
	((x y *) a =)
	print_i (a)
	print_n
	exit (zero)
(funcend)

And here is “out.l1asm”:

.data
Q, 1, zero
@, 0Q, 0
Q, 1, x
@, 8Q, 23
Q, 1, y
@, 16Q, 42
Q, 1, a
@, 24Q, 0
B, 13, hello
@, 32Q, "Hello world!"
Q, 1, helloaddr
@, 45Q, 32Q
.dend
.code
:main
loada zero, 0, 0
loada helloaddr, 0, 1
intr0 6, 1, 0, 0
intr0 7, 0, 0, 0
loada x, 0, 2
loada y, 0, 3
muli 2, 3, 4
load a, 0, 5
pullqw 4, 5, 0
loada a, 0, 6
intr0 4, 6, 0, 0
intr0 7, 0, 0, 0
intr0 255, 0, 0, 0
rts
.cend

The first line begins the “.data” section with all variable declarations. The second and third line declare the variable “zero”:

Q, 1, zero

This sets “Q” for “int64” type, “1” for one variable and “zero” as the variable name.

@, 0Q, 0

Now here we have the memory address “0Q” and the value “0”.

And on line four and five we have:

Q, 1, x

This sets “Q” for “int64” type, “1” for one variable and “x” as the variable name.

@, 8Q, 23

Here we get the memory address “8Q” and the value “23”. So the memory address is increased by 8 byte to store the variable “x”. The compiler sets the right memory addresses for us, so have no worries about this!

The string declaration is different:

B, 13, hello

Here we have type “B” for byte or string. And “13” chars. The name is “hello”.

@, 32Q, "Hello world!"

The address is “32Q” and the value “Hello world!”. However here are two more lines:

Q, 1, helloaddr

This defines a “int64” type variable “helloaddr” storing the memory address of the string “hello”:

@, 45Q, 32Q

The memory address “32Q” is stored at location “45Q”. Internally a string variable is referenced by its address. This is done automatically.

Lets have a look at the multiplication code:

Brackets:

((x y *) a =)

Assembly:

loada x, 0, 2
loada y, 0, 3
muli 2, 3, 4
load a, 0, 5
pullqw 4, 5, 0

Here the “loada” opcode loads the “int64” variable “x” into register “2”. Then the next “loada” opcode loads the “int64” variable “y” into register “3”.

Now the “muli” opcode just multiplies the registers “2” and “3” and stores the result in the target register “4”. Now the compiler needs to store the register back into the variable “a”. This is done by load the address of “a” into register “5”:

load a, 0, 5

Now the register “4” is pulled into address “5” (a) by the “pullqw” opcode:

pullqw 4, 5, 0

I hope this posting could tell you more about what is going on in my compiler.
Have some fun!

13: assembly VOL II