Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
194 commits
Select commit Hold shift + click to select a range
8b1cfe7
AOT: frames of a constant text given to eval or `new Function` ignore…
Jarred-Sumner Nov 1, 2026
4f71978
AOT: a test file stuck on the top-level await of a compiled module wa…
Jarred-Sumner Nov 1, 2026
f400a7a
AOT: with a __mocks__ folder, a require() as the first load of a mock…
Jarred-Sumner Nov 1, 2026
c99ddad
AOT: the original of a file that had run before a mock was laid over it
Jarred-Sumner Nov 1, 2026
df61b24
AOT: `globalThis[holder].__defineGetter__(key, f)` was refused on con…
Jarred-Sumner Nov 1, 2026
403ffdd
AOT: vi.spyOn() through a barrel over a mocked module stored the spy …
Jarred-Sumner Nov 1, 2026
78a00b7
AOT: after a test file resets the modules, the next one finds what ra…
Jarred-Sumner Nov 1, 2026
7952413
AOT: the body of a linked module did not run when a helper mocked it
Jarred-Sumner Nov 1, 2026
a7b7241
AOT: SIGABRT "exception check validation failed" where preload module…
Jarred-Sumner Nov 1, 2026
8d07f1a
AOT: a function with 14,000 live small objects was refused for its st…
Jarred-Sumner Nov 1, 2026
6f04167
AOT: in the second test file that mocks a module, vi.spyOn() reached …
Jarred-Sumner Nov 1, 2026
b531630
AOT: no pass writes outside a function's tables of values and blocks …
Jarred-Sumner Nov 1, 2026
f7d2c6f
AOT: what the test runner hands out is held by native code
Jarred-Sumner Nov 1, 2026
2bc119b
AOT: four walks over "what the loader holds" went through a mocked mo…
Jarred-Sumner Nov 1, 2026
afe2532
AOT: JSON.stringify with a list of names did not count as reading tho…
Jarred-Sumner Nov 1, 2026
605bb53
AOT: the verifier stopped a build on a union member whose tag no memb…
Jarred-Sumner Nov 1, 2026
88c2264
AOT: lists made by one literal of constants were one cell though stru…
Jarred-Sumner Nov 1, 2026
96ec469
AOT: an import that went on behind mock factories lost its referrer's…
Jarred-Sumner Nov 1, 2026
120aab4
AOT: `using h = monitorEventLoopDelay()` failed: "[Symbol.dispose] wa…
Jarred-Sumner Nov 1, 2026
695963c
AOT: `f.apply(x, arguments)`, inlined, with an unknown callee: the li…
Jarred-Sumner Nov 1, 2026
bf0eff4
AOT: a call that Select loses stops the build
Jarred-Sumner Nov 1, 2026
a86305f
AOT: one IR test's hand-written Check made its generic version ill-typed
Jarred-Sumner Nov 1, 2026
bbf886d
AOT: a release hint kept a list out of the frame
Jarred-Sumner Nov 1, 2026
0b02d22
AOT: a copy of a list is read, written through a second name, and rea…
Jarred-Sumner Nov 1, 2026
893f79a
AOT: the mock of one test file reached the next
Jarred-Sumner Nov 1, 2026
5ed3bdd
AOT: `--explain=statistics` counts the method calls that are not fused
Jarred-Sumner Nov 1, 2026
c6f92b3
AOT: two IR tests of the counter need Bun's table and move to Bun's tree
Jarred-Sumner Nov 1, 2026
41f5c9f
AOT: a name the build left out of a compiled module is named in the e…
Jarred-Sumner Nov 1, 2026
0030a07
AOT: the compiler failed or aborted on a loop whose counter has two b…
Jarred-Sumner Nov 1, 2026
128368c
AOT: a quadratic step in Air's releaseDeadValues
Jarred-Sumner Nov 1, 2026
d1d643a
AOT: four planted faults and a statistic for three passes that decide…
Jarred-Sumner Nov 1, 2026
b67b361
AOT: the verifier stopped a build on a type test hoisted in front of …
Jarred-Sumner Nov 1, 2026
722a984
AOT: any mock of a file cost a closed program a tenth of its likeness…
Jarred-Sumner Nov 1, 2026
afa8d7b
AOT: `spyOn(Response.prototype, "status", "get")` was refused though …
Jarred-Sumner Nov 1, 2026
a9afeb8
AOT: a built-in that has escaped and climbs the prototype chain lets …
Jarred-Sumner Nov 1, 2026
96a3ce8
AOT: build time: what a spread copies is read once for a set of sources
Jarred-Sumner Nov 1, 2026
3916ee9
AOT: build time: three quadratic steps in SSA repair and the greedy r…
Jarred-Sumner Nov 1, 2026
282aa4f
AOT: build time: the owner of a block's parameter and the cycle of a …
Jarred-Sumner Nov 1, 2026
71701eb
AOT: `--aot-validate`: a claim inside the value's own type asks nothing
Jarred-Sumner Nov 1, 2026
18c4124
AOT: build time: the layouts of the receivers of a key are found once
Jarred-Sumner Nov 1, 2026
811c51f
AOT: the warning for `require` inside node:vm is back, narrowed
Jarred-Sumner Nov 1, 2026
22cc2a6
AOT: build time: the tables of a loop live as long as the pass
Jarred-Sumner Nov 1, 2026
88934ec
AOT: two planted faults for Map and Set lookups
Jarred-Sumner Nov 1, 2026
32c1a24
AOT: build time: phi placement skips up the dominator tree
Jarred-Sumner Nov 1, 2026
5e0865c
AOT: build time: what is copied onto a literal is asked by the set
Jarred-Sumner Nov 1, 2026
dbeba2d
AOT: build time: three quadratic steps of PlaceTypeTests and Simplify…
Jarred-Sumner Nov 1, 2026
5f4383e
AOT: the phase log says what an image's tables are made of
Jarred-Sumner Nov 1, 2026
c8084eb
AOT: two planted faults with their counters
Jarred-Sumner Nov 1, 2026
1b77add
AOT: idiomatic names in the B3 files that the JIT shares
Jarred-Sumner Nov 1, 2026
c686432
AOT: `--aot-validate`: what was there before an object was made is no…
Jarred-Sumner Nov 1, 2026
a8b45bf
AOT: build time: a comparison walks what its arm dominates and asks t…
Jarred-Sumner Nov 1, 2026
e49a383
AOT: the objects that native code makes by itself go into both dumps …
Jarred-Sumner Nov 1, 2026
ff3b03a
AOT: a verifying build goes on after a verification fails
Jarred-Sumner Nov 1, 2026
a5af883
AOT: "TypeOutOfDate" says which way
Jarred-Sumner Nov 1, 2026
a6a83ce
AOT: an import awaited at once begins behind its call
Jarred-Sumner Nov 1, 2026
daf8fb5
AOT: an import awaited through a cast
Jarred-Sumner Nov 1, 2026
1b8010e
AOT: build time: the new objects behind a store are found by one walk
Jarred-Sumner Nov 1, 2026
d2403c2
AOT: `valueflow-solve every-order` also applies flow's results under …
Jarred-Sumner Nov 1, 2026
b46882b
AOT: two `Object.create(target)` sites made reads of the target's get…
Jarred-Sumner Nov 1, 2026
2fc93dd
AOT: build time: tmps passed on the stack through a loop are priced b…
Jarred-Sumner Nov 1, 2026
dac1ab7
AOT: build time: an assumption is found by its value
Jarred-Sumner Nov 1, 2026
13a1a59
AOT: the check of moved loads prints and goes on
Jarred-Sumner Nov 1, 2026
8d4eacd
AOT: the bounds put on tallies get a planted fault and a counter
Jarred-Sumner Nov 1, 2026
e494320
AOT: a fused method call declares what its call did
Jarred-Sumner Nov 1, 2026
3910cfe
AOT: a planted fault for literals of constants
Jarred-Sumner Nov 1, 2026
c6ccd08
AOT: a mock that lacks a name which a file of the project hands on
Jarred-Sumner Nov 1, 2026
03b3e2a
AOT: accessors copied onto a built-in prototype were left out of the …
Jarred-Sumner Nov 1, 2026
7df1cf4
AOT: a store to `module.children` did not drop the children that were…
Jarred-Sumner Nov 1, 2026
475d0d8
AOT: the compiler aborted on a copy of a list that is kept under a se…
Jarred-Sumner Nov 1, 2026
e0cb003
AOT: Reflect.construct() with an heir as its target gave the object t…
Jarred-Sumner Nov 1, 2026
06a165d
AOT: an input of the verifier that nobody read is gone
Jarred-Sumner Nov 1, 2026
35ef3bb
AOT: under a standing mock, a file that was linked before the mock re…
Jarred-Sumner Nov 1, 2026
2089f28
AOT: build time: the parameter of a wide join waits for its sources
Jarred-Sumner Nov 1, 2026
2d4956e
AOT: deleting the oldest entry of a Map or Set stepped over every ent…
Jarred-Sumner Nov 1, 2026
52a5497
AOT: build time: a store asks the lengths read above it, the nearest …
Jarred-Sumner Nov 1, 2026
8a5cdaf
AOT: where a generic copy of a loop comes back, a test that narrows g…
Jarred-Sumner Nov 1, 2026
7dc5d2b
AOT: the row of a run-time function holds what its operation declares
Jarred-Sumner Nov 1, 2026
9f9979a
AOT: a test file's load is noted in the layers below
Jarred-Sumner Nov 1, 2026
b7cdfde
AOT: two planted faults and a counter for two rules of flow analysis
Jarred-Sumner Nov 1, 2026
dadc6ab
AOT: a planted fault and two counters for lists that a loop leaves alone
Jarred-Sumner Nov 1, 2026
e89145a
AOT: a comment says why a wide join begins at 16 edges
Jarred-Sumner Nov 1, 2026
0879cdd
AOT: promoteLoopMemory resets the owners of values, and two checks of…
Jarred-Sumner Nov 1, 2026
14ac3f2
AOT: three calls into the compiler's run time that a run without comp…
Jarred-Sumner Nov 1, 2026
5d26613
AOT: a verifying, layered test run stopped with "InvalidType: type 91…
Jarred-Sumner Nov 1, 2026
d11f5d9
AOT: a counter for the walks over a Map or Set that begin behind the …
Jarred-Sumner Nov 1, 2026
473bab8
AOT: `bun test --aot` over forked `export *` with a mocked module at …
Jarred-Sumner Nov 1, 2026
503cf98
AOT: a read of a copy of a list expects the storage of the list it wa…
Jarred-Sumner Nov 1, 2026
717e7a5
AOT: a store to an object that native code holds does not enter sette…
Jarred-Sumner Nov 1, 2026
cf38feb
AOT: a module that a failed graph left linked runs its body ahead of …
Jarred-Sumner Nov 1, 2026
2fb5473
AOT: `new WebAssembly.Suspending()` and `new WebAssembly.Tag()` witho…
Jarred-Sumner Nov 1, 2026
a5fc799
AOT: a JSON.stringify in a run without compiled code no longer watche…
Jarred-Sumner Nov 1, 2026
0d9cb23
AOT: build time: what may change a list's length is looked for once
Jarred-Sumner Nov 1, 2026
972955f
AOT: the run time stops a program that changes a compiled object in a…
Jarred-Sumner Nov 1, 2026
e6465e6
AOT: SSA repair no longer gathers the edges into a wide join
Jarred-Sumner Nov 1, 2026
b6b2f46
AOT: nine rows of the table of run-time functions say what their bodi…
Jarred-Sumner Nov 1, 2026
2ce04cb
AOT: "ASSERTION FAILED: m_moduleEnvironment" under `bun test --aot` i…
Jarred-Sumner Nov 1, 2026
930dc01
AOT: the list of planted faults in compile-call.def is laid out anew
Jarred-Sumner Nov 1, 2026
3bfa0cb
AOT: no added comments in the files that are shared with the rest of …
Jarred-Sumner Nov 1, 2026
613b623
B3: `continuesAfterMovedLoadReports` for `reportsOfMovedLoadsComeBack`
Jarred-Sumner Nov 1, 2026
840f28c
AOT: `spyOn(Bun.CryptoHasher.prototype, "algorithm", "get")` failed i…
Jarred-Sumner Nov 1, 2026
135601b
AOT: the compiler aborted in ExpandIteration under a lowered limit
Jarred-Sumner Nov 1, 2026
aa50cbf
testb3: two graphs take their arguments eagerly
Jarred-Sumner Nov 1, 2026
95461bc
AOT: a hardened arm64 executable starts where a page has 64 KB
Jarred-Sumner Nov 1, 2026
c33b0b4
AOT: the image that runs is called so
Jarred-Sumner Nov 1, 2026
0014a2e
AOT: two imports that wait for one asynchronous mock factory
Jarred-Sumner Nov 1, 2026
cb3905f
AOT: `vi.mock("node:fs")` without a factory failed with "Circular imp…
Jarred-Sumner Nov 1, 2026
0f56e3a
AOT: each image's "is asking the host" is put back to its own
Jarred-Sumner Nov 1, 2026
ca6dfd5
AOT: "Unchecked JS exception" from `spyOn()` of an export behind two …
Jarred-Sumner Nov 1, 2026
8456bd9
AOT: six more calls into the compiler's run time that a run without c…
Jarred-Sumner Nov 1, 2026
2be3779
AOT: in a test run a built-in object takes a name that it only inherits
Jarred-Sumner Nov 1, 2026
353d5d9
AOT: a mock of a JSON file never ran over layers
Jarred-Sumner Nov 1, 2026
b824612
AOT: the enums of compile-call.def are generated as five Rust modules
Jarred-Sumner Nov 1, 2026
97480b1
AOT: three planted faults for what lies in a frame
Jarred-Sumner Nov 1, 2026
3ebea05
AOT: an executable's TypeError for a name that a built-in only inheri…
Jarred-Sumner Nov 1, 2026
860598d
AOT: the mock of a built-in module outlived its test file
Jarred-Sumner Nov 1, 2026
5fca1b5
AOT: behind `export *`, a mock that took the original had no names
Jarred-Sumner Nov 1, 2026
00f1091
AOT: build time: what runs each turn of a loop is looked for inside t…
Jarred-Sumner Nov 1, 2026
32ae28c
AOT: `vi.spyOn(EventTarget, "prototype", "get")` ended the whole test…
Jarred-Sumner Nov 1, 2026
eb9b93e
AOT: one visitor names every member of ExpandIteration's arm state
Jarred-Sumner Nov 1, 2026
3d2fde0
AOT: a mock laid over an ES module that has run adds no name to it
Jarred-Sumner Nov 1, 2026
e59d065
AOT: a name that a compiled module's table lacks is refused in plain'…
Jarred-Sumner Nov 1, 2026
60288a0
AOT: five counts of the front end become rows of the table of limits
Jarred-Sumner Nov 1, 2026
a4cdfff
AOT: a test that ends the mock of a built-in module itself
Jarred-Sumner Nov 1, 2026
7d30f05
AOT: seven spies on a `prototype` are taken again
Jarred-Sumner Nov 1, 2026
a7ba60e
AOT: SSA repair gathers the equal edges into a wide join again, and c…
Jarred-Sumner Nov 1, 2026
2efce64
AOT: the verifier refuses a store that may create a field below an ac…
Jarred-Sumner Nov 1, 2026
cf635b9
AOT: a body asks for the room of its array methods with its own
Jarred-Sumner Nov 1, 2026
b326766
AOT: the image lists the modules that a test may mock and that are lo…
Jarred-Sumner Nov 1, 2026
040ccca
AOT: every image is asked before any is told of a copy from disk
Jarred-Sumner Nov 1, 2026
4c36284
AOT: a loader that is out of sight hands out the objects of Bun's own…
Jarred-Sumner Nov 1, 2026
9b1ceb0
AOT: `require()` of a module whose import had been mocked failed once…
Jarred-Sumner Nov 1, 2026
84cb483
AOT: a door for Bun to say whether a program's texts are held, the en…
Jarred-Sumner Nov 1, 2026
107371b
AOT: behind the mock of a built-in module its importers read the modu…
Jarred-Sumner Nov 1, 2026
ae89ec2
AOT: `$<name>` in a replacement text reads any name off the groups
Jarred-Sumner Nov 1, 2026
afdb357
AOT: "this assignment always throws" is a build error only if every r…
Jarred-Sumner Nov 1, 2026
0a8a1ef
AOT: the compiler's own check stopped the build where an object witho…
Jarred-Sumner Nov 1, 2026
c206cfa
AOT: a mock in the middle of a chain of re-exports answers
Jarred-Sumner Nov 1, 2026
b08853b
AOT: the schema's generator no longer refuses a long file
Jarred-Sumner Nov 1, 2026
89d3e48
AOT: a door for the original of a mocked package, the engine's half
Jarred-Sumner Nov 1, 2026
6c8f8d7
AOT: the answer of a loop's first question has one reader
Jarred-Sumner Nov 1, 2026
b706fbe
AOT: five more calls into the compiler's run time that a run without …
Jarred-Sumner Nov 1, 2026
cc99fb0
AOT: values copied onto an object of the realm escape
Jarred-Sumner Nov 1, 2026
925311d
AOT: a test that a branch narrows by stays in its loop, wherever the …
Jarred-Sumner Nov 1, 2026
1599cea
AOT: a `require()` answers the original until an asynchronous mock fa…
Jarred-Sumner Nov 1, 2026
56a7ccf
AOT: a receiver that leaves may be any object
Jarred-Sumner Nov 1, 2026
d8a300c
AOT: no copy of a hot loop's reads for a list that may hold undefined
Jarred-Sumner Nov 1, 2026
429629f
AOT: "Stopped while compiling" says that an earlier step may be at fault
Jarred-Sumner Nov 1, 2026
f2082ad
AOT: three more places look at the exception first
Jarred-Sumner Nov 1, 2026
cbaa2f2
AOT: what a mock replaced behind the stars of its file comes back whe…
Jarred-Sumner Nov 1, 2026
8ce3508
Revert "AOT: a JSON.stringify in a run without compiled code no longe…
Jarred-Sumner Nov 1, 2026
14921f7
AOT: three more things ahead of a locked write make "this assignment …
Jarred-Sumner Nov 1, 2026
25d446c
AOT: a planted fault for the copy of a hot loop's reads
Jarred-Sumner Nov 1, 2026
402e6cd
AOT: getters that a bundler's `__export` lays on an object of the rea…
Jarred-Sumner Nov 1, 2026
1b4379b
AOT: switch off the clamp of loop bounds
Jarred-Sumner Nov 1, 2026
d9e0976
AOT: the factory of a mock that imports the module it mocks, and fail…
Jarred-Sumner Nov 1, 2026
2de8c0a
AOT: the way to a mock goes on through a table that lacks the name
Jarred-Sumner Nov 1, 2026
275a7e9
AOT: a planted fault for what stores behind former entry checks hold
Jarred-Sumner Nov 1, 2026
e69dc6f
AOT: a mock of a built-in module that a test ends itself goes on for …
Jarred-Sumner Nov 1, 2026
7a85ad8
AOT: the run time reads no overloads
Jarred-Sumner Nov 1, 2026
fce15c4
AOT: the accessors of a library module that the test runner hands out…
Jarred-Sumner Nov 1, 2026
dcbcd6b
AOT: a helper that mocks a package which the test file comes to throu…
Jarred-Sumner Nov 1, 2026
9a3a0eb
AOT: every image is told of a file that a plugin's `onLoad()` filter …
Jarred-Sumner Nov 1, 2026
fe735a6
AOT: what a copy writes onto an object of the realm escapes
Jarred-Sumner Nov 1, 2026
96d2e38
AOT: the run-time counters ask whether a VM still has instances
Jarred-Sumner Nov 1, 2026
52fa2bf
AOT: `vi.doMock()` leaves alone what a test file has loaded, through …
Jarred-Sumner Nov 1, 2026
ec56e78
AOT: the road of an asynchronous mock factory checks its exceptions
Jarred-Sumner Nov 1, 2026
d2a9731
AOT: build time: whoever asks through tests holds the tables
Jarred-Sumner Nov 1, 2026
5460fea
B3: don't clamp the bound of a loop that counts down under an unsigne…
Jarred-Sumner Nov 1, 2026
f61d1c7
AOT: what the loader has let go is mocked ahead of its load
Jarred-Sumner Nov 1, 2026
66f540f
AOT: the state of a mock, computed and counted at the doors
Jarred-Sumner Nov 1, 2026
50e9f40
AOT: the mock of a package ends with its test file
Jarred-Sumner Nov 1, 2026
3d0ca6c
AOT: what lies behind a module that a mock stands for is not asked fo…
Jarred-Sumner Nov 1, 2026
3b4a32e
AOT: a second mock factory waits, as the first does
Jarred-Sumner Nov 1, 2026
fa9dfe5
AOT: build time: what a use means to a cell of the frame is asked once
Jarred-Sumner Nov 1, 2026
37f9b18
AOT: `console.log(fs.statSync("."))` stopped a validating build
Jarred-Sumner Nov 1, 2026
cfba9ac
AOT: closures made behind an allocation
Jarred-Sumner Nov 1, 2026
b7c7823
AOT: the mark of the deleted prefix is of compact tables alone
Jarred-Sumner Nov 1, 2026
e610723
AOT: a file of the project that a mocked package's original requires …
Jarred-Sumner Nov 1, 2026
eff4b87
AOT: the module object that a `require()` made goes with the mock
Jarred-Sumner Nov 1, 2026
148612e
AOT: the required file takes back all that the waiting mock set
Jarred-Sumner Nov 1, 2026
e391e53
AOT: the original of a file under a mock has the names behind its stars
Jarred-Sumner Nov 1, 2026
2e1a580
AOT: the state of a held import is handed on, not left in a member
Jarred-Sumner Nov 1, 2026
3617806
AOT: a built-in function that was changed before its first use is ref…
Jarred-Sumner Nov 1, 2026
fd483d5
AOT: a retired realm forgets its unfinished loads
Jarred-Sumner Nov 1, 2026
6e57a7d
AOT: modules that hidden code fetches by name are held, not escaped
Jarred-Sumner Nov 1, 2026
695721a
AOT: an export of another image is kept before a mock is laid over it
Jarred-Sumner Nov 1, 2026
144e9ec
AOT: while a mock stands that left a loaded module alone, the module …
Jarred-Sumner Nov 1, 2026
41e9312
AOT: in a build with assertions a value's number says which renumberi…
Jarred-Sumner Nov 1, 2026
58e0870
AOT: the end of a birth between the stores
Jarred-Sumner Nov 1, 2026
ba9917d
AOT: the run time reaches the mocks through a table that the host ans…
Jarred-Sumner Nov 1, 2026
3bc2ef1
AOT: build time: two new blocks take their parameters without asking …
Jarred-Sumner Nov 1, 2026
ccd68a8
AOT: the build warns of names that a copy adds to a built-in that tak…
Jarred-Sumner Nov 1, 2026
6cb9a2b
AOT: "ASSERTION FAILED: moduleData.isESModuleOfProgram()" at `mock.mo…
Jarred-Sumner Nov 1, 2026
32d809d
AOT: the table of stand-ins holds no realm
Jarred-Sumner Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions Source/JavaScriptCore/aot/AOT.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ set(JavaScriptCore_AOT_TEST_SOURCES
aot/tests/AOTObjectSlotExitTests.cpp
aot/tests/AOTOperationScopeTests.cpp
aot/tests/AOTOptionWordTests.cpp
aot/tests/AOTOriginalBesideMockTests.cpp
aot/tests/AOTParsedOptionalMemberTests.cpp
aot/tests/AOTPassTestCommands.cpp
aot/tests/AOTPipelineTestCommands.cpp
Expand Down
24 changes: 24 additions & 0 deletions Source/JavaScriptCore/aot/analysis/AOTBlockCycles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,30 @@ BlockCycles::BlockCycles(CFG& cfg, const Function* function)
ForestBuilder(cfg, function).findOutermostCycles(m_outermost, m_membersOfOutermostCycles, m_firstMemberOfOutermostCycle);
}

bool BlockCycles::isInCycleAvoiding(BlockID block, BlockID avoidedBlock, uint64_t& numberOfBlocksVisited) const
{
++numberOfBlocksVisited;
std::optional<unsigned> cycle = outermostCycleOf(block);
if (!cycle || block == avoidedBlock)
return false;
if (outermostCycleOf(avoidedBlock) != cycle)
return true;
BitVector isVisited(m_cfg.numNodes());
isVisited.set(avoidedBlock.index());
Vector<unsigned, 16> worklist { block.index() };
while (!worklist.isEmpty()) {
for (BlockID successor : m_cfg.successors(BlockID(worklist.takeLast()))) {
if (successor == block)
return true;
if (m_outermost[successor.index()] != *cycle || isVisited.set(successor.index()))
continue;
++numberOfBlocksVisited;
worklist.append(successor.index());
}
}
return false;
}

void BlockCycles::computeForest() const
{
m_forest.emplace(m_cfg, ForestBuilder(m_cfg, m_functionWithFrequencies).run(m_outermost.span(), m_membersOfOutermostCycles.span(), m_firstMemberOfOutermostCycle.span()));
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/aot/analysis/AOTBlockCycles.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class BlockCycles {
}

bool areInSameCycle(BlockID a, BlockID b) const { return isInCycle(a) && outermostCycleOf(a) == outermostCycleOf(b); }
bool isInCycleAvoiding(BlockID, BlockID avoidedBlock, uint64_t& numberOfBlocksVisited) const;

std::optional<unsigned> outermostCycleOf(BlockID block) const
{
Expand Down
3 changes: 3 additions & 0 deletions Source/JavaScriptCore/aot/analysis/AOTEscapeAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,9 @@ class EscapeAnalysis {
listUses();
return { entriesOf(object, m_uses, m_firstUse), m_sharedUses.span().data() };
}
// What goes by an entry of usesOf() alone can be kept under the entry's index, for all the objects that share it.
uint32_t numberOfSharedUses() const { return m_sharedUses.size(); }
uint32_t indexOfSharedUse(const ObjectUse& entry) const { return &entry - m_sharedUses.span().data(); }
std::span<const ObjectID> contentsOf(ObjectID object) const LIFETIME_BOUND { return entriesOf(object, m_contents, m_firstContent); }
std::span<const ObjectID> containersOf(ObjectID object) const LIFETIME_BOUND { return entriesOf(object, m_containers, m_firstContainer); }
bool isInLocalHeapWords(ObjectID object) const { return m_objectsInLocalHeapWords.contains(object); }
Expand Down
3 changes: 3 additions & 0 deletions Source/JavaScriptCore/aot/analysis/AOTFunctionAnalyses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,8 @@ FunctionAnalyses::~FunctionAnalyses()
statistics.count(StatisticID::EffectsComputed, numberOfComputedAnswers);
if (uint64_t numberOfKeptAnswers = m_effects.numberOfKeptAnswers())
statistics.count(StatisticID::EffectsKept, numberOfKeptAnswers);
if (m_numberOfBlocksReadToBuildControlFlowGraphs)
statistics.count(StatisticID::BlocksReadToBuildControlFlowGraphs, m_numberOfBlocksReadToBuildControlFlowGraphs);
}

void FunctionAnalyses::reportQuestionBeforeRepair() const
Expand Down Expand Up @@ -719,6 +721,7 @@ CFG& FunctionAnalyses::cfg()
dropResultsOfFormerGraph();
if (!m_controlFlow.cfg) {
m_controlFlow.cfg.emplace(m_function);
m_numberOfBlocksReadToBuildControlFlowGraphs += m_function.blocks.size();
m_controlFlow.isReadByUseLists = false;
m_controlFlow.numberOfBlocks = m_function.blocks.size();
didBuild(FunctionAnalysis::ControlFlow);
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/aot/analysis/AOTFunctionAnalyses.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ class FunctionAnalyses {
std::optional<EffectSummaries> m_unknownSummaries;
const EffectContext m_effectContext;
EffectsCache m_effects;
uint64_t m_numberOfBlocksReadToBuildControlFlowGraphs { 0 };
ControlFlowResults m_controlFlow;
ValueResults m_values;
const bool m_countsBuilds;
Expand Down
15 changes: 15 additions & 0 deletions Source/JavaScriptCore/aot/analysis/AOTGenericCopy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,19 @@ TypeID GenericCopy::typeAssumedInTypedCode(ValueID value, TypeID type, const Bit
return type;
}

bool GenericCopy::isTestOfBranchInOtherBlockThatPassesProofs(ValueID value) const
{
const UseLists& useLists = m_analyses.useLists();
return std::ranges::any_of(useLists.usesOf(value), [&](const Use& use) {
if (use.kind != Use::Kind::TerminatorOperand || use.block == useLists.blockOf(value))
return false;
const Terminator& terminator = m_function.at(use.block).terminator;
return terminator.isConditionalBranch() && std::ranges::any_of(terminator.targets, [](const Target& target) {
return !target.proofs.isEmpty();
});
});
}

auto GenericCopy::planRejoin(const TestedRejoin& rejoin, const RejoinTypes& rejoinTypes, const BitVector& isCopy) -> std::optional<RejoinPlan>
{
TypeArena& types = m_context.types;
Expand Down Expand Up @@ -767,6 +780,8 @@ auto GenericCopy::planRejoin(const TestedRejoin& rejoin, const RejoinTypes& rejo
Type passing = given.castTo(testable, types);
if (passing.isNever() || !passing.isSubsetOf(wanted, program))
return false;
if (!parameterIndex && isTestOfBranchInOtherBlockThatPassesProofs(inTypedCode))
return false;
plan.tests.append(RejoinTest { parameterIndex, inTypedCode, types.intern(testable) });
return true;
};
Expand Down
1 change: 1 addition & 0 deletions Source/JavaScriptCore/aot/analysis/AOTGenericCopy.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class GenericCopy {
bool mergesAllocationThatSinksWithItsCopyAt(BlockID) const;
bool mergesGeneratorWithItsCopyAt(BlockID) const;
bool canLeaveCopyAt(BlockID) const;
bool isTestOfBranchInOtherBlockThatPassesProofs(ValueID) const;
bool handsOverTooMuchAtExits() const;
bool tryPlan(Extent, uint64_t maxBlocks, const RegionCloner::LivenessSource&);
void recordTestedRejoins();
Expand Down
47 changes: 43 additions & 4 deletions Source/JavaScriptCore/aot/analysis/AOTGuardAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ class KillQuery {
if (!isConfined(subject)) {
bool wasThereAtEntry = namesOnlyObjectsOfKind(ObjectKind::EntryParameter, subject);
return mayWriteBeyondOperands(written) || std::ranges::any_of(operandsWrittenThrough(written), [&](ValueID operand) {
return escape.mayAlias(operand, subject) && !(wasThereAtEntry && namesOnlyObjectsOfKind(ObjectKind::Allocation, operand));
return escape.mayAlias(operand, subject) && !(wasThereAtEntry && namesOnlyObjectsOfKind(ObjectKind::Allocation, operand)) && !wasThereBeforeWhatIsNamedBy(operand, subject);
});
}

Expand All @@ -302,6 +302,25 @@ class KillQuery {
});
}

// What was computed once, ahead of every allocation that the operand names, is none of those objects. Only what judges finished code is told: told to the passes it moves code.
bool wasThereBeforeWhatIsNamedBy(ValueID operand, ValueID subject)
{
if (!m_analyses.context().onlyJudges() || !namesOnlyObjectsOfKind(ObjectKind::Allocation, operand))
return false;
ValueLocation computed = m_analyses.useLists().locationOf(m_analyses.identityOf(subject));
if (computed.placement != ValuePlacement::InBlock || m_analyses.isInCycle(computed.block))
return false;
const EscapeAnalysis& escape = m_analyses.escape();
for (ObjectID object : escape.pointsTo(operand).objects) {
ValueLocation allocation = m_analyses.useLists().locationOf(escape.at(object).definition);
if (allocation.placement != ValuePlacement::InBlock)
return false;
if (allocation.block == computed.block ? allocation.index <= computed.index : !m_analyses.dominators().dominates(computed.block, allocation.block))
return false;
}
return true;
}

bool mayBeMadeHere(ValueID operand)
{
return !m_analyses.context().onlyJudges() || !m_writer || m_analyses.escape().pointsTo(operand).objects.isEmpty() || !namesOnlyWhatCodeOutsideReaches(m_analyses, operand);
Expand Down Expand Up @@ -377,9 +396,29 @@ class KillQuery {
return use.kind == Use::Kind::Operand && use.user == m_writer;
return use.kind == Use::Kind::TerminatorOperand && use.block == m_point.block;
};
return std::ranges::any_of(m_analyses.escape().usesOf(object), [&](const ObjectUse& objectUse) {
return objectUse.isEscape && isUseByWriter(objectUse.use) && (!m_writer || mayBeMadeHere(m_function.at(m_writer).operand(objectUse.use.operandIndex)));
});
// The uses of an object stand in the order of the function, so those at one place stand together.
const UseLists& useLists = m_analyses.useLists();
const CFG& cfg = m_analyses.cfg();
auto placeOf = [&](ProgramPoint point) {
return std::pair { cfg.reversePostOrderIndex(point.block), point.index };
};
auto placeOfWriter = placeOf(m_writer ? useLists.pointOf(m_writer) : pointBeforeTerminatorOf(m_function, m_point.block));
ObjectUses uses = m_analyses.escape().usesOf(object);
AOT_THRESHOLD(size_t, fewestUsesSearched, 16);
size_t first = 0;
for (size_t end = uses.size() < fewestUsesSearched ? 0 : uses.size(); first < end;) {
size_t middle = first + (end - first) / 2;
if (placeOf(useLists.pointOf(uses[middle].use)) < placeOfWriter)
first = middle + 1;
else
end = middle;
}
bool escapes = false;
for (size_t index = first; !escapes && index < uses.size() && (uses.size() < fewestUsesSearched || placeOf(useLists.pointOf(uses[index].use)) == placeOfWriter); ++index) {
const ObjectUse& objectUse = uses[index];
escapes = objectUse.isEscape && isUseByWriter(objectUse.use) && (!m_writer || mayBeMadeHere(m_function.at(m_writer).operand(objectUse.use.operandIndex)));
}
return escapes;
}

std::span<const WrittenOperand> writtenOperands()
Expand Down
5 changes: 4 additions & 1 deletion Source/JavaScriptCore/aot/analysis/AOTGuardFacts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,10 @@ static ValueID valueStoredDirectlyBehind(FunctionAnalyses& analyses, ValueID all
bool storesIntoObject = (value.opcode == Opcode::PutSlot || value.opcode == Opcode::PutSlotOrAdd) && value.objectOperand() == allocation;
if (storesIntoObject && value.key() == key)
return value.valueOperand();
if (!storesIntoObject && !analyses.hasNoEffects(values[index]))
// A closure that is made takes the object along and looks at nothing: nobody calls it before the stores have run. The end of the birth takes a mark off the object and leaves its slots alone.
bool endsBirth = (value.opcode == Opcode::FinishBirth || value.opcode == Opcode::TryFinishBirth) && value.objectOperand() == allocation;
bool leavesSlotsAlone = (value.opcode == Opcode::NewClosure || endsBirth) && !analyses.context().build.isSabotaged(Sabotage::ClosuresMadeBehindAllocationsHaveEffects);
if (!storesIntoObject && !leavesSlotsAlone && !analyses.hasNoEffects(values[index]))
return { };
}
return { };
Expand Down
2 changes: 1 addition & 1 deletion Source/JavaScriptCore/aot/analysis/AOTIRBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ Terminator IRBuilder::makeTerminator(TerminatorKind kind, std::initializer_list<
ValueID IRBuilder::addValue(Value&& value)
{
m_function.add(WTF::move(value));
return ValueID(m_function.values.size() - 1);
return m_function.idOfValueAt(m_function.values.size() - 1);
}

TypeID partInside(const Program& program, TypeArena& types, TypeID narrower, TypeID type)
Expand Down
38 changes: 26 additions & 12 deletions Source/JavaScriptCore/aot/analysis/AOTIntegerRelations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,9 @@ static bool isInScope(FunctionAnalyses& analyses, BlockID scope, BlockID at)
return scope == at || analyses.dominators().terminatorDominates(scope, at);
}

static std::optional<ValueID> findValueOnEdge(const Function& function, const auto& assumptions, Edge edge, uint32_t parameterIndex)
static std::optional<ValueID> findValueOnEdge(const Function& function, const BitVector& isScopeOfAssumption, Edge edge, uint32_t parameterIndex)
{
bool mayLeaveBeforeAssumptionHolds = edge.isHandlerEdge() && std::ranges::any_of(assumptions, [&](const auto& assumption) {
return assumption.scope == edge.from;
});
bool mayLeaveBeforeAssumptionHolds = edge.isHandlerEdge() && isScopeOfAssumption.get(edge.from.index());
if (mayLeaveBeforeAssumptionHolds)
return std::nullopt;

Expand Down Expand Up @@ -96,6 +94,12 @@ IntegerRelations::IntegerRelations(FunctionAnalyses& analyses)
{
}

IntegerRelations::~IntegerRelations()
{
if (m_assumptionsRead)
m_analyses.context().build.statistics().count(StatisticID::AssumptionsReadForIntegerRelations, m_assumptionsRead);
}

IntegerRelations::Answer IntegerRelations::isAtMost(ValueID left, ValueID right, int64_t offset, BlockID at)
{
m_steps = 0;
Expand Down Expand Up @@ -154,16 +158,26 @@ void IntegerRelations::assumeRange(ValueID id, Range range, BlockID scope)
std::optional<int64_t> maximum = exactValueOf(CheckedInt64(range.maximum) - term.offset);
if (!minimum || !maximum)
return;
m_assumptions.append({ term.base, { *minimum, *maximum }, scope });
size_t numberOfEntries = m_lastAssumptionAbout.size();
if (numberOfEntries <= term.base.index()) {
m_lastAssumptionAbout.grow(std::max<size_t>(m_function.values.size(), term.base.index() + 1));
std::ranges::fill(m_lastAssumptionAbout.mutableSpan().subspan(numberOfEntries), 0);
}
uint32_t earlier = std::exchange(m_lastAssumptionAbout[term.base.index()], static_cast<uint32_t>(m_assumptions.size() + 1));
m_assumptions.append({ term.base, { *minimum, *maximum }, scope, earlier });
m_isScopeOfAssumption.set(scope.index());
}

std::optional<Range> IntegerRelations::assumedRangeOf(ValueID base, BlockID at) const
{
std::optional<Range> range;
for (const Assumption& assumption : m_assumptions) {
if (assumption.value != base || !isInScope(m_analyses, assumption.scope, at))
continue;
range = range ? range->intersectWith(assumption.range) : assumption.range;
if (!base || base.index() >= m_lastAssumptionAbout.size())
return range;
for (uint32_t number = m_lastAssumptionAbout[base.index()]; number; number = m_assumptions[number - 1].earlierAboutSameValue) {
const Assumption& assumption = m_assumptions[number - 1];
++m_assumptionsRead;
if (isInScope(m_analyses, assumption.scope, at))
range = range ? range->intersectWith(assumption.range) : assumption.range;
}
return range;
}
Expand Down Expand Up @@ -489,7 +503,7 @@ bool IntegerRelations::proveByInduction(ValueID left, ValueID right, int64_t off

goals.append({ left, right, offset });
bool holdsOnEveryEdge = std::ranges::all_of(cfg.incomingEdges(location.block), [&](Edge edge) {
std::optional<ValueID> incoming = findValueOnEdge(m_function, m_assumptions, edge, location.index);
std::optional<ValueID> incoming = findValueOnEdge(m_function, m_isScopeOfAssumption, edge, location.index);
if (!incoming)
return false;
Term argument = normalize(*incoming);
Expand Down Expand Up @@ -593,7 +607,7 @@ bool IntegerRelations::proveParameterAtLeast(ValueID parameter, int64_t minimum,

goals.append({ parameter, minimum });
bool holdsOnEveryEdge = std::ranges::all_of(cfg.incomingEdges(location.block), [&](Edge edge) {
std::optional<ValueID> incoming = findValueOnEdge(m_function, m_assumptions, edge, location.index);
std::optional<ValueID> incoming = findValueOnEdge(m_function, m_isScopeOfAssumption, edge, location.index);
return incoming && proveValueAtLeast(*incoming, minimum, edge.from, goals);
});
goals.removeLast();
Expand Down Expand Up @@ -632,7 +646,7 @@ bool IntegerRelations::proveWhole(ValueID id, BlockID at, AssumedWholeParameters

assumed.append(id);
bool receivesOnlyWholeNumbers = std::ranges::all_of(incomingEdges, [&](Edge edge) {
std::optional<ValueID> incoming = findValueOnEdge(m_function, m_assumptions, edge, location.index);
std::optional<ValueID> incoming = findValueOnEdge(m_function, m_isScopeOfAssumption, edge, location.index);
return incoming && proveWhole(*incoming, edge.from, assumed);
});
assumed.removeLast();
Expand Down
9 changes: 9 additions & 0 deletions Source/JavaScriptCore/aot/analysis/AOTIntegerRelations.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "AOTVector.h"
#include <optional>
#include <utility>
#include <wtf/BitVector.h>
#include <wtf/HashMap.h>
#include <wtf/Noncopyable.h>

Expand All @@ -21,6 +22,7 @@ class IntegerRelations {
WTF_MAKE_NONCOPYABLE(IntegerRelations);
public:
explicit IntegerRelations(FunctionAnalyses&);
~IntegerRelations();

enum class Answer : uint8_t { Unknown, Proven };

Expand Down Expand Up @@ -48,6 +50,8 @@ class IntegerRelations {
ValueID value;
Range range;
BlockID scope;
// The one made before it about the same value, counted from one. 0: there is none.
uint32_t earlierAboutSameValue { 0 };
};

struct LowerBoundGoal {
Expand Down Expand Up @@ -96,6 +100,11 @@ class IntegerRelations {
const Program& m_program;
const Function& m_function;
Vector<Assumption, 2> m_assumptions;
// One entry a value: the last assumption about it, counted from one. A function that reads a length in a thousand places has a thousand assumptions,
// and one question asks for those about a few values many times.
Vector<uint32_t> m_lastAssumptionAbout;
BitVector m_isScopeOfAssumption;
mutable uint64_t m_assumptionsRead { 0 };
Vector<uint32_t> m_nextBlockBehindComparison;
struct ComparedBases {
ValueID left;
Expand Down
10 changes: 9 additions & 1 deletion Source/JavaScriptCore/aot/analysis/AOTObservableOperations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ void didReplaceBody(ReasonsForOperationsThatWent& reasons)
reasons.didReplaceBody();
}

// The error goes on as it would without the handler.
static bool onlyThrowsAgain(const Function& function, BlockID handler)
{
const Block& block = function.at(handler);
return !block.handler && block.values.size() == 1 && function.at(block.values.first()).opcode == Opcode::CatchException
&& block.terminator.kind == TerminatorKind::Rethrow && block.terminator.exceptionOperand() == block.values.first();
}

Vector<OperationThatWent> findOperationsThatWentWithoutReason(const EffectContext& context, const Function& before, const Function& after, ReasonsForOperationsThatWent& reasons)
{
Vector<OperationThatWent> result;
Expand Down Expand Up @@ -213,7 +221,7 @@ Vector<OperationThatWent> findOperationsThatWentWithoutReason(const EffectContex
const Value& value = before.at(id);
ValueID now = findCurrent(id);
bool isListed = !!now;
if (isListed && block.handler && !hasHandlerAfter.quickGet(now.index()) && effectsOf(context, after, after.at(now)).normal.mayThrow && !reasons.hasReasonFor(id))
if (isListed && block.handler && !hasHandlerAfter.quickGet(now.index()) && effectsOf(context, after, after.at(now)).normal.mayThrow && !reasons.hasReasonFor(id) && !onlyThrowsAgain(before, block.handler->block))
result.append({ id, blockID, value.opcode, OperationThatWent::How::LostItsHandler, OperationThatWent::Under::TypesAsTheyStand });
// The pass that rewrote an operation into another one is responsible for it: it may know operand values that the types do not show.
// The replacement may have no effect and be removed by a later run, which does not report it, so tell the outer listener now.
Expand Down
Loading
Loading