Open
Conversation
0eb3475 to
7c1c574
Compare
75fdba5 to
527d5b9
Compare
Replace call_callback (which swallows {:js_throw,_}) with
invoke_callback_or_throw (which lets throws propagate) in:
- for_of_start: Symbol.iterator call
- for_of_next: next() call
- iterator_next: next(val) call
- collect_iterator: spread operator iteration
This ensures errors thrown by custom iterators (Symbol.iterator
function, next() method) are properly propagated to the catch
handler instead of being silently swallowed.
test262: 103 → 100 (3 tests fixed, 6 total this session)
93.7% pass rate (1,482 / 1,582)
collect_iterator (spread operator) needs call_callback's error swallowing — spread on undefined is valid JS behavior. Only for_of_start, for_of_next, and iterator_next should propagate iterator throws. test262: 100 → 98 (2 more fixed, net 8 fixed this session from both shape key sort + iterator error propagation) 93.8% pass rate (1,484 / 1,582)
1. op_append (spread in function args): use invoke_callback_or_throw for Symbol.iterator call instead of call_callback (which swallows). 2. Handle accessor descriptors on Symbol.iterator: when the iterator property is defined via Object.defineProperty with a getter, invoke the getter before calling the iterator function. 3. Same accessor handling in for_of_start. test262: 98 → 97 (1 test fixed, 9 total this session) 93.9% pass rate (1,485 / 1,582)
Per spec 7.4.1 GetIterator step 3: 'If Type(iterator) is not Object, throw a TypeError exception.' When Symbol.iterator returns null, undefined, or a primitive, throw TypeError instead of silently proceeding. test262: 97 → 96 (1 test fixed, 10 total this session) 93.9% pass rate (1,486 / 1,582)
Throw TypeError per spec 7.4.1 step 3 when Symbol.iterator returns a non-object value in for_of_start (matching the same check already in op_append for spread). test262: 96 failures (unchanged), 93.9% pass rate
Catch entries pushed during finally block execution (via gosub) now get trimmed when ret returns. This prevents stale catch entries from leaking to the outer scope and causing nested try-catch-finally to re-dispatch to the wrong handlers. Uses a catch_depth counter in Context to avoid expensive length() calls on every ret — only trims when mismatch detected. test262: 96 → 95 (S12.14_A7_T3.js fixed — nested try/finally) 93.9% pass rate (1,487 / 1,582)
Save catch_stack reference only when non-empty (common case is empty catch_stack). Compare by Erlang term identity (===) in ret for O(1) fast path. test262: 95 failures (11 fixed this session, 94.0% pass rate)
1. enumerable_string_props: resolve accessor descriptors to getter return values when copying from shape-backed objects. 2. gosub/ret catch_stack scoping: save reference only when non-empty, compare by identity for O(1) fast path. test262: 95 failures (11 fixed this session, 94.0% pass rate) 1,487 / 1,582 non-skipped tests passing
The interpreter fallback for closure callbacks works for closure variable mutation but doesn't help test262 because the failing tests have closure/global variable propagation issues where closure cell writes don't sync to global scope variables. test262: 95 failures (unchanged). Remaining failures require: - Closure/global variable propagation (~13 tests) - Compiled try-catch-finally fix (2 tests) - Array.prototype[Symbol.iterator] override (8 tests) - Closure identity in instanceof (4 tests) - Unicode surrogate comparison (4 tests) - Private fields (2 tests) - Spread with value getter timeout (2 tests) 94.0% pass rate (1,487 / 1,582)
Closure side effects in iterator callbacks (return/next) now propagate to global scope. After each callback, merge persistent globals into ctx.globals. Optimization: only merge when persistent globals is non-empty (map_size check). This avoids overhead for the common case (Preact SSR benchmark has no persistent globals during iteration). test262: 95 → 93 (2 tests fixed, 13 total this session) 94.1% pass rate (1,489 / 1,582), no perf regression.
Rest spread with getters ({...x} from objects with get accessors)
invokes getters that may modify closure-captured globals. Refresh
persistent globals after copy_data_properties to propagate these
side effects.
test262: 93 → 91 (2 more, 15 total this session)
94.2% pass rate (1,491 / 1,582)
Defensive fix: compiled path for_of_start now throws TypeError for null, undefined, and non-iterable values instead of silently returning empty iterator. test262: 91 failures (unchanged but correctness improved) 94.2% pass rate (1,491 / 1,582) Session total: 15 tests fixed (106 → 91)
Add default Symbol.iterator to Array.prototype. In for_of_start, check if the array's prototype iterator has been deleted or overridden before using the built-in fast path. - Deleted: throw TypeError per spec - Overridden: use custom iterator - Default: use built-in list iterator (fast path) Also add Heap.wrap_iterator for creating JS-visible iterator objects and Heap.get_array_proto for prototype chain lookup. test262: 91 → 87 (4 tests fixed) 94.5% pass rate (1,495 / 1,582)
test262: 87 failures (94.5%), 82s runtime
Session progress: 91 → 87 = 4 tests fixed Array.prototype[Symbol.iterator] fix resolved the iter-get-err tests. try/finally double-execution investigation ongoing. 94.5% pass rate (1,495 / 1,582)
Fix catch_js_throw and catch_js_throw_refresh_globals to use explicit try/catch around fun.() only, preventing the function- level catch from intercepting throws from run(pc+1,...) continuation. This is a correctness fix that prevents accidental catch of throws from subsequent opcode execution. Combined with the Array.prototype Symbol.iterator fix, resolves 4 additional tests. test262: 87 failures (94.5% pass rate, 1,495 / 1,582)
Separate Put.put from run(pc+1,...) in put_field's try/catch block.
Previously, the try block wrapped both the operation AND the
continuation, causing throws from subsequent opcodes to be caught
with a stale ctx and re-dispatched via throw_or_catch. This caused
the finally block to execute twice when the catch body re-threw.
Fix: wrap only Put.put in try, use :ok/{:throw,error} result tuple
for branching. The continuation run(pc+1,...) executes AFTER the
try block, so subsequent throws propagate naturally to the function-
level catch_js_throw_refresh_globals handler.
test262: 87 → 85 (2 tests fixed: completion-values-fn-finally)
94.6% pass rate (1,497 / 1,582)
Custom iterators on objects now get correct 'this' binding when called from for..of loops. Fixes non-generator custom iterators. Generator this binding is a separate issue. Also fix invoke_custom_iterator in both interpreter and compiled runtime_helpers to use invoke_with_receiver. test262: 85 failures (94.6% pass rate, 1,497 / 1,582)
Compiled path's compiled_gen_invoke doesn't preserve 'this' in generator continuations. Fix: compiled_method_callable? only returns true for func_kind=0 (regular functions), forcing generators/async to use the interpreter path which correctly passes 'this' through the context. test262: 85 → 81 (4 tests fixed: all iter-val-array-prototype) 94.9% pass rate (1,501 / 1,582)
1. Bytecode decoder: encode lone surrogates (U+D800-U+DFFF) as CESU-8 (3-byte) instead of returning error tuples. This allows JS strings with unpaired surrogates to be stored as binaries. 2. String comparison: implement utf16_compare that converts strings to UTF-16 code unit sequences for comparison. JS compares by UTF-16 code units, not UTF-8 bytes. Supplementary characters (U+10000+) encode as surrogate pairs (D800-DBFF) which sort BEFORE BMP chars in UTF-16 but AFTER in UTF-8. 3. Fast path: strings without bytes >= 0xED use direct binary comparison (UTF-8 and UTF-16 give same order for BMP-only). test262: 81 → 77 (4 tests fixed: all surrogate comparison tests) 95.1% pass rate (1,505 / 1,582)
1. instanceof: use Get.get for Symbol.hasInstance (invokes accessor
getters from defineProperty). Use catch_js_throw_refresh_globals
for globals propagation. Check {:obj,_} callable via 'call' property.
2. Unicode: CESU-8 encoding for lone surrogates + UTF-16 code unit
comparison for strings with supplementary characters.
3. Generator this: compiled_method_callable only for func_kind=0
(regular functions), generators use interpreter path.
4. Function.prototype.constructor set during globals init.
5. Array.prototype[Symbol.iterator]: check for deletion/override,
invoke_with_receiver for this binding, separate put_field try/catch.
test262: 91 → 75 (16 tests fixed this session)
95.3% pass rate (1,507 / 1,582)
When closures have accessor properties set via Object.defineProperty
(stored in ctor_statics), get_own now invokes the getter instead of
returning the raw {:accessor, ...} tuple.
test262: 75 failures (95.3% pass rate, 1,507 / 1,582)
Use Get.get instead of raw Map.get for reading 'done' and 'value' from iterator result objects. This properly invokes accessor getters set via Object.defineProperty on iterator results. test262: 75 → 73 (2 tests fixed: spread-err-*-itr-value timeout) 95.4% pass rate (1,509 / 1,582)
Values.shr matched shr(_, {:bigint, _}) before shr({:obj, _}, b),
causing TypeError to fire before ToPrimitive could throw the
correct error. Move object coercion clauses before BigInt clauses.
test262: 73 → 72 (bigint-toprimitive.js fixed)
95.4% pass rate (1,510 / 1,582)
Result: {"status":"keep","failures":72,"passing_tests":1510,"skipped_tests":912}
…ch handlers, extract for_of_start throws to throw_or_catch
Result: {"status":"keep","failures":68,"passing_tests":1514,"skipped_tests":912}
Result: {"status":"keep","failures":68,"passing_tests":1514,"skipped_tests":912}
…ith configurable:false
Result: {"status":"keep","failures":67,"passing_tests":1515,"skipped_tests":912}
…port correct constructor identity
Result: {"status":"keep","failures":66,"passing_tests":1516,"skipped_tests":912}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a second QuickJS execution backend on the BEAM.
What’s in here
:beambackend viaQuickBEAM.disasm/2mode: :beamsupport in the public APIrequire(), module loading, dynamic import, globals, handlers, and interop for the VM pathError.captureStackTraceRuntime coverage
Object,Array,Function,String,Number,BooleanMath,JSON,Date,RegExpMap,Set,WeakMap,WeakSet,SymbolPromise,async/await, generators, async generatorsProxy,ReflectTypedArray,ArrayBuffer,BigIntsuper, private fields, private methods, private accessors, static private members, brand checksValidation
QUICKBEAM_BUILD=1 MIX_ENV=test mix testMIX_ENV=test QUICKBEAM_BUILD=1 mix test test/vm/js_engine_test.exs --include js_engine --seed 0mix compile --warnings-as-errorsmix format --check-formattedmix credo --strictmix dialyzermix ex_dnazlint lib/quickbeam/*.zig lib/quickbeam/napi/*.zigbunx oxlint -c oxlint.json --type-aware --type-check priv/ts/bunx jscpd lib/quickbeam/*.zig priv/ts/*.ts --min-tokens 50 --threshold 0Current local result:
2363 tests, 0 failures, 1 skipped, 54 excluded