# How to get Julia SSA-form IR (with Phi nodes)?

**URL:** https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897
**Category:** Internals & Design
**Created:** [April 23, 2022, 3:19pm UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897 "2022-04-23T15:19:52Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![dfdx](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dfdx/32/120_2.png) [@dfdx](https://discourse.julialang.org/u/dfdx)
#### Post date: [April 23, 2022, 3:19pm UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/1 "2022-04-23T15:19:52Z")

</div>

Given a function call, how can I get the corresponding **SSA-form IR** described [here](https://docs.julialang.org/en/v1/devdocs/ssair/)?

Alternatively, what is the easiest way - function, package or algorithm - to convert a normal lowered representation to the SSA form? I’m specifically interested in **Phi nodes**.

* * *

For the context, I’m trying to **detect loops and conditions** in lowered code. I have a prototype for loop detection using heuristics over goto statements, but the complexity grows quickly, so control flow analysis using well-known algorithms looks like a more robust approach.

---

<div class="post-metadata">

### Author: ![gbaraldi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gbaraldi/32/22101_2.png) [@gbaraldi](https://discourse.julialang.org/u/gbaraldi)
#### Post date: [April 23, 2022, 3:40pm UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/2 "2022-04-23T15:40:15Z")

</div>

Funnily enough I’m working on a similar thing. Adding a LoopInfo pass.  
`code_typed` gives you the SSAIR, also check out [https://github.com/JuliaLang/julia/pull/36832](https://github.com/JuliaLang/julia/pull/36832) which is what i’m currently rebasing.

---

<div class="post-metadata">

### Author: ![anon56330260](https://avatars.discourse-cdn.com/v4/letter/a/f07891/32.png) [@anon56330260](https://discourse.julialang.org/u/anon56330260)
#### Post date: [April 23, 2022, 4:15pm UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/3 "2022-04-23T16:15:12Z")

</div>

Well, I think SSA-formed IR is produced by Julia’s type inferencer, so you can look through type inferencer’s code to get what you want. I believe it’s located in folder `compiler/ssair` and you can find the toplevel entry of conversion algorithm there. But this only produces untyped SSA and you still need the following abstract interpreter and optimizer to get the final typed IR. If you need typed IR you’d better directly invoke `code_type`.

---

<div class="post-metadata">

### Author: ![dfdx](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dfdx/32/120_2.png) [@dfdx](https://discourse.julialang.org/u/dfdx)
#### Post date: [April 23, 2022, 11:25pm UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/4 "2022-04-23T23:25:37Z")

</div>

> [@gbaraldi](#):
>
> Funnily enough I’m working on a similar thing. Adding a LoopInfo pass.

It looks really interesting! Do you plan to publish it in a particular package or merge into julia itself? I think we discussed loop detection in a compiler pass here and there, so it might be a pretty valuable work for the community.

Regarding `@code_typed`, indeed, I see the phi nodes, but unfortunately it’s terribly slow compared to `@code_lowered` and does much more transformations than I’d like to see in the IR.

> [@anon56330260](#):
>
> But this only produces untyped SSA and you still need the following abstract interpreter and optimizer to get the final typed IR.

The untyped IR is exactly what I need. I will go through the code you linked, thanks!

* * *

I also found out that IRTools - now unmaintained, but still powerful package - re-implements many of relevant transformations in a more readable way by introducing an abstraction layer on top of the compiler primitives. I will now try to combine all the sources of knowledge to better understand how to better handle loops and conditions.

---

<div class="post-metadata">

### Author: ![gbaraldi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gbaraldi/32/22101_2.png) [@gbaraldi](https://discourse.julialang.org/u/gbaraldi)
#### Post date: [April 24, 2022, 2:04am UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/5 "2022-04-24T02:04:59Z")

</div>

I hope to make it a compile pass for julia. You could just stop the compiler pipeline in the middle, similar to how it’s done here. [Inference · The Julia Language](https://docs.julialang.org/en/v1/devdocs/inference/). Revise is nice for this because you can just comment out the compiler passes.

---

<div class="post-metadata">

### Author: ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)
#### Post date: [April 24, 2022, 5:31am UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/6 "2022-04-24T05:31:33Z")

</div>

FYI, I wrapped some tools for the development of YaoCompiler, so that you don’t have to touch a lot of internals, personally I think `IRCode` is much much easier to work with than typed code if you want to write code transformation in Julia: [CompilerPluginTools.jl/ircode.jl at master · JuliaCompilerPlugins/CompilerPluginTools.jl · GitHub](https://github.com/JuliaCompilerPlugins/CompilerPluginTools.jl/blob/master/src/ircode.jl#L8)

---

<div class="post-metadata">

### Author: ![dfdx](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dfdx/32/120_2.png) [@dfdx](https://discourse.julialang.org/u/dfdx)
#### Post date: [April 24, 2022, 10:59pm UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/7 "2022-04-24T22:59:36Z")

</div>

I should have mentioned that I’m not writing a compiler pass 🙂 I found out that surprisingly many things can be done in the “user space” by manually traversing `CodeInfo` ([example](https://github.com/dfdx/Umlaut.jl/blob/main/src/trace.jl#L208-L277)), while debugging is a way simpler. Even more surprisingly, the performance of this approach is on par with the normal compilation-execution pipeline, at least in the current generation of the Julia compiler. But of course it’s just my current use case, so I’m really thankful for all the answers!

@Roger-luo Regarding `IRCode`, I played around with it a bit and can confirm it has much more useful details such as phi nodes, CFG, etc. But `code_ircode()` also optimizes out many details that I want to control. For instance, in this simple case:

```julia
foo(x) = 2x + 1
bar(x) = foo(x ^ 2)
baz(x, y) = bar(x) * foo(y)

```

`code_lowered(baz, (Float64, Int))` returns:

```julia
1-element Vector{CodeInfo}:
 CodeInfo(
1 ─ %1 = Main.bar(x)
│ %2 = Main.foo(y)
│ %3 = %1 * %2
└── return %3
)

```

while `code_ircode(baz, (Float64, Int))` produces:

```julia
1-element Vector{Pair{IRCode, DataType}}:
1 1 ─ %1 = Base.mul_float(_2, _2)::Float64 │╻╷╷ bar
  │ %2 = Base.mul_float(2.0, %1)::Float64 ││╻╷ foo
  │ %3 = Base.add_float(%2, 1.0)::Float64 │││╻ +
  │ %4 = Base.mul_int(2, _3)::Int64 ││╻ *
  │ %5 = Base.add_int(%4, 1)::Int64 ││╻ +
  │ %6 = Base.sitofp(Float64, %5)::Float64 ││╻╷╷╷ promote
  │ %7 = Base.mul_float(%3, %6)::Float64 ││╻ *
  └── return %7 │     
   => Float64

```

At the very least, `bar()` and `foo()` have been inlined preventing me from treating them in some special way, e.g. replacing with a different call. Assuming I don’t want to dive into custom interpreters yet, is the a way to turn off optimization as we can do in `code_typed()`?

---

<div class="post-metadata">

### Author: ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)
#### Post date: [April 24, 2022, 11:42pm UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/8 "2022-04-24T23:42:23Z")

</div>

Yes you can replace the optimize pass with an empty one I think

---

<div class="post-metadata">

### Author: ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)
#### Post date: [April 24, 2022, 11:55pm UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/9 "2022-04-24T23:55:53Z")

</div>

The default Julia pass does the inlining etc. [CompilerPluginTools.jl/passes.jl at master · JuliaCompilerPlugins/CompilerPluginTools.jl · GitHub](https://github.com/JuliaCompilerPlugins/CompilerPluginTools.jl/blob/master/src/passes.jl#L13)

but you can also just replace it with an empty function [CompilerPluginTools.jl/passes.jl at master · JuliaCompilerPlugins/CompilerPluginTools.jl · GitHub](https://github.com/JuliaCompilerPlugins/CompilerPluginTools.jl/blob/master/src/passes.jl#L13)

the `code_ircode` function just use default Julia pass by default [CompilerPluginTools.jl/ircode.jl at 0dce551c0c1786776f6df1923d8d9da666d5a90d · JuliaCompilerPlugins/CompilerPluginTools.jl · GitHub](https://github.com/JuliaCompilerPlugins/CompilerPluginTools.jl/blob/0dce551c0c1786776f6df1923d8d9da666d5a90d/src/ircode.jl#L16)

you can replace it with `no_passs` for sure, this is quite useful when writing IR transforms since you need to check if the transform happens as expected

PS. I haven’t checked if things are broken on 1.8 and 1.9 btw, so just in case if you hit anything on 1.8 & 1.9 there might be things broken due to API changes

---

<div class="post-metadata">

### Author: ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)
#### Post date: [April 25, 2022, 6:02am UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/10 "2022-04-25T06:02:29Z")

</div>

> [@dfdx](#):
>
> I should have mentioned that I’m not writing a compiler pass 🙂 I found out that surprisingly many things can be done in the “user space” by manually traversing `CodeInfo` ([example](https://github.com/dfdx/Umlaut.jl/blob/main/src/trace.jl#L208-L277)), while debugging is a way simpler.

It looks like you are implementing some kind of abstract interpretation tho? This code looks very similar to the IR interpret code to me…

---

<div class="post-metadata">

### Author: ![dfdx](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dfdx/32/120_2.png) [@dfdx](https://discourse.julialang.org/u/dfdx)
#### Post date: [April 25, 2022, 10:49pm UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/11 "2022-04-25T22:49:19Z")

</div>

> [@Roger-luo](#):
>
> It looks like you are implementing some kind of abstract interpretation tho? This code looks very similar to the IR interpret code to me…

Yes! The difference though is that I never delegate execution to the Julia compiler, but instead keep the full control. The task is to trace function call execution, recording onto a “tape” so-called primitives and recursing into non-primitives, and this is the n-th incarnation of the tracer. I used to do it by adding special instructions to the IR using Cassette, IRTools and more recently Mixtape. Yet it has always been like playing volleyball on a carousel - you throw the ball in one place and hope to catch it in another, but not really controlling the thing when it’s on the air. In the current approach, I just extract the `CodeInfo` and iterate over it, invoking function calls and following `goto`s. And this simple approach works surprisingly well!

Thanks for the detailed answers and links! I think my first approach will be to look into IRTools once again since I’m already familiar with its code base and it seems to implement most things I need (not just phi nodes). And if it doesn’t work, I will dive into the `IRCode` using CompilerPluginTools, which is pretty readable too!

---

<div class="post-metadata">

### Author: ![ToucheSir](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/touchesir/32/14411_2.png) [@ToucheSir](https://discourse.julialang.org/u/ToucheSir)
#### Post date: [April 26, 2022, 1:14am UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/12 "2022-04-26T01:14:08Z")

</div>

As nice as it would be to leave IRTools behind altogether, I’ve recently been looking into some optimizations + testing for it. This is mostly out of necessity since IRTools constitutes a large chunk of Zygote’s TTFG overhead, and hedging on us being unable to move off Zygote in the short-medium term is probably wise…

---

<div class="post-metadata">

### Author: ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)
#### Post date: [April 26, 2022, 2:57am UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/13 "2022-04-26T02:57:26Z")

</div>

Yeah I think it’s gonna be harder and harder to keep IRTools updated with Julia internals, we should just move on to `IRCode` which already has a lot of toolchains around inside Julia internals. I believe it’s more because the design is not stable yet and the lack of docstrings is preventing people from using it which is why I tried to create some higher-level wrappers around it in CompilerPluginTools

---

<div class="post-metadata">

### Author: ![dfdx](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dfdx/32/120_2.png) [@dfdx](https://discourse.julialang.org/u/dfdx)
#### Post date: [April 26, 2022, 8:03am UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/14 "2022-04-26T08:03:54Z")

</div>

> [@Roger-luo](#):
>
> we should just move on to `IRCode` which already has a lot of toolchains around inside Julia internals.

But how stable is `IRCode` itself? I mean, `CodeInfo` is at least exposed via official `@code_lowered`, I thought `IRCode` is a very, very deep internal thing and can be changed significantly or dropped altogether at any time, no?

---

<div class="post-metadata">

### Author: ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)
#### Post date: [April 26, 2022, 9:22am UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/15 "2022-04-26T09:22:32Z")

</div>

I think the plan is to make IRCode stable eventually and build a tool chain around at least this is what was discussed last time about compiler plugins that I know, as now the core develop decided to support this officially. I’m not sure when would that happen tho, since I haven’t been working on this for a while. @aviatesk is the best person to ask for sure.

---

<div class="post-metadata">

### Author: ![Xuan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/xuan/32/22100_2.png) [@Xuan](https://discourse.julialang.org/u/Xuan)
#### Post date: [April 27, 2022, 12:03am UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/16 "2022-04-27T00:03:07Z")

</div>

In case this is helpful, I implemented some loop-detection utilities on top of IRTools while working on [Genify.jl](https://github.com/probcomp/Genify.jl). The relevant code is here: [https://github.com/probcomp/Genify.jl/blob/main/src/utils.jl](https://github.com/probcomp/Genify.jl/blob/main/src/utils.jl)

Note that this only detects [natural loops](https://web.cs.wpi.edu/~kal/PLT/PLT8.6.4.html), and works under the expectation that people aren’t using `@goto` statements in their code.

Would be cool if someone does something similar for IRCode as well!

---

<div class="post-metadata">

### Author: ![ToucheSir](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/touchesir/32/14411_2.png) [@ToucheSir](https://discourse.julialang.org/u/ToucheSir)
#### Post date: [April 27, 2022, 4:49am UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/17 "2022-04-27T04:49:02Z")

</div>

There’s also the built-in [https://github.com/FluxML/IRTools.jl/blob/master/src/passes/relooper.jl](https://github.com/FluxML/IRTools.jl/blob/master/src/passes/relooper.jl), but I don’t know who actually uses it in the wild…

> [@Roger-luo](#):
>
> I think the plan is to make IRCode stable eventually and build a tool chain around at least this is what was discussed last time about compiler plugins that I know, as now the core develop decided to support this officially. I’m not sure when would that happen tho…

The timelines for this really are the biggest thing. If a stable, 1.6-compatible IRCode were available today, we could jettison IRTools completely from libraries like Zygote. As-is I would imagine that is unlikely to materialize ☹

---

<div class="post-metadata">

### Author: ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)
#### Post date: [May 8, 2022, 7:04am UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/18 "2022-05-08T07:04:22Z")

</div>

> [@dfdx](#):
>
> At the very least, `bar()` and `foo()` have been inlined preventing me from treating them in some special way, e.g. replacing with a different call. Assuming I don’t want to dive into custom interpreters yet, is the a way to turn off optimization as we can do in `code_typed()` ?

I just realize you might be asking for custom intrinsic support instead of inlining in the context of AD, since I think it’s better to let DCE etc. run first before doing AD, this is because we get less and simpler code to optimize and transform for AD, currently, the way doing custom intrinsic is not very stable, but you can use a custom method table to achieve this (check what GPUCompiler does for GPUs via abstract interpreter), basically, I think for those methods with handwritten ADs (primals) they can be treated as an intrinsic in the transformation passes, then you extract a method table from CR, then implement your own absinp to produce an IRCode that treats these methods as intrinsics. Then synthesize a gradient IRCode from it.

I like your idea on linearized IR btw, I think this might lead to better-optimized and more stable AD in Julia.

---

<div class="post-metadata">

### Author: ![dfdx](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dfdx/32/120_2.png) [@dfdx](https://discourse.julialang.org/u/dfdx)
#### Post date: [May 9, 2022, 10:04pm UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/19 "2022-05-09T22:04:38Z")

</div>

Thanks for the tip! I think the AD case is actually simpler than that - treating a program as a single computation graph actually opens the door for many optimizations that are simply not possible in a general-purpose compiler. But of course there are other cases for tracing that go beyond AD, and I’m enjoy watching the progress of the compile plugins infrastructure.

> Then synthesize a gradient IRCode from it.

Is it actually possible to compile IRCode back to a normal function? I think there was a discussion about it in Slack recently, and resolution was that conversion to IRCode is a one way ticket. Or I just didn’t read it to the end?

---

<div class="post-metadata">

### Author: ![Roger-luo](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/roger-luo/32/3399_2.png) [@Roger-luo](https://discourse.julialang.org/u/Roger-luo)
#### Post date: [May 11, 2022, 4:58pm UTC](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897/20 "2022-05-11T16:58:04Z")

</div>

> [@dfdx](#):
>
> Is it actually possible to compile IRCode back to a normal function? I think there was a discussion about it in Slack recently, and resolution was that conversion to IRCode is a one way ticket. Or I just didn’t read it to the end?

It is one-way, which means you can’t transform lowered IRCode back to `CodeInfo`, but you can transform IRCode to typed CodeInfo then go through Julia codegen, which will give you the executable.

> [@dfdx](#):
>
> I think the AD case is actually simpler than that - treating a program as a single computation graph actually opens the door for many optimizations that are simply not possible in a general-purpose compiler. But of course there are other cases for tracing that go beyond AD, and I’m enjoy watching the progress of the compile plugins infrastructure.

I didn’t mean a general-purpose compiler, it’s just about reducing the general SSA to a single basic block so you get the simple computational graph. And it’s gonna be simpler if you get the representation after compiler optimization.

[Next page](https://discourse.julialang.org/t/how-to-get-julia-ssa-form-ir-with-phi-nodes/79897.md?page=2)
