Quick and Easy GoAsm Code Snippets

Quick and Easy GoAsm Code Snippets

GoAsm is a lightweight assembly-like language for writing efficient, low-level routines that integrate smoothly with Go programs. This article gives several short, practical snippets you can copy, run, and adapt. Each example includes a brief explanation and a complete snippet that compiles with a typical Go + GoAsm setup.

Prerequisites

  • Go toolchain installed (go1.20+ recommended).
  • goasm-compatible assembler (the standard Go assembler supports Plan 9 syntax; this article uses that syntax).
  • Basic familiarity with Go calling conventions and Plan 9 assembly registers (RAX, RBX, etc., on x86-64).

1) Hello from GoAsm — calling from Go

A minimal example showing how Go calls an assembly function that returns a string pointer and length.

Assembly (hello.s):

asm

TEXT ·Hello(SB),NOSPLIT,$0 // Return pointer in AX, length in DX (Go uses two-word string header)

MOVQ $msg+0(SB), AX MOVQ $lenmsg, DX RET 

DATA msg+0(SB)/8, \(0 GLOBL msg(SB), RODATA, \)8 DATA msg(SB)/1, “Hi from GoAsm!” GLOBL lenmsg(SB), RODATA, \(8 DATA lenmsg+0(SB)/8, \)13

Go (main.go):

go

package main import “fmt” //go:noescape func Hello() (p byte, n int) func main() { p, n := Hello() s := string(([1 << 30]byte)(p)[:n:n]) fmt.Println(s) }

Explanation: The assembly sets up a pointer and length for a Go string and returns them. The Go wrapper reconstructs the string safely.

2) Simple integer add

An assembly function that adds two int64 values and returns the sum.

Assembly (add.s):

asm

TEXT ·Add(SB),NOSPLIT,$0 MOVQ a+0(FP), AX

ADDQ b+8(FP), AX MOVQ AX, ret+16(FP) RET 

GLOBL·Add(SB), NOSPLIT

Go (add.go):

go

package main import “fmt” //go:noescape func Add(a, b int64) int64 func main() { fmt.Println(Add(7, 5)) // 12 }

Explanation: Parameters are loaded from the stack frame, summed, and stored to the return slot.

3) Looping memory copy

A small memcpy implementation that copies n bytes from src to dst.

Assembly (memcpy.s):

asm

TEXT ·Memcpy(SB),NOSPLIT,$0 MOVQ dst+0(FP), DI

MOVQ src+8(FP), SI MOVQ n+16(FP), CX TESTQ CX, CX JE done 

loop:

MOVBL (SI), R8B MOVBL R8B, (DI) INCQ SI INCQ DI DECQ CX JNZ loop 

done:

RET 

Go (memcpy.go):

go

package main //go:noescape func Memcpy(dst, src *byte, n uint64) func main() {}

Note: For production use prefer Go’s copy() — this is instructional.

4) Compare two byte slices (returns 0 equal, <0 a0 a>b)

Assembly (cmp.s):

asm

TEXT ·Compare(SB),NOSPLIT,$0 MOVQ a+0(FP), SI

MOVQ b+8(FP), DI MOVQ n+16(FP), CX TESTQ CX, CX JE equal 

loop:

MOVBL (SI), AL MOVBL (DI), BL CMPB AL, BL JNE diff INCQ SI INCQ DI DECQ CX JNZ loop 

equal:

MOVQ $0, ret+24(FP) RET 

diff:

MOVQ $1, ret+24(FP) JG end MOVQ $-1, ret+24(FP) 

end:

RET 

Go (cmp.go):

go

package main //go:noescape func Compare(a, b *byte, n uint64) int func main() {}

5) Fast popcount (byte)

Count set bits in a single byte.

Assembly (popcnt.s):

asm

TEXT ·PopCount8(SB),NOSPLIT,$0 MOVB x+0(FP), AL

XORL CX, CX 

count:

TESTB AL, AL JE done ANDL $1, AL ADDL AL, CL SHRB $1, AL JMP count 

done:

MOVB CL, ret+8(FP) RET 

Go (popcnt.go):

go

package main //go:noescape func PopCount8(x byte) byte func main() {}

Tips and Best Practices

  • Use //go:noescape for functions that don’t let Go pointers escape.
  • Prefer NOSPLIT on tiny functions when stack growth is unnecessary.
  • Benchmark against Go equivalents; Go’s runtime and compiler provide excellent optimizations.
  • Keep calling conventions and frame layout consistent with Go’s ABI for your Go version.

Conclusion These snippets show common patterns: returning values, manipulating memory, looping, and byte ops. Use them as learning tools, then benchmark and adapt for performance-critical hotspots.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *