Repository navigation
bugc: keep variables right at inlined code - #341
Merged
Merged
Conversation
gnidan
force-pushed
the
inline-variables
branch
from
October 6, 2026 06:50
bc806ad to
6ba6481
Compare
Contributor
|
gnidan
force-pushed
the
inline-variables
branch
from
October 6, 2026 07:40
31210af to
4a46ce6
Compare
In inlined code, the instructions gather the call site's `code` with the callee's, and irgen's storage variables rode in the callee's gathered context while the callee's locals sat in a flat `variables` beside the gather. All of them apply, but a reader that took the flat list lost the storage variables, and a local could not hide a storage variable of its name. Now one flat `variables` list holds both. An inlined function's locals were also listed outside its body: on the jump back to the caller's continuation, after its return, and on its entry block's JUMPDEST, before its invoke. That jump now keeps the call's source range, inline sites and origin, so it lists the caller's variables, and the entry lists the caller's scope.
…t inlined activations Contexts are postconditions, so the op that carries an inlined function's `return` is past it: like a real call's return JUMPDEST, it now lists the caller's variables. The inliner records the caller's placement on the instruction that returns (`returned`), and code generation stamps that op with the caller's snapshot. An inlined function that starts with an inlined call, or returns one's result, had an empty entry (or return) block, so its invoke (or return) rode a jump that jump optimization removed. The inliner now passes over such blocks to the body's first (or last) instruction, and a marker left on a call carries over when that call is inlined, so each inlined function has its own invoke and return, outermost invoke first and innermost return first. Several markers on one op keep that order. Variables in a gathered context with a `frame` stay there: they hold only in that frame.
gnidan
force-pushed
the
inline-variables
branch
from
October 6, 2026 14:15
4a46ce6 to
501aa35
Compare
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.
At an inlined function's code, bugc gathers the call site's
coderange with the callee's, since the two collide. The storage variables from irgen rode along inside the callee's gathered context, and the callee's locals went in a flatvariablesbeside the gather. That says the right thing (all of a gather's contexts apply, and so do the keys beside it), but it splits one list in two: a reader that takes the flat list losestotalandcallsat every inlined step, and a local can't hide a storage variable of its name the way it does elsewhere. Now the storage variables move up into the one flatvariableslist, and the gather holds only the collidingcoderanges (and the transform and activation markers). A gathered context with aframekeeps its variables, which hold only in that frame.An inlined function's locals were also listed outside its body. Contexts are postconditions, so they belong from the op that invokes the function up to, not including, the op that returns from it. The op that carries the inlined
returnnow lists the caller's variables, as a real call's return JUMPDEST does: the inliner records the caller's placement on the instruction that returns, and code generation stamps that op with the caller's snapshot. The jump the inliner puts in place of the callee'sreturnkeeps the call's source range, inline sites and origin, so it lists the caller's variables too, and so does the entry block's JUMPDEST just before the invoke.An inlined function that starts with an inlined call, or returns one's result, has an empty entry (or return) block, so its invoke (or return) rode a jump that jump optimization removed, and the call stack didn't balance. The inliner now passes over such blocks to the body's first (or last) instruction, and a marker left on a call carries over when that call is inlined. Each inlined function has its own invoke and return: outermost invoke first, innermost return first, also when several land on one op.
The new soundness tests run small programs at O2 and O3: a local is listed only from its function's invoke up to its return; the op that returns lists the caller's locals, where the callee's have the same names; storage variables are listed at every inlined step, with no
variablesleft in a gather; and an inlined function that starts or ends with an inlined call gets invoke outer, invoke inner, return inner, return outer. The bytecode doesn't change (checked on every example at every level).Found while stepping through
weights.bugat O2 in the debugger demo. The reader side (@ethdebug/programs-reactdidn't look into a gather when the context had its ownvariables) is fixed in #342.