Skip to content

closure body elided + js_box_get null-box for class method via async () => driver.find(...) against real @perryts/mongodb #531

Closed

Description

@proggeramlug

Summary

Sibling to #528 (which v0.5.609 closed for its stated standalone repro). Different symptom, different signal — filing separately.

In the @perryts/mongodb benchmark cell on Perry-AOT, the find_movies_year_gte_2010_sort_limit_50 op fingerprints count=1 instead of 50. Instrumenting the adapter shows that PerryTSDriver.find's body never executes at all under perry — not even the first console.log line on entry — even though the closure async () => driver.find(...) is invoked hundreds of times via the bench's runOp wrapper.

The other 8 driver methods on the same class (findOne, distinct, aggregate, insertMany, updateMany, deleteMany, …) all run their bodies correctly under the same closure-through-runOp pattern. Only driver.find is silently elided.

This is a distinct bug class from #528:

Diagnostic

Instrumented bench/src/adapters/perryts.ts:

async find(db, col, filter, opts): Promise<Doc[]> {
    console.log('[adapter.find] entered');                  // ← never fires
    const findOpts: Record<string, unknown> = {};
    if (opts.sort !== undefined) findOpts.sort = opts.sort;
    // ...
    const cur = this.col(db, col).find(filter as Document, findOpts);
    console.log('[adapter.find] cur typeof=' + typeof cur); // ← never fires
    const arr = await cur.toArray();
    console.log('[adapter.find] arr len=' + ...);           // ← never fires
    return arr as Doc[];
}

Across ~100+ invocations from the bench's runOp closure (async () => driver.find(...)), zero [adapter.find] log lines fire. The closure resolves to undefined.

During the 3 warmup iterations of find_movies, perry-stdlib emits:

[PERRY WARN] js_box_get: invalid box pointer 0x0 #0
[PERRY WARN] js_box_get: invalid box pointer 0x0 #1
[PERRY WARN] js_box_get: invalid box pointer 0x0 #2

That telemetry is from crates/perry-runtime/src/box.rs:45 — it fires when a closure prologue passes a null pointer to js_box_get for a heap-boxed captured mutable variable. This is the smoking gun: the closure for driver.find is being constructed with a missing capture slot. When the closure is invoked, the box-get returns f64::NAN for whatever was supposed to be captured (likely driver or this), and the eventual method dispatch silently no-ops on the null receiver.

Reduction attempts (all passed on v0.5.609)

Could not reduce to a driver-free repro after four attempts:

  1. Driver class with this.col().find({}) chain — works (5)
  2. Same with closure invoked via runOp HOF — works (5)
  3. Cross-module split (Driver exported from another file) — works (5)
  4. 3-deep chain mirroring this.client.db(d).collection(c).find() — works (5)

So the bug needs the real @perryts/mongodb MongoClient / Db / Collection / FindCursor class hierarchy to manifest. The narrowing factor is presumably one of:

  • A specific shape of this-capture from the bench's runOp wrapper that only the deeper class hierarchy hits
  • A method-name collision (PerryTSDriver.find and Collection.find) confusing closure-table dispatch
  • Cross-module class method dispatch where the boxing of this (or driver) is dropped only when the path crosses ≥3 module boundaries

Repro instructions

  1. Clone @perryts/mongodb
  2. Build perry from current main (≥ v0.5.610)
  3. Start a local mongod
  4. perry run bench/run-perryts.ts
  5. Observe find_movies_year_gte_2010_sort_limit_50 fp=count (1) (expected 50)
  6. Observe 3× [PERRY WARN] js_box_get: invalid box pointer 0x0 warnings on stderr during warmup

Asks

  • A driver-free reduction (a synthetic class hierarchy that forces the same js_box_get: invalid box pointer 0x0 shape would be a huge unlock — happy to try more shapes if there's a hypothesis)
  • Or guidance on what closure-prologue / capture-table state to dump from the failing driver.find site to make the box-null capture slot identifiable

Environment

  • perry v0.5.610 (HEAD = 30055e2c)
  • macOS 26.4 (arm64)
  • Reference: bun 1.3.12, node 25.8.0 + tsx — both produce the correct count=50 fingerprint
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