Day 22: Jumping into Assembly
🍂 Mood: Watching the red leaves fall outside my window.
🎵 Soundtrack: This Is The Beatles
📚 Reading Github Issues
Read 13 new github issues this morning.
- One was about using an analyzer to detect code that uses generics
- One was about something in go fuzz, but most importantly the output in the issue uses the code that I contributed 🎉🎉🎉 !
💬 Gopher Slack
I am in gopher slack, but I find it very overwhelming. They explicitly don’t use threads, which makes things very hard to follow. And some of the channels (like #networking) are very chatty. And people reply so quickly! It seems either that (1) people respond instantly to questions so I have no time to research answers if I wanted to help others or (2) unanswered questions are pushed up by all the other chatting and it is so hard to follow that it is difficult to tell if they were answered eventually or not. All this to say, I am not very active in gopher slack.
BUT! I was in the #fuzzing channel, which is much less chatty given that it is about a beta feature, and I was able to answer someone’s question! I was able to answer said question because it came up in an issue I read a few weeks ago! This is one of the reasons that I read all the issues! My evil plan is working!
✂️ Dividing in Assembly
Rather than continuing to bash my head trying to figure out how to get my division function to work in assembly, I asked in gopher slack. And a very nice someone helped! Apparently I needed to clear the DX register.
Here is the working function:
// func div(x, y int) int
TEXT ·div(SB),$0-24
MOVQ x+0(FP), AX
MOVQ y+8(FP), BX
XORQ DX, DX // <--- This is what I was missing
IDIVQ BX
MOVQ AX, ret+16(FP)
RET
I have two open questions:
- ❓ Why did I need to clear the DX register? DX is the register where the remainder is stored. Why couldn’t IDIVQ just write over what is already in DX?
- ❓ Why did not clearing the register result in an error
panic: runtime error: integer divide by zero
? Neither the divisor or dividend were zero so why did I get this particular error?
🦘 Jumps in Assembly
Now that I got dividing figured out, I wanted to branch out into if statements. I was able to write a few very useful functions.
💻 lessThanTen returns 1 if the number is less than 10 and 2 if it is greater than 10.
TEXT ·lessThanTen(SB),$0
MOVQ a+0(FP), BX
MOVQ $10, AX
CMPQ BX, AX
JL LESS
JG MORE
LESS:
MOVQ $1, AX
JMP END
MORE:
MOVQ $2, AX
JMP END
END:
MOVQ AX, ret+8(FP)
RET
💻 isOdd returns 0 if the number is even and 1 if the number is odd.
TEXT ·isOdd(SB),$0
MOVQ a+0(FP), AX
MOVQ $2, BX
XORQ DX, DX
IDIVQ BX
CMPQ DX, $0
JE EVEN
JG ODD
EVEN:
MOVQ $0, AX
JMP END
ODD:
MOVQ $1, AX
JMP END
END:
MOVQ AX, ret+8(FP)
RET
This works with either of the below function signatures.
func isOdd(val int) bool
// OR
func isOdd(val int) int
🖨 Printing in Assembly
In the examples above I had a go file with a main function that called out to my
functions defined in assembly and then the main function would call fmt.Println
with the result. But for my next step I wanted to have the assembly functions
call fmt.Println
.
I started by adding the following instruction to one of my functions, just to see if I could call it.
CALL fmt·Println(SB)
But this resulted in an error:
relocation target fmt.Println not defined for ABI0 (but is defined for ABIInternal)
Okay… I’ve seen these words before. I know that they are different standards for go assembly that were made for backwards compatibility reasons.
In the go 1.12 release notes it says:
The compiler toolchain now uses different conventions to call Go functions and assembly functions. This should be invisible to users, except for calls that simultaneously cross between Go and assembly and cross a package boundary.
😅 That’s exactly what I am trying to do. Make a call that both crosses between Go and assembly and crosses a package boundary.
So I tried to make my function use ABIInternal by amending the first line to the following:
TEXT ·isOdd<ABIInternal>(SB),$0
But then I got this error:
ABI selector only permitted when compiling runtime, reference was to "\"\".isOdd"
According to this error (which is implemented here) only go runtime assembly code is allowed to use the ABIInternal standard. Well fine.
So I moved onto hacks! I knew from the release notes that I wouldn’t be able to cross package boundaries without hitting this error. So I made my own print function.
💻 evenOrOdd prints the string “even” or “odd” based on the integer provided.
Here is the assembly:
TEXT ·evenOrOdd(SB),$0
MOVQ val+0(FP), AX
MOVQ $2, BX
XORQ DX, DX
IDIVQ BX
CMPQ DX, $0
JE EVEN
JG ODD
EVEN:
CALL ·printEven(SB)
JMP END
ODD:
CALL ·printOdd(SB)
JMP END
END:
RET
Here is the go:
func evenOrOdd(val int)
func printEven() {
fmt.Println("even")
}
func printOdd() {
fmt.Println("odd")
}
🎁 Passing Arguments in Assembly
Okay so I cheated a little bit. I didn’t even pass any variables to my print statements. I haven’t even gotten to using strings yet! But before I try to pass strings, let’s start with passing ints.
💻 printVal Prints the argument passed to it
Here is the assembly:
TEXT ·printVal(SB),$0
MOVQ val+0(FP), AX
MOVQ AX, 0(SP) <--- Do this magic thing I found on stack overflow
CALL ·printInt(SB)
RET
Here is the go:
func printVal(val int)
func printInt(val int) {
fmt.Println(val)
}
And it works! It prints it! But how? 🤷♀️ No idea. Will work on it tomorrow along with passing multiple variables.