Commit graph

2591 commits

Author SHA1 Message Date
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
Andreas Kling
3e15e59cd1 LibJS: Move identifiers into a contiguous IdentifierArena
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.
2026-05-05 13:53:51 +02:00
Andreas Kling
d9b9925914 LibJS: Make CompiledRegex thread-safe with Arc + AtomicPtr
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.
2026-05-05 13:53:51 +02:00
Andreas Kling
5b2dd60d11 LibJS: Add AST arena types for identifiers, scopes, and interned strings
Introduce IdentifierArena, ScopeArena, and StringInterner plus their
opaque IdentifierId/ScopeId/StringId index newtypes, bundled under
AstArena.
2026-05-05 13:53:51 +02:00
Andreas Kling
9dcfe77a8c LibJS+LibWeb: Track Rust archive as input in static-lib merge
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.
2026-05-05 13:53:51 +02:00
Andreas Kling
dafd624177 LibJS: Assert asmint call frame ranges
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.
2026-05-03 13:10:48 +02:00
Andreas Kling
955de5392f LibJS: Assert asmint boolean helper results
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.
2026-05-03 13:10:48 +02:00
Andreas Kling
a01d4fd6fe LibJS: Assert asmint shape and storage pointers
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.
2026-05-03 13:10:48 +02:00
Andreas Kling
4f74458024 LibJS: Use asmint tag assertions for value contracts
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.
2026-05-03 13:10:48 +02:00
Andreas Kling
143d61c77a LibJS: Add asmint tag assertions
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.
2026-05-03 13:10:48 +02:00
Andreas Kling
c1ff3c07e2 LibJS: Assert asmint cache data pointers
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.
2026-05-03 13:10:48 +02:00