|
| 1 | +// #11635: `F.prototype.x = <call>` must not hold F in a register across the |
| 2 | +// call. |
| 3 | +// |
| 4 | +// HIR lowers `F.prototype.x = v` (and the aliased `var proto = F.prototype; |
| 5 | +// proto.x = v`) for a function declaration F into a runtime registration keyed |
| 6 | +// by F's closure. Codegen evaluated F first, then the value, then called the |
| 7 | +// registration with F's register. When the value is a call that collects, an |
| 8 | +// evacuating minor moves F while the register keeps its old address, and the |
| 9 | +// registration reads the retired closure. moment 2.31.0 does exactly this at |
| 10 | +// module init: `proto.toIsoString = deprecate(msg, toISOString$1)` with |
| 11 | +// `proto = Duration.prototype`, where Duration is a function declaration |
| 12 | +// inside the UMD factory. Under a seeded GC schedule the process faulted in |
| 13 | +// `synthetic_class_id_for_function`; without the from-space quarantine the |
| 14 | +// method could be registered under the stale address, so instances never saw |
| 15 | +// it. |
| 16 | +// |
| 17 | +// The shape is kept: F is declared inside a factory and captured by nested |
| 18 | +// functions (so it is a boxed heap closure that can move), the prototype is |
| 19 | +// aliased, and each value comes from a |
| 20 | +// helper that allocates enough garbage for a collection to land inside it. |
| 21 | +// |
| 22 | +// Output must be byte-identical to node. |
| 23 | + |
| 24 | +function churn(rounds: number): number { |
| 25 | + let n = 0; |
| 26 | + for (let r = 0; r < rounds; r++) { |
| 27 | + const a: any[] = new Array(16); |
| 28 | + for (let j = 0; j < 16; j++) a[j] = { j, r, s: "v" + j }; |
| 29 | + n += a.length; |
| 30 | + } |
| 31 | + return n; |
| 32 | +} |
| 33 | + |
| 34 | +let churned = 0; |
| 35 | + |
| 36 | +function factory(): any { |
| 37 | + const tag = "D"; |
| 38 | + function Duration(this: any, v: number) { |
| 39 | + this.v = v; |
| 40 | + } |
| 41 | + function deprecate(msg: string, fn: (this: any) => string): (this: any) => string { |
| 42 | + churned += churn(20000); |
| 43 | + return function (this: any) { |
| 44 | + return msg + ":" + fn.call(this); |
| 45 | + }; |
| 46 | + } |
| 47 | + function show(this: any) { |
| 48 | + return tag + this.v; |
| 49 | + } |
| 50 | + // Nested functions that capture Duration, as moment's do: that is what |
| 51 | + // puts Duration in a box whose read is not re-derivable from a root. |
| 52 | + function isDuration(o: any): boolean { |
| 53 | + return o instanceof Duration; |
| 54 | + } |
| 55 | + function make(v: number): any { |
| 56 | + return new (Duration as any)(v); |
| 57 | + } |
| 58 | + const proto = Duration.prototype; |
| 59 | + proto.a = deprecate("a", show); |
| 60 | + proto.b = deprecate("b", show); |
| 61 | + Duration.prototype.c = deprecate("c", show); |
| 62 | + proto.d = deprecate("d", show); |
| 63 | + (Duration as any).isDuration = isDuration; |
| 64 | + (Duration as any).make = make; |
| 65 | + return Duration; |
| 66 | +} |
| 67 | + |
| 68 | +const D = factory(); |
| 69 | +churn(20000); |
| 70 | +const out: string[] = []; |
| 71 | +for (let i = 0; i < 3; i++) { |
| 72 | + const d = new D(i); |
| 73 | + out.push(d.a(), d.b(), d.c(), d.d()); |
| 74 | + out.push(String(d instanceof D), typeof D.prototype.a, typeof d.d); |
| 75 | + out.push(String(D.isDuration(d)), D.make(i + 10).c()); |
| 76 | +} |
| 77 | +console.log(out.join(" ")); |
| 78 | +console.log("churned", churned > 0); |
0 commit comments