Now that instruction contexts are postconditions (#281, and #302 for external calls), the function contexts need another pass for internal calls. ethdebug/format/program/context/function/invoke says to put the context on the callee's entry JUMPDEST because "the caller's JUMP has consumed its destination operand by then", but that's just as true after the caller's JUMP. ethdebug/format/program/context/function/return only says the instruction "is associated with a successful function return", which doesn't tell anyone whether the function is still active after that instruction, or where the context belongs.
This shows up in our own tools. bugc puts return on the caller's continuation JUMPDEST, and the trace viewer pops the frame one step after that, so a frame stays on the call stack for two steps after control is back in the caller. Going in, the timing is exact. Neither tool is wrong according to the spec as written, which is the problem.
I've thought for a long time that an internal call has four edges, not two (this is where #141's "call setup / body / cleanup" and my comment on #41 were heading): the call (the caller's JUMP), the entry (the callee's JUMPDEST), the exit (the callee's JUMP back), and the resume (the caller's JUMPDEST after the call). JUMPDEST changes no state, so the edges pair up by what's true after them, but each knows something different: call and resume know the call site (and might not know the callee, if the jump is computed; see #290), while entry and exit know the function but not the caller. That's the same split DWARF makes between DW_TAG_call_site and DW_TAG_subprogram. Under the postcondition rule, the frame should change at the two edges that transfer control: after the call, the callee is active, and after the exit, it isn't. Today nothing marks the exit.
I'd like to do this in two passes. First, clarify what exists: say what invoke and return mean under postconditions, fix the invoke rationale, and recommend placement, so current producers and debuggers agree on timing. Second, add what's missing: decide how the four edges are represented (distinct kinds, or invoke/return plus call-site data), how activation (#245) pairs them, and how reverts fit, then update bugc and the trace viewer together.
Both passes change what debuggers have to handle, so solc, Fe and Solar will get a heads-up before anything ships. Modifiers (#313) raise the same questions about edges and may end up sharing this design.
Drafted by Claude. gnidan approved writing as "I" on his behalf for this issue.
Now that instruction contexts are postconditions (#281, and #302 for external calls), the function contexts need another pass for internal calls. ethdebug/format/program/context/function/invoke says to put the context on the callee's entry JUMPDEST because "the caller's JUMP has consumed its destination operand by then", but that's just as true after the caller's JUMP. ethdebug/format/program/context/function/return only says the instruction "is associated with a successful function return", which doesn't tell anyone whether the function is still active after that instruction, or where the context belongs.
This shows up in our own tools. bugc puts
returnon the caller's continuation JUMPDEST, and the trace viewer pops the frame one step after that, so a frame stays on the call stack for two steps after control is back in the caller. Going in, the timing is exact. Neither tool is wrong according to the spec as written, which is the problem.I've thought for a long time that an internal call has four edges, not two (this is where #141's "call setup / body / cleanup" and my comment on #41 were heading): the call (the caller's JUMP), the entry (the callee's JUMPDEST), the exit (the callee's JUMP back), and the resume (the caller's JUMPDEST after the call). JUMPDEST changes no state, so the edges pair up by what's true after them, but each knows something different: call and resume know the call site (and might not know the callee, if the jump is computed; see #290), while entry and exit know the function but not the caller. That's the same split DWARF makes between
DW_TAG_call_siteandDW_TAG_subprogram. Under the postcondition rule, the frame should change at the two edges that transfer control: after the call, the callee is active, and after the exit, it isn't. Today nothing marks the exit.I'd like to do this in two passes. First, clarify what exists: say what
invokeandreturnmean under postconditions, fix theinvokerationale, and recommend placement, so current producers and debuggers agree on timing. Second, add what's missing: decide how the four edges are represented (distinct kinds, orinvoke/returnplus call-site data), howactivation(#245) pairs them, and how reverts fit, then update bugc and the trace viewer together.Both passes change what debuggers have to handle, so solc, Fe and Solar will get a heads-up before anything ships. Modifiers (#313) raise the same questions about edges and may end up sharing this design.
Drafted by Claude. gnidan approved writing as "I" on his behalf for this issue.