Commit Graph

18 Commits

Author SHA1 Message Date
Andreas Kling
fa78df7ab7 LibJS/Rust: Call perform_needed_unwinds before Throw instructions
Add missing perform_needed_unwinds() calls before Throw instructions
in four places:
- Await continuation throw path
- Yield* throw_value_block
- Yield* iterator missing throw method
- Invalid left-hand side in assignment helper

This matches the C++ pipeline which calls perform_needed_unwinds<Throw>
before every Throw to restore lexical environments when throwing out of
scopes like with statements.
2026-03-01 21:20:54 +01:00
Andreas Kling
4f503ef243 LibJS/Rust: Use correct capacity for CreateVariableEnvironment
Pass the fully computed var_environment_bindings_count from the SFD
metadata to the codegen, instead of using the raw
non_local_var_count_for_parameter_expressions. The full count includes
additional bindings from Annex B function hoisting and strict-mode
lexical declarations that share the var environment.
2026-03-01 21:20:54 +01:00
Andreas Kling
e88932f75d LibJS/Rust: Always allocate fresh register for postfix update result
Match the C++ pipeline behavior where PostfixIncrement/PostfixDecrement
always writes to a freshly allocated register. The Rust pipeline was
using the caller's preferred_dst, producing one fewer Mov instruction
but causing bytecode mismatches.
2026-03-01 21:20:54 +01:00
Andreas Kling
1c40257c7f LibJS/Rust: Restore unwind handler when trampolining through finally
When break/continue trampolines through nested finally blocks, we need
to restore the unwind handler to the level that was active before each
finally context was pushed. Without this, trampoline blocks created for
inner finally dispatch incorrectly inherited the innermost exception
handler, causing exception handler range mismatches with C++.

Store the current_unwind_handler in each FinallyContext at push time,
and restore it in emit_trampoline_through_finally when popping through.
2026-03-01 21:20:54 +01:00
Andreas Kling
8f38e526e7 LibJS/Rust: Fix several bytecode mismatches with the C++ pipeline
- Don't emit dead code after Throw for UsingDeclaration in for-of
  LHS assignment. Guard loop body generation with
  is_current_block_terminated() in both for-in and for-of.

- Add LeaveLexicalEnvironment boundary tracking to for-loop
  per-iteration environment management, so that perform_needed_unwinds
  correctly emits SetLexicalEnvironment before Throw instructions
  inside the loop body.
2026-03-01 21:20:54 +01:00
Andreas Kling
d0b9905de1 LibJS/Rust: Use GetLengthWithThis for super.length property access
The C++ pipeline has an optimization that uses the GetLengthWithThis
instruction instead of GetByIdWithThis when accessing the "length"
property. Add the same optimization to the Rust pipeline by
introducing an emit_get_by_id_with_this helper that checks for the
"length" property name and emits the optimized instruction.

Also update emit_get_by_value_with_this to use GetLengthWithThis
when the computed property is a constant "length" string.
2026-03-01 21:20:54 +01:00
Andreas Kling
c365806041 LibJS/Rust: Fix evaluation order for super in tagged templates
Per spec, computed property key expressions should be evaluated
before calling ResolveSuperBase. Fix the Rust codegen for tagged
template literals with super member expressions to match the C++
pipeline's correct evaluation order.
2026-03-01 21:20:54 +01:00
Andreas Kling
56603319b4 LibJS/Rust: Fix evaluation order in delete super[key]
Per spec, the property key expression should be evaluated before
calling ResolveSuperBase. Fix the Rust codegen to match the C++
pipeline's correct evaluation order.
2026-03-01 21:20:54 +01:00
Andreas Kling
543bd7059a LibJS/Rust: Don't emit dead code after Throw for invalid LHS
Match the C++ pipeline behavior by not creating a dead basic block
after emitting a Throw instruction in emit_invalid_lhs_error.
2026-03-01 21:20:54 +01:00
Andreas Kling
18c40a1328 LibJS/Rust: Fix has_parameter_expressions and TDZ checks for arguments
Fix two bugs in the Rust bytecode codegen:

1. has_parameter_expressions incorrectly treated any destructuring
   parameter as a "parameter expression", when it should only do so
   for patterns that contain expressions (defaults or computed keys).
   This caused an unnecessary CreateLexicalEnvironment for simple
   destructuring like `function f({a, b}) {}`. The same bug existed
   in both codegen.rs and lib.rs (SFD metadata computation).

2. emit_set_variable used is_local_lexically_declared(index) for
   argument locals, but that function indexes into the local_variables
   array using the argument's index, checking the wrong variable.
   This caused spurious ThrowIfTDZ instructions when assigning to
   function arguments that happened to share an index with an
   uninitialized let/const variable.
2026-03-01 21:20:54 +01:00
Andreas Kling
00ffc340bc LibJS: Wrap CompiledRegex in Rc to allow AST cloning
CompiledRegex held an FFI handle with unique ownership and panicked
on clone. This caused a crash when a class field initializer contained
a regex literal, since the codegen wraps field initializers in a
synthetic function body by cloning the expression.

Wrapping CompiledRegex in Rc makes the clone a cheap refcount bump.
The take() semantics are preserved: the first codegen path to call
take() gets the handle, and Drop frees it if nobody took it.
2026-02-25 21:54:30 +01:00
Andreas Kling
f19d00ca9e LibJS: Memoize failed arrow function attempts in Rust parser
Cache failed arrow function attempts by token offset. Once we
determine that '(' at offset N is not the start of an arrow
function, skip re-attempting at the same offset.

Without memoization, nested expressions like (a=(b=(c=(d=0))))
cause exponential work: each failed arrow attempt at an outer '('
re-parses all inner '(' positions during grouping expression
re-parse, and each inner position triggers its own arrow
attempts. With n nesting levels, the innermost position is
processed O(2^n) times.

The C++ parser already has this optimization (via the
try_parse_arrow_function_expression_failed_at_position()
memoization cache).
2026-02-24 18:42:13 +01:00
xnacly
48e906edfd Meta: Add 'cargo clippy -- -D clippy::all' to lint-ci.sh 2026-02-24 16:35:51 +01:00
xnacly
bbb6121df4 LibJs/Rust: Migrate to edition 2024 2026-02-24 16:35:51 +01:00
xnacly
e897b77e83 LibJS/Rust: Cargo fmt on all source files 2026-02-24 16:35:51 +01:00
xnacly
809f81704c LibJS/Rust: Clean build script up
Small changes but many of them:

- all codegen now directly writes into the target file instead of
  creating intermediate Strings via the Write trait
- all unwraps are now a combination of Results and ?
- field_type_info now returns a structure instead of a tuple.
- rebuilding now no longer appends the same code again, but truncates
  before codegen
2026-02-24 16:35:51 +01:00
Andreas Kling
81cb230526 Flatpak: Add Rust toolchain and vendored cargo dependencies
Add the rust-stable SDK extension, pre-download Corrosion and all crate
dependencies, and set up cargo vendoring so the Flatpak build can
compile Rust code without network access during the build phase.
2026-02-24 09:39:42 +01:00
Andreas Kling
6cdfbd01a6 LibJS: Add alternative source-to-bytecode pipeline in Rust
Implement a complete Rust reimplementation of the LibJS frontend:
lexer, parser, AST, scope collector, and bytecode code generator.

The Rust pipeline is built via Corrosion (CMake-Cargo bridge) and
linked into LibJS as a static library. It is gated behind a build
flag (ENABLE_RUST, on by default except on Windows) and two runtime
environment variables:

- LIBJS_CPP: Use the C++ pipeline instead of Rust
- LIBJS_COMPARE_PIPELINES=1: Run both pipelines in lockstep,
  aborting on any difference in AST or bytecode generated.

The C++ side communicates with Rust through a C FFI layer
(RustIntegration.cpp/h) that passes source text to Rust and receives
a populated Executable back via a BytecodeFactory interface.
2026-02-24 09:39:42 +01:00