Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6e5dc1a
[JSC] Add live exports to module records
Jarred-Sumner Oct 14, 2026
16c9b13
[JSC] Allow a Proxy in the global object's prototype chain
Jarred-Sumner Oct 14, 2026
6a9159f
[JSC] Make the CodeCache entry limit configurable
Jarred-Sumner Oct 14, 2026
4e9ab72
[JSC] Resolve a static import once while it is loading
Jarred-Sumner Oct 14, 2026
5821913
[JSC] Compile /(?:X)+/ as /(?:X)*/ when X can always match the empty …
Jarred-Sumner Oct 14, 2026
df3fccd
[JSC] Function.prototype.call should not need an array for a few argu…
Jarred-Sumner Oct 14, 2026
93890b6
[JSC] Make the checks for live module exports cheaper
Jarred-Sumner Oct 15, 2026
1b38d15
[JSC] YarrJIT should save only the frame slots of the alternative tha…
Jarred-Sumner Oct 15, 2026
52cfbe9
[JSC] A WebAssembly import of a re-exported live export follows the r…
Jarred-Sumner Oct 15, 2026
2fecf94
[JSC] YarrJIT: a greedy group in tail position needs no state from ea…
Jarred-Sumner Oct 15, 2026
150a846
[JSC] Yarr: neighbouring alternatives of one character are one charac…
Jarred-Sumner Oct 15, 2026
ecb1cca
[JSC] Yarr: a tail loop matches a run of characters without going aro…
Jarred-Sumner Oct 15, 2026
ce8cd0f
[JSC] Yarr: the limit on a pattern's length does not count what is in…
Jarred-Sumner Oct 16, 2026
97bebb5
[JSC] YarrJIT should free the ParenContexts of a failed attempt befor…
Jarred-Sumner Oct 17, 2026
1dcfd63
Merge branch 'main' into claude/module-live-exports
Jarred-Sumner Oct 24, 2026
464364e
Merge branch 'main' into claude/module-live-exports
Jarred-Sumner Oct 26, 2026
0a1dc02
[JSC] Yarr: a character class with a property escape counts in full t…
Jarred-Sumner Oct 26, 2026
44bcc6a
[JSC] Yarr: only a class of characters and ranges is left out of the …
Jarred-Sumner Oct 26, 2026
eae4fc2
Merge branch 'main' into claude/module-live-exports
Jarred-Sumner Oct 26, 2026
cb2e3b1
[JSC] mergeSingleCharacterAlternatives() should restore m_isLastAlter…
Jarred-Sumner Oct 26, 2026
bb19c72
[JSC] function-prototype-call-not-called-as-a-method.js should tell u…
Jarred-Sumner Oct 26, 2026
0bf60dd
[JSC] Reword the comments about the Yarr pattern length limit and tai…
Jarred-Sumner Oct 26, 2026
2a1191f
[JSC] resolveExport() should treat an AOT module's `export *` targets…
Jarred-Sumner Oct 26, 2026
03984bd
Merge branch 'main' into claude/module-live-exports
Jarred-Sumner Oct 27, 2026
559f519
[JSC] Four RegExp stress tests should run in under 200 ms
Jarred-Sumner Oct 27, 2026
35c0e0c
Merge branch 'main' into claude/module-live-exports
Jarred-Sumner Oct 27, 2026
f2b7fc8
Merge branch 'main' into claude/module-live-exports
Jarred-Sumner Oct 27, 2026
482602d
Merge branch 'main' into claude/module-live-exports
Jarred-Sumner Oct 28, 2026
1840866
[JSC] A RegExp nested too deep should throw the same error as before
Jarred-Sumner Oct 28, 2026
1fb1904
Merge branch 'main' into claude/module-live-exports
Jarred-Sumner Oct 28, 2026
483c671
Merge branch 'main' into claude/module-live-exports
Jarred-Sumner Oct 29, 2026
e28a3c0
[JSC] BUN-TEST-DIFFERENCES.md should list the nine stress tests this …
Jarred-Sumner Oct 29, 2026
78b0b83
Merge branch 'main' into claude/module-live-exports
Jarred-Sumner Oct 29, 2026
2957847
[JSC] CallData::Type should be word-sized
Jarred-Sumner Oct 29, 2026
52117f4
[JSC] Interpreter::executeCall should not reserve stack for bound fun…
Jarred-Sumner Oct 29, 2026
e3728e9
Merge branch 'main' into claude/module-live-exports
Jarred-Sumner Oct 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions JSTests/BUN-TEST-DIFFERENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,15 @@ Not upstream's: a sync neither brings nor changes them. Most are named `stress/b
| `stress/array-sort-inline-int32-double-comparator.js` | an earlier, smaller test for the sort fix |
| `stress/bun-object-assign-calls-indexed-setters-on-the-chain.js` | `Object.assign()` stores with `[[Set]]` on its fast path too: an indexed setter on the target's prototype chain is called, and a read-only element there refuses the store |
| `stress/bun-arrays-become-prototypes-below-immutable-prototypes.js` | with `Object.prototype` and `Array.prototype` immutable and a setter under an index on a class prototype, an array or an object with elements that becomes a prototype keeps the storage it has; reads, stores and changes of length on it and through its heirs answer as the language says |
| `stress/function-prototype-call-not-called-as-a-method.js` | `f.call(...)` is compiled to a call of `f`, so only other ways reach `Function.prototype.call` itself: `call.bind(f)`, `Reflect.apply`, `call.call` and `call.apply`. Through each of them `this`, the arguments and `new.target` arrive as they should, and a callee that is not callable is a `TypeError`, in every tier |
| `stress/regexp-greedy-group-in-tail-position.js` | a greedy `*` or `+` group that nothing can fail after runs without the state of its earlier iterations: `exec()`, `match()` and `replace()` give the listed results |
| `stress/regexp-greedy-group-with-minimum-can-match-empty.js` | `YarrPattern` drops the minimum of a greedy group that has no maximum and whose body can match the empty string: the results are the same as with the minimum |
| `stress/regexp-long-source.js` | the limit of 1 MiB on the source of a pattern does not include the text of character classes made only of characters and ranges, up to 4 MiB in all. Everything else is "too large" where it was before |
| `stress/regexp-lookaround-releases-paren-contexts.js` | YarrJIT frees the state of the groups in a lookaround once the lookaround has matched: a search over a long input finds its match, where it used to run out of stack and report none |
| `stress/regexp-many-single-character-alternatives.js` | a long run of neighbouring one-character alternatives becomes several character classes: matches are right at the sizes around each split, and at 50,000 alternatives, where finishing one class was quadratic |
| `stress/regexp-repeated-group-saves-the-alternative-that-matched.js` | before each iteration of a repeated group YarrJIT saves only the frame slots of the alternative that matched the iteration before: patterns that backtrack into earlier iterations give the listed results |
| `stress/regexp-repeated-group-with-many-alternatives.js` | a repeated group of up to 16,000 capturing alternatives matches, and only the last iteration's capture is set. Compiling such a group was quadratic |
| `stress/regexp-tail-loop-with-many-alternatives.js` | the pass that lets a tail loop match a run of characters stops after a fixed amount of work: matches are the same whether or not it reaches the end of the loop, up to 12,000 alternatives and with classes of a million code points |
| `stress/regexp-bytecode-of-deeply-nested-fixed-count-groups.js` | the bytecode compiler of regular expressions takes a time in proportion to the depth of a nest of groups with a fixed count: no more than three times what it takes for lazy groups, at two depths |
| `stress/regexp-flat-program-counts-turns-of-alternatives-tried-once.js` | a pattern with a `^` in one alternative and none in another: the flat program that counts turns (`yarr/YarrFlatProgram*`) walks the alternatives that are tried once and their copies in the bytecode's order, and counts the same turns |
| `stress/regexp-flat-program-memoizes-unbounded-runs.js` | the flat program memoizes where the rest of an unbounded run has failed, keys entries only by the capture bounds a backreference can still read, and records a failed search under the bounds it started with: results and turn counts match the bytecode interpreter |
Expand Down
129 changes: 129 additions & 0 deletions JSTests/stress/function-prototype-call-not-called-as-a-method.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// f.call(...) is compiled to a call of f. These are the ways to reach Function.prototype.call itself.
function describe(value)
{
// By default JSON.stringify() prints these as null.
return JSON.stringify(value, (key, value) => value === undefined || (typeof value === "number" && !isFinite(value)) ? `<${value}>` : value);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}

function shouldBe(actual, expected, what)
{
actual = describe(actual);
expected = describe(expected);
if (actual !== expected)
throw new Error(what + ": expected " + expected + " but got " + actual);
}

function shouldThrow(func, errorType, what)
{
let error;
try {
func();
} catch (e) {
error = e;
}
if (!(error instanceof errorType))
throw new Error(what + ": expected a " + errorType.name + " but got " + error);
}

const call = Function.prototype.call;
shouldBe([call.length, call.name], [1, "call"], "length and name");

function strict() { "use strict"; return [this === undefined ? "undefined" : this, arguments.length, ...arguments]; }
const sloppy = new Function(`return [this === globalThis ? "global" : typeof this, arguments.length, ...arguments];`);
function target() { return new.target === undefined; }
function thrower(value) { throw new RangeError(value); }
class Base { constructor() { } }

const uncurried = call.bind(strict);
const uncurriedSloppy = call.bind(sloppy);
const callCall = call.bind(call);
const withThis = call.bind(strict, "bound this");
const withArguments = call.bind(strict, "bound this", 1, 2);
const hasOwn = call.bind(Object.prototype.hasOwnProperty);
const slice = call.bind(Array.prototype.slice);
const includes = call.bind(String.prototype.includes);
const push = call.bind(Array.prototype.push);
const max = call.bind(Math.max);
const symbol = Symbol();
const objects = [{ a: 1, [symbol]: 1 }, { b: 1 }, Object.create({ [symbol]: 1 }), [1], function () { }];

function test()
{
shouldBe(uncurried(), ["undefined", 0], "no arguments");
for (let count = 0; count <= 8; ++count) {
const rest = Array.from({ length: count }, (_, i) => i + 1);
shouldBe(uncurried("t", ...rest), ["t", count, ...rest], "spread, " + count);
shouldBe(Reflect.apply(call, strict, ["t", ...rest]), ["t", count, ...rest], "Reflect.apply, " + count);
shouldBe(callCall(strict, "t", ...rest), ["t", count, ...rest], "call.bind(call), " + count);
shouldBe(call.call(strict, "t", ...rest), ["t", count, ...rest], "call.call, " + count);
shouldBe(call.apply(strict, ["t", ...rest]), ["t", count, ...rest], "call.apply, " + count);
shouldBe(withThis(...rest), ["bound this", count, ...rest], "bound this, " + count);
shouldBe(withArguments(...rest), ["bound this", count + 2, 1, 2, ...rest], "bound arguments, " + count);
}
shouldBe(uncurried("t"), ["t", 0], "1");
shouldBe(uncurried("t", 1), ["t", 1, 1], "2");
shouldBe(uncurried("t", 1, 2), ["t", 2, 1, 2], "3");
shouldBe(uncurried("t", 1, 2, 3), ["t", 3, 1, 2, 3], "4");
shouldBe(uncurried("t", 1, 2, 3, 4), ["t", 4, 1, 2, 3, 4], "5");
shouldBe(uncurried("t", undefined), ["t", 1, undefined], "an undefined argument counts");
shouldBe(uncurried(undefined, undefined, undefined), ["undefined", 2, undefined, undefined], "undefined arguments count");
shouldBe(uncurried(null), [null, 0], "null this, strict");
shouldBe(uncurriedSloppy(), ["global", 0], "no this, sloppy");
shouldBe(uncurriedSloppy(null, 1), ["global", 1, 1], "null this, sloppy");
shouldBe(uncurriedSloppy(1, 1), ["object", 1, 1], "primitive this, sloppy");
shouldBe(call.bind(target)(), true, "new.target");
shouldBe(callCall(callCall, callCall, strict, "t", 1), ["t", 1, 1], "nested");

shouldBe(objects.map(object => hasOwn(object, symbol)), [true, false, false, false, false], "hasOwnProperty, symbol");
shouldBe(objects.map(object => hasOwn(object, "a")), [true, false, false, false, false], "hasOwnProperty, string");
shouldBe(objects.map(object => hasOwn(object, 0)), [false, false, false, true, false], "hasOwnProperty, index");
shouldBe(objects.map(object => hasOwn(object)), [false, false, false, false, false], "hasOwnProperty, nothing");
shouldBe([slice([1, 2, 3]), slice([1, 2, 3], 1), slice([1, 2, 3], 1, 2), slice("abc", 1)], [[1, 2, 3], [2, 3], [2], ["b", "c"]], "slice");
shouldBe([includes("abc", "b"), includes("abc", "b", 2), includes("abc")], [true, false, false], "includes");
const array = [];
shouldBe([push(array), push(array, 1), push(array, 2, 3), push(array, 4, 5, 6), push(array, 7, 8, 9, 10, 11), array], [0, 1, 3, 6, 11, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]], "push");
shouldBe([max(null, 1, 3, 2), max(null, 1, 3, 2, 5, 4), max()], [3, 5, -Infinity], "Math.max");

shouldThrow(() => hasOwn(undefined, "a"), TypeError, "hasOwnProperty of undefined");
shouldThrow(() => hasOwn(null, "a"), TypeError, "hasOwnProperty of null");
shouldThrow(() => call.bind(thrower)(null, "x"), RangeError, "the callee throws");
shouldThrow(() => call.bind(Base)(), TypeError, "a class constructor");
for (const notCallable of [undefined, null, 1, "s", symbol, {}, []]) {
for (let count = 0; count <= 6; ++count)
shouldThrow(() => Reflect.apply(call, notCallable, Array(count).fill(1)), TypeError, "not callable, " + count);
shouldThrow(() => callCall(notCallable, 1, 2), TypeError, "not callable, call.bind(call)");
}
shouldThrow(() => new call(), TypeError, "new");
}
noInline(test);

for (let i = 0; i < testLoopCount / 200; ++i)
test();

// Hot enough for the DFG and the FTL to inline the bound function, Function.prototype.call and the callee.
function hot(object, i)
{
return [hasOwn(object, symbol), hasOwn(object, "a"), uncurried(object, i)[2], callCall(strict, object, i, 1)[1], includes("abc", "b"), max(null, i, 1, 2, 3, 4)];
}
noInline(hot);
for (let i = 5; i < testLoopCount; ++i)
shouldBe(hot(objects[i % 2], i), [!(i % 2), !(i % 2), i, 2, true, i], "hot");

// A callee that changes, and an argument count that changes, at one call site.
function callIt(f, ...rest) { return callCall(f, ...rest); }
noInline(callIt);
for (let i = 0; i < testLoopCount / 2; ++i) {
shouldBe(callIt(strict, "t", i), ["t", 1, i], "same callee");
if (!(i % 100)) {
shouldBe(callIt(sloppy, 1, i, 2, 3, 4, 5), ["object", 5, i, 2, 3, 4, 5], "another callee");
shouldBe(callIt(Math.max, null, i, -1), i, "a native callee");
}
}

// Many arguments.
shouldBe(uncurried("t", ...Array(10000).fill(0))[1], 10000, "10000 arguments");
shouldThrow(() => uncurried("t", ...Array(0x100001).fill(0)), RangeError, "too many arguments");

// The arguments are read when the call is made.
function reassigns(a, b) { "use strict"; a = 10; b = 20; return [arguments[0], arguments[1], a, b]; }
shouldBe(call.bind(reassigns)(null, 1, 2), [1, 2, 10, 20], "the callee's parameters");
Loading
Loading