Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ODS integration #14

Open
vchuravy opened this issue May 17, 2023 · 4 comments
Open

ODS integration #14

vchuravy opened this issue May 17, 2023 · 4 comments

Comments

@vchuravy
Copy link
Member

The Python front-end has some amount of ODS integration,
https://github.com/llvm/llvm-project/blob/main/mlir/python/mlir/dialects/ArithOps.td

It would be nice if we could do that, without having to upstream our bindings...

@jumerckx
Copy link
Collaborator

jumerckx commented Jan 5, 2024

With #19 landed, there are now wrappers for MLIR operations that are nice to use. What's still lacking compared to other projects such as xDSL and Mojo is wrappers around types.

I tried out some things and here are some thoughts I have. I would be interested in any comments or corrections on this. (cc: @mofeing @Pangoraw)

Specifically, I was thinking to have each MLIR type be a concrete instance of an abstract MLIR IR.AbstractValue. For non-parametric typedefs such as shape.size or shape.type, this is quite easy to generate from the tablegen files:

struct ShapeType <: AbstractValue
    value::MlirValue
    function ShapeType(v::AbstractValue)
        get_type(v) == parse(MLIRType, "!shape<shape>") || error("Expected ShapeType, got ", get_type(v))
        return new(v.value)
    end
end
struct Size <: AbstractValue
    value::MlirValue
    function Size(v::AbstractValue)
        get_type(v) == parse(MLIRType, "!shape<size>") || error("Expected Size, got ", get_type(v))
        return new(v.value)
    end
end

Since these particular types have mnemonics, it's clear what the assemblyformat looks like and verifications like above can be generated.

Parametric Types

A lot of types are parametric, e.g. (complex<f32>, memref<10x10xf64, stride=...>, ...). I'm not sure how these could best be represented. xDSL represents parameters as fields of a class, this could also be done but then I think we miss out on multiple dispatch, on the other hand trying to cram all parameters in a parametric Julia type probably also doesn't make sense. (dispatching on the size/stride of a memref seems a bit much).
Maybe it would make sense to have proper Julia type parameters when the parameter is a type itself.
e.g. something like:

struct MemRef<element_type>
	shape
	layout
	memory_space
end

Builtin Types

Another major annoyance is that a lot of builtin types don't have rich information in tablegen.
On discord, Mathieu Fehr (author of xDSL) told me:

When building the same thing in Python, we hardcoded the parametric builtin types! Note that f64 is non-parametric, but i8 is (because it's essentially IntegerType(8)).
From my understanding, the textual representation is only defined in C++ in the type parser here: https://github.com/llvm/llvm-project/blob/main/mlir/lib/AsmParser/TypeParser.cpp

General Constraints

If we have a way to represent (all) MLIR types and attributes, we statically type those cases where operation arguments are constrained by a single type. However, often this is not the case and arguments are constrained by for example CPred
e.g.:

def AnyFloat : Type<CPred<"::llvm::isa<::mlir::FloatType>($_self)">, "floating-point",
                    "::mlir::FloatType">;

I'm not sure how this could work in Julia. Interesting might be the IRDL dialect which is used to define new dialects at runtime. They also have a tblgen-to-irdl that's currently being developed further which could be useful as they're also trying to represent these constraints in IRDL automatically.

@mofeing
Copy link
Collaborator

mofeing commented Jan 8, 2024

xDSL represents parameters as fields of a class, this could also be done but then I think we miss out on multiple dispatch, on the other hand trying to cram all parameters in a parametric Julia type probably also doesn't make sense. (dispatching on the size/stride of a memref seems a bit much).
Maybe it would make sense to have proper Julia type parameters when the parameter is a type itself.

My question here is: Are these generated types gonna implement some methods or some form of interface on the Julia side?

I think we will be using MLIR types as objects to be passed, not as types of objects we build per se (but I might be wrong). So multiple dispatch wouldn't be needed.

@jumerckx
Copy link
Collaborator

jumerckx commented Jan 8, 2024

My question here is: Are these generated types gonna implement some methods or some form of interface on the Julia side?

Yeah, what I wrote is missing some context. As part of my master thesis, I want to provide an easier way to generate MLIR using Julia constructs (this wouldn't necessarily be upstreamed here if it deviates too much from what others are thinking of).

My current idea is to extend Pangoraw's Brutus example to support function definitions. Users could then write functions operating on MLIR Values and returning MLIR Values.
I've got a very crude gist outlining part of the idea here.
The end goal would be to have something similar to Mojo here and here.
With this idea, it would be necessary to differentiate between the different types of MlirValues, for example when overloading Base.:+ for i8 vs. f64.

Even without all that, I think the one advantage of representing MlirValues as different types (instead of all IR.Value), would be to constrain input arguments for the generated functions. e.g. index.add can only take index-typed values but from the Julia side you can pass in any value and you only find out by the MLIR verifier.
This is admittedly maybe not as important and I mainly wanted to write down my findings somewhere, I don't think this has much priority.

LMK if you think I'm overlooking stuff or you disagree. I hope to have a more concrete implementation soon :)

@mofeing
Copy link
Collaborator

mofeing commented Jan 10, 2024

Ahh that's super cool! Like writing MLIR passes and lowerings in Julia? Then yes, it might make more sense to use parametric types.

My concerns are related to runtime dispatch and overspecialization, but...

  1. Parsing MLIR types already incurs in runtime dispatch because the type of the returned object is dynamic.
  2. Overspecialization can be mitigated with @nospecialized.

So... I'm ok with using parametric types. It will be also useful for checking traits of MLIR ops.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants