Commit graph

2601 commits

Author SHA1 Message Date
Luke Wilde
de35994fc2 LibJS+LibWeb: Use the unified Visitor for Variant-holding members 2026-05-15 08:51:17 +02:00
Andreas Kling
8ff0577dea LibJS: Remove FinalizationRegistry cleanupSome
The cleanupSome proposal was withdrawn, and other engines no longer
expose this method by default. Stop installing it on
FinalizationRegistry.prototype while keeping the internal cleanup path
used for queued cleanup jobs.

Add a test-js helper for invoking that internal cleanup path directly.
Move the FinalizationRegistry cleanup coverage onto it so callbacks
unregistering later dead records remain covered without exposing the
withdrawn API to JavaScript programs.

Fixes #2228
2026-05-15 00:08:00 +02:00
Andreas Kling
209579c964 LibJS: Avoid FinalizationRegistry iterator invalidation
Restart the cleanup scan after each finalization callback. This avoids
holding the next list iterator across user JavaScript. A callback can
call unregister() and remove that next record.

Add coverage for cleanupSome() invoking a callback for one dead record
while the callback unregisters the following record. This matches the
reported use-after-free shape without relying on ASAN to catch it.
2026-05-14 22:56:52 +02:00
Andreas Kling
d1ab21124c LibJS: Derive Shape prototype status from validity
Use the PrototypeChainValidity cell as the prototype-shape tag, since
prototype shapes already always own one and non-prototype shapes do not.
Remove the separate m_is_prototype_shape bit and its extra invariant.
2026-05-14 19:59:40 +02:00
Andreas Kling
2f9cc08cc1 LibJS: Eagerly allocate Shape dictionary tables
Descriptor arrays now carry non-dictionary property metadata directly,
so dictionary shapes no longer need a lazily allocated property table.
Allocate the dictionary table when the shape becomes a dictionary and
turn property-table access into a plain verified accessor.

This also removes the remaining const-mutation path from Shape property
lookups, since descriptor lookup and dictionary lookup are both direct.
2026-05-14 19:59:40 +02:00
Andreas Kling
bfe59292ef LibJS: Drop unused Shape transition-chain state
Descriptor arrays and dictionary tables now carry the complete property
metadata for Shape lookup and enumeration, so Shape no longer needs to
retain the transition payload that used to rebuild the lazy property
table.

Remove the previous-shape pointer, transition property key, transition
attributes, and transition type. Keep only a constructor-time property
count change so put, configure, delete, and prototype transitions still
initialize their visible property count explicitly.

This reduces Shape from 104 to 88 bytes.
2026-05-14 19:59:40 +02:00
Andreas Kling
40be311e42 LibJS: Index descriptors by insertion order
Keep a compact enum-index-to-entry-index vector beside the hash-sorted
entry storage. This preserves the sorted lookup layout while letting
insertion-order enumeration walk the visible descriptor prefix directly.
2026-05-14 19:59:40 +02:00
Andreas Kling
89a51bcb7b LibJS: Pack Shape descriptor and dictionary storage
Store either the descriptor array or dictionary table in a union keyed
by the existing dictionary flag. Use m_property_count as the visible
count for non-dictionary shapes. This removes the redundant count field
and puts Shape back at 104 bytes.

Assert that GC::Ptr<DescriptorArray> stays trivially destructible. The
Shape property storage union relies on that because only the dictionary
table arm needs explicit cleanup, which Shape performs from finalize().

Move descriptor-array key marking into DescriptorArray::visit_edges(),
and keep Shape responsible only for dictionary-table keys.
2026-05-14 19:59:40 +02:00
Andreas Kling
b83623b97a LibJS: Share Shape descriptor arrays across transitions
Let put transitions reuse their parent's descriptor array when the
parent's visible descriptor count still matches the array size. Append
the new descriptor to that shared array, and copy the visible prefix
when a sibling has already extended it.

Keep configure and delete transitions on private copies because they
alter existing descriptors. Let non-dictionary prototype transitions
inherit descriptor storage. Dictionary shapes that fit the descriptor
representation still synthesize a private descriptor copy when leaving
the mutable table path.
2026-05-14 19:59:40 +02:00
Andreas Kling
e3841a7392 LibJS: Compact Shape property table to a sorted flat array
Replace the lazy per-shape OrderedHashMap cache for non-dictionary
shapes with a GC-allocated descriptor array. Store descriptors in hash
order for lookup while keeping an enum index so callers can still walk
properties in insertion order.

Keep dictionary shapes on the mutable OrderedHashMap path, and migrate
callers that enumerated Shape::property_table() to the new insertion
order iterator. Cap descriptor arrays to their compact u16 index range
and keep larger dictionary shapes on the mutable table path across
prototype transitions and prototype clones.

Add coverage for setting the prototype of a dictionary object with more
than 65536 named properties.
2026-05-14 19:59:40 +02:00
Andreas Kling
a31c2c388b LibJS: Stop persisting basic_block_start_offsets on Executable
Keep basic block offsets as construction-only metadata rather than
storing them on every Executable. The validator now receives the offsets
through a transient Rust FFI span, and the bytecode dump rebuilds block
starts by scanning labels, terminators, and exception handler metadata.

Drop the table from the bytecode cache format and bump the format
version so old caches are rebuilt. This removes a field that was only
used by validation and bytecode dump paths.
2026-05-14 12:08:12 +02:00
Andreas Kling
21cbfb3cb1 LibJS: Drop source ranges from bytecode source maps
Store source map locations as bytecode offset, line, and column.
Runtime consumers only emit the start line and column, so source end
positions and source text offsets do not need to be carried through
Executable source maps, bytecode cache serialization, or the Rust FFI.

Keep SourceCode's internal position cache able to track source text
offsets so callers can still translate source offsets to line and
column pairs when needed. Hash dump-bytecode IDs from the name, first
source position, and bytecode size instead of source slices that need
end offsets.

Bump the bytecode cache format version for the slimmer serialized
source map entry shape.
2026-05-14 09:41:03 +02:00
Andreas Kling
b6ac36c200 LibJS: Deduplicate adjacent source map entries during codegen
Avoid emitting consecutive source map entries when they carry the
same source range. The bytecode offset for the previous entry remains
valid for later PCs because source lookup now uses the largest source
map entry whose offset is not greater than the program counter.

This keeps stack traces stable while allowing statement-sized runs of
bytecode to share one source map entry.
2026-05-14 09:41:03 +02:00
Andreas Kling
e926e86f8d LibJS: Materialize compiled function bytecode lazily
Keep fully compiled function bytecode in its Rust-side form until the
function is called for the first time. This covers decoded disk cache
records and freshly precompiled bytecode, so startup avoids eagerly
allocating every nested function executable.

Validate cached function bytecode before accepting a cache entry. This
keeps the existing failure behavior for corrupt on-disk cache data. Add
coverage for bytecode-cache and freshly precompiled functions to assert
that nested executables stay absent after script materialization, then
appear after the function is called.
2026-05-14 08:15:01 +02:00
Andreas Kling
4ef3c076f9 LibJS: Preserve imported names in module bytecode cache
Store the original imported binding name when serializing a re-export of
an imported binding as an indirect export. The cache previously kept the
local alias, so materialized modules could fail to resolve valid exports
such as `export { renamed as default }`.

Bump the bytecode cache format version so existing blobs with the stale
metadata are ignored. Add coverage for both normal module loading and
materializing this pattern from bytecode cache.
2026-05-13 20:54:10 +02:00
Timothy Flynn
7ffbf5a8c0 LibJS: Remove unused Rust function
This became unused after 87493f056d, and
we now see a warning during the build.
2026-05-10 20:50:20 +02:00
kunlinglio
87493f056d LibJS: Remove unused rust_compile_program function 2026-05-10 10:45:21 -04:00
Ali Mohammad Pur
65a4af3ab0 LibWeb: Use UnownedExternalBuffer for WebAssembly.Memory backing store 2026-05-10 16:41:42 +02:00
Ali Mohammad Pur
0bb987e809 LibJS+LibWeb: Allow instantiating DataBlock with an external buffer
This requires the minimal API exposed by ByteBuffer, allowing external
users to implement them as needed instead of being forced to use a
ByteBuffer.
2026-05-10 16:41:42 +02:00
Andreas Kling
37117b1cb4 LibJS: Check live blocks before pruning executable caches
Executable caches can retain weak pointers to shapes and prototypes
across collections. With incremental sweeping, a previous sweep may have
already freed the block behind one of those pointers by the time pruning
runs for weak containers.

Use the live HeapBlock registry before reading cached cells. This
matches the other weak containers updated for incremental sweeping.
2026-05-10 10:58:11 +02:00
Andreas Kling
b9ec043b5a LibGC: Add live HeapBlock registry to fix weak container UAF
Maintain a HashTable<HeapBlock*> of live heap blocks in the Heap,
updated on block creation and destruction.

Weak containers (WeakMap, WeakSet, WeakRef, FinalizationRegistry)
now check block liveness before accessing cell memory in their
remove_dead_cells() methods. This prevents use-after-free when
blocks have been freed during incremental sweeping.
2026-05-10 10:58:11 +02:00
Andreas Kling
245b7d74a7 LibGC: Prune weak containers in stop-the-world phase of GC
Move weak container cleanup (remove_dead_cells) out of both
sweep_dead_cells() and start_incremental_sweep() to the place
where it is actually safe to inspect cell state: collect_garbage().

Previously, remove_dead_cells could access cells that had already
been swept and poisoned by ASAN, causing use-after-poison crashes
when a new GC triggered while an incremental sweep was in progress.
2026-05-10 10:58:11 +02:00
Andreas Kling
f80f52b710 LibJS: Add JS_OBJECT_WITH_CUSTOM_CLASS_NAME
Some JS::Object subclasses need the GC-cell macro plumbing while still
computing their class_name() dynamically.

Add a GC_CELL helper that provides the Base alias and heap friendship
without defining class_name(), then expose it through LibJS. Teach the
LibJS GC Clang plugin to treat this helper as a JS_OBJECT-style macro so
the existing validation still checks the class name and base class
arguments.
2026-05-09 14:36:18 +02:00
Andreas Kling
ccf178a0e7 LibJS: Account keyed collection storage as external memory
Report Map, WeakMap, and WeakSet entry storage through the GC
external memory hook.

Replace the public values() accessors on WeakMap and WeakSet with
explicit methods so all mutations update external memory accounting.
2026-05-07 10:03:09 +02:00
Andreas Kling
745f73bbaa LibJS: Account module storage as external memory
Report module record vectors, loaded module request attributes, graph
loading visited sets, and module filename storage through the GC
external memory hook.
2026-05-07 10:03:09 +02:00
Andreas Kling
68fa684f76 LibJS: Account runtime storage as external memory
Report outline storage retained by scripts, environments, module
namespace objects, iterator helpers, property name iterators, argument
objects, and error tracebacks. These objects keep vectors and maps that
can grow independently from their GC cell sizes.
2026-05-07 10:03:09 +02:00
Andreas Kling
6751d348a7 LibJS: Account executable storage as external memory
Report outline storage retained by bytecode executables, table
objects, object property iterator cache data, and shared function
instance data. This includes bytecode vectors, cache arrays, source
maps, class blueprint elements, and binding metadata.
2026-05-07 10:03:09 +02:00
Andreas Kling
c6a79c3ae3 LibJS+LibCrypto: Account BigInt limbs as external memory
Expose the Tommath limb allocation size from LibCrypto big integers.
Use it as the external memory size for LibJS BigInt cells so large
integer values participate in GC threshold calculations.
2026-05-07 10:03:09 +02:00
Andreas Kling
277551c40f LibJS: Account promise storage as external memory
Add shared helpers for estimating external string and container storage.
Use them for symbols, bound function arguments, promise reactions,
and promise combinator value lists.
2026-05-07 10:03:09 +02:00
Andreas Kling
35510b5a78 LibJS: Account object storage as external memory
Report heap-allocated named property storage, indexed property
storage, sparse indexed dictionaries, private elements, and shape
tables through the LibGC external memory hook.
2026-05-07 10:03:09 +02:00
Andreas Kling
84ae7e5ccb LibJS: Account PrimitiveString storage as external memory
Report materialized UTF-8 and UTF-16 PrimitiveString storage through
the LibGC external memory hook.

This makes string-heavy live heaps contribute to post-collection GC
threshold sizing.
2026-05-07 10:03:09 +02:00
Andreas Kling
f6a45ea9a6 LibJS: Account ArrayBuffer storage as external memory
Report owned ByteBuffer outline capacity from ArrayBuffer as external
memory. Update the heap accounting when backing storage is attached,
resized, detached, or transferred.

Borrowed WebAssembly memory remains uncounted because ArrayBuffer does
not own that storage.
2026-05-07 10:03:09 +02:00
Andreas Kling
c32b5a3f73 LibWeb+RequestServer: Send cached bytecode with responses
Attach cached JavaScript bytecode sidecars to HTTP response headers so
WebContent can materialize classic and module scripts directly from a
decoded cache blob on cache hits.

Carry the disk cache vary key with the sidecar and reuse it when storing
fresh bytecode, avoiding mismatches against the augmented network
request headers used to create the cache entry.

Keep CORS-filtered module responses intact for status, MIME, and script
creation checks. Read bytecode sidecar data only from the internal
response, and treat decode or materialization failure as a cache miss
that falls back to normal source compilation.
2026-05-06 08:20:06 +02:00
Andreas Kling
afa1f77252 LibJS: Materialize decoded bytecode cache blobs
Create parser-free script and module materializers for decoded cache
blobs. Cached functions create SFDs without Rust compile inputs and
attach their precompiled executable immediately, while declaration
metadata is populated from decoded records.

Treat cache blobs as external input from the HTTP disk cache. Run
bytecode validation unconditionally before fixing up cache pointers, and
reject decoded source ranges or metadata indices that would be
out-of-bounds during C++ materialization.

Report executable validation failures as parser errors so callers can
reject corrupt sidecars and fall back to source compilation. LibJS tests
cover corrupt top-level bytecode, declaration bytecode, and declaration
source spans.
2026-05-06 08:20:06 +02:00
Andreas Kling
b265694f0d LibJS: Match bytecode cache blobs to their source
Store a SHA-256 fingerprint of the decoded source text in each bytecode
cache blob, and require callers to provide the expected fingerprint when
validating or decoding a blob.

This rejects sidecars for stale HTTP cache entries whose URL and request
headers still match but whose source body has been replaced. Bytecode
cache tests cover the mismatched-source rejection path.
2026-05-06 08:20:06 +02:00
Andreas Kling
de9f2b8343 LibJS: Decode bytecode cache blobs over FFI
Expose an owned decoded bytecode cache handle through RustIntegration.
This lets C++ callers keep validated metadata and executable records in
Rust-owned cache structures without invoking the parser.

Extend the js bytecode cache validation mode to create and free the FFI
handle so blob generation exercises the ownership path.
2026-05-06 08:20:06 +02:00
Andreas Kling
fb11f81305 LibJS: Cache declaration function bytecode
Precompile top-level function declarations used during script and
module instantiation while producing a full bytecode cache entry. Keep
normal off-thread execution artifacts on the existing eager path, and
store declaration records separately from executable nested functions.

Place those records after declaration metadata in the cache blob, and
bump the blob version so older sidecars are rejected.
2026-05-06 08:20:06 +02:00
Andreas Kling
9b33cd1c21 LibJS: Return decoded bytecode cache blobs
Make bytecode cache validation return a decoded blob containing the
validated program record, declaration metadata, and executable records.

This keeps a single Rust-owned object alive for consumers that need to
materialize cached bytecode after validation succeeds.
2026-05-06 08:20:06 +02:00
Andreas Kling
16f4775a99 LibJS: Decode bytecode cache executable records
Decode bytecode cache executable records into owned Rust data instead
of only skipping over their serialized fields during validation.

Keep cached bytecode, constants, nested functions, and class blueprints
available for later materialization without rebuilding ASTs.
2026-05-06 08:20:06 +02:00
Andreas Kling
4f1bf52eb3 LibJS: Persist bytecode cache declaration metadata
Store and decode script declaration-instantiation metadata and module
import, export, request, and declaration metadata in bytecode cache
blobs.

Return decoded metadata as owned Rust records so warm-cache script and
module construction can recover parser-derived facts from the sidecar.
2026-05-06 08:20:06 +02:00
Andreas Kling
c5b6739c47 LibJS: Tag bytecode cache blobs with program type
Record whether each bytecode cache blob contains a classic script or a
module, and pass that type through the serializer call sites.

Require validation callers to provide the expected program type so
script and module sidecars cannot be reused for the wrong loader.
2026-05-06 08:20:06 +02:00
Andreas Kling
b327f61ab3 LibJS: Validate bytecode cache blob records
Add structural validation for bytecode cache blobs using the serialized
record layout. The decoder shares primitive helpers across cache
sections instead of duplicating flat parsing logic.

Reject bad magic values, unsupported versions, invalid enum tags, and
truncated sections before materialization. Bound sequence reads against
the remaining blob so malformed sidecars cannot force large allocations.
2026-05-06 08:20:06 +02:00
Andreas Kling
96a6782800 LibJS: Serialize compiled bytecode cache blobs
Add a versioned Rust bytecode cache writer for fully compiled programs.
The blob records executable bytecode, metadata tables, source maps,
exception handlers, nested function bytecode, and class blueprints
without materializing GC objects.

Expose the serialized blob through RustIntegration as an owned
ByteBuffer so Web-facing callers can store it as HTTP cache data.
2026-05-06 08:20:06 +02:00
Andreas Kling
f25f245b3c LibJS: Split full off-thread script compilation
Keep fetched script and module compilation on the latency-sensitive path
limited to top-level code and eager direct IIFE compilation before
returning bytecode to the main thread.

Add a separate full off-thread compile entry point for bytecode cache
generation. Cache jobs can use it to compile every nested function after
the execution path has already been unblocked.
2026-05-06 08:20:06 +02:00
Andreas Kling
54fbf3ff30 LibWeb: Compile remaining script functions in background
After off-thread script or module compilation hands top-level bytecode
back to the main thread, clone the remaining lazy function payloads and
compile them on the thread pool.

Install completed bytecode only when the function is still lazy. If the
main thread compiled it first, discard the stale result and schedule
another pass over that executable so nested lazy payloads still move to
bytecode.
2026-05-06 08:20:06 +02:00
Andreas Kling
3604259676 LibJS: Add off-thread function bytecode artifacts
Add Rust and C++ integration points for cloning lazy function compile
payloads, compiling them to GC-free bytecode off the main thread, and
materializing the result later on the main thread.

The cloned payload lets background compilation race with lazy
main-thread compilation without sharing AST ownership between threads.
Compiled function artifacts recursively include nested functions, so
materialization can discard the corresponding AST subtree.
2026-05-06 08:20:06 +02:00
Andreas Kling
d9dd412440 LibJS: Use foldhash in parser and scope-collector hash maps
The std default RandomState (SipHash) was using ~9 percentage points
of CPU on hash_one and write across the parse hot path, with the
string interner adding another ~3 pp on top. The cost was spread
across the interner, the scope collector's IndexMap<Utf16String, _>,
and several parser-side HashSet<Utf16String> declarations.

Use foldhash::quality::RandomState for the parser, scope collector,
and string interner via a new fast_hash module. Quality keeps
HashDoS resistance (keys are lexer tokens, attacker-controlled in a
browser context) while shedding SipHash's per-byte cost, and on this
workload it benchmarks slightly faster than foldhash::fast.
2026-05-05 13:53:51 +02:00
Andreas Kling
17d3a285a7 LibJS: Move ScopeData into ScopeArena and reference it by ScopeId
The Rust AST kept every scope in Rc<RefCell<ScopeData>>. The Rc made
the AST !Send (cross-thread codegen needed unsafe impl Send), and the
RefCell added a runtime borrow check on every hot-path read.

AST nodes (Block, FunctionBody, Program, SwitchStatement, SwitchCase)
now hold a ScopeId index into ScopeArena. The scope collector and
codegen take &mut/&ScopeArena, so the borrow checker enforces the
previously-implicit invariant that two phases never touch the same
scope at once.

ParsedProgram is now naturally Send. The unsafe impl Send and the
arc_with_non_send_sync allow go away. CompiledProgram keeps its
hand-rolled Send impl because it carries codegen-time state outside
the AST.

FunctionDeclarationData::is_hoisted was a Cell<bool> only because the
old &[ScopeRecord] traversal couldn't get &mut to the AST. It is now
a plain bool.
2026-05-05 13:53:51 +02:00
Andreas Kling
f2bf914874 LibJS: Intern identifier names in a per-arena string table
Identifier::name was SharedUtf16String (Rc<Utf16String>), so equality
checks against literals walked the slice and the Rc made the AST
!Send.

Replace it with a StringId (u32 index) backed by a StringInterner on
AstArena. Repeated names dedupe to the same id, so name comparisons
collapse to u32 == u32. The lexer's short/recent identifier caches
and the shared_identifier_value field on Token go away; the interner
already deduplicates everything.

Methods that previously took &mut IdentifierArena now also take
&StringInterner so they can resolve names from StringId during
analyze. Codegen helpers in bytecode/codegen.rs uniformly take
&AstArena. Generator gains intern_identifier_id, intern_property_key_id,
and intern_string_id helpers.
2026-05-05 13:53:51 +02:00
Andreas Kling
9ace32e276 LibJS: Drop Cell<> wrappers from Identifier scope-analysis fields
scope_collector now reaches Identifier through &mut IdentifierArena
indexing instead of through Rc<Identifier>'s shared reference, so the
Cell<> wrappers on local_type, local_index, is_global,
is_inside_scope_with_eval, and declaration_kind no longer earn their
keep.

Replace each Cell<T> with a plain T. The borrow checker now enforces
the existing "only scope_collector mutates these post-parse"
invariant. Shrinks Identifier and removes a layer of indirection on
hot-path field reads in codegen and ast_dump.
2026-05-05 13:53:51 +02:00