Yash Nagda

Blog post

Week 1: GSoC Project Progress

Hours this week: 16

This week, I spent most of my time understanding the certik/mlir C APIs and various MLIR dialects and other related libraries. Specifically, I focused on the reasons why different compilers choose different MLIR dialects.

Progress and Understanding

Focusing on Clang IR (ClangIR / CIR), I found that it is a good starting point to understand the various MLIR dialects. Clang IR is a high-level MLIR dialect that sits between Clang’s AST and LLVM IR. The primary reason for its creation was that existing dialects were not able to handle the complexity of the C language.

Similarly, Flang has two different dialects, HLFIR and FIR. HLFIR keeps high-level Fortran semantics (array assignments, expressions), while FIR is closer to codegen and lowers toward LLVM IR. While lowering any language, any compiler has to make a lot of decisions, and having these intermediate dialects can help us to break down the decisions into smaller steps, each decision handled at a particular stage. For example consider lowering any vector addition, ie arr = arr + 1. Here, there can be various forms of decisions, like using a temporary to store the result, using vector operation or breaking it into a for loop. So, an intermediate dialect can help us to break down all of these decisions into sub-steps, each decision handled at a particular stage, and defining boundaries of where to make a decision.

So, having multiple dialects can be helpful. For LFortran, alot of these decisions are already made in the ASR passes. However, for TinyC compiler, we start with an AST, and make all decisions in the MLIR backend. So, having multiple dialects can be helpful to break down the decisions into smaller steps, each decision handled at a particular stage, and defining boundaries of where to make a decision, in the TinyC compiler. Considering these cases, I have narrowed down two primary approaches to start implementing the MLIR backend.

Approach 1: Direct lowering to LLVM IR

flowchart TB A0["Initial ASR"] --> A1["Run all Passes"] A1 --> A2["Final ASR"] A2 --> L1["LLVM-dialect MLIR"] L1 --> IR1["LLVM IR"]

Approach 2: Lowering through multiple dialects

flowchart TB B0["Initial ASR"] --> H["High-level MLIR Dialects"] H --> L2["LLVM-dialect MLIR"] L2 --> IR2["LLVM IR"]

Based on these approaches, I have made an initial implementation of the MLIR API in LFortran codebase on the following branches:

Both paths reuse LFortran’s existing LLVM IR → object code flow after MLIR translation.

I also implemented debug flags (--show-mlir, --show-mlir-llvm-dialect, --show-llvm-from-mlir) and an environment switch (USE_MLIR_Upstream=1) to compare native C lowering vs upstream MLIR passes. I have used only the necessary codefiles from the certik/mlir repository, in these implementations, to keep the codefiles smaller.

Next Steps

Next, I am starting to work on two different tasks for the MLIR backend.

  • First, to figure out the ASR dialect for the LFortran compiler.
  • Second, to figure the alternative for MachO backend, for x86_64 architecture.

Over the next few days, I will be working on these tasks, and will be updating the progress here. Also, I shall be working to add support for more operations in the new MLIR backend, as per the project’s needs.

Acknowledgments

A huge thanks to my mentors, Ondřej Čertík, Pranav Goswami, Harshita Kalani & other contributors for helping me to work on this project.