Skip to content

Getting started

ThePantsThief edited this page Feb 23, 2017 · 5 revisions

In higher level languages, you're used to declaring your own variables on a single line and passing them to functions like(this, and, this) and storing function return values
like = this(). Assembly doesn't have functions or variables in the way you might think of them. It's all an elaborate ruse. Given some tools, you have to problem-solve until you can simulate variables and functions.

Storing values

Registers

In assembly, you have these things called registers. You can think of them as global variables, except they're the only variables you can use. There is no language-level concept of null in assembly like there is in Swift, so these variables always have some value that can be interpreted however you see fit (like in C). You also have to remember what you're doing with each register, since you can't name them.

The Stack

Since you have a limited number of these registers, you sometimes run out of space. Thankfully you have another place to store things called the stack. (If you're not familiar with the stack data structure, it's basically an array you can only ever access the last element of. You push things on and pop them off as needed.) When you run out of registers to use, you can push their values onto the stack to save them for later, and pop them back off when you're doing using some other registers.

Dynamically allocated memory

TBA. At the time of writing, there is no way to dynamically allocate memory in this project.

Function calling

Assembly also doesn't have a language-level concept of function calling. To simulate calling a function, the caller and callee (callee = function) have to agree on where parameters will be and where the return value will go. This is known as the calling convention.

Parameters

Most of the time, parameters can be put into registers. But if there are more parameters than can fit in registers, parameters can be put on the stack, too, and the callee can retrieve them as needed. You should document where the callee expects parameters to be.

Return value(s)

Usually you will be able to return a value by putting it in a register. But if you want to return more than one value, you'll have to split them up into multiple registers, or put them on the stack, or dynamically allocate some memory for them and put a pointer to this memory in a register. Problem solve! Just be sure to document where it will be so the caller knows where to look.

Program instructions

This project provides some functions that closely represent actual assembly instructions (and so I will refer to them as instructions). For example, if you wanted to add two numbers together, here's the quickest way to do it:

mov(.rb, 5)    // Store 5 in the rb register
add(.rb, 3)    // Add 3 to the value in rb and put it in the ra register

Once this code has executed, ra holds the sum of 3 and 5 (and rb still has a 5 in it). If you couldn't tell, mov stands for "move," and add means addition. Other instructions, as I will refer to them, are included for all kinds of arithmetic (sub, idiv, imul, etc). Make note, most of these instructions only operate on integers.

How about something a little more complex? To print a string to the console, you first declare the string globally, then move it into a register inside the main function:

let message = "Hello, World!"

func main() {
    mov(.ra, message)    // Store the string in .ra
    print()              // print(message)
}

main()

Quick note: main is only here to enforce a separation of concerns. Global variables like message should be declared outside any and all functions. There should be no top-level code executing except the main() statement below itself (so, put all of your code in main or in another function).

Anyway, the print function here only expects one argument, a String, which should be put into ra prior to calling print(). This is how you pass arguments to functions in assembly, by putting them where the function knows where to look for them. There is also a formattable print function, printf, which takes two arguments in registers and variadic arguments on the stack. In a higher level langauge, you might call it like this:

printf("Hello %@! My name is %@", 2, "bob", "lisa")

where the 2 represents the number of arguments to be interpolated (aka VA args) into the string, and where the first argument is the string itself. In this playground, you would call it like this:

let msg    = "Hello %@! My name is %@"
let name   = "bob"
let myName = "lisa"

func main() {
    mov(.rb, name)          // Load strings into registers
    mov(.rc, myname)        // ...
    Stack.push([.rc, .rb])  // Store variadic args on stack
    
    mov(.ra, msg)           // First param, msg, goes in ra
    mov(.rb, 2)             // Second param, # of VA args, goes in rb
    printf()                // printf(msg, 2)
}

printf expects the first VA arg to be on top of the Stack, so you must push them onto the Stack in reverse order, as if you were stacking some books alphabetically.

--

That's all for now!