How to Lower Custom Type to LLVM Dialect


I am currently working on creating a new dialect following the Toy tutorial for a demo at my workplace. It’s a tensor-level dialect for an accelerator, and I needed to create a custom type (because the standard tensor types do not track necessary information about strides or hardware level memory spaces which I need to keep track of).

My CustomTensorType is a collection of shape information, element type, and memory space. Ultimately, I want to lower CustomTensorType to a named struct at the LLVM IR level. This must conform to the expectations of an existing backend: an LLVM struct with a specific type name, fixed number of parameters, and integer bit widths for specific elements. I noticed that in the Toy tutorial, the first six chapters use standard types, and when a new type is introduced in Ch7, the dialect hook “materializeConstant” is used so that the struct can be “generated to LLVM without any changes to our pipeline” (Ch7). I couldn’t find much help figuring out how to lower my custom type with more control. I’m not really sure where to start, and would be grateful for any help.

Are there any good tutorials or examples of lowering a custom type to the LLVM dialect, where some control is needed to output a specific representation?

Thanks in advance!


Hi Veronica,

When lowering a custom type to LLVM you just need to:

A) Add a conversion to the LLVMTypeConverter for your type.

LLVMTypeConverter converter(ctx);

converter.addConversion([](CustomTensorType type) {

  // Add the conversion here that returns the lowered form(i.e. LLVMType) of 'type'.


B) Make sure that the lowerings for users of the type properly handle/produce the lowered form of your custom type as necessary. The current lowerings to LLVM are a good source of knowledge for this.

An example in the code base is the Linalg lowering to LLVM: llvm-project/LinalgToLLVM.cpp at 0d65000e11777b8d2d6aa9f135753209593f2f00 · llvm/llvm-project · GitHub

– River


Thanks for your response! I have actually already done this, as I saw that the LLVMTypeConverter was being used to convert Standard types to LLVM dialect types. My issue now is that I’m not sure how to properly lower the declaration operation for CustomTensorType. I have a single operation called “CustomTensorTypeDecl” which declares a variable of type CustomTensorType.

Here is what the emitted MLIR for my operation looks like, if that helps:
module {
func @main() {
%0 = “myDialect.CustomTensorDecl”() {value = [1,2,3,0,0]} : () → !myDialect.CustomTensor<i64, i64, i64, i64, i64>

What I’m wondering is, how do I lower a declaration of my custom type? I’m not sure how to do this.

Thank you again,

Hi Veronica,

you also need to convert the operation itself. You can do so using the dialect conversion infrastructure. Normally, you would need a pattern that matches your custom tensor declaration and produces an equivalent data structure in the LLVM dialect. If the equivalent type is an LLVM struct, I’d expect llvm.undef followed by a sequence of llvm.insertelement to populate that struct. We do something similar for memref that gets converted to a struct, see an example here

Note that named structs are not round-trippable in MLIR at the moment, that is, you cannot parse them back. I’m working on a better way of modeling LLVM types.


Thanks very much for your reply. I have now figured out how to lower my custom type to a struct. This might be a silly question, but given my declaration operation:

%0 = “myDialect.CustomTensorDecl”() {value = [1,2,3,0,0]} : () → !myDialect.CustomTensor<i64, i64, i64, i64, i64>

I’ve been creating LLVM::ConstantOp operations with dummy values and inserting them into the struct using LLVM::InsertValueOp, but I can’t figure out how to access the raw integers (1,2,3,0,0) above given just the operation in my lowering pattern. When I try to access the elements from the CustomTensor type it seems like I can only access the element types. I really need the constant values themselves (the parameters are always constant i64s). Can anyone give me a hint? Am I doing this wrong? I can provide more of my code if that helps!

Thank you,


The integer values are stored in the attribute, depending on how you registered the operation you have different facilities to access it.

For example the Toy ConstantOp defines its value here:

  // The constant operation takes an attribute as the only input.
  let arguments = (ins F64ElementsAttr:$value);

In the code if you have a toy::ConstantOp op you can access DenseElementsAttr constantValue = op.value();
The DenseElementsAttr expose the method to get the values.

If you have a generic Operation *op you can iterate over the attribute or get them by name:

Attribute value = op->getAttr("value");