This short post aims to clarify a detail missing from the Golang assembly documentation, namely how to accept a slice argument to an assembly function.

The answer to this depends on the internal representation of slices, captured by the reflect.SliceHeader type.

// SliceHeader is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

Therefore under the hood a Go slice is a pointer with length and capacity values, and when passed to an assembly function these three fields will be available. For example, the following Go assembly sums a slice of uint64 values.

#include "textflag.h"

// func Sum(x []uint64) uint64
TEXT ┬ĚSum(SB), NOSPLIT, $0
    MOVQ x_ptr+0(FP), DI
    MOVQ x_len+8(FP), AX

    XORQ R8, R8
loop:
    CMPQ AX, $0
    JE done
    MOVQ (DI), R9
    ADDQ R9, R8
    ADDQ $8, DI
    DECQ AX
    JMP loop

done:
    MOVQ R8, ret+24(FP)
    RET

Note we ignore the capacity field of the slice at 16(FP), but we account for it when we write the return value to ret+24(FP).