Fun with go assembler
03 Jul 2016
Go has a neat feature where a function can be written with no body, and as long as there is a related assembler file present that defines the function, it will be compiled into your final Go binary. Neat stuff.
I got a lot of useful info here, here, here, and here.
Interestingly, I found that the compiler would trip over the NOSPLIT
keyword:
$ go build # github.com/manniwood/playground/rotl ./rotl_amd64.s:2: illegal or missing addressing mode for symbol NOSPLIT asm: asm: assembly of ./rotl_amd64.s failed
so unlike the advice given, I omitted that keyword, and things seemed to go fine.
My objective was to play with the x86 ROL
instruction to do barrell rolls
of bytes, so I modified the ·add
example given at
https://goroutines.com/asm to see if I could use
the two-argument version of ROLQ
to do a left bit rotation on a 64-bit value.
The add example, from https://goroutines.com/asm, is:
TEXT ·add(SB),NOSPLIT,$0 MOVQ x+0(FP), BX MOVQ y+8(FP), BP ADDQ BP, BX MOVQ BX, ret+16(FP) RET
so I figured this would allow me to do a bit rotation of x y times:
TEXT ·rotl(SB), $0 MOVQ x+0(FP), BX MOVQ y+8(FP), BP ROLQ BP, BX MOVQ BX, ret+16(FP) RET
But no such luck:
$ go build # github.com/manniwood/playground/rotl asm: invalid instruction: 00010 (/home/mwood/go/src/github.com/manniwood/playground/rotl/rotl_amd64.s:5) ROLQ BP, BX asm: asm: assembly of ./rotl_amd64.s failed
A bit of Googling showed me that I had to use the special CX
register, which
is where ROLQ
looks for that argument:
TEXT ·rotl(SB), $0 MOVQ x+0(FP), BX // C is the counter register, used for args // for things like bit shift commands MOVQ y+8(FP), CX ROLQ CX, BX MOVQ BX, ret+16(FP) RET
And that compiled!
Here are the two files I used:
main.go rotl_amd64.s
And here are their contents:
// main.go package main import "fmt" // notice no function body! func rotl(x, y int64) int64 func main() { fmt.Println(rotl(2, 3)) }
// rotl_amd64.s TEXT ·rotl(SB), $0 MOVQ x+0(FP), BX // C is the counter register, used for args // for things like bit shift commands MOVQ y+8(FP), CX ROLQ CX, BX MOVQ BX, ret+16(FP) RET
A few notes on names.
I tried changing the name of argument y
to argument z
in the assembler code, and Go happily still built the binary, so it looks like the
argument names are in the assembly file for the benefit of the reader, not the compiler.
I renamed the file rotl_amd64.s to foo_amd64.s and Go still happily compiled it.
I renamed the file rotl_amd64.s to rotl_foo.s and Go still happily compiled it.
But I wouldn't recommend devaiting from the naming convention set out in examples.
Final note: altough it's cool that one can use assembler to leverage x86's bit rotate
instruction (nicer than having to or
together a
right shift and a left shift in Go code to mimic the rotation), the overhead of the function call
is likely a big hit. However, for longer snippets of code where one might want to speed things up,
being able to so easily use assembler is pretty spiffy.
Finally, as has been pointed out elsewhere, the easy way to look at the assembler Go will generate from a .go source file is to do the following:
$ build -gcflags -S file.go