Commit graph

240 commits

Author SHA1 Message Date
Vanand Gasparyan
f3a3488cda Rust: Set import granularity to Item
By default, `rustfmt` persists the import granularity. In practice, most
Rust code has import granularity "Module" due to LSP's actions.

"Item" gets rid of import groupings and achieves cleaner diffs and
better conflict resolution. Better greppability is a positive side
effect.

Note: it's an unstable rustfmt feature. `cargo +nightly fmt` must be
used instead of `cargo fmt`.
2026-05-28 06:52:18 +02:00
Andreas Kling
19c1cae030 LibJS: Preserve strict unresolvable assignment references
Resolve strict global-looking assignment targets before evaluating their
right-hand side instead of lowering them directly to SetGlobal. Preserve
the original unresolvable reference for PutValue so a RHS-created global
cannot hide the ReferenceError.

Add a runtime test for that ordering and update the bytecode expectation
for a strict top-level assignment that now uses SetResolvedBinding.
2026-05-22 14:30:35 +02:00
Andreas Kling
8b64f0079b LibJS: Allow parenthesized dynamic import under new
Parenthesized import calls are valid callees for new expressions. The
unparenthesized form remains a syntax error, but the grouped expression
must parse and then fail at runtime if the produced Promise is not a
constructor.

Add syntax coverage for both forms.
2026-05-22 14:30:35 +02:00
Andreas Kling
795a82d621 LibJS: Track shared function data ownership
Give scripts and modules an explicit list of their shared function data
records, and teach Rust materialization to populate that list directly.
This gives bytecode cache installation a stable set of functions to
update when replacing executable backing storage.
2026-05-22 10:54:44 +02:00
Andreas Kling
790386e114 LibJS: Preserve Promise job ordering across calls
Only drain the standalone Promise job queue when unwinding the outermost
bytecode execution. Draining after every nested run_executable() return
let sibling Promise reactions run before current-job reactions.

Plain async function returns were relying on that extra drain because
return values were compiled as implicit awaits. Resolve completed async
functions through the Promise resolving function instead, while keeping
the async generator return-await path.

Add Promise ordering coverage and update the asm inline-call regression
to check the pre-drain state.
2026-05-22 01:56:57 +02:00
Andreas Kling
a9e54bd745 LibJS: Preserve resolved assignment bindings
Resolve dynamic identifier assignment targets before evaluating the
right hand side, then store through that saved environment record. This
matches the ECMA-262 ordering for simple assignment and var initializers
when a with binding is deleted or direct eval creates a nearer var.

Add runtime coverage for with and direct-eval cases and refresh bytecode
expectations for dynamic assignments that now snapshot the binding
before storing.
2026-05-22 01:56:57 +02:00
Andreas Kling
eeda29d9ed LibJS: Hoist sloppy labelled function declarations
Treat labelled FunctionDeclaration nodes as declaration-bearing items
when collecting block lexical and top-level var scoped declarations.
This lets Annex B declaration instantiation initialize them before
execution and copy block functions back to the var binding.

Add LibJS coverage for function bodies, direct eval, blocks, switch
cases, and duplicate labelled declarations.
2026-05-22 01:56:57 +02:00
Andreas Kling
8a64784d46 LibJS: Check strict destructured parameter names
Run deferred parameter validation whenever the surrounding context is
already strict, not only when a function body contains a Use Strict
Directive. Destructured parameter names are collected while parsing the
parameter list, so eval and arguments bindings in patterns otherwise
escaped the strict-mode early error for functions and methods.

Add parser coverage for sloppy destructuring remaining accepted, strict
source being rejected, and class methods applying their strict context.
2026-05-22 01:56:57 +02:00
Andreas Kling
2f99653b33 LibJS: Resolve super constructor before arguments
Match ECMA-262's SuperCall evaluation order by resolving the super
constructor before evaluating the argument list. This preserves the
constructor across argument-side prototype mutations and still lets
abrupt argument evaluation happen before the constructor check.

Add LibJS coverage and update bytecode baselines for the new saved
super-constructor operand. The test262 superCallOrder staging test now
passes.
2026-05-22 01:56:57 +02:00
Andreas Kling
c23b944340 LibJS: Reject parenthesized nested assignment patterns
Reject parenthesized object and array literals when a destructuring
assignment element is refined into a nested assignment pattern. These
grouped forms do not cover AssignmentPattern, while parenthesized simple
assignment targets such as identifiers and member expressions remain
valid.

Add local syntax coverage for the invalid nested patterns and the valid
simple target cases.
2026-05-22 01:56:57 +02:00
Andreas Kling
99d3b27b17 LibJS: Allow async as a for-await-of target
Apply the `async of` grammar exclusion only to ordinary for-of
statements. The for-await-of production excludes `let` at the start of
the left-hand side, but does not exclude `async of`, so `async` remains
a valid assignment target there.

Add local parser coverage for async functions and async generators.
2026-05-22 01:56:57 +02:00
Andreas Kling
45bd4087c4 LibJS: Reject unparenthesized await before exponentiation
Parse await through the unary-expression path so the existing
exponentiation early error also applies to AwaitExpression. This keeps
parenthesized await expressions valid while rejecting await on the
left-hand side of **.

Add parser coverage for the async-function constructor case.
2026-05-22 01:56:57 +02:00
Andreas Kling
72876d8b93 LibJS: Reject unconditional reserved words as labels
Reject labels whose string value is an unconditional ReservedWord,
while keeping await and yield on their contextual identifier paths.
This makes super labels a syntax error, matching ECMA-262 and V8.

Add coverage for the rejected super label form.
2026-05-22 01:56:57 +02:00
Andreas Kling
62ac304099 LibJS: Avoid naming parenthesized assignment functions
NamedEvaluation for assignment only applies when the left hand side is
an IdentifierRef. Parenthesized identifiers are still assignment
targets, but are not IdentifierRefs, so `(fn) = function () {}` must
leave the function anonymous.

Carry the parser's parenthesized-LHS bit into assignment codegen. Use
it when setting pending_lhs_name for plain and logical assignment, and
cover parenthesized assignment targets in the function-name tests.
2026-05-22 01:56:57 +02:00
Andreas Kling
22e1df58d3 LibJS: Delete through optional chain property references
The delete operator needs the Reference produced by an optional chain
when the chain ends in a property access. We evaluated the chain for
side effects and returned true, so delete object?.property reported
success without removing the property.

Emit delete-specific optional-chain bytecode. It short-circuits to true
for nullish optional hops, but performs DeleteById or DeleteByValue
when the final property reference is reached. Add runtime coverage for
named, computed, and short-circuited optional deletes.
2026-05-22 01:56:57 +02:00
Andreas Kling
94fea5ad2b LibJS: Re-export imported module namespaces indirectly
`import * as ns; export { ns }` was being recorded as a local
export, so two star exports of matching namespace objects resolved
through different fixture modules and became ambiguous.

Emit the same indirect namespace export metadata used by `export * as`
for both normal compilation and bytecode-cache materialization. Add a
module test that merges two matching namespace re-exports.
2026-05-22 01:56:57 +02:00
Andreas Kling
410f0fdbb0 LibJS: Yield delegated iterator results directly
Sync yield* must suspend with the delegated IteratorResult object itself
on non-terminal steps. We were reading value and yielding that value,
causing Generator.prototype.next() to create a fresh result object.

Add a bytecode op for this path so ordinary yield keeps wrapping its
value, and teach generator resume to return the prebuilt result object.
This also avoids touching value before the delegated iterator is done.

Add regression coverage and update the yield* bytecode baseline.
2026-05-22 01:56:57 +02:00
Andreas Kling
6ee59d8bcb LibJS: Scope directive prologue octal tracking
Reset the legacy octal string marker at the start of each directive
prologue scan. Otherwise an octal string parsed later in an outer
function body can leak into a nested function and be treated as part of
that nested function's directive prologue.

Add coverage for a nested strict function following a non-directive
octal string in the outer function body.
2026-05-22 01:56:57 +02:00
Andreas Kling
6c7e642b67 LibJS: Reject non-of for-await loops
Reject for-await loop heads that fall through to normal for-loop or
for-in parsing. ECMA-262 only defines for-await with for-of productions,
and V8 rejects these forms as syntax errors.

Add parser coverage for the valid for-await-of form and the invalid
C-style and for-in forms.
2026-05-22 01:56:57 +02:00
Andreas Kling
bf530ad2af LibJS: Fix destructured primitive string const loops
Mark binding-pattern identifiers with their declaration kind so local
destructuring assignments use the normal TDZ and const assignment path.
This makes local const destructuring match the environment-backed path.

Also teach GetById to expose primitive string virtual index properties
before boxing, matching StringGetOwnProperty and GetByValue. Together
these fix the SpiderMonkey for-in/of const declaration coverage and the
lexical destructuring TDZ test.
2026-05-22 01:56:57 +02:00
Andreas Kling
b42aa88189 LibJS: Preserve generator yield grammar context
YieldExpression carries the surrounding In grammar parameter into its
operand. The parser was always parsing yield operands with In enabled,
accepting invalid generator for-loop heads containing `yield ... in`.

GeneratorExpression parses its optional binding identifier with the
Yield grammar parameter enabled, so `yield` is not a valid name for
a named generator expression. Reject it while still allowing sloppy
generator declarations named `yield`, and add syntax coverage for
both cases.
2026-05-22 01:56:57 +02:00
Andreas Kling
e0ee0990cf LibJS: Preserve no-In context in arrow bodies
ArrowFunction carries the surrounding In grammar parameter into its
ConciseBody. The parser recognized arrows in for-loop init expressions,
but parsed expression bodies with In always enabled, so
`for (x => 0 in 1;;)` was accepted as a normal for loop.

Thread the existing forbidden-token state through arrow parsing so the
body leaves `in` for the for-head parser. That parser then rejects the
arrow as an invalid for-in left-hand side. Add coverage in the for-loop
no-In syntax test.
2026-05-22 01:56:57 +02:00
Andreas Kling
6c2487cc9c LibJS: Allow sloppy labelled functions in function bodies
Annex B.3.2 suppresses the labelled FunctionDeclaration early error
in non-strict code when the host supports that compatibility feature.
Script and block statement lists already enabled that path, but function
bodies parsed their statement lists with it disabled.

Parse function-body statement lists with the same labelled-function
allowance while the existing strict and generator checks continue to
reject the invalid forms. Add coverage for all three cases.
2026-05-22 01:56:57 +02:00
Andreas Kling
4046f65ae7 LibJS: Treat class definition parts as strict
Record the generator's strictness for each emitted bytecode
instruction, instead of applying the executable's final strictness to
every instruction during assembly. This lets class heritage and
computed element names run with strict assignment semantics inside
sloppy scripts.

Also create the class self-binding as a strict immutable binding,
matching ClassDefinitionEvaluation, and cover strict class heritage and
computed names in the runtime tests.
2026-05-22 01:56:57 +02:00
Andreas Kling
6d36517042 LibJS: Infer names for default parameter functions
SingleNameBinding default initializers use NamedEvaluation when the
argument value is undefined and the initializer is an anonymous function
or class definition.

Set the pending inferred name while evaluating default initializers for
identifier parameters, and add coverage for function, generator, async,
arrow, and class expressions.
2026-05-22 01:56:57 +02:00
Andreas Kling
9987035c0a LibJS: Preserve empty with completions
WithStatement evaluation updates empty statement completions to
undefined. The bytecode generator reused the surrounding completion
register while compiling the with body, so an empty break or continue
could leak the enclosing loop's previous completion value.

Give the with body its own completion register initialized to undefined
and add regression coverage for abrupt exits from the body.
2026-05-22 01:56:57 +02:00
Andreas Kling
99596d68ab LibJS: Fix await boundaries in class elements
Treat ordinary and generator function expressions inside class static
blocks as boundaries for await binding names, while still rejecting
await as an arrow parameter in the static block itself.

Parse field initializers without inheriting an enclosing async
function's await-expression context, so script field initializers can
resolve await as an identifier while computed field names still use the
enclosing expression context.

Cover static block function expressions, class field initializers, and
the AST shape for an await arrow inside a nested function.
2026-05-22 01:56:57 +02:00
Andreas Kling
0c6f86c80d LibJS: Evaluate object destructuring targets before GetV
Evaluate member targets in object destructuring before reading the
source property or copying rest properties. Convert computed source
property keys before evaluating the target reference so the generated
bytecode follows the spec ordering.

Cover plain, computed, defaulted, and rest object destructuring.
2026-05-22 01:56:57 +02:00
Andreas Kling
a598e09f22 LibJS: Reject arrow functions in class heritage
ClassHeritage parses an extends clause as a LeftHandSideExpression,
which excludes unparenthesized arrow functions. Reject arrows that start
at the heritage expression while still allowing parenthesized arrows and
arrows nested inside call arguments.

Add class inheritance coverage for declarations, expressions, and nested
arrow arguments.
2026-05-22 01:56:57 +02:00
Andreas Kling
77bf420654 LibJS: Reject duplicate method parameters
Formal parameter early errors depend on the final parameter-list shape.
A duplicate in `function(a, a, ...rest)` was accepted because the
duplicate appeared before the rest parameter was seen.

Track the final simple-parameter-list result before checking duplicate
names, and enforce `UniqueFormalParameters` for method definitions. Add
coverage for sloppy functions with a later rest parameter and sloppy
object methods.
2026-05-22 01:56:57 +02:00
Andreas Kling
a6e642790f LibJS: Validate eval private names at instantiation
Track private identifiers that eval code references outside a local
class body and pass them through EvalDeclarationInstantiation data.
This lets direct eval validate those names against the caller private
environment before execution, while indirect eval and missing names
still throw SyntaxError.

Add coverage for direct eval access from methods, field initializers,
static methods, and the missing-private-name SyntaxError path.
2026-05-22 01:56:57 +02:00
Andreas Kling
7a246b63c7 LibJS: Infer computed property function names
Use a runtime SetFunctionName bytecode operation when object literal
property keys are not known until evaluation. This lets anonymous
function and class expressions, methods, and accessors receive names
from numeric, computed, and Symbol property keys.

Store inferred ECMAScript function names on each function object instead
of mutating shared function data. That keeps repeated evaluations with
different computed keys from leaking names across closures, while still
using the per-instance name for stack metadata.

Add regression coverage for computed object property names, repeated
computed-key evaluations, and preserving unnamed functions that are only
referenced by a computed property value.
2026-05-22 01:56:57 +02:00
Andreas Kling
5c881d1553 LibJS: Close iterators during array destructuring
Route abrupt completions from array destructuring target, default, and
store evaluation through IteratorClose when the iterator is still open.
Keep abrupt completions from iterator stepping itself on the existing
propagation path, matching the spec distinction used by other engines.

Also write back bytecode iterator done state when iterator abstract
operations mark the iterator as completed, so later close decisions see
the updated Iterator Record state. Add regression coverage for target,
default, iterator-next, and generator-return paths.
2026-05-22 01:56:57 +02:00
Andreas Kling
4ac744082b LibJS: Cache dynamic environment coordinates
Dynamic environment binding opcodes lost the old coordinate warmup.
They were split away from the static coordinate opcodes. Hot closures
and eval-sensitive functions then resolved the same binding by name on
every execution, which regressed JS benchmark throughput badly.

Give each dynamic environment opcode a per-executable coordinate cache
slot. The cache keeps the bytecode stream immutable while letting both
interpreters take a direct declarative environment fast path after the
first lookup. Keep the existing eval invalidation behavior and only warm
caches for declarative-only chains so with environments continue to
observe object shadowing.

Reject cached bytecode that uses the no-cache sentinel for dynamic
environment coordinate cache operands, since execution indexes those
cache arrays unconditionally.

Rebaseline bytecode expectations for the instruction size changes. Add
coverage for with-object shadowing across repeated dynamic lookups and
for rejecting corrupt dynamic environment cache indices.
2026-05-19 15:54:23 +02:00
Andreas Kling
948afeb7ee LibJS: Reuse cached constant buffers when materializing
Validate cached constant buffers without decoding them into Rust
ConstantValue objects, then pass the bytes to the C++ executable
factory. Align string constants in the FFI encoding so fresh and cached
buffers use the same layout.
2026-05-19 11:32:50 +02:00
Andreas Kling
afa4a69315 LibJS: Borrow bytecode cache tables when materializing
Pass decoded UTF-16 table entries to the C++ executable factory as
borrowed FFI slices during bytecode cache materialization. This avoids
copying cache-backed names into generator-owned Rust vectors before C++
materializes its own bytecode tables.
2026-05-19 11:32:50 +02:00
Andreas Kling
75b2bcd7e4 LibJS: Track duplicate environment binding slots
Duplicate sloppy block function declarations append duplicate runtime
bindings, and name lookup resolves to the newest slot. The bytecode
coordinate tracker was keeping the first slot instead, so a second
InitializeLexicalBinding could target an already initialized binding
when running through the C++ interpreter.

Record every emitted binding creation and let later duplicates replace
the coordinate map entry, matching the way DeclarativeEnvironment
appends bindings.
2026-05-18 20:35:14 +02:00
Andreas Kling
cd38cdf6cf LibJS: Pass var environment binding counts through FFI
Thread the var environment binding count through the Rust and C++ SFD
metadata helpers wherever the function environment binding count already
travels. This lets CreateVariableEnvironment use the cached var
environment shape for functions with parameter expressions.
2026-05-18 20:35:14 +02:00
Andreas Kling
afcc39fdec LibJS: Track var coordinates in var environments
Record CreateVariable instructions in the coordinate scope that matches
their runtime environment mode. Lexical variables still use the current
lexical scope. Var bindings use the generator's var environment anchor.

Add coverage for an Annex B block function in a function with default
parameters and a body lexical environment. This catches cases where the
var binding is recorded in the parameter scope. A later call could read
the wrong environment slot.
2026-05-18 20:35:14 +02:00
Andreas Kling
ef74c1ca55 LibJS: Keep cached bytecode file-backed
Teach Bytecode::Executable to store its instruction stream as either
an owned Vector or a retained Core::ImmutableBytes range. Cached
bytecode materialization now clones the immutable blob owner and lets
the executable point directly into the file-backed cache blob instead
of copying instruction bytes back onto the heap.

Keep a cached instruction data pointer inside the stream wrapper so the
asm interpreter still has a direct hot-path load. Align executable
bytecode payloads in the cache format so mmap-backed instruction
streams satisfy validator and interpreter alignment requirements.
2026-05-18 20:35:14 +02:00
Andreas Kling
cd7ff16106 LibJS: Emit eager environment coordinates in bytecode
Track bytecode-created declarative bindings while generating code so
most environment-coordinate operands can be emitted eagerly. Dynamic
lookup instructions remain for scopes where eval or object environments
can change resolution at runtime.

This lets the static coordinate bytecode variants run without cached-
vs-uncached branches or eval poison checks, and rebaselines bytecode
output for the newly eager coordinate operands.
2026-05-18 20:35:14 +02:00
Andreas Kling
a5ba300186 LibJS: Split dynamic environment lookups from coordinates
Add separate bytecode instructions for environment lookups that must
stay dynamic, such as eval- and with-sensitive scopes. Keep the
coordinate variants for eagerly resolved declarative environments so
their operands can be treated as immutable at runtime.

This removes cached-coordinate mutation from the interpreter paths and
updates the bytecode expectations for the new dynamic lookup opcodes.
2026-05-18 20:35:14 +02:00
Andreas Kling
1ce4242b4b LibJS: Store bytecode cache indexes instead of pointers
Store compact cache indexes in bytecode instructions instead of raw
pointers to the executable cache vectors. This keeps the instruction
stream independent from heap addresses and removes pointer fixups when
materializing cached bytecode.

Resolve the mutable cache pointers at execution time from the current
Executable. Bytecode test expectations are updated for the smaller cache
operands and resulting instruction offsets.
2026-05-18 20:35:14 +02:00
Andreas Kling
30314e8ad0 LibJS: Lazily decode cached function bytecode
Store nested function executables in the bytecode cache as
length-prefixed payloads, and keep those payloads mapped instead of
decoding every function when materializing the outer script or module.
Decode and validate a cached function executable only when that
function is installed for lazy materialization.

Validate cached executable bytecode before storing it on shared data,
and recurse source range validation through the lazy executable
payload. This keeps corrupt cache entries on the recoverable
materialization path instead of crashing when a function is first used.

This avoids turning the warm disk cache into retained dirty heap for
all uncalled nested functions on large sites.
2026-05-17 08:58:45 +02:00
Andreas Kling
7e7d57fb1a LibJS: Reject impossible bytecode cache table counts
Reject counted bytecode cache table payloads whose count is larger than
the payload byte length. All currently encoded records and constants use
at least one payload byte, so these counts cannot be valid. Accepting
them would only risk oversized allocations during materialization.

Add Rust coverage for record sequence and constant table headers with
impossible counts.
2026-05-16 08:13:35 +02:00
Andreas Kling
6c615c40f3 LibJS: Borrow bytecode cache executable tables
Store executable-side tables as counted encoded payloads instead of
allocating decoded Rust vectors while reading the bytecode cache. This
keeps identifier, property key, string, exception handler, source map,
local variable, shared function, and class blueprint tables borrowed
from mapped cache blobs until validation or materialization walks them.

Bump the bytecode cache format because these table encodings now carry
raw payload lengths and preserve their own nested alignment.
2026-05-16 08:13:35 +02:00
Andreas Kling
ac02d2e956 LibJS: Borrow bytecode cache constant tables
Store executable constants as a counted encoded payload instead of a
sequence of decoded records. Decoding a mapped cache blob can now retain
that payload as a borrowed range and postpone inflating ConstantValue
objects until executable materialization actually needs them.
2026-05-16 08:13:35 +02:00
Andreas Kling
1ad3039001 LibJS: Align bytecode cache UTF-16 payloads
Bump the bytecode cache format and pad serialized UTF-16 strings to u16
alignment. This lets mapped cache blobs expose decoded UTF-16 payloads
as FFI slices directly instead of re-decoding them into temporary
aligned buffers during materialization.

Update the bytecode cache corruption helpers to skip the new string
padding when walking serialized blobs.
2026-05-16 08:13:35 +02:00
Andreas Kling
6f4bdb3cb6 LibJS: Borrow bytecode cache UTF-16 strings
Keep decoded executable strings as ranges into the mapped bytecode cache
blob when the decoder has a foreign owner. Materialize aligned UTF-16
buffers only when passing the data to C++ or rebuilding generator-owned
structures for executable creation.

This covers executable string tables, string constants, local variable
names, nested function metadata, and cached class blueprint strings. Add
Rust coverage for borrowing decoded UTF-16 payloads from a foreign blob.
2026-05-16 08:13:35 +02:00
Andreas Kling
a646f9d0bf LibJS: Borrow mapped bytecode cache executable bytes
Keep executable bytecode payloads decoded from owner-backed bytecode
cache blobs as ranges into the original blob instead of copying them
into Rust Vec allocations. The mapped blob owner is held by decoded
executable records, including lazy nested function executables, so the
borrowed bytecode remains alive until materialization copies it into the
final C++ Executable.

Use the owner-backed decoder for HTTP bytecode cache hits and keep the
plain byte decoder for tests and in-memory callers. Add coverage for
materializing bytecode cache data from an ImmutableBytes mapped file.
2026-05-16 08:13:35 +02:00