Drop the FixedArray allocation in ArrayDestructure, and write registers
directly when performing the ArrayDestructure.
This is needed not (just) for performance, but for correctness. The
two-phase approach of reading the iterator into a FixedArray and then
setting registers meant that throwing an exception in `next()` would
cause either all or none of the target registers to be set, rather
than setting them one-by-one as they are returned by the iterator.
This is done differently depending on the tier:
* In Ignition and Sparkplug, the ArrayDestructure builtin now writes
directly to the register frame, receiving its address as an
argument.
* In Maglev, the ArrayDestructure builtin is effectively eagerly
inlined, desugared into the appropriate iteration protocol
execution, where the abstract interpreter register values are
updated one-by one to be exception safe, and lazy continuations
handle finishing the writes to the remaining register in case
there's a deopt
* In Turbofan, I ain't doin' all that, so it's UNREACHABLE. Please
write your local Turbolev representative to get the Maglev
implementation working in the 4th tier.
TAG=agy
Conversation-Id: 7fc7c031-68c6-4ddc-bdff-6ce4e63adc59
Change-Id: Ie1d21dc20f845e8d04aea8a177cd632b155dbd32
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8035120
Reviewed-by: Marja Hölttä <marja@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#108498}
147 lines
3.4 KiB
JavaScript
147 lines
3.4 KiB
JavaScript
// Copyright 2026 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// Flags: --allow-natives-syntax --maglev --turbolev --array-destructure-bytecode
|
|
|
|
(function TestEarlyReturnCalled() {
|
|
let return_called = 0;
|
|
let iterable = {
|
|
[Symbol.iterator]() {
|
|
let count = 0;
|
|
return {
|
|
next() {
|
|
count++;
|
|
return { value: count * 10, done: false };
|
|
},
|
|
return() {
|
|
return_called++;
|
|
return { done: true };
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
function f(obj) {
|
|
let [x, y] = obj;
|
|
return x + y;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(f);
|
|
assertEquals(30, f(iterable));
|
|
assertEquals(1, return_called);
|
|
assertEquals(30, f(iterable));
|
|
assertEquals(2, return_called);
|
|
%OptimizeFunctionOnNextCall(f);
|
|
assertEquals(30, f(iterable));
|
|
assertEquals(3, return_called);
|
|
assertOptimized(f);
|
|
})();
|
|
|
|
(function TestReturnNotCalledWhenDone() {
|
|
let return_called = 0;
|
|
let iterable = {
|
|
[Symbol.iterator]() {
|
|
let count = 0;
|
|
return {
|
|
next() {
|
|
if (count < 2) {
|
|
count++;
|
|
return { value: count * 10, done: false };
|
|
}
|
|
return { value: undefined, done: true };
|
|
},
|
|
return() {
|
|
return_called++;
|
|
return { done: true };
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
function f(obj) {
|
|
let [x, y, z] = obj;
|
|
return x + y;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(f);
|
|
assertEquals(30, f(iterable));
|
|
assertEquals(0, return_called);
|
|
%OptimizeFunctionOnNextCall(f);
|
|
assertEquals(30, f(iterable));
|
|
assertEquals(0, return_called);
|
|
})();
|
|
|
|
(function TestReturnThrowingException() {
|
|
let iterable = {
|
|
[Symbol.iterator]() {
|
|
return {
|
|
next() {
|
|
return { value: 42, done: false };
|
|
},
|
|
return() {
|
|
throw new Error("Exception in return");
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
function f(obj) {
|
|
let [x] = obj;
|
|
return x;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(f);
|
|
assertThrows(() => f(iterable), Error, "Exception in return");
|
|
%OptimizeFunctionOnNextCall(f);
|
|
assertThrows(() => f(iterable), Error, "Exception in return");
|
|
})();
|
|
|
|
(function TestEmptyPatternCountZero() {
|
|
let iterator_called = 0;
|
|
let return_called = 0;
|
|
let next_called = 0;
|
|
let iterable = {
|
|
[Symbol.iterator]() {
|
|
iterator_called++;
|
|
return {
|
|
next() {
|
|
next_called++;
|
|
return { value: 10, done: false };
|
|
},
|
|
return() {
|
|
return_called++;
|
|
return { done: true };
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
function f(obj) {
|
|
let [] = obj;
|
|
return 42;
|
|
}
|
|
|
|
%PrepareFunctionForOptimization(f);
|
|
assertEquals(42, f(iterable));
|
|
assertEquals(1, iterator_called);
|
|
assertEquals(0, next_called);
|
|
assertEquals(1, return_called);
|
|
%OptimizeFunctionOnNextCall(f);
|
|
assertEquals(42, f(iterable));
|
|
assertEquals(2, iterator_called);
|
|
assertEquals(0, next_called);
|
|
assertEquals(2, return_called);
|
|
assertOptimized(f);
|
|
})();
|
|
|
|
(function TestIteratorNotExhausted() {
|
|
const map = new Map([[1, 'a'], [2, 'b'], [3, 'c']]);
|
|
const iter = map.values();
|
|
const [first] = iter;
|
|
assertEquals('a', first);
|
|
assertEquals({ value: 'b', done: false }, iter.next());
|
|
assertEquals({ value: 'c', done: false }, iter.next());
|
|
assertEquals({ value: undefined, done: true }, iter.next());
|
|
})();
|