Skip to content

yudongusa/LLVM-in-Rust

Repository files navigation

LLVM-in-Rust

A pure Rust re-implementation of LLVM — no C++, no FFI, no llvm-sys. The full compiler pipeline from LLVM IR through optimization passes to machine code generation is implemented entirely in safe Rust.

Why?

The official LLVM is a C++ library that Rust projects consume through a fragile C FFI wrapper (llvm-sys). This project explores what a clean, idiomatic Rust implementation of the same pipeline looks like: arena-free ownership, type-safe index handles, trait-based pass infrastructure, and zero unsafe code in the core IR.

Quick Start

use llvm_in_rust_ir::{Builder, Context, Linkage, Module};
use llvm_in_rust_transforms::{build_pipeline, OptLevel};

fn main() {
    // Build: i32 @add(i32 %a, i32 %b) { entry: %s = add i32 %a, %b  ret i32 %s }
    let mut ctx = Context::new();
    let mut module = Module::new("example");
    let mut b = Builder::new(&mut ctx, &mut module);
    let i32_ty = b.ctx.i32_ty;
    b.add_function(
        "add",
        i32_ty,
        vec![i32_ty, i32_ty],
        vec!["a".into(), "b".into()],
        false,
        Linkage::External,
    );
    let entry = b.add_block("entry");
    b.position_at_end(entry);
    let a = b.get_arg(0);
    let bv = b.get_arg(1);
    let sum = b.build_add("s", a, bv);
    b.build_ret(sum);
    drop(b);

    // Run -O2 optimizations
    let mut pm = build_pipeline(OptLevel::O2);
    pm.run_until_fixed_point(&mut ctx, &mut module, 8);
    println!(
        "Optimized: {} functions, {} blocks",
        module.functions.len(),
        module.functions[0].blocks.len()
    );
}

See the src/llvm/examples/ directory for more runnable examples including dead-code elimination (opt_pipeline.rs) and a full hello-world IR program (hello_world.rs).

Status

LLVM-in-Rust is in a production-readiness stage, not a general drop-in LLVM replacement. Milestones A-U in the production roadmap are complete; the 2026-05-26 audit added release-blocking follow-up Milestones V-Z before any general production-ready declaration.

Use case Status Boundary
Constrained production pilots Supported with controls Trusted LLVM 15+ opaque-pointer IR, pinned commits/releases, green release-blocking CI, and an explicitly supported backend/object-format combination.
General LLVM replacement Not supported yet The project still has open V-Z audit follow-ups, backend contract gaps, and RC burn-in requirements.
Untrusted or adversarial input Not supported without sandboxing Parser, optimizer, codegen, and JIT paths must run behind external process/container isolation, CPU/memory limits, and JIT disablement where inputs are not trusted.

The workspace test inventory changes frequently and is intentionally not hard-coded here. Treat CI plus the validation commands in docs/production_operations.md as the current source of truth for release-blocking quality status.

See docs/production_support_boundaries.md for the public support contract, API stability matrix, and backend/platform boundaries. See docs/sandbox_deployment.md before routing untrusted or tenant-controlled input through parser, optimization, codegen, object emission, or JIT paths.


Versioning

LLVM-in-Rust follows Semantic Versioning with an explicit pre-1.0 stability policy:

  • 0.x.y releases: no public API stability guarantee. Any 0.x minor-version bump may include breaking public API changes as the IR model, pass interfaces, and backend traits continue to mature.
  • 1.0.0 readiness: the project will not declare a stable 1.0 API until the production-readiness roadmap's V-Z follow-up milestones are complete, release-candidate burn-in has passed, and maintainers have signed off at least one constrained production pilot with documented fallback.
  • Post-1.0 releases: standard SemVer rules apply. Breaking API changes are reserved for major versions and include a documented deprecation/migration cycle.

The pre-1.0 API stability matrix lives in docs/production_support_boundaries.md. Release notes live in CHANGELOG.md; the v0.1.0 GitHub release links to the 0.1.0 changelog entry.


Performance

Benchmarks compare this project against LLVM 19.1.7 (Homebrew) on a 15-function representative module (src/llvm-bench/fixtures/sample.ll, ~340 lines, integer/FP/memory ops).

Run the benchmarks yourself:

cargo bench -p llvm-in-rust-bench

Golden codegen gate

The deterministic codegen corpus is locked by object checksums and runs in CI. Unexpected drift should be investigated before updating baselines; intentional updates use scripts/update_golden_codegen.sh --bless and require maintainer sign-off. See docs/golden_codegen_gate.md.

Benchmarks use Criterion and run on stable Rust; no nightly-only bench harness is required.

Results (x86_64 macOS, Apple M-series, release build)

Pipeline stage This project LLVM 19 tool LLVM 19 (processing only¹)
Parse .ll → IR 183 µs llvm-as: 116 ms wall ~36 ms
Print IR → .ll 33 µs llvm-dis: 82 ms wall ~2 ms
mem2reg pass 80 µs² opt -passes=mem2reg: 98 ms wall ~10 ms
DCE pass 55 µs² opt -passes=dce: 90 ms wall ~2 ms
x86_64 codegen 116 µs llc -O0: 108 ms wall ~18 ms
Builder API (2 fns) 2.4 µs

¹ LLVM tool wall-clock includes ~80–90 ms process startup + dynamic library loading. "Processing only" subtracts the baseline measured with a trivial single-function input. This makes the comparison more representative of in-process library use.

² Mem2reg and DCE benchmarks include parsing the fixture on each iteration; net pass-only time is wall time minus the 183 µs parse cost.

Interpretation

  • This project runs in-process with zero startup cost, which explains most of the wall-clock advantage. LLVM tools (llvm-as, opt, llc) pay 80–90 ms every invocation just to load the shared libraries — dwarfing the actual work on a small module.

  • Processing-time comparison: even after subtracting startup overhead, this implementation is meaningfully faster for small-to-medium modules (~5–125×). The primary reasons:

    • Focused implementation without LLVM's plugin, debug, metadata, and attribute infrastructure
    • Vec-based flat arenas vs. LLVM's layered allocator hierarchy
    • No LLVM pass-manager bookkeeping (analyses, invalidation, statistics)
  • Scalability caveat: LLVM is highly optimised for large programs (hundreds of thousands of IR instructions). At that scale LLVM's mature optimisations will outperform this project. These benchmarks target the small-module embedded-library use case.

  • Code quality: the repository has deterministic codegen, large-program, and performance-budget gates, but it does not claim broad LLVM -O2 parity. Treat output quality as scoped to the backend and workload contract documented in the production support boundaries.


LLVM IR compatibility

This project is a standalone re-implementation, not a wrapper around LLVM. "LLVM compatibility" means compatibility with the LLVM IR text format (.ll files) that real LLVM tools (clang, opt, llc, llvm-as) read and write.

Supported LLVM versions: 15 – 22

LLVM release .ll files this project emits .ll files from that LLVM version
≤ 14 Not readable by LLVM ≤14 (opaque-pointer syntax) Not parseable (typed pointer syntax unsupported)
15 Compatible Compatible (opaque pointers on by default)
16 Compatible Compatible
17 Compatible Compatible
18 – 22 Compatible Compatible

This table records the parser/printer compatibility target, not a claim about the latest upstream LLVM release.

Key IR feature versions

IR feature This project First LLVM version
Opaque pointers (ptr) Yes — only pointer representation Default in LLVM 15; exclusive in LLVM 17
Typed pointers (i32*, float*) No — not supported Removed in LLVM 17
bfloat (BFloat16) type Yes LLVM 7
Scalable vectors (<vscale x N x T>) Yes LLVM 11 (SVE/RVV)
nuw / nsw / exact flags Yes LLVM 2.9
Fast-math flags (nnan, ninf, fast, …) Yes LLVM 3.1
Tail-call kinds (tail, musttail, notail) Yes LLVM 3.0
fneg instruction Yes LLVM 9
freeze instruction Yes LLVM 10
vp.* vector-predication intrinsics Yes — recognized as intrinsic calls and lowered through implemented vector paths LLVM 11
Inline assembly Partial — parse/print plus nop template lowering on x86-64/AArch64 LLVM IR feature
Atomics (fence, cmpxchg, atomicrmw) Yes — backend coverage varies by target and operation LLVM IR feature
EH paths (invoke, landingpad, funclet pads) Partial — IR round-trip and metadata/object support are present; runtime language interop remains experimental LLVM IR feature

What this means in practice

  • Reading LLVM 15+ output: .ll files generated by clang -S -emit-llvm with LLVM 15 or later are accepted for the implemented subset. Unsupported IR constructs should fail with diagnostics rather than being treated as a production-supported path.
  • Reading LLVM ≤14 output: Not supported. LLVM 14 and earlier emit typed-pointer syntax (i32*, i8**) that this parser does not recognise. Pass those files through opt --opaque-pointers -S with LLVM 15 to upgrade them first.
  • Interop with LLVM tools: .ll files printed by llvm_ir::Printer are valid input to opt, llc, and llvm-as version 15 or later.

Binary format (LRIR) — not LLVM bitcode

The llvm-bitcode crate implements a custom compact binary format called LRIR, identified by the magic bytes LRIR and format version 1. This is not the LLVM bitcode format (.bc) and cannot be processed by LLVM tools (llvm-as, llvm-dis, llvm-link). LRIR exists solely for fast round-trip serialization within this project. Use the text format (.ll) for interoperability with external LLVM tools.


Crate layout

llvm-ir/          Core IR types: types, values, instructions, modules, builder, printer
llvm-ir-parser/   .ll text format parser
llvm-analysis/    CFG, dominator tree, use-def chains, loop info
llvm-transforms/  Optimization passes: mem2reg, DCE, const folding/prop, inliner
llvm-codegen/     Target-independent codegen: legalization, isel, regalloc, scheduling
llvm-target-x86/  x86_64 backend
llvm-target-arm/  AArch64 backend
llvm-bitcode/     Binary IR format (LRIR) reader/writer
llvm/             Top-level crate re-exporting everything

Crate dependency graph (arrows = "depends on"):

llvm-ir-parser ──┐
llvm-analysis  ──┤
                 ├──► llvm-ir
llvm-transforms──┤
llvm-codegen   ──┘
    │
    ├──► llvm-target-x86
    └──► llvm-target-arm
llvm-bitcode ──► llvm-ir
llvm ──► all of the above

Prerequisites

  • Rust 1.75 or later (2021 edition)
  • Cargo (ships with Rust)

Install Rust via rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Building

# Clone the repository
git clone https://github.com/yudongusa/LLVM-in-Rust.git
cd LLVM-in-Rust

# Debug build (fast compile, includes assertions)
cargo build

# Release build (optimised)
cargo build --release

# Type-check only (fastest feedback loop)
cargo check

Build a specific crate

cargo build -p llvm-in-rust-ir
cargo build -p llvm-in-rust-transforms
cargo build -p llvm-in-rust-target-x86

Testing

# Run all tests
cargo test

# Run tests for a single crate
cargo test -p llvm-in-rust-ir
cargo test -p llvm-in-rust-ir-parser
cargo test -p llvm-in-rust-analysis
cargo test -p llvm-in-rust-transforms
cargo test -p llvm-in-rust-codegen
cargo test -p llvm-in-rust-target-x86
cargo test -p llvm-in-rust-target-arm
cargo test -p llvm-in-rust-bitcode

# Run a named test
cargo test roundtrip_add
cargo test mem2reg_simple_alloca

Linting and formatting

cargo clippy --all-targets   # must be warning-free
cargo fmt --check            # check formatting
cargo fmt                    # auto-format

Fuzzing

# Parser/optimizer/codegen fuzzing via llvm-stress corpus + cargo-fuzz
./scripts/llvm_stress_fuzz.sh --iterations 10000 --max-total-time 300

# Semantic differential fuzzing with CSmith (default 1000 programs)
./scripts/csmith_test.sh --count 1000

# Package a failing IR input for crash triage
scripts/reduce_ci_failure.sh \
  --input failing.ll \
  --predicate './repro.sh {{input}}' \
  --evidence-dir ci-failures/example

# Sanitizer/UB hardening lanes (nightly Rust required)
scripts/sanitizer_matrix.sh asan-core
scripts/sanitizer_matrix.sh miri-core
scripts/sanitizer_matrix.sh tsan-core

# Platform matrix lanes
scripts/platform_matrix.sh host-core
scripts/platform_matrix.sh target-x86_64
scripts/platform_matrix.sh target-aarch64
scripts/platform_matrix.sh target-rv64gc
scripts/platform_matrix.sh known-issues

# Interoperability conformance lanes
scripts/interoperability_conformance.sh link
scripts/interoperability_conformance.sh debug
scripts/interoperability_conformance.sh abi
scripts/interoperability_conformance.sh mixed
scripts/interoperability_conformance.sh release

# Release artifact provenance dry-run
scripts/release_artifacts.sh dry-run

Crash and miscompilation triage follows the SLO and evidence package process in docs/crash_triage_runbook.md.

The release sanitizer/UB matrix and suppression policy are documented in docs/sanitizer_ub_gate.md.

Supported platform tiers, Tier-1/Tier-2 expectations, and the release sign-off checklist are documented in docs/platform_support_policy.md. Tracked platform exceptions live in docs/platform_known_issues.json.

The M2 linker/debugger/ABI conformance suite and release sign-off process are documented in docs/interoperability_conformance.md.

Reproducible release artifacts, checksum publication, detached signatures, and release provenance are covered by the Release Artifact Provenance workflow and docs/release_artifact_pipeline.md.

Production operations, observability, incident response, contributor triage paths, and the full runbook index are documented in docs/production_operations.md.


Installation

This project is a Cargo workspace of library crates. Add whichever layers you need to your project's Cargo.toml:

[dependencies]
# Core IR types only
llvm-ir = { package = "llvm-in-rust-ir", git = "https://github.com/yudongusa/LLVM-in-Rust" }

# IR + .ll text parser
llvm-ir-parser = { package = "llvm-in-rust-ir-parser", git = "https://github.com/yudongusa/LLVM-in-Rust" }

# IR + optimization passes
llvm-transforms = { package = "llvm-in-rust-transforms", git = "https://github.com/yudongusa/LLVM-in-Rust" }

# Full pipeline including x86_64 backend
llvm = { package = "llvm-in-rust", git = "https://github.com/yudongusa/LLVM-in-Rust" }

For a path dependency (local development):

[dependencies]
llvm-ir = { package = "llvm-in-rust-ir", path = "../LLVM-in-Rust/src/llvm-ir" }

Usage

Build IR programmatically

use llvm_ir::{Builder, Context, IntPredicate, Linkage, Module, Printer};

fn main() {
    let mut ctx = Context::new();
    let mut module = Module::new("example");
    let mut b = Builder::new(&mut ctx, &mut module);

    // define i32 @max(i32 %x, i32 %y)
    let _fid = b.add_function(
        "max",
        b.ctx.i32_ty,
        vec![b.ctx.i32_ty, b.ctx.i32_ty],
        vec!["x".into(), "y".into()],
        false,
        Linkage::External,
    );

    let entry   = b.add_block("entry");
    let ret_x   = b.add_block("ret_x");
    let ret_y   = b.add_block("ret_y");

    b.position_at_end(entry);
    let x    = b.get_arg(0);
    let y    = b.get_arg(1);
    let cond = b.build_icmp("cond", IntPredicate::Sgt, x, y);
    b.build_cond_br(cond, ret_x, ret_y);

    b.position_at_end(ret_x);
    b.build_ret(x);

    b.position_at_end(ret_y);
    b.build_ret(y);

    // Print LLVM IR text
    let ir = Printer::new(b.ctx).print_module(b.module);
    println!("{ir}");
}

Output:

; Module: example

define i32 @max(i32 %x, i32 %y) {
entry:
  %cond = icmp sgt i32 %x, %y
  br i1 %cond, label %ret_x, label %ret_y
ret_x:
  ret i32 %x
ret_y:
  ret i32 %y
}

Parse a .ll file

use llvm_ir_parser::parse;
use llvm_ir::Printer;

fn main() {
    let src = std::fs::read_to_string("input.ll").unwrap();
    let (ctx, module) = parse(&src).expect("parse error");

    // Round-trip back to text
    let ir = Printer::new(&ctx).print_module(&module);
    println!("{ir}");
}

Run optimization passes

use llvm_ir::{Builder, Context, Linkage, Module};
use llvm_transforms::{
    ConstProp, DeadCodeElim, Inliner, Mem2Reg,
    FunctionPass, ModulePass, PassManager,
    pass::FunctionPassAdapter,
};

fn main() {
    // ... build or parse your module into (ctx, module) ...

    let mut pm = PassManager::new();
    pm.add(Box::new(FunctionPassAdapter { pass: Mem2Reg }));
    pm.add(Box::new(FunctionPassAdapter { pass: ConstProp }));
    pm.add(Box::new(FunctionPassAdapter { pass: DeadCodeElim }));
    pm.add(Box::new(Inliner::new(/* size_limit */ 100)));

    pm.run(&mut ctx, &mut module); // runs to fixed-point
}

Emit x86_64 machine code

use llvm_ir::{Builder, Context, Linkage, Module};
use llvm_codegen::{emit_object, ObjectFormat};
use llvm_target_x86::X86Backend;

fn main() {
    // ... build or parse your module ...

    let backend = X86Backend::new();
    let obj = emit_object(&ctx, &module, &backend, ObjectFormat::Elf)
        .expect("codegen failed");

    std::fs::write("output.o", &obj.bytes).unwrap();
}

Save/load binary IR (LRIR format)

use llvm_bitcode::{read_bitcode, write_bitcode};

// Serialise
let bytes = write_bitcode(&ctx, &module).unwrap();
std::fs::write("module.lrir", &bytes).unwrap();

// Deserialise
let bytes = std::fs::read("module.lrir").unwrap();
let (ctx, module) = read_bitcode(&bytes).unwrap();

Architecture overview

IR design

All IR lives in SSA form. Ownership is structured around three stack values:

  • Context — interned type table and constant pool
  • Module — functions and global variables
  • Function — flat instruction pool; BasicBlock stores Vec<InstrId> indices into it

All handles (TypeId, BlockId, InstrId, ArgId, ConstId, GlobalId, FunctionId) are Copy u32 newtypes. Cross-entity references use ValueRef, a Copy enum:

pub enum ValueRef {
    Instruction(InstrId),
    Argument(ArgId),
    Constant(ConstId),
    Global(GlobalId),
}

There is no Rc, no RefCell, no unsafe, and no external arena crate.

Pass infrastructure

FunctionPass and ModulePass are simple traits:

pub trait FunctionPass {
    fn run_on_function(&mut self, ctx: &mut Context, func: &mut Function) -> bool;
    fn name(&self) -> &'static str;
}

FunctionPassAdapter lifts any FunctionPass into a ModulePass. PassManager sequences passes and iterates until no pass reports a change.

Code generation

Target backends implement IselBackend. The pipeline is:

IR  →  legalize  →  instruction selection (DAG lowering)
    →  register allocation (linear scan)
    →  instruction scheduling
    →  machine-code encoding  →  ELF / Mach-O object file

Usage example: embedding as a JIT backend

The examples/tikv_jit crate shows how a project like TiKV could embed LLVM-in-Rust to JIT-compile coprocessor filter predicates — with no dependency on the LLVM C++ library.

What it compiles

// Clipped-difference range filter: return (value - threshold) when value > threshold, else 0.
i64 eval_predicate(i64 value, i64 threshold) {
    if (value > threshold) return value - threshold;
    return 0;
}

Full pipeline in ~60 lines of Rust

use llvm_codegen::{
    emit_object, isel::IselBackend, ObjectFormat,
    regalloc::{apply_allocation, compute_live_intervals, insert_spill_reloads, linear_scan},
};
use llvm_ir::{Builder, Context, IntPredicate, Linkage, Module, Printer};
use llvm_target_x86::{instructions::{MOV_LOAD_MR, MOV_STORE_RM}, X86Backend, X86Emitter};
use llvm_transforms::{pass::PassManager, ConstProp, DeadCodeElim, Mem2Reg};

// 1. Build IR
let mut ctx = Context::new();
let mut module = Module::new("tikv_coprocessor");
let i64_ty = ctx.i64_ty;
{
    let mut bldr = Builder::new(&mut ctx, &mut module);
    bldr.add_function("eval_predicate", i64_ty,
        vec![i64_ty, i64_ty], vec!["value".into(), "threshold".into()],
        false, Linkage::External);

    let entry = bldr.add_block("entry");
    bldr.position_at_end(entry);
    let value = bldr.get_arg(0);
    let threshold = bldr.get_arg(1);
    let cond = bldr.build_icmp("cond", IntPredicate::Sgt, value, threshold);
    let then_bb = bldr.add_block("then");
    let else_bb = bldr.add_block("else");
    bldr.build_cond_br(cond, then_bb, else_bb);

    let merge_bb = bldr.add_block("merge");
    bldr.position_at_end(then_bb);
    let diff = bldr.build_sub("diff", value, threshold);
    bldr.build_br(merge_bb);

    bldr.position_at_end(else_bb);
    bldr.build_br(merge_bb);

    bldr.position_at_end(merge_bb);
    let zero = bldr.const_i64(0);
    let result = bldr.build_phi("result", i64_ty, vec![(diff, then_bb), (zero, else_bb)]);
    bldr.build_ret(result);
}

// 2. Print IR (optional — for logging / debugging)
let ir_text = Printer::new(&ctx).print_module(&module);

// 3. Optimise
let mut pm = PassManager::new();
pm.add_function_pass(Mem2Reg);
pm.add_function_pass(ConstProp);
pm.add_function_pass(DeadCodeElim);
pm.run(&mut ctx, &mut module);

// 4. x86-64 codegen
let func = module.functions.iter().find(|f| !f.is_declaration).unwrap();
let mut mf = X86Backend.lower_function(&ctx, &module, func);
let intervals = compute_live_intervals(&mf);
let mut result = linear_scan(&intervals, &mf.allocatable_pregs);
insert_spill_reloads(&mut mf, &mut result, MOV_LOAD_MR, MOV_STORE_RM);
apply_allocation(&mut mf, &result);

// 5. Emit ELF object file
let mut emitter = X86Emitter::new(ObjectFormat::Elf);
let obj = emit_object(&mf, &mut emitter);
std::fs::write("/tmp/eval_predicate.o", obj.to_bytes()).unwrap();
// Link with: cc /tmp/eval_predicate.o your_main.o -o binary

Running the example

cargo run -p tikv_jit
# Prints the IR before/after optimisation, then writes /tmp/eval_predicate.o
objdump -d /tmp/eval_predicate.o   # inspect generated x86-64 assembly

Linking emitted .o files

Linux (ELF):

# Relocatable link
ld -r /tmp/eval_predicate.o -o /tmp/eval_predicate.linked.o

# Link an executable with system startup/runtime
cc /tmp/eval_predicate.o -o /tmp/eval_predicate_bin

Integrated Assembler (Direct MC Emission)

The default backend flow already uses an integrated assembler path: machine IR is encoded directly into section bytes and serialized to object files (ELF / Mach-O / COFF) without generating textual assembly as an intermediate.

llvm-codegen exposes this stage explicitly:

use llvm_codegen::{assemble_with_report, ObjectFormat};
use llvm_target_x86::X86Emitter;

let mut emitter = X86Emitter::new(ObjectFormat::Elf);
let assembled = assemble_with_report(&mf, &mut emitter);
std::fs::write("/tmp/out.o", &assembled.bytes)?;
println!("mc bytes: {}", assembled.report.bytes);

Inline assembly support is intentionally small in v0.1.x: textual IR can parse and print LLVM-style inline asm calls such as call void asm sideeffect "nop", ""(), and x86-64/AArch64 lowering recognizes nop templates for direct machine-byte emission. Constraint solving, output operands, and register allocation across general asm operands remain outside the production support contract.

Exception-handling IR support covers the core invoke/landingpad and funclet shapes used by Itanium-style and Windows-style unwinding: the parser, printer, binary IR round-trip, CFG successors, value rewriting, LSDA/xdata builders, and tier-1 backends preserve the relevant edges and metadata. Cross-language runtime throw/catch behavior remains experimental and is scoped in docs/unwind_compatibility_matrix.md.

macOS (Mach-O):

# Relocatable link
ld -r /tmp/eval_predicate.o -o /tmp/eval_predicate.linked.o

# Link an executable with system toolchain
cc /tmp/eval_predicate.o -o /tmp/eval_predicate_bin

Windows (COFF):

# Produce COFF object by selecting ObjectFormat::Coff in the emitter.
# Then inspect sections:
llvm-readobj --sections .\eval_predicate.obj

# Future milestone: link with debug info to PDB
lld-link /DEBUG /ENTRY:main /SUBSYSTEM:CONSOLE /OUT:eval_predicate.exe .\eval_predicate.obj

DWARF debug sections (.debug_line / .debug_info / .debug_abbrev)

When LLVM IR carries !dbg / !DILocation metadata, ELF object emission adds coherent DWARF sections: .debug_line, .debug_info, and .debug_abbrev. Quick checks:

readelf -S /tmp/eval_predicate.o | grep -E 'debug_(line|info|abbrev)'
llvm-dwarfdump --verify /tmp/eval_predicate.o
llvm-dwarfdump --debug-line /tmp/eval_predicate.o

Current limitations:

  • Single compile unit per object/function emission path
  • Single source-file path per function (source_filename)
  • Minimal CU attributes (enough for valid line mapping and section coherence)

Windows debug milestone (.debug$S / CodeView)

COFF emission now supports a first Windows debug milestone: when debug metadata is present, emitted COFF objects include .debug$S with CV_SIGNATURE_C13 and a minimal DEBUG_S_SYMBOLS subsection carrying source/line hints.

For architecture and staged roadmap details, see: docs/windows_debug_codeview.md.

Adding to your own project

Add the crates you need to your Cargo.toml. For a local checkout use path dependencies:

[dependencies]
llvm-ir         = { package = "llvm-in-rust-ir", path = "path/to/LLVM-in-Rust/src/llvm-ir" }
llvm-transforms = { package = "llvm-in-rust-transforms", path = "path/to/LLVM-in-Rust/src/llvm-transforms" }
llvm-codegen    = { package = "llvm-in-rust-codegen", path = "path/to/LLVM-in-Rust/src/llvm-codegen" }
llvm-target-x86 = { package = "llvm-in-rust-target-x86", path = "path/to/LLVM-in-Rust/src/llvm-target-x86" }
# optional: llvm-target-arm  for AArch64 output
# optional: llvm-ir-parser   to accept .ll text files as input
# optional: llvm-bitcode     to read/write the LRIR binary format

RFC Process

Breaking changes to the public API, IR semantics (InstrKind/TypeData variants), object-file format compatibility, or mandatory trait methods require an RFC before implementation. An RFC is a short Markdown document that describes the motivation, the precise before/after API change, a migration guide, and alternatives considered. RFCs go through at least 7 days of open review followed by a 3-day Final Comment Period before a maintainer merges them. See docs/rfcs/README.md for the full process, triggers, and submission instructions.


Contributing

  1. Fork the repository and create a feature branch.
  2. Make your changes; ensure cargo clippy --all-targets is warning-free and cargo fmt --check passes.
  3. Add tests covering new behaviour.
  4. Open a pull request against main.

Bug reports and feature requests go to the issue tracker.


Security

See SECURITY.md for how to report a vulnerability.


License

Licensed under the Apache License, Version 2.0.

About

Rust support of LLVM

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors