IRx¶
IRx is a Python library that lowers
ARXLang ASTx nodes to LLVM IR using
llvmlite. It provides a visitor-based codegen pipeline and a small builder API
that can translate ASTs to LLVM IR text or produce runnable executables
via clang.
Status: early but functional. Arithmetic, variables, functions, returns, basic control flow, and a few system-level expressions (e.g.
PrintExpr) are supported.
Features¶
- ASTx → LLVM IR via multiple-dispatch visitors
(
plum). - Back end: IR construction and object emission with llvmlite.
- Native build: links with
clangto produce an executable. - Optional runtime features: native capabilities are feature-gated per compilation unit instead of being linked into every binary.
- PIE-friendly objects: emits PIC-compatible objects by default to work with modern PIE-default linkers.
-
Supported nodes (subset; exact ASTx class names):
-
Literals:
LiteralInt16,LiteralInt32,LiteralString - Variables:
Variable,VariableDeclaration,InlineVariableDeclaration - Ops:
UnaryOp(++,--),BinaryOp(+ - * / < >) with simple type promotion - Flow:
IfStmt,ForCountLoopStmt,ForRangeLoopStmt - Functions:
FunctionPrototype,Function,FunctionReturn,FunctionCall -
System:
system.PrintExpr(string printing) -
Built-ins:
putchar,putchard(emitted as IR);putsdeclaration when needed. - Optional native runtimes:
libcexterns are routed through the runtime feature layer, and Arrow is now available as an optional native runtime feature.
Quick Start¶
Requirements¶
- Python 3.10 – 3.13.
- A recent LLVM/Clang toolchain available on
PATH. - A working C standard library (e.g., system libc) for linking calls like
puts. - Python deps:
llvmlite,pytest, etc. (seepyproject.toml/requirements.txt). - Note: llvmlite has specific Python/LLVM compatibility windows; see its docs.
Install (dev)¶
git clone https://github.com/arxlang/irx.git
cd irx
conda env create --file conda/dev.yaml
conda activate irx
poetry install
You can also install it from PyPI: pip install pyirx.
More details: https://irx.arxlang.org/installation/
Minimal Examples¶
1) Translate to LLVM IR (no linking)¶
import astx
from irx.builders.llvmliteir import LLVMLiteIR
builder = LLVMLiteIR()
module = builder.module()
# int main() { return 0; }
proto = astx.FunctionPrototype("main", astx.Arguments(), astx.Int32())
body = astx.Block()
body.append(astx.FunctionReturn(astx.LiteralInt32(0)))
module.block.append(astx.Function(prototype=proto, body=body))
ir_text = builder.translate(module)
print(ir_text) # LLVM IR text (str)
translate returns a str with LLVM IR. It does not produce an object file
or binary; use it for inspection, tests, or feeding another tool.
2) Build and run a tiny program that prints and returns 0¶
import astx
from irx.builders.llvmliteir import LLVMLiteIR
from irx.system import PrintExpr
builder = LLVMLiteIR()
module = builder.module()
# int main() { print("Hello, IRx!"); return 0; }
main_proto = astx.FunctionPrototype("main", astx.Arguments(), astx.Int32())
body = astx.Block()
body.append(PrintExpr(astx.LiteralString("Hello, IRx!")))
body.append(astx.FunctionReturn(astx.LiteralInt32(0)))
module.block.append(astx.Function(prototype=main_proto, body=body))
builder.build(module, "hello") # emits object + links with clang
result = builder.run() # executes ./hello → CommandResult
print(result.stdout) # "Hello, IRx!"
How It Works¶
Builders & Visitors¶
-
LLVMLiteIR(public API) -
translate(ast) -> str— generate LLVM IR text. build(ast, output_path)— emit object via llvmlite and link withclang.-
run()— execute the produced binary; returns aCommandResultwith.stdout,.stderr,.returncode, and.success. -
LLVMLiteIRVisitor(codegen) - Uses
@dispatchto visit each ASTx node type. - Maintains a value stack (
result_stack) and symbol table (named_values). - Emits LLVM IR with
llvmlite.ir.IRBuilder.
System Printing¶
PrintExpr is an astx.Expr holding a LiteralString. Its lowering:
- Create a global constant for the string (with
\0). - GEP to an
i8*pointer. - Declare (or reuse)
i32 @puts(i8*). - Call
puts.
Optional Runtime Features¶
IRx now has a generic runtime-feature system for native integrations that do not belong as handwritten LLVM container logic.
- Features are registered by name, such as
libcandarrow. - Features can declare external symbols, native C sources, objects, or static libraries.
- The linker only compiles and links artifacts for features that are active in the current compilation unit.
- This is intentionally separate from any future Arx import/module layer.
Arrow uses this path as its first substantial consumer:
- native runtime implemented in C under
src/irx/runtime/arrow/ - opaque
irx_arrow_*handles only - Arrow C Data import/export boundary
- Python
nanoarrowinstalled by default for interop and tests arx-nanoarrow-sourcesinstalled by default for native runtime builds
The current MVP is intentionally narrow: primitive int32 arrays, lifecycle
operations, inspection, and C Data roundtrip support. No full Arrow container
semantics are encoded directly in LLVM IR.
Testing¶
pytest -vv
Example style (simplified):
def test_binary_op_basic():
builder = LLVMLiteIR()
module = builder.module()
decl_a = astx.VariableDeclaration("a", astx.Int32(), astx.LiteralInt32(1))
decl_b = astx.VariableDeclaration("b", astx.Int32(), astx.LiteralInt32(2))
a, b = astx.Variable("a"), astx.Variable("b")
expr = astx.LiteralInt32(1) + b - a * b / a
proto = astx.FunctionPrototype("main", astx.Arguments(), astx.Int32())
block = astx.Block()
block.append(decl_a); block.append(decl_b)
block.append(astx.FunctionReturn(expr))
module.block.append(astx.Function(proto, block))
ir_text = builder.translate(module)
assert "add" in ir_text
Troubleshooting¶
macOS: ld: library 'System' not found¶
- Ensure Xcode Command Line Tools are installed:
xcode-select --install. - Verify
clang --versionworks. - If needed:
export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"
- CI note: macOS jobs currently run on Python 3.12 only.
Non-zero exit when function returns void¶
- Define
mainto returnInt32and emitreturn 0. Falling off the end or returningvoidcan yield an arbitrary exit code.
plum.resolver.NotFoundLookupError¶
- A visitor is missing
@dispatchor is typed against a different class than the one instantiated. Ensure signatures match the exact runtime class (e.g.,visit(self, node: PrintExpr)).
Linker or clang not found¶
- Install a recent LLVM/Clang. On Linux, use distro packages.
- On macOS, install Xcode CLT.
- On Windows, ensure LLVM’s
bindirectory is onPATH.
PIE mismatch (R_X86_64_32 ... can not be used when making a PIE object)¶
- This usually means your linker is enforcing PIE while the object was compiled with non-PIE relocations.
- Current IRx defaults to PIC-compatible object emission, which should work with PIE-default linkers.
- If you are using an older ARX/IRX stack, update first.
- If you must link externally as a workaround, use:
clang -no-pie file.o -o program
Platform Notes¶
- Linux & macOS: supported and used in CI.
- Windows: expected to work with a proper LLVM/Clang setup; consider it
experimental.
builder.run()will executehello.exe.
Roadmap¶
- More ASTx coverage (booleans, arrays, structs, varargs/options).
- Richer stdlib bindings (I/O, math).
- Optimization toggles/passes.
- Alternative backends and/or JIT runner.
- Better diagnostics and source locations in IR.
- Expand optional Apache Arrow runtime support: nullable arrays, more primitive types, streams, and higher-level handles.
Contributing¶
Please see the contributing guide. Add tests for new features and keep visitors isolated (avoid special-casing derived nodes inside generic visitors).
Acknowledgments¶
- LLVM and llvmlite for the IR infrastructure.
- ASTx / ARXLang for the front-end AST.
- Contributors and users experimenting with IRx.
License¶
License: BSD-3-Clause. See LICENSE.