Commit graph

281 commits

Author SHA1 Message Date
Andreas Kling
7a6af95db3 LibJS: Move bytecode instruction dumping to Rust
Generate Rust bytecode dump helpers from Bytecode.def and route
Executable::dump() through them for instruction stream formatting.

Add a small Rust runtime::value helper for decoding encoded LibJS
Values so immediate Value operands are formatted on the Rust side. C++
callbacks remain only for local names and GC-backed Value payloads that
still need LibJS object access.

Remove the generated C++ to_byte_string_impl() methods and the old
Instruction::to_byte_string() dispatch. The bytecode dump tests cover
output compatibility.
2026-06-15 02:41:57 +02:00
Andreas Kling
213403542c LibJS: Remove AsmInt slow path stats collection
Remove the optional slow path hit counters from AsmSlowPaths.cpp. This
also drops the registration call from the AsmInt entry path, leaving
slow paths focused on executing the out-of-line instruction behavior.
2026-06-14 20:27:59 +02:00
Andreas Kling
97a6807ffa LibJS: Remove JS_BYTECODE_DEBUG
Remove the stale bytecode execution debug hook from Interpreter.cpp now
that bytecode dispatch always enters AsmInt directly. The remaining
bytecode dump flag is separate and still used by parser/codegen paths.
2026-06-14 20:27:59 +02:00
Andreas Kling
8fee268851 LibJS: Call AsmInt directly from run_executable
Remove the empty AsmInterpreter wrapper and the VM::run_bytecode()
trampoline now that the bytecode interpreter only enters AsmInt. Move
the stack-limit check and generated assembly entry call into
run_executable(), then drop the stale wrapper source file and includes.
2026-06-14 20:27:59 +02:00
Andreas Kling
060ba41a84 LibJS: Remove unused VM interpreter helpers
Remove VM helpers that became unused after bytecode execution stopped
using the generic interpreter path. The AsmInt entry path now owns these
transitions directly.
2026-06-14 20:27:59 +02:00
Andreas Kling
6e6726b612 LibJS: Move instruction bodies into AsmInt slow paths
Move the remaining bytecode instruction implementations out of
execute_impl() and into AsmInt slow paths. Remove the execute_impl()
bodies once their only caller is gone, leaving instruction classes as
bytecode data containers.
2026-06-14 20:27:59 +02:00
Andreas Kling
5bc002d71c LibJS: Move property and call handlers into AsmInt
Move property access, iterator, object property iterator, import, class
and argument-array call opcodes out of the generic fallback path. Keep
the semantic work in C++ slow paths and dispatch to them from AsmInt.
2026-06-14 20:27:59 +02:00
Andreas Kling
058f63efd2 LibJS: Move control and binding handlers into AsmInt
Move the remaining control-flow, conversion, creation, delete, binding,
private-name and environment-related fallback handlers into AsmInt. This
keeps the generic fallback path shrinking while leaving complex behavior
in C++ slow paths.
2026-06-14 20:27:59 +02:00
Andreas Kling
fff128a8cc LibJS: Move simple bytecode handlers into AsmInt
Move simple fallback handlers into the AsmInt DSL or dedicated slow-path
calls. This covers straightforward allocation, environment setup,
argument creation, completion state, template object, async iterator and
function allocation opcodes.
2026-06-14 20:27:59 +02:00
Andreas Kling
5ca52d2a77 LibJS: Remove generic bytecode interpreter
Remove the C++ bytecode interpreter dispatch loop now that AsmInt is the
only bytecode execution engine. Keep the existing AsmInt fallback path
for instructions that have not yet been moved into assembly or C++ slow
path handlers.
2026-06-14 20:27:59 +02:00
Tim Ledbetter
36ed97128a LibJS: Stop scanning the shape once an enumerable string key is found 2026-06-09 16:56:54 +02:00
Luke Wilde
d9e7109d29 LibJS: Use conservative GC containers for unrooted property-key sets 2026-05-28 21:16:23 +02:00
Andreas Kling
fafba20b1a LibJS: Preserve runtime caches across executable swaps
Allow a freshly materialized executable to inherit compatible runtime
caches from the executable it replaces. Store template object caches as
GC cells so they can be shared safely between executable instances.
2026-05-22 10:54:44 +02:00
Andreas Kling
54d0dc1a85 LibJS: Defer Promise jobs during module execution
Avoid draining the Promise job queue from VM::run_executable while a
SourceTextModule is still executing its body. Dynamic import queues the
load/link/evaluate continuation as a Promise job, and running that job
before ModuleEvaluation unwinds can re-enter Link() while the entry
module is still evaluating.

Track module execution depth so standalone script execution still drains
jobs at the same boundary, but module jobs are drained by
VM::run(SourceTextModule) after Link/Evaluate returns. Add coverage for
an entry module dynamically importing itself during evaluation.
2026-05-22 01:56:57 +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
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
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
dafe90a955 LibJS: Handle cached accessors without getter or setter
Accessor cache entries can be reused by objects with the same shape even
when the accessor functions differ. Reusing a getter cache for a later
setter-only object tried to call null instead of returning undefined.

Route cached gets through the missing-getter behavior from OrdinaryGet,
and fall back to the slow set path when a cached accessor has no setter
so strict assignment failures still come from OrdinarySet.

Add IC coverage for the missing accessor half in both directions.
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
Shannon Booth
78a4438cd8 LibGC: Default-construct RootHashTable from the global heap 2026-05-20 20:37:55 +02:00
Shannon Booth
387cd6e2e2 LibGC: Default-construct RootVector from the global heap
Similar to GC::Root<T>, make GC::RootVector<T> constructible without
explicitly passing a Heap.

This is implemented by having RootVectorBase use GC::Heap::the() for
heap-free construction.
2026-05-20 20:37:55 +02:00
Luke Wilde
1bae832ce1 LibJS: Root the NewClass element-keys local with RootVector 2026-05-19 19:24:08 +02:00
Luke Wilde
52bf9c5fcb LibJS+LibWeb: Adopt RootHashTable for unrooted GC-pointer storage 2026-05-19 19:24:08 +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
0a49dd1c28 LibJS: Reduce inline cache memory usage
Store bytecode property lookup caches as tiered handles instead of
eagerly allocating four entries for every cache slot. Each slot starts
empty, grows to one monomorphic entry after the first cacheable lookup,
and promotes to the existing four-entry table when another cache key
appears.

Global variable caches keep one inline property entry because they
usually warm up as global object property accesses. This keeps common
global access allocation-free while still avoiding the inherited
polymorphic cache cost.

Generated asm offsets now point at the lazy property-cache storage and
the inline global entry, and asm fast paths bail out for empty property
cache slots.
2026-05-19 01:12:36 +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
28ab80ebc9 LibJS: Share parameter var environment shapes
Cache EnvironmentShape for var environments created when default
parameter expressions force body vars into a separate environment. Reuse
that cache in CreateVariableEnvironment when the capacity matches the
active function metadata.

Expose active SharedFunctionInstanceData through VM so the bytecode
instruction can update the cache through a narrow API. Add coverage for
repeated calls with captured vars in this environment.
2026-05-15 14:04:47 +02:00
Andreas Kling
5dfe145c03 LibJS: Share function environment binding shapes
Cache EnvironmentShape on SharedFunctionInstanceData after the first
function environment creates its statically known bindings. Install the
cached shape before later calls create bindings so those environments
avoid building their own name vector and lookup map.

Let DeclarativeEnvironment keep a local overlay for eval-created
bindings, and keep the packed flag mirror per environment while the asm
interpreter still reads flags directly from the environment. Add runtime
coverage for repeated calls with an eval-created binding.
2026-05-15 14:04:47 +02:00
Martin Chrástek
29dc4df4e5 LibJS: Fix var declarations in direct eval inside catch blocks
The Annex B.3.4 spec change requires distinguishing
catch clause environments from other lexical environments
when checking for var/let conflicts in eval, which is now tracked
via a flag on DeclarativeEnvironment and propagated through the
CreateLexicalEnvironment bytecode instruction from the Rust codegen.
2026-05-15 10:01:08 +02:00
Andreas Kling
e3841a7392 LibJS: Compact Shape property table to a sorted flat array
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.
2026-05-14 19:59:40 +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
Aliaksandr Kalenik
5538d62c9e LibJS: Store pending completion on generator instead of CompletionCell
Avoids a heap allocation per generator/async-generator resumption by
storing the pending completion value and type directly on the
GeneratorObject / AsyncGenerator, instead of allocating a separate
CompletionCell and passing it into the executable.

GetCompletionFields and SetCompletionType now read/write the fields on
the generator object directly.
2026-05-03 12:24:04 +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
583fa475fb LibJS: Call RawNativeFunction directly from asm Call
The asm interpreter already inlines ECMAScript calls, but builtin calls
still went through the generic C++ Call slow path even when the callee
was a plain native function pointer. That added an avoidable boundary
around hot builtin calls and kept asm from taking full advantage of the
new RawNativeFunction representation.

Teach the asm Call handler to recognize RawNativeFunction, allocate the
callee frame on the interpreter stack, copy the call-site arguments,
and jump straight to the stored C++ entry point.
NativeJavaScriptBackedFunction and other non-raw callees keep falling
through to the existing C++ slow path unchanged.
2026-04-15 15:57:48 +02:00
Andreas Kling
df0fdee2a0 LibJS: Cache JS-to-JS inline call eligibility
Store whether a function can participate in JS-to-JS inline calls on
SharedFunctionInstanceData instead of recomputing the function kind,
class-constructor bit, and bytecode availability at each fast-path
call site.
2026-04-14 08:14:43 +02:00
Andreas Kling
9af5508aef LibJS: Split inline frames from execution context stack
Keep JS-to-JS inline calls out of m_execution_context_stack and walk
the active stack from the running execution context instead. Base
pushes now record the previous running context so duplicate
TemporaryExecutionContext pushes and host re-entry still restore
correctly.

This keeps the fast JS-to-JS path off the vector without losing GC
root collection, stack traces, or helpers that need to inspect the
active execution context chain.
2026-04-13 18:29:43 +02:00
Andreas Kling
2ca7dfa649 LibJS: Move bytecode interpreter state to VM
The bytecode interpreter only needed the running execution context,
but still threaded a separate Interpreter object through both the C++
and asm entry points. Move that state and the bytecode execution
helpers onto VM instead, and teach the asm generator and slow paths to
use VM directly.
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
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
Tim Ledbetter
275e141823 LibJS: Rename Value::as_array() to Value::as_array_exotic_object()`
This better describes what the method returns and avoids the possible
confusion caused by the mismatch in behavior between
`Value::is_array()` and `Value::as_array()`.
2026-03-29 13:45:38 +02:00
InvalidUsernameException
cf6c9d69f4 LibJS: Initialize constants in execution context right away
In b2d9fd3352, the root cause of the crash
was somewhat misdiagnosed, particularly around what place in code an
allocation could occur while constants data was uninitialized.

But more importantly, we can do better than the solution in that commit.
Instead of initializing constants with default values and then
overwriting them afterwards, simply initialize them with their actual
values directly when constructing the execution context.

This effectivly reverts commit b2d9fd3352.
2026-03-29 13:44:06 +02:00
InvalidUsernameException
7002c47ce1 LibJS+LibWeb: Pass constants into execution context constructor
The additional data being passed will be used in an upcoming commit.
Allows splitting the churn of modified function signatures from the
logically meaningful code change.

No behavior change.
2026-03-29 13:44:06 +02:00
Andreas Kling
e243e146de LibJS+LibRegex: Switch RegExp over to the Rust engine
Switch LibJS `RegExp` over to the Rust-backed `ECMAScriptRegex` APIs.

Route `new RegExp()`, regex literals, and the RegExp builtins through
the new compile and exec APIs, and stop re-validating patterns with the
deleted C++ parser on the way in. Preserve the observable error
behavior by carrying structured compile errors and backtracking-limit
failures across the FFI boundary. Cache compiled regex state and named
capture metadata on `RegExpObject` in the new representation.

Use the new API surface to simplify and speed up the builtin paths too:
share `exec_internal`, cache compiled regex pointers, keep the legacy
RegExp statics lazy, run global replace through batch `find_all`, and
optimize replace, test, split, and String helper paths. Add regression
tests for those JavaScript-visible paths.
2026-03-27 17:32:19 +01:00
Andreas Kling
72fdf10248 LibJS: Don't cache dictionary shapes in NewObject premade shape cache
Dictionary shapes are mutable (properties added/removed in-place via
add_property_without_transition), so sharing them between objects via
the NewObject premade shape cache is unsafe.

When a large object literal (>64 properties) is created repeatedly in
a loop, the first execution transitions to a dictionary shape, which
CacheObjectShape then caches. Subsequent iterations create new objects
all pointing to the same dictionary shape. If any of these objects adds
a new property, it mutates the shared shape in-place, increasing its
property_count, but only grows its own named property storage. Other
objects sharing the shape are left with undersized storage, leading to
a heap-buffer-overflow when the GC visits their edges.

Fix this by not caching dictionary shapes. This means object literals
with >64 properties won't get the premade-shape fast path, but such
literals are uncommon.
2026-03-22 11:10:01 -05:00