mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-03 07:54:40 +08:00
Keep a generator method's generator ABI through the prototype-access widening
A class whose methods are reachable through `X.prototype` widens its method signatures to checked values so a JS caller can hand anything across. The widening replaced every non-void return with DYN, which erased a GENERATOR method's generator type; the generator branch below then had no generator channels to build its metadata from and refused the whole class (SC2001 in `%init`, re-emitted as a runtime fence in a JS file). Every `assert.sameValue(c.m, C.prototype.m)` variant of the campaign therefore refused at runtime, 759 of them in the generator-method chapters alone. A generator's return kind is already a checked-crossable ABI: dynFrom boxes a generator by reference and dyn calls resume it through the dyn generator ops, so only the arguments need the widening. Guard the return widening to non-generator methods. Test262, 1,110 affected variants, before/after on the candidate (class/gen-method 52->76, class/async-gen-method 76->128, class/scope-gen-meth 0->8); no pass->fail and no typed-path delta. Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3451,7 +3451,15 @@ export function collectClassShapeInner(
|
||||
if (shape.bodyType) shape.bodyType = DYN;
|
||||
}
|
||||
ft.params = shapes.map((shape) => shape.type);
|
||||
if (ft.ret.kind !== "void") ft.ret = DYN;
|
||||
// A GENERATOR method's result is the suspended generator its spawn
|
||||
// wrapper answers, so the generator kind IS the checked-crossable
|
||||
// ABI: dynFrom boxes a generator by reference and dyn calls resume
|
||||
// it through the dyn generator ops. Replacing it with plain DYN
|
||||
// would leave the method no representation at all (the generator
|
||||
// branch below needs the channels to build its metadata), which
|
||||
// fenced every `c.m` read on a prototype-accessed class. Only the
|
||||
// ARGUMENTS cross as checked values here.
|
||||
if (ft.ret.kind !== "void" && member.asteriskToken === undefined) ft.ret = DYN;
|
||||
}
|
||||
// JS subclasses can add parameters to a method. Reserve checked
|
||||
// argument slots in the base before constructing its vtable; shorter
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/* A generator method VALUE crossing the checked-value boundary.
|
||||
*
|
||||
* A class whose methods are reachable through its prototype object answers
|
||||
* its methods as checked (dynamic) values: the value read off an instance is
|
||||
* the same function object the prototype carries, a checked callback can hold
|
||||
* it and call it, and the generator object it produces keeps the generator
|
||||
* protocol across the box. Node is the oracle. */
|
||||
class Seq {
|
||||
constructor(tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
*items(n) {
|
||||
for (let i = 0; i < n; i++) yield this.tag + ":" + i;
|
||||
return n;
|
||||
}
|
||||
async *later(n) {
|
||||
yield n + 1;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
function sameValue(actual, expected, label) {
|
||||
if (actual !== expected) throw new Error("not the same: " + label);
|
||||
}
|
||||
function typeofOf(value) {
|
||||
return typeof value;
|
||||
}
|
||||
function callWith(receiver, fn, ...args) {
|
||||
return fn.apply(receiver, args);
|
||||
}
|
||||
|
||||
const seq = new Seq("a");
|
||||
sameValue(seq.items, Seq.prototype.items, "instance method value");
|
||||
sameValue(seq.items, seq.items, "repeated read");
|
||||
sameValue(seq.later, Seq.prototype.later, "async instance method value");
|
||||
console.log(typeofOf(seq.items), typeofOf(seq.later));
|
||||
|
||||
const gen = callWith(seq, seq.items, 2);
|
||||
console.log(gen.next().value, gen.next().value);
|
||||
const finished = gen.next();
|
||||
console.log(finished.value, finished.done);
|
||||
|
||||
// The direct call answers its own generator object. (Identity and reflection
|
||||
// on a generator object are separate, still-fenced operations.)
|
||||
const direct = seq.items(1);
|
||||
console.log(direct.next().value);
|
||||
|
||||
// An async generator value crosses the same boundary.
|
||||
callWith(seq, seq.later, 4)
|
||||
.next()
|
||||
.then(function (step) {
|
||||
console.log("async", step.value, step.done);
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 1349, "san": 2585 }
|
||||
Reference in New Issue
Block a user