llvm-ir

repository·main·Indexed 20 days ago

https://github.com/cdisselkoen/llvm-ir

A Rust library providing a high-level representation of LLVM IR using native Rust data structures like enums and structs. Optimized for program analysis and reading LLVM IR in pure safe Rust, it parses LLVM bitcode (.bc) and text-format IR (.ll) into a Module. It supports LLVM versions 9 through 19 via feature flags and provides abstractions such as Instruction, BasicBlock, Function, and Constant for analyzing IR without relying on FFI-heavy bindings.

Tokens
16.1K
Snippets
46
Records
80
Agent score
72%

What's inside llvm-ir

  1. How llvm-ir works: Pure Rust representation

    main

    Unlike FFI-heavy bindings like inkwell, llvm-ir is designed for consumption and analysis of LLVM IR.

    It uses the LLVM API (via llvm-sys) only during the initial parsing step to construct a rich, native Rust representation. Once the Module is created, all FFI objects are dropped, allowing you to work with the IR in pure, safe Rust.

    Key Abstractions:

    • Instruction: An enum with variants like Add, Call, and Store (rather than an opaque type).
    • BasicBlock, Function, Module: Rust structs containing complete IR information.
    • HasDebugLoc: A trait used to access DebugLoc on Instructions, Terminators, GlobalVariables, and Functions (requires -g during IR generation).
  2. Install llvm-ir via Cargo

    main

    Add llvm-ir to your Cargo.toml. You must select exactly one LLVM version feature flag corresponding to the LLVM library installed on your system. Supported versions include llvm-9 through llvm-19.

    [dependencies]
    llvm-ir = { version = "0.11.3", features = ["llvm-19"] }
  3. Generate LLVM IR for testing and debugging

    main

    When developing or debugging, you may need text-format (*.ll) files or bitcode with debug information.

    For C/C++ sources (using clang):

    • Bitcode: clang -c -emit-llvm source.c -o source.bc
    • Text IR: clang -S -emit-llvm source.c -o source.ll
    • Bitcode with debuginfo: Add the -g flag to the commands above.

    For Rust sources (using rustc):

    • Bitcode: rustc --emit=llvm-bc source.rs
    • Text IR: rustc --emit=llvm-ir source.rs
    • Bitcode with debuginfo: Add the -g flag to the command.
    # Generate bitcode from C
    clang -c -emit-llvm source.c -o source.bc
    
    # Generate text IR from C
    clang -S -emit-llvm source.c -o source.ll
  4. The Instruction enum

    main

    The Instruction enum is the core representation of all non-terminator LLVM instructions in the library. It categorizes instructions into several groups, including:

    • Integer binary ops: Add, Sub, Mul, UDiv, SDiv, URem, SRem
    • Bitwise binary ops: And, Or, Xor, Shl, LShr, AShr
    • Floating-point ops: FAdd, FSub, FMul, FDiv, FRem, FNeg
    • Vector ops: ExtractElement, InsertElement, ShuffleVector
    • Aggregate ops: ExtractValue, InsertValue
    • Memory-related ops: Alloca, Load, Store, Fence, CmpXchg, AtomicRMW, GetElementPtr
    • Conversion ops: Trunc, ZExt, SExt, FPTrunc, FPExt, FPToUI, FPToSI, UIToFP, SIToFP, PtrToInt, IntToPtr, BitCast, AddrSpaceCast
    • Other operations: ICmp, FCmp, Phi, Select, Freeze, Call, VAArg, LandingPad, CatchPad, CleanupPad (Note: Freeze requires feature = "llvm-10-or-greater").
  5. Use TypeRef for efficient type handling

    main

    A TypeRef is a lightweight, reference-counted handle (Arc<Type>) to an LLVM Type.

    Instead of passing around owned Type objects, you should use TypeRef. Cloning a TypeRef is a cheap operation that only increments a reference count, making it suitable for frequent use in IR construction. It implements Deref<Target = Type>, allowing you to access the underlying Type methods directly.

  6. Perform constant arithmetic and bitwise operations

    main

    LLVM allows constant expressions where operations are applied to other constants. The Constant enum provides variants for these operations, such as:

    Integer Arithmetic:

    • Add, Sub, Mul, UDiv, SDiv, URem, SRem (Note: division/remainder availability depends on LLVM version).

    Bitwise Operations:

    • And, Or, Xor, Shl, LShr, AShr.

    Floating-Point Operations:

    • FAdd, FSub, FMul, FDiv, FRem.

    Vector Operations:

    • ExtractElement, InsertElement, ShuffleVector.

    Conversion Operations:

    • Trunc, ZExt, SExt, FPTrunc, FPExt, FPToUI, FPToSI, UIToFP, SIToFP, PtrToInt, IntToPtr, BitCast, AddrSpaceCast.

    Most of these operations are implemented as structs containing ConstantRef operands.

  7. Categorize instructions using BinaryOp and UnaryOp

    main

    The llvm-ir crate provides BinaryOp and UnaryOp enums to categorize LLVM instructions into arithmetic, logic, and type conversion groups. This allows you to work with groups of instructions rather than individual variants.

    BinaryOp

    Used for instructions with two operands. It includes:

    • Integer binary ops: Add, Sub, Mul, UDiv, SDiv, URem, SRem
    • Bitwise binary ops: And, Or, Xor, Shl, LShr, AShr
    • Floating-point binary ops: FAdd, FSub, FMul, FDiv, FRem

    UnaryOp

    Used for instructions with a single operand. It includes:

    • AddrSpaceCast, BitCast, FNeg, FPExt, FPToSI, FPToUI, FPTrunc, Freeze (requires llvm-10-or-greater feature), IntToPtr, PtrToInt, SExt, SIToFP, Trunc, UIToFP, ZExt.

    Conversions and Traits

    • From Instruction: You can convert a BinaryOp or UnaryOp into a general Instruction using .into().
    • To Instruction: You can attempt to extract a BinaryOp or UnaryOp from an Instruction using TryFrom::try_from(inst). This returns an error if the instruction does not belong to that group.
    • Accessing Data: Both enums implement traits to access instruction components:
      • HasResult: Use .get_result() to get the &Name of the output.
      • BinaryOp trait: Use .get_operand0() and .get_operand1() to access operands.
      • UnaryOp trait: Use .get_operand() to access the single operand.
      • Typed: Use .get_type(&types) to retrieve the TypeRef for the operation.
  8. Represent LLVM IR constants with the `Constant` enum

    main

    The Constant enum is the primary way to represent literal values and constant expressions in LLVM IR. It covers simple constants (integers, floats, null pointers, aggregate zero initializers) and complex constant expressions (arithmetic, bitwise operations, vector operations, and memory-related operations like GetElementPtr).

    Key variants include:

    • Int { bits: u32, value: u64 }: Represents an integer. Note that LLVM integers are not signed/unsigned; the instruction using the constant determines this.
    • Float(Float): Represents floating-point values.
    • Null(TypeRef): A null pointer of the specified type.
    • AggregateZero(TypeRef): A zero-initialized aggregate.
    • GlobalReference { name: Name, ty: TypeRef }: A reference to a global variable or function.
    • Undef(TypeRef): An undefined value.
    • Poison(TypeRef): A poison value (available in llvm-12-or-greater).
    • BlockAddress: The address of a basic block.
    • Various operation variants like Add, Sub, Mul, BitCast, GetElementPtr, etc., which allow building constant expressions.
    use llvm_ir::Constant;
    use llvm_ir::name::Name;
    use llvm_ir::types::Types;
    
    // Example: Creating an integer constant
    let my_int = Constant::Int { bits: 32, value: 42 };
    
    // Example: Creating a global reference
    // (Assuming 'types' is an instance of Types)
    let global_ref = Constant::GlobalReference {
        name: Name::Name("my_global".to_string()),
        ty: types.ptr(),
    };
  9. Represent LLVM Metadata and Debug Information

    main

    The metadata module provides Rust data structures that map to LLVM Metadata nodes and Debug Information (DI) types. It allows for the representation of complex debug structures like compile units, types, variables, and scopes in a type-safe manner.

    Key abstractions include:

    • Metadata: The top-level container for metadata, which can be a String, a Node (referencing a MetadataNode), or a Value (an Operand).
    • MetadataNode: Represents specific LLVM metadata nodes such as Tuple, Expression, Location, or DINode.
    • MetadataRef<T>: A wrapper used to handle metadata references, allowing for either a direct MetadataNodeID or an Inline value.
    • DIType: Represents debug types, categorized into Basic, Composite, Derived, or Subroutine types.
  10. Understand the Module structure

    main

    A Module is the top-level container for an LLVM IR program. Its primary components include:

    • name: The module identifier.
    • source_file_name: The source filename.
    • data_layout: Information about the target's data layout.
    • target_triple: The target architecture triple (e.g., x86_64-pc-linux-gnu).
    • functions: A list of defined Functions.
    • func_declarations: A list of declared (but not defined) FunctionDeclarations.
    • global_vars: A list of GlobalVariables.
    • global_aliases: A list of GlobalAliases.
    • global_ifuncs: A list of GlobalIFuncs.
    • inline_assembly: Module-level inline assembly code.
    • types: A Types object facilitating lookups for all types used in the module.
  11. Reference LLVM Metadata Node structures

    main

    The MetadataNode enum represents the various types of metadata nodes defined in LLVM. Use these to construct or inspect debug information.

    Available variants:

    • Tuple(Vec<Option<Metadata>>): A collection of metadata (where None is null).
    • Expression(DIExpression): A debug expression (a vector of DWOp).
    • GlobalVariableExpression(DIGlobalVariableExpression): Links a global variable to an expression.
    • Location(DILocation): Represents source code location (line/column/scope).
    • MacroNode(DIMacroNode): Represents macro definitions or macro files.
    • Node(DINode): A generic node containing specific debug entities like Enumerator, ImportedEntity, Scope, Subprogram, or Variable.
  12. Manage constant references with `ConstantRef`

    main

    A ConstantRef is a lightweight, reference-counted handle to a Constant using Arc. It is used throughout the library to avoid expensive cloning of large constant structures.

    ConstantRef implements:

    • AsRef<Constant>: To access the underlying constant.
    • Deref<Target = Constant>: To use the constant directly.
    • Typed: To retrieve the TypeRef of the constant.
    • Display: To get the LLVM IR string representation.

    Use ConstantRef::new(constant) to wrap an owned Constant into a reference.

    use llvm_ir::Constant;
    use llvm_ir::ConstantRef;
    
    let c = Constant::Int { bits: 32, value: 100 };
    let c_ref = ConstantRef::new(c);
    
    // You can deref c_ref to access Constant methods
    println!("{}", *c_ref);