Skip to content

Symex: typed step-result wire; suspend on unresolved globals; drop kind tags#589

Merged
kumarak merged 1 commit into
mainfrom
symex-typed-wire-protocol
May 5, 2026
Merged

Symex: typed step-result wire; suspend on unresolved globals; drop kind tags#589
kumarak merged 1 commit into
mainfrom
symex-typed-wire-protocol

Conversation

@pgoodman

@pgoodman pgoodman commented May 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Strips three variants of "stuff a kind tag in a value when the type system already had it" from the symex policy boundary, plus replaces a silent default-value fallback with failure-atomic suspension.

  • ("ptr", N) wrapping removed. Pointers travel as bare PyLongs. The kind tag was already inconsistent — extract_address had a bare-int fallback because pointers loaded out of memory came through that way; only fresh mints (make_literal_ptr, resume_addr, libc models) wrapped. has_address now matches concrete_has_address (non-bool, non-zero PyLong). Consumer-side defensive decoders dropped across dispatch.py, lens.py, events.py, and the python_to_value* helpers.

  • CallAction enum dropped. (SKIP, v) and (MODEL, v) fell through to identical C++ behavior, so the wrapper was speculative ceremony. Handlers now return their replacement value directly; None / NotImplemented falls through; Skip(value) is the typed disambiguator for the rare "skip with None" case (e.g. ctx.default()). CallAction::MODEL removed from Suspension.h.

  • Step results are typed dataclasses. SymbolicStep and the concrete step no longer emit ("completed", v)-style string-tagged tuples; they construct Completed / Errored / Budget / Branch / Switch / MemAddrSuspension / GlobalSuspension / Suspended instances. Forks use typed BranchFork / SwitchFork / MemAddrFork / GlobalFork. Engine dispatches via isinstance. Drops StepResultKind and the latent forks[0]["sub_kind"] reader bug (the fork dict's "kind" field was never the right discriminator). The previous "call-addr" / "load-addr" / "store-addr" sub-kinds collapse into one MemAddrSuspension with is_write and is_call_target booleans.

  • Failure-atomic unresolved globals. compute_global_ptr no longer drops a valid address_hint because the IR-derived size is 0 (place_at substitutes 8 internally). When resolution genuinely fails, the policy hands off via a new Policy::on_unresolved_global hook: the concrete default errors the path with a new ErrorKind::UNRESOLVED_GLOBAL; the symbolic policy snapshots, re-pushes the suspended work item, and emits a GlobalContinuation (which already existed but was never wired). New _interp.resume_global(state, entity_id, address) writes the resolved address into the snapshot's global_addresses cache so the re-stepped COMPUTE_GLOBAL_PTR cache-hits. engine.py dispatches global suspensions through the existing intercept.address_for(kind="global") chain, terminating with Terminal.UNRESOLVED_GLOBAL when no handler claims it.

…nd tags

Strips three variants of "stuff a kind tag in a value when the type system
already had it" from the symex policy boundary, plus replaces a silent
default-value fallback with failure-atomic suspension.

* ("ptr", N) wrapping removed. Pointers travel as bare PyLongs. The kind
  tag was already inconsistent — extract_address had a bare-int fallback
  because pointers loaded out of memory came through that way; only fresh
  mints (make_literal_ptr, resume_addr, libc models) wrapped. has_address
  now matches concrete_has_address (non-bool, non-zero PyLong). Consumer-
  side defensive decoders dropped across dispatch.py, lens.py, events.py,
  and the python_to_value* helpers.

* CallAction enum dropped. (SKIP, v) and (MODEL, v) fell through to
  identical C++ behavior, so the wrapper was speculative ceremony. Handlers
  now return their replacement value directly; None / NotImplemented falls
  through; Skip(value) is the typed disambiguator for the rare "skip with
  None" case (e.g. ctx.default()). CallAction::MODEL removed from
  Suspension.h.

* Step results are typed dataclasses. SymbolicStep and the concrete step
  no longer emit ("completed", v)-style string-tagged tuples; they
  construct Completed / Errored / Budget / Branch / Switch /
  MemAddrSuspension / GlobalSuspension / Suspended instances. Forks use
  typed BranchFork / SwitchFork / MemAddrFork / GlobalFork. Engine
  dispatches via isinstance. Drops StepResultKind and the latent
  forks[0]["sub_kind"] reader bug (the fork dict's "kind" field was never
  the right discriminator). The previous "call-addr" / "load-addr" /
  "store-addr" sub-kinds collapse into one MemAddrSuspension with is_write
  and is_call_target booleans.

* Failure-atomic unresolved globals. compute_global_ptr no longer drops a
  valid address_hint because the IR-derived size is 0 (place_at substitutes
  8 internally). When resolution genuinely fails, the policy hands off via
  a new Policy::on_unresolved_global hook: the concrete default errors the
  path with a new ErrorKind::UNRESOLVED_GLOBAL; the symbolic policy
  snapshots, re-pushes the suspended work item, and emits a
  GlobalContinuation (which already existed but was never wired). New
  _interp.resume_global(state, entity_id, address) writes the resolved
  address into the snapshot's global_addresses cache so the re-stepped
  COMPUTE_GLOBAL_PTR cache-hits. engine.py dispatches global suspensions
  through the existing intercept.address_for(kind="global") chain,
  terminating with Terminal.UNRESOLVED_GLOBAL when no handler claims it.
@pgoodman pgoodman requested a review from kumarak May 5, 2026 00:49
@kumarak kumarak merged commit d7743f9 into main May 5, 2026
2 checks passed
@kumarak kumarak deleted the symex-typed-wire-protocol branch May 5, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants