Defining Dialects

Intro

Dialects are semantically-complete purpose-focused pieces of intermediate representation. An example of a TIR dialect is riscv dialect, that contains operations, types and algorithms to accurately represent RISC-V ISA on both assembly and binary levels. Dialects can both co-exist together and progressively lower one to another.

Defining Operations

TIR provides a crate to help developers easily define new operations.

Below is an example of a simple operation:

#![allow(unused)]
fn main() {
use crate::builtin::DIALECT_NAME;
use tir_core::{Op, OpImpl, Type};
use tir_macros::{Assembly, Op};

#[derive(Op, Assembly)]
#[operation(name = "super", known_attrs(value: IntegerAttr))]
pub struct SuperOp {
    #[operand]
    operand1: Register,
    #[ret_type]
    return_type: Type,
    r#impl: OpImpl,
}

}

The helper macros would implement the following things for you:

  • Getters and setters for operands (i.e., get_operand1, set_operand1)
  • Getters for regions
  • Default assembly parser and printer.

Additional methods can be defined manually by implementing impl SuperOp {...}.

Field Configurations

#[operation(..., known_attrs(attr1: AttrType))]

Defines an attribute of a specific type. Any type used in attributes must be convertible to tir_core::Attribute enum. Also implements basic attribute getters and setters.

#[operand]

Defines an operand of a specific type. Also implements basic setters and getters for the operand.

#[region(single_block, no_args)]

Defines a region. Also defines a basic getter get_<field_name>_region. If single_block argument is passed, also defines a get_<field_name> single block getter. If both single_block and no_args are passed, a default region will be created during operation building.