Some pointers on how to use assembly with Go?

3,697 views
Skip to first unread message

Job van der Zwan

unread,
Jul 24, 2012, 7:07:35 PM7/24/12
to golan...@googlegroups.com
Remember the discussion about integer operators with multiple return values?

"Integer Division and Modulo as a single operator?"

The result was the proposal to implement them as functions, though they would have to be written in assembly to actually be useful, and for every basic type as well. Since nobody has done that I might as well give it a try. The plan is to add these functions to the integer math library I'm writing. 

The problem is that I can't find clear documentation on how Go handles assembly (and have close to no experience with assembly[1], but I figured this is a good simple way to start). I've tried to make sense of the manual for the Plan 9 assember, and found myself some reference sheets for i386 (in this context AMD64 would be almost the same but using MOVQ instead of MOVL, correct?) and ARM instructions. I've looked at source code in an attempt to understand the syntax, for example:


So, am I correct in understanding that in order to make a custom assembly file, one:
  1. Makes a regular go file with the function one want to rewrite in assembly
  2. Also makes a file with the same name plus an underscore and the relevent architecture, and the .s filetype
  3. Follows the conventions explained in the Plan 9 assembler document, but with a · (a dot) in front of the name of the function? (If so: why?)
Thanks in advance!

[1] Ok, just a teensy bit of experience, but unless there's a Z80 port of Go that I'm not aware of that's going to be useless in this context.

Job van der Zwan

unread,
Jul 24, 2012, 7:11:57 PM7/24/12
to golan...@googlegroups.com
So, for example a naive implementation of Mul for int32:

1 package i32
2
3 func Mul(a, b int32) (p, u int32) {
4 v := int64(a)*int64(b)
5 p = int32(v&0xFFFFFFFF)
6 u = int32(v>>32)
7 return
8 }


Which on a 386 system would result in:
 
$go build -gcflags "-S " mul.go
# command-line-arguments

--- prog list "Mul" ---
0000 (mul.go:3) TEXT    Mul+0(SB),$0-16
0001 (mul.go:3) MOVL    $0,AX
0002 (mul.go:4) MOVL    a+0(FP),AX
0003 (mul.go:4) CDQ     ,
0004 (mul.go:4) MOVL    AX,DI
0005 (mul.go:4) MOVL    DX,SI
0006 (mul.go:4) MOVL    b+4(FP),AX
0007 (mul.go:4) CDQ     ,
0008 (mul.go:4) MOVL    AX,CX
0009 (mul.go:4) MOVL    DX,BX
0010 (mul.go:4) MOVL    DI,AX
0011 (mul.go:4) MOVL    SI,DX
0012 (mul.go:4) MOVL    SI,BP
0013 (mul.go:4) ORL     BX,BP
0014 (mul.go:4) JNE     ,17
0015 (mul.go:4) MULL    CX,
0016 (mul.go:4) JMP     ,24
0017 (mul.go:4) IMULL   CX,DX
0018 (mul.go:4) MOVL    DI,BP
0019 (mul.go:4) IMULL   BX,BP
0020 (mul.go:4) ADDL    DX,BP
0021 (mul.go:4) MOVL    CX,DX
0022 (mul.go:4) MULL    DX,
0023 (mul.go:4) ADDL    BP,DX
0024 (mul.go:5) MOVL    AX,p+8(FP)
0025 (mul.go:6) MOVL    DX,AX
0026 (mul.go:6) MOVL    DX,u+12(FP)
0027 (mul.go:7) RET     ,
*/

If I then make a mul_386.s file, with simplified instructions like so:

TEXT ·Mul(SB),$0-16
MOVL    a+0(FP),AX
MOVL    b+4(FP),DX
IMULL   DX
MOVL    AX,p+8(FP)
MOVL    DX,u+12(FP)
RET     , 

Would that work? As in: compile and be included in the binary instead of the compiled Go code? Aside from the fact that I'm not quite sure if the code does what I think it does...

brainman

unread,
Jul 24, 2012, 8:16:10 PM7/24/12
to golan...@googlegroups.com
On Wednesday, 25 July 2012 09:11:57 UTC+10, Job van der Zwan wrote:
Which on a 386 system would result in:
 
$go build -gcflags "-S " mul.go
# command-line-arguments

--- prog list "Mul" ---
0000 (mul.go:3) TEXT    Mul+0(SB),$0-16

You need to be aware, that Go linker modifies your code too. For example, it will insert some more code in you function to support "segmented stacks". So, if you want to be sure that you are seeing final product, you must look at linker output instead. 8l has "-a" flag that will output asm.

Alex

Job van der Zwan

unread,
Jul 25, 2012, 7:15:01 AM7/25/12
to golan...@googlegroups.com
Ok, that's good to know. But that doesn't really really explains how to inject custom assembly files in the Go compiler toolchain. Could you elaborate a bit?

brainman

unread,
Jul 25, 2012, 7:55:10 PM7/25/12
to golan...@googlegroups.com
On Wednesday, 25 July 2012 21:15:01 UTC+10, Job van der Zwan wrote:
... explains how to inject custom assembly files in the Go compiler toolchain. Could you elaborate a bit?


Here is simple example of 386 asm:

# ls -l
total 8
-rw-r--r-- 1 root root 249 Jul 26 09:47 add.go
-rw-r--r-- 1 root root 109 Jul 26 09:45 add_386.s
# cat add.go
package main

import (
        "fmt"
)

/*

assembler is equivalent of

func add(a, b int32) (result int32) {
        return a+b
}

*/

func add(a, b int32) (result int32) // implemented in add_386.s

func main() {
        fmt.Printf("%d + %d = %d\n", 2, 2, add(2, 2))
}
# cat add_386.s

TEXT ·add(SB),7,$0-12
        MOVL    a+0(FP),AX
        MOVL    AX,BX
        ADDL    b+4(FP),BX
        MOVL    BX,result+8(FP)
        RET
# go build -o a
# ./a
2 + 2 = 4
#

If something is not clear, just ask.

Alex

Job van der Zwan

unread,
Jul 25, 2012, 9:02:59 PM7/25/12
to golan...@googlegroups.com
On Thursday, 26 July 2012 01:55:10 UTC+2, brainman wrote:
If something is not clear, just ask.

Alex

First of all, thanks for taking the time to give an example. Now for the questions. First, are these three points correct?

On Wednesday, 25 July 2012 01:07:35 UTC+2, Job van der Zwan wrote:
So, am I correct in understanding that in order to make a custom assembly file, one:
  1. Makes a regular go file with the function one want to rewrite in assembly
  2. Also makes a file with the same name plus an underscore and the relevent architecture, and the .s filetype
  3. Follows the conventions explained in the Plan 9 assembler document, but with a · (a dot) in front of the name of the function? (If so: why?)
And some more questions:

Do I need an empty function declaration? If so, how do I make a "fallback" for architectures that are supported by the compiler but don't have a special ASM files written for them. Minux also sent me this example library to take a look at:
.. and the source there seems to suggest that the answer is "yes, and you can make a fallback by making an identical non-exported version of the function". Correct?

What's with the capital å before the function name (or the dot) in your add example? It's not there in the ASM sources in the library linked above. Typo?

Finally, does using custom made ASM play nicely with inlining? Because if it doesn't it kind of defeats the purpose in these specific cases...

minux

unread,
Jul 25, 2012, 9:11:59 PM7/25/12
to Job van der Zwan, golan...@googlegroups.com
On Wed, Jul 25, 2012 at 6:02 PM, Job van der Zwan <j.l.van...@gmail.com> wrote:
On Wednesday, 25 July 2012 01:07:35 UTC+2, Job van der Zwan wrote:
So, am I correct in understanding that in order to make a custom assembly file, one:
  1. Makes a regular go file with the function one want to rewrite in assembly
  2. Also makes a file with the same name plus an underscore and the relevent architecture, and the .s filetype
  3. Follows the conventions explained in the Plan 9 assembler document, but with a · (a dot) in front of the name of the function? (If so: why?)
1 would be "make a go declaration of the function implemented in assembly"
2 the file name are not constrained, anything_$GOARCH.s would work, but it's very convenient to
follow your rule of naming. 
3 correct. the linker will automatically rewrite the function name to be 'pkg.func'.
And some more questions:

Do I need an empty function declaration? If so, how do I make a "fallback" for architectures that are supported by the compiler but don't have a special ASM files written for them. Minux also sent me this example library to take a look at:
.. and the source there seems to suggest that the answer is "yes, and you can make a fallback by making an identical non-exported version of the function". Correct?

What's with the capital å before the function name (or the dot) in your add example? It's not there in the ASM sources in the library linked above. Typo?
i think it's a typo. 

Finally, does using custom made ASM play nicely with inlining? Because if it doesn't it kind of defeats the purpose in these specific cases...
it is not required. in fact, this works simply because of the go tool will selectively compile files:

Job van der Zwan

unread,
Jul 25, 2012, 9:33:33 PM7/25/12
to golan...@googlegroups.com, Job van der Zwan
On Thursday, 26 July 2012 03:11:59 UTC+2, minux wrote:
Finally, does using custom made ASM play nicely with inlining? Because if it doesn't it kind of defeats the purpose in these specific cases...
it is not required. in fact, this works simply because of the go tool will selectively compile files:

I'm probably missing something, but I don't see the connection between building constraints and inlining? The latter isn't even mentioned in that document at all.

Ken Thompson has said elsewhere on this board that "inline" is this decades "register", and he has a point, but in this case the whole point of using assembly is exposing basic CPU-level functionality at the higher level with minimal overhead. If these minimal functions would require a function call every time they are used, it would partially defeat that purpose, no? Even though it would probably still be faster than output of plain Go code.

Job van der Zwan

unread,
Jul 25, 2012, 9:34:25 PM7/25/12
to golan...@googlegroups.com, Job van der Zwan
... and thanks for your answers so far and sending me that link, of course!

Nigel Tao

unread,
Jul 25, 2012, 9:34:54 PM7/25/12
to Job van der Zwan, golan...@googlegroups.com
On 26 July 2012 11:02, Job van der Zwan <j.l.van...@gmail.com> wrote:
> Finally, does using custom made ASM play nicely with inlining? Because if it
> doesn't it kind of defeats the purpose in these specific cases...

IIUC, asm isn't inlined. Inlining is done at the AST level, before the
compiler generates arch-specific code.

Nigel Tao

unread,
Jul 25, 2012, 9:35:26 PM7/25/12
to Job van der Zwan, golan...@googlegroups.com
I should qualify that this is with the gc compiler. I don't know about gccgo.

minux

unread,
Jul 25, 2012, 9:39:42 PM7/25/12
to Job van der Zwan, golan...@googlegroups.com
On Wed, Jul 25, 2012 at 6:33 PM, Job van der Zwan <j.l.van...@gmail.com> wrote:
On Thursday, 26 July 2012 03:11:59 UTC+2, minux wrote:
Finally, does using custom made ASM play nicely with inlining? Because if it doesn't it kind of defeats the purpose in these specific cases...
it is not required. in fact, this works simply because of the go tool will selectively compile files:

I'm probably missing something, but I don't see the connection between building constraints and inlining? The latter isn't even mentioned in that document at all.
i forgot to answer that question.
but i think both gc and gccgo (even with lto) can't inline (external) assembly functions.

the build constrain is to answer your question about fallback implementation.

brainman

unread,
Jul 26, 2012, 12:37:42 AM7/26/12
to golan...@googlegroups.com
On Thursday, 26 July 2012 11:02:59 UTC+10, Job van der Zwan wrote:

> So, am I correct in understanding that in order to make a custom assembly file, one:

> Makes a regular go file with the function one want to rewrite in assembly
> Also makes a file with the same name plus an underscore and the relevent architecture, and the .s filetype
> Follows the conventions explained in the Plan 9 assembler document, but with a · (a dot) in front of the name of the function? (If so: why?)

If you want function written in asm, then write it in asm, but you must put it in a .s file, not .go file. It does not matter what .s file name is, but it does matter what it ends as. As is described in http://golang.org/pkg/go/build/#Build_Constraints, if your asm file is for 386, it must have _386.s at the end.

Also, all functions inside Go executable (including the ones written in C and asm) share the same namespace, therefore functions from package alex will have prefix alex., so, for example, add function in package alex will have global name of alex.add (you can use "go tool nm <an_go_executable>" to see that). A · (MIDDLE DOT) is used in C and asm to help this cause, so if you have ·add in asm in package alex, it gets renamed into alex.add (I COULD BE WRONG ABOUT DETAILS HERE, but Go team will correct me):

# go build -o a | go tool nm a | grep main.add
 8048d30 T main.add

otherwise your asm add function might clash with other add functions inside you executable.

> Do I need an empty function declaration? ...

You, obviously, do:

# go build
# t
./add.go:20: undefined: add
#

> If so, how do I make a "fallback" for architectures that are supported by the compiler but don't have a special ASM files written for them. ...

Put whatever code you want for whatever arch in a file that fits file name as per http://golang.org/pkg/go/build/#Build_Constraints. For example, if your implementation is asm for 386 and Go for amd64, you could put your 386 asm code into aaa_386.s and adm64 Go code into bbb_adm64.go.

> ... Minux also sent me this example ... Correct?
   
I do not know. I didn't look at https://github.com/ziutek/blas.
   
> What's with the capital å before the function name (or the dot) in your add example? ...

My telnet program is ascii only :-(. It is, in fact, a · (MIDDLE DOT). Sorry.

> ..., does using custom made ASM play nicely with inlining? ...

I do not understand your question. Go language does not allow any "inlining" inside of Go code.

Alex

Nigel Tao

unread,
Jul 26, 2012, 1:05:29 AM7/26/12
to brainman, golan...@googlegroups.com
On 26 July 2012 14:37, brainman <alex.b...@gmail.com> wrote:
>> ..., does using custom made ASM play nicely with inlining? ...
>
> I do not understand your question. Go language does not allow any "inlining"
> inside of Go code.

The gc compilers do inline short (Go, not asm) functions. This is a
compiler optimization, not an explicit language feature.

$ go tool 6g -h | grep inl
-l disable inlining

Jingcheng Zhang

unread,
Jul 26, 2012, 1:12:45 AM7/26/12
to Nigel Tao, brainman, golan...@googlegroups.com
By the way, what's wrong with my codes when building a C file and a Go file?

D:\code\mine\src\demo>type lib.c
#include "runtime.h"

int32
main·F(int32 x, int32 y) {
return x + y;
}

D:\code\mine\src\demo>type main.go
package main

import (
"fmt"
)

func F(x int32, y int32) int32

func main() {
z := F(3, 4)
fmt.Printf("%d\n", z)
}

D:\code\mine\src\demo>go build -o main.exe

D:\code\mine\src\demo>main.exe
291897416

Thanks!
--
Best regards,
Jingcheng Zhang
Beijing, P.R.China

brainman

unread,
Jul 26, 2012, 1:20:18 AM7/26/12
to golan...@googlegroups.com, Nigel Tao, brainman
On Thursday, 26 July 2012 15:12:45 UTC+10, Jingcheng Zhang wrote:

> By the way, what's wrong with my codes when building a C file and a Go file?

I THINK ...

Go function with signature:

func F(x int32, y int32) (ret int32)

layout all input (a and y) and output (ret) parameters on stack.

C function


int32 main·F(int32 x, int32 y) {

compiled with Go 8c compiler expects x and y to be on stack, but return value is returned in AX.

I THINK ....

Print asm for your executable to see for yourself.

Alex

Jingcheng Zhang

unread,
Jul 26, 2012, 1:34:26 AM7/26/12
to brainman, golan...@googlegroups.com, Nigel Tao
The result follows. So.. How can I call a C function in Go correctly?

D:\code\mine\src\demo>go tool 8c -I%GOROOT%\src\pkg\runtime -S lib.c
(lib.c:4) TEXT main.F+0(SB),(gok(71))
(lib.c:5) MOVL x+0(FP),AX
(lib.c:5) ADDL y+4(FP),AX
(lib.c:5) RET ,
......

D:\code\mine\src\demo>go tool 8g -S main.go

--- prog list "main" ---
0000 (main.go:9) TEXT main+0(SB),$64-0
0001 (main.go:10) MOVL $3,(SP)
0002 (main.go:10) MOVL $4,4(SP)
0003 (main.go:10) CALL ,F+0(SB)
0004 (main.go:10) MOVL 8(SP),CX
0005 (main.go:11) MOVL $0,AX
0006 (main.go:11) LEAL autotmp_0003+-20(SP),DI
0007 (main.go:11) STOSL ,
......

minux

unread,
Jul 26, 2012, 1:34:28 AM7/26/12
to Jingcheng Zhang, Nigel Tao, brainman, golan...@googlegroups.com
On Wed, Jul 25, 2012 at 10:12 PM, Jingcheng Zhang <dio...@gmail.com> wrote:
By the way, what's wrong with my codes when building a C file and a Go file?

D:\code\mine\src\demo>type lib.c
#include "runtime.h"

int32
main·F(int32 x, int32 y) {
    return x + y;
}
this should be:
int32
main·F(int32 x, int32 y, int32 r) {
    r = x + y;
    FLUSH(&r);

Jingcheng Zhang

unread,
Jul 26, 2012, 1:42:23 AM7/26/12
to minux, Nigel Tao, brainman, golan...@googlegroups.com
Thanks, it works.

D:\code\mine\src\demo>type lib.c
#include "runtime.h"

void
main·F(int32 x, int32 y, int32 r) {
r = x + y;
FLUSH(&r);
}

D:\code\mine\src\demo>type main.go
package main

import (
"fmt"
)

func F(x int32, y int32) int32

func main() {
z := F(3, 4)
fmt.Printf("%d\n", z)
}

D:\code\mine\src\demo>go build -o main.exe

D:\code\mine\src\demo>main
7

brainman

unread,
Jul 26, 2012, 1:42:50 AM7/26/12
to golan...@googlegroups.com, brainman, Nigel Tao
On Thursday, 26 July 2012 15:34:26 UTC+10, Jingcheng Zhang wrote:

> The result follows. So.. How can I call a C function in Go correctly?

Use cgo :-) Or do what minux suggested, but it is not supported.

Alex

Jingcheng Zhang

unread,
Jul 26, 2012, 1:48:12 AM7/26/12
to brainman, golan...@googlegroups.com, Nigel Tao
Thanks, why FLUSH is not supported?

brainman

unread,
Jul 26, 2012, 1:50:53 AM7/26/12
to golan...@googlegroups.com, brainman, Nigel Tao
On Thursday, 26 July 2012 15:48:12 UTC+10, Jingcheng Zhang wrote:
Thanks, why FLUSH is not supported?


This is all guess work on my part. But, I suspect, it is all internals of Go runtime. Go team is free to change them any time if need be.

Alex

Jingcheng Zhang

unread,
Jul 26, 2012, 2:04:33 AM7/26/12
to brainman, golan...@googlegroups.com, Nigel Tao
Got it. Thanks very much.

Job van der Zwan

unread,
Jul 26, 2012, 7:05:55 AM7/26/12
to golan...@googlegroups.com, brainman, Nigel Tao
Woah, this topic exploded overnight. Thanks for all the feedback guys! I think I'll manage for a while :)

PS:
> My telnet program is ascii only :-(. It is, in fact, a · (MIDDLE DOT). Sorry.
You're accessing Google Groups over telnet? Nice :D
Reply all
Reply to author
Forward
0 new messages