-
Notifications
You must be signed in to change notification settings - Fork 216
Write Bril MLIR project blog post #534
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
Conversation
sampsyo
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, everybody—I'm sorry to hear that you had to abandon your proposal from #500. It sounds like this project taught you something about MLIR, but it ended up with some significant limitations: it relied on the "old SSA" form and therefore inherited the flaws in its implementation, and (even with that restriction) the round-trip fails for many benchmarks for reasons you have not fully explained yet.
To wrap up this project, can you please at least look a little closer into the failures? I think it's important to understand, even at a very high level, what has gone wrong with your current implementation of the translation.
content/blog/2025-05-13-bril-mlir.md
Outdated
| Code: | ||
| [https://github.com/bryantpark04/bril/tree/main/bril-mlir](https://github.com/bryantpark04/bril/tree/main/bril-mlir) | ||
| [https://github.com/bryantpark04/llvm-project/tree/main/mlir/examples/brilc](https://github.com/bryantpark04/llvm-project/tree/main/mlir/examples/brilc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May I suggest using a complete sentence for these links instead of just the URLs? Like, you could link the phrases "Bril2MLIR" and "a custom Bril MLIR dialect" above.
content/blog/2025-05-13-bril-mlir.md
Outdated
|
|
||
| In our original project proposal, we wanted to implement and compare two different register allocation approaches in Bril, namely the linear scan register allocation algorithm and an SSA-based register allocation described in [this paper](https://compilers.cs.uni-saarland.de/papers/ssara.pdf). Without too much effort, we were able to familiarize ourselves with the linear scan and come up with our [own implementation](https://github.com/lisarli/cs6120-tasks/blob/main/finalproject/register_allocation.cpp), utilizing our existing dataflow analysis and maintaining spilled variables (using `id` instructions before they were operated on). However, it turned out that the SSA-based register allocation mentioned in the paper was significantly harder to implement and ultimately we scrapped this project idea as a whole. | ||
|
|
||
| We decided to pivot our project to implementing translation of SSA-based Bril JSON programs into MLIR and vice versa. MLIR is a powerful and extensible compiler infrastructure which supports custom dialects and transformations across different levels of abstraction, making it a popular and flexible system. This pivot allowed us to explore the transformation from the lightweight BRIL IR to the more involved MLIR form. To do this, we created a dialect for Bril consisting of custom operations for instructions such as `id`, `nop`, and `print`, and we built on existing MLIR dialects such as `arith` for common operations like `add` and `sub`. In doing so, we were able to represent all of the core Bril instructions, including all arithmetic and control flow. The first direction of our translation was from Bril JSON to MLIR, which involved parsing the input program into a traversable representation, constructing the corresponding MLIR operations, and emitting a completed `ModuleOp` with correctly assembled functions and basic blocks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a link to MLIR when you first introduce it.
content/blog/2025-05-13-bril-mlir.md
Outdated
|
|
||
| In our original project proposal, we wanted to implement and compare two different register allocation approaches in Bril, namely the linear scan register allocation algorithm and an SSA-based register allocation described in [this paper](https://compilers.cs.uni-saarland.de/papers/ssara.pdf). Without too much effort, we were able to familiarize ourselves with the linear scan and come up with our [own implementation](https://github.com/lisarli/cs6120-tasks/blob/main/finalproject/register_allocation.cpp), utilizing our existing dataflow analysis and maintaining spilled variables (using `id` instructions before they were operated on). However, it turned out that the SSA-based register allocation mentioned in the paper was significantly harder to implement and ultimately we scrapped this project idea as a whole. | ||
|
|
||
| We decided to pivot our project to implementing translation of SSA-based Bril JSON programs into MLIR and vice versa. MLIR is a powerful and extensible compiler infrastructure which supports custom dialects and transformations across different levels of abstraction, making it a popular and flexible system. This pivot allowed us to explore the transformation from the lightweight BRIL IR to the more involved MLIR form. To do this, we created a dialect for Bril consisting of custom operations for instructions such as `id`, `nop`, and `print`, and we built on existing MLIR dialects such as `arith` for common operations like `add` and `sub`. In doing so, we were able to represent all of the core Bril instructions, including all arithmetic and control flow. The first direction of our translation was from Bril JSON to MLIR, which involved parsing the input program into a traversable representation, constructing the corresponding MLIR operations, and emitting a completed `ModuleOp` with correctly assembled functions and basic blocks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BRIL -> Bril
content/blog/2025-05-13-bril-mlir.md
Outdated
|
|
||
| ## Bril to MLIR | ||
|
|
||
| The [MLIR Toy tutorial](https://mlir.llvm.org/docs/Tutorials/Toy/Ch-2/) provides a walkthrough of building a simple language using MLIR with parsing and AST construction. Using this as a starting point, we adapted a similar structure to create a dialect for Bril by defining Bril-specific operations in TableGen, implementing the dialect in C++, and extending the MLIR generation logic to emit those custom operations. For many of the core operations like arithmetic (add, mul, etc…), control flow, and comparisons, we leveraged existing MLIR dialects like `arith` and `cf`. These dialects already provided the correct semantics and functionality for our use cases, allowing us to focus our custom dialect on features unique to Bril. While creating our own MLIR dialect for all Bril operations could have been more “proper”, it didn’t have a significant impact on the MLIR construction or the implementation for translation back to Bril. Here’s a small example of the MLIR conversion for integer arithmetic operations: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While creating our own MLIR dialect for all Bril operations could have been more “proper”,
What do you mean by this? Please expand on what you think is missing.
| builder.create<Nop>(loc); | ||
| return mlir::success(); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of listing the source code for the translation (which is quite straightforward), I think it would be more useful to see what the dialect looks like. Can you show a couple of smallish examples to compare the Bril text format with the MLIR text format for the equivalent program? I think that would illustrate more about your design decisions here.
| The final output is a complete Bril JSON program with labeled blocks, typed instructions, and SSA-correct variable naming. This approach ensures fidelity to the original semantics and enables round-tripping from Bril → MLIR → Bril for debugging, testing, or further analysis. | ||
|
|
||
| ## Challenges | ||
| One difficult aspect of this project was properly converting Phi nodes in Bril into SSA form in MLIR. In particular, we used the old version of SSA which utilized `phi` instructions as opposed to `get` and `set`. A `phi` instruction consists of the source labels and variables for each predecessor, but in MLIR, this functionality is represented using block arguments. Predecessors provide the value of each of the block arguments before entering the block containing the Phi node. To correctly represent this behavior, we needed to add block arguments to MLIR blocks containing Phi nodes, and we needed to identify predecessor blocks in which to insert arguments for branches. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you maybe show at least one example of the conversion? (Comparing the original Bril text form to the MLIR equivalent.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added an example, let us know if it should be shortened to just one function!
content/blog/2025-05-13-bril-mlir.md
Outdated
| ## Challenges | ||
| One difficult aspect of this project was properly converting Phi nodes in Bril into SSA form in MLIR. In particular, we used the old version of SSA which utilized `phi` instructions as opposed to `get` and `set`. A `phi` instruction consists of the source labels and variables for each predecessor, but in MLIR, this functionality is represented using block arguments. Predecessors provide the value of each of the block arguments before entering the block containing the Phi node. To correctly represent this behavior, we needed to add block arguments to MLIR blocks containing Phi nodes, and we needed to identify predecessor blocks in which to insert arguments for branches. | ||
|
|
||
| Another difficult aspect was correctly implementing control flow translation from Bril JSON into the MLIR block representation. In particular, we needed to devise a scheme to map Bril labels into MLIR blocks, which we then used as the targets for jumps and branches. We also needed to ensure that the targets passed in to MLIR `cf.br` and `cf.cond_br` were correct, as we initially ran into difficulties identifying the correct labels since we did not properly construct the block to label mapping. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we needed to devise a scheme
Please tell us what that scheme was.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was just a map haha - updated the post for clarity
content/blog/2025-05-13-bril-mlir.md
Outdated
|
|
||
| We checked the correctness of our implementation by running through all of the core benchmarks in the Bril repository. We compared the number of programs that ran correctly using the provided inputs in the benchmark files between a Bril-SSA-Bril round trip and a Bril-SSA-MLIR-SSA-Bril round trip, with TDCE+ running after each SSA transformation. We used the SSA round trip as a baseline instead of a regular because the old Phi-based SSA form was known to be flawed, with some programs crashing just as a result of the round trip, even with dead code elimination passes. Of the 106 programs in the core benchmark suite, 93 benchmarks survived the SSA round trip and produced a result. Then, of those 93 benchmarks, 48 survived the SSA+MLIR round trip and produced the same result as the SSA round trip. Of the 43 that did not pass the differential testing, all failed due to a crash of some sort. | ||
|
|
||
| After some investigation, we found that all of the benchmarks we manually tested had segmentation faults during the transformation into MLIR. Due to the difficulty we faced in debugging these segmentation faults, we eventually gave up on trying to fix them, and moved on to transforming MLIR programs back into Bril JSON. Interestingly, we couldn’t find an easy pattern in the segfault-causing benchmarks. We initially hypothesized that function calls may have been the issue, but we found programs without any functions other than main to also segmentation fault. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please explain more. This is maybe the most important part of your evaluation: understand what about your project was successful and what was not. The 43 benchmarks that failed this test should indicate something about the limitations of your current dialect and translation.
Unfortunately, "the crash was because of a segfault" is too generic to understand anything about what went wrong. Even knowing whether the crash came from your code (where?) or the MLIR framework itself (also where?) would say a lot about what is wrong with your translation. I think it's important to do a little bit more work here to know something about what's not working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found the causes of the errors and fixed them! All benchmarks pass now
|
Cool; it's great that you got things working for the full benchmark suite! I think the long code examples are fine; I hope they help illustrate how the dialect works. |
@bryantpark04 @dhan0779
Closes #500