Commit graph

88 commits

Author SHA1 Message Date
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
Aliaksandr Kalenik
2171563daf LibJS: Avoid function envs for lexical-this arrows
Track whether a function needs environment-backed this resolution
separately from whether it needs to allocate its own function
environment. Arrow functions that only capture lexical this can now
resolve through the outer environment without allocating an empty
function environment for every call.

Keep the asm Call path conservative by routing functions that still need
lexical-this resolution through the C++ inline-call helper, so the call
receiver is not cached as the arrow function's this value.

Microbenchmark:

    function makeLexicalThisArrow() {
        return () => this.value;
    }

    let object = { value: 1, makeLexicalThisArrow };
    let fn = object.makeLexicalThisArrow();
    for (let i = 0; i < 20_000_000; ++i)
        fn();

Measured with the same Release build toggling this patch:

    baseline:  1069.2 ms mean over 12 runs
    optimized:  501.2 ms mean over 12 runs
    speedup:    2.13 times faster
2026-04-30 18:44:34 +02:00
Andreas Kling
e65e85cb8c LibJS: Materialize arguments object for shorthand { arguments }
The parser only set `might_need_arguments_object` when an `arguments`
or `eval` Identifier went through `consume()`, but shorthand object
properties create the reference via `make_identifier()` directly. As
a result `function f() { return { arguments } }` allocated an
`arguments` local, never initialized it, and crashed at runtime when
the property was read.

Fall back to scope-driven detection: if scope analysis allocated a
non-lexical `arguments` local for the function, treat it as a real
arguments-object reference and emit `CreateArguments`. Skip the
fallback when a function declaration named `arguments` claims the
local, since that local belongs to the function, not the arguments
object.

Add a runtime test covering shorthand inside a free function and a
method, plus a regression test for `({ eval } = ...)` to confirm
destructuring assignment doesn't accidentally trigger arguments
materialization.
2026-04-27 08:04:11 +02:00
Andreas Kling
141063e91d LibJS: Precompile top-level IIFEs off-thread
Mark direct calls to function expressions while generating top-level
Rust bytecode, then compile those functions before returning the
off-thread compilation result to WebContent.

The main thread still performs all VM and GC-backed materialization. It
now receives an already assembled executable for each eager IIFE and
attaches it to the SharedFunctionInstanceData while creating the parent
Executable. Nested functions owned by the eager executable remain lazy.

This targets large wrapper IIFEs that are invoked as soon as top-level
code starts running. Their bytecode generation now runs on the existing
script compilation worker instead of blocking the main thread on first
call.
2026-04-26 21:51:52 +02:00
Andreas Kling
0d120019df LibJS: Resolve VM constants during executable creation
Rust bytecode generation still reached into the VM to encode well-known
symbols and intrinsic abstract-operation functions as raw JS::Value
constants. That is not compatible with running top-level code generation
away from the main thread.

Keep those constants symbolic in the Rust constant pool instead. The C++
Executable materialization step now resolves them into real VM values
while it is already decoding the rest of the constant table on the main
thread.

This removes another VM dependency from Rust bytecode emission without
changing when the resulting constants become visible to the bytecode
interpreter.
2026-04-26 21:51:52 +02:00
Andreas Kling
56625c13d7 LibJS: Defer function data materialization
Rust bytecode generation currently creates SharedFunctionInstanceData
and ClassBlueprint GC objects as soon as nested functions and classes
are encountered. That keeps the whole code generation phase tied to
the main-thread VM and heap.

Record pending descriptors on the Generator instead, then materialize
those descriptors while creating the C++ Executable. This keeps the GC
allocation boundary exactly where it already belongs, but removes the
last direct function-data allocations from the codegen walk.

This is a preparatory step for compiling top-level bytecode off-thread
and only doing C++ materialization after returning to the main thread.
2026-04-26 21:51:52 +02:00
Andreas Kling
63d6ef1026 LibJS: Track nested function ids during Rust parsing
FunctionTable::extract_reachable() used to rediscover a function's
nested functions by walking the full body and parameter list during
bytecode generation. This is hot during page loading because creating
every lazy SFD pays for an extra structural AST traversal.

Record each parser-created function's direct child function ids while
parsing instead. Extraction can then recursively move that known
subtree without scanning the enclosing function again.

Keep the old structural scan for codegen-synthesized wrappers, such as
class field initializers, where no parser function context exists.
This preserves the sparse FunctionTable storage while making the common
extraction path proportional to the nested function count.
2026-04-26 21:51:52 +02:00
Andreas Kling
eb9432fcb8 LibJS: Preserve source positions in bytecode source maps
Carry full source positions through the Rust bytecode source map so
stack traces and other bytecode-backed source lookups can use them
directly.

This keeps exception-heavy paths from reconstructing line and column
information through SourceCode::range_from_offsets(), which can spend a
lot of time building SourceCode's position cache on first use.

We're trading some space for time here, but I believe it's worth it at
this tag, as this saves ~250ms of main thread time while loading
https://x.com/ on my Linux machine. :^)

Reading the stored Position out of the source map directly also exposed
two things masked by the old range_from_offsets() path: a latent
off-by-one in Lexer::new_at_offset() (its consume() bumped line_column
past the character at offset; only synthesize_binding_pattern() hit it),
and a (1,1) fallback in range_from_offsets() that fired whenever the
queried range reached EOF. Fix the lexer, then rebaseline both the
bytecode dump tests (no more spurious "1:1") and the destructuring AST
tests (binding-pattern identifiers now report their real columns).
2026-04-22 22:34:54 +02:00
Andrew Kaster
f26cb24751 Rust: Add a config file for rustfmt
This sets max_width to 120, which causes a lot of reformatting.
2026-04-18 08:05:47 -04:00
Andreas Kling
530f6fb05c LibJS: Fold nested Rust match conditionals
Move several let/const checks and the `instanceof` keyword check into
match guards.
2026-04-16 22:44:41 +02:00
Andreas Kling
c301a21960 LibJS: Skip preserving zero-argument call callees
The callee and this-value preservation copies only matter while later
argument expressions are still being evaluated. For zero-argument calls
there is nothing left to clobber them, so we can keep the original
operand and let the interpreter load it directly.

This removes the hot Mov arg0->reg pattern from zero-argument local
calls and reduces register pressure.
2026-04-13 18:29:43 +02:00
Andreas Kling
3e18136a8c LibJS: Add a String.fromCharCode builtin opcode
Specialize only the fixed unary case in the bytecode generator and let
all other argument counts keep using the generic Call instruction. This
keeps the builtin bytecode simple while still covering the common fast
path.

The asm interpreter handles int32 inputs directly, applies the ToUint16
mask in-place, and reuses the VM's cached ASCII single-character
strings when the result is 7-bit representable. Non-ASCII single code
unit results stay on the dedicated builtin path via a small helper, and
the dedicated slow path still handles the generic cases.
2026-04-12 19:15:50 +02:00
Andreas Kling
7bc40bd54a LibJS: Add a charAt builtin bytecode fast path
Tag String.prototype.charAt as a builtin and emit a dedicated
bytecode instruction for non-computed calls.

The asm interpreter can then stay on the fast path when the
receiver is a primitive string with resident UTF-16 data and the
selected code unit is ASCII. In that case we can return the VM's
cached empty or single-character ASCII string directly.
2026-04-12 19:15:50 +02:00
Andreas Kling
d31750a43c LibJS: Add a charCodeAt builtin bytecode fast path
Teach builtin call specialization to recognize non-computed
member calls to charCodeAt() and emit a dedicated builtin opcode.
Mark String.prototype.charCodeAt with that builtin tag, then add
an asm interpreter fast path for primitive-string receivers whose
UTF-16 data is already resident.

The asm path handles both ASCII-backed and UTF-16-backed resident
strings, returns NaN for out-of-bounds Int32 indices, and falls
back to the generic builtin call path for everything else. This
keeps the optimistic case in asm while preserving the ordinary
method call semantics when charCodeAt has been replaced or when
string resolution would be required.
2026-04-12 19:15:50 +02:00
Andreas Kling
7ffe01cee3 LibJS: Split builtin call bytecode opcodes
Replace the generic CallBuiltin instruction with one opcode per
supported builtin call and make those instructions fixed-size by
arity. This removes the builtin dispatch sled in the asm
interpreter, gives each builtin a dedicated slow-path entry point,
and lets bytecode generation encode the callee shape directly.

Keep the existing handwritten asm fast paths for the Math builtins
that already benefit from them, while routing the other builtin
opcodes through their own C++ execute implementations. Build the
new opcode directly in Rust codegen, and keep the generic call
fallback when the original builtin function has been replaced.
2026-04-12 19:15:50 +02:00
RubenKelevra
a1ae402bb9 LibJS: Make folded non-decimal prefix parsing UTF-8-safe
Folded StringToNumber() and StringToBigInt() detected non-decimal
prefixes by slicing the string at byte offset 2. On UTF-8 input this
could split at a non-character boundary and panic.

To prevent this, we replace the byte-based split with ASCII prefix
stripping and preserve rejection of empty suffixes such as "0x", "0o",
and "0b" explicitly before parsing the remaining digits.

This makes non-decimal prefix folding UTF-8-safe and preserves the
expected invalid-result behavior for empty prefixed literals.

Tests:

Add regression coverage for folded StringToNumber() and StringToBigInt()
non-decimal prefix handling to validate the UTF-8 safety fix as
'string-to-number-and-bigint-non-decimal-prefixes.js'.

These tests ensure empty suffixes like "0x", "0o", and "0b" and
other invalid prefixed forms stay invalid, while valid prefixed
literals continue to be accepted.

Since we removed a byte-index split in folded
StringToNumber()/StringToBigInt() coercion that could panic when byte
index 2 landed inside a multi-byte UTF-8 scalar, we add regression
tests for representative panic-shape inputs to ensure these coercions
now return invalid results instead of crashing as
'string-to-number-and-bigint-utf8-boundary.js'
2026-04-12 17:36:51 +02:00
Andreas Kling
879ac36e45 LibJS: Cache stable for-in iteration at bytecode sites
Cache the flattened enumerable key snapshot for each `for..in` site and
reuse a `PropertyNameIterator` when the receiver shape, dictionary
generation, indexed storage kind and length, prototype chain
validity, and magical-length state still match.

Handle packed indexed receivers as well as plain named-property
objects. Teach `ObjectPropertyIteratorNext` in `asmint.asm` to return
cached property values directly and to fall back to the slow iterator
logic when any guard fails.

Treat arrays' hidden non-enumerable `length` property as a visited
name for for-in shadowing, and include the receiver's magical-length
state in the cache key so arrays and plain objects do not share
snapshots.

Add `test-js` and `test-js-bytecode` coverage for mixed numeric and
named keys, packed receiver transitions, re-entry, iterator reuse, GC
retention, array length shadowing, and same-site cache reuse.
2026-04-10 15:12:53 +02:00
Johan Dahlin
6e33d36eb5 LibJS: Cache common identifier spellings in the lexer
Add SharedUtf16String (Rc<Utf16String>) for zero-copy sharing. Lexer
caches short ASCII identifiers in a direct-mapped table.

WebsitesParse: 1.03x faster, -5.1% RSS (-164 MB)
WebsitesRun:   1.05x faster, -4.7% RSS (-161 MB)
2026-04-08 16:41:25 +02:00
Johan Dahlin
b542617e09 LibJS: Box StatementKind::ClassFieldInitializer variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
057725c731 LibJS: Box StatementKind::FunctionDeclaration variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
247b9a0a87 LibJS: Box StatementKind::UsingDeclaration variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
0167c83b35 LibJS: Box StatementKind::VariableDeclaration variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
938ec6ca18 LibJS: Box StatementKind::Labelled variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
345dbab49a LibJS: Box StatementKind::With variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
a36d7e5d88 LibJS: Box StatementKind::ForInOf variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
64ea365379 LibJS: Box StatementKind::For variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
31ae13eec6 LibJS: Box StatementKind::DoWhile variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
f29b7ab579 LibJS: Box StatementKind::While variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
3496379d09 LibJS: Box StatementKind::If variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
5ab51b173d LibJS: Box ExpressionKind::Yield variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
c62b7f2f87 LibJS: Box ExpressionKind::ImportCall variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
15097fa8e0 LibJS: Box ExpressionKind::TaggedTemplateLiteral variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
9d0d8129f4 LibJS: Box ExpressionKind::OptionalChain variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
7262ab5880 LibJS: Box ExpressionKind::Member variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
07e187ae6d LibJS: Box ExpressionKind::Conditional variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
333ae7cc6d LibJS: Box ExpressionKind::Assignment variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
0cae77b94a LibJS: Box ExpressionKind::Update variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
4d5df1f10b LibJS: Box ExpressionKind::Logical variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
5a02479704 LibJS: Box ExpressionKind::Binary variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
f3c68d516b LibJS: Box ExpressionKind::Sequence variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
9f0265f953 LibJS: Box ExpressionKind::BigIntLiteral variant 2026-03-28 11:55:41 +01:00
Johan Dahlin
5da800bff2 LibJS: Box ExpressionKind::StringLiteral variant 2026-03-28 11:55:41 +01:00
InvalidUsernameException
61e6dbe4e7 LibJS: Copy object of member expression to preserve evaluation order
Noticed this pattern when reading some minified JS while debugging a
seemingly unrelated problem and immediately got suspicious because of my
earlier, similar fixes.
2026-03-22 15:40:38 +01:00
Andreas Kling
bb0acb54ae LibJS: Optimize x >> 0 to ToInt32 in bytecode codegen
x >> 0 is a common JS idiom equivalent to ToInt32(x). We already had
this optimization for x | 0, now do it for right shift by zero as well.

This allows the asmint handler for ToInt32 to run instead of the more
expensive RightShift handler, which wastes time loading and checking the
rhs operand and performing a shift by zero.
2026-03-20 00:51:23 -05:00
Andreas Kling
144ab69715 LibJS: Remove C++ pipeline compatibility hacks from Rust codegen
Now that the C++ bytecode pipeline has been removed, we no longer
need to match its register allocation or block layout. This removes:

- All manual drop() calls that existed solely to match C++ register
  lifetimes, replaced with scope blocks to naturally limit register
  lifetimes without increasing register pressure.

- The unnecessary saved_property copy in update expressions. The
  property register is now used directly since emit_update_op
  doesn't evaluate user expressions that could mutate it. The copy
  is retained in compound/logical assignments where the RHS can
  mutate the property variable (e.g. a[i] |= a[++i]).

- All "matching C++", "Match C++", etc. comments throughout
  codegen.rs and generator.rs that referenced the removed pipeline.
2026-03-20 00:51:23 -05:00
Andrew Kaster
f06bd0303f LibJS: Use enum for retrieving well known symbols from C++ to Rust 2026-03-19 09:48:32 +01:00
Andrew Kaster
5d43707896 LibJS: Directly use LiteralValueKind enum across FFI boundary 2026-03-19 09:48:32 +01:00
Andrew Kaster
92e4c20ad5 LibJS: Generate FFI header using cbindgen instead of hand-rolling
Replace the BytecodeFactory header with cbindgen.

This will help ensure that types and enums and constants are kept in
sync between the C++ and Rust code. It's also a step in exporting more
Rust enums directly rather than relying on magic constants for
switch statements.

The FFI functions are now all placed in the JS::FFI namespace, which
is the cause for all the churn in the scripting parts of LibJS and
LibWeb.
2026-03-17 20:49:50 -05:00
juhotuho10
8deedd4e67 LibJS/Rust: Apply lint clippy::unnested_or_patterns 2026-03-16 13:55:16 -05:00
juhotuho10
277177484e LibJS/Rust: Apply lint clippy::ref_option 2026-03-16 13:55:16 -05:00