Skip to content

Why not EVM OPCODE to RISC-V recompiler? #291

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

Closed
Lohann opened this issue Apr 26, 2025 · 16 comments
Closed

Why not EVM OPCODE to RISC-V recompiler? #291

Lohann opened this issue Apr 26, 2025 · 16 comments

Comments

@Lohann
Copy link

Lohann commented Apr 26, 2025

First thank you for all the hard work on this awesome project.

I have a few questions, this project aims to recompile YUL to RISC-V, but without low level OPCODE support it also means not all YUL features like verbatim can be supported.
I also wrote a few smart-contracts in pure EVM assembly, that would be impossible to be written efficiently in YUL or solidity:

  1. EVM interpreter written in EVM Assembly
  1. A contract that wastes 100% of the gas you provide to it, used in testing.

I understand if this may not be in the scope of this project to support those contracts, should be a separated project? if it is in the scope of this project I would like to contribute to it.

@xermicus
Copy link
Member

Thanks for your kind words and interest in the project.

As for Why not EVM OPCODE to RISC-V recompiler?: Not all opcodes can be supported regardless whether we compile from YUL or EVM opcodes. EVM byte code is a stack machine byte code which means the control flow is obscured, which in term leads to much worse effective RISC-V byte code. Also we rely on certain YUL builtins to make contracts deployment works seamlessly. Summa summarum this would not win us anything but we would loose a lot on the practicality and efficiency side of it.

EVM interpreter written in EVM Assembly

I think writing a EVM interpreter contract in Rust, compiled to RISC-V (which EVM contracts might be able to delegate call into) should achieve the same while being the much more efficient solution. This way you can support all EVM instructions. Once the PVM JIT compiler becomes available, such a interpreter contract would also run near native speed. Unless the EVM being written in EVM itself is somehow a hard requirement for you?

@Lohann
Copy link
Author

Lohann commented Apr 26, 2025

I think writing a EVM interpreter contract in Rust, compiled to RISC-V (which EVM contracts might be able to delegate call into) should achieve the same while being the much more efficient solution.

Totally agree write a EVM interpreter in Rust is faster, but it cannot run inside other EVM compatible blockchain's.
For context, this interpreter is intended to be used by Analog's Gateway Contract, which is a bridge between EVM compatible blockchains, ex: allow you to execute some arbitrary logic from Astar on Moonbean without having to deploy a contract. That's why this interpreter must run on-chain.

But the question wasn't about this specific contract, but about the ones which use low-level EVM or verbatim in general, wondering if it is in the scope of revive.

Even if it doesn't use verbatim at all, this is how I deploy this interpreter in Solidity/Yul:

The create and create2 opcodes also deploy contracts in assembly, without YUL intermediary language, and contracts that deploy other contract are pretty common.

@athei
Copy link
Member

athei commented Apr 26, 2025

Our current stance is that we only support YUL as long as we can get away with it. Only supporting EVM and not YUL is not really viable because we rely on it to make contract instantiation work.

Yes, we are aware that are some things that would require an EVM frontend. But thats more like anecdotal evidence. We would need some data or market research to justify adding this huge chunk of complexity.

So short answer: We don't know.

@Lohann
Copy link
Author

Lohann commented Apr 26, 2025

Yes, we are aware that are some things that would require an EVM frontend.

Yeah that's exactly my question, what is the scope of revive, if the goal is to enable all existing Solidity code to run on PolkaVm, then I don't see how this is possible without support low-level evm opcodes, many projects rely on low-evm code, including Open-Zeppelin and a few ethereum standards.

EIP-1014 - Skinny CREATE2

Needs the EVM binary to compute the contract address.

EIP-1167 - Minimal Proxy Standard

EIP-3448 - MetaProxy Standard

I understand how challenging is porting a Stack Machine like EVM to a Register Machine like RISC-V, I'm thinking about propose an EVM Interpreter optimized to run on RISC-V, once the existing EVM stack used by many parachains (Astar, Moonbean, Acala, etc..) seems abandoned and unmaintained:

@athei
Copy link
Member

athei commented Apr 26, 2025

if the goal is to enable all existing Solidity code to run on PolkaVm

This is not the goal. Because even with an EVM frontend we couldn't enable all existing code to run. Super low level stuff will never work because of our differences in bytecode and contract instantiation.

@xermicus
Copy link
Member

@Lohann may add, I see your usecase and justification. However note that EVM compatibility is solved in our ecosystem already. I think you could as well deploy to moonbeam and bridge into AssetHub for whatever your needs are?

@Lohann
Copy link
Author

Lohann commented Apr 26, 2025

However note that EVM compatibility is solved in our ecosystem already.

I don't think is solved, I worked building bridges between many networks inside and outside polkadot ecosystem, for some I had to use a specific language/tools, such as ink! for parachains, cosmwasm for cosmos, bpf for Solana, PyTeal for Algorand, etc.. But the most widely supported is EVM, and frontier-based chains are the most incompatible and painful to get working, is the only one which forced me to add Custom Solidity Code to get the same contract working on all EVM Chains, the rpc-api is inconsistent, the opcodes doesn't behave the same, the block-hash stored doesn't match the block returned by the API, I opened a PR trying to fix it which was never reviewed, etc..

When I saw Revive, I thought it would replace frontier, but now I understood it was never the goal.

@Lohann
Copy link
Author

Lohann commented Apr 26, 2025

I think you could as well deploy to moonbeam and bridge into AssetHub for whatever your needs are?

The use case is having the same Solidity code that works on Ethereum, Polygon, Near, Avalanche, (any other EVM compatible blockchain actually), working on substrate. BridgesHub/AssetHub only works for Polkadot consensus, it cannot connect Polkadot to Avalanche for example.
In a cross-chain environment, for sending arbitrary messages using EVM is easier, because is easier for EVM talk to EVM, than EVM talk to ink! for example, each new stack requires maintain more gateway contracts and tooling.

@athei
Copy link
Member

athei commented Apr 28, 2025

The use case is having the same Solidity code that works on Ethereum, Polygon, Near, Avalanche, (any other EVM compatible blockchain actually), working on substrate.

The goal is that this is true for 99% of contracts. Exceptions apply. We are compatible where we can and take it very seriously. However, there are cases where we simply can't be compatible. I am not ruling out an EVM frontend if it turns out we need it because a substantial amount of contracts is written in assembly. But for the time being it looks like niche use cases. The OpenZeppelin contract you posted seems to use just YUL.

@Lohann
Copy link
Author

Lohann commented Apr 28, 2025

The OpenZeppelin contract you posted seems to use just YUL.

Yes, is a YUL code that manipulates EVM bytecode for deploying a EIP-1167 Minimal Proxy. Usually every time solidity code uses create or create2 opcodes directly, is because some EVM bytecode manipulation is needed.

@athei
Copy link
Member

athei commented Apr 28, 2025

I see. Yeah this will not work. But it won't be fixed by an EVM frontend, either. This is because on-chain it will be all PolkaVM. The transformation EVM -> PolkaVM is done offline.

@Lohann
Copy link
Author

Lohann commented Apr 28, 2025

yep, that's why I think an EVM interpreter is unavoidable if the goal is being 99.99% EVM compatible and support all features up to shanghai hardfork, support all open-zeppelin contracts, etc.

@athei
Copy link
Member

athei commented Apr 28, 2025

yep, that's why I think an EVM interpreter is unavoidable if the goal is being 99.99% EVM compatible and support all features up to shanghai hardfork, support all open-zeppelin contracts, etc.

Agreed. But why are you posting that on a repo on a recompiler to RISC-V? Like what is your point?

@Lohann
Copy link
Author

Lohann commented Apr 28, 2025

Like what is your point?

I believe is possible to convert EVM OPCODES to RISC-Von-chain in O(1) complexity, which will generate code less efficient than YUL to LLVM, but will be a more generic solution.

I believe the reasonw why YUL was choosen over EVM is because YUL doesn't allow arbitrary JUMPS and can be easily converted to LLVM-IR, and also because assign a stack to a register isn't possible in O(1). But in reality 99% of EVM code can be divided into blocks, because 100% of the jumps generated by solidity code have fixed destination in this form:

<CONDITION>
PUSH2 <DEST>
JUMPI

Also all valid destinations must be tagged with JUMPDEST opcode.

EVM only allows OPCODES to access the 16 elements in the top of the stack, coincidentally is the number of registers supported by riscv32e target.

@athei
Copy link
Member

athei commented Apr 28, 2025

I believe is possible to convert EVM OPCODES to RISC-Von-chain in O(1) complexity, which will generate code less efficient than YUL to LLVM, but will be a more generic solution.

I assume you meant O(n) here. Oh we are back to baseline compilers now? I am glad we closed that chapter a long time ago when we moved away from Wasm. Even in O(n) the compilation will be slow and a tradeoff between compile and execution time. Might just stick to an EVM interpeter. The whole point of PolkaVM is to not make this tradeoff. I swear to god if you say "caching" now I will close this thread.

@Lohann
Copy link
Author

Lohann commented Apr 28, 2025

Oh we are back to baseline compilers now?

I wasn't aware about this chapter, actually we can close this thread because all my questions were answered, thank you @athei and @xermicus, now I have a better idea of what is the scope, goals and non-goals of revive.

  1. revive will replace frontier as a new compatibility layer between Substrate <-> Ethereum?
    Answer: No, not all existing EVM contracts and libraries will run seamless in pallet-revive.

  2. It was considered to compile EVM to RISC-V?
    Answer: No, baseline compilers isn't an option.

I'm interested on improve the compatibility between Substrate and EVM, because I believe compatibility is more important than performance, once if my goal is implement the most efficient contract possible, I would choose ink! + Rust instead of using ink! + Solidity which is impossible to generate efficient code because YUL/Solidity assumes 256bit word size.

@Lohann Lohann closed this as completed Apr 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants