LLGo - A Go compiler based on LLVM
September 6, 2026 · View on GitHub
LLGo is a Go compiler based on LLVM in order to better integrate Go with the C ecosystem, including Python and JavaScript. It's a subproject of the XGo project.
LLGo aims to expand the boundaries of Go/XGo, providing limitless possibilities such as:
- Game development
- AI and data science
- WebAssembly
- Embedded development
- ...
How can these be achieved?
LLGo := Go * C ecosystem
LLGo is compatible with the C ecosystem through the C Application Binary Interface (ABI), while LLGo is compatible with Go at the source-code level. The C ecosystem includes languages that expose C-compatible interfaces (e.g. C/C++, Python, JavaScript, Objective-C, and Swift).
Go support
LLGo is compatible with Go 1.20+ source code and supports the complete Go 1.27 language syntax, as well as cgo.
Compiler compatibility is checked against applicable upstream GOROOT/test cases using the pinned Go 1.27 toolchain. User projects and packages under test/ are additionally tested with exact Go 1.20 through Go 1.27 toolchains. Remaining applicable differences are recorded in xfail.yaml; gc-specific mechanisms outside LLGo's compatibility goals are documented in notapplicable.yaml.
Runtime
LLGo uses a different runtime from the standard Go toolchain. Native goroutines map 1:1 to OS threads with fixed native stacks, so direct C calls require no Go-to-C stack or scheduler transition, avoiding the cgo overhead that makes frequent C calls costly in standard Go.
The default garbage collector is conservative BDWGC (also known as libgc). Bare-metal embedded targets instead use a TinyGo-derived conservative mark-and-sweep collector.
Garbage collection can be disabled with the nogc build tag. For example:
llgo run -tags nogc .
Standard libraries
LLGo fully supports the Go standard library on supported native platforms. CI requires compatibility coverage for every public package and exported symbol in the primary Go toolchain, and runs focused test/std compatibility sets with each older supported toolchain.
Other targets may not provide every OS service or implementation-specific runtime behavior.
| Target | Current coverage |
|---|---|
| Native | Linux amd64/arm64, macOS amd64/arm64, and Windows amd64/arm64 (MSVC and MinGW) release builds; primary CI on Linux amd64, macOS arm64, and Windows toolchain profiles |
| WebAssembly | js/wasm and wasip1/wasm builds; WASI and Emscripten CI coverage |
| Embedded | -target configurations for supported boards and MCUs, with selected QEMU/emulator smoke tests |
Named WebAssembly targets select an ecosystem C ABI independently of the Go
source tags: -target emscripten (and the legacy -target wasm alias) emits an
ES module plus its sibling wasm32 module, -target emscripten-memory64 emits the
same pair with an LP64 C data model, and -target wasi/-target wasip1 emits a
WASI Preview 1 module. Raw GOOS=js/wasip1 GOARCH=wasm remains a separate
compatibility path reserved for convergence with the standard Go platform ABI.
C/C++ support
LLGo lets you import and call C/C++ libraries directly, without wrappers or cgo overhead.
Interop mechanism
LLGo uses go:linkname to bind a Go declaration directly to a C ABI symbol:
import _ "unsafe" // for go:linkname
//go:linkname Sqrt C.sqrt
func Sqrt(x float64) float64
You can use this directly in your own code:
package main
import _ "unsafe" // for go:linkname
//go:linkname Sqrt C.sqrt
func Sqrt(x float64) float64
func main() {
println("sqrt(2) =", Sqrt(2))
}
Or organize such bindings into a package, as c/math does:
package main
import "github.com/goplus/lib/c/math"
func main() {
println("sqrt(2) =", math.Sqrt(2))
}
Because calls into C compile to native calls against the C ABI, there is no Go-to-C stack or scheduler transition, so frequent C calls stay cheap.
On Windows, bind APIs declared with WINAPI or __stdcall through the
stdcall. namespace. The convention is distinct on 386; Windows amd64 and
arm64 use their unified native C ABI. An explicitly decorated 386 name such as
_MessageBoxW@16 is also accepted and is normalized to MessageBoxW on
64-bit targets.
//go:linkname MessageBoxW stdcall.MessageBoxW
func MessageBoxW(hwnd uintptr, text, caption *uint16, flags uint32) int32
//llgo:type stdcall
type Callback func(context uintptr) uintptr
stdcall. declarations and //llgo:type stdcall apply only to non-variadic
function types. A native callback is one function pointer, so a Go callback
must be a direct function reference; pass state through an explicit context
pointer rather than a capturing closure.
C/C++ standard libraries
LLGo provides Go bindings for the C/C++ standard library:
| Package | Description |
|---|---|
| c | C standard library core |
| c/syscall | System calls |
| c/sys | System headers |
| c/os | OS interfaces |
| c/math | Math functions |
| c/math/cmplx | Complex math |
| c/math/rand | Random number generation |
| c/pthread | POSIX threads |
| c/pthread/sync | Thread synchronization |
| c/sync/atomic | Atomic operations |
| c/time | Time functions |
| c/net | Networking |
| cpp/std | C++ standard library core |
Here is a simple example calling the C printf function:
package main
import "github.com/goplus/lib/c"
func main() {
c.Printf(c.Str("Hello world\n"))
}
c.Str is not a runtime conversion from a Go string to a C string — it is a built-in instruction that llgo recognizes and compiles directly into a C string constant.
Additional demos are available in the _demo directory (prefixed with _ so the go command skips them):
- hello: call C
printfandfprintfwith Go and C strings - qsort: call a C function that takes a callback (e.g.
qsort)
To run a demo (see How to install if llgo isn't installed yet):
cd <demo-directory> # e.g. cd _demo/c/hello
llgo run .
Other frequently used libraries
Beyond the standard library, LLGo can import libraries from across the C/C++ ecosystem. Bindings are currently maintained by hand; automating this process, as is already done for Python library imports, is planned for the future.
Available bindings include:
- c/bdwgc
- c/cjson
- c/clang
- c/ffi
- c/libuv
- c/llama2
- c/lua
- c/neco
- c/openssl
- c/raylib
- c/sqlite
- c/zlib
- cpp/inih
- cpp/llvm
Examples built on these bindings:
- llama2-c: inference Llama 2 (the first LLGo AI example)
- mkjson: create a JSON object and print it
- sqlitedemo: a basic SQLite demo
- tetris: a Tetris game based on raylib
Python support
You can import a Python library in LLGo!
You can import Python libraries into llgo through llpyg (see Development tools). Available bindings include:
- py (abi)
- py/std (builtins)
- py/sys
- py/os
- py/math
- py/json
- py/inspect
- py/statistics
- py/numpy
- py/pandas
- py/torch
- py/matplotlib
Third-party libraries such as pandas and PyTorch must be installed separately.
Here is an example:
package main
import (
"github.com/goplus/lib/py"
"github.com/goplus/lib/py/math"
"github.com/goplus/lib/py/std"
)
func main() {
x := math.Sqrt(py.Float(2)) // x = sqrt(2)
std.Print(py.Str("sqrt(2) ="), x) // print("sqrt(2) =", x)
}
It is equivalent to the following Python code:
import math
x = math.sqrt(2)
print("sqrt =", x)
Here, We call py.Float(2) to create a Python number 2, and pass it to Python’s math.sqrt to get x. Then we call std.Print to print the result.
Let's look at a slightly more complex example. For example, we use numpy to calculate:
package main
import (
"github.com/goplus/lib/py"
"github.com/goplus/lib/py/numpy"
"github.com/goplus/lib/py/std"
)
func main() {
a := py.List(
py.List(1.0, 2.0, 3.0),
py.List(4.0, 5.0, 6.0),
py.List(7.0, 8.0, 9.0),
)
b := py.List(
py.List(9.0, 8.0, 7.0),
py.List(6.0, 5.0, 4.0),
py.List(3.0, 2.0, 1.0),
)
x := numpy.Add(a, b)
std.Print(py.Str("a+b ="), x)
}
Here we define two 3x3 matrices a and b, add them to get x, and then print the result.
The _demo/py/ directory contains some python related demos:
- basic: call Python math, statistics, variadic builtin, iterator, and print APIs
- scientific: convert nested lists through NumPy and PyTorch
To run these demos (If you haven't installed llgo yet, please refer to How to install):
cd <demo-directory> # eg. cd _demo/py/basic
llgo run .
Dependencies
- Go 1.27 for building LLGo; CI validates user packages separately with Go 1.20 through Go 1.27
- LLVM 22
- Clang 22
- LLD 22
- LLDB (LLVM 22 packages on Linux/Windows; Xcode's Apple LLDB on macOS)
- pkg-config 0.29+
- bdwgc/libgc 8.0+
- libffi
- OpenSSL 3.0+
- zlib 1.2+
- Python 3.12+ (optional, for github.com/goplus/lib/py)
How to install
Follow these steps to install the llgo command, whose usage is similar to the go command:
on macOS
brew update
brew install llvm@22 lld@22 bdw-gc openssl cjson libffi libuv pkg-config
brew install python@3.12 # optional
brew link --force --overwrite llvm@22 lld@22 libffi
# curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash
./install.sh
Homebrew's versioned LLVM 22 formula does not ship LLDB and there is no
lldb@22 formula. LLGo checks common Homebrew and system locations, then
lldb on PATH; use LLGO_LLDB or llgo lldb -lldb to select one explicitly.
on Linux
Debian/Ubuntu
echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-22 main" | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install -y llvm-22-dev clang-22 libclang-22-dev lld-22 lldb-22 libunwind-22-dev libc++-22-dev pkg-config libgc-dev libssl-dev zlib1g-dev libffi-dev libcjson-dev libsqlite3-dev libuv1-dev
sudo apt-get install -y python3.12-dev # optional
#curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash
./install.sh
Alpine Linux
apk add go llvm22-dev clang22-dev lld22 lldb pkgconf gc-dev libunwind-dev openssl-dev zlib-dev libffi-dev cjson-dev sqlite-dev libuv-dev
apk add python3-dev # optional
apk add g++ # build only
export LLVM_CONFIG=/usr/lib/llvm22/bin/llvm-config
export CGO_CPPFLAGS="$($LLVM_CONFIG --cppflags)"
export CGO_CXXFLAGS=-std=c++17
export CGO_LDFLAGS="$($LLVM_CONFIG --ldflags) $($LLVM_CONFIG --libs all)"
curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash
Fedora Linux 44 or newer
Fedora 44 and 45 ship LLVM 22 as the default LLVM stack. Fedora 43 still ships LLVM 21 and is not a supported default-toolchain environment for this LLGo release.
sudo dnf install -y llvm-devel clang-devel lld lldb libcxx-devel llvm-libunwind-devel \
pkgconf-pkg-config gc-devel openssl-devel libffi-devel libuv-devel \
cjson-devel sqlite-devel zlib-ng-compat-devel
llvm-config --version # must report 22.x
docker alpine 386 llgo environment
export GCC_ROOT_DIR=$(gcc -print-search-dirs | grep 'install:' | awk -F': ' '{print \$2}')
export LDFLAGS="-L$GCC_ROOT_DIR -B$GCC_ROOT_DIR -Wl,-dynamic-linker,/lib/ld-musl-i386.so.1"
llgo run .
on Windows
The release workflow builds four integrated Windows archives:
llgo<VERSION>.windows-{amd64,arm64}-{msvc,mingw}.tar.gz. Check the
release assets for availability in
each version. Add the extracted bin directory to PATH; the archives keep
the same runtime, targets, and crosscompile/clang layout as Unix releases.
The MSVC compiler links LLVM statically; MinGW archives include the native
LLVM and C++ DLL dependencies beside llgo.exe.
Use the archive matching your native architecture and toolchain profile. Native
programs still need the corresponding SDK/CRT, Clang, and dependencies described
below: Visual Studio's C++ developer environment for MSVC, or MSYS2 CLANG64
(amd64) / CLANGARM64 (arm64) for MinGW. The bundled ESP Clang remains the
upstream x64 Windows payload, including in ARM64 archives, and runs through
Windows' x64 emulation there; llgo.exe itself is native ARM64 in those archives.
The recommended GNU-hosted setup is an MSYS2 CLANG64 shell. Install the LLVM
22 stack and LLGo's native dependencies, then provide the versioned pkg-config
metadata used by the Go/C++ bindings:
pacman -S --needed \
mingw-w64-clang-x86_64-{clang,llvm,llvm-tools,lld,lldb,compiler-rt,libc++,libunwind} \
mingw-w64-clang-x86_64-{gc,libffi,libuv,openssl,cjson,sqlite3,zlib,pkgconf}
test "$(llvm-config --version | cut -d. -f1)" = 22
pc_dir="$MINGW_PREFIX/lib/pkgconfig"
mkdir -p "$pc_dir"
printf '%s\n' \
'Name: LLVM 22' \
'Description: LLVM 22 host compiler and linker flags' \
"Version: $(llvm-config --version)" \
"Cflags: $(llvm-config --cflags)" \
"Libs: $(llvm-config --ldflags --libs all --system-libs)" \
> "$pc_dir/llvm-22.pc"
The native MSVC CI profile uses LLVM's official 22.1.8 development archive for
headers, libraries, Clang, LLD, and all code-generation backends. That archive
does not contain LLDB, so the profile also extracts LLDB from the matching
official win64 or woa64 installer. LLVM 22 has no official Win32 installer;
the Windows 386 lane uses the LLVM 22.1.8-based llvm-mingw 20260616 builtins and
qualifies the official x64 LLDB under WoW64. The complete pinned setup,
checksums, and generated llvm-22.pc are in
.github/actions/setup-deps/action.yml.
Install from source
git clone https://github.com/xgo-dev/llgo.git
cd llgo
./install.sh
Development tools
- pydump: It is the first production program compiled with
llgorather thango. It outputs symbol information (functions, variables, and constants) from a Python library in JSON format, preparing for the generation of corresponding packages inllgo. - pysigfetch: It generates symbol information by extracting information from Python's documentation site. This tool is not part of the
llgoproject, but we depend on it. - llpyg: It is used to automatically convert Python libraries into Go packages that
llgocan import. It depends onpydumpandpysigfetchto accomplish the task. - llgen: It is used to compile Go packages into LLVM IR files (*.ll).
- gentests: It refreshes runtime-output and package-metadata golden data under
cl/_test*. LLVM IR checks live in Go sources as// LITTESTFileCheck directives. - litgen: It maintains explicitly opted-in, source-embedded FileCheck snapshots. It supports function/global selection, update-only operation, stale-check verification, and stable LLVM value abstractions. Small handwritten checks remain manual.
- ssadump: It is a Go SSA builder and interpreter.
For local workflows and test-golden refresh commands, see dev/README.md.
How do I generate these tools?
git clone https://github.com/xgo-dev/llgo.git
cd llgo
go install -v ./cmd/...
go install -v ./chore/... # compile all tools except pydump
export LLGO_ROOT=$PWD
cd _xtool
llgo install ./... # compile pydump
go install github.com/goplus/hdq/chore/pysigfetch@v0.8.1 # compile pysigfetch
Key modules
Below are the key modules for understanding the implementation principles of llgo:
- ssa: It generates LLVM IR files (LLVM SSA) using the semantics and interfaces of Go SSA. Although
LLVM SSAandGo SSAare both IR languages, they work at completely different levels.LLVM SSAis closer to machine code and abstracts over different instruction sets, whileGo SSAis closer to a high-level language. We can think of it as the instruction set of theGo computer.llgo/ssais not limited to thellgocompiler. If we view it as providing the high-level expressive power ofLLVM, it is very useful. Its advanced SSA form lets clients use LLVM without operating directly on machine-code semantics. - cl: It is the core of the llgo compiler. It converts a Go package into LLVM IR files. It depends on
llgo/ssa. - internal/build: It strings together the entire compilation process of
llgo. It depends onllgo/ssaandllgo/cl.