The exception handler table is sorted by start_offset, so use
binary_search instead of a linear scan. This matches the pattern
already used by source_range_at() in the same file.
This comment referenced the old runtime unwind context stack behavior
where a flag had to be set to prevent yield from going through a
finally statement. That mechanism was removed and finally is now
handled purely through explicit completion records in bytecode.
After replacing the runtime unwind context stack with explicit
completion records for try/finally dispatch, the distinction between
"handler" (catch) and "finalizer" (finally) in the exception handler
table is no longer meaningful at runtime.
handle_exception() checked handler first, then finalizer, but they
did the exact same thing (set the PC). When both were present, the
finalizer was dead code.
Collapse both fields into a single handler_offset (now non-optional,
since an entry always has a target), remove the finalizer concept
from BasicBlock, UnwindContext, and ExceptionHandlers, and simplify
handle_exception() to a direct assignment.
After removing the unwind context stack, ExecutionContextRareData only
held two GC::Ptr fields — both trivially destructible. The indirection
cost more than it saved: a GC cell allocation per EC, an extra pointer
chase on every source range lookup, and unnecessary complexity.
Replace the rare data cell with two inline fields on ExecutionContext:
cached_source_range and context_owner.
The runtime unwind context stack was pushed by EnterUnwindContext
and popped by LeaveUnwindContext. With both opcodes removed, it is
no longer read or written by anything.
Remove UnwindInfo, the unwind_contexts vector, its GC visit loop,
its copy in ExecutionContext::copy(), and the VERIFY assertions that
referenced it in handle_exception() and catch_exception().
LeaveUnwindContext popped the runtime unwind context stack. With the
stack being removed, all emission sites become dead code. Remove the
opcode and all its emissions.
EnterUnwindContext pushed an UnwindInfo and jumped to entry_point.
Without the stack push, it's just a Jump. Replace the single emission
site with a Jump and remove the opcode entirely.
Replace the saved_lexical_environments stack in ExecutionContextRareData
with explicit register-based environment tracking. Environments are now
stored in registers and restored via SetLexicalEnvironment, making the
environment flow visible in bytecode.
Key changes:
- Add GetLexicalEnvironment and SetLexicalEnvironment opcodes
- CreateLexicalEnvironment takes explicit parent and dst operands
- EnterObjectEnvironment stores new environment in a dst register
- NewClass takes an explicit class_environment operand
- Remove LeaveLexicalEnvironment opcode (instead: SetLexicalEnvironment)
- Remove saved_lexical_environments from ExecutionContextRareData
- Use a reserved register for the saved lexical environment to avoid
dominance issues with lazily-emitted GetLexicalEnvironment
Each finally scope gets two registers (completion_type and
completion_value) that form an explicit completion record. Every path
into the finally body sets these before jumping, and a dispatch chain
after the finally body routes to the correct continuation.
This replaces the old implicit protocol that relied on the exception
register, a saved_return_value register, and a scheduled_jump field
on ExecutionContext, allowing us to remove:
- 5 opcodes (ContinuePendingUnwind, ScheduleJump, LeaveFinally,
RestoreScheduledJump, PrepareYield)
- 1 reserved register (saved_return_value)
- 2 ExecutionContext fields (scheduled_jump, previously_scheduled_jumps)
The spec for PropertyDefinitionEvaluation requires that when evaluating
a property definition with a computed key (PropertyDefinition :
PropertyName : AssignmentExpression), the PropertyName is fully
evaluated (including ToPropertyKey, which calls ToPrimitive) before the
value's AssignmentExpression is evaluated.
Our bytecode compiler was evaluating the key expression first, then
the value expression, and only performing ToPropertyKey later inside
PutByValue at runtime. This meant user-observable side effects from
ToPrimitive (such as calling Symbol.toPrimitive or toString on the key
object) would fire after the value expression had already been
evaluated.
Fix this by using a new ToPrimitiveWithStringHint instruction that
performs ToPrimitive with string hint(!), and emitting it between the
key and value evaluations in ObjectExpression codegen.
After ToPrimitive, the key is already a primitive, so the subsequent
ToPropertyKey inside PutByValue becomes a no-op from the perspective
of user-observable side
effects.
Also update an existing test that was asserting the old (incorrect)
evaluation order, and add comprehensive new tests for computed property
key evaluation order.
When the rest element in an object destructuring assignment targets a
MemberExpression (e.g. `({a, ...t.rest} = obj)`), we were incorrectly
storing the original source object to the reference instead of the
rest object produced by CopyObjectExcludingProperties.
For example, `({a, ...t.rest} = {a:1, b:2, c:3})` would set t.rest
to `{a:1, b:2, c:3}` instead of the correct `{b:2, c:3}`.
The fix is to pass the result of CopyObjectExcludingProperties
to emit_store_to_reference instead of the original RHS.
The FIXME comments suggested that ToPropertyKey was called at the wrong
time for computed super property access. However, extensive testing
shows that both Ladybird and V8 implement the correct ordering according
to the ECMA262 specification.
Remove the outdated FIXME comments and add comprehensive test coverage
for super property computed keys with Symbol.toPrimitive to prevent
regressions.
Route tagged template identifier lookup through
GetCalleeAndThisFromEnvironment only when the identifier is non-local.
Keep local and global identifiers on Identifier::generate_bytecode so
TDZ checks and ordinary undefined-this behavior stay intact.
Expand runtime coverage with a tagged-template TDZ regression case,
sequential with-binding calls, and getter-returned tag functions.
For non-Reference calls (e.g. (0, fn)(), (cond ? fn : x)()), the
codegen correctly passes undefined as the thisValue, matching step 2b
of EvaluateCall in the spec. OrdinaryCallBindThis then coerces
undefined to the global object in sloppy mode at runtime. Replace the
stale FIXME with a clarifying comment.
Also add comprehensive tests for this-value behavior in non-Reference
call patterns (comma, ternary, logical, assignment, nullish coalescing)
in both sloppy and strict mode.
Both SetFunctionName and MakeConstructor are already performed by
ECMAScriptFunctionObject::initialize() when the object is created
via create_from_function_node:
- SetFunctionName: The name is passed to SharedFunctionInstanceData,
and initialize() creates the "name" property from it.
- MakeConstructor: has_constructor() returns true for all normal
non-arrow functions, m_constructor_kind defaults to Base, and
m_may_need_lazy_prototype_instantiation handles the prototype
property creation lazily.
The FIXME claimed that IsAnonymousFunctionDefinition + NamedEvaluation
was missing for simple assignment expressions like `x = function() {}`.
However, the code directly below the FIXME already implements this
correctly via emit_named_evaluation_if_anonymous_function.
The GetGlobal bytecode optimization bypasses the normal environment
record lookup for global variable access. When a global property is
an accessor (getter), the receiver passed to the getter must be the
global object, not undefined.
The spec's Get(O, P) abstract operation is defined as O.[[Get]](P, O),
meaning the object itself is always the receiver. The global
environment's GetBindingValue delegates to its object record's
GetBindingValue, which calls Get(bindingObject, N), so the receiver
should be the binding object (the global object).
Both the cached path (calling the getter directly from get_direct)
and the non-cached path (calling internal_get) were passing
js_undefined() as the receiver. This caused strict-mode getters on
global properties to receive undefined as their this-value instead
of globalThis.
Notably, the corresponding SetGlobal paths already correctly passed
&binding_object for setter calls.
The i32 multiplication fast path in Mul::execute_impl was producing
+0 instead of -0 when one operand was negative and the other was
zero (e.g. `var a = -1, b = 0; a * b`).
This happened because i32 can't represent -0, so `Value(0)` was
always positive zero. We now fall through to the double path when
the i32 result is zero, which correctly handles the sign.
Also add comprehensive multiplication tests covering negative zero,
basic arithmetic, large integers, type coercion, NaN, and Infinity.
The parser previously detected direct eval() calls at the end of
parse_expression(), by checking if the final expression was a
CallExpression with "eval" as the callee. This missed cases where
eval() appeared as a subexpression, e.g. `eval(code) | 0`, since
the final expression would be a BinaryExpression, not a
CallExpression.
Move the detection into parse_call_expression() where the
CallExpression is actually created. This ensures we always set the
contains_direct_call_to_eval flag regardless of surrounding
operators, so local variables are correctly placed in the
declarative environment where eval'd code can find them.
Otherwise we'll fail to recognise that the identifier token is invalid
(e.g. a keyword such as null) in a binding pattern and that it may not
be a binding pattern after all, but an object expression.
Fixes some scripts on Discord failing to parse.
Intermediate classes in the initialize() chain set up prototypes and
define properties. Forgetting to call Base::initialize() in any
override would silently skip that setup.
LibJS+DevTools: Implement console.trace() with source locations
- Add Console::TraceFrame struct with source location data
- Implement Console::trace() to gather stack information
- Add WebView::StackFrame and ConsoleTrace for IPC
- Implement DevToolsConsoleClient::printer() for traces
- Update FrameActor to format traces for DevTools
- Update WorkerDebugConsoleClient trace handling
- Update ReplConsoleClient to format trace output
As JS::Value is marked "trivial" without actually being trivial, make
the one user that would lead to garbage JS::Value entries provide a
default value instead.
There is no need to concat empty string literals when building template
literals. Now strings will only be concatenated if they need to be.
To handle the edge case where the first segment is not a string
literal, a new `ToString` op code has been added to ensure the value is
a string concatenating more strings.
In addition, basic const folding is now supported for template literal
constants (templates with no interpolated values), which is commonly
used for multi-line string constants.
This improves and expands the ability to do dead code elimination on
conditions which are always truthy or falsey.
The following cases are now optimized:
* `if (true){}` -> Only emit `if` block, ignore `else`
* `if (false){}` -> Only emit `else if`/`else` block
* `while (false){}` -> Ignore `while` loop entirely
* `for (x;false;){}` -> Only emit `x` (if it exists), skip `for` block
* Ternary -> Directly return left/right hand side if condition is const
Add a clang plugin check that flags GC::Cell subclasses (and their
base classes within the Cell hierarchy) that have destructors with
non-trivial bodies. Such logic should use Cell::finalize() instead.
Add GC_ALLOW_CELL_DESTRUCTOR annotation macro for opting out in
exceptional cases (currently only JS::Object).
This prevents us from accidentally adding code in destructors that
runs after something we're pointing to may have been destroyed.
(This could become a problem when the garbage collector sweeps
objects in an unfortunate order.)
This new check uncovered a handful of bugs which are then also fixed
in this commit. :^)
This reverts commit 3f75cf270a.
This change was meant to be editorial, but it caused a regression. See:
3ffa677
A separate test was not added here to catch this regression, because it
affects non-ISO-8601 calendars, which we do not yet implement.
Previously, deleting a property from a cacheable dictionary would
transition it to an uncacheable dictionary, defeating inline caching
for all subsequent property accesses on that object.
This was particularly impactful for the global object, which becomes a
dictionary due to its large number of properties. Test frameworks that
delete global properties (like test-js) would cause the global object
to become uncacheable, preventing global variable caching from working.
The fix is simple: instead of transitioning to uncacheable, we continue
using the existing remove_property_without_transition() which already
remaps all property offsets greater than the deleted offset. Combined
with the dictionary_generation counter (which is already incremented),
this ensures caches are properly invalidated while keeping the shape
cacheable for future accesses.
Previously, when parsing a named function expression like
`Oops = function Oops() { Oops }`, the parser set a group-level flag
`might_be_variable_in_lexical_scope_in_named_function_assignment` that
propagated to the parent scope. This incorrectly prevented ALL `Oops`
identifiers from being marked as global, including those outside the
function expression.
Fix this by marking identifiers individually using
`set_is_inside_scope_with_eval()` only for identifiers inside the
function scope. This allows identifiers outside the function expression
to correctly use GetGlobal/SetGlobal while identifiers inside still
use GetBinding (since they may refer to the function's name binding).
Previously, when a nested function contained eval(), the parser would
mark all identifiers in parent functions as "inside scope with eval".
This prevented those identifiers from being marked as global, forcing
them to use GetBinding instead of GetGlobal.
However, eval() can only inject variables into its containing function's
scope, not into parent function scopes. So a parent function's reference
to a global like `Number` should still be able to use GetGlobal even if
a nested function contains eval().
This change adds a new flag `m_eval_in_current_function` that propagates
through block scopes within the same function but stops at function
boundaries. This flag is used for marking identifiers, while the
existing `m_screwed_by_eval_in_scope_chain` continues to propagate
across functions for local variable deoptimization (since eval can
access closure variables).
Before: `new Number(42)` in outer() with eval in inner() -> GetBinding
After: `new Number(42)` in outer() with eval in inner() -> GetGlobal
Previously, when direct eval() was called, we would mark the entire
environment chain as "permanently screwed by eval", disabling variable
access caching all the way up to the global scope.
This was overly conservative. According to the ECMAScript specification,
a sloppy direct eval() can only inject var declarations into its
containing function's variable environment - it cannot inject variables
into parent function scopes.
This patch makes two changes:
1. Stop propagating the "screwed by eval" flag at function boundaries.
When set_permanently_screwed_by_eval() hits a FunctionEnvironment or
GlobalEnvironment, it no longer continues to outer environments.
2. Check each environment during cache lookup traversal. If any
environment in the path is marked as screwed, we bail to the slow
path. This catches the case where we're inside a function with eval
and have a cached coordinate pointing to an outer scope.
The second change is necessary because eval can create local variables
that shadow outer bindings. When looking up a variable from inside a
function that called eval, we can't trust cached coordinates that point
to outer scopes, since eval may have created a closer binding.
This improves performance for code with nested functions where an inner
function uses eval but parent functions perform many variable accesses.
The parent functions can now use cached environment coordinates.
All 29 new tests verify behavior matches V8.
Bytecode source map entries are always added in order of increasing
bytecode offset, and lookups only happen during error handling (a cold
path). This makes a sorted vector with binary search a better fit than
a hash map.
This change reduces memory overhead and speeds up bytecode generation
by avoiding hash table operations during compilation. Lookups remain
fast via binary search, and since source_range_at() is only called
when generating stack traces, the O(log n) lookup is acceptable.
Add VERIFY guards to catch bytecode programs that exceed u32::max bytes
and narrow the bytecode_offset parameter in add_source_map_entry() to
u32. This is a preparatory change for optimizing source map storage.
Cache necessary data during parsing to eliminate HashMap operations
in SharedFunctionInstanceData construction.
Before: 2 HashMap copies + N HashMap insertions with hash computations
After: Direct vector iteration with no hashing
Build FunctionScopeData for function scopes in the parser containing:
- functions_to_initialize: deduplicated var-scoped function decls
- vars_to_initialize: var decls with is_parameter/is_function_name
- var_names: HashTable for AnnexB extension checks
- Pre-computed counts for environment size calculation
- Flags for "arguments" handling
Add ScopeNode::ensure_function_scope_data() to compute the data
on-demand for edge cases that don't go through normal parser flow
(synthetic class constructors, static initializers, module wrappers).
Use this cached data directly in SFID with zero HashMap operations.
AK/Random is already the same as SecureRandom. See PR for more details.
ProcessPrng is used on Windows for compatibility w/ sandboxing measures
See e.g. https://crbug.com/40277768
Logical expressions like `true || false` are now constant folded. This
also allows for dead code elimination if we know the right-hand side of
the expression will never be evaluated (such as `false && f()` or
`true || f()`).
In the test suites, the values are now being constant folded at compile
time. To ensure that the actual evaluation logic is being called
properly, I had to duplicate the tests and call them via a function so
the compiler would not optimize the evaluation logic away.
This also demotes `NaN` and `Infinity` identifiers to `nan` and
`inf` double literals, which will further help with const folding.
This is a common way to convert a value to a boolean. Instead of doing
a boolean conversion and 2 negate operations, we replace this with a
single `ToBoolean` op code.
This adds a new `test-js-bytecode` target which ensures that codegen
changes do not impact emitted bytecode IR, or if it does, it is known
and the tests are updated accordingly.
Similar to the LibWeb tests, the tests are stored in the following
format:
* `Libraries/LibJS/Bytecode/Tests/input`: Input `.js` files
* `Libraries/LibJS/Bytecode/Tests/expected`: Expected `.txt` bytecode
* `Libraries/LibJS/Bytecode/Tests/output`: Emitted `.txt` bytecode
The `output` dir is git-ignored, but stores the output so you can diff
and inspect failed tests more easily.
There is only one test so far, which is a baseline test that should not
change dramatically unless we change the bytecode output format.
Numeric string keys like "0" are converted to numeric property keys and
stored in indexed storage rather than shape-based storage. The shape
caching optimization introduced in 505fe0a977 didn't account for this,
causing properties with numeric keys to be lost on subsequent calls.
The fix excludes object literals with numeric string keys from the
shape caching fast path by checking if any key would become a numeric
property index.
This ensures that we are explicitly declaring the allocator to use when
allocating a cell(-inheriting) type, instead of silently falling back
to size-based allocation.
Since this is done in allocate_cell, this will only be detected for
types that are actively being allocated. However, since that means
they're _not_ being allocated, that means it's safe to not declare
an allocator to use for those. For example, the base TypedArray<T>,
which is never directly allocated and only the defined specializations
are ever allocated.
Every function call allocates an ExecutionContext with a trailing array
of Values for registers, locals, constants, and arguments. Previously,
the constructor would initialize all slots to js_special_empty_value(),
but constant slots were then immediately overwritten by the interpreter
copying in values from the Executable before execution began.
To eliminate this redundant initialization, we rearrange the layout from
[registers | constants | locals] to [registers | locals | constants].
This groups registers and locals together at the front, allowing us to
initialize only those slots while leaving constant slots uninitialized
until they're populated with their actual values.
This reduces the per-call initialization cost from O(registers + locals
+ constants) to O(registers + locals).
Also tightens up the types involved (size_t -> u32) and adds VERIFYs to
guard against overflow when computing the combined slot counts, and to
ensure the total fits within the 29-bit operand index field.