Search Documentation

Cranelift Documentation

Latest: v14.2 Rust JIT Compiler

A fast, reliable code generator focused on WebAssembly and written in Rust.

On this page

    What is Cranelift?

    Cranelift is an optimizing compiler backend developed by the Bytecode Alliance, written in Rust. Unlike traditional compiler backends such as LLVM that focus primarily on ahead-of-time compilation, Cranelift specifically targets just-in-time compilation with an explicit goal of achieving short compile times.

    The project started in 2016 (formerly known as Cretonne) and has evolved into a robust, secure, and relatively simple compiler framework that converts target-independent intermediate representations into executable machine code. Cranelift currently supports multiple instruction set architectures including x86-64, AArch64, RISC-V, and IBM z/Architecture.

    Quick Info

    • Project: Cranelift
    • Organization: Bytecode Alliance
    • Language: Rust
    • Focus: Just-in-Time (JIT) compilation
    • Architectures: x86-64, AArch64, RISC-V, IBM z/Architecture
    • GitHub: bytecodealliance/wasmtime/cranelift

    Key Features

    Fast Compilation

    Optimized for short compilation times, making it ideal for just-in-time compilation scenarios.

    Multiple ISA Support

    Supports x86-64, AArch64, RISC-V, and IBM z/Architecture with a unified backend structure.

    SIMD Support

    Comprehensive support for vectorized operations across supported architectures.

    Security-Focused

    Built with security considerations from the ground up, essential for WebAssembly use cases.

    CLIF IR

    A target-independent intermediate representation designed for efficient compilation.

    ISLE DSL

    Instruction Selection Language for simplified backend development and maintenance.

    Cranelift vs. LLVM

    Feature Cranelift LLVM
    Primary Focus JIT compilation speed Thorough optimization
    Implementation Language Rust C++
    Code Size Smaller (~100K LOC) Larger (~1M+ LOC)
    Compilation Speed Faster Slower
    Code Quality Good Excellent
    Memory Usage Lower Higher
    Target Architecture Support 4 primary targets Many targets

    Supported Architectures

    Cranelift supports the following target architectures:

    x86-64

    Full support for x86-64 architecture with a focus on modern instruction set extensions like AVX and SSE.

    Complete

    AArch64

    Support for ARM 64-bit architecture with NEON SIMD operations and other ARMv8 features.

    Complete

    RISC-V

    Support for the open RISC-V instruction set architecture, including RV64GC features.

    In Progress

    IBM z/Architecture

    Support for s390x mainframe architecture with vector facility.

    In Progress

    Getting Started

    To use Cranelift in your Rust project, add the appropriate crates to your Cargo.toml:

    Cargo.toml
    [dependencies]
    cranelift = "0.100.0"
    cranelift-module = "0.100.0"
    cranelift-jit = "0.100.0"

    Here's a simple example of using Cranelift to generate a function that adds two integers:

    main.rs
    use cranelift::prelude::*;
    use cranelift_module::{Linkage, Module};
    use cranelift_jit::{JITBuilder, JITModule};
    
    // Create JIT instance
    let mut builder = JITBuilder::new(cranelift_module::default_libcall_names());
    let mut module = JITModule::new(builder);
    let mut ctx = module.make_context();
    
    // Define the function signature
    let mut sig = module.make_signature();
    sig.params.push(AbiParam::new(types::I32));
    sig.params.push(AbiParam::new(types::I32));
    sig.returns.push(AbiParam::new(types::I32));
    
    // Create the function and build it
    let func_id = module.declare_function("add", Linkage::Export, &sig).unwrap();
    ctx.func.signature = sig;
    
    let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig);
    let mut builder_ctx = FunctionBuilderContext::new();
    let mut builder = FunctionBuilder::new(&mut func, &mut builder_ctx);
    
    // Build the function body
    let block0 = builder.create_block();
    builder.append_block_params_for_function_params(block0);
    builder.switch_to_block(block0);
    
    let param0 = builder.block_params(block0)[0];
    let param1 = builder.block_params(block0)[1];
    let sum = builder.ins().iadd(param0, param1);
    builder.ins().return_(&[sum]);
    
    builder.finalize();
    
    // Finalize and execute
    module.define_function(func_id, &ctx).unwrap();
    module.finalize_definitions().unwrap();
    
    // Get and call the JIT-compiled function
    let code = module.get_finalized_function(func_id);
    let add = unsafe { std::mem::transmute::<_, fn(i32, i32) -> i32>(code) };
    let result = add(5, 6);
    assert_eq!(result, 11);

    Important Note

    The std::mem::transmute function used in this example is unsafe. In production code, ensure you're managing memory safely and following Rust's safety guidelines.

    Quick Start Steps

    1

    Add dependencies

    Include Cranelift crates in your Cargo.toml file

    2

    Create a JIT compiler

    Initialize the JIT module and context

    3

    Define function signature

    Create the function type with parameters and return types

    4

    Build the function

    Use the function builder to construct the function body

    5

    Compile and execute

    Finalize the function and call it

    Architecture

    Cranelift uses a multi-phase compilation pipeline that balances compilation speed with code quality. The design prioritizes modularity, allowing different parts of the pipeline to be reused or replaced as needed.

    Compilation Pipeline

    1. Frontend - Converts source format (like WebAssembly) to Cranelift IR (CLIF)
      • Parses input code and translates to CLIF
      • Performs initial validation
      • Sets up control flow graph
    2. Middle-end - Performs target-independent optimizations
      • Constant folding and propagation
      • Dead code elimination
      • Common subexpression elimination
      • Control flow optimizations
    3. Backend - Target-specific processing
      • Instruction selection using ISLE patterns
      • Register allocation
      • Instruction scheduling
      • Code emission

    Compilation Pipeline

    Source
    Frontend
    CLIF IR
    Optimizer
    Backend
    Machine Code

    CLIF Intermediate Representation

    Cranelift IR (CLIF) is a low-level, SSA-based intermediate representation that serves as the core of Cranelift's compilation process. It has the following key characteristics:

    • Static Single Assignment (SSA) form - Each variable is assigned exactly once
    • Control Flow Graph - Code is organized into basic blocks with explicit control flow
    • Rich type system - Supports various integer types, floating point, SIMD vectors, and references
    • Platform-neutral - Independent of any specific hardware architecture
    • Human-readable text format - Makes debugging and development easier
    Example CLIF IR
    function %add(i32, i32) -> i32 {
    block0(v0: i32, v1: i32):
        v2 = iadd v0, v1
        return v2
    }

    Performance

    Cranelift is designed with performance in mind, particularly focusing on compilation speed over absolute code quality. This makes it especially suitable for JIT compilation scenarios where compilation time is part of the end-user experience.

    Compilation Time Comparison

    Cranelift vs. LLVM (lower is better)

    Small Functions
    15ms
    55ms
    Medium Functions
    35ms
    85ms
    Large Functions
    60ms
    100ms
    Cranelift
    LLVM

    Key Performance Characteristics

    • Fast compilation times - 3-5x faster than LLVM for many workloads
    • Lower memory footprint - Uses significantly less memory during compilation
    • Good but not optimal code quality - Generated code performs well but prioritizes compilation speed over maximum optimization
    • Predictable latency - More consistent compilation times across different code patterns

    Examples

    Explore these examples to understand how to use Cranelift in different scenarios.

    Basic Arithmetic Function

    This example shows how to create a function that computes (a + b) * c:

    arithmetic.rs
    use cranelift::prelude::*;
    use cranelift_module::{Linkage, Module};
    use cranelift_jit::{JITBuilder, JITModule};
    
    fn main() {
        // Create JIT instance
        let mut builder = JITBuilder::new(cranelift_module::default_libcall_names());
        let mut module = JITModule::new(builder);
        let mut ctx = module.make_context();
    
        // Define the function signature (a: i32, b: i32, c: i32) -> i32
        let mut sig = module.make_signature();
        sig.params.push(AbiParam::new(types::I32));
        sig.params.push(AbiParam::new(types::I32));
        sig.params.push(AbiParam::new(types::I32));
        sig.returns.push(AbiParam::new(types::I32));
    
        // Create the function
        let func_id = module.declare_function("compute", Linkage::Export, &sig).unwrap();
        ctx.func.signature = sig;
    
        let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig);
        let mut builder_ctx = FunctionBuilderContext::new();
        let mut builder = FunctionBuilder::new(&mut func, &mut builder_ctx);
    
        // Build function body: (a + b) * c
        let block0 = builder.create_block();
        builder.append_block_params_for_function_params(block0);
        builder.switch_to_block(block0);
    
        let a = builder.block_params(block0)[0];
        let b = builder.block_params(block0)[1];
        let c = builder.block_params(block0)[2];
        
        let sum = builder.ins().iadd(a, b);
        let product = builder.ins().imul(sum, c);
        
        builder.ins().return_(&[product]);
        builder.finalize();
    
        // Finalize and execute
        module.define_function(func_id, &ctx).unwrap();
        module.finalize_definitions().unwrap();
    
        // Get and call the JIT-compiled function
        let code = module.get_finalized_function(func_id);
        let compute = unsafe { std::mem::transmute::<_, fn(i32, i32, i32) -> i32>(code) };
        
        let result = compute(5, 6, 7);
        println!("(5 + 6) * 7 = {}", result); // Should print 77
    }

    Conditional Branching

    This example demonstrates how to create a function with conditional branching to implement a simple max function:

    max.rs
    use cranelift::prelude::*;
    use cranelift_module::{Linkage, Module};
    use cranelift_jit::{JITBuilder, JITModule};
    
    fn main() {
        // Create JIT instance
        let mut builder = JITBuilder::new(cranelift_module::default_libcall_names());
        let mut module = JITModule::new(builder);
        let mut ctx = module.make_context();
    
        // Define the function signature: max(a: i32, b: i32) -> i32
        let mut sig = module.make_signature();
        sig.params.push(AbiParam::new(types::I32));
        sig.params.push(AbiParam::new(types::I32));
        sig.returns.push(AbiParam::new(types::I32));
    
        // Create the function
        let func_id = module.declare_function("max", Linkage::Export, &sig).unwrap();
        ctx.func.signature = sig;
    
        let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig);
        let mut builder_ctx = FunctionBuilderContext::new();
        let mut builder = FunctionBuilder::new(&mut func, &mut builder_ctx);
    
        // Create blocks for the function
        let entry_block = builder.create_block();
        let a_gt_b_block = builder.create_block();
        let b_gt_a_block = builder.create_block();
    
        // Fill entry block
        builder.append_block_params_for_function_params(entry_block);
        builder.switch_to_block(entry_block);
        
        let a = builder.block_params(entry_block)[0];
        let b = builder.block_params(entry_block)[1];
        
        // Compare a and b
        let condition = builder.ins().icmp(IntCC::SignedGreaterThan, a, b);
        builder.ins().brif(condition, a_gt_b_block, &[], b_gt_a_block, &[]);
    
        // Fill a > b block
        builder.switch_to_block(a_gt_b_block);
        builder.ins().return_(&[a]);
        
        // Fill b >= a block
        builder.switch_to_block(b_gt_a_block);
        builder.ins().return_(&[b]);
        
        builder.finalize();
    
        // Finalize and execute
        module.define_function(func_id, &ctx).unwrap();
        module.finalize_definitions().unwrap();
    
        // Get and call the JIT-compiled function
        let code = module.get_finalized_function(func_id);
        let max_func = unsafe { std::mem::transmute::<_, fn(i32, i32) -> i32>(code) };
        
        println!("max(5, 10) = {}", max_func(5, 10)); // Should print 10
        println!("max(20, 7) = {}", max_func(20, 7)); // Should print 20
    }

    Community

    Cranelift is developed as part of the Bytecode Alliance, a nonprofit organization dedicated to creating secure new software foundations built on WebAssembly and WebAssembly System Interface (WASI).

    How to Contribute

    1

    Find an Issue

    Look for issues labeled with "good first issue" or "help wanted"

    2

    Join the Discussion

    Engage in Zulip chat to discuss your intended contributions

    3

    Submit a PR

    Fork the repository, make changes, and submit a pull request

    4

    Review Process

    Work with maintainers to refine your contribution

    Frequently Asked Questions

    What is the difference between Cranelift and LLVM?

    Cranelift and LLVM have different design goals. LLVM prioritizes generating the most optimized code possible, often at the expense of compilation time. Cranelift, on the other hand, focuses on fast compilation times for just-in-time compilation scenarios, with good but not exhaustive optimizations.

    Cranelift is also written in Rust (versus LLVM's C++), has a smaller codebase, and is designed with a focus on security and WebAssembly compilation use cases.

    Which architectures does Cranelift support?

    Cranelift currently supports:

    • x86-64
    • AArch64 (ARM 64-bit)
    • RISC-V
    • IBM z/Architecture (s390x)

    Support for additional architectures is planned or in development.

    How do I use Cranelift in my Rust project?

    To use Cranelift in a Rust project, add the appropriate crates to your Cargo.toml:

    [dependencies]
    cranelift = "0.100.0"
    cranelift-module = "0.100.0"
    cranelift-jit = "0.100.0"

    You can then use Cranelift to generate code at runtime. The cranelift-jit crate provides a simple API for JIT compilation.

    Is Cranelift used in any production systems?

    Yes, Cranelift is used in several production systems:

    • Wasmtime - A standalone WebAssembly runtime
    • Fastly's Compute@Edge - A serverless compute platform
    • Firefox - Used for some WebAssembly compilation
    • Various WebAssembly-based projects - Including blockchain systems and edge computing platforms

    What is ISLE and how is it used in Cranelift?

    ISLE (Instruction Selection/Lowering Expressions) is a domain-specific language (DSL) used in Cranelift for instruction selection during code generation. It allows Cranelift developers to write declarative rules for matching and transforming IR patterns into target-specific machine instructions.

    ISLE makes it easier to develop and maintain different architecture backends by providing a concise, declarative way to specify instruction selection rules. This approach helps ensure correctness and reduces the complexity of backend code.

    Thank you for your feedback!