MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add a conservative inline-feasibility pass

+648 -4
+189 -4
src/silver/swarm.c
··· 1558 1558 } 1559 1559 1560 1560 static bool jit_inline_body_feasible(sv_func_t *callee) { 1561 + if (!callee) return false; 1562 + int max_stack = callee->max_stack > 0 ? callee->max_stack : 4; 1563 + int n_locals = callee->max_locals > 0 ? callee->max_locals : 1; 1564 + uint8_t stack_types[max_stack]; 1565 + uint8_t local_types[n_locals]; 1566 + memset(stack_types, SLOT_BOXED, sizeof(stack_types)); 1567 + memset(local_types, SLOT_BOXED, sizeof(local_types)); 1568 + int sp = 0; 1569 + bool side_effect_seen = false; 1570 + 1571 + #define FEAS_POP(n) do { if (sp < (n)) return false; sp -= (n); } while (0) 1572 + #define FEAS_PUSH(t) do { if (sp >= max_stack) return false; stack_types[sp++] = (t); } while (0) 1573 + #define FEAS_GUARD_AFTER_EFFECT() do { if (side_effect_seen) return false; } while (0) 1574 + 1561 1575 uint8_t *ip = callee->code; 1562 1576 uint8_t *end = callee->code + callee->code_len; 1563 1577 while (ip < end) { ··· 1565 1579 int sz = sv_op_size[op]; 1566 1580 if (sz == 0) return false; 1567 1581 switch (op) { 1568 - default: break; 1582 + case OP_GET_ARG: 1583 + FEAS_PUSH(SLOT_BOXED); 1584 + break; 1585 + case OP_CONST_I8: 1586 + FEAS_PUSH(SLOT_NUM); 1587 + break; 1588 + case OP_CONST: { 1589 + uint32_t idx = sv_get_u32(ip + 1); 1590 + if (idx >= (uint32_t)callee->const_count) return false; 1591 + FEAS_PUSH(vtype(callee->constants[idx]) == T_NUM ? SLOT_NUM : SLOT_BOXED); 1592 + break; 1593 + } 1594 + case OP_CONST8: { 1595 + uint8_t idx = sv_get_u8(ip + 1); 1596 + if (idx >= (uint8_t)callee->const_count) return false; 1597 + FEAS_PUSH(vtype(callee->constants[idx]) == T_NUM ? SLOT_NUM : SLOT_BOXED); 1598 + break; 1599 + } 1600 + case OP_UNDEF: case OP_NULL: case OP_TRUE: case OP_FALSE: 1601 + case OP_THIS: 1602 + case OP_GET_UPVAL: 1603 + case OP_GET_GLOBAL: 1604 + case OP_SPECIAL_OBJ: 1605 + if (side_effect_seen && (op == OP_GET_GLOBAL || op == OP_SPECIAL_OBJ)) 1606 + return false; 1607 + FEAS_PUSH(SLOT_BOXED); 1608 + break; 1609 + case OP_GET_LOCAL: { 1610 + uint16_t idx = sv_get_u16(ip + 1); 1611 + if (idx >= (uint16_t)n_locals) return false; 1612 + FEAS_PUSH(local_types[idx]); 1613 + break; 1614 + } 1615 + case OP_GET_LOCAL8: { 1616 + uint8_t idx = sv_get_u8(ip + 1); 1617 + if (idx >= (uint8_t)n_locals) return false; 1618 + FEAS_PUSH(local_types[idx]); 1619 + break; 1620 + } 1621 + case OP_PUT_LOCAL: { 1622 + uint16_t idx = sv_get_u16(ip + 1); 1623 + if (idx >= (uint16_t)n_locals || sp < 1) return false; 1624 + local_types[idx] = stack_types[--sp]; 1625 + break; 1626 + } 1627 + case OP_PUT_LOCAL8: { 1628 + uint8_t idx = sv_get_u8(ip + 1); 1629 + if (idx >= (uint8_t)n_locals || sp < 1) return false; 1630 + local_types[idx] = stack_types[--sp]; 1631 + break; 1632 + } 1633 + case OP_SET_LOCAL: { 1634 + uint16_t idx = sv_get_u16(ip + 1); 1635 + if (idx >= (uint16_t)n_locals || sp < 1) return false; 1636 + local_types[idx] = stack_types[sp - 1]; 1637 + break; 1638 + } 1639 + case OP_SET_LOCAL8: { 1640 + uint8_t idx = sv_get_u8(ip + 1); 1641 + if (idx >= (uint8_t)n_locals || sp < 1) return false; 1642 + local_types[idx] = stack_types[sp - 1]; 1643 + break; 1644 + } 1645 + case OP_SET_LOCAL_UNDEF: { 1646 + uint16_t idx = sv_get_u16(ip + 1); 1647 + if (idx >= (uint16_t)n_locals) return false; 1648 + local_types[idx] = SLOT_BOXED; 1649 + break; 1650 + } 1651 + case OP_POP: 1652 + FEAS_POP(1); 1653 + break; 1654 + case OP_DUP: 1655 + if (sp < 1) return false; 1656 + { uint8_t top_type = stack_types[sp - 1]; FEAS_PUSH(top_type); } 1657 + break; 1658 + case OP_DUP2: { 1659 + if (sp < 2) return false; 1660 + uint8_t a = stack_types[sp - 2]; 1661 + uint8_t b = stack_types[sp - 1]; 1662 + FEAS_PUSH(a); 1663 + FEAS_PUSH(b); 1664 + break; 1665 + } 1666 + case OP_INSERT2: { 1667 + if (sp < 2) return false; 1668 + uint8_t a = stack_types[sp - 1]; 1669 + uint8_t obj = stack_types[sp - 2]; 1670 + stack_types[sp - 2] = a; 1671 + stack_types[sp - 1] = obj; 1672 + FEAS_PUSH(a); 1673 + break; 1674 + } 1675 + case OP_INSERT3: { 1676 + if (sp < 3) return false; 1677 + uint8_t a = stack_types[sp - 1]; 1678 + uint8_t prop = stack_types[sp - 2]; 1679 + uint8_t obj = stack_types[sp - 3]; 1680 + stack_types[sp - 3] = a; 1681 + stack_types[sp - 2] = obj; 1682 + stack_types[sp - 1] = prop; 1683 + FEAS_PUSH(a); 1684 + break; 1685 + } 1686 + case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: 1687 + case OP_ADD_NUM: case OP_SUB_NUM: case OP_MUL_NUM: case OP_DIV_NUM: { 1688 + if (sp < 2) return false; 1689 + bool known_num = 1690 + stack_types[sp - 1] == SLOT_NUM && stack_types[sp - 2] == SLOT_NUM; 1691 + if (!known_num) FEAS_GUARD_AFTER_EFFECT(); 1692 + sp -= 2; 1693 + FEAS_PUSH(SLOT_NUM); 1694 + break; 1695 + } 1696 + case OP_MOD: 1697 + FEAS_GUARD_AFTER_EFFECT(); 1698 + FEAS_POP(2); 1699 + FEAS_PUSH(SLOT_NUM); 1700 + break; 1701 + case OP_NEG: 1702 + if (sp < 1) return false; 1703 + FEAS_GUARD_AFTER_EFFECT(); 1704 + stack_types[sp - 1] = SLOT_NUM; 1705 + break; 1706 + case OP_LT: case OP_LE: case OP_GT: case OP_GE: 1707 + FEAS_GUARD_AFTER_EFFECT(); 1708 + FEAS_POP(2); 1709 + FEAS_PUSH(SLOT_BOXED); 1710 + break; 1711 + case OP_SEQ: case OP_SNE: case OP_EQ: case OP_NE: 1712 + FEAS_POP(2); 1713 + FEAS_PUSH(SLOT_BOXED); 1714 + break; 1715 + case OP_IS_UNDEF: case OP_IS_NULL: 1716 + if (sp < 1) return false; 1717 + stack_types[sp - 1] = SLOT_BOXED; 1718 + break; 1719 + case OP_JMP: 1720 + case OP_NOP: case OP_LINE_NUM: case OP_COL_NUM: case OP_LABEL: 1721 + break; 1722 + case OP_JMP_TRUE: case OP_JMP_FALSE: 1723 + case OP_JMP_TRUE8: case OP_JMP_FALSE8: 1724 + FEAS_POP(1); 1725 + break; 1726 + case OP_JMP_TRUE_PEEK: case OP_JMP_FALSE_PEEK: 1727 + if (sp < 1) return false; 1728 + break; 1729 + case OP_RETURN: 1730 + FEAS_POP(1); 1731 + break; 1732 + case OP_RETURN_UNDEF: 1733 + break; 1734 + case OP_GET_FIELD: 1735 + if (side_effect_seen) return false; 1736 + FEAS_POP(1); 1737 + FEAS_PUSH(SLOT_BOXED); 1738 + break; 1739 + case OP_GET_FIELD2: 1740 + if (side_effect_seen) return false; 1741 + if (sp < 1) return false; 1742 + FEAS_PUSH(SLOT_BOXED); 1743 + break; 1744 + case OP_PUT_FIELD: 1745 + FEAS_POP(2); 1746 + side_effect_seen = true; 1747 + break; 1748 + default: 1749 + return false; 1569 1750 } 1570 1751 ip += sz; 1571 1752 } 1753 + #undef FEAS_GUARD_AFTER_EFFECT 1754 + #undef FEAS_PUSH 1755 + #undef FEAS_POP 1572 1756 return true; 1573 1757 } 1574 1758 ··· 1923 2107 1924 2108 case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: 1925 2109 case OP_ADD_NUM: case OP_SUB_NUM: case OP_MUL_NUM: case OP_DIV_NUM: { 2110 + bool l_known_num = inl_slot_type[isp - 2] == SLOT_NUM; 2111 + bool r_known_num = inl_slot_type[isp - 1] == SLOT_NUM; 1926 2112 MIR_reg_t rr = inl_vs[--isp]; 1927 2113 MIR_reg_t rl = inl_vs[--isp]; 1928 2114 int dst_i = isp; 1929 2115 MIR_reg_t rd = inl_vs[isp++]; 1930 2116 1931 - if (!(op == OP_ADD_NUM || op == OP_SUB_NUM || 1932 - op == OP_MUL_NUM || op == OP_DIV_NUM)) { 2117 + if (!l_known_num) 1933 2118 mir_emit_is_num_guard(ctx, jit_func, r_bool, rl, slow); 2119 + if (!r_known_num) 1934 2120 mir_emit_is_num_guard(ctx, jit_func, r_bool, rr, slow); 1935 - } 1936 2121 1937 2122 if (!*p_d_slot) { 1938 2123 *p_d_slot = MIR_new_func_reg(ctx, jit_func->u.func,
+178
tests/bench_jit_method_inlining.js
··· 1 + function nowMs() { 2 + if (typeof performance !== "undefined" && performance && typeof performance.now === "function") 3 + return performance.now(); 4 + return Date.now(); 5 + } 6 + 7 + function parseScale() { 8 + if (typeof process === "undefined" || !process || !process.argv) return 1; 9 + const raw = Number(process.argv[2]); 10 + return Number.isFinite(raw) && raw > 0 ? raw : 1; 11 + } 12 + 13 + function sortNumbers(values) { 14 + const out = values.slice(); 15 + for (let i = 1; i < out.length; i++) { 16 + const v = out[i]; 17 + let j = i - 1; 18 + while (j >= 0 && out[j] > v) { 19 + out[j + 1] = out[j]; 20 + j--; 21 + } 22 + out[j + 1] = v; 23 + } 24 + return out; 25 + } 26 + 27 + function median(values) { 28 + const sorted = sortNumbers(values); 29 + return sorted[(sorted.length / 2) | 0]; 30 + } 31 + 32 + const SCALE = parseScale(); 33 + const RUNS = 7; 34 + let sink = 0; 35 + 36 + function bench(label, iterations, fn) { 37 + for (let i = 0; i < 3; i++) sink ^= fn(Math.max(1, (iterations / 10) | 0)) | 0; 38 + 39 + const samples = []; 40 + let result = 0; 41 + for (let i = 0; i < RUNS; i++) { 42 + const t0 = nowMs(); 43 + result = fn(iterations); 44 + samples.push(nowMs() - t0); 45 + } 46 + 47 + sink ^= result | 0; 48 + const med = median(samples); 49 + const opsPerMs = med > 0 ? (iterations / med).toFixed(2) : "inf"; 50 + console.log(label + ": " + med.toFixed(3) + "ms, " + opsPerMs + " ops/ms, result=" + result); 51 + return med; 52 + } 53 + 54 + function Sequence(start) { 55 + this.item = start; 56 + } 57 + 58 + Sequence.prototype.next = function() { 59 + const old = this.item; 60 + this.item = old + 2; 61 + return old; 62 + }; 63 + 64 + function runInlineableMethod(n) { 65 + const seq = new Sequence(1); 66 + let out = 0; 67 + for (let i = 0; i < n; i++) out = seq.next(); 68 + return out + seq.item; 69 + } 70 + 71 + function runManualEquivalent(n) { 72 + const seq = new Sequence(1); 73 + let out = 0; 74 + for (let i = 0; i < n; i++) { 75 + out = seq.item; 76 + seq.item = out + 2; 77 + } 78 + return out + seq.item; 79 + } 80 + 81 + function PolyA(start) { 82 + this.item = start; 83 + } 84 + 85 + function PolyB(start) { 86 + this.padding = 1; 87 + this.item = start; 88 + } 89 + 90 + PolyA.prototype.next = Sequence.prototype.next; 91 + PolyB.prototype.next = Sequence.prototype.next; 92 + 93 + function callPoly(obj) { 94 + return obj.next(); 95 + } 96 + 97 + function runPolymorphicMethod(n) { 98 + const a = new PolyA(1); 99 + const b = new PolyB(1); 100 + let out = 0; 101 + for (let i = 0; i < n; i++) out = callPoly((i & 1) === 0 ? a : b); 102 + return out + a.item + b.item; 103 + } 104 + 105 + function Vec2(x, y) { 106 + this.x = x; 107 + this.y = y; 108 + } 109 + 110 + Vec2.prototype.sumAndBump = function(dx, dy) { 111 + const old = this.x + this.y; 112 + this.x = this.x + dx; 113 + this.y = this.y + dy; 114 + return old; 115 + }; 116 + 117 + function runInlineableArgs(n) { 118 + const v = new Vec2(1, 2); 119 + let out = 0; 120 + for (let i = 0; i < n; i++) out = v.sumAndBump(1, 2); 121 + return out + v.x + v.y; 122 + } 123 + 124 + function runManualArgs(n) { 125 + const v = new Vec2(1, 2); 126 + let out = 0; 127 + for (let i = 0; i < n; i++) { 128 + out = v.x + v.y; 129 + v.x = v.x + 1; 130 + v.y = v.y + 2; 131 + } 132 + return out + v.x + v.y; 133 + } 134 + 135 + function LargeMethod(start) { 136 + this.item = start; 137 + this.flag = false; 138 + } 139 + 140 + LargeMethod.prototype.next = function() { 141 + const old = this.item; 142 + this.item = old + 2; 143 + if (this.flag) { 144 + let sum = 0; 145 + sum = sum + old; 146 + sum = sum + this.item; 147 + sum = sum + old; 148 + sum = sum + this.item; 149 + return sum; 150 + } 151 + return old; 152 + }; 153 + 154 + function runOverBudgetMethod(n) { 155 + const seq = new LargeMethod(1); 156 + let out = 0; 157 + for (let i = 0; i < n; i++) out = seq.next(); 158 + return out + seq.item; 159 + } 160 + 161 + const iterations = Math.max(100000, Math.floor(1000000 * SCALE)); 162 + 163 + console.log("method inlining benchmark"); 164 + console.log("scale: " + SCALE); 165 + console.log("iterations: " + iterations); 166 + 167 + const manual = bench("manual slot loop", iterations, runManualEquivalent); 168 + const inlineable = bench("inlineable method next()", iterations, runInlineableMethod); 169 + const poly = bench("polymorphic method fallback", iterations, runPolymorphicMethod); 170 + const manualArgs = bench("manual arg slot loop", iterations, runManualArgs); 171 + const inlineArgs = bench("inlineable method with args", iterations, runInlineableArgs); 172 + const overBudget = bench("over-budget method", iterations, runOverBudgetMethod); 173 + 174 + console.log("next()/manual: " + (inlineable / manual).toFixed(2) + "x"); 175 + console.log("polymorphic/inlineable: " + (poly / inlineable).toFixed(2) + "x"); 176 + console.log("args/manual: " + (inlineArgs / manualArgs).toFixed(2) + "x"); 177 + console.log("over-budget/inlineable: " + (overBudget / inlineable).toFixed(2) + "x"); 178 + console.log("sink: " + sink);
+86
tests/jit_method_inlining_fuzz.js
··· 1 + function assertEq(actual, expected, label) { 2 + if (actual !== expected) 3 + throw new Error(label + ": expected " + expected + ", got " + actual); 4 + } 5 + 6 + function sharedNext(delta) { 7 + const old = this.item; 8 + this.item = old + delta; 9 + return old; 10 + } 11 + 12 + function runCase(k) { 13 + function Box(start) { 14 + if ((k & 1) === 0) { 15 + this.item = start; 16 + this.pad = k; 17 + } else { 18 + this.pad = k; 19 + this.item = start; 20 + } 21 + if ((k & 2) !== 0) this.more = k + 1; 22 + } 23 + 24 + Box.prototype.next = sharedNext; 25 + 26 + function callNext(obj, delta) { 27 + return obj.next(delta); 28 + } 29 + 30 + const obj = new Box(k + 1); 31 + const delta = (k % 4) + 1; 32 + for (let i = 0; i < 260; i++) callNext(obj, delta); 33 + 34 + const before = obj.item; 35 + 36 + switch (k % 5) { 37 + case 0: { 38 + obj.extra = k * 3; 39 + assertEq(callNext(obj, delta), before, "expando result " + k); 40 + assertEq(obj.item, before + delta, "expando store " + k); 41 + assertEq(obj.extra, k * 3, "expando preserved " + k); 42 + break; 43 + } 44 + case 1: { 45 + Box.prototype.next = function(d) { 46 + return this.item + d + 1000; 47 + }; 48 + assertEq(callNext(obj, delta), before + delta + 1000, "prototype replacement " + k); 49 + assertEq(obj.item, before, "prototype replacement no old store " + k); 50 + break; 51 + } 52 + case 2: { 53 + obj.next = function(d) { 54 + return this.item * 10 + d; 55 + }; 56 + assertEq(callNext(obj, delta), before * 10 + delta, "own shadow " + k); 57 + assertEq(obj.item, before, "own shadow no old store " + k); 58 + break; 59 + } 60 + case 3: { 61 + const alt = { other: k, item: 50 + k }; 62 + Object.setPrototypeOf(alt, Box.prototype); 63 + assertEq(callNext(alt, delta), 50 + k, "alternate shape result " + k); 64 + assertEq(alt.item, 50 + k + delta, "alternate shape store " + k); 65 + assertEq(alt.other, k, "alternate shape preserved " + k); 66 + break; 67 + } 68 + default: { 69 + let backing = before + 5; 70 + Object.defineProperty(obj, "item", { 71 + get() { return backing; }, 72 + set(v) { backing = v * 2; }, 73 + configurable: true 74 + }); 75 + assertEq(callNext(obj, delta), before + 5, "accessor result " + k); 76 + assertEq(backing, (before + 5 + delta) * 2, "accessor setter " + k); 77 + break; 78 + } 79 + } 80 + } 81 + 82 + for (let round = 0; round < 3; round++) { 83 + for (let k = 0; k < 40; k++) runCase(round * 40 + k); 84 + } 85 + 86 + console.log("jit method inlining fuzz: ok");
+195
tests/jit_method_inlining_stress.js
··· 1 + function fail(label, expected, actual) { 2 + throw new Error(label + ": expected " + expected + ", got " + actual); 3 + } 4 + 5 + function assertEq(actual, expected, label) { 6 + if (actual !== expected) fail(label, expected, actual); 7 + } 8 + 9 + function warm(fn, n) { 10 + for (let i = 0; i < (n || 300); i++) fn(i); 11 + } 12 + 13 + // Basic direct-slot load/add/store through a prototype method. 14 + function Seq(start) { 15 + this.item = start; 16 + } 17 + 18 + Seq.prototype.next = function() { 19 + const old = this.item; 20 + this.item = old + 2; 21 + return old; 22 + }; 23 + 24 + function runSeqLoop(n) { 25 + const seq = new Seq(1); 26 + let out = 0; 27 + for (let i = 0; i < n; i++) out = seq.next(); 28 + return out + seq.item; 29 + } 30 + 31 + for (let i = 0; i < 200; i++) runSeqLoop(8); 32 + assertEq(runSeqLoop(1000), 4000, "hot loop inline result"); 33 + 34 + function callSeq(seq) { 35 + return seq.next(); 36 + } 37 + 38 + const seq = new Seq(1); 39 + warm(function() { callSeq(seq); }); 40 + assertEq(callSeq(seq), 601, "basic inline result"); 41 + assertEq(seq.item, 603, "basic inline store"); 42 + 43 + // Same call site, different receiver shape and different property slot order. 44 + function SlotA() { 45 + this.item = 1; 46 + } 47 + 48 + SlotA.prototype.next = Seq.prototype.next; 49 + 50 + function callSlotA(obj) { 51 + return obj.next(); 52 + } 53 + 54 + const slotA = new SlotA(); 55 + warm(function() { callSlotA(slotA); }); 56 + 57 + const slotB = { padding: 99, item: 10 }; 58 + Object.setPrototypeOf(slotB, SlotA.prototype); 59 + assertEq(callSlotA(slotB), 10, "receiver shape fallback result"); 60 + assertEq(slotB.item, 12, "receiver shape fallback store"); 61 + assertEq(slotB.padding, 99, "receiver shape fallback preserves other slot"); 62 + 63 + // Prototype method identity changes after the call site has warmed. 64 + function Replaceable(start) { 65 + this.item = start; 66 + } 67 + 68 + Replaceable.prototype.next = function() { 69 + const old = this.item; 70 + this.item = old + 1; 71 + return old; 72 + }; 73 + 74 + function callReplaceable(obj) { 75 + return obj.next(); 76 + } 77 + 78 + const replaceable = new Replaceable(5); 79 + warm(function() { callReplaceable(replaceable); }); 80 + const replaceableBefore = replaceable.item; 81 + Replaceable.prototype.next = function() { 82 + return this.item + 1000; 83 + }; 84 + assertEq(callReplaceable(replaceable), replaceableBefore + 1000, "prototype method replacement"); 85 + assertEq(replaceable.item, replaceableBefore, "prototype replacement does not run old body"); 86 + 87 + // Own method shadowing changes the receiver shape and callee identity. 88 + function Shadowed(start) { 89 + this.item = start; 90 + } 91 + 92 + Shadowed.prototype.next = Seq.prototype.next; 93 + 94 + function callShadowed(obj) { 95 + return obj.next(); 96 + } 97 + 98 + const shadowed = new Shadowed(7); 99 + warm(function() { callShadowed(shadowed); }); 100 + shadowed.next = function() { 101 + return this.item * 10; 102 + }; 103 + assertEq(callShadowed(shadowed), shadowed.item * 10, "own method shadow fallback"); 104 + 105 + // Accessor conversion after warmup must not use stale direct slot metadata. 106 + function AccessorBox(start) { 107 + this.item = start; 108 + } 109 + 110 + AccessorBox.prototype.next = Seq.prototype.next; 111 + 112 + function callAccessor(obj) { 113 + return obj.next(); 114 + } 115 + 116 + const accessor = new AccessorBox(3); 117 + warm(function() { callAccessor(accessor); }); 118 + let backing = 20; 119 + Object.defineProperty(accessor, "item", { 120 + get() { return backing; }, 121 + set(v) { backing = v + 100; }, 122 + configurable: true 123 + }); 124 + assertEq(callAccessor(accessor), 20, "accessor fallback getter result"); 125 + assertEq(backing, 122, "accessor fallback setter result"); 126 + 127 + // A callee with a side effect before a later shape-dependent read should not be 128 + // inlined; otherwise a late guard failure would rerun the method and duplicate 129 + // the side effect. 130 + function LateGuardBox() { 131 + this.count = 0; 132 + } 133 + 134 + LateGuardBox.prototype.sideThenRead = function(other) { 135 + this.count = this.count + 1; 136 + return other.value; 137 + }; 138 + 139 + function callLateGuard(box, other) { 140 + return box.sideThenRead(other); 141 + } 142 + 143 + const late = new LateGuardBox(); 144 + const monoOther = { value: 1 }; 145 + warm(function() { callLateGuard(late, monoOther); }); 146 + const lateBefore = late.count; 147 + const otherShape = { padding: 0, value: 7 }; 148 + assertEq(callLateGuard(late, otherShape), 7, "late guard fallback result"); 149 + assertEq(late.count, lateBefore + 1, "late guard does not duplicate side effect"); 150 + 151 + // Argument mapping and branch returns in an inlineable method body. 152 + function Accum(start) { 153 + this.item = start; 154 + } 155 + 156 + Accum.prototype.addScaled = function(delta, scale) { 157 + if (delta < 0) return this.item; 158 + const old = this.item; 159 + this.item = old + delta * scale; 160 + return this.item; 161 + }; 162 + 163 + function callAddScaled(obj, delta, scale) { 164 + return obj.addScaled(delta, scale); 165 + } 166 + 167 + const accum = new Accum(2); 168 + warm(function() { callAddScaled(accum, 1, 3); }); 169 + const accumBefore = accum.item; 170 + assertEq(callAddScaled(accum, 2, 5), accumBefore + 10, "argument mapping result"); 171 + assertEq(callAddScaled(accum, -1, 5), accumBefore + 10, "branch return result"); 172 + 173 + // Class method path, including class-created prototype methods. 174 + class ClassSeq { 175 + constructor(start) { 176 + this.item = start; 177 + } 178 + 179 + next() { 180 + const old = this.item; 181 + this.item = old + 4; 182 + return old; 183 + } 184 + } 185 + 186 + function callClassSeq(obj) { 187 + return obj.next(); 188 + } 189 + 190 + const classSeq = new ClassSeq(11); 191 + warm(function() { callClassSeq(classSeq); }); 192 + assertEq(callClassSeq(classSeq), 1211, "class inline result"); 193 + assertEq(classSeq.item, 1215, "class inline store"); 194 + 195 + console.log("jit method inlining stress: ok");