Commit 7bcd9265 authored by Daniel Clifford's avatar Daniel Clifford Committed by Commit Bot

Implement Array.prototype.reduceRight inlining in TF

Bug: v8:1956
Change-Id: I785986ed20e60e21966abe82a1567d239b22b416
Reviewed-on: https://chromium-review.googlesource.com/840026Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Daniel Clifford <danno@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50283}
parent 699144a2
......@@ -2212,6 +2212,36 @@ TF_BUILTIN(ArrayReduceRightLoopContinuation, ArrayBuiltinCodeStubAssembler) {
MissingPropertyMode::kSkip, ForEachDirection::kReverse);
}
TF_BUILTIN(ArrayReduceRightLoopEagerDeoptContinuation,
ArrayBuiltinCodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext);
Node* receiver = Parameter(Descriptor::kReceiver);
Node* callbackfn = Parameter(Descriptor::kCallbackFn);
Node* accumulator = Parameter(Descriptor::kAccumulator);
Node* initial_k = Parameter(Descriptor::kInitialK);
Node* len = Parameter(Descriptor::kLength);
Callable stub(Builtins::CallableFor(
isolate(), Builtins::kArrayReduceRightLoopContinuation));
Return(CallStub(stub, context, receiver, callbackfn, UndefinedConstant(),
accumulator, receiver, initial_k, len, UndefinedConstant()));
}
TF_BUILTIN(ArrayReduceRightLoopLazyDeoptContinuation,
ArrayBuiltinCodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext);
Node* receiver = Parameter(Descriptor::kReceiver);
Node* callbackfn = Parameter(Descriptor::kCallbackFn);
Node* initial_k = Parameter(Descriptor::kInitialK);
Node* len = Parameter(Descriptor::kLength);
Node* result = Parameter(Descriptor::kResult);
Callable stub(Builtins::CallableFor(
isolate(), Builtins::kArrayReduceRightLoopContinuation));
Return(CallStub(stub, context, receiver, callbackfn, UndefinedConstant(),
result, receiver, initial_k, len, UndefinedConstant()));
}
TF_BUILTIN(ArrayReduceRight, ArrayBuiltinCodeStubAssembler) {
Node* argc =
ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount));
......
......@@ -311,6 +311,10 @@ namespace internal {
/* ES6 #sec-array.prototype.reduceRight */ \
TFS(ArrayReduceRightLoopContinuation, kReceiver, kCallbackFn, kThisArg, \
kAccumulator, kObject, kInitialK, kLength, kTo) \
TFJ(ArrayReduceRightLoopEagerDeoptContinuation, 4, kCallbackFn, kInitialK, \
kLength, kAccumulator) \
TFJ(ArrayReduceRightLoopLazyDeoptContinuation, 4, kCallbackFn, kInitialK, \
kLength, kResult) \
TFJ(ArrayReduceRight, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES6 #sec-array.prototype.entries */ \
TFJ(ArrayPrototypeEntries, 0) \
......
......@@ -185,6 +185,8 @@ Callable Builtins::CallableFor(Isolate* isolate, Name name) {
case kArrayMapLoopLazyDeoptContinuation:
case kArrayReduceLoopEagerDeoptContinuation:
case kArrayReduceLoopLazyDeoptContinuation:
case kArrayReduceRightLoopEagerDeoptContinuation:
case kArrayReduceRightLoopLazyDeoptContinuation:
case kConsoleAssert:
return Callable(code, BuiltinDescriptor(isolate));
default:
......@@ -239,6 +241,8 @@ bool Builtins::IsLazy(int index) {
case kArrayFilterLoopLazyDeoptContinuation: // https://crbug.com/v8/6786.
case kArrayReduceLoopEagerDeoptContinuation: // https://crbug.com/v8/6786.
case kArrayReduceLoopLazyDeoptContinuation: // https://crbug.com/v8/6786.
case kArrayReduceRightLoopEagerDeoptContinuation:
case kArrayReduceRightLoopLazyDeoptContinuation:
case kCheckOptimizationMarker:
case kCompileLazy:
case kDeserializeLazy:
......
......@@ -1206,6 +1206,236 @@ Reduction JSCallReducer::ReduceArrayReduce(Handle<JSFunction> function,
return Replace(curloop);
} // namespace compiler
Reduction JSCallReducer::ReduceArrayReduceRight(Handle<JSFunction> function,
Node* node) {
if (!FLAG_turbo_inline_array_builtins) return NoChange();
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* context = NodeProperties::GetContextInput(node);
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
// Try to determine the {receiver} map.
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* fncallback = node->op()->ValueInputCount() > 2
? NodeProperties::GetValueInput(node, 2)
: jsgraph()->UndefinedConstant();
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
if (result != NodeProperties::kReliableReceiverMaps) {
return NoChange();
}
if (receiver_maps.size() == 0) return NoChange();
ElementsKind kind = IsDoubleElementsKind(receiver_maps[0]->elements_kind())
? PACKED_DOUBLE_ELEMENTS
: PACKED_ELEMENTS;
for (Handle<Map> receiver_map : receiver_maps) {
ElementsKind next_kind = receiver_map->elements_kind();
if (!CanInlineArrayIteratingBuiltin(receiver_map)) {
return NoChange();
}
if (!IsFastElementsKind(next_kind) ||
(IsDoubleElementsKind(next_kind) && IsHoleyElementsKind(next_kind))) {
return NoChange();
}
if (IsDoubleElementsKind(kind) != IsDoubleElementsKind(next_kind)) {
return NoChange();
}
if (IsHoleyElementsKind(next_kind)) {
kind = HOLEY_ELEMENTS;
}
}
// Install code dependencies on the {receiver} prototype maps and the
// global array protector cell.
dependencies()->AssumePropertyCell(factory()->no_elements_protector());
Node* original_length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayLength(PACKED_ELEMENTS)),
receiver, effect, control);
Node* k = graph()->NewNode(simplified()->NumberSubtract(), original_length,
jsgraph()->Constant(1));
std::vector<Node*> checkpoint_params({receiver, fncallback, k,
original_length,
jsgraph()->UndefinedConstant()});
const int stack_parameters = static_cast<int>(checkpoint_params.size());
// Check whether the given callback function is callable. Note that this has
// to happen outside the loop to make sure we also throw on empty arrays.
Node* check_frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), function, Builtins::kArrayReduceRightLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters - 1,
outer_frame_state, ContinuationFrameStateMode::LAZY);
Node* check_fail = nullptr;
Node* check_throw = nullptr;
WireInCallbackIsCallableCheck(fncallback, context, check_frame_state, effect,
&control, &check_fail, &check_throw);
// Set initial accumulator value
Node* cur = nullptr;
Node* initial_element_check_fail = nullptr;
Node* initial_element_check_throw = nullptr;
if (node->op()->ValueInputCount() > 3) {
cur = NodeProperties::GetValueInput(node, 3);
} else {
Node* check = graph()->NewNode(simplified()->NumberEqual(), original_length,
jsgraph()->SmiConstant(0));
Node* check_branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
initial_element_check_fail =
graph()->NewNode(common()->IfTrue(), check_branch);
initial_element_check_throw = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowTypeError, 2),
jsgraph()->Constant(MessageTemplate::kReduceNoInitial), fncallback,
context, check_frame_state, effect, initial_element_check_fail);
control = graph()->NewNode(common()->IfFalse(), check_branch);
cur = SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback());
k = graph()->NewNode(simplified()->NumberSubtract(), k,
jsgraph()->Constant(1));
}
// Start the loop.
Node* loop = control = graph()->NewNode(common()->Loop(2), control, control);
Node* eloop = effect =
graph()->NewNode(common()->EffectPhi(2), effect, effect, loop);
Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop);
NodeProperties::MergeControlToEnd(graph(), common(), terminate);
Node* kloop = k = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2), k, k, loop);
Node* curloop = cur = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2), cur, cur, loop);
checkpoint_params[2] = k;
checkpoint_params[4] = curloop;
control = loop;
effect = eloop;
Node* continue_test = graph()->NewNode(simplified()->NumberLessThanOrEqual(),
jsgraph()->Constant(0), k);
Node* continue_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue),
continue_test, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), continue_branch);
Node* if_false = graph()->NewNode(common()->IfFalse(), continue_branch);
control = if_true;
Node* frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), function,
Builtins::kArrayReduceRightLoopEagerDeoptContinuation, node->InputAt(0),
context, &checkpoint_params[0], stack_parameters, outer_frame_state,
ContinuationFrameStateMode::EAGER);
effect =
graph()->NewNode(common()->Checkpoint(), frame_state, effect, control);
// Make sure the map hasn't changed during the iteration
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone, receiver_maps), receiver,
effect, control);
Node* element =
SafeLoadElement(kind, receiver, control, &effect, &k, p.feedback());
Node* next_k = graph()->NewNode(simplified()->NumberSubtract(), k,
jsgraph()->Constant(1));
checkpoint_params[2] = next_k;
Node* hole_true = nullptr;
Node* hole_false = nullptr;
Node* effect_true = effect;
if (IsHoleyElementsKind(kind)) {
// Holey elements kind require a hole check and skipping of the element in
// the case of a hole.
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), element,
jsgraph()->TheHoleConstant());
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
hole_true = graph()->NewNode(common()->IfTrue(), branch);
hole_false = graph()->NewNode(common()->IfFalse(), branch);
control = hole_false;
// The contract is that we don't leak "the hole" into "user JavaScript",
// so we must rename the {element} here to explicitly exclude "the hole"
// from the type of {element}.
element = graph()->NewNode(common()->TypeGuard(Type::NonInternal()),
element, control);
}
frame_state = CreateJavaScriptBuiltinContinuationFrameState(
jsgraph(), function, Builtins::kArrayReduceRightLoopLazyDeoptContinuation,
node->InputAt(0), context, &checkpoint_params[0], stack_parameters - 1,
outer_frame_state, ContinuationFrameStateMode::LAZY);
Node* next_cur = control = effect =
graph()->NewNode(javascript()->Call(6, p.frequency()), fncallback,
jsgraph()->UndefinedConstant(), cur, element, k,
receiver, context, frame_state, effect, control);
// Rewire potential exception edges.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
RewirePostCallbackExceptionEdges(check_throw, on_exception, effect,
&check_fail, &control);
}
if (IsHoleyElementsKind(kind)) {
Node* after_call_control = control;
Node* after_call_effect = effect;
control = hole_true;
effect = effect_true;
control = graph()->NewNode(common()->Merge(2), control, after_call_control);
effect = graph()->NewNode(common()->EffectPhi(2), effect, after_call_effect,
control);
}
k = next_k;
cur = next_cur;
loop->ReplaceInput(1, control);
kloop->ReplaceInput(1, k);
curloop->ReplaceInput(1, cur);
eloop->ReplaceInput(1, effect);
control = if_false;
effect = eloop;
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
// completion to the graph end.
Node* throw_node =
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
if (node->op()->ValueInputCount() <= 3) {
// Wire up the branch for the case when an array is empty.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the
// successful completion to the graph end.
Node* throw_node =
graph()->NewNode(common()->Throw(), initial_element_check_throw,
initial_element_check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
}
ReplaceWithValue(node, curloop, effect, control);
return Replace(curloop);
} // namespace compiler
Reduction JSCallReducer::ReduceArrayMap(Handle<JSFunction> function,
Node* node) {
if (!FLAG_turbo_inline_array_builtins) return NoChange();
......@@ -2381,6 +2611,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceArrayForEach(function, node);
case Builtins::kArrayReduce:
return ReduceArrayReduce(function, node);
case Builtins::kArrayReduceRight:
return ReduceArrayReduceRight(function, node);
case Builtins::kArrayMap:
return ReduceArrayMap(function, node);
case Builtins::kArrayFilter:
......
......@@ -73,6 +73,7 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceReflectHas(Node* node);
Reduction ReduceArrayForEach(Handle<JSFunction> function, Node* node);
Reduction ReduceArrayReduce(Handle<JSFunction> function, Node* node);
Reduction ReduceArrayReduceRight(Handle<JSFunction> function, Node* node);
Reduction ReduceArrayMap(Handle<JSFunction> function, Node* node);
Reduction ReduceArrayFilter(Handle<JSFunction> function, Node* node);
enum class ArrayFindVariant : uint8_t { kFind, kFindIndex };
......
......@@ -867,3 +867,315 @@ assertEquals(undefined, arr.reduceRight(function(val) { return val }));
done = true;
assertEquals(null, g());
})();
(function OptimizedReduceRight() {
let count = 0;
let f = (a,current,i) => a + current * ++count;
let g = function(a) {
count = 0;
return a.reduceRight(f);
}
let a = [1,2,3,4,5,6,7,8,9,10];
g(a); g(a);
let total = g(a);
%OptimizeFunctionOnNextCall(g);
assertEquals(total, g(a));
})();
(function OptimizedReduceEmpty() {
let count = 0;
let f = (a,current,i) => a + current * ++count;
let g = function(a) {
count = 0;
return a.reduceRight(f);
}
let a = [1,2,3,4,5,6,7,8,9,10];
g(a); g(a); g(a);
%OptimizeFunctionOnNextCall(g);
g(a);
assertThrows(() => g([]));
})();
(function OptimizedReduceLazyDeopt() {
let deopt = false;
let f = (a,current) => { if (deopt) %DeoptimizeNow(); return a + current; };
let g = function(a) {
return a.reduceRight(f);
}
let a = [1,2,3,4,5,6,7,8,9,10];
g(a); g(a);
let total = g(a);
%OptimizeFunctionOnNextCall(g);
g(a);
deopt = true;
assertEquals(total, g(a));
})();
(function OptimizedReduceLazyDeoptMiddleOfIteration() {
let deopt = false;
let f = (a,current) => {
if (current == 6 && deopt) %DeoptimizeNow();
return a + current;
};
let g = function(a) {
return a.reduceRight(f);
}
let a = [11,22,33,45,56,6,77,84,93,101];
g(a); g(a);
let total = g(a);
%OptimizeFunctionOnNextCall(g);
g(a);
deopt = true;
assertEquals(total, g(a));
})();
(function OptimizedReduceEagerDeoptMiddleOfIteration() {
let deopt = false;
let array = [11,22,33,45,56,6,77,84,93,101];
let f = (a,current) => {
if (current == 6 && deopt) {array[9] = 1.5; }
return a + current;
};
let g = function() {
return array.reduceRight(f);
}
g(); g();
let total = g();
%OptimizeFunctionOnNextCall(g);
g();
deopt = true;
g();
deopt = false;
array = [11,22,33,45,56,6,77,84,93,101];
%OptimizeFunctionOnNextCall(g);
g();
deopt = true;
assertEquals(total, g());
})();
(function ReduceCatch() {
let f = (a,current) => {
return a + current;
};
let g = function() {
try {
return array.reduceRight(f);
} catch (e) {
}
}
g(); g();
let total = g();
%OptimizeFunctionOnNextCall(g);
g();
g();
assertEquals(total, g());
})();
(function ReduceThrow() {
let done = false;
let f = (a, current) => {
if (done) throw "x";
return a + current;
};
let array = [1,2,3];
let g = function() {
try {
return array.reduceRight(f);
} catch (e) {
return null;
}
}
g(); g();
let total = g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
done = false;
g(); g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
})();
(function ReduceThrow() {
let done = false;
let f = (a, current) => {
if (done) throw "x";
return a + current;
};
%NeverOptimizeFunction(f);
let array = [1,2,3];
let g = function() {
try {
return array.reduceRight(f);
} catch (e) {
return null;
}
}
g(); g();
let total = g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
done = false;
g(); g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
})();
(function ReduceFinally() {
let done = false;
let f = (a, current) => {
if (done) throw "x";
return a + current;
};
let array = [1,2,3];
let g = function() {
try {
return array.reduceRight(f);
} catch (e) {
} finally {
if (done) return null;
}
}
g(); g();
let total = g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
done = false;
g(); g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
})();
(function ReduceFinallyNoInline() {
let done = false;
let f = (a, current) => {
if (done) throw "x";
return a + current;
};
%NeverOptimizeFunction(f);
let array = [1,2,3];
let g = function() {
try {
return array.reduceRight(f);
} catch (e) {
} finally {
if (done) return null;
}
}
g(); g();
let total = g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
done = false;
g(); g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
})();
(function ReduceNonCallableOpt() {
let done = false;
let f = (a, current) => {
return a + current;
};
let array = [1,2,3];
let g = function() {
return array.reduceRight(f);
}
g(); g();
let total = g();
%OptimizeFunctionOnNextCall(g);
g(); g();
assertEquals(6, g());
f = null;
assertThrows(() => g());
})();
(function ReduceCatchInlineDeopt() {
let done = false;
let f = (a, current) => {
if (done) {
%DeoptimizeNow();
throw "x";
}
return a + current;
};
let array = [1,2,3];
let g = function() {
try {
return array.reduceRight(f);
} catch (e) {
if (done) return null;
}
}
g(); g();
let total = g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
done = false;
g(); g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
})();
(function ReduceFinallyInlineDeopt() {
let done = false;
let f = (a, current) => {
if (done) {
%DeoptimizeNow();
throw "x";
}
return a + current;
};
let array = [1,2,3];
let g = function() {
try {
return array.reduceRight(f);
} catch (e) {
} finally {
if (done) return null;
}
}
g(); g();
let total = g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
done = false;
g(); g();
%OptimizeFunctionOnNextCall(g);
g();
assertEquals(6, g());
done = true;
assertEquals(null, g());
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment