fix(gnovm): match Go's call-time dispatch for interface-bound method values#5737
fix(gnovm): match Go's call-time dispatch for interface-bound method values#5737ltzmaxwell wants to merge 13 commits into
Conversation
🛠 PR Checks SummaryAll Automated Checks passed. ✅ Manual Checks (for Reviewers):
Read More🤖 This bot helps streamline PR reviews by verifying automated checks and providing guidance for contributors and reviewers. ✅ Automated Checks (for Contributors):🟢 Maintainers must be able to edit this pull request (more info) ☑️ Contributor Actions:
☑️ Reviewer Actions:
📚 Resources:Debug
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
This is a design problem, not a tweak. The root cause sits a level deeper than the single flag site, so as written the change fixes the interface case but regresses the concrete one. Go has two distinct timings: a concrete *T(nil) derefs *pt when the method value is formed, so it panics eagerly at bind, while an interface-boxed *T only derefs inside the call, so its panic lands at call time. The nilReceiverPanic flag is read at the single VPDerefValMethod site, but that site runs after the interface is unwrapped to a bare *T, so it can't tell the two apart and defers both, when only the interface case should defer. To hold both, the decision needs to live where the receiver kind is still known (interface dispatch, or marked in the preprocessor) rather than at that one late site.
receiver form Go master this PR
------------------- ------------ ------------- --------------
defer pt.M() concrete eager(=0) eager(=0) ✓ call-time(=1) ✗ <- regressed
defer i.M() iface call-time(=1) eager(=0) ✗ call-time(=1) ✓ <- fixed
G=i.M; persist; G() panic (binds eager) no panic ✗ <- new hole
The current filetest covers the interface case; the concrete case and the persistence hole below are both unexercised today, so they'd be worth adding. An ADR pinning the intended semantics would also help anchor the fix.
Repros run at 4c57c37.
47b512e to
db352a1
Compare
0b89756 to
581b58c
Compare
d1bdb5a to
48dae0c
Compare
14a0c36 to
0a1d788
Compare
…values Interface-bound method values now resolve their concrete method and receiver at call time (deref, value snapshot, nil panic, embedded-field re-read and dynamic re-dispatch all match Go), instead of binding eagerly. The call-time dispatch is charged via OpCPULazyBoundResolve. Hard-fork: BoundMethodValue gains a persisted Method field (wire/merkle/gas change).
… orthogonal boxed-realm txtar
…patch Raise a fatal, gno-unrecoverable panic when the call-time dispatch loop revisits an operand, instead of hanging. Covers direct/indirect/embedded/deferred/persisted cycles plus negative guards.
Thread the machine into resolveLazyBound and charge OpCPULazyBoundResolve each iteration so cost scales with embedded-interface depth. Gas-neutral for the common single-hop case; complements (does not replace) cycle detection.
# Conflicts: # gnovm/pkg/gnolang/op_call.go
g := i.M,defer i.M()) now resolve their concrete method and receiver at call time, matching Go: nil-panic timing, value snapshot, embedded promotion (value/pointer/interface fields, any depth), field re-read through a boxed pointer, and dynamic re-dispatch. Also fixes two VM crashes:defer i.M()on a non-nil interface, and a nil embedded pointer-receiver (c5).BoundMethodValuegains aMethodfield;Func == nilmarks a lazy bind resolved at the call (resolveLazyBound); deferred callables are unified ontoDefer.Callable(type-switched likedoOpPrecall). Concrete/pointer binds (pt.M,t.M) are unchanged.Methodis persisted (proto field 4;pb3_genregenerated),_allocBoundMethodValue200→216 → wire/merkle/gas change; coordinated-upgrade / fresh-genesis only (like ADR-5544), old state still decodes.OpCPUSelectorInterface751→276 (the lazy bind no longer walks) and newOpCPULazyBoundResolve=529 (the call-time walk); net ≈ +54/interface call. Ratio-scaled (reference HW unavailable) — calibration TODO before the fork.cur realmtxtars; rationale ingnovm/adr/pr5737_nil_value_method_panic_timing.md. Pre-existing orthogonal divergences filed separately: method expressions don't support interface, promoted, or mixed-receiver forms #5787, GnoVM: calling a method on a nil interface panics uncatchably instead of a recoverable nil-pointer panic #5850.