gorse-io/goat

Go assembly transpiler for C programming language support amd64, arm64, riscv64, loong64, s390x and ppc64le

Go

120

67 commits

updated Sep 4, 2026

See the code

README

GoAT

test GitHub release (with filter)

Go assembly transpiler for C programming languages. It help to utilize optimization from C compiler in Go projects. For example, generate SIMD vectorized functions for Go (refer to How to Use AVX512 in Golang).

Prerequisites

The following packages are required:

  • Ubuntu for AMD64:
sudo apt update
sudo apt install clang libc6-dev-i386
  • Ubuntu for ARM64:
sudo apt update
sudo apt install clang
  • macOS:
brew install llvm binutils

# Override the default clang and objdump to use Homebrew's version
export PATH="/opt/homebrew/opt/llvm/bin:$PATH"
export PATH="/opt/homebrew/opt/binutils/bin:$PATH"
  • Windows:

Install LLVM and MinGW from the official website or use Chocolatey package manager:

choco install llvm mingw

Install

Install the latest version:

go install github.com/gorse-io/goat@latest

Or install the latest development version:

go install github.com/gorse-io/goat@main

Usage

  goat source [-o output_directory] [flags]

Flags:
  -e, --extra-option strings     extra option for clang
  -h, --help                     help for goat
  -m, --machine-option strings   machine option for clang
  -O, --optimize-level int       optimization level for clang
  -o, --output string            output directory of generated files
  -t, --target string            target architecture, using Go GOARCH names (default current GOARCH)
  -v, --verbose                  if set, increase verbosity level

Example

Suppose you have a C function that adds two arrays of floats in src/add.c:

void add(float *a, float *b, float *result, long n) {
    for (long i = 0; i < n; i++) {
        result[i] = a[i] + b[i];
    }
}

You can use GoAT to transpile this C function to Go assembly code with the following command:

goat src/add.c -o . -O3 -mavx

[!WARNING] The output directory must be different from the source directory, otherwise the intermediate files will break the compilation.

Two files will be generated in the output directory:

  • Go function definition file add.go:
//go:build !noasm && amd64
// Code generated by GoAT. DO NOT EDIT.
// versions:
// 	clang   18.1.3 (1ubuntu1)
// 	objdump 2.42
// flags: -mavx -O0
// source: example/src/add.c

package example

import "unsafe"

//go:noescape
func add(a, b, result unsafe.Pointer, n int64)
  • Go assembly file add.s:
//go:build !noasm && amd64
// Code generated by GoAT. DO NOT EDIT.
// versions:
// 	clang   18.1.3 (1ubuntu1)
// 	objdump 2.42
// flags: -mavx -O0
// source: example/src/add.c

TEXT ·add(SB), $0-32
	MOVQ a+0(FP), DI
	MOVQ b+8(FP), SI
	MOVQ result+16(FP), DX
	MOVQ n+24(FP), CX
	BYTE $0x55                           // pushq	%rbp
	WORD $0x8948; BYTE $0xe5             // movq	%rsp, %rbp
	LONG $0xf8e48348                     // andq	$-8, %rsp
	LONG $0x30ec8348                     // subq	$48, %rsp
	LONG $0x247c8948; BYTE $0x28         // movq	%rdi, 40(%rsp)
	LONG $0x24748948; BYTE $0x20         // movq	%rsi, 32(%rsp)
	LONG $0x24548948; BYTE $0x18         // movq	%rdx, 24(%rsp)
	LONG $0x244c8948; BYTE $0x10         // movq	%rcx, 16(%rsp)
	QUAD $0x000000082444c748; BYTE $0x00 // movq	$0, 8(%rsp)

LBB0_1:
	LONG $0x24448b48; BYTE $0x08 // movq	8(%rsp), %rax
	LONG $0x24443b48; BYTE $0x10 // cmpq	16(%rsp), %rax
	JGE  LBB0_4
	LONG $0x24448b48; BYTE $0x28 // movq	40(%rsp), %rax
	LONG $0x244c8b48; BYTE $0x08 // movq	8(%rsp), %rcx
	LONG $0x0410fac5; BYTE $0x88 // vmovss	(%rax,%rcx,4), %xmm0            # xmm0 = mem[0],zero,zero,zero
	LONG $0x24448b48; BYTE $0x20 // movq	32(%rsp), %rax
	LONG $0x244c8b48; BYTE $0x08 // movq	8(%rsp), %rcx
	LONG $0x0458fac5; BYTE $0x88 // vaddss	(%rax,%rcx,4), %xmm0, %xmm0
	LONG $0x24448b48; BYTE $0x18 // movq	24(%rsp), %rax
	LONG $0x244c8b48; BYTE $0x08 // movq	8(%rsp), %rcx
	LONG $0x0411fac5; BYTE $0x88 // vmovss	%xmm0, (%rax,%rcx,4)
	LONG $0x24448b48; BYTE $0x08 // movq	8(%rsp), %rax
	LONG $0x01c08348             // addq	$1, %rax
	LONG $0x24448948; BYTE $0x08 // movq	%rax, 8(%rsp)
	JMP  LBB0_1

LBB0_4:
	WORD $0x8948; BYTE $0xec // movq	%rbp, %rsp
	BYTE $0x5d               // popq	%rbp
	RET

Finally, the add function can be used by:

func Add(a, b, c []float32) {
	if len(a) ! = len(b) || len(a) ! = len(c) {
		panic("floats: slice lengths do not match")
	}
	add(unsafe.Pointer(&a[0]), unsafe.Pointer(&b[0]), unsafe.Pointer(&c[0]), int64(len(a)))
}

Limitations

  • No call statements except for inline functions.
  • Arguments must be int64_t, long, float, double, _Bool or pointer.
  • Potentially BUGGY code generation.

Acknowledgments

Contributors

zhenghaoz

39 commits

zhangzhenghao

14 commits

asdine

6 commits

asimshankar

2 commits

gorse-io/goat

Go assembly transpiler for C programming language support amd64, arm64, riscv64, loong64, s390x and ppc64le

Go

120

67 commits

updated Sep 4, 2026

See the code

README

GoAT

test GitHub release (with filter)

Go assembly transpiler for C programming languages. It help to utilize optimization from C compiler in Go projects. For example, generate SIMD vectorized functions for Go (refer to How to Use AVX512 in Golang).

Prerequisites

The following packages are required:

  • Ubuntu for AMD64:
sudo apt update
sudo apt install clang libc6-dev-i386
  • Ubuntu for ARM64:
sudo apt update
sudo apt install clang
  • macOS:
brew install llvm binutils

# Override the default clang and objdump to use Homebrew's version
export PATH="/opt/homebrew/opt/llvm/bin:$PATH"
export PATH="/opt/homebrew/opt/binutils/bin:$PATH"
  • Windows:

Install LLVM and MinGW from the official website or use Chocolatey package manager:

choco install llvm mingw

Install

Install the latest version:

go install github.com/gorse-io/goat@latest

Or install the latest development version:

go install github.com/gorse-io/goat@main

Usage

  goat source [-o output_directory] [flags]

Flags:
  -e, --extra-option strings     extra option for clang
  -h, --help                     help for goat
  -m, --machine-option strings   machine option for clang
  -O, --optimize-level int       optimization level for clang
  -o, --output string            output directory of generated files
  -t, --target string            target architecture, using Go GOARCH names (default current GOARCH)
  -v, --verbose                  if set, increase verbosity level

Example

Suppose you have a C function that adds two arrays of floats in src/add.c:

void add(float *a, float *b, float *result, long n) {
    for (long i = 0; i < n; i++) {
        result[i] = a[i] + b[i];
    }
}

You can use GoAT to transpile this C function to Go assembly code with the following command:

goat src/add.c -o . -O3 -mavx

[!WARNING] The output directory must be different from the source directory, otherwise the intermediate files will break the compilation.

Two files will be generated in the output directory:

  • Go function definition file add.go:
//go:build !noasm && amd64
// Code generated by GoAT. DO NOT EDIT.
// versions:
// 	clang   18.1.3 (1ubuntu1)
// 	objdump 2.42
// flags: -mavx -O0
// source: example/src/add.c

package example

import "unsafe"

//go:noescape
func add(a, b, result unsafe.Pointer, n int64)
  • Go assembly file add.s:
//go:build !noasm && amd64
// Code generated by GoAT. DO NOT EDIT.
// versions:
// 	clang   18.1.3 (1ubuntu1)
// 	objdump 2.42
// flags: -mavx -O0
// source: example/src/add.c

TEXT ·add(SB), $0-32
	MOVQ a+0(FP), DI
	MOVQ b+8(FP), SI
	MOVQ result+16(FP), DX
	MOVQ n+24(FP), CX
	BYTE $0x55                           // pushq	%rbp
	WORD $0x8948; BYTE $0xe5             // movq	%rsp, %rbp
	LONG $0xf8e48348                     // andq	$-8, %rsp
	LONG $0x30ec8348                     // subq	$48, %rsp
	LONG $0x247c8948; BYTE $0x28         // movq	%rdi, 40(%rsp)
	LONG $0x24748948; BYTE $0x20         // movq	%rsi, 32(%rsp)
	LONG $0x24548948; BYTE $0x18         // movq	%rdx, 24(%rsp)
	LONG $0x244c8948; BYTE $0x10         // movq	%rcx, 16(%rsp)
	QUAD $0x000000082444c748; BYTE $0x00 // movq	$0, 8(%rsp)

LBB0_1:
	LONG $0x24448b48; BYTE $0x08 // movq	8(%rsp), %rax
	LONG $0x24443b48; BYTE $0x10 // cmpq	16(%rsp), %rax
	JGE  LBB0_4
	LONG $0x24448b48; BYTE $0x28 // movq	40(%rsp), %rax
	LONG $0x244c8b48; BYTE $0x08 // movq	8(%rsp), %rcx
	LONG $0x0410fac5; BYTE $0x88 // vmovss	(%rax,%rcx,4), %xmm0            # xmm0 = mem[0],zero,zero,zero
	LONG $0x24448b48; BYTE $0x20 // movq	32(%rsp), %rax
	LONG $0x244c8b48; BYTE $0x08 // movq	8(%rsp), %rcx
	LONG $0x0458fac5; BYTE $0x88 // vaddss	(%rax,%rcx,4), %xmm0, %xmm0
	LONG $0x24448b48; BYTE $0x18 // movq	24(%rsp), %rax
	LONG $0x244c8b48; BYTE $0x08 // movq	8(%rsp), %rcx
	LONG $0x0411fac5; BYTE $0x88 // vmovss	%xmm0, (%rax,%rcx,4)
	LONG $0x24448b48; BYTE $0x08 // movq	8(%rsp), %rax
	LONG $0x01c08348             // addq	$1, %rax
	LONG $0x24448948; BYTE $0x08 // movq	%rax, 8(%rsp)
	JMP  LBB0_1

LBB0_4:
	WORD $0x8948; BYTE $0xec // movq	%rbp, %rsp
	BYTE $0x5d               // popq	%rbp
	RET

Finally, the add function can be used by:

func Add(a, b, c []float32) {
	if len(a) ! = len(b) || len(a) ! = len(c) {
		panic("floats: slice lengths do not match")
	}
	add(unsafe.Pointer(&a[0]), unsafe.Pointer(&b[0]), unsafe.Pointer(&c[0]), int64(len(a)))
}

Limitations

  • No call statements except for inline functions.
  • Arguments must be int64_t, long, float, double, _Bool or pointer.
  • Potentially BUGGY code generation.

Acknowledgments

Contributors

zhenghaoz

39 commits

zhangzhenghao

14 commits

asdine

6 commits

asimshankar

2 commits

Languages

Go

96.4%

C

3.6%