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).
LLGo is compatible with Go 1.20+ source code and supports the complete Go 1.26 language syntax, as well as cgo
.
Compatibility is checked against applicable upstream GOROOT/test cases using pinned Go 1.25 and Go 1.26 toolchains. Remaining applicable differences are recorded in
; gc-specific mechanisms outside LLGo's compatibility goals are documented in
xfail.yaml
notapplicable.yaml
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 .
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 test/std with both supported toolchains.
Other targets may not provide every OS service or implementation-specific runtime behavior.
| Target | Current coverage |
|---|---|
| Native | Linux amd64/arm64 and macOS amd64/arm64 |
js/wasm
and wasip1/wasm
builds; WASI and Emscripten CI coverageconfigurations for supported boards and MCUs, with selected QEMU/emulator smoke tests-target
LLGo lets you import and call C/C++ libraries directly, without wrappers or cgo overhead.
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.
LLGo provides Go bindings for the C/C++ standard library:
| Package | Description |
|---|---|
c/syscallc/sysc/osc/mathc/math/cmplxc/math/randc/pthreadc/pthread/syncc/sync/atomicc/timec/netcpp/stdHere 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 Cprintf
to printHello world
concat: call Cfprintf
withstderr
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 .
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/bdwgcc/cjsonc/clangc/ffic/libuvc/llama2c/luac/necoc/opensslc/raylibc/sqlitec/zlibcpp/inihcpp/llvm
Examples built on these bindings:
llama2-c: inference Llama 2 (the first LLGo AI example)mkjson: create a JSON object and print itsqlitedemo: a basic SQLite demotetris: a Tetris game based on raylib
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/syspy/ospy/mathpy/jsonpy/inspectpy/statisticspy/numpypy/pandaspy/torchpy/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:
callpy: call Python standard library functionmath.sqrt
pi: print python constantsmath.pi
statistics: define a python list and callstatistics.mean
to get the meanmatrix: a basicnumpy
demo
To run these demos (If you haven't installed llgo
yet, please refer to How to install):
cd <demo-directory> # eg. cd _demo/py/callpy
llgo run .
Go 1.25+(to build LLGo; CI also validates user packages with pinned Go 1.25 and Go 1.26 toolchains)LLVM 19Clang 19LLD 19pkg-config 0.29+bdwgc/libgc 8.0+libffilibuvOpenSSL 3.0+zlib 1.2+Python 3.12+(optional, forgithub.com/goplus/lib/py)
Follow these steps to install the llgo
command, whose usage is similar to the go
command:
brew update
brew install llvm@19 lld@19 bdw-gc openssl cjson libffi libuv pkg-config
brew install python@3.12 # optional
brew link --overwrite llvm@19 lld@19 libffi
./install.sh
echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-19 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-19-dev clang-19 libclang-19-dev lld-19 libunwind-19-dev libc++-19-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
apk add go llvm19-dev clang19-dev lld19 pkgconf gc-dev libunwind-dev openssl-dev zlib-dev
apk add python3-dev # optional
apk add g++ # build only
export LLVM_CONFIG=/usr/lib/llvm19/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
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 .
TODO
git clone https://github.com/xgo-dev/llgo.git
cd llgo
./install.sh
pydump: It is the first production program compiled withllgo
rather 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 thellgo
project, but we depend on it.llpyg: It is used to automatically convert Python libraries into Go packages thatllgo
can import. It depends onpydump
andpysigfetch
to 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 undercl/_test*
. LLVM IR checks live in Go sources as// LITTEST
FileCheck 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
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. AlthoughLLVM SSA
andGo SSA
are both IR languages, they work at completely different levels.LLVM SSA
is closer to machine code and abstracts over different instruction sets, whileGo SSA
is closer to a high-level language. We can think of it as the instruction set of theGo computer
.llgo/ssa
is not limited to thellgo
compiler. 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 onllgo/ssa
.internal/build: It strings together the entire compilation process ofllgo
. It depends onllgo/ssa
andllgo/cl
.