Is it possible to include inline assembly in Go code?

Is it possible to include inline assembly in Go code?

This blog post shows compiling Go to a separate .s file and editing it, but not inline asm as part of a Go function like many C compilers support.


Solution 1:

There is no support for inline assembly, but you can link with code written in assembly through C, compiling with cgo and using import "C", like in gmp.go. You could alternatively write in a style of assembly that is directly compatible with Go, like in asm_linux_amd64.s, that requires that function names start with "·".

Or, you can use nasm and gccgo, my favorite way so far. (Note that Nasm does not seem to support functions starting with "·").

Here's a working "hello world" example:

hello.asm:

; Based on hello.asm from nasm

    SECTION .data       ; data section
msg:    db "Hello World",10 ; the string to print, 10=cr
len:    equ $-msg       ; "$" means "here"
                ; len is a value, not an address

    SECTION .text       ; code section

global go.main.hello        ; make label available to linker (Go)
go.main.hello:

    ; --- setup stack frame
    push rbp            ; save old base pointer
    mov rbp,rsp   ; use stack pointer as new base pointer

    ; --- print message
    mov edx,len     ; arg3, length of string to print
    mov ecx,msg     ; arg2, pointer to string
    mov ebx,1       ; arg1, where to write, screen
    mov eax,4       ; write sysout command to int 80 hex
    int 0x80        ; interrupt 80 hex, call kernel

    ; --- takedown stack frame
    mov rsp,rbp  ; use base pointer as new stack pointer
    pop rbp      ; get the old base pointer

    ; --- return
    mov rax,0       ; error code 0, normal, no error
    ret         ; return

main.go:

package main

func hello();

func main() {
    hello()
    hello()
}

And a handy Makefile:

main: main.go hello.o
    gccgo hello.o main.go -o main

hello.o: hello.asm
    nasm -f elf64 -o hello.o hello.asm

clean:
    rm -rf _obj *.o *~ *.6 *.gch a.out main

I call hello() twice in main.go, just to double check that hello() returns properly.

Note that calling interrupt 80h directly is not considered good style on Linux, and calling functions written is C is more "future proof". Also note that this is assembly specifically for 64-bit Linux, and is not platform-independent in any way, shape or form.

I know it's not a direct answer to your question, but it's the easiest route I know for using assembly with Go, in lack of inlining. If you really need inlining, it's possible to write a script that extracts inline assembly from source files and prepares it in a way that follows the pattern above. Close enough? :)

Quick example for Go, C and Nasm: gonasm.tgz

Update: Later versions of gccgo needs the -g flag and only "main.hello" is needed instead of "go.main.hello". Here is an updated example for Go, C and Yasm: goyasm.tgz

Solution 2:

There is no facility in the Go programming language to support in-line assembler language code, and there are no plans to do so. Go does support linking to routines written in assembler and C. There is an experimental feature which adds SWIG support to Go.

Solution 3:

No, you can't, but it is easy to provide an assembly implementation of just one function by using the go compiler. There is no need to use "Import C" to use assembly.

Take a look at an example from the math library:

http://golang.org/src/pkg/math/abs.go : The Abs function is declared in this go file. (There is also an implementation of abs in go in this file, but this isn't exported as it has a lower-case name.)

package math

// Abs returns the absolute value of x.
//
// Special cases are:
//  Abs(±Inf) = +Inf
//  Abs(NaN) = NaN
func Abs(x float64) float64

Then, in http://golang.org/src/pkg/math/abs_amd64.s , Abs is implemented for intel 64 bit in this file:

#include "textflag.h"

// func Abs(x float64) float64
TEXT ·Abs(SB),NOSPLIT,$0
    MOVQ   $(1<<63), BX
    MOVQ   BX, X0 // movsd $(-0.0), x0
    MOVSD  x+0(FP), X1
    ANDNPD X1, X0
    MOVSD  X0, ret+8(FP)
    RET

One problem with assembly functions like this is that they aren't inlined by the go compiler, so there's a limit to how much performance you can gain if you're calling a small function many times. The abs function is no longer implemented in assembly in the go library. I would say that the go compiler has improved such that with inlining it is faster for compiling the abs function without assembly in more recent go releases.

Solution 4:

The optimization pass in the standard Go compiler (that is: 8g+8l, not gccgo) is basically working with raw instructions in binary form. There is currently no way (it isn't implemented) for the compiler to distinguish compiler-generated assembly from user-supplied inline assembly code - this is the primary reason why the Go compiler doesn't allow inline assembly. In other words, the compiler has no support for inline assembly because of compiler architecture.

There is of course nothing in the Go language itself which would be preventing other Go language implementations (that is: other Go compilers) to have support for inline assembly. Inline assembly is a compiler-specific decision - it has little to do with the Go language itself.

In either case, inline assembly is unsafe because it cannot be checked for correctness by Go's type system. It seems it is better to implement any function which requires usage of inline assembly in a language like C, and call the C function from Go.