-
Notifications
You must be signed in to change notification settings - Fork 4
Simple Functions
Problem: you want to calculate the volume of a box, given its dimensions. To limit code duplication you want to put it in a function.
If you were to write this function in Swift it would look something like this:
func calculateVolume(width: Int, height: Int, depth: Int) -> Int {
return width * height * depth
}
Pretty simple right? But… how would you implement this in assembly? What's the best way to do it? Should you pass parameters on the stack or in registers?
Answer: probably in registers, if you can spare 3 of them (and chances are, you can). Here's how the same function would look in this playground, taking parameters in registers:
func calculateVolume() {
// width in ra, height in rb, depth in rc
imul() // ra = width * height
mov(.rb, .rc) // rb = depth
imul() // ra = (width*height) * depth, done
}
So what's happening here? We see two calls to the imul
instruction, which makes sense since we do two multiplications, but what's with the mov
instruction?
If you read the documentation on imul
, it multiplies whatever integer value is in ra
by whatever integer value is in rb
, and stores the result in ra
. TLDR, imul
executes ra = ra * rb
. After that first imul
instruction, we're done with the value in rb
. Now we need to multiply that product by the depth, which is in rc
. Our only option is to move rc
into rb
, which is what happens with the mov
instruction there in the middle. The second imul
instruction finishes the job, and we're done, since the result is in ra
and that's where the caller expects it to be for small return values like a number.
However, this is a pretty bare-bones implementation. Every time you perform arithmetic, the Flags
are updated to reflect the result of the operation. We could let that happen, but what if we don't want to affect the Flags
values? There's no way around the fact that arithmetic will change them, so... we have to save them somehow, and restore them after we finish our multiplication! Thankfully, we can do that with Stack.pushf()
and Stack.popf()
(the f
stands for Flags
). These instructions simply push and pop Flags.current
to and from the Stack. Putting these at the top and bottom of our function will ensure the flags remain untouched, as far as the caller can see:
func calculateVolume() {
Stack.pushf() // Save flags we might clobber
// width in ra, height in rb, depth in rc
imul() // ra = width * height
mov(.rb, .rc) // rb = depth
imul() // ra = (width*height) * depth, done
Stack.popf() // Restore flags
}
Now that our function is polished, let's take it out for a spin in main()
. Say, width = 3, height = 4, and depth = 5. First, we prepare the arguments in registers. Then we call the function:
mov(.ra, 3)
mov(.rb, 4)
mov(.rc, 5)
calculateVolume()
The above is equivalent to higher level code which looks like this:
var result = calculateVolume(3, 4, 5)
where result
is the value left over in ra
after the function call. The difference between let
and var
doesn't mean much at the assembly level, so don't worry about whether something is described as let
or var
in any example Swift code.
Next: Complex Functions