inkwell

repository·master·Indexed 25 days ago

https://github.com/thedan64/inkwell

A safe Rust wrapper for LLVM (via llvm-sys) designed to help developers build programming languages. It provides a strongly typed interface that replicates LLVM IR's typing at compile time. Supports LLVM versions 11 through 22 via Cargo feature flags and requires Rust 1.85 or higher.

Tokens
9.5K
Snippets
27
Records
57
Agent score
84%

What's inside inkwell

  1. Install Inkwell via Cargo

    master

    To use Inkwell, add it to your Cargo.toml. You must specify a single LLVM version feature flag that matches the LLVM version installed on your system. The feature flag follows the pattern llvmM-0 where M is the major LLVM version.

    Supported LLVM versions range from 11 to 22.

    [dependencies]
    inkwell = { version = "0.8.0", features = ["llvm22-1"] }
  2. Run the Kaleidoscope example

    master

    To run the Kaleidoscope implementation example, you must have the LLVM version installed on your system. Use the following command to run the example with the appropriate LLVM feature flag automatically detected from your system's llvm-config:

    cargo run --example kaleidoscope --features llvm$(llvm-config --version | sed 's/\./-/;s/\.[0-9]*//')

    Once running, you can interact with the language via a prompt. To exit the prompt, type exit or quit.

  3. Configure LLVM version via features

    master

    Inkwell requires you to specify a desired LLVM version at compile and link time using Cargo features. You must provide exactly one LLVM feature flag. Supported features include:

    • llvm11-0
    • llvm12-0
    • llvm13-0
    • llvm14-0
    • llvm15-0
    • llvm16-0
    • llvm17-0
    • llvm18-1
    • llvm19-1
    • llvm20-1
    • llvm21-1
    • llvm22-1
  4. JIT Compile and Execute a Function with Inkwell

    master

    This example demonstrates how to use Inkwell to JIT compile a simple sum function. It involves creating a Context, a Module, a Builder, and an ExecutionEngine. Note that calling functions retrieved from the execution engine requires unsafe blocks because the signature cannot be verified at compile time.

    use inkwell::builder::Builder;
    use inkwell::context::Context;
    use inkwell::execution_engine::{ExecutionEngine, JitFunction};
    use inkwell::module::Module;
    use inkwell::OptimizationLevel;
    
    use std::error::Error;
    
    /// Convenience type alias for the `sum` function.
    ///
    /// Calling this is innately `unsafe` because there's no guarantee it doesn't
    /// do `unsafe` operations internally.
    type SumFunc = unsafe extern "C" fn(u64, u64, u64) -> u64;
    
    struct CodeGen<'ctx> {
        context: &'ctx Context,
        module: Module<'ctx>,
        builder: Builder<'ctx>,
        execution_engine: ExecutionEngine<'ctx>,
    }
    
    impl<'ctx> CodeGen<'ctx> {
        fn jit_compile_sum(&self) -> Option<JitFunction<'_, SumFunc>> {
            let i64_type = self.context.i64_type();
            let fn_type = i64_type.fn_type(&[i64_type.into(), i64_type.into(), i64_type.into()], false);
            let function = self.module.add_function("sum", fn_type, None);
            let basic_block = self.context.append_basic_block(function, "entry");
    
            self.builder.position_at_end(basic_block);
    
            let x = function.get_nth_param(0)?.into_int_value();
            let y = function.get_nth_param(1)?.into_int_value();
            let z = function.get_nth_param(2)?.into_int_value();
    
            let sum = self.builder.build_int_add(x, y, "sum").unwrap();
            let sum = self.builder.build_int_add(sum, z, "sum").unwrap();
    
            self.builder.build_return(Some(&sum)).unwrap();
    
            unsafe { self.execution_engine.get_function("sum").ok() }
        }
    }
    
    fn main() -> Result<(), Box<dyn Error>> {
        let context = Context::create();
        let module = context.create_module("sum");
        let execution_engine = module.create_jit_execution_engine(OptimizationLevel::None)?;
        let codegen = CodeGen {
            context: &context,
            module,
            builder: context.create_builder(),
            execution_engine,
        };
    
        let sum = codegen.jit_compile_sum().ok_or("Unable to JIT compile `sum`")?;
    
        let x = 1u64;
        let y = 2u64;
        let z = 3u64;
    
        unsafe {
            println!("{} + {} + {} = {}", x, y, z, sum.call(x, y, z));
            assert_eq!(sum.call(x, y, z), x + y + z);
        }
    
        Ok()
    }
  5. Use Kaleidoscope debug flags

    master

    The Kaleidoscope executable supports several flags to display intermediate compiler stages (Lexer, Parser, and Compiler output). Use these flags to inspect how your input is being processed.

    Available flags:

    • --dc: Display Compiler output (IR generation).
    • --dp: Display Parser output (AST structure).
    • --dl: Display Lexer output (Token stream).

    Example of running with all debug flags enabled:

  6. Create an ArrayValue

    master

    An ArrayValue represents a block of contiguous constants or variables in LLVM IR. You can create one from an existing LLVMValueRef or by creating a new constant array from a type and a slice of values.

    Safety Note:

    • When using new(value), the provided LLVMValueRef must be valid and of type array.
    • When using new_const_array(ty, values), all values must be of the same type as ty.
  7. Get `FloatType` metadata and properties

    master

    Use the following methods to inspect a FloatType:

    • size_of(): Returns an IntValue representing the size of the type in bytes (architecture dependent).
    • get_bit_width(): Returns the bit width of the floating-point type (e.g., 32 for f32, 64 for f64).
    • get_context(): Returns a reference to the Context that created this type.
    • print_to_string(): Returns an LLVMString containing the LLVM definition of the type.
  8. Create a FunctionType using ArrayType as a return type

    master

    You can define a function where the return type is an ArrayType using the .fn_type() method.

    use inkwell::context::Context;
    
    let context = Context::create();
    let i8_type = context.i8_type();
    let i8_array_type = i8_type.array_type(3);
    
    // Create a function type that returns an array of 3 i8s, with no parameters
    let fn_type = i8_array_type.fn_type(&[], false);
    use inkwell::context::Context;
    
    let context = Context::create();
    let i8_type = context.i8_type();
    let i8_array_type = i8_type.array_type(3);
    let fn_type = i8_array_type.fn_type(&[], false);
  9. Create a pointer type from an ArrayType

    master

    You can create a PointerType that points to an ArrayType.

    Note: The .ptr_type(address_space) method is deprecated for LLVM versions 15.0 and newer because LLVM no longer differentiates between pointer types. For modern LLVM versions, use Context::ptr_type instead.

    use inkwell::context::Context;
    use inkwell::AddressSpace;
    
    let context = Context::create();
    let i8_type = context.i8_type();
    let i8_array_type = i8_type.array_type(3);
    
    // Deprecated: Use Context::ptr_type instead for LLVM 15+
    let i8_array_ptr_type = i8_array_type.ptr_type(AddressSpace::default());
    use inkwell::context::Context;
    use inkwell::AddressSpace;
    
    let context = Context::create();
    let i8_type = context.i8_type();
    let i8_array_type = i8_type.array_type(3);
    let i8_array_ptr_type = i8_array_type.ptr_type(AddressSpace::default());
  10. Create a declaration for an intrinsic

    master

    Use get_declaration(module, param_types) to create or insert the declaration of an intrinsic within a specific Module.

    Important: For overloaded intrinsics, you must provide the correct param_types (as a slice of BasicTypeEnum) to identify the specific overload. If you call this on an overloaded intrinsic with an empty param_types slice, it will return None to prevent an LLVM crash.

    use inkwell::{intrinsics::Intrinsic, context::Context};
    
    let trap_intrinsic = Intrinsic::find("llvm.trap").unwrap();
    
    let context = Context::create();
    let module = context.create_module("trap");
    let builder = context.create_builder();
    let void_type = context.void_type();
    let fn_type = void_type.fn_type(&[], false);
    let fn_value = module.add_function("trap", fn_type, None);
    let entry = context.append_basic_block(fn_value, "entry");
    
    // Create the declaration (empty slice for non-overloaded)
    let trap_function = trap_intrinsic.get_declaration(&module, &[]).unwrap();
    
    builder.position_at_end(entry);
    builder.build_call(trap_function, &[], "trap_call");
  11. Use `FloatType` to define floating-point types

    master

    FloatType represents the type of a floating-point constant or variable in LLVM. It can be used to create function types, arrays, vectors, and constant values.

    use inkwell::context::Context;
    
    let context = Context::create();
    let f32_type = context.f32_type();