Skip to content

Statepoint bridge emits no statepoint on invoke — every call inside a try is unrooted #7327

Description

@proggeramlug

The explicit statepoint bridge emits no statepoint on invoke, so every call inside a try is unrooted under PERRY_STATEPOINTS=1.

Evidence

In the emitted IR: 58 invokes, 0 carrying gc.statepoint. Invoked callees include allocating runtime helpers — js_object_alloc_class_inline_keys, js_array_push_f64, js_native_call_method_by_id.

Isolation, same program and runtime, PERRY_STACKMAP_WALKER=unwind:

backend result
explicit bridge 0/3 clean
RS4GC 3/3
shadow stack 3/3

So this is bridge-specific; RS4GC covers invoke correctly.

The reporting is silent about it

--statepoint-report counts "310 textual calls" and never sees these. "0 parser fallbacks" therefore says nothing about any call inside a try — the report's clean bill of health has a hole exactly where exceptions are.

This matters because #7302 moved exception lowering to invoke/landingpad precisely so that try-carrying functions could take statepoints like any other, and #7314 added 09_try_catch_roots.ts because nothing in the suite contained a try at all (0 of 8 probes). The probe passes; the bridge still does not root invoked calls.

Reproducer: wt-spsoak/gc-soak/try_root_probe.ts. Found during the statepoint adoption soak.

Activity

  1. proggeramlug commented on Aug 3, 2026

    @proggeramlug
    ContributorAuthor

    Fixed in #7330 — but as a refusal, not as invoke support, and the reason matters for the plan.

    is_call matched only call/tail call, so an invoke skipped both the statepoint conversion and the fail-closed panic! beneath it. It now refuses when an invoke carries live roots (and the callee is neither an llvm.* intrinsic nor in the audited cannot-collect table), and invokes are counted by --statepoint-report so the census stops overstating its coverage.

    The finding underneath is larger than the bug: there is currently no working statepoint path for try-carrying code on a default toolchain.

    • The bridge cannot express a statepoint on an invoke.
    • RS4GC can, but on a default macOS toolchain it fails at clang -c with error: unterminated attribute group under Apple clang 21, and needs PERRY_LLVM_CLANG pointed at a version-matched LLVM 22. Verified pre-existing — identical failure with an unmodified binary built before fix(gc): the statepoint bridge must refuse an invoke it cannot root (#7327) #7330.

    Exceptions are not an edge case, so this is on the critical path for removing the shadow stack. Two ways forward, and they are not equivalent:

    1. Teach the bridge invokes — the statepoint must itself become an invoke, with gc.result and the relocates in the normal successor. RS4GC is the in-tree reference for the shape.
    2. Make RS4GC work on a stock toolchain — which likely means it stops depending on a separately-installed LLVM, or the in-process backend (In-process LLVM backend: native construction via the C API, opt-in (#7241, engine-plan layer 0) #7301) becomes the path so the pass pipeline and the parser are the same version by construction.

    Option 2 looks structurally better: #7301 already pins LLVM 22 in-process and its rs4gc_schedules_in_process test proves RS4GC schedules there. The version-skew problem this hits is exactly what the in-process backend exists to remove.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions