Skip to main content

Python bindings for QBE (Quite Bare Engine) compiler backend

Project description

qbepy

Python bindings for QBE, a minimalist compiler backend.

QBE is a small, fast compiler backend that takes an SSA-based intermediate language (IL) and produces native machine code for multiple architectures. qbepy provides Python bindings via CFFI, allowing you to compile QBE IL directly from Python without spawning subprocesses.

Features

  • Direct FFI bindings - No subprocess overhead; QBE is compiled as a Python extension
  • Multiple targets - Supports amd64 (System V and Apple ABIs), ARM64, and RISC-V 64
  • Pythonic IR builder - Construct QBE IL programmatically with a clean API
  • Error handling - QBE errors are raised as Python exceptions
  • Vendored QBE - QBE source is included and built automatically during installation

Installation

pip install qbepy
# or
uv add qbepy

Or from source:

git clone https://github.com/user/qbepy.git
cd qbepy
uv sync

Quick Start

Compiling Raw IL

>>> import qbepy
>>>
>>> # QBE IL for a simple add function
>>> il = """
... export function w $add(w %a, w %b) {
... @start
...     %r =w add %a, %b
...     ret %r
... }
... """
>>>
>>> # Compile to assembly
>>> asm = qbepy.compile_il(il)
>>> print(asm)  # doctest: +SKIP

The output will contain assembly code for the target platform. For example, on ARM64 Apple:

.text
.balign 4
.globl _add
_add:
    hint    #34
    stp     x29, x30, [sp, -16]!
    mov     x29, sp
    add     w0, w0, w1
    ldp     x29, x30, [sp], 16
    ret

Using the IR Builder

>>> import qbepy
>>> from qbepy import Module, Function, DataDef, W, L
>>> from qbepy.ir import BinaryOp, Call, Return, Temporary, Global, IntConst
>>>
>>> # Create a module
>>> mod = Module()
>>>
>>> # Add a string constant
>>> mod.add_data(
...     DataDef("greeting")
...     .add_string("Hello, World!")
...     .add_bytes(0)  # null terminator
... )  # doctest: +ELLIPSIS, +SKIP
>>>
>>> # Create main function
>>> func = Function("main", W, export=True)
>>> block = func.add_block("start")
>>>
>>> # Call puts($greeting)
>>> r = func.new_temp("r")
>>> block.instructions.append(
...     Call(Global("puts"), [(L, Global("greeting"))], r, W)
... )
>>> block.terminator = Return(IntConst(0))
>>>
>>> mod.add_function(func)  # doctest: +SKIP
>>>
>>> # Compile to assembly
>>> asm = qbepy.compile_module(mod)  # doctest: +SKIP
>>> print(asm)  # doctest: +SKIP

Specifying a Target

>>> # Compile for x86-64 System V ABI
>>> asm = qbepy.compile_il(il, target="amd64_sysv")
>>> len(asm) > 0  # Verify compilation succeeded
True
>>>
>>> # Or use the Compiler class
>>> compiler = qbepy.Compiler(target="arm64")
>>> asm = compiler.compile(il)
>>> len(asm) > 0  # Verify compilation succeeded
True
>>>
>>> # Available targets
>>> targets = qbepy.Compiler.get_available_targets()
>>> 'amd64_sysv' in targets
True
>>> 'arm64' in targets
True
>>>
>>> # Get default target for current platform
>>> default_target = qbepy.Compiler.get_default_target()
>>> default_target in targets
True

## API Reference

### Compiler

```python
>>> from qbepy import Compiler, compile_il, compile_module
>>>
>>> # Create a compiler instance
>>> compiler = Compiler(target=None)  # None = platform default
>>> compiler is not None
True
>>>
>>> # Compile IL string to assembly
>>> asm = compiler.compile(il)
>>> len(asm) > 0
True
>>>
>>> # Change target
>>> compiler.set_target("amd64_sysv")
>>> compiler.target
'amd64_sysv'
>>>
>>> # Convenience functions
>>> asm = compile_il(il, target=None)
>>> len(asm) > 0
True

### Types

```python
>>> from qbepy import W, L, S, D, BaseType, ExtType, AggregateType
>>>
>>> # Base types
>>> W  # word (32-bit integer)
<BaseType.WORD: 'w'>
>>> L  # long (64-bit integer)
<BaseType.LONG: 'l'>
>>> S  # single (32-bit float)
<BaseType.SINGLE: 's'>
>>> D  # double (64-bit float)
<BaseType.DOUBLE: 'd'>
>>>
>>> # Extended types (for memory operations)
>>> ExtType.BYTE
<ExtType.BYTE: 'b'>
>>> ExtType.WORD
<ExtType.WORD: 'w'>
>>>
>>> # Aggregate types (structs)
>>> point = AggregateType("point", [
...     (ExtType.WORD, 1),  # x: 1 word
...     (ExtType.WORD, 1),  # y: 1 word
... ])
>>> point.name
'point'

### Values

```python
>>> from qbepy.ir import Temporary, Global, Label, IntConst, FloatConst
>>>
>>> Temporary("x")      # %x - SSA temporary
Temporary(name='x')
>>> Global("main")      # $main - global symbol
Global(name='main')
>>> Label("loop")       # @loop - block label
Label(name='loop')
>>> IntConst(42)        # 42 - integer constant
IntConst(value=42)
>>> FloatConst(3.14)    # d_3.14 - double constant
FloatConst(value=3.14, is_single=False)
>>> FloatConst(3.14, is_single=True)  # s_3.14 - float constant
FloatConst(value=3.14, is_single=True)

### Instructions

```python
>>> from qbepy.ir import (
...     BinaryOp,    # add, sub, mul, div, rem, or, xor, and, sar, shr, shl
...     UnaryOp,     # neg, copy
...     Copy,        # copy value
...     Load,        # load from memory
...     Store,       # store to memory
...     Alloc,       # stack allocation
...     Call,        # function call
...     Comparison,  # ceqw, cnew, csltw, etc.
...     Conversion,  # extsw, truncd, stosi, cast, etc.
...     Phi,         # SSA phi node
... )
>>>
>>> # Examples
>>> from qbepy.ir import Temporary, Global
>>> BinaryOp("add", Temporary("r"), W, Temporary("a"), Temporary("b"))
BinaryOp(op='add', result=Temporary(name='r'), result_type=<BaseType.WORD: 'w'>, left=Temporary(name='a'), right=Temporary(name='b'))
>>> Load(Temporary("v"), W, Temporary("ptr"))
Load(result=Temporary(name='v'), result_type=<BaseType.WORD: 'w'>, address=Temporary(name='ptr'), load_type='load')
>>> Store("storew", Temporary("v"), Temporary("ptr"))
Store(store_type='storew', value=Temporary(name='v'), address=Temporary(name='ptr'))
>>> Call(Global("printf"), [(L, Global("fmt"))], Temporary("r"), W)
Call(target=Global(name='printf'), args=[(<BaseType.LONG: 'l'>, Global(name='fmt'))], result=Temporary(name='r'), result_type=<BaseType.WORD: 'w'>, is_variadic=False)

### Control Flow

```python
>>> from qbepy.ir import Jump, Branch, Return, Halt
>>>
>>> Jump(Label("next"))                           # jmp @next
Jump(target=Label(name='next'))
>>> Branch(Temporary("c"), Label("t"), Label("f")) # jnz %c, @t, @f
Branch(condition=Temporary(name='c'), if_true=Label(name='t'), if_false=Label(name='f'))
>>> Return(Temporary("r"))                         # ret %r
Return(value=Temporary(name='r'))
>>> Return(IntConst(0))                           # ret 0
Return(value=IntConst(value=0))
>>> Return()                                       # ret (void)
Return(value=None)
>>> Halt()                                         # hlt (unreachable)
Halt()

### Building Modules

```python
>>> from qbepy import Module, Function, Block, DataDef
>>>
>>> # Module - container for types, data, and functions
>>> mod = Module()
>>> len(mod.functions)
0
>>>
>>> # Function
>>> func = Function("test", W, params=[(W, "a"), (L, "b")], export=True)
>>> block = func.add_block("start")
>>> temp = func.new_temp("x")  # creates unique temporary
>>> temp.name
'x.1'
>>>
>>> # DataDef
>>> data = (DataDef("test_data", export=True, align=8)
...     .add_string("hello")
...     .add_bytes(0)
...     .add_words(1, 2, 3)
...     .add_longs(0x1234567890)
...     .add_zero(16))
>>> data.name
'test_data'

## Supported Targets

| Target | Description |
|--------|-------------|
| `amd64_sysv` | x86-64 with System V ABI (Linux, BSD) |
| `amd64_apple` | x86-64 with Apple ABI (macOS Intel) |
| `arm64` | ARM64 with standard ABI (Linux) |
| `arm64_apple` | ARM64 with Apple ABI (macOS Apple Silicon) |
| `rv64` | RISC-V 64-bit |

## QBE IL Reference

QBE uses a simple SSA-based intermediate language. For the complete specification, see the [QBE IL documentation](https://c9x.me/compile/doc/il.html).

### Basic IL Structure

Type definitions

type :point = { w, w }

Data definitions

data $message = { b "Hello", b 0 }

Function definitions

export function w $main() { @start %x =w copy 42 ret %x }


### IL Basics

- Temporaries: `%name` (SSA values)
- Globals: `$name` (functions and data)
- Labels: `@name` (basic blocks)
- Types: `w` (word), `l` (long), `s` (single), `d` (double)

## Error Handling

```python
>>> from qbepy import CompilationError, compile_il
>>>
>>> try:
...     asm = compile_il("invalid IL code")
... except CompilationError as e:
...     print(f"Compilation failed: {e}")
Compilation failed: unknown keyword invalid

## Project Structure

qbepy/ ├── src/qbepy/ │ ├── init.py # Public API exports │ ├── _ffi.py # Low-level CFFI bindings │ ├── compiler.py # Compiler class │ ├── errors.py # Exception types │ └── ir/ # IR builder module │ ├── types.py # Type definitions │ ├── values.py # Value types │ ├── instructions.py # Instructions │ ├── control.py # Control flow │ └── builder.py # Module, Function, Block, DataDef ├── csrc/ │ ├── qbepy_wrapper.c # C wrapper with error handling │ └── qbepy_wrapper.h ├── vendor/qbe/ # Vendored QBE source ├── build_ffi.py # CFFI build script └── tests/ # Test suite


## How It Works

qbepy vendors the QBE compiler source and builds it as a Python extension using CFFI. The main challenge is that QBE's error handling uses `exit()`, which would terminate the Python process. qbepy solves this by:

1. Redirecting QBE's `err()` function to a custom handler using preprocessor macros
2. Using `setjmp`/`longjmp` to catch errors and return control to Python
3. Converting errors to Python exceptions

This allows QBE to be used as a library rather than a standalone compiler.

## License

qbepy is released under the MIT License.

QBE is developed by Quentin Carbonneaux and is also MIT licensed. See [vendor/qbe/LICENSE](vendor/qbe/LICENSE).

## Credits

- [QBE](https://c9x.me/compile/) - The compiler backend by Quentin Carbonneaux
- [CFFI](https://cffi.readthedocs.io/) - C Foreign Function Interface for Python

## See Also

- [QBE Documentation](https://c9x.me/compile/doc/il.html) - Complete IL specification
- [cproc](https://sr.ht/~mcf/cproc/) - A C11 compiler using QBE as backend
- [Hare](https://harelang.org/) - A systems programming language using QBE

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qbepy-2026.2.2.tar.gz (132.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qbepy-2026.2.2-cp312-cp312-macosx_14_0_arm64.whl (117.4 kB view details)

Uploaded CPython 3.12macOS 14.0+ ARM64

File details

Details for the file qbepy-2026.2.2.tar.gz.

File metadata

  • Download URL: qbepy-2026.2.2.tar.gz
  • Upload date:
  • Size: 132.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for qbepy-2026.2.2.tar.gz
Algorithm Hash digest
SHA256 6cfad26a83621c995d8853e002b539404fd129e01ffb35927f7019eb3bf6a347
MD5 f49a851ecd13378d8a8bf2ab964756fe
BLAKE2b-256 6baea980a53d42332f97f86fcef6ed52c8325daf249dd6a5e9369b5a3179640b

See more details on using hashes here.

File details

Details for the file qbepy-2026.2.2-cp312-cp312-macosx_14_0_arm64.whl.

File metadata

  • Download URL: qbepy-2026.2.2-cp312-cp312-macosx_14_0_arm64.whl
  • Upload date:
  • Size: 117.4 kB
  • Tags: CPython 3.12, macOS 14.0+ ARM64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for qbepy-2026.2.2-cp312-cp312-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 830806e2855cc23b789e31c82782a49080bd93931391f7f5613b8820e1ad7fd6
MD5 e287636be24c682348db879190a533d9
BLAKE2b-256 0da50af94b73e53a877dc5f1131a89657c734f9df853cd094da9119c0e802868

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page