A Go compiler based on LLVM in order to better integrate Go with the C ecosystem including Python and JavaScript
753
stars
6,322
commits
LLVM
primary language
Sep 11, 2026
updated
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:
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.27 language syntax, as well as cgo.
Compatibility is checked against applicable upstream GOROOT/test cases using pinned Go 1.26 and Go 1.27 toolchains. 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.
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 release artifacts; primary CI on Linux amd64 and macOS arm64 |
| 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 |
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 | 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):
printf to print Hello worldfprintf with stderrqsort)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:
Examples built on these bindings:
You can import a Python library in LLGo!
You can import Python libraries into llgo through llpyg (see Development tools). Available bindings include:
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:
math.sqrtmath.pistatistics.mean to get the meannumpy demoTo 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 .
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
# curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash
./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
llgo rather than go. It outputs symbol information (functions, variables, and constants) from a Python library in JSON format, preparing for the generation of corresponding packages in llgo.llgo project, but we depend on it.llgo can import. It depends on pydump and pysigfetch to accomplish the task.cl/_test*. LLVM IR checks live in Go sources as // LITTEST FileCheck directives.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:
LLVM SSA and Go SSA are both IR languages, they work at completely different levels. LLVM SSA is closer to machine code and abstracts over different instruction sets, while Go SSA is closer to a high-level language. We can think of it as the instruction set of the Go computer. llgo/ssa is not limited to the llgo compiler. If we view it as providing the high-level expressive power of LLVM, it is very useful. Its advanced SSA form lets clients use LLVM without operating directly on machine-code semantics.llgo/ssa.llgo. It depends on llgo/ssa and llgo/cl.LLVM
46.3%
Go
39.8%
Assembly
10.8%
C
1.5%
A Go compiler based on LLVM in order to better integrate Go with the C ecosystem including Python and JavaScript
753
stars
6,322
commits
LLVM
primary language
Sep 11, 2026
updated
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:
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.27 language syntax, as well as cgo.
Compatibility is checked against applicable upstream GOROOT/test cases using pinned Go 1.26 and Go 1.27 toolchains. 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.
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 release artifacts; primary CI on Linux amd64 and macOS arm64 |
| 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 |
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 | 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):
printf to print Hello worldfprintf with stderrqsort)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:
Examples built on these bindings:
You can import a Python library in LLGo!
You can import Python libraries into llgo through llpyg (see Development tools). Available bindings include:
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:
math.sqrtmath.pistatistics.mean to get the meannumpy demoTo 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 .
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
# curl https://raw.githubusercontent.com/xgo-dev/llgo/refs/heads/main/install.sh | bash
./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
llgo rather than go. It outputs symbol information (functions, variables, and constants) from a Python library in JSON format, preparing for the generation of corresponding packages in llgo.llgo project, but we depend on it.llgo can import. It depends on pydump and pysigfetch to accomplish the task.cl/_test*. LLVM IR checks live in Go sources as // LITTEST FileCheck directives.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:
LLVM SSA and Go SSA are both IR languages, they work at completely different levels. LLVM SSA is closer to machine code and abstracts over different instruction sets, while Go SSA is closer to a high-level language. We can think of it as the instruction set of the Go computer. llgo/ssa is not limited to the llgo compiler. If we view it as providing the high-level expressive power of LLVM, it is very useful. Its advanced SSA form lets clients use LLVM without operating directly on machine-code semantics.llgo/ssa.llgo. It depends on llgo/ssa and llgo/cl.LLVM
46.3%
Go
39.8%
Assembly
10.8%
C
1.5%