Repository navigation
Conversation
The entry of an inlined function's body is the caller's position, but it was placed at the body's first instruction's origin, which is in the callee. There the caller's versions are not placed, so every caller's local with a removed version lost its pointer for that one step. Use the origin of the jump into the inlined code instead.
Contributor
|
The jump into inlined code is not always the call: jump optimization can send the jump that returns from one inlined function straight into the next, or a branch straight into inlined code. Have inlining record where the call was before optimization (callOrigin) on the op that carries the invoke, and place the entry there: at the call's own position, which is the caller's state as the call begins. Test each shape by execution, with the values the local holds.
withPlacement kept an instruction's inline sites, origin and returned debug, but not callOrigin, so folding, CSE, read/write merging and nested inlining dropped it while keeping the invoke. Keep it, and test a constant argument and a nested call; run (m) at O3 too.
This branch has not been deployed
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 -O 2 and up, the JUMPDEST where an inlined function's code starts listed the caller's locals with no pointer, for that one step, though their bytes had not moved. The jump before it and the steps after the return both had the pointers. For example, a contract that copies a struct from storage to a
let player: Playerin memory, then calls an inlined_rolledHit(), givesplayerno pointer at that JUMPDEST only.The cause is in local-variable tracking. A block's entry is placed just before its first instruction. At the start of inlined code, that instruction carries the
invoke, so the entry lists the caller's locals. But the position used to decide which version of each local is current was that instruction's origin, which is in the callee. The caller's versions have no place there, so tracking fell back to the position after optimization, where a local with a version the optimizer removed gets no pointer.The fix: inlining now records where the call was before optimization (
callOrigin) on the op that carries the invoke, and the entry is placed there. The call's own position is the caller's state as the call begins (a call's result is defined in its continuation), so the entry lists what the call itself lists. This holds whatever jump optimization does later: the jump into inlined code can be a branch, a jump from a block where two branches meet, or the jump that returns from another inlined function, and none of these carries the call's position. Where other passes combine an instruction's debug (folding, CSE, read/write merging, nested inlining),withPlacementkeepscallOriginwith the instruction's other placement.The tests walk the trace. At O2 and O3, for a call straight on, on one branch, after two branches meet, with a constant argument (so the callee's first instruction folds away), and across a call that an inlined function inlines, the step before the invoke is a JUMPDEST that lists the struct local with a pointer, and the soundness check reads the right value through it. A local given a constant between two inlined calls has no pointer at the second call's JUMPDEST: its value there is the constant, which has no place, and its old place still holds the old value. These tests fail on main.
In a larger contract, the output changes only at the JUMPDESTs that start inlined code: two at O2 and one at O3, each now listing the same variables as the jump before it. Bytecode does not change at any level, and the output at O0 and O1 is identical.