Skip to content

Commit

Permalink
add more content
Browse files Browse the repository at this point in the history
  • Loading branch information
edg-l committed Dec 9, 2024
1 parent bee0289 commit 1c15485
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 4 deletions.
124 changes: 122 additions & 2 deletions docs/src/mlir_basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ let my_u64: Type<'c> = IntegerType::new(context, 64).into();
Most operations accept or require attributes, for example the `func.func` operation requires a `StringAttribute` to define the function name, or a `TypeAttribute` to define the function type.

```rust
let my_type_attr: Attribute<'c> = TypeAttribute::new(IntegerType::new(context, 64).into()).into();
let my_type_attr: Attribute<'c> =
TypeAttribute::new(IntegerType::new(context, 64).into()).into();
```

In melior there are 4 ways to create a operation, using ods, using a method from the `dialect` melior rust module or using the operation builder.
Expand All @@ -92,7 +93,9 @@ let ptr: Value<'c> = my_alloca.result(0).unwrap().into();
This is a handcrafted API, so it may miss a lot of operations:

```rust
let my_alloca = block.append_operation(melior::dialect::llvm::alloca(context, array_size, ptr_type, location, extra_options));
let my_alloca = block.append_operation(
melior::dialect::llvm::alloca(context, array_size, ptr_type, location, extra_options)
);
// Get the returned ptr
let ptr: Value<'c> = my_alloca.result(0).unwrap().into();
```
Expand All @@ -114,4 +117,121 @@ let operation = OperationBuilder::new("foo", Location::unknown(&context))

## Region

A region holds one or multiple blocks, it depends on the operation whether there are 1 or more regions.

Usually multiple regions are used in higher level dialects, like SCF, which has while and for constructs, the CF dialect instead works
with blocks.

A region is more isolated than a block, you can easily use a value from a predecessor block within a given block, but taking a value from another region requires passing it as argument to the operation/block. This makes operations that work with regions like SCF a bit harder to work with in some contexts.

```rust
let region = Region::new();

// Add a block to the region.
let block_ref = region.append_block(Block::new(&[]));

// Here one would implement the function body

// pass the region to a operation.
let func_op = func::func(context, name, r#type, region, attributes, location);
```

## Block

A block holds a sequence of operations, control flow can only happen within the isolated operations but control returns always to the next operation within the block. A block must always have a terminator, that is a operation that has the Terminator Trait, this is usually operations that do branching like `cf.br` or that diverge `llvm.unreachable`

```rust
// To create a block we must pass the arguments it accepts, it is an array of a tuple of (Type, Location)
let block = Block::new(&[
(Type::float32(&context), Location::unknown(&context))
]);

// Get the first argument to use it in future operations:
let arg1: Value = block.argument(0)?.into();

block.append_operation(my_op_here);

```

## Example function adding 2 arguments

Here you can view how to create a function that accepts 2 arguments:

```rust
use melior::{
Context,
dialect::{arith, DialectRegistry, func},
ir::{*, attribute::{StringAttribute, TypeAttribute}, r#type::FunctionType},
utility::register_all_dialects,
};

// We need a registry to hold all the dialects
let registry = DialectRegistry::new();
// Register all dialects that come with MLIR.
register_all_dialects(&registry);

// The MLIR context, like the LLVM one.
let context = Context::new();
context.append_dialect_registry(&registry);
context.load_all_available_dialects();

// A location is a debug location like in LLVM, in MLIR all
// operations need a location, even if its "unknown".
let location = Location::unknown(&context);

// A MLIR module is akin to a LLVM module.
let module = Module::new(location);

// A integer-like type with platform dependent bit width. (like size_t or usize)
// This is a type defined in the Builtin dialect.
let index_type = Type::index(&context);

// Append a `func::func` operation to the body (a block) of the module.
// This operation accepts a string attribute, which is the name.
// A type attribute, which contains a function type in this case.
// Then it accepts a single region, which is where the body
// of the function will be, this region can have
// multiple blocks, which is how you may implement
// control flow within the function.
// These blocks each can have more operations.
module.body().append_operation(func::func(
&context,
// accepts a StringAttribute which is the function name.
StringAttribute::new(&context, "add"),
// A type attribute, defining the function signature.
TypeAttribute::new(
FunctionType::new(&context, &[index_type, index_type], &[index_type]).into()
),
{
// The first block within the region, blocks accept arguments
// In regions with control flow, MLIR leverages
// this structure to implicitly represent
// the passage of control-flow dependent values without the complex nuances
// of PHI nodes in traditional SSA representations.
let block = Block::new(&[(index_type, location), (index_type, location)]);

// Use the arith dialect to add the 2 arguments.
let sum = block.append_operation(arith::addi(
block.argument(0).unwrap().into(),
block.argument(1).unwrap().into(),
location
));

// Return the result using the "func" dialect return operation.
block.append_operation(
func::r#return( &[sum.result(0).unwrap().into()], location)
);

// The Func operation requires a region,
// we add the block we created to the region and return it,
// which is passed as an argument to the `func::func` function.
let region = Region::new();
region.append_block(block);
region
},
&[],
location,
));

assert!(module.as_operation().verify());
```
3 changes: 1 addition & 2 deletions src/codegen.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use std::{collections::HashMap, path::Path};

use melior::{
dialect::{self, llvm::r#type::pointer, ods},
ir::{r#type::IntegerType, Block, BlockRef, Location, Module, Operation, Region, Type, Value},
ir::{Block, BlockRef, Module, Region, Value},
Context,
};

Expand Down

0 comments on commit 1c15485

Please sign in to comment.