Porting Go to NetBSD/arm64

About me:

  • coypu@sdf.org
  • maya@NetBSD.org


This talk will only refer to the main Go implementation
(the one in github.com/golang/go).

Several other implementations of Go exist.

Go - typical programming languages facts

  • Compiled language
  • Strongly typed
  • Self-hosted (written in Go)
  • Memory safe, garbage collected
  • Build logic built into the language
Go - unusual benefits
  • Lightweight, easy concurrency
    (Small auto-growing stack, goroutines)
  • Static binaries by default
  • Extremely easy to cross-build (another OS, another architecture)
    GOOS=plan9 GOARCH=arm

Typical programming language structure (C with GCC)

↓ C code ↓
Compiler (GCC)
↓ human-readable assembly ↓
Assembler (binutils)
↓ machine-readable assembly ↓
Linker (binutils)
executableglue code (ld.so)libckernel

Reasonable programming languages reuse existing tools:

  • Mangle into valid C names:
    (c++filt) std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >::data() const
  • No reason to write a new assembler/linker!

Go overview. Parts implemented by Go are in red

↓ Go code ↓
↓ human-readable assembly ↓
↓ machine-readable assembly ↓

"go libc"

Why not libc?


  • save all registers used in Go (not needed for direct syscall)
  • garbage collection accounting
  • change to a C-appropriate stack

How to begin?

~/g/src> env GOOS=netbsd GOARCH=arm64 bash ./make.bash
Building packages and commands for target, netbsd/arm64.
cmd/go: unsupported GOOS/GOARCH pair netbsd/arm64

Error strings come from somewhere.

List of things to implement in "Go libc"

lwp_create, lwp_tramp, osyield, lwp_park, lwp_unpark, lwp_self, exit, exitThread,
open, closefd, read, write, usleep, raise, raiseproc, setitimer, walltime, nanotime,
getcontext, sigprocmask, sigreturn_tramp, sigaction, sigfwd, sigtramp, mmap, munmap,
madvise, sigaltstack, settls, sysctl, kqueue, kevent, closeonexec.

Simple implementation: exit

// Exit the entire program (like C exit)
TEXT runtime·exit(SB),NOSPLIT,$-8
        MOVL    code+0(FP), DI          // arg 1 - exit status
        MOVL    $1, AX                  // sys_exit
        MOVL    $0xf1, 0xf1             // crash
#define SYS_exit                        1

// Exit the entire program (like C exit)
TEXT runtime·exit(SB),NOSPLIT,$-8
        MOVD    code+0(FP), R0          // arg 1 - exit status
        SVC     $SYS_exit
        MOVD    $0, R0                  // If we're still running,
        MOVD    R0, (R0)                // crash


/* syscall: "exit" ret: "void" args: "int" */
#define SYS_exit        1

Procedure Call Standard for ARM64

SPStack pointer
r30LRLink register
r29FPFrame pointer
r19..r28Callee-saved registers
r9..r15Temporary registers
r8Indirect result location register
r0..r7Parameter/result registers

objdump -d /lib/libc.so

  191810:       21 00 00 d4     svc     #0x1
  191814:       c0 03 5f d6     ret

Eventually wanted to have them all available...

auto-generate, easier with SYS_syscall

func Sync() (err error) {
        _, _, e1 := Syscall(SYS_SYNC, 0, 0, 0)
        if e1 != 0 {
                err = errnoErr(e1)

Implement Syscall3, Syscall6, Syscall9

// func RawSyscall(trap uintptr, a1, a2, a3 uintptr) (r1, r2, err uintptr)
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
	MOVD	trap+0(FP), R17	// syscall entry
	MOVD	a1+8(FP), R0
	MOVD	a2+16(FP), R1
	MOVD	a3+24(FP), R2
	SVC	$SYS_syscall
	BCC	ok
	MOVD	$-1, R1
	MOVD	R1, r1+32(FP)	// r1
	MOVD	ZR, r2+40(FP)	// r2
	MOVD	R0, err+48(FP)	// err
	MOVD	R0, r1+32(FP)	// r1
	MOVD	R1, r2+40(FP)	// r2
	MOVD	ZR, err+48(FP)	// err

Calling convention? not for syscalls

> ktruss -i ./hello
    34      1 hello    __sigprocmask14(0x3, 0, 0x1840c0) = 0
    34      1 hello    __clock_gettime50(0x3, 0xffffffffe8b8) = 0

Status and future work

  • Hello world works
  • Go itself runs a little ("go version")
  • compiler doesn't terminate
  • Signal handling is broken
  • thread-local storage is broken
  • ... unknown additional breakage
  • upstream!