Melior Documentation

repository·main·Indexed 19 days ago

https://github.com/mlir-rs/melior

High-level, safe Rust bindings for the MLIR C API, designed to represent MLIR's ownership model using Rust's type system. Melior provides tools for context initialization, dialect registration, and IR construction. It includes the melior-macro crate for automating the generation of Rust code for MLIR dialects from TableGen files, including support for operations, types, attributes, and passes. Requires LLVM/MLIR 22.

Tokens
15.8K
Snippets
52
Records
69
Agent score
67%

What's inside Melior

  1. Safety and Technical Notes in Melior

    main

    Users should be aware of the following technical constraints and safety considerations:

    Ownership and Borrowing

    • Melior uses &T instead of &mut T for MLIR objects to manage the loose ownership model of the MLIR C API.
    • Warning: IR object references returned from functions that move ownership of arguments (e.g., Region::append_block()) might become invalid later because the API uses &self rather than &mut self to return them.

    Dialects and Runtime Errors

    • Accessing operations, types, or attributes belonging to dialects that are not loaded in the current Context can lead to runtime errors or segmentation faults.

    String Encoding

    • Only UTF-8 is supported for string encoding. Most string conversions between Rust and C are cached internally.
  2. Melior Naming Conventions

    main

    Melior maps MLIR C API functions to idiomatic Rust names following these rules:

    • Object Naming: Mlir<X> objects are named <X> if they have no destructor. For owned objects, the name is <X>; for borrowed references, the name is <X>Ref.
    • Creation: mlir<X>Create functions are renamed to <X>::new.
    • Accessors: mlir<X>Get<Y> functions are renamed based on the relationship to self:
      • If the result refers to &self, it is named <X>::as_<Y>.
      • Otherwise, it is named <X>::<Y> (and may include arguments like position indices).
  3. Install Melior

    main

    To add Melior to your Rust project, use cargo add to include the melior crate.

    Prerequisite: You must have LLVM/MLIR 22 installed on your system. On Linux and macOS, you can install it via Homebrew.

    cargo add melior
    
    # Install LLVM/MLIR 22 dependency
    brew install llvm@22
  4. Understand Type Inference in generated builders

    main

    When using the Melior macro system to generate code for MLIR operations, the TypeInference enum determines how the generated builder populates result types. This is crucial for understanding how the builder handles result type derivation:

    • Interface: The operation implements InferTypeOpInterface. The builder relies on the interface's logic to infer types.
    • SameOperands: The operation has the SameOperandsAndResultType trait. The builder copies the type of the first operand to all results in the first-operand setter.
    • FirstAttrDerived: The operation has the FirstAttrDerivedResultType trait. The builder derives the result type from the first attribute in the first-attribute setter.
    pub enum TypeInference {
        /// Op implements `InferTypeOpInterface` — call `enable_result_type_inference()`.
        Interface,
        /// Op has `SameOperandsAndResultType` — copy `operands[0].type()` to all results in the first-operand setter.
        SameOperands,
        /// Op has `FirstAttrDerivedResultType` — derive result type from the first attribute in the first-attribute setter.
        FirstAttrDerived,
    }
  5. Compare MLIR types for equality

    main

    MLIR types can be compared for equality using the == operator. This performs a structural equality check within the MLIR context.

    let t1 = Type::float32(&context);
    let t2 = Type::float32(&context);
    let t3 = Type::float64(&context);
    
    assert!(t1 == t2);
    assert!(t1 != t3);
  6. Reference operations using `OperationRef` and `OperationRefMut`

    main

    To work with operations without taking ownership, use OperationRef<'c, 'a> (immutable) or OperationRefMut<'c, 'a> (mutable). These types are lightweight and implement Deref and DerefMut to provide access to the underlying Operation.

    • OperationRef: Use this for read-only access to operations, such as inspecting attributes, operands, or results.
    • OperationRefMut: Use this when you need to modify the operation, such as changing operands, setting attributes, or reordering operations within a block.

    Both types allow you to access results via .result(index).

    // Using a mutable reference to modify an operation
    let mut op_ref = block.first_operation_mut().unwrap();
    op_ref.set_operand(0, new_value);
  7. How `GepIndex` works for `getelementptr` operations

    main

    When using the gep method to create an llvm.getelementptr operation, you can specify a mix of compile-time constants and runtime values using the GepIndex enum.

    • GepIndex::Const(i32): Represents a constant index known at compile time.
    • GepIndex::Value(Value): Represents an index that is a runtime Value.

    This allows you to construct complex pointer arithmetic that combines static offsets with dynamic array indexing.

    use melior::helpers::llvm::GepIndex;
    
    // Creating a GEP with a constant index followed by a runtime value index
    let indices = [
        GepIndex::Const(0),
        GepIndex::Value(runtime_index_value),
    ];
    
    let gep_result = block.gep(
        ctx,
        location,
        pointer,
        &indices,
        element_type,
    )?;
  8. Implement an MLIR pass in Rust using `RunExternalPass`

    main

    To write a custom MLIR pass in Rust, implement the RunExternalPass<'c> trait for a type that is Sized and Clone. This allows you to define the lifecycle and execution logic of a pass that can be integrated into the MLIR pipeline.

    Key lifecycle methods:

    • construct(): Called when the pass is created.
    • destruct(): Called when the pass is destroyed.
    • initialize(&mut self, context: ContextRef<'c>): Called to set up the pass using the provided MLIR context.
    • run(&mut self, operation: OperationRef<'c, '_>, pass: ExternalPass<'_>): The core logic where you manipulate the IR. The operation argument is the operation this pass is targeting, and pass provides a handle to the external pass itself.

    Alternatively, you can implement a pass using a simple closure that implements FnMut(OperationRef<'c, '_>, ExternalPass<'_>) + Clone.

    use melior::{
        ContextRef,
        ir::{OperationRef, operation::OperationLike},
        pass::{ExternalPass, RunExternalPass},
    };
    
    #[derive(Clone, Debug)]
    struct ExamplePass;
    
    impl<'c> RunExternalPass<'c> for ExamplePass {
        fn construct(&mut self) {
            println!("Constructed pass!");
        }
    
        fn initialize(&mut self, context: ContextRef<'c>) {
            println!("Initialize called!");
        }
    
        fn run(&mut self, operation: OperationRef<'c, '_>, _pass: ExternalPass<'_>) {
            operation.dump();
        }
    }
  9. Manage rewrite patterns with RewritePatternSet

    main

    A RewritePatternSet is a mutable collection of rewrite patterns associated with a specific Context. You can add individual RewritePattern objects to it using .add(). Once you have finished configuring the set, you must call .freeze() to convert it into a FrozenRewritePatternSet. This frozen set is immutable and is typically what is passed to rewrite drivers (like apply_patterns_and_fold_greedily) to perform transformations on the IR.

    let context = Context::new();
    let mut patterns = RewritePatternSet::new(&context);
    
    // Add patterns
    patterns.add(my_pattern);
    
    // Freeze the set for use in a rewrite driver
    let frozen_patterns = patterns.freeze();
  10. Build a function to add integers with Melior

    main

    This example demonstrates how to use Melior to construct an MLIR module containing a function that adds two integers using the arith and func dialects. It covers context initialization, dialect registration, type creation, and operation building within a block.

    use melior::{
        Context,
        dialect::{DialectRegistry, arith, func},
        ir::{
            attribute::{StringAttribute, TypeAttribute},
            operation::OperationLike,
            r::type::FunctionType,
            *,
        },
        utility::register_all_dialects,
    };
    
    let registry = DialectRegistry::new();
    register_all_dialects(&registry);
    
    let context = Context::new();
    context.append_dialect_registry(&registry);
    context.load_all_available_dialects();
    
    let location = Location::unknown(&context);
    let module = Module::new(location);
    
    let index_type = Type::index(&context);
    
    module.body().append_operation(func::func(
        &context,
        StringAttribute::new(&context, "add"),
        TypeAttribute::new(
            FunctionType::new(&context, &[index_type, index_type], &[index_type]).into(),
        ),
        {
            let block = Block::new(&[(index_type, location), (index_type, location)]);
    
            let sum = block
                .append_operation(arith::addi(
                    block.argument(0).unwrap().into(),
                    block.argument(1).unwrap().into(),
                    location,
                ))
                .result(0)
                .unwrap();
    
            block.append_operation(func::r#return(&[sum.into()], location));
    
            let region = Region::new();
            region.append_block(block);
            region
        },
        &[],
        location,
    ));
    
    assert!(module.as_operation().verify());
  11. Troubleshoot ODS dialect generation with OdsError

    main

    When using melior-macro for ODS (Op Definition Specification) dialect generation, you may encounter OdsError. This error type indicates issues during the macro expansion process where the provided records do not match the expected structural requirements of the MLIR dialect definitions.

    Common error variants include:

    • ExpectedSuperClass: The record was expected to be a sub-class of a specific class.
    • UnexpectedSuperClass: The record should not have been a sub-class of the specified class.
    • InvalidTrait: The record uses a trait that is not supported by the generator.
    • UnnamedDagArg: An argument in a DAG (Directed Acyclic Graph) was found without a name.
    // Possible error variants encountered during macro expansion:
    // OdsError::ExpectedSuperClass("...")
    // OdsError::InvalidTrait
    // OdsError::UnexpectedSuperClass("...")
    // OdsError::UnnamedDagArg("...")
  12. Build a simple integer addition function in MLIR

    main

    This example demonstrates how to use Melior to construct a basic MLIR module containing a function that adds two 64-bit integers using the arith and func dialects.

    let context = Context::new();
    load_all_dialects(&context);
    
    let location = Location::unknown(&context);
    let module = Module::new(location);
    
    let integer_type = IntegerType::new(&context, 64).into();
    
    let function = {
        let block = Block::new(&[(integer_type, location), (integer_type, location)]);
    
        let sum = block.append_operation(arith::addi(
            block.argument(0).unwrap().into(),
            block.argument(1).unwrap().into(),
            location,
        ));
    
        block.append_operation(func::r#return(&[sum.result(0).unwrap().into()], location));
    
        let region = Region::new();
        region.append_block(block);
        region
    };
    
    func::func(
        &context,
        StringAttribute::new(&context, "add"),
        TypeAttribute::new(
            FunctionType::new(&context, &[integer_type, integer_type], &[integer_type])
                .into(),
        ),
        function,
        &[],
        Location::unknown(&context),
    );
    
    module.body().append_operation(function);
    assert!(module.as_operation().verify());