Skip to content

gc/thread: iterator / generator / %TypedArray% intrinsic towers are built once per PROCESS, not per realm #8002

Description

@proggeramlug

Same family as #7988 / #8001. One shape, several cells, one fix.

Each of these is a once-per-process if PTR.load() == 0 { build… } gate
whose result is a raw address of an object in the building thread's arena —
while the tower it names is a realm intrinsic and every perry/thread agent has
its own realm.

cells gate
ITERATOR_PROTOTYPE_PTR, ARRAY_ITERATOR_PROTOTYPE_PTR, MAP_/SET_/STRING_/REGEXP_STRING_ITERATOR_PROTOTYPE_PTR (object/iterator_prototypes.rs:41-46) ensure_iterator_prototypes() (:240)
generator / async-generator intrinsic tower, 6 cells (object/mod.rs:280-285) ensure_generator_intrinsics() (object/global_this/generator.rs:716)
TYPED_ARRAY_INTRINSIC_PTR, TYPED_ARRAY_INTRINSIC_PROTO_PTR (object/mod.rs:256-257) ensure_typed_array_intrinsic() (object/global_this/typed_array.rs:418)

attach_iterator_prototype (iterator_prototypes.rs:270-288) then calls
chain_to(obj_ptr, proto_ptr) on every newly allocated iterator on every
thread, using whichever thread built the tower first — so a foreign pointer is
stored into a live object in this agent's own heap, which dangles once the
building thread exits. Any for…of / spread / Iterator.from hits it, i.e. it
is reachable from about the most common operation in the language.

Two of these carry comments that are now false:

  • typed_array.rs:416"Called from populate_global_this_builtins
    (single-threaded under the singleton CAS)"
    . Every thread's first
    js_get_global_this runs populate_global_this_builtins independently, so
    that assumption stops holding the moment a second OS thread exists.
  • object/mod.rs:267-279 records that GC: the two lazy intrinsic-tower builders need #7217's no-move window, and need a gate that can fail first #7251 already hit this for tests and
    fixed it with per_test_global! — which, per per_test_global.rs:60-62,
    "in a non-test build … expands to the plain static it replaced, byte for
    byte"
    . The production defect was never addressed.

scripts/gc_runtime_root_holders.json:90-130 marks the iterator cells
covered_elsewhere; that verdict is about GC-scan coverage
(object::scan_object_cache_roots_mut, object/mod.rs:1141) and says nothing
about the per-realm hazard, so it does not rule this out.

Suggested acceptance

A perry/thread test in which agent B runs for (const x of [1,2,3]) and a
function*, and a perry-runtime unit test in the shape of
a_second_agents_prototype_addresses_are_its_own (#7994) asserting two live
agents get DIFFERENT tower addresses, with per-agent liveness asserted before
distinctness.

The obstacle usually cited ("Darwin has no local-exec TLS") is stale — see
#8001's closing note and crates/perry-runtime/src/tls_hot.rs.

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