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.
Replace per-AST-node Rc<Identifier> with a Copy IdentifierId index
into a Vec<Identifier> arena, plumbed through Parser, scope_collector,
codegen, ast_dump, and the FFI. The arena lives on the parser during
parse, ships out via Arc<AstArena> on ParsedProgram, and is shared by
each child Generator and FunctionPayload through Arc clones.
Eliminates the per-occurrence Rc::new in the parser: every identifier
reference, parameter binding, function name, class name, and
binding-pattern target lands in the arena's Vec instead of getting its
own malloc plus Rc control block. Identifier field reads in codegen
become direct array indexing.
Identifier still carries Cell<>-wrapped scope-analysis state, so
AstArena is not yet Send + Sync; the existing unsafe-impl-Send wrapper
on ParsedProgram covers cross-thread handoff. Removing the Cells is
the next step.
The regex literal handle is shared between AST clones (e.g. class
field initializers reuse the same compiled regex), so shared ownership
has to stay. Switch from Rc to Arc and from Cell<*mut c_void> to
AtomicPtr<c_void> so the regex can travel with a function payload to
a worker thread without UB on the non-atomic Rc refcount.
The POST_BUILD step that merges libjs_rust.a / libweb_rust.a into
liblagom-{js,web}.a has no inputs declared to ninja, so Rust-only
edits left the merged archive stale. bin/js then linked pre-change
Rust code: the stale merged archive shadows the directly-linked
fresh Rust .a on the link line.
Add an OBJECT_DEPENDS from RustIntegration.cpp / RustTokenizer.cpp
to the Rust archive so the FFI bridge recompiles, the C++ archive
re-builds, and POST_BUILD re-runs the merge. Shared-library builds
already track the Rust .a as a regular link input.
Assert that inline Call frame-top arithmetic does not wrap after adding
the current interpreter stack top, and that raw native frames copy valid
caller lexical and variable environments.
Also check that the raw native ThrowCompletionOr variant index stays in
the expected 0-or-1 range before the asm fast path branches on it.
Assert that asm_helper_to_boolean returns only 0 or 1 before the asm
interpreter branches on the raw helper result. This documents the helper
ABI used by jump and logical-not fast paths without adding code to
release or distribution builds.
Add assertion-only checks for object shapes, binding storage, UTF-16
string backing storage, and iterator receiver shapes before the asm
interpreter relies on those pointers for follow-up loads.
These checks document invariants that are already required by the fast
paths, while still compiling away from release and distribution builds.
Replace assertion-only tag extraction around String helper return
values with assert_tag so release and distribution builds do not carry
code that only exists to feed assertions.
Asserting and non-asserting asmint generation both handle the updated
handlers.
Add assert_tag and assert_not_tag DSL instructions for checking the
upper NaN-boxing tag bits of a value. These lower to assertion-only
code in debug and sanitizer builds, so handlers can validate value
contracts without adding tag extraction instructions to release builds.
The instruction metadata records the hidden scratch register used by
each backend so named temporaries cannot overlap the codegen scratch.
Add debug-only assertions for typed-array cached data and property
iterator cache pointers before the asm fast paths dereference them. The
checks only consume values already loaded for normal execution, so
release and distribution builds still compile them out completely.
asmintgen successfully lowers this file for x86_64 with assertions
enabled.