solx: an LLVM-based Solidity toolchain - Oleksandr Zarudnyi

Solidity Summit at Devconnect Argentina 2025 · 18 Nov 2025 · 24:31

Oleksandr Zarudnyi of Nomic Foundation presents solx, an LLVM-based Solidity compiler for the EVM. The Solidity Summit talk walks through how an LLVM-based toolchain addresses practical constraints seen in current solc pipelines, and sets out the vision for where the project goes next.

Speakers

Topics

From Solidity Summit at Devconnect Argentina 2025, hosted by Solidity.

Transcript

Glad to see everyone. Uh my name is Alex. I am a compiler front end lead at select. Uh the compiler was originally created by Matter Labs but now a part of it has been moved to Nomic Foundation and today I will be talking about select mostly from the perspective of our interaction with the SolC compiler and uh I will show a couple of problems we faced and how we fix them and how uh reusable components such as LVM framework work helped us in our journey.

Uh so the story begins in early 2021 when uh Matter Labs was building uh Zikync VM uh a virtual machine with a custom instruction set which was register based and we were a small team of uh one frontend engineer, one LVM engineer and the QA who had to deliver a compiler for this virtual machine in roughly one year. Um the time frame was challenging. So we at first we didn't had no idea if we could do that but we started to look around for the reusable components we could uh reuse and deliver faster. At first we

looked at the sol compiler uh because it was uh the only compiler available back then. We looked if we could reuse it in a sense that it it would emit a different bite code like CK sync VM by bite code. But unfortunately his design was quite EVMcentric and tailored uh towards emitting EVM bite code. But however it had two pipelines, EVM assembly and ule that could emit uh IRS that could be consumed via different tools that are calling

uh sol um AVM assembly was quite uh low level ST based and really close to EVM bite code. So we looked at dual first. It was just added a couple of versions ago, really early stage, not battle tested, but it was nice and uh simple and easy to work with. So we wrote a small adapter between Ule and LLVM. It's the red area on the left. It's basically taking ULE and translating it to LVM.

uh that can be later compiled to the the kissing VM byte code and for the middle end and back end we used LLVM the choice was obvious because it's kind of the biggest and the most mature compiler framework in the world uh we could get the decent performance from the LVM back um middle end and the LVM back end also allowed us to reuse all the infrastructure in LVM and deliver the back end many many times faster probably around uh uh 10 20 times faster depending on how uh how much of the infrastructure of LVM you want to use. Like if you want to repeat the same the same quality, the same number of

features uh as an LVM, you will likely spend decades of engineering time. So this is how our pipeline looks like. um we reused all the green area and we only wrote ourselves the red areas which are secl adapter between dual level vir and the zikync vm back end itself. So in total we used more than 95% of the of the code base and we successfully delivered the compiler in one year. uh users could deploy uh solidity contracts to sync VM and since we have ported UL to LVMR it was now possible to use all the

other backends available in LVM and this is what happened uh three three years later when guys from par forked the case and uh changed just a few% of the code base and compiled u to risk 5 so it is now basically possible to compile files related to risk 5 using their work available at the link at the GitHub link below. Uh some aspects of the developer experience were not great. For instance, ule was only available in solidity 08 and it had semantic differences with L1 uh because SolC is using the EVM assembly code gen by default. So all our

contracts had a couple of differences with the contracts on L1. It also suffered from compiler crashes and long compilation times. So at some point we realized that we had to support the EVM assembly as well. Um it was challenging for a couple of reasons and the reasons were that EVM assembly is a very low-level ST based representation and LVMIR is a register based. The second reason is that the control flow is is not uh uh explicit in uh in the VM assembly.

So if you see these jumps, you don't really know where the jump is going to, you basically have to rebuild the stack first and then uh after rebuilding all the text states for each instruction, you can uh have an idea where the block is going to. So we had to translate the representation on the left to the presentation on the right. For this purpose, we created this IR to fix this both problems. uh it is basically rebuilding the stack and uh maps all the stack slots onto virtual registers of LVMIR and it also solves the problem of the control flow. Uh you can see the

predecessors next to each block. So basically we are tracking all the jumps and mapping the destination tags onto these jumps. We had a lot more challenges than this, but I will not go into them low level in this talk. Probably deserves another one. Uh, I'll just say that it didn't come for free. We had a couple of performance regressions. We had to deliver a couple of extra optimizations and we had to fork LC to get rid of dynamic jumps. Basically, we did that in all versions of Solidity. We had to maintain

uh 80 solidity versions for 3 and a half years uh since we released the first EVM assembly support and the translation itself was complex and the algorithm was kind of fragile. Uh however, we finally supported all solidity versions on the Kync VM and they all could now be compiled to other targets in LVM just like you before. Of course, we didn't want this to become a one-time job. So at some point in mid 2023 we had grown to team of five engineers and we

realized that we could reuse all this expertise and uh the code bases to deliver a greater public good for Ethereum an alternative compiler for the EVM. So uh we asked one engineer to start working on an EVM back end inside LVM. Uh and we knew that some some people tried to do this before. A couple of other projects tried to deliver uh uh LLVM framework capabilities to Solidity, but they were abandoned for a couple of reasons.

And after this the dominating idea in the Ethereum community was that uh LLVM was not suitable for the EVM. Uh however it did not make a lot of sense to us because we knew that there was already one target in LVM implementing a stack machine. It was web assembly. So we based our stickification algorithm on a web assembly back end and a few ideas we also borrowed from the SLC back end. So here we are basically doing uh the opposite of what we had to do with VM assembly.

We are turning registers into stack slots. So in one year and a half we had a working back end. It was passing all the tests uh for solidity from the upstream repo and also some tests that we had written for CK sync VM. However, nobody could use it yet because we had no front end. But or to be more accurate, we had 90% of the front end hidden inside the case.

Um on the first infographics you can see that uh the target independent nature of LVM uh allows you to have your front end as generic as possible and you can basically reuse 90% of the LVM lowering from zinc VM to EVM to risk 5 and all the other targets. Um, and that's what we wanted to do. We just forked the KLC and we got selects front end out of that codebase. We still reused all the middle end, all the LLVM code. You can see on the second chart.

Uh, it's like 800,000 lines of code and uh, anyone can do that with our codebase since we are almost 100% MIT Apache licensed. GPL version 3 is only for the parts where we interact with the sol uh like the bindings between C++ and Rust code. Uh so yeah we had selects but we did not want to release it just anyhow. We wanted to deliver something special to the community and make users uh love selects.

So we came across this solidity survey that had been listing the same top one problem year after year after year. It was stack too deep. And we realized that with all the power of LVM of all the layers of obstructions it provides it was not such a big deal to fix. So here is what we try. On the left, we are basically running the stickification algorithm once and recording how uh many variables did not fit into the stack and then we rerun the compilation with all these variables move to the heap. And since our solution works on the backend

level, it solves stack to deep for all code gen and all pipelines and and all solidity versions uh with 100% accuracy. If you still have stack too deep with the selects, it's a critical bug to please submit it to us. Uh it's a P 0, we will fix it. Uh yeah, on the other side, you can see that u even in the latest version still has these uh crazy errors. Um so apparently the solution is not perfect. Yeah, here's what we are doing. It's basically the same as uh as the previous presenters talked about. We are allocating the

spill region on the heap. We are moving the memory card further and the user memory shift further and all these variables from the ST they're allocated on the spill area. So yeah, ST to deep solved uh any code gen uh all pipelines and we have delivered the first LVM based compiler for solidity. We saw some moderate gas red gas usage reductions from um the default LLVM optimizations from the middle end.

But unfortunately like any first release we suffered from long compilation times and bite code size issues and that's what we had to solve in the next versions. We decided to fix the compilation time first because we knew that it was a really hard problem for sol developers especially with the ule with the IR pipeline. Um it didn't take us a lot of time. We only spent like one month on this and in the beginning select was three times slower on average. It kind of depends on what pipeline you're using. EVM assembly is really fast but we knew that via IR

is going to be made uh the default pipeline. So we paid more attention to that one and we wrote a small tool that would benchmark all the parts of the pipeline uh break it down by contracts by translation units and so on. And we found out these two problems were the biggest black holes in the compilation time. Sol is completely single threaded. So if you have 64 core machine only one thread is used. So basically you are having still see that is 64 times slower than it should be.

Um and the second problem is that we still had both optimizers enabled. We did not want to turn off the ill optimizer because perhaps we'll have gas regressions. We just wanted to keep them both. So um we tried to add multi- threading to sol but the coupling between components was so high and there were so many global variables the whole design was not suitable for multi- threading. So we only turned off the soly optimizer for ule and here is what happened.

Uh this is not an average case. This is an extreme case. I just want to showcase it because a v3 it was kind of very infamous for having very slow compilation times on ule. After turning off the u optimizer the compilation time reduced by eightfold and the middle end and back end stage itself it was 30x faster with LVM uh pipeline rather than dual pipeline. And it's not the end of the story. After we turned off the U optimizer, Sol C was still accounted for 75% of the total running time. This is the light, the

dark blue stripe below. And the reason is mostly because it's single threaded like a has around 100 contracts or maybe 400 I don't remember. Uh if you if you scale the front end and run it in multi- threaded mode uh you can probably reduce these 80 seconds by 12 times and your front end will be just executed in 5 seconds. So yeah um an average compile time reduction was like uh 3 4x compared to soul C.

Um and the last problem the bite code size um we could not deploy a lot of projects because they just uh they just used to exceed the AVM bite code size limit. The bite code was 6% larger on median but some contracts were 50% larger. So we investigated what was going on and we still had those chunks of zeros and fs in our bite code. um we had to get rid of them. So we implemented this constant unfolding algorithm that would just uh unfold these big constants that were usually representing negative numbers such as minus one into something uh

concise but uh probably costing more gas than just simple push. Um we also implemented an adaptive algorithm that would track uh the EVM size limit and when the bite code is close to the EVM size limit it would unfold more constants or just apply additional optimizations and this is all well tuned. We are exposing some variables that parameters that can help you to tune the pipeline in case you need more gas optimizations or more size optimizations and still LVM will make sure that you're not exceeding the bite code size limit.

So this is the bite code size benchmarks. Uh we are roughly 50/50 on par with you because we are still preferring gas to bite code size but on average on median we are uh 11% uh smaller than uh the U pipeline and 28% smaller than the EVM assembly pipeline. The gas benchmarks uh we have been working on the gas optimizations since the very first first release. So there is no specific release focusing on them. But on the last release you will have a roughly

13% reduction of the gas in the in in any pipeline. They're kind of similar in the results, but you will have the reduction on almost all contracts. Like 90 95% of contracts are cheaper in selects than in salcy. Uh the most important topic I guess uh how about testing and security? So on the left you can see all the tests and projects we are running on every patch on every commit. 24 foundry projects basically all of them that could be integrated in CI uh

it's kind of a few dozens of thousands of tests and we are running all the upstream solidity tests we are running CK sync VM test that we had written before and LVM unit test of course we are also being fed by an external team the fuzzer is run is being run on select 24/7 it already found some compiler crashes And speaking of where we are security wise in in comparison to Sol C, the data shows that we had the same number of uh found issues in SOLC C and select X. So there is no strong reason to believe that select X is less secure at the moment than S C. We also aiming to have more

than 90% test coverage for the front end and the back end. We want to integrate the contract sanctuary and we are open to discussions about u front end uh audit and backend audit. We have very little expertise here but I hope that the community will help us to make so more trustworthy and uh make users not afraid of it. So when selects uh we are currently recommending using select for development that is you can just compile your project with select while you're developing it but then deploy it with soulc or if you really suffer from stack to deep issues you can

just switch to select for non-m mission critical projects or projects where you have 100% as coverage and where you are really confident and of course when you have done that, please share your experience with us in our telegram channel or in Twitter. On the left, you can see all our resources where you can reach out to us and uh please try select today. Do you have time for your name? [applause] Awesome. Thank you so much. Um two questions for you, Alex. How relevant were existing LLVM optimizations were to the EVM?

>> Could you repeat the question? How >> how relevant were existing LLVM optimizations were to the EVM? >> Uh so we can answer this question by looking at what we had in the first release when we barely started working on EVM specific optimizations. We had some moderate gas improvements. I think we were around 3 to 5% more efficient than Sol C. So basically it it gives us the idea that the middle end optimizations from the LVM the default ones uh they were enough to be roughly um viable for an EVM pipeline. So you could deploy the contracts and you could

roughly had a decent gas price for your methods. Uh most of them were relevant because they are relevant for all targets. Uh they are very target independent. It's just something basic like that that store elimination like loophole optimizations and so on. >> Are there any plans for foundry to support solex >> for >> foundry >> uh >> to support solex? >> I think foundry is generic enough. it can just uh use selects already with a different profile. So you can just declare a new profiles and specify the path to your selects. But you still have to download the executable locally and

specify the full path. Uh I don't know about uh foundry supporting selects as the first class citizen because we still have no either scan support and our release pipeline is not stable yet. So maybe we will change the versioning, maybe we will change uh this or that. But overall you can just use select locally with foundry uh and hard head. >> Okay. And one question is what are the differences between so and solar the new paradigm solidity compiler.

>> Well as far as I know solar is targeting different backends at least three or four of them but we are targeting only one uh back end. So we are putting all the effort into uh the LLVM pipeline. Uh that probably will make us more efficient with uh this one single pipeline. Awesome. Uh another fun part is that there's a solar talk as well and that's going to be the last talk of the day. So, please make sure that you wait until the last talk of the day and then um catch all of our speakers to pick their brains about their projects. Um but thank you so much, Alex. That was a

wonderful talk. That will be it. [applause]