# Tria - the Elixir optimizer

**URL:** <https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250>\
**Category:** Announcing\
**Tags:** library, optimization, tria\
**Created:** [January 18, 2023, 1:13am UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250 "2023-01-18T01:13:03Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![hst337](https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/h/41988e/32.png) [@hst337](https://forum.elixirforum.com/u/hst337)\
**Post date:** [January 18, 2023, 1:13am UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/1 "2023-01-18T01:13:03Z")

</div>

# Tria

[https://github.com/hissssst/tria](https://github.com/hissssst/tria)

An optimizing compiler for Elixir language

### Public and alpha 🎉

I’ve made this repository public to finally get some rest and find other developers who may be interested in participating in this project (it sounds so naive when I write it) before getting hands on the second iteration.

### Current state

**!!!Unstable!!!**

Tria passes most of the tests in `Phoenix`, `Ecto`, `Plug` and other projects, while it may fail to compile other projects. You’re welcome to compile projects using `Tria` and fill an issue if something fails. This kind of help would be heavily appreciated.

Even without considering this, Tria compiler lacks some important features like incremental compilation, warning handling and stuff.

**However, you can expect the stable version during 2023-ish**

### Current features

1. Compile-time evaluation.  
Yes, as simple as it sounds, Elixir and Erlang were incapable of doing this, since they support runtime recompilation. Ahead-of-time nature of Tria allowed to evaluate pure statement in compile time

2. Enum fusion  
Join multiple `Enum.map`s, optimize `for` loops to finally be efficient and other magic.

I haven’t benchmarked these yet, but these optimizations actually work and (more importantly) work _correctly in most cases_

### Planned features

1. Broad documentation (like [pathex](https://github.com/hissssst/pathex) has)
2. Inlining
3. Hot-reloading support
4. Peephole optimizations for common suboptimal code.
5. `map.field` handling (did you know that it is 2-3 times slower than `Map.fetch!(map, :field)`?)
6. Type-checker integration

* * *

# Help wanted

If you’re interested in optimizing compiler development, you’re welcome to take a look around, poke some things, check some stuff. I know that some Brazilian universities are working in Elixir-related compiler development, and I hope we can collaborate on Tria someday

**PS: Questions are appreciated**

* * *

UPD: License is going to change in a near future

---

<div class="post-metadata">

**Author:** ![sergio](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/sergio/32/26966_2.png) [@sergio](https://forum.elixirforum.com/u/sergio)\
**Post date:** [January 18, 2023, 1:44am UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/2 "2023-01-18T01:44:47Z")

</div>

I’m still a little confused what this project is/does.

Can you try to restate what it is and what it does in simpler terms?

---

<div class="post-metadata">

**Author:** ![hst337](https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/h/41988e/32.png) [@hst337](https://forum.elixirforum.com/u/hst337)\
**Post date:** [January 18, 2023, 1:47am UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/3 "2023-01-18T01:47:03Z")

</div>

“Optimizer” means a thing which makes something more efficient.

So is `Tria`. It simply makes your code more efficient in terms of time and memory when you add it to your project as a dependency (and a list of compilers). It makes `Enum` calls and `for` loops faster, it removes unnecessary calls, it evaluates some stuff in compile time

---

<div class="post-metadata">

**Author:** ![benwilson512](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/benwilson512/32/1457_2.png) [@benwilson512](https://forum.elixirforum.com/u/benwilson512)\
**Post date:** [January 18, 2023, 1:54am UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/4 "2023-01-18T01:54:47Z")

</div>

This is very exciting! Looking forward to testing this on some projects.

Tiny question: In the install guide you add `{:tria, github: "hissssst/tria"}` to the deps, presumably this could be `runtime: false` or does it need to exist when the app runs too?

---

<div class="post-metadata">

**Author:** ![hst337](https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/h/41988e/32.png) [@hst337](https://forum.elixirforum.com/u/hst337)\
**Post date:** [January 18, 2023, 1:56am UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/5 "2023-01-18T01:56:12Z")

</div>

No, Tria is not called in runtime, though I haven’t testing it with this flag yet. To be honest, I’ve only tested it with `path: "../tria"` during development 😅

---

<div class="post-metadata">

**Author:** ![Eiji](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/eiji/32/36743_2.png) [@Eiji](https://forum.elixirforum.com/u/Eiji)\
**Post date:** [January 18, 2023, 3:12am UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/6 "2023-01-18T03:12:17Z")

</div>

Ahh, so bad we can’t set `compilers` via `Mix.install/2` (we can only turn off consolidated protocols) … Is there any particular reason why thee is no support for it?

---

<div class="post-metadata">

**Author:** ![hst337](https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/h/41988e/32.png) [@hst337](https://forum.elixirforum.com/u/hst337)\
**Post date:** [January 18, 2023, 8:53am UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/7 "2023-01-18T08:53:42Z")

</div>

I think we can create a proposal for the language. I don’t see any boundaries why this shouldn’t be implemented there

---

<div class="post-metadata">

**Author:** ![Eiji](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/eiji/32/36743_2.png) [@Eiji](https://forum.elixirforum.com/u/Eiji)\
**Post date:** [January 18, 2023, 9:43am UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/8 "2023-01-18T09:43:34Z")

</div>

I was thinking the same, but your comment made me sure about it. Here is in issue:  
[https://github.com/elixir-lang/elixir/issues/12349](https://github.com/elixir-lang/elixir/issues/12349)

As wrote in it once there would be a support for it at least in `main` branch I would try your library with my scripts like scrapers and I would try to submit some benchmarks as well.

---

<div class="post-metadata">

**Author:** ![josevalim](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/32/1787_2.png) [@josevalim](https://forum.elixirforum.com/u/josevalim)\
**Post date:** [January 18, 2023, 10:01am UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/9 "2023-01-18T10:01:35Z")

</div>

Exciting to see your ideas come to life!

> [@hst337](#):
>
> Ahead-of-time nature of Tria allowed to evaluate pure statement in compile time

How do you know which functions are pure? Especially when anonymous functions are involved?

> [@hst337](#):
>
> optimize `for` loops to finally be efficient

Could you please expand on the tricks here (or share a link)? 😃 I assume some of those may be implementable in Elixir itself, since `for` is opaque (although it is unlikely we would do Enum fusion).

> [@hst337](#):
>
> Inlining

Curious: how would that be different from Erlang inlining via `@compile :inline`and `@compile {:inline, size}`?

> [@hst337](#):
>
> - `map.field` handling (did you know that it is 2-3 times slower than `Map.fetch!(map, :field)`?)

Any ideas here? We do compile `map.field` to a case statement but, last time I measured, the pattern matching was faster than invoking a BIF instruction (which is what `Map.fetch!` does). `map.field`should be slower if the field does not exist though.

Other than that, nice work! I am sure the Erlang Compiler team would also be interested in further ideas to optimize. Some of the peephole and type-checker integration may already be implemented there. Although we don’t perform protocol related type optimizations and the Erlang compiler does not have enough information to do so, so maybe Elixir could/should.

---

<div class="post-metadata">

**Author:** ![hst337](https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/h/41988e/32.png) [@hst337](https://forum.elixirforum.com/u/hst337)\
**Post date:** [January 18, 2023, 11:48am UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/10 "2023-01-18T11:48:37Z")

</div>

## Thanks for your questions!

> [@josevalim](#):
>
> How do you know which functions are pure? Especially when anonymous functions are involved?

This is a big questions. Generally, there are two types of functions: functions which are defined in Erlang or Elixir, or functions which are undefined (thus contain `erlang:nif_error` or call to themselves in their bodies like in Elixir bootstrap).

For the latter, I’ve prepared the [ets table file](https://github.com/hissssst/tria/blob/main/priv/pure.ets) with purity for all such function from all available modules. When unexpected NIF function is found in call stack, Tria would just ask the user in TTY if such function is pure. I have a plan to add annotations like

```elixir
@tria pure: true, safe: true
def my_nif() do
  ...
end

```

For the functions which have Erlang/Elixir definitions, compiler fetches the abstract code, translates it to Tria and just takes a look at all possible calls. So, calls to impure or anonymous functions are considered impure and this is not always correct, but it works for the first iteration of compiler and it can be partially solved with type checking in the future

* * *

> [@josevalim](#):
>
> Could you please expand on the tricks here (or share a link)? 😃 I assume some of those may be implementable in Elixir itself, since `for` is opaque (although it is unlikely we would do Enum fusion).

It translates `Enum.reduce() |> :lists.reverse` to `Enum.map`. Compiler just needs to make sure that `Enum.reduce` aggregates each item straight to the accumulator list, without any conditions. Here’s the optimization: [tria/lib/tria/optimizer/pass/enum\_fusion.ex at main · hissssst/tria · GitHub](https://github.com/hissssst/tria/blob/main/lib/tria/optimizer/pass/enum_fusion.ex#L230). You can see there `tri` macro which is a great tool for pattern-matching on Tria AST.

* * *

> [@josevalim](#):
>
> Curious: how would that be different from Erlang inlining via `@compile :inline`and `@compile {:inline, size}`?

It will be different in some ways. For example, inter-module calls will be inlined. And defaults (like `foo(x, y \\ 0)` will be expanded too, removing one call in the stack

* * *

However, some of these approaches change the behaviour of the program in two ways:

1. Pure code which raises exceptions may be reordered by EnumFusion optimization. I am working on fixing this behavior to have more correct compiler.
2. Runtime recompilation is not yet possible. It can be possible in the future, but Tria just needs some tooling for this.

---

<div class="post-metadata">

**Author:** ![josevalim](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/32/1787_2.png) [@josevalim](https://forum.elixirforum.com/u/josevalim)\
**Post date:** [January 18, 2023, 12:27pm UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/11 "2023-01-18T12:27:06Z")

</div>

> [@hst337](#):
>
> This is a big questions. Generally, there are two types of functions: functions which are defined in Erlang or Elixir, or functions which are undefined (thus contain `erlang:nif_error` or call to themselves in their bodies like in Elixir bootstrap).

What we could do in Elixir itself is to keep a list of the functions in its standard library that are pure. Erlang also keeps a similar list for its standard library and inlines those. WDYT?

We can make this generally applicable but it is worth keeping in mind that each inlined modules becomes a compile-time dependency, and enlarging the compile time graphic can have huge impacts on performance. This is not a concern for Elixir stdlib itself though.

It is also not possible to know if something is pure or not when calling Erlang functions (unless we have an FFI layer where you also explicitly declare those).

> [@hst337](#):
>
> It translates `Enum.reduce() |> :lists.reverse` to `Enum.map`.

Ah, thank you. [We had a previous discussion about this](https://forum.elixirforum.com/t/enum-fusion/36193/31). At the time, the cost of traversal in `for` comparing Enum.reduce vs Enum.map was roughly 13% faster. However, your benchmarks show the body recursive version can be even faster and we could emit Erlang AST with a body recursive version using Erlang’s “named anonymous functions”. Is this something you have an interest in contributing? Or should I open up an issue for someone else to pick?

---

<div class="post-metadata">

**Author:** ![hst337](https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/h/41988e/32.png) [@hst337](https://forum.elixirforum.com/u/hst337)\
**Post date:** [January 18, 2023, 12:38pm UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/12 "2023-01-18T12:38:40Z")

</div>

> [@josevalim](#):
>
> What we could do in Elixir itself is to keep a list of the functions in its standard library that are pure. Erlang also keeps a similar list for its standard library and inlines those. WDYT?

Yeah, I am using Erlang’s list, but unfortunately, `:erl_bifs` covers only `:erlang` module. I didn’t know about list of pure Elixir functions, I’ll grep the Elixir sources then, thanks for the tip!

* * *

> [@josevalim](#):
>
> We can make this generally applicable but it is worth keeping in mind that each inlined modules becomes a compile-time dependency, and enlarging the compile time graphic can have huge impacts on performance

I didn’t quite get what you meant.

* * *

> [@josevalim](#):
>
> It is also not possible to know if something is pure or not when calling Erlang functions (unless we have an FFI layer where you also explicitly declare those).

Well, it depends on these Erlang functions. If abstract\_code for the function can be fetched, it can be analyzed for purity and safety. If the abstract\_code is not present, or the function is a NIF/BIF stub, we can ask a user to provide the value for a trait, or just assume the worst (impurity or unsafety)

---

<div class="post-metadata">

**Author:** ![ityonemo](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/ityonemo/32/11341_2.png) [@ityonemo](https://forum.elixirforum.com/u/ityonemo)\
**Post date:** [January 18, 2023, 3:24pm UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/13 "2023-01-18T15:24:27Z")

</div>

You don’t need abstract code. You can decompile the module and trace the bytecode to make sure there aren’t any calls that are known to be mutating.

---

<div class="post-metadata">

**Author:** ![hst337](https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/h/41988e/32.png) [@hst337](https://forum.elixirforum.com/u/hst337)\
**Post date:** [January 18, 2023, 3:34pm UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/14 "2023-01-18T15:34:52Z")

</div>

> [@ityonemo](#):
>
> You can decompile the module and trace the bytecode to make sure there aren’t any calls that are known to be mutating

Yes, that’s true, but right now I’ve decided to rely on abstract code, since I already have an abstract code translator. Plus, this is a foundation for applying Tria to Erlang projects. However, I’ve planned Erlang support only for the third iteration

---

<div class="post-metadata">

**Author:** ![josevalim](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/32/1787_2.png) [@josevalim](https://forum.elixirforum.com/u/josevalim)\
**Post date:** [January 18, 2023, 10:33pm UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/15 "2023-01-18T22:33:12Z")

</div>

> [@hst337](#):
>
> Yeah, I am using Erlang’s list, but unfortunately, `:erl_bifs` covers only `:erlang` module. I didn’t know about list of pure Elixir functions, I’ll grep the Elixir sources then, thanks for the tip!

We would need to build such a list but I guess it is worth it? 🤔

> [@hst337](#):
>
> I didn’t quite get what you meant.

If A inlines code from B, every time B changes now A has to recompiled. This has a compilation time cost. Perhaps inlining should be enable for production (think -O3).

---

<div class="post-metadata">

**Author:** ![hst337](https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/h/41988e/32.png) [@hst337](https://forum.elixirforum.com/u/hst337)\
**Post date:** [January 18, 2023, 10:44pm UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/16 "2023-01-18T22:44:07Z")

</div>

> [@josevalim](#):
>
> We would need to build such a list but I guess it is worth it? 🤔

I don’t know if it will be useful anywhere except Tria 😅

> [@josevalim](#):
>
> If A inlines code from B, every time B changes now A has to recompiled. This has a compilation time cost. Perhaps inlining should be enable for production (think -O3).

Ahhh, yes, that’s true and I came up with several solutions and chose the solution with separating code into contexts.

Modules will be divided into groups, and each module group will be joined and compiled into separate module. Inlinings between groups won’t be possible.

So, the problem is to divide modules into groups. Right now Tria compiles everything into one group (called `TriaGlobalContext`) And my ideas for this are

1. Using @sasajuric 's `Boundary`
2. Using explicit tria annotation
3. Profiling application during testing and using this data to statistically find optimal groups
4. Just compiling everything into one module

---

<div class="post-metadata">

**Author:** ![josevalim](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/32/1787_2.png) [@josevalim](https://forum.elixirforum.com/u/josevalim)\
**Post date:** [January 19, 2023, 1:18pm UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/17 "2023-01-19T13:18:51Z")

</div>

> [@hst337](#):
>
> I don’t know if it will be useful anywhere except Tria 😅

The goal would be to start doing some of those optimizations in Elixir too. 🙂

---

<div class="post-metadata">

**Author:** ![hst337](https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/h/41988e/32.png) [@hst337](https://forum.elixirforum.com/u/hst337)\
**Post date:** [January 19, 2023, 3:39pm UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/18 "2023-01-19T15:39:17Z")

</div>

Haha, you can always hire me to do so 😁

But anyway, it would change behavior of a language heavily and would require reimplementing about 99% of what I already wrote in Tria

---

<div class="post-metadata">

**Author:** ![josevalim](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/32/1787_2.png) [@josevalim](https://forum.elixirforum.com/u/josevalim)\
**Post date:** [January 19, 2023, 3:45pm UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/19 "2023-01-19T15:45:36Z")

</div>

I thinking inlining calls to pure Elixir functions should be ok, with no large changes to the semantics, especially if we do it on the pass we convert to Erlang. 🙂

---

<div class="post-metadata">

**Author:** ![hst337](https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/h/41988e/32.png) [@hst337](https://forum.elixirforum.com/u/hst337)\
**Post date:** [January 19, 2023, 3:55pm UTC](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250/20 "2023-01-19T15:55:31Z")

</div>

Well, it might be an issue when someone recompiles the project in runtime. For example, `Patch` project allows recompilation of Elixir’s modules, and this code might be broken

[Next page](https://forum.elixirforum.com/t/tria-the-elixir-optimizer/53250.md?page=2)
