Charms.Defm.Expander (charms v0.1.4)

Expander is a module of functions to compile Elixir AST to MLIR.

In MLIR, the process of creating IR from source or AST is usually called "import". While in our case, we are following Elixir's convention to call it "expansion". While we are not just expanding Elixir's AST tree, in the end one Elixir function definition defm will be compiled to one MLIR func.func.

Working with Elixir's parallel compiler

Charms will try to work with Elixir's parallel compiler in the most well-integrated way. This allows us to compile multiple modules at the same time, which can speed up the compilation process. Another benefit is that all defm functions can also be executed at compile time, just like vanilla Elixir functions defined by def. Behind the scenes, func.func generated from defm will also be used as reference to infer and check types of a remote function calls when compiling another module. To take deep dive, Elixir has an official blog post about parallel compiler.

Cyclic dependency

Charms will disallow cyclic dependency. If module A calls module B, module B cannot call module A. This is to prevent infinite recursion and streamline compile-time features. Although this is technically possible as long as function calls and function definitions are following the same signature (like the separate compilation of C/C++ files and linkage), we still make this trade-off for simplicity.

Compile an AST without enough type information

Being an dynamic language, it is possible for Elixir to have an AST that is valid in Elixir but not in MLIR. For example, Elixir allows to define a serial of nested function calls without any return type, like a(b(c())). In Elixir everything is expression so a function call is assumed to always return a value while in MLIR it is possible to have a function call that does not return anything (equivalent to a void function in C). In this case, Charms will generate a ub.poison operation with result type none. If the result value of created ub.poison op will never be used, nothing will happen. If used, it will raise an error in later verification or passes. This is meant to allow Elixir code to work with the AST with interest only on Elixir semantic keep going without interruption as much as possible, and limit the error information to the type level, instead of leaking it to the syntax level.

Return type convention

MLIR allows multiple values as a function return, while Elixir only allows one. To keep the convention, internally Charms will always use a list to store function's return type. If the function has only one return, it will be a list with one element. If the function has no return, it will be an empty list. This should streamline the transformation and pattern matching on the function signature.

Summary

Functions

Decomposes a call into the call part and return type.

Expand an Elixir AST into MLIR.

Expand an AST into MLIR.

Functions

decompose_call_signature(call)

Decomposes a call into the call part and return type.

expand(ast, file)

Expand an Elixir AST into MLIR.

Note that this function will not do any resource management, like destroying MLIR context or module. So this function should be used with care, and mainly for testing or debugging purpose.

expand_to_mlir(ast, env, mlir_expander)

Expand an AST into MLIR.