Commit Graph

455 Commits

Author SHA1 Message Date
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
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
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
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
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
Andreas Kling
15563aa9d4 LibJS: Assert asmint value contracts
Add debug-only assertions for value and frame contracts that cross the
asm/C++ boundary. Check resumed inline frames have executable bytecode,
lexical environments exist before boxing, and string builtin helpers
return string values for the fast paths that store their results.

asmintgen successfully lowers this file for x86_64 with assertions
enabled.
2026-05-03 13:10:48 +02:00
Andreas Kling
27b4527d8b LibJS: Assert asmint size invariants
Add debug-only checks for relationships between fast-path metadata and
addressing bounds. These catch inconsistent inline-call slot counts,
argument padding, and flattened iterator cache lengths before the asm
path uses them for addressing.

asmintgen successfully lowers this file for x86_64 with assertions
enabled.
2026-05-03 13:10:48 +02:00
Andreas Kling
5ffcd4f5b9 LibJS: Assert asmint call-frame invariants
Add debug-only assertions around inline Call frame construction and
state restoration. The checks cover executable metadata, stack cursors,
realm/environment pointers, constant storage, raw native entry points,
and the VM state reloaded after helper calls.

asmintgen successfully lowers this file for x86_64 with assertions
enabled.
2026-05-03 13:10:48 +02:00
Andreas Kling
69425b9892 LibJS: Assert asmint pointer invariants
Add debug-only asmint assertions for cache and backing-store pointers
that fast paths dereference after metadata has selected a cache hit.
These checks catch stale property/global caches, broken environment
coordinates, and inconsistent indexed storage state without changing
release or distribution builds.

asmintgen successfully lowers this file for x86_64 with assertions
enabled.
2026-05-03 13:10:48 +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
Andreas Kling
47c82b38d0 LibJS: Range-check enum-typed bytecode fields in the validator
The validator now bounds-checks the five enum-shaped field types
that appear in Bytecode.def: Completion::Type, IteratorHint,
EnvironmentMode, PutKind, and ArgumentsKind. The codegen
recognizes each by its .def type name and emits a u32 read plus a
range check against the corresponding variant count.

The variant counts ride across the FFI as new fields on
FFIValidatorBounds rather than being hardcoded on the Rust side,
so the Rust validator never has to know which variants the C++
enum currently defines. The C++ side computes each count as
`to_underlying(LastVariant) + 1` with a static_assert pinning the
expected value, so adding or removing a variant in any of these
enums fails the build until the validator is updated.
2026-05-03 08:43:19 +02:00
Andreas Kling
2782fa1559 LibJS: Tighten the bytecode validator's argument operand bound
Until now the validator passed `u32::MAX` as the argument-region
upper bound because nothing on Executable tracked how many
argument slots a given bytecode buffer might reference. That left
the largest validation hole open: any flat operand index above
`registers + locals + constants` slid through the check.

The Rust assembler already walks every operand during phase 1 so
it can offset each one into the runtime's flat layout. This commit
piggybacks on that walk to record the highest `Operand::argument`
index touched and surfaces `(max + 1)` (or zero if no argument is
ever referenced) on `AssembledBytecode`. The value rides through
`FFIExecutableData` onto a new `Executable::number_of_arguments`
field, which `Validator.cpp` then feeds into `FFIValidatorBounds`.

The bound is now tight: every operand index in the encoded stream
is range-checked against the actual runtime array size, including
the argument region.
2026-05-03 08:43:19 +02:00
Andreas Kling
d3ca680a62 LibJS: Validate basic blocks, exception handlers, and source map
Pass 3 cross-checks the structural metadata stored alongside the
bytecode buffer on Executable against the offset set built during
Pass 1. Every basic block start offset must point at an instruction
boundary; exception handler start, end, and handler offsets must
either be at an instruction boundary or, for the inclusive-start /
exclusive-end pair, equal to the bytecode length; source map
entries must do the same.

Of these, the exception handler's handler_offset is the safety-
critical one for the disk-cache use case: a corrupted offset there
sends control flow into the middle of an instruction. The other
checks tighten the cache-load surface area and catch obvious file
corruption.

The metadata is plumbed across the FFI as a separate
FFIValidatorExtras struct so the validator entry point keeps the
single-call shape, with a flat-offset mirror struct for exception
handlers since the original carries no source data we need.
2026-05-03 08:43:19 +02:00
Andreas Kling
0b8fbc03ef LibJS: Add per-field bytecode validation generated from Bytecode.def
Pass 2 of the validator now runs a per-instruction check that walks
each opcode's fields and verifies every reference points somewhere
sensible. Operand indices, label addresses, identifier/string/
property-key/regex table indices, cache indices, and trailing
operand arrays are all bound-checked against the values the C++
side carries on the Executable. Fields whose bound depends on an
enum variant count or other type information not present in
Bytecode.def are left for a follow-up.

The codegen lives in build.rs and reuses the existing layout
machinery from the bytecode_def crate, so each opcode gets a match
arm whose body reads each field at its known byte offset and calls
the right hand-written validate_* helper. Variable-length
instructions cross-check the count field against m_length before
iterating the trailing array, which guards against an attacker
sneaking a count that walks off the end of the instruction.

Note that the encoded operand format is a flat u32 index into the
runtime [registers | locals | constants | arguments] array, since
Operand::offset_index_by zeroes the 3-bit type tag during assembly.
The validator therefore range-checks the flat index rather than
reading the type tag and dispatching per kind.

The argument-count upper bound isn't tracked on Executable yet, so
arguments remain effectively unbounded; tightening that bound is
left for a later commit.

Cache pointer fields are validated only when before_cache_fixup is
true, since after the fixup pass they hold real pointers and must
be left alone. NewFunction and NewClass have plain u32 fields for
shared-function-data and class-blueprint indices; those are
recognized by name in the codegen so the indices still get
range-checked.

The error category enum is renumbered to drop the per-operand-kind
codes, since at the bytecode level we no longer differentiate.
2026-05-03 08:43:19 +02:00
Andreas Kling
d4ed658429 LibJS: Add bytecode validator scaffolding driven from Bytecode.def
The plan is to start caching compiled JS bytecode on disk. Before
loading anything from a cache we need confidence that the bytes are
structurally well-formed, since a corrupted or tampered-with cache
file could otherwise hand the interpreter an out-of-bounds jump or a
constant-pool index that points past the end of the table.

This commit lays down the scaffolding for that validator. The walker
lives in Rust (Libraries/LibJS/Rust/src/bytecode/validator.rs) so
that it can share the existing Bytecode.def-driven layout machinery
with the encoder. C++ calls into it through cbindgen, the same way
the rest of the Rust pipeline is wired up.

For now, the validator only does Pass 1: walk the byte stream,
verify each instruction is 8-byte aligned, the opcode byte is in
range, and the reported length keeps us inside the buffer. The
length lookup is generated from Bytecode.def so fixed-length and
variable-length instructions stay in sync with the rest of the
codegen automatically. Per-field bounds checks (operands, labels,
table indices, cache indices) and structural extras (basic block
offsets, exception handlers, source map) come in follow-up commits.

The validator runs after every successful compilation in debug and
sanitizer builds, gated on !NDEBUG || HAS_ADDRESS_SANITIZER, so we
get an extra sanity check on every executable the encoder produces
without paying for it in release builds. Failure trips a
VERIFY_NOT_REACHED with the offset, opcode, and error category
logged via dbgln().
2026-05-03 08:43:19 +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
928a9dfbf7 LibJS+AsmIntGen: Retire the positional t0..t8 / ft0..ft3 DSL aliases
asmint.asm no longer references any positional temp register name --
every handler and macro declares its temporaries by name with `temp` /
`ftemp` and lets the register allocator place them. Migrate the last
two macros holding out:

  * dispatch_current uses a macro-local `opcode` temp for the load8 +
    indirect jmp.
  * pop_inline_frame_and_resume names its return-pc, dst-index, value-
    address, vm-pointer, and executable temps explicitly.

With nothing left referring to the positional aliases, drop the
tN / ftN -> physical-register fallback from registers::resolve_register
and update the DSL reference comments at the top of asmint.asm and in
main.rs to describe the named-temp model. The two pre-existing codegen
tests that probed the old positional behavior get rewritten to use the
post-allocation physical-register names directly, since that is now
the actual contract of resolve_op.
2026-04-26 13:29:56 +02:00
Andreas Kling
3950e434cb LibJS: Migrate the asm-managed Call handler to named DSL temporaries
The Call handler is the asm interpreter's hot path for inline JS-to-JS
dispatch, and it carries roughly fifty distinct named values across
its body (callee object, packed metadata word, formal/passed/total
counts, two stack-side pointers, the argument-copy cursor, the
script-or-module pair-load scratch, the native return value and
variant tag, ...).

The native-exception path adds a small `mov helper_arg, native_return`
bridge between call_raw_native (output pinned to rax by
fixed_operands) and call_helper (input pinned to rcx by
fixed_operands), since the two ABIs land their values in different
registers and the explicit mov is the cleanest way to express the
hand-off.
2026-04-26 13:29:56 +02:00
Andreas Kling
e8cd0ef7b8 LibJS: Migrate ObjectPropertyIteratorNext to named DSL temporaries
Convert the for-in iteration fast path to named temps. The handler
threads ~25 distinct values through its body (the iterator value,
its tag, the unboxed iterator, the fast-path discriminator, the
property cache pointer, the cached and current shapes, the receiver,
the dictionary-generation pair, the indexed-count and next-indexed
counters, the named-index, the named-key data pointer, and a few
scratch slots), so the migrated form is much easier to read than the
positional spaghetti where t0..t8 each meant five different things in
different basic blocks.
2026-04-26 13:29:56 +02:00
Andreas Kling
d930f135d2 LibJS: Migrate GetByValue to named DSL temporaries
Convert the array / typed-array load fast paths in GetByValue, the
mirror of PutByValue. Roughly the same shape: ~18 named GPR temps
and a slot_dbl FPR temp covering the loaded value, the bytes-or-int
raw integer view, the address scratch, and the negative-zero compare.

The .ta_float32 / .ta_float64 paths become readable: where the
positional code had "Exclude negative zero early (t1 gets clobbered
by double_to_int32)" because t1 had to do double duty, the migrated
form names slot, slot_dbl, neg_zero, and raw separately and the
allocator lays them out without the implicit clobber dance.
2026-04-26 13:29:56 +02:00
Andreas Kling
5015715c9d LibJS: Migrate PutByValue to named DSL temporaries
Convert the array / typed-array store fast paths in PutByValue --
the largest single handler so far at 150+ positional t-uses -- to
declare its 19 GPR temps and 1 FPR temp by name (kind, base, prop,
index, obj, flags, storage_kind, size, elements, src, capacity_addr,
capacity, slot, empty_tag, kind_byte, addr, src_int32, max, result,
src_dbl, plus base_tag/prop_tag scratches reused across paths).

The migrated body removes the "save in t0 before load_operand
clobbers it" / "compute store address before check_is_double clobbers
t4" maneuvers that the positional code needed -- the allocator
handles those constraints automatically.
2026-04-26 13:29:56 +02:00
Andreas Kling
f816e43fbb LibJS: Migrate string builtins and the utf16-code-unit load macro
Convert the load_primitive_string_utf16_code_unit macro to take its
inputs (string pointer, index) and output (code_unit) as explicit
parameters, plus migrate CallBuiltinStringFromCharCode,
CallBuiltinStringPrototypeCharCodeAt, and
CallBuiltinStringPrototypeCharAt.

The macro previously hard-coded inputs to t2/t4 and output to t0,
which forced callers to remember those slot assignments and to wrap
the call_helper interactions around them. The migrated form -- where
the caller names what it wants in each slot -- removes another
"clobbers t3, t5" comment.
2026-04-26 13:29:56 +02:00
Andreas Kling
f0db84b1ae LibJS: Migrate GetLength, GetGlobal, SetGlobal to named DSL temporaries
Convert the global variable IC and the array-length fast path to
named temps. These three handlers are large -- GetGlobal and
SetGlobal carry ~20 named values across each path (realm pointer,
global object, declarative environment, cache pointer, two serial
numbers, the shape, the cached shape, the dictionary generations,
the property offset, the named-properties pointer, the loaded value
and tag, plus environment-binding state) -- and the migration makes
those values easy to read instead of having to remember which slot
each piece of state was occupying.

GetLength's "magical" int-to-double widening picks up a named
temp for the sign-bit check and the result, which removes another
"NB: load_operand clobbers t0" comment.
2026-04-26 13:29:56 +02:00
Andreas Kling
85e6930806 LibJS: Migrate GetById and PutById to named DSL temporaries
Convert the inline-cache fast paths for property load/store to named
temps. Both handlers carry a substantial amount of state across the
ic-hit check (the boxed base value, its tag, the unboxed Object*,
the shape, the PLC pointer, the cached shape and prototype, the
property offset, the current and cached dictionary generations, the
named-properties pointer, and the loaded value), so giving each
piece an explicit name removes a real readability burden. The IC-miss
path uses the new 3-operand call_interp form.

Notably, PutById's old code carried a "save property offset in t4
before load_operand clobbers t0 (rax)" maneuver -- that is exactly
the kind of cross-cut the allocator handles automatically once the
offset is named. The migrated handler just keeps using `prop_offset`
across the load_operand and the store, no scratch shuffle needed.
2026-04-26 13:29:56 +02:00
Andreas Kling
8259a229d5 LibJS: Migrate validate_callee_builtin and Math builtins
Convert the validate_callee_builtin macro and the Math.abs / floor /
ceil / sqrt / exp / and ResolveThisBinding handlers to named DSL
temporaries. Math.exp uses the 3-operand call_helper form; the
allocator pins its `arg` to t1 (rcx) and `result` to t0 (rax)
automatically.
2026-04-26 13:29:56 +02:00
Andreas Kling
9e6a205575 LibJS: Migrate Div and Mod handlers to named DSL temporaries
Convert the Div and Mod handlers to named DSL temporaries. Mod is the
first migrated handler that uses divmod, with its quot/rem pinned to
rax/rdx by fixed_operands and its dividend/divisor kept off both by
the operand-vs-implicit-output interference rule.
2026-04-26 13:29:56 +02:00
Andreas Kling
a124af2abb LibJS: Migrate walk_env_chain and the binding handlers
Convert the walk_env_chain macro to take its outputs (target_env,
bind_index) as explicit parameters, then migrate every handler that
walks the environment chain: GetBinding, GetInitializedBinding,
InitializeLexicalBinding, SetLexicalBinding, and
GetCalleeAndThisFromEnvironment.

The macro's internal scratch (the EnvironmentCoordinate address, the
hops counter, the invalid-coord sentinel, and the screwed-by-eval
flag) is now declared as macro-local temps that the allocator places
without touching whatever the caller has live. Each handler picks
its own names for env / idx / binding / value, instead of remembering
that the macro happens to leave them in t3 and t2.
2026-04-26 13:29:56 +02:00
Andreas Kling
bb23e784dc LibJS: Migrate bitwise/UnaryPlus path and coerce_to_int32s macro
Convert coerce_to_int32s and bitwise_op to take operand temps as
explicit parameters, then migrate UnaryPlus.

The bitwise_op macro is the actual body of BitwiseAnd, BitwiseOr, and
BitwiseXor (which just call into it), so a single macro migration
covers all three handlers. UnaryPlus is migrated alongside since it
shares the same coerce-to-int32 conceptual path.
2026-04-26 13:29:56 +02:00
Andreas Kling
3a9661aa22 LibJS: Migrate equality handlers and their core macros
Convert the equality dispatch chain to take operand temps as explicit
parameters: equality_same_tag, double_equality_compare,
strict_equality_core, and loose_equality_core. Then migrate the
StrictlyEquals, StrictlyInequals, LooselyEquals, LooselyInequals,
JumpStrictlyEquals, JumpStrictlyInequals, JumpLooselyEquals, and
JumpLooselyInequals handlers.

These macros are tightly coupled -- strict_equality_core and
loose_equality_core both invoke equality_same_tag and reach
double_equality_compare via .double_compare, a label that crosses
macro boundaries. The existing label-uniquification rule (only
self-contained labels are renamed) keeps that contract working
without further plumbing.
2026-04-26 13:29:56 +02:00
Andreas Kling
86476e80e0 LibJS: Migrate Add/Sub/Mul, comparisons, and their core macros
Convert the heavy comparison and arithmetic macros to take their
operand temps as explicit parameters, then migrate every handler that
uses them: coerce_to_doubles, numeric_compare, numeric_compare_coerce,
boolean_result_epilogue, jump_binary_epilogue, plus the Add, Sub, Mul,
LessThan, LessThanEquals, GreaterThan, GreaterThanEquals,
JumpLessThan, JumpGreaterThan, JumpLessThanEquals, and
JumpGreaterThanEquals handlers.
2026-04-26 13:29:56 +02:00
Andreas Kling
123616d8ef LibJS: Migrate scratch-using helper macros to macro-local temps
Convert check_is_double, check_both_double, and box_double_or_int32 to
declare their internal scratch as macro-local `temp` decls instead of
hardcoding t3/t4. Each invocation now gets a freshly uniquified temp
that the allocator places independently of any other live values in
the caller, so adding a `temp` declaration around a sequence that
calls into one of these macros no longer needs to know which
positional slot the macro happened to be using.

box_double_or_int32 in particular is on the hot arithmetic path
(Add/Sub/Mul/PostfixIncrement/etc.), so this opens the door to
migrating those handlers without conflicting with the macro's
internal scratch use.
2026-04-26 13:29:56 +02:00
Andreas Kling
a19ae44977 LibJS: Migrate PostfixIncrement/PostfixDecrement/UnaryMinus/ToInt32
Convert these handlers to named DSL temporaries.
2026-04-26 13:29:56 +02:00
Andreas Kling
7fcaace5e8 LibJS: Migrate ThrowIf*/BitwiseNot/Shift* to named DSL temporaries
Convert another batch of macro-free handlers to named DSL temporaries.
The shift handlers in particular are nice exercises of the allocator's
fixed-operand support: x86's `shl`/`shr`/`sar` need the count register
to be rcx, and the allocator pins the named `count` temp accordingly
without the user having to remember it.
2026-04-26 13:29:56 +02:00
Andreas Kling
ddc5b995e5 LibJS: Migrate Increment/Decrement/Not/Return/End and friends
Convert another batch of straightforward handlers to named DSL
temporaries: Increment, Decrement, Not, GetLexicalEnvironment, Return,
and End. The migration uncovers a pleasing property of the new
pipeline: when a migrated handler invokes an un-migrated macro
(pop_inline_frame_and_resume here, which still uses positional t2/t3
/t4 internally), the macro body's positional uses appear as physical-
register defs in the flat instruction list and the allocator's
liveness analysis avoids placing live named temps in those registers.
2026-04-26 13:29:56 +02:00
Andreas Kling
9790948279 LibJS: Migrate JumpIf/JumpTrue/JumpFalse to named DSL temporaries
Convert the three boolean-conditional jump handlers to named temps,
exercising the 3-operand call_helper form so the input value and
truthy result no longer have to be pinned to t1/t0 by hand. The
allocator places `condition` in rcx (matching call_helper's input
convention) and `truthy` in rax (matching its output) automatically.
2026-04-26 13:29:56 +02:00
Andreas Kling
3d5b5602b4 LibJS: Migrate simple jump handlers to named DSL temporaries
Convert Jump, JumpNullish, and JumpUndefined to declare named
temporaries instead of pinning values to t0..t2 directly. These three
handlers are macro-free and call-free, so the migration is mechanical
and the allocator picks register assignments equivalent in cost to
the hand-written positional code.

The other Jump* handlers (JumpIf/JumpTrue/JumpFalse) drive
call_helper, which still needs the value to be in t1 (rcx) on x86 by
the existing register convention. Migrating those needs DSL syntax to
attach call_helper's input/output to named temps; until that lands
they stay on the positional form.
2026-04-26 13:29:56 +02:00
Andreas Kling
f1f7039d10 LibJS: Migrate Mov/Mov2/Mov3 handlers to named DSL temporaries
These three trivial data-movement handlers are a useful first
migration target -- they exercise the new `temp` declaration syntax
and the register allocator end-to-end without interacting with macros
or fixed-operand constraints. The allocator picks t1-equivalent
registers for the first temp (which is the smallest live range that
must survive load_operand's rax/x9 scratch use) and reuses the slot
across non-overlapping live ranges where possible.
2026-04-26 13:29:56 +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
Andreas Kling
e5d4c5cce8 LibJS: Check TDZ state in asm environment calls
GetCalleeAndThisFromEnvironment treated a binding as initialized when
its value slot was not <empty>. Declarative bindings do not encode TDZ
in that slot, though: uninitialized bindings keep a separate initialized
flag and their value starts as undefined.

That let the first slow-path TDZ failure populate the environment cache,
then a second call at the same site reused the cached coordinate and
turned the required ReferenceError into a TypeError from calling
undefined.

Check Binding.initialized in the asm fast path instead and cover the
cached second-hit case with a regression test.
2026-04-20 11:23: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
23fea4208c LibJS: Pair-load asm global Realm metadata
Place Realm's cached declarative environment next to its global object
so the asm global access fast paths can fetch the two pointers with a
paired load. These handlers never use the intervening GlobalEnvironment
pointer directly.
2026-04-14 12:37:12 +02:00
Andreas Kling
b6c7f6c0c4 LibJS: Cache Executable constants for asm Call
Mirror Executable's constants size and data pointer in adjacent fields
so the asm Call fast path can pair-load them together. The underlying
Vector layout keeps size and data apart, so a small cached raw span
lets the hot constant-copy loop fetch both pieces of metadata at once.
2026-04-14 12:37:12 +02:00
Andreas Kling
5761f6bc54 LibJS: Pair-load PropertyNameIterator index counters
Load PropertyNameIterator's indexed-property count and next index
together when stepping the fast path. Keeping the paired count live
into the named-property case also avoids reloading it before computing
the flattened index.
2026-04-14 12:37:12 +02:00
Andreas Kling
3005945b38 LibJS: Pair-load PropertyNameIterator shape metadata
Load PropertyNameIterator's cached property cache and shape snapshot
together before validating the receiver shape. The two fields already
sit adjacent in the object layout, so the fast path can fetch both
without any extra reshuffling.
2026-04-14 12:37:12 +02:00
Andreas Kling
8dcb2b95ec LibJS: Pair-load asm environment coordinates
Load EnvironmentCoordinate::hops and ::index together in the asm
environment-walk helper. The pair-load keeps the DSL explicit about
which two fields travel together and removes another scalar metadata
fetch from the fast path.
2026-04-14 12:37:12 +02:00