qbepy 2026.2.1__cp312-cp312-macosx_14_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
qbepy/__init__.py ADDED
@@ -0,0 +1,85 @@
1
+ """QBE Python Bindings - Compile QBE IL to assembly.
2
+
3
+ This package provides Python bindings for QBE (Quite Bare Engine), a minimalist
4
+ compiler backend. It includes:
5
+
6
+ 1. A Compiler class that uses FFI to compile IL to assembly
7
+ 2. An IR builder module for constructing QBE IL programmatically
8
+
9
+ Example usage:
10
+
11
+ # Raw IL compilation
12
+ import qbepy
13
+
14
+ il = '''
15
+ data $str = { b "hello", b 0 }
16
+ export function w $main() {
17
+ @start
18
+ %r =w call $puts(l $str)
19
+ ret 0
20
+ }
21
+ '''
22
+ asm = qbepy.compile_il(il)
23
+
24
+ # Using the IR builder
25
+ from qbepy import Module, Function, DataDef, W, L
26
+ from qbepy.ir import Call, Return, Global, Temporary, IntConst
27
+
28
+ mod = Module()
29
+ mod.add_data(DataDef("str").add_string("hello").add_bytes(0))
30
+
31
+ func = Function("main", W, export=True)
32
+ block = func.add_block("start")
33
+ block.instructions.append(
34
+ Call(Global("puts"), [(L, Global("str"))], Temporary("r"), W)
35
+ )
36
+ block.terminator = Return(IntConst(0))
37
+ mod.add_function(func)
38
+
39
+ asm = qbepy.compile_module(mod)
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ from .compiler import Compiler, compile_il, compile_module
45
+ from .errors import CompilationError, QBEPyError
46
+ from .ir import (
47
+ AggregateType,
48
+ BaseType,
49
+ Block,
50
+ D,
51
+ DataDef,
52
+ ExtType,
53
+ Function,
54
+ L,
55
+ Module,
56
+ S,
57
+ SubWordType,
58
+ W,
59
+ )
60
+
61
+ __all__ = [
62
+ "AggregateType",
63
+ # Types
64
+ "BaseType",
65
+ "Block",
66
+ "CompilationError",
67
+ # Compiler
68
+ "Compiler",
69
+ "D",
70
+ "DataDef",
71
+ "ExtType",
72
+ "Function",
73
+ "L",
74
+ # IR building
75
+ "Module",
76
+ "QBEPyError",
77
+ "S",
78
+ "SubWordType",
79
+ "W",
80
+ # Convenience functions
81
+ "compile_il",
82
+ "compile_module",
83
+ ]
84
+
85
+ __version__ = "0.1.0"
qbepy/_ffi.py ADDED
@@ -0,0 +1,66 @@
1
+ """Low-level FFI bindings to QBE."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ._qbe_ffi import ffi, lib
6
+
7
+
8
+ class QBEError(Exception):
9
+ """Error from QBE compilation."""
10
+
11
+
12
+ def compile_il(source: str, target: str | None = None) -> str:
13
+ """
14
+ Compile QBE IL to assembly.
15
+
16
+ Args:
17
+ source: QBE IL source code
18
+ target: Target architecture (or None for default)
19
+ Valid targets: amd64_sysv, amd64_apple, arm64, arm64_apple, rv64
20
+
21
+ Returns:
22
+ Assembly code as string
23
+
24
+ Raises:
25
+ QBEError: If compilation fails
26
+ """
27
+ source_bytes = source.encode("utf-8")
28
+ target_bytes = target.encode("utf-8") if target else ffi.NULL
29
+
30
+ output_ptr = ffi.new("char**")
31
+ output_len = ffi.new("size_t*")
32
+
33
+ result = lib.qbepy_compile(
34
+ source_bytes, len(source_bytes), target_bytes, output_ptr, output_len
35
+ )
36
+
37
+ if result != lib.QBEPY_OK:
38
+ error_msg = ffi.string(lib.qbepy_get_error()).decode("utf-8")
39
+ raise QBEError(error_msg)
40
+
41
+ try:
42
+ if output_ptr[0] == ffi.NULL:
43
+ msg = "No output generated"
44
+ raise QBEError(msg)
45
+ output = ffi.string(output_ptr[0], output_len[0]).decode("utf-8")
46
+ finally:
47
+ if output_ptr[0] != ffi.NULL:
48
+ lib.qbepy_free(output_ptr[0])
49
+
50
+ return output
51
+
52
+
53
+ def get_targets() -> list[str]:
54
+ """Get list of available target architectures."""
55
+ targets_str = ffi.string(lib.qbepy_get_targets()).decode("utf-8")
56
+ return targets_str.split(",")
57
+
58
+
59
+ def get_default_target() -> str:
60
+ """Get default target for current platform."""
61
+ return ffi.string(lib.qbepy_get_default_target()).decode("utf-8")
62
+
63
+
64
+ def version() -> str:
65
+ """Get QBE version string."""
66
+ return ffi.string(lib.qbepy_version()).decode("utf-8")
qbepy/_qbe_ffi.abi3.so ADDED
Binary file
qbepy/compiler.py ADDED
@@ -0,0 +1,164 @@
1
+ """High-level QBE compiler interface."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Literal
6
+
7
+ from ._ffi import QBEError, compile_il as _compile_il, get_default_target, get_targets
8
+ from .errors import CompilationError
9
+
10
+ if TYPE_CHECKING:
11
+ from .ir.builder import Module
12
+
13
+ Target = Literal["amd64_sysv", "amd64_apple", "arm64", "arm64_apple", "rv64"]
14
+
15
+
16
+ class Compiler:
17
+ """QBE compiler interface.
18
+
19
+ Provides methods to compile QBE IL to native assembly code.
20
+
21
+ Example:
22
+ >>> compiler = Compiler()
23
+ >>> il = '''
24
+ ... export function w $add(w %a, w %b) {
25
+ ... @start
26
+ ... %r =w add %a, %b
27
+ ... ret %r
28
+ ... }
29
+ ... '''
30
+ >>> asm = compiler.compile(il)
31
+ """
32
+
33
+ TARGETS: list[str] = ["amd64_sysv", "amd64_apple", "arm64", "arm64_apple", "rv64"]
34
+
35
+ def __init__(self, target: Target | None = None):
36
+ """Initialize compiler.
37
+
38
+ Args:
39
+ target: Target architecture. If None, uses platform default.
40
+ Valid targets: amd64_sysv, amd64_apple, arm64, arm64_apple, rv64
41
+
42
+ Raises:
43
+ ValueError: If target is not a valid target name.
44
+ """
45
+ if target and target not in self.TARGETS:
46
+ msg = f"Unknown target: {target}. Valid targets: {self.TARGETS}"
47
+ raise ValueError(msg)
48
+ self.target = target
49
+
50
+ def compile(self, il: str) -> str:
51
+ """Compile QBE IL to assembly.
52
+
53
+ Args:
54
+ il: QBE IL source code
55
+
56
+ Returns:
57
+ Generated assembly code as a string
58
+
59
+ Raises:
60
+ CompilationError: If compilation fails
61
+ """
62
+ try:
63
+ return _compile_il(il, self.target)
64
+ except QBEError as e:
65
+ raise CompilationError(str(e), source=il) from e
66
+
67
+ def compile_module(self, module: Module) -> str:
68
+ """Compile a Module to assembly.
69
+
70
+ Args:
71
+ module: IR Module object
72
+
73
+ Returns:
74
+ Generated assembly code as a string
75
+
76
+ Raises:
77
+ CompilationError: If compilation fails
78
+ """
79
+ il = module.emit()
80
+ return self.compile(il)
81
+
82
+ def set_target(self, target: Target) -> None:
83
+ """Set the compilation target.
84
+
85
+ Args:
86
+ target: Target architecture name
87
+
88
+ Raises:
89
+ ValueError: If the target is not recognized
90
+ """
91
+ if target not in self.TARGETS:
92
+ msg = (
93
+ f"Unknown target: {target}. "
94
+ f"Available targets: {', '.join(self.TARGETS)}"
95
+ )
96
+ raise ValueError(msg)
97
+ self.target = target
98
+
99
+ @staticmethod
100
+ def get_default_target() -> str:
101
+ """Get QBE's default target for the current platform.
102
+
103
+ Returns:
104
+ The default target name
105
+ """
106
+ return get_default_target()
107
+
108
+ @staticmethod
109
+ def get_available_targets() -> list[str]:
110
+ """Get list of available compilation targets.
111
+
112
+ Returns:
113
+ List of target names
114
+ """
115
+ return get_targets()
116
+
117
+
118
+ def compile_il(il: str, target: Target | None = None) -> str:
119
+ """Compile QBE IL text to assembly.
120
+
121
+ Convenience function that creates a Compiler and compiles the IL.
122
+
123
+ Args:
124
+ il: QBE intermediate language source code
125
+ target: Target architecture (e.g., 'amd64_sysv', 'arm64').
126
+ If None, uses QBE's default for the current platform.
127
+
128
+ Returns:
129
+ Generated assembly code as a string
130
+
131
+ Raises:
132
+ CompilationError: If compilation fails
133
+
134
+ Example:
135
+ >>> il = '''
136
+ ... export function w $main() {
137
+ ... @start
138
+ ... ret 0
139
+ ... }
140
+ ... '''
141
+ >>> asm = compile_il(il)
142
+ """
143
+ compiler = Compiler(target)
144
+ return compiler.compile(il)
145
+
146
+
147
+ def compile_module(module: Module, target: Target | None = None) -> str:
148
+ """Compile a Module to assembly.
149
+
150
+ Convenience function that creates a Compiler and compiles the module.
151
+
152
+ Args:
153
+ module: A Module containing type definitions, data, and functions
154
+ target: Target architecture (e.g., 'amd64_sysv', 'arm64').
155
+ If None, uses QBE's default for the current platform.
156
+
157
+ Returns:
158
+ Generated assembly code as a string
159
+
160
+ Raises:
161
+ CompilationError: If compilation fails
162
+ """
163
+ compiler = Compiler(target)
164
+ return compiler.compile_module(module)
qbepy/errors.py ADDED
@@ -0,0 +1,20 @@
1
+ """Exception types for qbepy."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class QBEPyError(Exception):
7
+ """Base exception for qbepy errors."""
8
+
9
+
10
+ class CompilationError(QBEPyError):
11
+ """Error during QBE compilation.
12
+
13
+ Attributes:
14
+ message: Error message from QBE
15
+ source: The IL source that caused the error (if available)
16
+ """
17
+
18
+ def __init__(self, message: str, source: str | None = None):
19
+ self.source = source
20
+ super().__init__(message)
qbepy/ir/__init__.py ADDED
@@ -0,0 +1,73 @@
1
+ """QBE Intermediate Representation builder.
2
+
3
+ This module provides a Pythonic API for constructing QBE IL programs.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from .builder import Block, DataDef, Function, Module
9
+ from .control import Branch, Halt, Jump, Return, Terminator
10
+ from .instructions import (
11
+ Alloc,
12
+ BinaryOp,
13
+ Blit,
14
+ Call,
15
+ Comparison,
16
+ Conversion,
17
+ Copy,
18
+ Instruction,
19
+ Load,
20
+ Phi,
21
+ Store,
22
+ UnaryOp,
23
+ VaArg,
24
+ VaStart,
25
+ )
26
+ from .types import AggregateType, BaseType, D, ExtType, L, S, SubWordType, W
27
+ from .values import FloatConst, Global, IntConst, Label, Temporary, TypeRef, Value
28
+
29
+ __all__ = [
30
+ "AggregateType",
31
+ "Alloc",
32
+ # Types
33
+ "BaseType",
34
+ "BinaryOp",
35
+ "Blit",
36
+ # Builders
37
+ "Block",
38
+ "Branch",
39
+ "Call",
40
+ "Comparison",
41
+ "Conversion",
42
+ "Copy",
43
+ "D",
44
+ "DataDef",
45
+ "ExtType",
46
+ "FloatConst",
47
+ "Function",
48
+ "Global",
49
+ "Halt",
50
+ # Instructions
51
+ "Instruction",
52
+ "IntConst",
53
+ "Jump",
54
+ "L",
55
+ "Label",
56
+ "Load",
57
+ "Module",
58
+ "Phi",
59
+ "Return",
60
+ "S",
61
+ "Store",
62
+ "SubWordType",
63
+ # Values
64
+ "Temporary",
65
+ # Control flow
66
+ "Terminator",
67
+ "TypeRef",
68
+ "UnaryOp",
69
+ "VaArg",
70
+ "VaStart",
71
+ "Value",
72
+ "W",
73
+ ]
qbepy/ir/builder.py ADDED
@@ -0,0 +1,259 @@
1
+ """QBE IR builder classes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import TYPE_CHECKING
7
+
8
+ from .types import AggregateType, BaseType
9
+ from .values import Global, Label, Temporary
10
+
11
+ if TYPE_CHECKING:
12
+ from .control import Terminator
13
+ from .instructions import Instruction, Phi
14
+
15
+
16
+ @dataclass
17
+ class Block:
18
+ """A basic block in a function.
19
+
20
+ Each block has:
21
+ - A label name
22
+ - Optional phi instructions (for SSA)
23
+ - Regular instructions
24
+ - A terminator (jump, branch, return, or halt)
25
+ """
26
+
27
+ name: str
28
+ phis: list[Phi] = field(default_factory=list)
29
+ instructions: list[Instruction] = field(default_factory=list)
30
+ terminator: Terminator | None = None
31
+
32
+ @property
33
+ def label(self) -> Label:
34
+ """Get this block's label."""
35
+ return Label(self.name)
36
+
37
+ def emit(self) -> str:
38
+ """Emit this block as QBE IL."""
39
+ lines = [f"@{self.name}"]
40
+
41
+ for phi in self.phis:
42
+ lines.append(f"\t{phi.emit()}")
43
+
44
+ for ins in self.instructions:
45
+ lines.append(f"\t{ins.emit()}")
46
+
47
+ if self.terminator:
48
+ lines.append(f"\t{self.terminator.emit()}")
49
+
50
+ return "\n".join(lines)
51
+
52
+
53
+ @dataclass
54
+ class Function:
55
+ """A function definition.
56
+
57
+ Example:
58
+ # export function w $add(w %a, w %b) { ... }
59
+ func = Function("add", BaseType.WORD,
60
+ params=[(BaseType.WORD, "a"), (BaseType.WORD, "b")],
61
+ export=True)
62
+ """
63
+
64
+ name: str
65
+ return_type: BaseType | str | None = None
66
+ params: list[tuple[BaseType | str, str]] = field(default_factory=list)
67
+ export: bool = False
68
+ vararg: bool = False
69
+ blocks: list[Block] = field(default_factory=list)
70
+ _temp_counter: int = field(default=0, repr=False)
71
+
72
+ def new_temp(self, prefix: str = "t") -> Temporary:
73
+ """Create a new unique temporary variable."""
74
+ self._temp_counter += 1
75
+ return Temporary(f"{prefix}.{self._temp_counter}")
76
+
77
+ def add_block(self, name: str) -> Block:
78
+ """Add a new basic block to this function."""
79
+ block = Block(name)
80
+ self.blocks.append(block)
81
+ return block
82
+
83
+ def emit(self) -> str:
84
+ """Emit this function as QBE IL."""
85
+ lines = []
86
+
87
+ if self.export:
88
+ lines.append("export")
89
+
90
+ # Return type
91
+ if self.return_type:
92
+ if isinstance(self.return_type, BaseType):
93
+ ret_str = self.return_type.value
94
+ else:
95
+ ret_str = f":{self.return_type}"
96
+ else:
97
+ ret_str = ""
98
+
99
+ # Parameters
100
+ param_strs = []
101
+ for param_type, param_name in self.params:
102
+ if isinstance(param_type, BaseType):
103
+ type_str = param_type.value
104
+ else:
105
+ type_str = f":{param_type}"
106
+ param_strs.append(f"{type_str} %{param_name}")
107
+
108
+ if self.vararg:
109
+ param_strs.append("...")
110
+
111
+ params_str = ", ".join(param_strs)
112
+
113
+ lines.append(f"function {ret_str} ${self.name}({params_str}) {{")
114
+
115
+ for block in self.blocks:
116
+ lines.append(block.emit())
117
+
118
+ lines.append("}")
119
+
120
+ return "\n".join(lines)
121
+
122
+
123
+ @dataclass
124
+ class DataDef:
125
+ """A data definition.
126
+
127
+ Example:
128
+ # data $str = { b "hello world", b 0 }
129
+ data = DataDef("str")
130
+ data.add_string("hello world")
131
+ data.add_bytes(0)
132
+ """
133
+
134
+ name: str
135
+ export: bool = False
136
+ align: int | None = None
137
+ section: str | None = None
138
+ items: list = field(default_factory=list)
139
+
140
+ def add_bytes(self, *values: int) -> DataDef:
141
+ """Add byte values."""
142
+ self.items.append(("b", list(values)))
143
+ return self
144
+
145
+ def add_halfs(self, *values: int) -> DataDef:
146
+ """Add half-word (16-bit) values."""
147
+ self.items.append(("h", list(values)))
148
+ return self
149
+
150
+ def add_words(self, *values: int) -> DataDef:
151
+ """Add word (32-bit) values."""
152
+ self.items.append(("w", list(values)))
153
+ return self
154
+
155
+ def add_longs(self, *values: int | Global) -> DataDef:
156
+ """Add long (64-bit) values or global references."""
157
+ self.items.append(("l", list(values)))
158
+ return self
159
+
160
+ def add_singles(self, *values: float) -> DataDef:
161
+ """Add single-precision float values."""
162
+ formatted = [f"s_{v}" for v in values]
163
+ self.items.append(("s", formatted))
164
+ return self
165
+
166
+ def add_doubles(self, *values: float) -> DataDef:
167
+ """Add double-precision float values."""
168
+ formatted = [f"d_{v}" for v in values]
169
+ self.items.append(("d", formatted))
170
+ return self
171
+
172
+ def add_string(self, s: str) -> DataDef:
173
+ """Add a string (as bytes). Does NOT add null terminator."""
174
+ # Escape the string for QBE
175
+ escaped = s.replace("\\", "\\\\").replace('"', '\\"')
176
+ self.items.append(("b", [f'"{escaped}"']))
177
+ return self
178
+
179
+ def add_zero(self, count: int) -> DataDef:
180
+ """Add zero-initialized padding."""
181
+ self.items.append(("z", count))
182
+ return self
183
+
184
+ def add_ref(self, name: str, offset: int = 0) -> DataDef:
185
+ """Add a reference to another global symbol."""
186
+ if offset:
187
+ self.items.append(("l", [f"${name} + {offset}"]))
188
+ else:
189
+ self.items.append(("l", [f"${name}"]))
190
+ return self
191
+
192
+ def emit(self) -> str:
193
+ """Emit this data definition as QBE IL."""
194
+ lines = []
195
+
196
+ if self.export:
197
+ lines.append("export")
198
+
199
+ if self.section:
200
+ lines.append(f'section "{self.section}"')
201
+
202
+ align_str = f"align {self.align} " if self.align else ""
203
+ lines.append(f"data ${self.name} = {align_str}{{")
204
+
205
+ for item in self.items:
206
+ if item[0] == "z":
207
+ lines.append(f"\tz {item[1]}")
208
+ else:
209
+ type_char, values = item
210
+ vals_str = " ".join(str(v) for v in values)
211
+ # Handle empty values (shouldn't happen but be safe)
212
+ if vals_str:
213
+ lines.append(f"\t{type_char} {vals_str},")
214
+
215
+ lines.append("}")
216
+
217
+ return "\n".join(lines)
218
+
219
+
220
+ @dataclass
221
+ class Module:
222
+ """A complete QBE IL module.
223
+
224
+ Contains type definitions, data definitions, and functions.
225
+ """
226
+
227
+ types: list[AggregateType] = field(default_factory=list)
228
+ data: list[DataDef] = field(default_factory=list)
229
+ functions: list[Function] = field(default_factory=list)
230
+
231
+ def add_type(self, type_def: AggregateType) -> Module:
232
+ """Add a type definition."""
233
+ self.types.append(type_def)
234
+ return self
235
+
236
+ def add_data(self, data_def: DataDef) -> Module:
237
+ """Add a data definition."""
238
+ self.data.append(data_def)
239
+ return self
240
+
241
+ def add_function(self, func: Function) -> Module:
242
+ """Add a function definition."""
243
+ self.functions.append(func)
244
+ return self
245
+
246
+ def emit(self) -> str:
247
+ """Emit this module as QBE IL."""
248
+ parts = []
249
+
250
+ for t in self.types:
251
+ parts.append(t.emit())
252
+
253
+ for d in self.data:
254
+ parts.append(d.emit())
255
+
256
+ for f in self.functions:
257
+ parts.append(f.emit())
258
+
259
+ return "\n\n".join(parts)