Commit 545a2502 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Add fast-path for Math.hypot().

This adds a fast-path to inline `Math.hypot(v1,...,vn)` into optimized
code assuming that v1,...,vn are already numbers. The inlining follows
the general C++ implementation (which was also simplified a bit), and
thus uses Kahan summation to avoid rounding errors.

This improves the benchmark in [1] from around

  testHypot: 656 ms.
  testSqrt: 105 ms.
  testExp: 103 ms.

to

  testHypot: 147 ms.
  testSqrt: 103 ms.
  testExp: 102 ms.

so its roughly a **4.5x improvement**.

[1] https://github.com/bmeurer/js-micro-benchmarks/blob/60a34c0dd29b77e6950555c2dd9687b1a0a7671e/bench-math-hypot.js

Bug: chromium:979893
Change-Id: Id834d5613bc22aa7ce27b9d6eca1f1f1979aa3e7
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1684178
Auto-Submit: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarSigurd Schneider <sigurds@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62483}
parent 2f1e0b76
......@@ -20,7 +20,6 @@ BUILTIN(MathHypot) {
if (length == 0) return Smi::kZero;
DCHECK_LT(0, length);
double max = 0;
bool one_arg_is_nan = false;
std::vector<double> abs_values;
abs_values.reserve(length);
for (int i = 0; i < length; i++) {
......@@ -28,29 +27,20 @@ BUILTIN(MathHypot) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, x,
Object::ToNumber(isolate, x));
double abs_value = std::abs(x->Number());
if (std::isnan(abs_value)) {
one_arg_is_nan = true;
} else {
abs_values.push_back(abs_value);
if (max < abs_value) {
max = abs_value;
}
abs_values.push_back(abs_value);
// Use negation here to make sure that {max} is NaN
// in the end in case any of the arguments was NaN.
if (!(abs_value <= max)) {
max = abs_value;
}
}
if (max == V8_INFINITY) {
return *isolate->factory()->NewNumber(V8_INFINITY);
}
if (one_arg_is_nan) {
return ReadOnlyRoots(isolate).nan_value();
}
if (max == 0) {
return Smi::kZero;
} else if (max == V8_INFINITY) {
return ReadOnlyRoots(isolate).infinity_value();
}
DCHECK_GT(max, 0);
DCHECK(!(max <= 0));
// Kahan summation to avoid rounding errors.
// Normalize the numbers to the largest one to avoid overflow.
......
......@@ -179,6 +179,100 @@ Reduction JSCallReducer::ReduceMathMinMax(Node* node, const Operator* op,
return Replace(value);
}
// ES section #sec-math.hypot Math.hypot ( value1, value2, ...values )
Reduction JSCallReducer::ReduceMathHypot(Node* node) {
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
if (node->op()->ValueInputCount() < 3) {
Node* value = jsgraph()->ZeroConstant();
ReplaceWithValue(node, value);
return Replace(value);
}
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
NodeVector values(graph()->zone());
Node* max = effect =
graph()->NewNode(simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, p.feedback()),
NodeProperties::GetValueInput(node, 2), effect, control);
max = graph()->NewNode(simplified()->NumberAbs(), max);
values.push_back(max);
for (int i = 3; i < node->op()->ValueInputCount(); ++i) {
Node* input = effect = graph()->NewNode(
simplified()->SpeculativeToNumber(NumberOperationHint::kNumberOrOddball,
p.feedback()),
NodeProperties::GetValueInput(node, i), effect, control);
input = graph()->NewNode(simplified()->NumberAbs(), input);
values.push_back(input);
// Make sure {max} is NaN in the end in case any argument was NaN.
max = graph()->NewNode(
common()->Select(MachineRepresentation::kTagged),
graph()->NewNode(simplified()->NumberLessThanOrEqual(), input, max),
max, input);
}
Node* check0 = graph()->NewNode(simplified()->NumberEqual(), max,
jsgraph()->ZeroConstant());
Node* branch0 =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check0, control);
Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0);
Node* vtrue0 = jsgraph()->ZeroConstant();
Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0);
Node* vfalse0;
{
Node* check1 = graph()->NewNode(simplified()->NumberEqual(), max,
jsgraph()->Constant(V8_INFINITY));
Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kFalse),
check1, if_false0);
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* vtrue1 = jsgraph()->Constant(V8_INFINITY);
Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1);
Node* vfalse1;
{
// Kahan summation to avoid rounding errors.
// Normalize the numbers to the largest one to avoid overflow.
Node* sum = jsgraph()->ZeroConstant();
Node* compensation = jsgraph()->ZeroConstant();
for (Node* value : values) {
Node* n = graph()->NewNode(simplified()->NumberDivide(), value, max);
Node* summand = graph()->NewNode(
simplified()->NumberSubtract(),
graph()->NewNode(simplified()->NumberMultiply(), n, n),
compensation);
Node* preliminary =
graph()->NewNode(simplified()->NumberAdd(), sum, summand);
compensation = graph()->NewNode(
simplified()->NumberSubtract(),
graph()->NewNode(simplified()->NumberSubtract(), preliminary, sum),
summand);
sum = preliminary;
}
vfalse1 = graph()->NewNode(
simplified()->NumberMultiply(),
graph()->NewNode(simplified()->NumberSqrt(), sum), max);
}
if_false0 = graph()->NewNode(common()->Merge(2), if_true1, if_false1);
vfalse0 = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue1, vfalse1, if_false0);
}
control = graph()->NewNode(common()->Merge(2), if_true0, if_false0);
Node* value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue0,
vfalse0, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSCallReducer::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kJSConstruct:
......@@ -3518,6 +3612,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node,
return ReduceMathUnary(node, simplified()->NumberFloor());
case Builtins::kMathFround:
return ReduceMathUnary(node, simplified()->NumberFround());
case Builtins::kMathHypot:
return ReduceMathHypot(node);
case Builtins::kMathLog:
return ReduceMathUnary(node, simplified()->NumberLog());
case Builtins::kMathLog1p:
......
......@@ -156,6 +156,7 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Reduction ReduceMathImul(Node* node);
Reduction ReduceMathClz32(Node* node);
Reduction ReduceMathMinMax(Node* node, const Operator* op, Node* empty_value);
Reduction ReduceMathHypot(Node* node);
Reduction ReduceNumberIsFinite(Node* node);
Reduction ReduceNumberIsInteger(Node* node);
......
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