Conversation
With `--evm-version future` (a new experimental version for instructions proposed but not yet scheduled), internal function calls compile to the call and return instructions of EIP-7979 instead of being synthesized from jumps: - a call is `PUSH <function label>; CALLSUB`, with no return label pushed and no landing pad after the call; - a function entry is a CALLDEST label, with no return_pc on the stack; - the cleanup routine ends with RETURNSUB. Plumbing: - opcodes: the "future" version and CALLSUB (0xB0, 8 gas), CALLDEST (0xB1, 1 gas), RETURNSUB (0xB2, 5 gas); - assembler: a SubroutineLabel kind, assembled as CALLDEST; RETURNSUB terminates a block; the jumpdest-merging pass never redirects a CALLSUB onto a JUMPDEST (a surviving label becomes a subroutine entry; no jump threading through one); source map marks CALLSUB/RETURNSUB; - IR: `gosub` (CALLSUB with args, like goto), `retsub`, and `subroutine` (a label assembled as CALLDEST), named to avoid the opcode-name collision that IR node names are subject to. With EIP-8337 validation in mind, the shared revert block becomes a subroutine entry, and subroutine code gets its own: code shared by several subroutines must be entered as a subroutine, and must not be shared between subroutine code and top-level code. Out of scope: the Venom backend, which ignores the new convention.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft. A demonstration for the EIP-7979 discussion, not a merge candidate.
EIP-7979 (proposed for Hegotá) gives the EVM a return stack and three instructions:
CALLSUB,CALLDEST,RETURNSUB. This PR makes the legacy pipeline use them for internal function calls under a new experimental EVM version,future, so the question "what does it cost Vyper to target this?" has an answer.What changes in the generated code
futurePUSH ret; [buf]; PUSH f; JUMP/ret: JUMPDEST[buf]; PUSH f; CALLSUBf: JUMPDESTwithreturn_pcon the stackf: CALLDEST,return_bufferonlyJUMPtoreturn_pcRETURNSUBSize of the change
138 lines added across ten files, but the calling convention itself is 39 of them (32 without comments), in
self_call.pyandinternal_function.py. The rest is plumbing: the version and opcodes (10), aSubroutineLabelkind assembled asCALLDEST(19), three IR ops in the lowerer (47), two guards in the assembly optimizer so aCALLSUBis never redirected onto aJUMPDEST(19), IR-node valency (7), and registering the ops (3).Results on a small contract
calls.vybelow has a plain internal call, a nested one, and a loop. Runtime underfuture: 4CALLSUB, 3RETURNSUB, 10JUMPI, and no plainJUMP; the Prague build has 7, all of them calls and returns.Run on the EIP-7979 reference implementation in execution-specs (ethereum/execution-specs#3575):
compute(a, b)futureRuntime size 260 bytes vs 272. The gas saving per call is the return label's push and landing pad; Vyper passes return values through memory, so nothing else in the convention changes.
The output validates under EIP-8337 (static control flow, no underflow), checked with its reference validator (ethereum/execution-specs#3576). That took one design change: the shared global revert block was reached from the dispatcher and from inside every subroutine — from different entries, and from both framed and unframed code — which the validator rejects. Under
futurethe shared revert block is a subroutine entry (CALLDEST), and subroutine code has its own. Four bytes. The general rule, for any optimizer targeting these EIPs: shared code must be entered as a subroutine, and must not be shared between subroutine code and top-level code.Notes
gosub,retsubandsubroutinerather thancallsubetc.JUMPIs left in the demo it validates, but a dense selector table's computed jump would not until the EVM has a static multi-way jump.The demo contract