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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Add shared helpers for estimating external string and container storage.
Use them for symbols, bound function arguments, promise reactions,
and promise combinator value lists.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.