Cache necessary data during parsing to eliminate HashMap operations
in SharedFunctionInstanceData construction.
Before: 2 HashMap copies + N HashMap insertions with hash computations
After: Direct vector iteration with no hashing
Build FunctionScopeData for function scopes in the parser containing:
- functions_to_initialize: deduplicated var-scoped function decls
- vars_to_initialize: var decls with is_parameter/is_function_name
- var_names: HashTable for AnnexB extension checks
- Pre-computed counts for environment size calculation
- Flags for "arguments" handling
Add ScopeNode::ensure_function_scope_data() to compute the data
on-demand for edge cases that don't go through normal parser flow
(synthetic class constructors, static initializers, module wrappers).
Use this cached data directly in SFID with zero HashMap operations.
AK/Random is already the same as SecureRandom. See PR for more details.
ProcessPrng is used on Windows for compatibility w/ sandboxing measures
See e.g. https://crbug.com/40277768
Logical expressions like `true || false` are now constant folded. This
also allows for dead code elimination if we know the right-hand side of
the expression will never be evaluated (such as `false && f()` or
`true || f()`).
In the test suites, the values are now being constant folded at compile
time. To ensure that the actual evaluation logic is being called
properly, I had to duplicate the tests and call them via a function so
the compiler would not optimize the evaluation logic away.
This also demotes `NaN` and `Infinity` identifiers to `nan` and
`inf` double literals, which will further help with const folding.
This is a common way to convert a value to a boolean. Instead of doing
a boolean conversion and 2 negate operations, we replace this with a
single `ToBoolean` op code.
This adds a new `test-js-bytecode` target which ensures that codegen
changes do not impact emitted bytecode IR, or if it does, it is known
and the tests are updated accordingly.
Similar to the LibWeb tests, the tests are stored in the following
format:
* `Libraries/LibJS/Bytecode/Tests/input`: Input `.js` files
* `Libraries/LibJS/Bytecode/Tests/expected`: Expected `.txt` bytecode
* `Libraries/LibJS/Bytecode/Tests/output`: Emitted `.txt` bytecode
The `output` dir is git-ignored, but stores the output so you can diff
and inspect failed tests more easily.
There is only one test so far, which is a baseline test that should not
change dramatically unless we change the bytecode output format.
Numeric string keys like "0" are converted to numeric property keys and
stored in indexed storage rather than shape-based storage. The shape
caching optimization introduced in 505fe0a977 didn't account for this,
causing properties with numeric keys to be lost on subsequent calls.
The fix excludes object literals with numeric string keys from the
shape caching fast path by checking if any key would become a numeric
property index.
This ensures that we are explicitly declaring the allocator to use when
allocating a cell(-inheriting) type, instead of silently falling back
to size-based allocation.
Since this is done in allocate_cell, this will only be detected for
types that are actively being allocated. However, since that means
they're _not_ being allocated, that means it's safe to not declare
an allocator to use for those. For example, the base TypedArray<T>,
which is never directly allocated and only the defined specializations
are ever allocated.
Every function call allocates an ExecutionContext with a trailing array
of Values for registers, locals, constants, and arguments. Previously,
the constructor would initialize all slots to js_special_empty_value(),
but constant slots were then immediately overwritten by the interpreter
copying in values from the Executable before execution began.
To eliminate this redundant initialization, we rearrange the layout from
[registers | constants | locals] to [registers | locals | constants].
This groups registers and locals together at the front, allowing us to
initialize only those slots while leaving constant slots uninitialized
until they're populated with their actual values.
This reduces the per-call initialization cost from O(registers + locals
+ constants) to O(registers + locals).
Also tightens up the types involved (size_t -> u32) and adds VERIFYs to
guard against overflow when computing the combined slot counts, and to
ensure the total fits within the 29-bit operand index field.
Generating the stack trace string is expensive as it involves
formatting each frame with function names, file paths, and line
numbers. Since the stack trace is immutable after error creation,
we can cache the formatted string on first access.
This matches the caching behavior of V8 and JavaScriptCore, which
also return the same cached string on subsequent accesses to the
stack property.
ENABLE_WINDOWS_CI and the *_CI presets were initially added back when
the AK library and all the AK Test* executables were the only targets
that supported building and running in CI. Since then, almost all the
targets in the codebase are built on Windows besides the following:
- LibLine
- test-262-runner
Since these targets above are not required to actually run or test the
browser on Windows in its current experimental state, fully disabling
them should be fine for now.
ENABLE_WINDOWS_CI was also used to exclude test-web from ctest. This
can be fully disabled on Windows for now until proper runtime support
is added.
The remaining locations were all using ENABLE_WINDOWS_CI as a proxy for
ENABLE_ADDRESS_SANITIZER, so we can just be explicit instead.
The new presets map much more directly to the unix Release, Debug, and
Sanitizer presets which should make setting up ladybird on Windows less
confusing.
We also make the new Windows_Experimental_Release preset the default in
ladybird.py to match Unix.
This commit implements the regexp-modifiers proposal. It allows us to
use modification of i,m,s flags within groups using
`(?flags:subpattern)` and `(?flags-flags:subpattern)` syntax.
5e0ee26e8b pulls in an old version of
simdjson, this commit upgrades to the latest release.
This commit also pins `dav1d` to the version it was before this change.
This patch improves JSON.stringify performance through three changes:
1. Use a single StringBuilder for the entire operation instead of
building up intermediate strings and concatenating them.
2. Format numbers directly into the StringBuilder via a new public
number_to_string(StringBuilder&, ...) overload, avoiding temporary
String allocations.
3. Track indentation as a depth counter instead of repeatedly
concatenating the gap string.
Replace the custom AK JSON parser with simdjson for parsing JSON in
LibJS. This eliminates the intermediate AK::JsonValue object graph,
going directly from JSON text to JS::Value.
simdjson's on-demand API parses at ~4GB/s and only materializes values
as they are accessed, making this both faster and more memory efficient
than the previous approach.
The AK JSON parser is still used elsewhere (WebDriver protocol, config
files, etc.) but LibJS now uses simdjson exclusively for JSON.parse()
and JSON.rawJSON().
When a function creates object literals with simple property names,
we now cache the resulting shape after the first instantiation. On
subsequent calls, we create the object with the cached shape directly
and write property values at their known offsets.
This avoids repeated shape transitions and property offset lookups
for a common JavaScript pattern.
The optimization uses two new bytecode instructions:
- CacheObjectShape: Captures the final shape after object construction
- InitObjectLiteralProperty: Writes properties using cached offsets
Only "simple" object literals are optimized (string literal keys with
simple value expressions). Complex cases like computed properties,
getters/setters, and spread elements use the existing slow path.
3.4x speedup on a microbenchmark that repeatedly instantiates an object
literal with 26 properties. Small progressions on various benchmarks.
When instantiating an object literal, we go through the list of
properties and add them one by one to the new object. However, we were
totally neglecting to cache the shape transitions incurred by this
process, even though we had allocated a PropertyLookupCache for it.
1.25x speedup on Octane/splay.js and just generally very useful.
We know the length they're gonna end up with up front since we're
instantiating array literals. Pre-sizing them allows us to skip
incremental resizing of the property storage.
When passing a Vector<JS::Value> to the MarkingVisitor, we were
iterating over the vector and visiting one value at a time. This led
to a very inefficient way of building up the GC's work queue.
By adding a new visit_impl() virtual to Cell::Visitor, we can now
grow the work queue capacity once, and then add without incrementally
growing the storage.
If we call put() directly on the underlying indexed property storage
like we were doing here, we skip the checks that switch from flat to
sparse property storage when a huge index is suddenly accessed.
This was caught by folks hitting memory issues when running test-js.
It's harmless if the WeakRef is asked to remove dead cells when it has
no dead cells to remove. This can happen if the WeakRef lives longer
due to someone referencing it. Caught by test-js with different GC
frequency.
This adds visit_edges(Cell::Visitor&) methods to various helper structs
that contain GC pointers, and makes sure they are called from owning
GC-heap-allocated objects as needed.
These were found by our Clang plugin after expanding its capabilities.
The added rules will be enforced by CI going forward.
This resolves a FIXME in its code generation, particularly for:
- Caching the template object
- Setting the correct property attributes
- Freezing the resulting objects
This allows archive.org to load, which uses the Lit library.
The Lit library caches these template objects to determine if a
template has changed, allowing it to determine to do a full template
rerender or only partially update the rendering. Before, we would
always cause a full rerender on update because we didn't return the
same template object.
This caused issues with archive.org's code, I believe particularly with
its router library, where we would constantly detach and reattach nodes
unexpectedly, ending up with the page content not being attached to the
router's custom element.
The AddOwnProperty inline cache would incorrectly apply to frozen,
sealed, or non-extensible objects because it only checked if the
object's shape matched the cached "from_shape", not whether the object
was actually extensible.
Since Object.freeze(), Object.seal(), and Object.preventExtensions()
don't change the object's shape, a normal empty object {} and a
frozen Object.freeze({}) would share the same shape. The IC cache
populated from adding a property to the normal object would then be
incorrectly used for the frozen object, allowing property addition
to what should be a non-extensible object.
The fix adds an extensibility check before applying the AddOwnProperty
cache. Also adds comprehensive tests for dictionary shapes and
non-extensible object IC behavior.
This adds 119 tests covering various inline caching scenarios:
- Basic property get/set
- Prototype chain access at various depths
- Shape transitions
- Property deletion and re-addition
- Getters and setters (own and inherited)
- Object.defineProperty edge cases
- Prototype manipulation at runtime
- Proxy objects in various configurations
- Special object types (arrays, typed arrays, strings, functions)
- Symbol properties
- Computed property access
- Constructor and class patterns
- Edge cases (frozen/sealed objects, null prototypes, etc.)
- IC invalidation scenarios
- Polymorphic IC boundary tests (1-5+ shapes)
- IC cache thrashing with many shapes
These tests exercise the polymorphic inline cache implementation,
particularly around the 4-shape limit where caches transition
from polymorphic to megamorphic behavior.
The previous fix prevented eval() in sibling function scopes from
affecting each other, but it still had a limitation: when identifiers
from multiple scopes were merged into the same identifier group at
Program scope, the presence of eval() anywhere would taint all
identifiers in the group.
This change tracks per-identifier whether it was inside a scope with
eval() in the scope chain. When a scope closes, if it contains eval()
or has eval() in its parent chain, each identifier in that scope is
marked with `is_inside_scope_with_eval`. At Program scope finalization,
only identifiers that are NOT marked can be optimized to global lookups.
This allows code like:
```js
var x = undefined; // Can be optimized (program scope)
(function() {
function walk() { undefined; } // Cannot be optimized
eval('');
})();
```
Before: Neither `undefined` could be optimized
After: The program-scope `undefined` is optimized, while the one inside
the function with eval() correctly uses dynamic lookup.
This was causing GC-related crashes on various websites, most
prominently on any site that contains embedded YouTube videos. The issue
can be reproduced by going to any YouTube video, using the _Share_
button below it and pasting the embed code into an empty HTML file and
loading it through localhost.
This is technically a regression from
89dbdd3411 in that the problem became
visible with that commit. However, there is nothing wrong with the
commit by itself. It just happens that `Origin::is_same_origin_domain()`
prior to that commit was completely bogus and would mistakenly return
true in almost all cases, so the cross-origin code paths were not
exercised.
I am uncertain how to make a automatic test case for this problem, given
the nature of it being GC- and cross-origin-related. So there is no
regression test included in this commit.
I mistakenly used ensure_capacity upon first implementing this, leading
to a crash when the requested memory couldn't be allocated. Instead,
we now pass the maxByteLength to create_shared_byte_data_block (which
throws the RangeError for us) and shrink the data block back down to the
proper size.
The parser was preventing identifiers like `undefined` from being
optimized to global constants when eval() was present in any function
scope, even when eval() could not affect the identifier's binding.
This change makes the parser smarter by preventing eval() in one
function scope from affecting global identifier optimization in sibling
function scopes. The key insight is that eval() in function A cannot
shadow globals in function B.