leaven

package module
v0.0.0-...-f2053fe Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2026 License: MIT Imports: 30 Imported by: 0

README

Leaven

Leaven: Compile LLVM IR to Go

Leaven translates LLVM intermediate representation to Go. In theory, it should be able to transpile any language that has an LLVM-based compiler to Go. But so far I’ve only used it for C.

Each LLVM instruction is translated to an equivalent statement in Go. This produces very verbose code; if you are looking for a tool that will convert a C codebase into maintainable Go, Leaven isn’t it.

But it does allow you to call C code from Go without using CGo. And I am hoping that it can produce a working Go translation of a program, which will be a good starting point for incrementally re-translating it (probably by hand) into idiomatic Go.

Warning

This software is incomplete and experimental. It does not support nearly all LLVM instructions. It is 64-bit only (amd64, arm64, and other 8-byte-pointer hosts).

The transpiler at github.com/andybalholm/c2go produces much better results (but it is not as automatic).

Install

Download the archive for your OS/arch from GitHub Releases, extract leaven, put it on PATH.

Builds cover the same targets as CI: linux, darwin, and windows, amd64 and arm64.

From source:

go install github.com/lewtec/leaven/cmd/leaven@latest

Usage Example

(Translating strcmp from musl libc.)

$ cat strcmp.c
#include <string.h>

int strcmp(const char *l, const char *r)
{
	for (; *l==*r && *l; l++, r++);
	return *(unsigned char *)l - *(unsigned char *)r;
}
$ clang -S -emit-llvm -fno-discard-value-names strcmp.c
$ go run ./cmd/leaven strcmp.ll
$ clang -S -emit-llvm -fno-discard-value-names -o - strcmp.c | go run ./cmd/leaven > strcmp.go
$ goimports -w strcmp.go
$ cat strcmp.go
package main

import (
	"unsafe"

	"github.com/lewtec/leaven/libc"
)

func strcmp(l unsafe.Pointer, r unsafe.Pointer) int32 {
	var cmp, tobool, v6 bool
	var conv, conv1, conv3, conv5, conv6, sub int32
	var v1, v3, v5, v10, v12 byte
	var v0, v2, v4, v7, incdec_ptr, v8, incdec_ptr4, v9, v11 unsafe.Pointer
	var l_addr, r_addr unsafe.Pointer
	_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _ = l_addr, r_addr, v0, v1, conv, v2, v3, conv1, cmp, v4, v5, conv3, tobool, v6, v7, incdec_ptr, v8, incdec_ptr4, v9, v10, conv5, v11, v12, conv6, sub

	l_addr_mem := libc.Alloca[unsafe.Pointer](1, int64(8))
	l_addr = libc.Ptr(l_addr_mem)
	defer libc.AllocaFree(libc.As[byte](libc.Ptr(l_addr_mem)))
	r_addr_mem := libc.Alloca[unsafe.Pointer](1, int64(8))
	r_addr = libc.Ptr(r_addr_mem)
	defer libc.AllocaFree(libc.As[byte](libc.Ptr(r_addr_mem)))
	*libc.As[unsafe.Pointer](l_addr) = l
	*libc.As[unsafe.Pointer](r_addr) = r
	goto for_cond

for_cond:
	v0 = *libc.As[unsafe.Pointer](l_addr)
	v1 = *libc.As[byte](v0)
	conv = int32(int8(v1))
	v2 = *libc.As[unsafe.Pointer](r_addr)
	v3 = *libc.As[byte](v2)
	conv1 = int32(int8(v3))
	cmp = conv == conv1
	if cmp {
		goto land_rhs
	} else {
		v6 = false
		goto land_end
	}

land_rhs:
	v4 = *libc.As[unsafe.Pointer](l_addr)
	v5 = *libc.As[byte](v4)
	conv3 = int32(int8(v5))
	tobool = conv3 != 0
	v6 = tobool
	goto land_end

land_end:
	if v6 {
		goto for_body
	} else {
		goto for_end
	}

for_body:
	goto for_inc

for_inc:
	v7 = *libc.As[unsafe.Pointer](l_addr)
	incdec_ptr = libc.Ptr(libc.AddPointer[byte](libc.As[byte](v7), int(1)*1))
	*libc.As[unsafe.Pointer](l_addr) = incdec_ptr
	v8 = *libc.As[unsafe.Pointer](r_addr)
	incdec_ptr4 = libc.Ptr(libc.AddPointer[byte](libc.As[byte](v8), int(1)*1))
	*libc.As[unsafe.Pointer](r_addr) = incdec_ptr4
	goto for_cond

for_end:
	v9 = *libc.As[unsafe.Pointer](l_addr)
	v10 = *libc.As[byte](v9)
	conv5 = int32(uint32(v10))
	v11 = *libc.As[unsafe.Pointer](r_addr)
	v12 = *libc.As[byte](v11)
	conv6 = int32(uint32(v12))
	sub = conv5 - conv6
	return sub
}

Release

GoReleaser + svu. Archives and checksums only (no Homebrew, Docker, or packages). Tags have no v prefix (.svu.yml).

mise release          # next (svu) + goreleaser (needs GITHUB_TOKEN)
mise release patch    # or major | minor | next

CI: .github/workflows/autorelease.yml. Push/PR runs the six-target test matrix. workflow_dispatch with patch/minor/major runs that matrix, then tags and publishes if every cell passed.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BlockName

func BlockName(v value.Value) string

func Compile

func Compile(out io.Writer, m *ir.Module, packageName string) error

func FormatSigned

func FormatSigned(v value.Value) (*jen.Statement, error)

FormatSigned is like FormatValue, except that it converts "byte" to "int8".

func FormatUnsigned

func FormatUnsigned(v value.Value) (*jen.Statement, error)

FormatUnsigned is like FormatValue, except that it converts integer types to unsigned.

func FormatValue

func FormatValue(v value.Value) (*jen.Statement, error)

FormatValue formats a constant or variable as it should appear in an expression.

func GetElementPtr

func GetElementPtr(elemType types.Type, src value.Value, indices []value.Value) (expr, error)

GetElementPtr translates a getelementptr expression. The LLVM pointer value is always unsafe.Pointer. *T exists only while indexing into a struct/array or as AddPointer's type argument.

func PhiAssignments

func PhiAssignments(a, b value.Value) (*jen.Statement, error)

PhiAssignments returns an assignment statement expressing the effects of Phi nodes on the branch from block a to block b. If block b has no phi nodes, it returns nil.

func Qual

func Qual[T any]() *jen.Statement

Qual is T as jennifer AST. Named types become jen.Qual(pkg, name).

func Sym

func Sym(fn any) goRef

Sym is a resolved package-level func (pkg+name). Call/Types emit jen. Generic instantiations drop [T]. Maps store this; FuncForPC runs once.

func TranslateInstruction

func TranslateInstruction(inst ir.Instruction) ([]jen.Code, error)

TranslateInstruction translates an LLVM instruction to one or more Go statements. A nil slice means the instruction is a no-op in Go.

func TypeDefinition

func TypeDefinition(t types.Type) (*jen.Statement, error)

TypeDefinition returns the definition (not just the name) of t.

func TypeName

func TypeName(t types.Type) string

TypeName returns t's local Go name, or the empty string if t is unnamed or mapped to a standard-library type. Clang anonymous structs become names like "anon.1"; those are sanitized to legal Go identifiers (anon_1).

func TypeSpec

func TypeSpec(t types.Type) (*jen.Statement, error)

TypeSpec returns the name (if it has one) or the definition of t.

func VariableName

func VariableName(v value.Named) string

VariableName returns the name to use for a local variable or parameter.

Types

type Command

type Command struct {
	// Package is the generated Go package name. Empty means "main".
	Package string
	// Name is an optional source label for parse errors (file path or "<stdin>").
	Name string
	// Input is LLVM IR assembly.
	Input io.Reader
	// Output receives generated Go source.
	Output io.Writer
}

Command transpiles LLVM IR to Go. Fill Input (and usually Output), then call Run.

func (*Command) Run

func (c *Command) Run(ctx context.Context) error

Run reads LLVM IR from Input and writes Go to Output. ctx is checked before parse and before compile.

type Index

type Index struct {
	// contains filtered or unexported fields
}

An Index has maps for navigating "against the grain" of the pointers in the intermediate representation: from values to the instructions that use them, and from instructions to the blocks that contain them.

func (*Index) Add

func (i *Index) Add(f *ir.Func)

Add adds the instructions in f to the index.

func (*Index) DeleteInstruction

func (i *Index) DeleteInstruction(inst ir.Instruction)

DeleteInstruction deletes inst.

func (*Index) ReplaceValue

func (i *Index) ReplaceValue(oldVal, newVal value.Value)

ReplaceValue replaces oldVal with newVal wherever it is used.

func (*Index) Users

func (i *Index) Users(v value.Value) []value.User

Users returns a slice of the instructions and terminators that use v.

Directories

Path Synopsis
cmd
leaven command
internal
genir command
Command genir emits testdata/ir/*/input.<LLVM-major>.ll from source.{c,cpp,rs}.
Command genir emits testdata/ir/*/input.<LLVM-major>.ll from source.{c,cpp,rs}.
llir/internal/enc
Package enc implements encoding of identifiers for LLVM IR assembly.
Package enc implements encoding of identifiers for LLVM IR assembly.
llir/internal/gep
Package gep computes the result type of getelementptr instructions and constant expressions.
Package gep computes the result type of getelementptr instructions and constant expressions.
llir/internal/natsort
Package natsort implements natural sort.
Package natsort implements natural sort.
llir/ir
Package ir declares the types used to represent LLVM IR modules.
Package ir declares the types used to represent LLVM IR modules.
llir/ir/constant
Package constant implements values representing immutable LLVM IR constants.
Package constant implements values representing immutable LLVM IR constants.
llir/ir/enum
Package enum defines enumerate types of LLVM IR.
Package enum defines enumerate types of LLVM IR.
llir/ir/metadata
Package metadata provides access to LLVM IR metadata.
Package metadata provides access to LLVM IR metadata.
llir/ir/types
Package types declares the data types of LLVM IR.
Package types declares the data types of LLVM IR.
llir/ir/value
Package value provides a definition of LLVM IR values.
Package value provides a definition of LLVM IR values.
llir/v14/asm
Package asm implements a parser for LLVM IR assembly files.
Package asm implements a parser for LLVM IR assembly files.
The libc package implements various functions from the C standard library in Go.
The libc package implements various functions from the C standard library in Go.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL