šŸ˜– Mood: The shame of forgetting Nadia Comăneci’s name for the NYT crossword despite having spent a summer living in Bucharest.

šŸŽµ Soundtrack: Not In Chronological Order

šŸ“š Reading Github Issues

Oops. Got distracted and didn’t read any.

šŸ’¾ Go Memory Model

I watched a (sadly, private) video of a talk given by my mentor Gareth Smith about some background knowledge to understand the changes to the Go memory model as discussed by Russ Cox . It was great; I learned more about pipelining, branch prediction, and the happens-before relation. And I am not just saying that because I know he reads this (helloooo).

The more resources I consume around this topic the more pieces are being filled in. Which, like Julia Evans, is my favoite way to learn.

I planned to read the Russ’ 3 blog posts on memory models (1, 2, 3), but I didn’t make it there today. Instead I had much fun playing with assembly.

āœļø Reviewing the Assembly I Already Know

Instead of jumping straight back into passing variables, I decided to write a little function that would use all of the knowledge I already have. So here is my function that: takes arguments, uses jumps to make a loops, and calls out to another function.

šŸ’» countDown takes an integer and counts down from that integer to zero. Then it explodes šŸ’„

Assembly:

TEXT Ā·countDown(SB),$0
	MOVQ val+0(FP), BP
	MOVQ $0, AX // AX = 0
	CMPQ AX, BP
	JGE END
LOOP:
	MOVQ BP, 0(SP)
	CALL Ā·printInt(SB)
	CALL Ā·waitOneSec(SB)
	SUBQ $1, BP
	MOVQ $0, AX // doesn't work unless you set it back to 0
	CMPQ AX, BP
	JL LOOP
END:
	CALL Ā·printBoom(SB)
	RET

Go:

func countDown(val int)

func main(){
	arg, _ := strconv.Atoi(os.Args[1])
  countDown(arg)
}

func printInt(val int) {
	fmt.Println(val)
}

func printBoom() {
	fmt.Println("boom")
}

func waitOneSec() {
	time.Sleep(1 * time.Second)
}

Output:

$ ./main 4

4
3
2
1
boom

Open question:

  • ā“ Why do I have to set AX back to 0 every time in the loop? Maybe I should be using a different register?

šŸ“„ Passing Variables in Assembly

Yesterday I figured out how to pass one variable to another function in assembly. But I didn’t understand what it was doing. Like the good staff software engineer I am, I just copied some code off stack overflow without understanding it. Today is the day to understand!

A stranger on the internet recommended that I read the book ā€œx86-64 Assembly Language Programming with Ubuntuā€. Despite the dry name, it has been very readable!

In the section about arguments I saw that there are three ways to pass arguments between functions:

  1. Placing values in register
  2. Globally defined variables
  3. Putting values and/or addresses on stack šŸ‘ˆ this is the one my example did

So I set out to try to do each one.

šŸ„ž Passing Variables by Adding Them to the Stack

Yesterday I was able to add a variable to the stack with this instruction: MOVQ BX, 0(SP). I think this is adding the variable onto what will become the next function stack frame. Then the variable is already there waiting for the next function. Or something?

I got one variable working. Next step: TWO VARIABLES! Why not just do the same thing twice?

šŸ’» print2Vals takes two ints and passes them to a print function. nice.

// main.go snippet
print2Vals(val, val+1)


// assembly snippet
TEXT Ā·print2Vals(SB),$0
	MOVQ a+0(FP), BX
	MOVQ b+0(FP), BP
	MOVQ BX, 0(SP) // pushing it onto the stack
	MOVQ BP, 0(SP) // pushing it onto the stack... again?
	CALL Ā·print2Ints(SB)
	RET

// output
$ ./print 7
7 17347307

Well it didn’t error, but it also didn’t work. Clearly the second var didn’t come through and some random left over garbage was read for the second variables.

Next guess: add it to the stack but offset by 8 bytes.

// assembly snippet
TEXT Ā·print2Vals(SB),$0
	MOVQ a+0(FP), BX
	MOVQ b+0(FP), BP
	MOVQ BX, 0(SP) // pushing it onto the stack
	MOVQ BP, 8(SP) // pushing it onto the stack?
	CALL Ā·print2Ints(SB)
	RET

// output
$ ./print 7
7 7

runtime: unexpected return pc for runtime.sigpanic called from 0x7
stack: frame={sp:0xc000092f10, fp:0xc000092f60} stack=[0xc000092000,0xc000093000)
0x000000c000092e10:  0x000000c000092e18  0x000000000102e1a0 <runtime.addOneOpenDeferFrame.func1+0x0000000000000000>
0x000000c000092e20:  0x0000000001043367 <runtime.sigpanic+0x0000000000000327>  0x000000c000092f10
0x000000c000092e30:  0x000000c0000001a0  0x0000000000000000
0x000000c000092e40:  0x000000c000092f00  0x000000000102ef74 <runtime.gopanic+0x0000000000000114>
0x000000c000092e50:  0x00000000011363a0  0x000000000109f560
0x000000c000092e60:  0x000000c00009add0  0x00000
...

So it printed 7 twice? And then broke.

So my favorite way to figure something out is to look for examples, so I found this example of a function call passing variables in go. In the example it is passing variables via registers, but it is also doing a push/pop thing. Hmmmm 🧐 Let’s copy it!

// assembly snippet
TEXT Ā·print2Vals(SB),$0
	PUSHQ BP
	MOVQ a+0(FP), BX
	MOVQ b+8(FP), AX
	MOVQ BX, 0(SP)
	MOVQ AX, 8(SP)
	CALL Ā·print2Ints(SB)
	POPQ	BP
	RET

// output
$ ./print 7
7 8

It worked!!!!! šŸŽ‰šŸŽ‰šŸŽ‰šŸŽ‰šŸŽ‰

I think the push and pop are doing some variable clean up. According to the assembly book:

Additionally, when the function is completed, the calling routine is responsible for clearing the arguments from the stack. Instead of doing a series of pop instructions, the stack pointer, rsp, is adjusted as necessary to clear the arguments off the stack.

šŸ—³ Passing Variables by Register

So earlier I saw the example in go that passed variables via registers.

Also I found this passage in the assembly book:

The standard calling convention specifies the usage of registers when making function calls. Specifically, some registers are expected to be preserved across a function call.

How hard could this be?

šŸ’» print2SixesWithRegisters (attempts to) pass the integer 6 twice to the print function.

// main.go snippet

func print2SixesWithRegisters()
func main(){ printSixesWithRegisters() }

// assembly snippet
TEXT Ā·print2SixesWithRegisters(SB),NOSPLIT,$0
	PUSHQ BP
	MOVQ $6, SI      // arg 2?
	MOVQ $6, DI      // arg 1?
	CALL Ā·print2Ints(SB)
	POPQ	BP
	RET

// output
$ ./print
824634322800 17347295

Um. That’s not what I intended. It just printed some junk.

I spent a lot of time tryign to debug this. Nothing seemed to work. I tried every register. Everything I read said that this should work.

But what if this only works for other assembly functions???

So I set up a test with two different functions, where the sender function passes variables via reigsters to the reciever function. Then the reciever function calls the print function and passes arguments by placing them on the stack (since I know that one works).

// assembly snippet
TEXT Ā·sender(SB),NOSPLIT,$0
	MOVQ $6, SI
	MOVQ $6, DX
	CALL Ā·reciever(SB)
	RET

TEXT Ā·reciever(SB),NOSPLIT,$0
	PUSHQ BP
	MOVQ	SP, BP
	MOVQ SI, 0(SP)
	MOVQ DX, 8(SP)
	CALL Ā·print2Ints(SB)
	MOVQ	BP, SP
	POPQ BP
	RET

// output
$ ./print
6 6
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x6 pc=0x6]

goroutine 1 [running]:
main.main()
	/Users/adowns/workspace/amelia/assembly-str/main.go:26 +0x4b

And it kinda worked! It did print out 6 twice… before getting a seg fault. I couldn’t figure out the seg fault before the end of the day, hopefully I’ll figure it out tomorrow!

šŸ”— And some frontend work!

I added some previous/next links to all of my posts. Thank god for jekyll tutorials. (But I made them pink and added some wonderful <hr>s.)