Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,201 @@
// Copyright 2024 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.
#include "src/base/vector.h"
#include "src/compiler/turboshaft/assembler.h"
#include "src/compiler/turboshaft/branch-elimination-reducer.h"
#include "src/compiler/turboshaft/copying-phase.h"
#include "src/compiler/turboshaft/dead-code-elimination-reducer.h"
#include "src/compiler/turboshaft/loop-peeling-reducer.h"
#include "src/compiler/turboshaft/machine-optimization-reducer.h"
#include "src/compiler/turboshaft/operations.h"
#include "src/compiler/turboshaft/representations.h"
#include "src/compiler/turboshaft/required-optimization-reducer.h"
#include "src/compiler/turboshaft/variable-reducer.h"
#include "test/unittests/compiler/turboshaft/reducer-test.h"
namespace v8::internal::compiler::turboshaft {
#include "src/compiler/turboshaft/define-assembler-macros.inc"
class ControlFlowTest : public ReducerTest {};
// This test creates a chain of empty blocks linked by Gotos. CopyingPhase
// should automatically inline them, leading to the graph containing a single
// block after a single CopyingPhase.
TEST_F(ControlFlowTest, DefaultBlockInlining) {
auto test = CreateFromGraph(1, [](auto& Asm) {
OpIndex cond = Asm.GetParameter(0);
for (int i = 0; i < 10000; i++) {
Label<> l(&Asm);
GOTO(l);
BIND(l);
}
__ Return(cond);
});
test.Run<>();
ASSERT_EQ(test.graph().block_count(), 1u);
}
// This test creates a fairly large graph, where a pattern similar to this is
// repeating:
//
// B1 B2
// \ /
// \ /
// Phi
// Branch(Phi)
// / \
// / \
// B3 B4
//
// BranchElimination should remove such branches by cloning the block with the
// branch. In the end, the graph should contain (almost) no branches anymore.
TEST_F(ControlFlowTest, BranchElimination) {
static constexpr int kSize = 10000;
auto test = CreateFromGraph(1, [](auto& Asm) {
V<Word32> cond =
__ TaggedEqual(Asm.GetParameter(0), __ SmiConstant(Smi::FromInt(0)));
Block* end = __ NewBlock();
V<Word32> cst1 = __ Word32Constant(42);
std::vector<Block*> destinations;
for (int i = 0; i < kSize; i++) destinations.push_back(__ NewBlock());
ZoneVector<SwitchOp::Case>* cases =
Asm.graph().graph_zone()->template New<ZoneVector<SwitchOp::Case>>(
Asm.graph().graph_zone());
for (int i = 0; i < kSize; i++) {
cases->push_back({i, destinations[i], BranchHint::kNone});
}
__ Switch(cond, base::VectorOf(*cases), end);
__ Bind(destinations[0]);
Block* b = __ NewBlock();
__ Branch(cond, b, end);
__ Bind(b);
for (int i = 1; i < kSize; i++) {
V<Word32> cst2 = __ Word32Constant(1);
__ Goto(destinations[i]);
__ Bind(destinations[i]);
V<Word32> phi = __ Phi({cst1, cst2}, RegisterRepresentation::Word32());
Block* b1 = __ NewBlock();
__ Branch(phi, b1, end);
__ Bind(b1);
}
__ Goto(end);
__ Bind(end);
__ Return(cond);
});
// BranchElimination should remove all branches (except the first one), but
// will not inline the destinations right away.
test.Run<BranchEliminationReducer, MachineOptimizationReducer>();
ASSERT_EQ(test.CountOp(Opcode::kBranch), 1u);
// An empty phase will then inline the empty intermediate blocks.
test.Run<>();
// The graph should now contain 2 blocks per case (1 edge-split + 1 merge),
// and a few blocks before and after (the switch and the return for
// instance). To make this test a bit future proof, we just check that the
// number of block is "number of cases * 2 + a few more blocks" rather than
// computing the exact expected number of blocks.
static constexpr int kMaxOtherBlocksCount = 10;
ASSERT_LE(test.graph().block_count(),
static_cast<size_t>(kSize * 2 + kMaxOtherBlocksCount));
}
// When the block following a loop header has a single predecessor and contains
// Phis with a single input, loop peeling should be careful not to think that
// these phis are loop phis.
TEST_F(ControlFlowTest, LoopPeelingSingleInputPhi) {
auto test = CreateFromGraph(1, [](auto& Asm) {
Block* loop = __ NewLoopHeader();
Block *loop_body = __ NewBlock(), *outside = __ NewBlock();
__ Goto(loop);
__ Bind(loop);
V<Word32> cst = __ Word32Constant(42);
__ Goto(loop_body);
__ Bind(loop_body);
V<Word32> phi = __ Phi({cst}, RegisterRepresentation::Word32());
__ GotoIf(phi, outside);
__ Goto(loop);
__ Bind(outside);
__ Return(__ Word32Constant(17));
});
test.Run<LoopPeelingReducer>();
}
// This test checks that DeadCodeElimination (DCE) eliminates dead blocks
// regardless or whether they are reached through a Goto or a Branch.
TEST_F(ControlFlowTest, DCEGoto) {
auto test = CreateFromGraph(1, [](auto& Asm) {
// This whole graph only contains unused operations (except for the final
// Return).
Block *b1 = __ NewBlock(), *b2 = __ NewBlock(), *b3 = __ NewBlock(),
*b4 = __ NewBlock();
__ Bind(b1);
__ Word32Constant(71);
__ Goto(b4);
__ Bind(b4);
OpIndex cond = Asm.GetParameter(0);
IF (cond) {
__ Word32Constant(47);
__ Goto(b2);
__ Bind(b2);
__ Word32Constant(53);
} ELSE {
__ Word32Constant(19);
}
__ Word32Constant(42);
__ Goto(b3);
__ Bind(b3);
__ Return(__ Word32Constant(17));
});
test.Run<DeadCodeEliminationReducer>();
// The final graph should contain at most 2 blocks (we currently don't
// eliminate the initial empty block, so we end up with 2 blocks rather than
// 1; a subsequent optimization phase would remove the empty 1st block).
ASSERT_LE(test.graph().block_count(), static_cast<size_t>(2));
}
TEST_F(ControlFlowTest, LoopVar) {
auto test = CreateFromGraph(1, [](auto& Asm) {
OpIndex p = Asm.GetParameter(0);
Variable v1 = __ NewVariable(RegisterRepresentation::Tagged());
Variable v2 = __ NewVariable(RegisterRepresentation::Tagged());
__ SetVariable(v1, p);
__ SetVariable(v2, p);
LoopLabel<Word32> loop(&Asm);
Label<Word32> end(&Asm);
GOTO(loop, 0);
BIND_LOOP(loop, iter) {
GOTO_IF(__ Word32Equal(iter, 42), end, 15);
__ SetVariable(v1, __ SmiConstant(Smi::FromInt(17)));
GOTO(loop, __ Word32Add(iter, 1));
}
BIND(end, ret);
OpIndex t = __ Word32Mul(ret, __ GetVariable(v1));
__ Return(__ Word32BitwiseAnd(t, __ GetVariable(v2)));
});
ASSERT_EQ(0u, test.CountOp(Opcode::kPendingLoopPhi));
}
#include "src/compiler/turboshaft/undef-assembler-macros.inc"
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,352 @@
// Copyright 2024 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.
#include "src/compiler/turboshaft/late-load-elimination-reducer.h"
#include "src/compiler/turboshaft/assembler.h"
#include "src/compiler/turboshaft/copying-phase.h"
#include "src/compiler/turboshaft/machine-optimization-reducer.h"
#include "src/compiler/turboshaft/operations.h"
#include "src/compiler/turboshaft/opmasks.h"
#include "src/compiler/turboshaft/phase.h"
#include "src/compiler/turboshaft/representations.h"
#include "src/compiler/turboshaft/required-optimization-reducer.h"
#include "src/compiler/turboshaft/variable-reducer.h"
#include "test/common/flag-utils.h"
#include "test/unittests/compiler/turboshaft/reducer-test.h"
namespace v8::internal::compiler::turboshaft {
#include "src/compiler/turboshaft/define-assembler-macros.inc"
// Use like this:
// V<...> C(my_var) = ...
#define C(value) value = Asm.CaptureHelperForMacro(#value)
class LateLoadEliminationReducerTest : public ReducerTest {
public:
LateLoadEliminationReducerTest()
: ReducerTest(),
flag_load_elimination_(&v8_flags.turboshaft_load_elimination, true) {}
private:
const FlagScope<bool> flag_load_elimination_;
};
/* TruncateInt64ToInt32(
* BitcastTaggedToWordPtrForTagAndSmiBits(
* Load[Tagged]))
* => Load[Int32]
*/
TEST_F(LateLoadEliminationReducerTest, Int32TruncatedLoad_Foldable) {
auto test = CreateFromGraph(2, [](auto& Asm) {
V<Object> C(load) = __ Load(
Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(), RegisterRepresentation::Tagged(), 0);
V<WordPtr> temp = __ BitcastTaggedToWordPtrForTagAndSmiBits(load);
V<Word32> C(truncate) = __ TruncateWordPtrToWord32(temp);
V<Object> C(result) =
__ Conditional(truncate, Asm.GetParameter(0), Asm.GetParameter(1));
__ Return(result);
});
test.Run<LateLoadEliminationReducer>();
#ifdef V8_COMPRESS_POINTERS
// Load should have been replaced by an int32 load.
const LoadOp* load = test.GetCapturedAs<LoadOp>("load");
ASSERT_NE(load, nullptr);
ASSERT_EQ(load->loaded_rep, MemoryRepresentation::Int32());
ASSERT_EQ(load->result_rep, RegisterRepresentation::Word32());
// The truncation chain should have been eliminated.
ASSERT_TRUE(test.GetCapture("truncate").IsEmpty());
// The select uses the load as condition directly.
const SelectOp* result = test.GetCapturedAs<SelectOp>("result");
ASSERT_NE(result, nullptr);
ASSERT_EQ(&test.graph().Get(result->cond()), load);
#endif
}
/* TruncateInt64ToInt32(
* BitcastTaggedToWordPtrForTagAndSmiBits(
* Load[Tagged]))
* cannot be optimized because Load[Tagged] has another non-truncating use.
*/
TEST_F(LateLoadEliminationReducerTest,
Int32TruncatedLoad_NonFoldable_AdditionalUse) {
auto test = CreateFromGraph(1, [](auto& Asm) {
V<Object> C(load) = __ Load(
Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(), RegisterRepresentation::Tagged(), 0);
V<WordPtr> temp = __ BitcastTaggedToWordPtrForTagAndSmiBits(load);
V<Word32> C(truncate) = __ TruncateWordPtrToWord32(temp);
__ Return(__ Conditional(truncate, Asm.GetParameter(0), load));
});
test.Run<LateLoadEliminationReducer>();
#ifdef V8_COMPRESS_POINTERS
// Load should still be tagged.
const LoadOp* load = test.GetCapturedAs<LoadOp>("load");
ASSERT_NE(load, nullptr);
ASSERT_EQ(load->loaded_rep, MemoryRepresentation::AnyTagged());
ASSERT_EQ(load->result_rep, RegisterRepresentation::Tagged());
// The truncation chain should still be present.
ASSERT_FALSE(test.GetCapture("truncate").IsEmpty());
#endif
}
/* TruncateInt64ToInt32(
* BitcastTaggedToWordPtrForTagAndSmiBits(
* Load[Tagged]))
* cannot be optimized because there is another non-truncated Load that is
* elminated by LateLoadElimination that adds additional uses.
*/
TEST_F(LateLoadEliminationReducerTest,
Int32TruncatedLoad_NonFoldable_ReplacingOtherLoad) {
auto test = CreateFromGraph(1, [](auto& Asm) {
V<Object> C(load) = __ Load(
Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(), RegisterRepresentation::Tagged(), 0);
V<WordPtr> temp = __ BitcastTaggedToWordPtrForTagAndSmiBits(load);
V<Word32> C(truncate) = __ TruncateWordPtrToWord32(temp);
V<Object> C(other_load) = __ Load(
Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(), RegisterRepresentation::Tagged(), 0);
V<Object> C(result) =
__ Conditional(truncate, Asm.GetParameter(0), other_load);
__ Return(result);
});
test.Run<LateLoadEliminationReducer>();
#ifdef V8_COMPRESS_POINTERS
// Load should still be tagged.
const LoadOp* load = test.GetCapturedAs<LoadOp>("load");
ASSERT_NE(load, nullptr);
ASSERT_EQ(load->loaded_rep, MemoryRepresentation::AnyTagged());
ASSERT_EQ(load->result_rep, RegisterRepresentation::Tagged());
// The truncation chain should still be present.
ASSERT_FALSE(test.GetCapture("truncate").IsEmpty());
// The other load has been eliminated.
ASSERT_TRUE(test.GetCapture("other_load").IsEmpty());
// The select's input is the first load.
const SelectOp* result = test.GetCapturedAs<SelectOp>("result");
ASSERT_NE(result, nullptr);
ASSERT_EQ(&test.graph().Get(result->vfalse()), load);
#endif
}
/* TruncateInt64ToInt32(
* BitcastTaggedToWordPtrForTagAndSmiBits(
* Load[Tagged]))
* => Load[Int32]
* because the other load that is eliminated by LateLoadElimination is also a
* truncating load.
*/
TEST_F(LateLoadEliminationReducerTest,
Int32TruncatedLoad_Foldable_ReplacingOtherLoad) {
auto test = CreateFromGraph(1, [](auto& Asm) {
V<Object> C(load) = __ Load(
Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(), RegisterRepresentation::Tagged(), 0);
V<WordPtr> temp = __ BitcastTaggedToWordPtrForTagAndSmiBits(load);
V<Word32> C(truncate) = __ TruncateWordPtrToWord32(temp);
V<Object> C(other_load) = __ Load(
Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(), RegisterRepresentation::Tagged(), 0);
V<WordPtr> other_temp =
__ BitcastTaggedToWordPtrForTagAndSmiBits(other_load);
V<Word32> C(other_truncate) = __ TruncateWordPtrToWord32(other_temp);
V<Word32> C(result) =
__ Conditional(truncate, __ Word32Constant(42), other_truncate);
__ Return(__ TagSmi(result));
});
test.Run<LateLoadEliminationReducer>();
#ifdef V8_COMPRESS_POINTERS
// Load should have been replaced by an int32 load.
const LoadOp* load = test.GetCapturedAs<LoadOp>("load");
ASSERT_NE(load, nullptr);
ASSERT_EQ(load->loaded_rep, MemoryRepresentation::Int32());
ASSERT_EQ(load->result_rep, RegisterRepresentation::Word32());
// Both truncation chains should have been eliminated.
ASSERT_TRUE(test.GetCapture("truncate").IsEmpty());
ASSERT_TRUE(test.GetCapture("other_truncate").IsEmpty());
// The other load should have been eliminated.
ASSERT_TRUE(test.GetCapture("other_load").IsEmpty());
// The select uses the load as condition and the second input directly.
const SelectOp* result = test.GetCapturedAs<SelectOp>("result");
ASSERT_NE(result, nullptr);
ASSERT_EQ(&test.graph().Get(result->cond()), load);
ASSERT_EQ(&test.graph().Get(result->vfalse()), load);
#endif
}
/* TruncateInt64ToInt32(
* BitcastTaggedToWordPtrForTagAndSmiBits(
* Load[Tagged]))
* cannot be optimized because this load is replaced by another load that has
* non-truncated uses.
*/
TEST_F(LateLoadEliminationReducerTest,
Int32TruncatedLoad_NonFoldable_ReplacedByOtherLoad) {
auto test = CreateFromGraph(1, [](auto& Asm) {
V<Object> C(other_load) = __ Load(
Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(), RegisterRepresentation::Tagged(), 0);
V<Object> C(load) = __ Load(
Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(), RegisterRepresentation::Tagged(), 0);
V<WordPtr> temp = __ BitcastTaggedToWordPtrForTagAndSmiBits(load);
V<Word32> C(truncate) = __ TruncateWordPtrToWord32(temp);
V<Object> C(result) =
__ Conditional(truncate, Asm.GetParameter(0), other_load);
__ Return(result);
});
test.Run<LateLoadEliminationReducer>();
#ifdef V8_COMPRESS_POINTERS
// The other load should still be tagged.
const LoadOp* other_load = test.GetCapturedAs<LoadOp>("other_load");
ASSERT_NE(other_load, nullptr);
ASSERT_EQ(other_load->loaded_rep, MemoryRepresentation::AnyTagged());
ASSERT_EQ(other_load->result_rep, RegisterRepresentation::Tagged());
// The truncation chain should still be present.
const ChangeOp* truncate = test.GetCapturedAs<ChangeOp>("truncate");
ASSERT_NE(truncate, nullptr);
// ... but the input is now the other load.
const TaggedBitcastOp& bitcast =
test.graph().Get(truncate->input()).Cast<TaggedBitcastOp>();
ASSERT_EQ(other_load, &test.graph().Get(bitcast.input()));
// The load has been eliminated.
ASSERT_TRUE(test.GetCapture("load").IsEmpty());
// The select's input is unchanged.
const SelectOp* result = test.GetCapturedAs<SelectOp>("result");
ASSERT_NE(result, nullptr);
ASSERT_EQ(&test.graph().Get(result->cond()), truncate);
ASSERT_EQ(&test.graph().Get(result->vfalse()), other_load);
#endif
}
/* TruncateInt64ToInt32(
* BitcastTaggedToWordPtrForTagAndSmiBits(
* Load[Tagged]))
* => Load[Int32]
* because the other load that is replacing the load is also a truncating load.
*/
TEST_F(LateLoadEliminationReducerTest,
Int32TruncatedLoad_Foldable_ReplacedByOtherLoad) {
auto test = CreateFromGraph(1, [](auto& Asm) {
V<Object> C(other_load) = __ Load(
Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(), RegisterRepresentation::Tagged(), 0);
V<Object> C(load) = __ Load(
Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(), RegisterRepresentation::Tagged(), 0);
V<WordPtr> temp = __ BitcastTaggedToWordPtrForTagAndSmiBits(load);
V<Word32> C(truncate) = __ TruncateWordPtrToWord32(temp);
V<WordPtr> other_temp =
__ BitcastTaggedToWordPtrForTagAndSmiBits(other_load);
V<Word32> C(other_truncate) = __ TruncateWordPtrToWord32(other_temp);
V<Word32> C(result) =
__ Conditional(truncate, __ Word32Constant(42), other_truncate);
__ Return(__ TagSmi(result));
});
test.Run<LateLoadEliminationReducer>();
#if V8_COMPRESS_POINTERS
// The other load should be replaced by an int32 load.
const LoadOp* other_load = test.GetCapturedAs<LoadOp>("other_load");
ASSERT_NE(other_load, nullptr);
ASSERT_EQ(other_load->loaded_rep, MemoryRepresentation::Int32());
ASSERT_EQ(other_load->result_rep, RegisterRepresentation::Word32());
// The truncation chains should be eliminated.
ASSERT_TRUE(test.GetCapture("truncate").IsEmpty());
ASSERT_TRUE(test.GetCapture("other_truncate").IsEmpty());
// The load has been eliminated.
ASSERT_TRUE(test.GetCapture("load").IsEmpty());
// The select uses the other load as condition and the second input directly.
const SelectOp* result = test.GetCapturedAs<SelectOp>("result");
ASSERT_NE(result, nullptr);
ASSERT_EQ(&test.graph().Get(result->cond()), other_load);
ASSERT_EQ(&test.graph().Get(result->vfalse()), other_load);
#endif
}
/* TruncateInt64ToInt32(
* BitcastTaggedToWordPtrForTagAndSmiBits(
* Load[Tagged]))
* cannot be optimized because the BitcastTaggedToWordPtrForTagAndSmiBits has an
* additional (potentially non-truncating) use.
*/
TEST_F(LateLoadEliminationReducerTest,
Int32TruncatedLoad_NonFoldable_AdditionalBitcastUse) {
auto test = CreateFromGraph(1, [](auto& Asm) {
V<Object> C(load) = __ Load(
Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(), RegisterRepresentation::Tagged(), 0);
V<WordPtr> temp = __ BitcastTaggedToWordPtrForTagAndSmiBits(load);
V<Word32> C(truncate) = __ TruncateWordPtrToWord32(temp);
V<WordPtr> C(result) = __ Conditional(
truncate, __ BitcastTaggedToWordPtr(Asm.GetParameter(0)), temp);
__ Return(__ BitcastWordPtrToSmi(result));
});
test.Run<LateLoadEliminationReducer>();
#ifdef V8_COMPRESS_POINTERS
// The load should still be tagged.
const LoadOp* other_load = test.GetCapturedAs<LoadOp>("load");
ASSERT_NE(other_load, nullptr);
ASSERT_EQ(other_load->loaded_rep, MemoryRepresentation::AnyTagged());
ASSERT_EQ(other_load->result_rep, RegisterRepresentation::Tagged());
// The truncation chain should still be present.
const ChangeOp* truncate = test.GetCapturedAs<ChangeOp>("truncate");
ASSERT_NE(truncate, nullptr);
// The select's input is unchanged.
const SelectOp* result = test.GetCapturedAs<SelectOp>("result");
ASSERT_NE(result, nullptr);
ASSERT_EQ(&test.graph().Get(result->cond()), truncate);
#endif
}
#undef C
#include "src/compiler/turboshaft/undef-assembler-macros.inc"
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,491 @@
// Copyright 2024 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.
#include "src/compiler/turboshaft/assembler.h"
#include "src/compiler/turboshaft/loop-unrolling-reducer.h"
#include "test/unittests/compiler/turboshaft/reducer-test.h"
namespace v8::internal::compiler::turboshaft {
#include "src/compiler/turboshaft/define-assembler-macros.inc"
class LoopUnrollingAnalyzerTest : public ReducerTest {};
template <typename T>
class LoopUnrollingAnalyzerTestWithParam
: public LoopUnrollingAnalyzerTest,
public ::testing::WithParamInterface<T> {};
size_t CountLoops(const Graph& graph) {
size_t count = 0;
for (const Block& block : graph.blocks()) {
if (block.IsLoop()) count++;
}
return count;
}
const Block& GetFirstLoop(const Graph& graph) {
DCHECK_GE(CountLoops(graph), 1u);
for (const Block& block : graph.blocks()) {
if (block.IsLoop()) return block;
}
UNREACHABLE();
}
#define BUILTIN_CMP_LIST(V) \
V(Uint32LessThan) \
V(Uint32LessThanOrEqual) \
V(Int32LessThan) \
V(Int32LessThanOrEqual) \
V(Word32Equal)
#define CMP_GREATER_THAN_LIST(V) \
V(Uint32GreaterThan) \
V(Uint32GreaterThanOrEqual) \
V(Int32GreaterThan) \
V(Int32GreaterThanOrEqual)
#define CMP_LIST(V) \
BUILTIN_CMP_LIST(V) \
CMP_GREATER_THAN_LIST(V)
enum class Cmp {
#define DEF_CMP_OP(name) k##name,
CMP_LIST(DEF_CMP_OP)
#undef DEF_CMP_OP
};
std::ostream& operator<<(std::ostream& os, const Cmp& cmp) {
switch (cmp) {
case Cmp::kUint32LessThan:
return os << "<ᵘ";
case Cmp::kUint32LessThanOrEqual:
return os << "<=ᵘ";
case Cmp::kInt32LessThan:
return os << "";
case Cmp::kInt32LessThanOrEqual:
return os << "<=ˢ";
case Cmp::kUint32GreaterThan:
return os << ">ᵘ";
case Cmp::kUint32GreaterThanOrEqual:
return os << ">=ᵘ";
case Cmp::kInt32GreaterThan:
return os << "";
case Cmp::kInt32GreaterThanOrEqual:
return os << ">=ᵘ";
case Cmp::kWord32Equal:
return os << "!=";
}
}
bool IsGreaterThan(Cmp cmp) {
switch (cmp) {
#define GREATER_THAN_CASE(name) \
case Cmp::k##name: \
return true;
CMP_GREATER_THAN_LIST(GREATER_THAN_CASE)
default:
return false;
}
}
Cmp GreaterThanToLessThan(Cmp cmp, ConstOrV<Word32>* left,
ConstOrV<Word32>* right) {
if (IsGreaterThan(cmp)) std::swap(*left, *right);
switch (cmp) {
case Cmp::kUint32GreaterThan:
return Cmp::kUint32LessThan;
case Cmp::kUint32GreaterThanOrEqual:
return Cmp::kUint32LessThanOrEqual;
case Cmp::kInt32GreaterThan:
return Cmp::kInt32LessThan;
case Cmp::kInt32GreaterThanOrEqual:
return Cmp::kInt32LessThanOrEqual;
default:
return cmp;
}
}
#define NO_OVERFLOW_BINOP_LIST(V) \
V(Word32Add) \
V(Word32Sub) \
V(Word32Mul) \
V(Int32Div) \
V(Uint32Div)
#define OVERFLOW_CHECKED_BINOP_LIST(V) \
V(Int32AddCheckOverflow) \
V(Int32SubCheckOverflow) \
V(Int32MulCheckOverflow)
#define BINOP_LIST(V) \
NO_OVERFLOW_BINOP_LIST(V) \
OVERFLOW_CHECKED_BINOP_LIST(V)
enum class Binop {
#define DEF_BINOP_OP(name) k##name,
BINOP_LIST(DEF_BINOP_OP)
#undef DEF_BINOP_OP
};
std::ostream& operator<<(std::ostream& os, const Binop& binop) {
switch (binop) {
case Binop::kWord32Add:
return os << "+";
case Binop::kWord32Sub:
return os << "-";
case Binop::kWord32Mul:
return os << "*";
case Binop::kInt32Div:
return os << "";
case Binop::kUint32Div:
return os << "/ᵘ";
case Binop::kInt32AddCheckOverflow:
return os << "+ᵒ";
case Binop::kInt32SubCheckOverflow:
return os << "-ᵒ";
case Binop::kInt32MulCheckOverflow:
return os << "*ᵒ";
}
}
V<Word32> EmitCmp(TestInstance& test_instance, Cmp cmp, ConstOrV<Word32> left,
ConstOrV<Word32> right) {
cmp = GreaterThanToLessThan(cmp, &left, &right);
switch (cmp) {
#define CASE(name) \
case Cmp::k##name: \
return test_instance.Asm().name(left, right);
BUILTIN_CMP_LIST(CASE)
#undef CASE
default:
UNREACHABLE();
}
}
V<Word32> EmitBinop(TestInstance& test_instance, Binop binop,
ConstOrV<Word32> left, ConstOrV<Word32> right) {
switch (binop) {
#define CASE_NO_OVERFLOW(name) \
case Binop::k##name: \
return test_instance.Asm().name(left, right);
NO_OVERFLOW_BINOP_LIST(CASE_NO_OVERFLOW)
#undef CASE_NO_OVERFLOW
#define CASE_OVERFLOW(name) \
case Binop::k##name: \
return test_instance.Asm().Projection<0>( \
test_instance.Asm().name(left, right));
OVERFLOW_CHECKED_BINOP_LIST(CASE_OVERFLOW)
#undef CASE_OVERFLOW
}
}
struct BoundedLoop {
int init;
Cmp cmp;
int max;
Binop binop;
int increment;
uint32_t expected_iter_count;
const char* name;
uint32_t expected_unroll_count = 0;
};
std::ostream& operator<<(std::ostream& os, const BoundedLoop& loop) {
return os << loop.name;
}
static const BoundedLoop kSmallBoundedLoops[] = {
// Increasing positive counter with add increment.
{0, Cmp::kInt32LessThan, 3, Binop::kWord32Add, 1, 3,
"for (int32_t i = 0; i < 3; i += 1)"},
{0, Cmp::kInt32LessThanOrEqual, 3, Binop::kWord32Add, 1, 4,
"for (int32_t i = 0; i <= 3; i += 1)"},
{0, Cmp::kUint32LessThan, 3, Binop::kWord32Add, 1, 3,
"for (uint32_t i = 0; i < 3; i += 1)"},
{0, Cmp::kUint32LessThanOrEqual, 3, Binop::kWord32Add, 1, 4,
"for (uint32_t i = 0; i <= 3; i += 1)"},
// Decreasing counter with add/sub increment.
{1, Cmp::kInt32GreaterThan, -2, Binop::kWord32Sub, 1, 3,
"for (int32_t i = 1; i > -2; i -= 1)"},
{1, Cmp::kInt32GreaterThan, -2, Binop::kWord32Add, -1, 3,
"for (int32_t i = 1; i > -2; i += -1)"},
{1, Cmp::kInt32GreaterThanOrEqual, -2, Binop::kWord32Sub, 1, 4,
"for (int32_t i = 1; i >= -2; i -= 1)"},
{1, Cmp::kInt32GreaterThanOrEqual, -2, Binop::kWord32Add, -1, 4,
"for (int32_t i = 1; i >= -2; i += -1)"},
// Increasing negative counter with add increment.
{-5, Cmp::kInt32LessThan, -2, Binop::kWord32Add, 1, 3,
"for (int32_t i = -5; i < -2; i += 1)"},
{-5, Cmp::kInt32LessThanOrEqual, -2, Binop::kWord32Add, 1, 4,
"for (int32_t i = -5; i <= -2; i += 1)"},
// Increasing positive counter with mul increment.
{3, Cmp::kInt32LessThan, 13, Binop::kWord32Mul, 2, 3,
"for (int32_t i = 3; i < 13; i *= 2)"},
{3, Cmp::kInt32LessThanOrEqual, 13, Binop::kWord32Mul, 2, 3,
"for (int32_t i = 3; i <= 13; i *= 2)"},
};
static const BoundedLoop kLargeBoundedLoops[] = {
// Increasing positive counter with add increment.
{0, Cmp::kInt32LessThan, 4500, Binop::kWord32Add, 1, 4500,
"for (int32_t i = 0; i < 4500; i += 1)"},
{0, Cmp::kInt32LessThan, 1000000, Binop::kWord32Add, 1, 1000000,
"for (int32_t i = 0; i < 1000000; i += 1)"},
{0, Cmp::kUint32LessThan, 4500, Binop::kWord32Add, 1, 4500,
"for (uint32_t i = 0; i < 4500; i += 1)"},
{0, Cmp::kUint32LessThan, 1000000, Binop::kWord32Add, 1, 1000000,
"for (uint32_t i = 0; i < 1000000; i += 1)"},
// Decreasing counter with add increment.
{700, Cmp::kInt32GreaterThan, -1000, Binop::kWord32Add, -2, 850,
"for (int32_t i = 700; i > -1000; i += -1)"},
{700, Cmp::kInt32GreaterThanOrEqual, -1000, Binop::kWord32Add, -2, 851,
"for (int32_t i = 700; i >= -1000; i += -1)"},
};
static const BoundedLoop kUnderOverflowBoundedLoops[] = {
// Increasing positive to negative with add increment and signed overflow.
// Small loop.
{std::numeric_limits<int32_t>::max() - 2, Cmp::kInt32GreaterThan,
std::numeric_limits<int32_t>::min() + 10, Binop::kWord32Add, 1, 3,
"for (int32_i = MAX_INT-2; i > MIN_INT+10; i += 1)"},
{std::numeric_limits<int32_t>::max() - 2, Cmp::kInt32GreaterThanOrEqual,
std::numeric_limits<int32_t>::min() + 10, Binop::kWord32Add, 1, 3,
"for (int32_i = MAX_INT-2; i >= MIN_INT+10; i += 1)"},
// Larger loop.
{std::numeric_limits<int32_t>::max() - 100, Cmp::kInt32GreaterThan,
std::numeric_limits<int32_t>::min() + 100, Binop::kWord32Add, 1, 200,
"for (int32_i = MAX_INT-100; i > MIN_INT+100; i += 1)"},
{std::numeric_limits<int32_t>::max() - 100, Cmp::kInt32GreaterThanOrEqual,
std::numeric_limits<int32_t>::min() + 100, Binop::kWord32Add, 1, 201,
"for (int32_i = MAX_INT-100; i >= MIN_INT+100; i += 1)"},
// Decreasing negative to positive with add/sub increment and signed
// underflow.
// Small loop.
{std::numeric_limits<int32_t>::min() + 2, Cmp::kInt32LessThan,
std::numeric_limits<int32_t>::max() - 10, Binop::kWord32Add, -1, 3,
"for (int32_t i = MIN_INT+2; i < MAX_INT-10; i += -1)"},
{std::numeric_limits<int32_t>::min() + 2, Cmp::kInt32LessThan,
std::numeric_limits<int32_t>::max() - 10, Binop::kWord32Sub, 1, 3,
"for (int32_t i = MIN_INT+2; i < MAX_INT-10; i -= 1)"},
{std::numeric_limits<int32_t>::min() + 2, Cmp::kInt32LessThanOrEqual,
std::numeric_limits<int32_t>::max() - 10, Binop::kWord32Add, -1, 3,
"for (int32_t i = MIN_INT+2; i <= MAX_INT-10; i += -1)"},
{std::numeric_limits<int32_t>::min() + 2, Cmp::kInt32LessThanOrEqual,
std::numeric_limits<int32_t>::max() - 10, Binop::kWord32Sub, 1, 3,
"for (int32_t i = MIN_INT+2; i <= MAX_INT-10; i -= 1)"},
// Large loop.
{std::numeric_limits<int32_t>::min() + 100, Cmp::kInt32LessThan,
std::numeric_limits<int32_t>::max() - 100, Binop::kWord32Add, -1, 200,
"for (int32_t i = MIN_INT+100; i < MAX_INT-100; i -= 1)"},
{std::numeric_limits<int32_t>::min() + 100, Cmp::kInt32LessThanOrEqual,
std::numeric_limits<int32_t>::max() - 100, Binop::kWord32Add, -1, 201,
"for (int32_t i = MIN_INT+100; i <= MAX_INT-100; i -= 1)"},
};
using LoopUnrollingAnalyzerSmallLoopTest =
LoopUnrollingAnalyzerTestWithParam<BoundedLoop>;
// Checking that the LoopUnrollingAnalyzer correctly computes the number of
// iterations of small loops.
TEST_P(LoopUnrollingAnalyzerSmallLoopTest, ExactLoopIterCount) {
BoundedLoop params = GetParam();
auto test = CreateFromGraph(1, [&params](auto& Asm) {
using AssemblerT = std::remove_reference<decltype(Asm)>::type::Assembler;
OpIndex cond = Asm.GetParameter(0);
ScopedVar<Word32, AssemblerT> index(&Asm, params.init);
WHILE(EmitCmp(Asm, params.cmp, index, params.max)) {
__ JSLoopStackCheck(__ NoContextConstant(), Asm.BuildFrameState());
// Advance the {index}.
index = EmitBinop(Asm, params.binop, index, params.increment);
}
__ Return(index);
});
LoopUnrollingAnalyzer analyzer(test.zone(), &test.graph(), false);
auto stack_checks_to_remove = test.graph().stack_checks_to_remove();
const Block& loop = GetFirstLoop(test.graph());
ASSERT_EQ(1u, stack_checks_to_remove.size());
EXPECT_TRUE(stack_checks_to_remove.contains(loop.index().id()));
IterationCount iter_count = analyzer.GetIterationCount(&loop);
ASSERT_TRUE(iter_count.IsExact());
EXPECT_EQ(params.expected_iter_count, iter_count.exact_count());
}
INSTANTIATE_TEST_SUITE_P(LoopUnrollingAnalyzerTest,
LoopUnrollingAnalyzerSmallLoopTest,
::testing::ValuesIn(kSmallBoundedLoops));
using LoopUnrollingAnalyzerLargeLoopTest =
LoopUnrollingAnalyzerTestWithParam<BoundedLoop>;
// Checking that the LoopUnrollingAnalyzer correctly computes the number of
// iterations of small loops.
TEST_P(LoopUnrollingAnalyzerLargeLoopTest, LargeLoopIterCount) {
BoundedLoop params = GetParam();
auto test = CreateFromGraph(1, [&params](auto& Asm) {
using AssemblerT = std::remove_reference<decltype(Asm)>::type::Assembler;
OpIndex cond = Asm.GetParameter(0);
ScopedVar<Word32, AssemblerT> index(&Asm, params.init);
WHILE(EmitCmp(Asm, params.cmp, index, params.max)) {
__ JSLoopStackCheck(__ NoContextConstant(), Asm.BuildFrameState());
// Advance the {index}.
index = EmitBinop(Asm, params.binop, index, params.increment);
}
__ Return(index);
});
LoopUnrollingAnalyzer analyzer(test.zone(), &test.graph(), false);
auto stack_checks_to_remove = test.graph().stack_checks_to_remove();
const Block& loop = GetFirstLoop(test.graph());
if (params.expected_iter_count <=
LoopUnrollingAnalyzer::kMaxIterForStackCheckRemoval) {
EXPECT_EQ(1u, stack_checks_to_remove.size());
EXPECT_TRUE(stack_checks_to_remove.contains(loop.index().id()));
IterationCount iter_count = analyzer.GetIterationCount(&loop);
ASSERT_TRUE(iter_count.IsApprox());
EXPECT_TRUE(iter_count.IsSmallerThan(
LoopUnrollingAnalyzer::kMaxIterForStackCheckRemoval));
} else {
EXPECT_EQ(0u, stack_checks_to_remove.size());
EXPECT_FALSE(stack_checks_to_remove.contains(loop.index().id()));
IterationCount iter_count = analyzer.GetIterationCount(&loop);
ASSERT_TRUE(iter_count.IsApprox());
EXPECT_FALSE(iter_count.IsSmallerThan(
LoopUnrollingAnalyzer::kMaxIterForStackCheckRemoval));
}
}
INSTANTIATE_TEST_SUITE_P(LoopUnrollingAnalyzerTest,
LoopUnrollingAnalyzerLargeLoopTest,
::testing::ValuesIn(kLargeBoundedLoops));
using LoopUnrollingAnalyzerOverflowTest =
LoopUnrollingAnalyzerTestWithParam<BoundedLoop>;
// Checking that the LoopUnrollingAnalyzer correctly computes the number of
// iterations of small loops.
TEST_P(LoopUnrollingAnalyzerOverflowTest, LargeLoopIterCount) {
BoundedLoop params = GetParam();
auto test = CreateFromGraph(1, [&params](auto& Asm) {
using AssemblerT = std::remove_reference<decltype(Asm)>::type::Assembler;
OpIndex cond = Asm.GetParameter(0);
ScopedVar<Word32, AssemblerT> index(&Asm, params.init);
WHILE(EmitCmp(Asm, params.cmp, index, params.max)) {
__ JSLoopStackCheck(__ NoContextConstant(), Asm.BuildFrameState());
// Advance the {index}.
index = EmitBinop(Asm, params.binop, index, params.increment);
}
__ Return(index);
});
LoopUnrollingAnalyzer analyzer(test.zone(), &test.graph(), false);
auto stack_checks_to_remove = test.graph().stack_checks_to_remove();
const Block& loop = GetFirstLoop(test.graph());
EXPECT_EQ(0u, stack_checks_to_remove.size());
EXPECT_FALSE(stack_checks_to_remove.contains(loop.index().id()));
IterationCount iter_count = analyzer.GetIterationCount(&loop);
EXPECT_TRUE(iter_count.IsUnknown());
}
INSTANTIATE_TEST_SUITE_P(LoopUnrollingAnalyzerTest,
LoopUnrollingAnalyzerOverflowTest,
::testing::ValuesIn(kUnderOverflowBoundedLoops));
#ifdef V8_ENABLE_WEBASSEMBLY
struct BoundedPartialLoop {
int init;
Cmp cmp;
Binop binop;
int max;
uint32_t loop_body_size;
uint32_t expected_unroll_count;
const char* name;
};
std::ostream& operator<<(std::ostream& os, const BoundedPartialLoop& loop) {
return os << loop.name;
}
static const BoundedPartialLoop kPartiallyUnrolledLoops[] = {
{0, Cmp::kInt32LessThan, Binop::kWord32Add, 80, 8, 4,
"for (int32_t i = 0; i < 80; i += 8)"},
{0, Cmp::kInt32LessThan, Binop::kWord32Add, 160, 16, 4,
"for (int32_t i = 0; i < 160; i += 16)"},
{0, Cmp::kInt32LessThan, Binop::kWord32Add, 240, 24, 4,
"for (int32_t i = 0; i < 240; i += 24)"},
{0, Cmp::kInt32LessThan, Binop::kWord32Add, 320, 32, 3,
"for (int32_t i = 0; i < 320; i += 32)"},
{0, Cmp::kInt32LessThan, Binop::kWord32Add, 400, 40, 0,
"for (int32_t i = 0; i < 400; i += 40)"},
};
using LoopUnrollingAnalyzerPartialUnrollTest =
LoopUnrollingAnalyzerTestWithParam<BoundedPartialLoop>;
// Checking that the LoopUnrollingAnalyzer determines the partial unroll count
// base upon the size of the loop.
TEST_P(LoopUnrollingAnalyzerPartialUnrollTest, PartialUnrollCount) {
BoundedPartialLoop params = GetParam();
auto test = CreateFromGraph(1, [&params](auto& Asm) {
using AssemblerT = std::remove_reference<decltype(Asm)>::type::Assembler;
OpIndex cond = Asm.GetParameter(0);
ScopedVar<Word32, AssemblerT> index(&Asm, params.init);
WHILE(EmitCmp(Asm, params.cmp, index, params.max)) {
__ WasmStackCheck(WasmStackCheckOp::Kind::kLoop);
// Advance the {index} a number of times.
for (uint32_t i = 0; i < params.loop_body_size; ++i) {
index = EmitBinop(Asm, params.binop, index, 1);
}
}
__ Return(index);
});
constexpr bool is_wasm = true;
LoopUnrollingAnalyzer analyzer(test.zone(), &test.graph(), is_wasm);
const Block& loop = GetFirstLoop(test.graph());
EXPECT_EQ(analyzer.ShouldPartiallyUnrollLoop(&loop),
params.expected_unroll_count != 0);
if (analyzer.ShouldPartiallyUnrollLoop(&loop)) {
EXPECT_EQ(params.expected_unroll_count,
analyzer.GetPartialUnrollCount(&loop));
}
}
INSTANTIATE_TEST_SUITE_P(LoopUnrollingAnalyzerTest,
LoopUnrollingAnalyzerPartialUnrollTest,
::testing::ValuesIn(kPartiallyUnrolledLoops));
#endif // V8_ENABLE_WEBASSEMBLY
#include "src/compiler/turboshaft/undef-assembler-macros.inc"
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,128 @@
// Copyright 2024 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.
#include "src/compiler/turboshaft/operations.h"
#include "src/compiler/turboshaft/opmasks.h"
#include "src/heap/parked-scope.h"
#include "testing/gtest-support.h"
namespace v8::internal::compiler::turboshaft {
#include "src/compiler/turboshaft/field-macro.inc"
struct MyFakeOp;
// We reuse `Opcode::kConstant` because extending the opcode enum is hard from
// within the test.
template <>
struct operation_to_opcode<MyFakeOp>
: std::integral_constant<Opcode, Opcode::kConstant> {};
struct MyFakeOp : FixedArityOperationT<0, MyFakeOp> {
enum class Kind : uint16_t {
kA = 0x0000,
kB = 0x0001,
kC = 0x0100,
kD = 0x11F8,
kE = 0xFFFF,
};
Kind kind;
uint16_t value;
MyFakeOp(Kind kind, uint16_t value) : Base(), kind(kind), value(value) {}
};
using namespace Opmask;
using MyFakeMask = Opmask::MaskBuilder<MyFakeOp, FIELD(MyFakeOp, kind),
FIELD(MyFakeOp, value)>;
using kA0 = MyFakeMask::For<MyFakeOp::Kind::kA, 0>;
using kB0 = MyFakeMask::For<MyFakeOp::Kind::kB, 0>;
using kC0 = MyFakeMask::For<MyFakeOp::Kind::kC, 0>;
using kD0 = MyFakeMask::For<MyFakeOp::Kind::kD, 0>;
using kA1 = MyFakeMask::For<MyFakeOp::Kind::kA, 1>;
using kC1 = MyFakeMask::For<MyFakeOp::Kind::kC, 1>;
using kB0100 = MyFakeMask::For<MyFakeOp::Kind::kB, 0x0100>;
using kD0100 = MyFakeMask::For<MyFakeOp::Kind::kD, 0x0100>;
using kA11F8 = MyFakeMask::For<MyFakeOp::Kind::kA, 0x11F8>;
using kB11F8 = MyFakeMask::For<MyFakeOp::Kind::kB, 0x11F8>;
using MyFakeKindMask = Opmask::MaskBuilder<MyFakeOp, FIELD(MyFakeOp, kind)>;
using kA = MyFakeKindMask::For<MyFakeOp::Kind::kA>;
using kC = MyFakeKindMask::For<MyFakeOp::Kind::kC>;
class OpmaskTest : public ::testing::Test {};
template <typename... CandidateList>
struct MaskList;
template <typename Head, typename... Tail>
struct MaskList<Head, Tail...> {
template <typename Expected>
static void Check(const MyFakeOp& op) {
ASSERT_EQ(op.template Is<Head>(), (std::is_same_v<Expected, Head>));
MaskList<Tail...>::template Check<Expected>(op);
}
};
template <>
struct MaskList<> {
template <typename Expected>
static void Check(const MyFakeOp&) {}
};
template <typename Expected>
void Check(const MyFakeOp& op) {
MaskList<kA0, kB0, kC0, kD0, kA1, kC1, kB0100, kD0100, kA11F8,
kB11F8>::Check<Expected>(op);
}
TEST_F(OpmaskTest, FullMask) {
MyFakeOp op_A0(MyFakeOp::Kind::kA, 0);
Check<kA0>(op_A0);
MyFakeOp op_B0(MyFakeOp::Kind::kB, 0);
Check<kB0>(op_B0);
MyFakeOp op_C1(MyFakeOp::Kind::kC, 1);
Check<kC1>(op_C1);
MyFakeOp op_B0100(MyFakeOp::Kind::kB, 0x0100);
Check<kB0100>(op_B0100);
MyFakeOp op_D0100(MyFakeOp::Kind::kD, 0x0100);
Check<kD0100>(op_D0100);
MyFakeOp op_A11F8(MyFakeOp::Kind::kA, 0x11F8);
Check<kA11F8>(op_A11F8);
// Ops that should not match any mask.
MyFakeOp op_other1(MyFakeOp::Kind::kE, 0);
Check<void>(op_other1);
MyFakeOp op_other2(MyFakeOp::Kind::kE, 0x11F8);
Check<void>(op_other2);
MyFakeOp op_other3(MyFakeOp::Kind::kA, 2);
Check<void>(op_other3);
MyFakeOp op_other4(MyFakeOp::Kind::kD, 0xF811);
Check<void>(op_other4);
MyFakeOp op_other5(MyFakeOp::Kind::kA, 0x0100);
Check<void>(op_other5);
}
TEST_F(OpmaskTest, PartialMask) {
for (uint16_t v : {0, 1, 2, 0x0100, 0x0101, 0x11F8}) {
MyFakeOp op(MyFakeOp::Kind::kA, v);
ASSERT_TRUE(op.Is<kA>());
ASSERT_FALSE(op.Is<kC>());
}
for (uint16_t v : {0, 1, 2, 0x0100, 0x0101, 0x11F8}) {
MyFakeOp op(MyFakeOp::Kind::kC, v);
ASSERT_FALSE(op.Is<kA>());
ASSERT_TRUE(op.Is<kC>());
}
}
#undef FIELD
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,232 @@
// Copyright 2024 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.
#include <map>
#include "src/compiler/backend/instruction.h"
#include "src/compiler/turbofan-graph-visualizer.h"
#include "src/compiler/turboshaft/assembler.h"
#include "src/compiler/turboshaft/phase.h"
#include "src/compiler/turboshaft/variable-reducer.h"
#include "test/unittests/test-utils.h"
namespace v8::internal::compiler::turboshaft {
class TestInstance {
public:
using Assembler = TSAssembler<VariableReducer>;
struct CapturedOperation {
TestInstance* instance;
OpIndex input;
std::set<OpIndex> generated_output;
bool IsEmpty() const { return generated_output.empty(); }
template <typename Op>
bool Contains() const {
for (OpIndex o : generated_output) {
if (instance->graph().Get(o).Is<Op>()) return true;
}
return false;
}
template <typename Op>
const underlying_operation_t<Op>* GetFirst() const {
for (OpIndex o : generated_output) {
if (auto result = instance->graph().Get(o).TryCast<Op>()) {
return result;
}
}
return nullptr;
}
template <typename Op>
const underlying_operation_t<Op>* GetAs() const {
DCHECK_EQ(generated_output.size(), 1);
return GetFirst<Op>();
}
const Operation* Get() const {
DCHECK_EQ(generated_output.size(), 1);
return &instance->graph().Get(*generated_output.begin());
}
};
template <typename Builder>
static TestInstance CreateFromGraph(PipelineData* data, int parameter_count,
const Builder& builder, Isolate* isolate,
Zone* zone) {
auto graph = std::make_unique<Graph>(zone);
TestInstance instance(data, std::move(graph), isolate, zone);
// Generate a function prolog
Block* start_block = instance.Asm().NewBlock();
instance.Asm().Bind(start_block);
instance.Asm().Parameter(3, RegisterRepresentation::Tagged(), "%context");
instance.Asm().Parameter(0, RegisterRepresentation::Tagged(), "%this");
for (int i = 0; i < parameter_count; ++i) {
instance.parameters_.push_back(
instance.Asm().Parameter(1 + i, RegisterRepresentation::Tagged()));
}
builder(instance);
return instance;
}
Assembler& Asm() { return assembler_; }
Graph& graph() { return *graph_; }
Factory& factory() { return *isolate_->factory(); }
Zone* zone() { return zone_; }
Assembler& operator()() { return Asm(); }
template <template <typename> typename... Reducers>
void Run(bool trace_reductions = v8_flags.turboshaft_trace_reduction) {
TSAssembler<GraphVisitor, Reducers...> phase(
data_, graph(), graph().GetOrCreateCompanion(), zone_);
#ifdef DEBUG
if (trace_reductions) {
phase.template VisitGraph<true>();
} else {
phase.template VisitGraph<false>();
}
#else
phase.template VisitGraph<false>();
#endif
// Map all captured inputs.
for (auto& [key, captured] : captured_operations_) {
std::set<OpIndex> temp = std::move(captured.generated_output);
for (OpIndex index : graph_->AllOperationIndices()) {
OpIndex origin = graph_->operation_origins()[index];
if (temp.contains(origin)) captured.generated_output.insert(index);
}
}
}
V<Object> GetParameter(int index) {
DCHECK_LE(0, index);
DCHECK_LT(index, parameters_.size());
return parameters_[index];
}
OpIndex BuildFrameState() {
FrameStateData::Builder builder;
// Closure
builder.AddInput(MachineType::AnyTagged(),
Asm().SmiConstant(Smi::FromInt(0)));
// TODO(nicohartmann@): Parameters, Context, Locals, Accumulator if
// necessary.
FrameStateFunctionInfo* function_info =
zone_->template New<FrameStateFunctionInfo>(
FrameStateType::kUnoptimizedFunction, 0, 0, 0,
Handle<SharedFunctionInfo>{}, Handle<BytecodeArray>{});
const FrameStateInfo* frame_state_info =
zone_->template New<FrameStateInfo>(BytecodeOffset(0),
OutputFrameStateCombine::Ignore(),
function_info);
return Asm().FrameState(
builder.Inputs(), builder.inlined(),
builder.AllocateFrameStateData(*frame_state_info, zone_));
}
OpIndex Capture(OpIndex input, const std::string& key) {
captured_operations_[key] =
CapturedOperation{this, input, std::set<OpIndex>{input}};
return input;
}
template <typename T>
V<T> Capture(V<T> input, const std::string& key) {
return V<T>::Cast(Capture(static_cast<OpIndex>(input), key));
}
const CapturedOperation& GetCapture(const std::string& key) const {
auto it = captured_operations_.find(key);
DCHECK_NE(it, captured_operations_.end());
return it->second;
}
const Operation* GetCaptured(const std::string& key) const {
return GetCapture(key).Get();
}
template <typename Op>
const underlying_operation_t<Op>* GetCapturedAs(
const std::string& key) const {
return GetCapture(key).GetAs<Op>();
}
size_t CountOp(Opcode opcode) {
auto operations = graph().AllOperations();
return std::count_if(
operations.begin(), operations.end(),
[opcode](const Operation& op) { return op.opcode == opcode; });
}
struct CaptureHelper {
TestInstance* instance;
std::string key;
OpIndex operator=(OpIndex value) { return instance->Capture(value, key); }
};
CaptureHelper CaptureHelperForMacro(const std::string& key) {
return CaptureHelper{this, std::move(key)};
}
void PrintGraphForTurbolizer(const char* phase_name) {
if (!stream_) {
const testing::TestInfo* test_info =
testing::UnitTest::GetInstance()->current_test_info();
std::stringstream file_name;
file_name << "turbo-" << test_info->test_suite_name() << "_"
<< test_info->name() << ".json";
stream_ = std::make_unique<std::ofstream>(file_name.str(),
std::ios_base::trunc);
*stream_ << "{\"function\" : ";
size_t len = strlen("test_generated_function") + 1;
auto name = std::make_unique<char[]>(len);
snprintf(name.get(), len, "test_generated_function");
JsonPrintFunctionSource(*stream_, -1, std::move(name),
DirectHandle<Script>{}, isolate_,
DirectHandle<SharedFunctionInfo>{});
*stream_ << ",\n\"phases\":[";
}
PrintTurboshaftGraphForTurbolizer(*stream_, graph(), phase_name, nullptr,
zone_);
}
private:
TestInstance(PipelineData* data, std::unique_ptr<Graph> graph,
Isolate* isolate, Zone* zone)
: data_(data),
assembler_(data, *graph, *graph, zone),
graph_(std::move(graph)),
isolate_(isolate),
zone_(zone) {}
PipelineData* data_;
Assembler assembler_;
std::unique_ptr<Graph> graph_;
std::unique_ptr<std::ofstream> stream_;
Isolate* isolate_;
Zone* zone_;
base::SmallMap<std::map<std::string, CapturedOperation>> captured_operations_;
base::SmallVector<OpIndex, 4> parameters_;
};
class ReducerTest : public TestWithNativeContextAndZone {
public:
template <typename Builder>
TestInstance CreateFromGraph(int parameter_count, const Builder& builder) {
return TestInstance::CreateFromGraph(pipeline_data_.get(), parameter_count,
builder, isolate(), zone());
}
void SetUp() override {
pipeline_data_.reset(new turboshaft::PipelineData(
&zone_stats_, TurboshaftPipelineKind::kJS, this->isolate(), nullptr,
AssemblerOptions::Default(this->isolate())));
}
void TearDown() override { pipeline_data_.reset(); }
ZoneStats zone_stats_{this->zone()->allocator()};
std::unique_ptr<turboshaft::PipelineData> pipeline_data_;
};
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,327 @@
// Copyright 2022 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.
#include "src/compiler/turboshaft/snapshot-table.h"
#include "src/base/vector.h"
#include "test/unittests/test-utils.h"
namespace v8::internal::compiler::turboshaft {
class SnapshotTableTest : public TestWithPlatform {};
TEST_F(SnapshotTableTest, BasicTest) {
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
using Key = SnapshotTable<int>::Key;
using Snapshot = SnapshotTable<int>::Snapshot;
SnapshotTable<int> table(&zone);
Key k1 = table.NewKey(1);
Key k2 = table.NewKey(2);
Key k3 = table.NewKey(3);
Key k4 = table.NewKey(4);
table.StartNewSnapshot();
EXPECT_EQ(table.Get(k1), 1);
EXPECT_EQ(table.Get(k2), 2);
EXPECT_EQ(table.Get(k3), 3);
EXPECT_EQ(table.Get(k4), 4);
table.Set(k1, 10);
table.Set(k2, 20);
table.Set(k4, 4);
EXPECT_EQ(table.Get(k1), 10);
EXPECT_EQ(table.Get(k2), 20);
EXPECT_EQ(table.Get(k3), 3);
EXPECT_EQ(table.Get(k4), 4);
Snapshot s1 = table.Seal();
table.StartNewSnapshot();
EXPECT_EQ(table.Get(k1), 1);
EXPECT_EQ(table.Get(k2), 2);
EXPECT_EQ(table.Get(k3), 3);
EXPECT_EQ(table.Get(k4), 4);
table.Set(k1, 11);
table.Set(k3, 33);
EXPECT_EQ(table.Get(k1), 11);
EXPECT_EQ(table.Get(k2), 2);
EXPECT_EQ(table.Get(k3), 33);
EXPECT_EQ(table.Get(k4), 4);
Snapshot s2 = table.Seal();
table.StartNewSnapshot(s2);
// Assignments of the same value are ignored.
EXPECT_EQ(table.Get(k1), 11);
table.Set(k1, 11);
// Sealing an empty snapshot does not produce a new snapshot.
EXPECT_EQ(table.Seal(), s2);
table.StartNewSnapshot({s1, s2},
[&](Key key, base::Vector<const int> values) {
if (key == k1) {
EXPECT_EQ(values[0], 10);
EXPECT_EQ(values[1], 11);
} else if (key == k2) {
EXPECT_EQ(values[0], 20);
EXPECT_EQ(values[1], 2);
} else if (key == k3) {
EXPECT_EQ(values[0], 3);
EXPECT_EQ(values[1], 33);
} else {
EXPECT_TRUE(false);
}
return values[0] + values[1];
});
EXPECT_EQ(table.Get(k1), 21);
EXPECT_EQ(table.Get(k2), 22);
EXPECT_EQ(table.Get(k3), 36);
EXPECT_EQ(table.Get(k4), 4);
table.Set(k1, 40);
EXPECT_EQ(table.Get(k1), 40);
EXPECT_EQ(table.Get(k2), 22);
EXPECT_EQ(table.Get(k3), 36);
EXPECT_EQ(table.Get(k4), 4);
EXPECT_EQ(table.GetPredecessorValue(k1, 0), 10);
EXPECT_EQ(table.GetPredecessorValue(k1, 1), 11);
EXPECT_EQ(table.GetPredecessorValue(k2, 0), 20);
EXPECT_EQ(table.GetPredecessorValue(k2, 1), 2);
EXPECT_EQ(table.GetPredecessorValue(k3, 0), 3);
EXPECT_EQ(table.GetPredecessorValue(k3, 1), 33);
table.Seal();
table.StartNewSnapshot({s1, s2});
EXPECT_EQ(table.Get(k1), 1);
EXPECT_EQ(table.Get(k2), 2);
EXPECT_EQ(table.Get(k3), 3);
EXPECT_EQ(table.Get(k4), 4);
table.Seal();
table.StartNewSnapshot(s2);
EXPECT_EQ(table.Get(k1), 11);
EXPECT_EQ(table.Get(k2), 2);
EXPECT_EQ(table.Get(k3), 33);
EXPECT_EQ(table.Get(k4), 4);
table.Set(k3, 30);
EXPECT_EQ(table.Get(k3), 30);
Snapshot s4 = table.Seal();
table.StartNewSnapshot({s4, s2},
[&](Key key, base::Vector<const int> values) {
if (key == k3) {
EXPECT_EQ(values[0], 30);
EXPECT_EQ(values[1], 33);
} else {
EXPECT_TRUE(false);
}
return values[0] + values[1];
});
EXPECT_EQ(table.Get(k1), 11);
EXPECT_EQ(table.Get(k2), 2);
EXPECT_EQ(table.Get(k3), 63);
EXPECT_EQ(table.Get(k4), 4);
EXPECT_EQ(table.GetPredecessorValue(k3, 0), 30);
EXPECT_EQ(table.GetPredecessorValue(k3, 1), 33);
table.Seal();
table.StartNewSnapshot(s2);
table.Set(k1, 5);
// Creating a new key while the SnapshotTable is already in use. This is the
// same as creating the key at the beginning.
Key k5 = table.NewKey(-1);
EXPECT_EQ(table.Get(k5), -1);
table.Set(k5, 42);
EXPECT_EQ(table.Get(k5), 42);
EXPECT_EQ(table.Get(k1), 5);
Snapshot s6 = table.Seal();
// We're merging {s6} and {s1}, to make sure that {s1}'s behavior is correct
// with regard to {k5}, which wasn't created yet when {s1} was sealed.
table.StartNewSnapshot({s6, s1},
[&](Key key, base::Vector<const int> values) {
if (key == k1) {
EXPECT_EQ(values[1], 10);
EXPECT_EQ(values[0], 5);
} else if (key == k2) {
EXPECT_EQ(values[1], 20);
EXPECT_EQ(values[0], 2);
} else if (key == k3) {
EXPECT_EQ(values[1], 3);
EXPECT_EQ(values[0], 33);
} else if (key == k5) {
EXPECT_EQ(values[0], 42);
EXPECT_EQ(values[1], -1);
return 127;
} else {
EXPECT_TRUE(false);
}
return values[0] + values[1];
});
EXPECT_EQ(table.Get(k1), 15);
EXPECT_EQ(table.Get(k2), 22);
EXPECT_EQ(table.Get(k3), 36);
EXPECT_EQ(table.Get(k4), 4);
EXPECT_EQ(table.Get(k5), 127);
EXPECT_EQ(table.GetPredecessorValue(k1, 0), 5);
EXPECT_EQ(table.GetPredecessorValue(k1, 1), 10);
EXPECT_EQ(table.GetPredecessorValue(k2, 0), 2);
EXPECT_EQ(table.GetPredecessorValue(k2, 1), 20);
EXPECT_EQ(table.GetPredecessorValue(k3, 0), 33);
EXPECT_EQ(table.GetPredecessorValue(k3, 1), 3);
EXPECT_EQ(table.GetPredecessorValue(k5, 0), 42);
EXPECT_EQ(table.GetPredecessorValue(k5, 1), -1);
// We're not setting anything else, but the merges should produce entries in
// the log.
Snapshot s7 = table.Seal();
table.StartNewSnapshot(s7);
// We're checking that {s7} did indeed capture the merge entries, despite
// that we didn't do any explicit Set.
EXPECT_EQ(table.Get(k1), 15);
EXPECT_EQ(table.Get(k2), 22);
EXPECT_EQ(table.Get(k3), 36);
EXPECT_EQ(table.Get(k4), 4);
EXPECT_EQ(table.Get(k5), 127);
table.Seal();
}
TEST_F(SnapshotTableTest, KeyData) {
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
struct Data {
int x;
};
using STable = SnapshotTable<int, Data>;
using Key = STable::Key;
STable table(&zone);
Key k1 = table.NewKey(Data{5}, 1);
EXPECT_EQ(k1.data().x, 5);
}
TEST_F(SnapshotTableTest, ChangeCallback) {
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
SnapshotTable<int> table(&zone);
using Key = decltype(table)::Key;
using Snapshot = decltype(table)::Snapshot;
Key k1 = table.NewKey(1);
table.StartNewSnapshot();
table.Set(k1, 5);
Snapshot s1 = table.Seal();
int invoked = 0;
table.StartNewSnapshot({}, [&](Key key, int old_value, int new_value) {
invoked++;
EXPECT_EQ(key, k1);
EXPECT_EQ(old_value, 5);
EXPECT_EQ(new_value, 1);
});
EXPECT_EQ(invoked, 1);
table.Set(k1, 7);
Snapshot s2 = table.Seal();
invoked = 0;
table.StartNewSnapshot(
{s1, s2},
[&](Key key, base::Vector<const int> values) {
EXPECT_EQ(key, k1);
EXPECT_EQ(values[0], 5);
EXPECT_EQ(values[1], 7);
return 10;
},
[&](Key key, int old_value, int new_value) {
// We are invoked twice because the table is rolled back first and then
// merged. But the only important invariant we should rely on is that
// the updates collectively transform the table into the new state.
switch (invoked++) {
case 0:
EXPECT_EQ(key, k1);
EXPECT_EQ(old_value, 7);
EXPECT_EQ(new_value, 1);
break;
case 1:
EXPECT_EQ(key, k1);
EXPECT_EQ(old_value, 1);
EXPECT_EQ(new_value, 10);
break;
default:
UNREACHABLE();
}
});
EXPECT_EQ(invoked, 2);
EXPECT_EQ(table.Get(k1), 10);
}
TEST_F(SnapshotTableTest, ChangeTrackingSnapshotTable) {
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
struct KeyData {
int id;
};
struct Table : ChangeTrackingSnapshotTable<Table, bool, KeyData> {
using ChangeTrackingSnapshotTable::ChangeTrackingSnapshotTable;
std::set<int> active_keys;
void OnNewKey(Key key, bool value) {
if (value) {
active_keys.insert(key.data().id);
}
}
void OnValueChange(Key key, bool old_value, bool new_value) {
if (old_value && !new_value) {
active_keys.erase(key.data().id);
} else if (!old_value && new_value) {
active_keys.insert(key.data().id);
}
}
} table(&zone);
using Key = Table::Key;
using Snapshot = Table::Snapshot;
Key k1 = table.NewKey(KeyData{5}, true);
Key k2 = table.NewKey(KeyData{7}, false);
table.StartNewSnapshot();
EXPECT_EQ(table.active_keys, std::set<int>({5}));
table.Set(k2, true);
EXPECT_EQ(table.active_keys, std::set<int>({5, 7}));
Snapshot s1 = table.Seal();
table.StartNewSnapshot();
EXPECT_EQ(table.active_keys, std::set<int>({5}));
table.Set(k1, false);
EXPECT_EQ(table.active_keys, std::set<int>({}));
table.Set(k2, true);
EXPECT_EQ(table.active_keys, std::set<int>({7}));
Snapshot s2 = table.Seal();
table.StartNewSnapshot({s1, s2},
[&](Key key, base::Vector<const bool> values) {
EXPECT_EQ(values.size(), 2u);
return values[0] ^ values[1];
});
EXPECT_EQ(table.active_keys, std::set<int>({5}));
table.Seal();
table.StartNewSnapshot({s1, s2},
[&](Key key, base::Vector<const bool> values) {
EXPECT_EQ(values.size(), 2u);
return values[0] || values[1];
});
EXPECT_EQ(table.active_keys, std::set<int>({5, 7}));
table.Seal();
}
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,58 @@
// Copyright 2024 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.
#include "src/compiler/turboshaft/assembler.h"
#include "src/compiler/turboshaft/copying-phase.h"
#include "src/compiler/turboshaft/operations.h"
#include "src/compiler/turboshaft/store-store-elimination-reducer-inl.h"
#include "test/unittests/compiler/turboshaft/reducer-test.h"
namespace v8::internal::compiler::turboshaft {
#include "src/compiler/turboshaft/define-assembler-macros.inc"
class StoreStoreEliminationReducerTest : public ReducerTest {};
TEST_F(StoreStoreEliminationReducerTest, MergeObjectInitialzationStore) {
auto test = CreateFromGraph(1, [](auto& Asm) {
OpIndex param0 = Asm.GetParameter(0);
OpIndex heap_const0 = __ HeapConstant(Asm.factory().undefined_value());
OpIndex heap_const1 = __ HeapConstant(Asm.factory().null_value());
__ Store(param0, heap_const0, StoreOp::Kind::TaggedBase(),
MemoryRepresentation::TaggedPointer(),
WriteBarrierKind::kNoWriteBarrier, 0, true);
OpIndex store0 = __ output_graph().LastOperation();
DCHECK(__ output_graph().Get(store0).template Is<StoreOp>());
Asm.Capture(store0, "store0");
__ Store(param0, heap_const1, StoreOp::Kind::TaggedBase(),
MemoryRepresentation::AnyTagged(),
WriteBarrierKind::kNoWriteBarrier, 4, true);
OpIndex store1 = __ output_graph().LastOperation();
DCHECK(__ output_graph().Get(store1).template Is<StoreOp>());
Asm.Capture(store1, "store1");
__ Return(param0);
});
test.Run<StoreStoreEliminationReducer>();
#ifdef V8_COMPRESS_POINTERS
const auto& store0_out = test.GetCapture("store0");
const StoreOp* store64 = store0_out.GetFirst<StoreOp>();
ASSERT_TRUE(store64 != nullptr);
ASSERT_EQ(store64->kind, StoreOp::Kind::TaggedBase());
ASSERT_EQ(store64->stored_rep, MemoryRepresentation::Uint64());
ASSERT_EQ(store64->write_barrier, WriteBarrierKind::kNoWriteBarrier);
const auto& store1_out = test.GetCapture("store1");
ASSERT_TRUE(store1_out.IsEmpty());
#endif // V8_COMPRESS_POINTERS
}
#include "src/compiler/turboshaft/undef-assembler-macros.inc"
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,344 @@
// Copyright 2023 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.
#include <limits>
#include "src/common/globals.h"
#include "src/compiler/turboshaft/typer.h"
#include "src/handles/handles.h"
#include "test/unittests/test-utils.h"
namespace v8::internal::compiler::turboshaft {
template <typename T>
class WordTyperTest : public TestWithNativeContextAndZone {
public:
using word_t = typename T::word_t;
static constexpr size_t Bits = sizeof(word_t) * kBitsPerByte;
WordTyperTest() : TestWithNativeContextAndZone() {}
};
template <typename T>
class FloatTyperTest : public TestWithNativeContextAndZone {
public:
using float_t = typename T::float_t;
static constexpr size_t Bits = sizeof(float_t) * kBitsPerByte;
FloatTyperTest() : TestWithNativeContextAndZone() {}
};
template <typename T>
struct Slices {
Slices(std::initializer_list<T> slices) : slices(slices) {}
std::vector<T> slices;
};
template <typename T>
inline std::ostream& operator<<(std::ostream& os, const Slices<T>& slices) {
os << "Slices{";
for (const auto& s : slices.slices) os << s << ", ";
return os << "}";
}
// We define operator<= here for Type so that we can use gtest's EXPECT_LE to
// check for subtyping and have the default printing.
inline bool operator<=(const Type& lhs, const Type& rhs) {
return lhs.IsSubtypeOf(rhs);
}
template <typename T>
inline bool operator<=(const Slices<T>& lhs, const T& rhs) {
for (const auto& s : lhs.slices) {
if (!s.IsSubtypeOf(rhs)) return false;
}
return true;
}
using WordTypes = ::testing::Types<Word32Type, Word64Type>;
TYPED_TEST_SUITE(WordTyperTest, WordTypes);
#define DEFINE_TEST_HELPERS() \
using T = TypeParam; \
using word_t = typename TestFixture::word_t; \
using Slices = Slices<T>; \
constexpr word_t max = std::numeric_limits<word_t>::max(); \
auto Constant = [&](word_t value) { return T::Constant(value); }; \
auto Set = [&](std::initializer_list<word_t> elements) { \
return WordOperationTyper<TestFixture::Bits>::FromElements(elements, \
this->zone()); \
}; \
auto Range = [&](word_t from, word_t to) { \
return T::Range(from, to, this->zone()); \
}; \
USE(Slices{}, Constant, Set, Range);
TYPED_TEST(WordTyperTest, Add) {
DEFINE_TEST_HELPERS()
#define EXPECT_ADD(lhs, rhs, result) \
EXPECT_LE(result, WordOperationTyper<TestFixture::Bits>::Add(lhs, rhs, \
this->zone())); \
EXPECT_LE(result, WordOperationTyper<TestFixture::Bits>::Add(rhs, lhs, \
this->zone()))
// Adding any.
{
// Any + Any
EXPECT_ADD(T::Any(), T::Any(), T::Any());
// c + Any
EXPECT_ADD(Constant(42), T::Any(), T::Any());
// {x1, ..., xn} + Any
EXPECT_ADD(Set({8, 11, 922}), T::Any(), T::Any());
// [a, b] + Any
EXPECT_ADD(Range(800, 1020), T::Any(), T::Any());
}
// Adding constants.
{
// c' + c
EXPECT_ADD(Constant(8), Constant(10003), Constant(8 + 10003));
EXPECT_ADD(Constant(max), Constant(0), Constant(max));
EXPECT_ADD(Constant(max - 8), Constant(12), Constant(3));
EXPECT_ADD(Constant(max), Constant(max), Constant(max - 1));
// {x1, ..., xn} + c
auto set1 = Set({0, 87});
EXPECT_ADD(set1, Constant(0), set1);
EXPECT_ADD(set1, Constant(2005), Set({2005, 2092}));
EXPECT_ADD(set1, Constant(max - 4), Set({82, max - 4}));
EXPECT_ADD(set1, Constant(max), Set({86, max}));
auto set2 = Set({15, 25025, max - 99});
EXPECT_ADD(set2, Constant(0), set2);
EXPECT_ADD(set2, Constant(4), Set({19, 25029, max - 95}));
EXPECT_ADD(set2, Constant(max - 50), Set({24974, max - 150, max - 35}));
EXPECT_ADD(set2, Constant(max), Set({14, 25024, max - 100}));
// [a, b](non-wrapping) + c
auto range1 = Range(13, 288);
EXPECT_ADD(range1, Constant(0), range1);
EXPECT_ADD(range1, Constant(812), Range(825, 1100));
EXPECT_ADD(range1, Constant(max - 103), Range(max - 90, 184));
EXPECT_ADD(range1, Constant(max - 5), Range(7, 282));
EXPECT_ADD(range1, Constant(max), Range(12, 287));
// [a, b](wrapping) + c
auto range2 = Range(max - 100, 70);
EXPECT_ADD(range2, Constant(0), range2);
EXPECT_ADD(range2, Constant(14), Range(max - 86, 84));
EXPECT_ADD(range2, Constant(101), Range(0, 171));
EXPECT_ADD(range2, Constant(200), Range(99, 270));
EXPECT_ADD(range2, Constant(max), Range(max - 101, 69));
}
// Adding sets.
{
// {y1, ..., ym} + {x1, ..., xn}
auto set1 = Set({0, 87});
EXPECT_ADD(set1, set1, Set({0, 87, (87 + 87)}));
EXPECT_ADD(set1, Set({3, 4, 5}), Set({3, 4, 5, 90, 91}));
EXPECT_ADD(set1, Set({3, 7, 11, 114}),
Set({3, 7, 11, 90, 94, 98, 114, 201}));
EXPECT_ADD(set1, Set({0, 1, 87, 200, max}),
Set({0, 1, 86, 87, 88, 174, 200, 287, max}));
EXPECT_ADD(set1, Set({max - 86, max - 9, max}),
Set({0, 77, 86, max - 86, max - 9, max}));
// [a, b](non-wrapping) + {x1, ..., xn}
auto range1 = Range(400, 991);
EXPECT_ADD(range1, Set({0, 55}), Range(400, 1046));
EXPECT_ADD(range1, Set({49, 110, 100009}), Range(449, 101000));
EXPECT_ADD(
range1, Set({112, max - 10094, max - 950}),
Slices({Range(0, 40), Range(512, 1103), Range(max - 9694, max)}));
EXPECT_ADD(range1, Set({112, max - 850}),
Slices({Range(512, 1103), Range(max - 450, 140)}));
EXPECT_ADD(range1, Set({max - 3, max - 1, max}), Range(396, 990));
// [a,b](wrapping) + {x1, ..., xn}
auto range2 = Range(max - 30, 82);
EXPECT_ADD(range2, Set({0, 20}),
Slices({Range(max - 30, 82), Range(max - 10, 102)}));
EXPECT_ADD(range2, Set({20, 30, 32, max}),
Slices({Range(max - 10, 101), Range(0, 112), Range(1, 114),
Range(max - 31, 81)}));
EXPECT_ADD(range2, Set({1000, 2000}),
Slices({Range(969, 1082), Range(1969, 2082)}));
EXPECT_ADD(range2, Set({max - 8, max - 2}),
Slices({Range(max - 39, 73), Range(max - 33, 79)}));
}
// Adding ranges.
{
// [a, b](non-wrapping) + [c, d](non-wrapping)
auto range1 = Range(30, 990);
EXPECT_ADD(range1, Range(0, 2), Range(30, 992));
EXPECT_ADD(range1, Range(1000, 22000), Range(1030, 22990));
EXPECT_ADD(range1, Range(0, max - 1000), Range(30, max - 10));
EXPECT_ADD(range1, Range(max - 800, max - 700), Range(max - 770, 289));
EXPECT_ADD(range1, Range(max - 5, max), Range(24, 989));
// [a, b](wrapping) + [c, d](non-wrapping)
auto range2 = Range(max - 40, 40);
EXPECT_ADD(range2, Range(0, 8), Range(max - 40, 48));
EXPECT_ADD(range2, Range(2000, 90000), Range(1959, 90040));
EXPECT_ADD(range2, Range(max - 400, max - 200),
Range(max - 441, max - 160));
EXPECT_ADD(range2, Range(0, max - 82), Range(max - 40, max - 42));
EXPECT_ADD(range2, Range(0, max - 81), T::Any());
EXPECT_ADD(range2, Range(20, max - 20), T::Any());
// [a, b](wrapping) + [c, d](wrapping)
EXPECT_ADD(range2, range2, Range(max - 81, 80));
EXPECT_ADD(range2, Range(max - 2, 2), Range(max - 43, 42));
EXPECT_ADD(range2, Range(1000, 100), Range(959, 140));
}
#undef EXPECT_ADD
}
TYPED_TEST(WordTyperTest, WidenExponential) {
DEFINE_TEST_HELPERS()
auto SizeOf = [&](const T& type) -> word_t {
DCHECK(!type.is_any());
if (type.is_set()) return type.set_size();
if (type.is_wrapping()) {
return type.range_to() + (max - type.range_from()) + word_t{2};
}
return type.range_to() - type.range_from() + word_t{1};
};
auto DoubledInSize = [&](const T& old_type, const T& new_type) {
// If the `new_type` is any, we accept it.
if (new_type.is_any()) return true;
return SizeOf(old_type) <= 2 * SizeOf(new_type);
};
#define EXPECT_WEXP(old_type, new_type) \
{ \
const T ot = old_type; \
const T nt = new_type; \
auto result = WordOperationTyper<TestFixture::Bits>::WidenExponential( \
ot, nt, this->zone()); \
EXPECT_LE(ot, result); \
EXPECT_LE(nt, result); \
EXPECT_TRUE(DoubledInSize(ot, result)); \
}
// c W set
EXPECT_WEXP(Constant(0), Set({0, 1}));
EXPECT_WEXP(Constant(0), Set({0, 3}));
EXPECT_WEXP(Constant(0), Set({0, 1, max}));
EXPECT_WEXP(Constant(0), Set({0, 1, 2, max - 2, max - 1, max}));
EXPECT_WEXP(Constant(max), Set({0, 1, 2, max - 2, max}));
// c W range
EXPECT_WEXP(Constant(0), Range(0, 100));
EXPECT_WEXP(Constant(100), Range(50, 100));
EXPECT_WEXP(Constant(100), Range(50, 150));
EXPECT_WEXP(Constant(0), Range(max - 10, 0));
EXPECT_WEXP(Constant(0), Range(max - 10, 10));
EXPECT_WEXP(Constant(50), Range(max - 10000, 100));
EXPECT_WEXP(Constant(max), T::Any());
// set W set
EXPECT_WEXP(Set({0, 1}), Set({0, 1, 2}));
EXPECT_WEXP(Set({0, 1}), Set({0, 1, 2, 3, 4}));
EXPECT_WEXP(Set({0, max}), Set({0, 1, max}));
EXPECT_WEXP(Set({8, max - 8}), Set({7, 8, max - 8, max - 7}));
EXPECT_WEXP(Set({3, 5, 7, 11}), Set({2, 3, 5, 7, 11}));
// set W range
EXPECT_WEXP(Set({3, 5, 7, 11}), Range(3, 11));
EXPECT_WEXP(Set({3, 5, 7, 11}), Range(0, 11));
EXPECT_WEXP(Set({3, 5, 7, 11}), Range(3, 100));
EXPECT_WEXP(Set({3, 5, 7, 11}), Range(max, 11));
EXPECT_WEXP(Set({3, 5, 7, 11}), Range(max - 100, 100));
EXPECT_WEXP(Set({3, 5, 7, 11}), T::Any());
// range W range
EXPECT_WEXP(Range(0, 20), Range(0, 21));
EXPECT_WEXP(Range(0, 20), Range(0, 220));
EXPECT_WEXP(Range(0, 20), Range(max, 20));
EXPECT_WEXP(Range(0, 20), Range(max - 200, 20));
EXPECT_WEXP(Range(0, 20), T::Any());
EXPECT_WEXP(Range(max - 100, max - 80), Range(max - 101, max - 80));
EXPECT_WEXP(Range(max - 100, max - 80), Range(max - 100, max - 79));
EXPECT_WEXP(Range(max - 100, max - 80), Range(max - 101, max - 79));
EXPECT_WEXP(Range(max - 100, max - 80), Range(max - 200, 20));
EXPECT_WEXP(Range(max - 100, max - 80), T::Any());
EXPECT_WEXP(Range(max - 20, 0), Range(max - 20, 1));
EXPECT_WEXP(Range(max - 20, 20), Range(max - 20, 21));
EXPECT_WEXP(Range(max - 20, 20), Range(max - 21, 20));
EXPECT_WEXP(Range(max - 20, 20), Range(max - 21, 21));
EXPECT_WEXP(Range(max - 20, 20), Range(max - 2000, 2000));
EXPECT_WEXP(Range(max - 20, 20), T::Any());
#undef EXPECT_WEXP
}
#undef DEFINE_TEST_HELPERS
using FloatTypes = ::testing::Types<Float32Type, Float64Type>;
TYPED_TEST_SUITE(FloatTyperTest, FloatTypes);
#define DEFINE_TEST_HELPERS() \
using T = TypeParam; \
using float_t = typename TestFixture::float_t; \
using Slices = Slices<T>; \
auto Constant = [&](float_t value) { return T::Constant(value); }; \
auto Set = [&](std::initializer_list<float_t> elements, \
uint32_t special_values = 0) { \
return T::Set(elements, special_values, this->zone()); \
}; \
auto Range = [&](float_t from, float_t to, uint32_t special_values = 0) { \
return T::Range(from, to, special_values, this->zone()); \
}; \
constexpr uint32_t kNaN = T::kNaN; \
constexpr uint32_t kMZ = T::kMinusZero; \
constexpr float_t nan = nan_v<TestFixture::Bits>; \
constexpr float_t inf = std::numeric_limits<float_t>::infinity(); \
USE(Slices{}, Constant, Set, Range); \
USE(kNaN, kMZ, nan, inf);
TYPED_TEST(FloatTyperTest, Divide) {
DEFINE_TEST_HELPERS()
#define EXPECT_DIV(lhs, rhs, result) \
EXPECT_LE(result, FloatOperationTyper<TestFixture::Bits>::Divide( \
lhs, rhs, this->zone()))
// 0 / x
EXPECT_DIV(Constant(0.0), T::Any(), Set({0}, kNaN | kMZ));
EXPECT_DIV(T::MinusZero(), T::Any(), Set({0}, kNaN | kMZ));
EXPECT_DIV(Constant(0.0), Range(0.001, inf), Constant(0));
EXPECT_DIV(T::MinusZero(), Range(0.001, inf), T::MinusZero());
EXPECT_DIV(Constant(0.0), Range(-inf, -0.001), T::MinusZero());
EXPECT_DIV(T::MinusZero(), Range(-inf, -0.001), Constant(0));
EXPECT_DIV(Set({0.0}, kMZ), Constant(3), Set({0}, kMZ));
EXPECT_DIV(Set({0.0}), Set({-2.5, 0.0, 1.5}), Set({0.0}, kNaN | kMZ));
EXPECT_DIV(Set({0.0}, kMZ), Set({-2.5, 0.0, 1.5}), Set({0.0}, kNaN | kMZ));
EXPECT_DIV(Set({0.0}), Set({1.5}, kMZ), Set({0.0}, kNaN));
EXPECT_DIV(Set({0.0}, kMZ), Set({1.5}, kMZ), Set({0.0}, kNaN | kMZ));
// x / 0
EXPECT_DIV(Constant(1.0), Constant(0), Constant(inf));
EXPECT_DIV(Constant(1.0), T::MinusZero(), Constant(-inf));
EXPECT_DIV(Constant(inf), Constant(0), Constant(inf));
EXPECT_DIV(Constant(inf), T::MinusZero(), Constant(-inf));
EXPECT_DIV(Constant(-1.0), Constant(0), Constant(-inf));
EXPECT_DIV(Constant(-1.0), T::MinusZero(), Constant(inf));
EXPECT_DIV(Constant(-inf), Constant(0), Constant(-inf));
EXPECT_DIV(Constant(-inf), T::MinusZero(), Constant(inf));
EXPECT_DIV(Constant(1.5), Set({0.0}, kMZ), Set({-inf, inf}));
EXPECT_DIV(Constant(-1.5), Set({0.0}, kMZ), Set({-inf, inf}));
EXPECT_DIV(Set({1.5}, kMZ), Set({0.0}, kMZ), Set({-inf, inf}, kNaN));
EXPECT_DIV(Set({-1.5}, kMZ), Set({0.0}, kMZ), Set({-inf, inf}, kNaN));
// 0 / 0
EXPECT_DIV(Constant(0), Constant(0), T::NaN());
EXPECT_DIV(Constant(0), T::MinusZero(), T::NaN());
EXPECT_DIV(T::MinusZero(), Constant(0), T::NaN());
EXPECT_DIV(T::MinusZero(), T::MinusZero(), T::NaN());
EXPECT_DIV(Set({0}, kMZ), Set({1}, kMZ), Set({0}, kNaN | kMZ));
// inf / inf
EXPECT_DIV(Constant(inf), Constant(inf), T::NaN());
EXPECT_DIV(Constant(inf), Constant(-inf), T::NaN());
EXPECT_DIV(Constant(-inf), Constant(inf), T::NaN());
EXPECT_DIV(Constant(-inf), Constant(-inf), T::NaN());
EXPECT_DIV(Set({-inf, inf}), Constant(inf), T::NaN());
EXPECT_DIV(Set({-inf, inf}), Constant(-inf), T::NaN());
EXPECT_DIV(Set({-inf, inf}), Set({-inf, inf}), T::NaN());
}
#undef DEFINE_TEST_HELPERS
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,785 @@
// Copyright 2022 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.
#include "src/compiler/turboshaft/types.h"
#include "src/handles/handles.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace v8::internal::compiler::turboshaft {
class TurboshaftTypesTest : public TestWithNativeContextAndZone {
public:
using Kind = Type::Kind;
TurboshaftTypesTest() : TestWithNativeContextAndZone() {}
};
TEST_F(TurboshaftTypesTest, Word32) {
const auto max_value = std::numeric_limits<Word32Type::word_t>::max();
// Complete range
{
Word32Type t = Word32Type::Any();
EXPECT_TRUE(Word32Type::Constant(0).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Constant(800).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Constant(max_value).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Set({0, 1}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Set({0, max_value}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Set({3, 9, max_value - 1}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(0, 10, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(800, 1200, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(1, max_value - 1, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(0, max_value, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(max_value - 20, 20, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(1000, 999, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Any().IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Range (non-wrapping)
{
Word32Type t = Word32Type::Range(100, 300, zone());
EXPECT_TRUE(!Word32Type::Constant(0).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Constant(99).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Constant(100).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Constant(250).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Constant(300).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Constant(301).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Set({0, 150}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Set({99, 100}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Set({100, 105}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Set({150, 200, 250}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Set({150, 300}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Set({300, 301}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(50, 150, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(99, 150, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(100, 150, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(150, 250, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(250, 300, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(250, 301, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(99, 301, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(800, 9000, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Word32Type::Range(max_value - 100, 100, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(250, 200, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Any().IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Range (wrapping)
{
const auto large_value = max_value - 1000;
Word32Type t = Word32Type::Range(large_value, 800, zone());
EXPECT_TRUE(Word32Type::Constant(0).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Constant(800).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Constant(801).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Constant(5000).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Constant(large_value - 1).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Constant(large_value).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Constant(large_value + 5).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Constant(max_value).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Set({0, 800}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Set({0, 801}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Set({0, 600, 900}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Set({0, max_value}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Set({100, max_value - 100}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Set({large_value - 1, large_value + 5}, zone())
.IsSubtypeOf(t));
EXPECT_TRUE(
Word32Type::Set({large_value, large_value + 5, max_value - 5}, zone())
.IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(0, 800, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(100, 300, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(0, 801, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Word32Type::Range(200, max_value - 200, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Word32Type::Range(large_value - 1, max_value, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
Word32Type::Range(large_value, max_value, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(large_value + 100, max_value - 100, zone())
.IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Range(large_value, 800, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
Word32Type::Range(large_value + 100, 700, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Word32Type::Range(large_value - 1, 799, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Word32Type::Range(large_value + 1, 801, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(5000, 100, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Any().IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Set
{
CHECK_GT(Word32Type::kMaxSetSize, 2);
Word32Type t = Word32Type::Set({4, 890}, zone());
EXPECT_TRUE(!Word32Type::Constant(0).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Constant(3).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Constant(4).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Constant(5).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Constant(889).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Constant(890).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Set({0, 4}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Set({4, 90}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word32Type::Set({4, 890}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Set({0, 4, 890}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Set({4, 890, 1000}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Set({890, max_value}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(0, 100, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(4, 890, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(800, 900, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(800, 100, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(890, 4, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Range(max_value - 5, 4, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word32Type::Any().IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
}
TEST_F(TurboshaftTypesTest, Word64) {
const auto max_value = std::numeric_limits<Word64Type::word_t>::max();
// Complete range
{
Word64Type t = Word64Type::Any();
EXPECT_TRUE(Word64Type::Constant(0).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Constant(800).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Constant(max_value).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Set({0, 1}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Set({0, max_value}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Set({3, 9, max_value - 1}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(0, 10, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(800, 1200, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(1, max_value - 1, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(0, max_value, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(max_value - 20, 20, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(1000, 999, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Any().IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Range (non-wrapping)
{
Word64Type t = Word64Type::Range(100, 300, zone());
EXPECT_TRUE(!Word64Type::Constant(0).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Constant(99).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Constant(100).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Constant(250).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Constant(300).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Constant(301).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Set({0, 150}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Set({99, 100}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Set({100, 105}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Set({150, 200, 250}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Set({150, 300}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Set({300, 301}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(50, 150, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(99, 150, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(100, 150, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(150, 250, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(250, 300, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(250, 301, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(99, 301, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(800, 9000, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Word64Type::Range(max_value - 100, 100, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(250, 200, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Any().IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Range (wrapping)
{
const auto large_value = max_value - 1000;
Word64Type t = Word64Type::Range(large_value, 800, zone());
EXPECT_TRUE(Word64Type::Constant(0).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Constant(800).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Constant(801).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Constant(5000).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Constant(large_value - 1).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Constant(large_value).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Constant(large_value + 5).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Constant(max_value).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Set({0, 800}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Set({0, 801}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Set({0, 600, 900}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Set({0, max_value}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Set({100, max_value - 100}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Set({large_value - 1, large_value + 5}, zone())
.IsSubtypeOf(t));
EXPECT_TRUE(
Word64Type::Set({large_value, large_value + 5, max_value - 5}, zone())
.IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(0, 800, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(100, 300, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(0, 801, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Word64Type::Range(200, max_value - 200, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Word64Type::Range(large_value - 1, max_value, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
Word64Type::Range(large_value, max_value, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(large_value + 100, max_value - 100, zone())
.IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Range(large_value, 800, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
Word64Type::Range(large_value + 100, 700, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Word64Type::Range(large_value - 1, 799, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Word64Type::Range(large_value + 1, 801, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(5000, 100, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Any().IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Set
{
CHECK_GT(Word64Type::kMaxSetSize, 2);
Word64Type t = Word64Type::Set({4, 890}, zone());
EXPECT_TRUE(!Word64Type::Constant(0).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Constant(3).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Constant(4).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Constant(5).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Constant(889).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Constant(890).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Set({0, 4}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Set({4, 90}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Word64Type::Set({4, 890}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Set({0, 4, 890}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Set({4, 890, 1000}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Set({890, max_value}, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(0, 100, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(4, 890, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(800, 900, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(800, 100, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(890, 4, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Range(max_value - 5, 4, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Word64Type::Any().IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
}
TEST_F(TurboshaftTypesTest, Float32) {
const auto large_value =
std::numeric_limits<Float32Type::float_t>::max() * 0.99f;
const auto inf = std::numeric_limits<Float32Type::float_t>::infinity();
const auto kNaN = Float32Type::kNaN;
const auto kMinusZero = Float32Type::kMinusZero;
const auto kNoSpecialValues = Float32Type::kNoSpecialValues;
// Complete range (with NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = kMinusZero | (with_nan ? kNaN : kNoSpecialValues);
Float32Type t = Float32Type::Any(kNaN | kMinusZero);
EXPECT_TRUE(Float32Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(0.0f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::MinusZero().IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(391.113f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Set({0.13f, 91.0f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
Float32Type::Set({-100.4f, large_value}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Set({-inf, inf}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Range(0.0f, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Range(-inf, 12.3f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Range(-inf, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Complete range (without NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = kMinusZero | (with_nan ? kNaN : kNoSpecialValues);
Float32Type t = Float32Type::Any(kMinusZero);
EXPECT_TRUE(!Float32Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(0.0f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::MinusZero().IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(391.113f).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float32Type::Set({0.13f, 91.0f}, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(
!with_nan,
Float32Type::Set({-100.4f, large_value}, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float32Type::Set({-inf, inf}, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float32Type::Range(0.0f, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float32Type::Range(-inf, 12.3f, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float32Type::Range(-inf, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan, Float32Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Range (with NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = kMinusZero | (with_nan ? kNaN : kNoSpecialValues);
Float32Type t =
Float32Type::Range(-1.0f, 3.14159f, kNaN | kMinusZero, zone());
EXPECT_TRUE(Float32Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(-100.0f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(-1.01f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(-1.0f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(-0.99f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::MinusZero().IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(0.0f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(3.14159f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(3.15f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Set({-0.5f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Set({-1.1f, 1.5f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Set({-0.9f, 1.88f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Set({0.0f, 3.142f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Set({-inf, 0.3f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(-inf, 0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(-1.01f, 0.0f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Range(-1.0f, 1.0f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Range(0.0f, 3.14159f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(0.0f, 3.142f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(3.0f, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Range (without NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = kMinusZero | (with_nan ? kNaN : kNoSpecialValues);
Float32Type t = Float32Type::Range(-1.0f, 3.14159f, kMinusZero, zone());
EXPECT_TRUE(!Float32Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(-100.0f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(-1.01f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(-1.0f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(-0.99f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::MinusZero().IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(0.0f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(3.14159f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(3.15f).IsSubtypeOf(t));
EXPECT_EQ(!with_nan, Float32Type::Set({-0.5f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Set({-1.1f, 1.5f}, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float32Type::Set({-0.9f, 1.88f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Set({0.0f, 3.142f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Set({-inf, 0.3f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(-inf, 0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(-1.01f, 0.0f, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float32Type::Range(-1.0f, 1.0f, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float32Type::Range(0.0f, 3.14159f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(0.0f, 3.142f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(3.0f, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Set (with NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = with_nan ? kNaN : kNoSpecialValues;
Float32Type t = Float32Type::Set({-1.0f, 3.14159f}, kNaN, zone());
EXPECT_TRUE(Float32Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(-100.0f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(-1.01f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(-1.0f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(1.0f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(3.14159f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(3.1415f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(inf).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Set({-inf, 0.0f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Set({-1.0f, 0.0f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Set({-1.0f, 3.14159f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Float32Type::Set({3.14159f, 3.1416f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(-inf, -1.0f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(-1.01f, -1.0f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Float32Type::Range(-1.0f, 3.14159f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(3.14159f, 4.0f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Set (without NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = with_nan ? kNaN : kNoSpecialValues;
Float32Type t =
Float32Type::Set({-1.0f, 3.14159f}, kNoSpecialValues, zone());
EXPECT_TRUE(!Float32Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(-100.0f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(-1.01f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(-1.0f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(1.0f).IsSubtypeOf(t));
EXPECT_TRUE(Float32Type::Constant(3.14159f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(3.1415f).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Constant(inf).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Set({-inf, 0.0f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Set({-1.0f, 0.0f}, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float32Type::Set({-1.0f, 3.14159f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Float32Type::Set({3.14159f, 3.1416f}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(-inf, -1.0f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(-1.01f, -1.0f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Float32Type::Range(-1.0f, 3.14159f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Range(3.14159f, 4.0f, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float32Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// -0.0f corner cases
{
EXPECT_TRUE(!Float32Type::MinusZero().IsSubtypeOf(
Float32Type::Set({0.0f, 1.0f}, zone())));
EXPECT_TRUE(
!Float32Type::Constant(0.0f).IsSubtypeOf(Float32Type::MinusZero()));
EXPECT_TRUE(
Float32Type::Set({3.2f}, kMinusZero, zone())
.IsSubtypeOf(Float32Type::Range(0.0f, 4.0f, kMinusZero, zone())));
EXPECT_TRUE(!Float32Type::Set({-1.0f, 0.0f}, kMinusZero, zone())
.IsSubtypeOf(Float32Type::Range(-inf, 0.0f, zone())));
}
}
TEST_F(TurboshaftTypesTest, Float64) {
const auto large_value =
std::numeric_limits<Float64Type::float_t>::max() * 0.99;
const auto inf = std::numeric_limits<Float64Type::float_t>::infinity();
const auto kNaN = Float64Type::kNaN;
const auto kMinusZero = Float64Type::kMinusZero;
const auto kNoSpecialValues = Float64Type::kNoSpecialValues;
// Complete range (with NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = kMinusZero | (with_nan ? kNaN : kNoSpecialValues);
Float64Type t = Float64Type::Any(kNaN | kMinusZero);
EXPECT_TRUE(Float64Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(0.0).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::MinusZero().IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(391.113).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Set({0.13, 91.0}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
Float64Type::Set({-100.4, large_value}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Set({-inf, inf}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Range(0.0, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Range(-inf, 12.3, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Range(-inf, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Complete range (without NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = kMinusZero | (with_nan ? kNaN : kNoSpecialValues);
Float64Type t = Float64Type::Any(kMinusZero);
EXPECT_TRUE(!Float64Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(0.0).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::MinusZero().IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(391.113).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float64Type::Set({0.13, 91.0}, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(
!with_nan,
Float64Type::Set({-100.4, large_value}, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float64Type::Set({-inf, inf}, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float64Type::Range(0.0, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float64Type::Range(-inf, 12.3, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float64Type::Range(-inf, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan, Float64Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Range (with NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = kMinusZero | (with_nan ? kNaN : kNoSpecialValues);
Float64Type t =
Float64Type::Range(-1.0, 3.14159, kNaN | kMinusZero, zone());
EXPECT_TRUE(Float64Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(-100.0).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(-1.01).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(-1.0).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(-0.99).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::MinusZero().IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(0.0).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(3.14159).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(3.15).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Set({-0.5}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Set({-1.1, 1.5}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Set({-0.9, 1.88}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Set({0.0, 3.142}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Set({-inf, 0.3}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(-inf, 0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(-1.01, 0.0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Range(-1.0, 1.0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Range(0.0, 3.14159, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(0.0, 3.142, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(3.0, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Range (without NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = kMinusZero | (with_nan ? kNaN : kNoSpecialValues);
Float64Type t = Float64Type::Range(-1.0, 3.14159, kMinusZero, zone());
EXPECT_TRUE(!Float64Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(-100.0).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(-1.01).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(-1.0).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(-0.99).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::MinusZero().IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(0.0).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(3.14159).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(3.15).IsSubtypeOf(t));
EXPECT_EQ(!with_nan, Float64Type::Set({-0.5}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Set({-1.1, 1.5}, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float64Type::Set({-0.9, 1.88}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Set({0.0, 3.142}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Set({-inf, 0.3}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(-inf, 0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(-1.01, 0.0, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float64Type::Range(-1.0, 1.0, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float64Type::Range(0.0, 3.14159, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(0.0, 3.142, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(3.0, inf, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Set (with NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = with_nan ? kNaN : kNoSpecialValues;
Float64Type t = Float64Type::Set({-1.0, 3.14159}, kNaN, zone());
EXPECT_TRUE(Float64Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(-100.0).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(-1.01).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(-1.0).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(1.0).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(3.14159).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(3.1415).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(inf).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Set({-inf, 0.0}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Set({-1.0, 0.0}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Set({-1.0, 3.14159}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Float64Type::Set({3.14159, 3.1416}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(-inf, -1.0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(-1.01, -1.0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(-1.0, 3.14159, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(3.14159, 4.0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// Set (without NaN)
for (bool with_nan : {false, true}) {
uint32_t sv = with_nan ? kNaN : kNoSpecialValues;
Float64Type t = Float64Type::Set({-1.0, 3.14159}, kNoSpecialValues, zone());
EXPECT_TRUE(!Float64Type::NaN().IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(-100.0).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(-1.01).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(-1.0).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(1.0).IsSubtypeOf(t));
EXPECT_TRUE(Float64Type::Constant(3.14159).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(3.1415).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Constant(inf).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Set({-inf, 0.0}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Set({-1.0, 0.0}, sv, zone()).IsSubtypeOf(t));
EXPECT_EQ(!with_nan,
Float64Type::Set({-1.0, 3.14159}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(
!Float64Type::Set({3.14159, 3.1416}, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(-inf, -1.0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(-1.01, -1.0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(-1.0, 3.14159, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Range(3.14159, 4.0, sv, zone()).IsSubtypeOf(t));
EXPECT_TRUE(!Float64Type::Any(sv).IsSubtypeOf(t));
EXPECT_TRUE(t.IsSubtypeOf(t));
}
// -0.0 corner cases
{
EXPECT_TRUE(!Float64Type::MinusZero().IsSubtypeOf(
Float64Type::Set({0.0, 1.0}, zone())));
EXPECT_TRUE(
!Float64Type::Constant(0.0).IsSubtypeOf(Float64Type::MinusZero()));
EXPECT_TRUE(
Float64Type::Set({3.2}, kMinusZero, zone())
.IsSubtypeOf(Float64Type::Range(0.0, 4.0, kMinusZero, zone())));
EXPECT_TRUE(
Float64Type::Set({0.0}, kMinusZero, zone())
.IsSubtypeOf(Float64Type::Range(-inf, 0.0, kMinusZero, zone())));
}
}
TEST_F(TurboshaftTypesTest, Word32LeastUpperBound) {
auto CheckLubIs = [&](const Word32Type& lhs, const Word32Type& rhs,
const Word32Type& expected) {
EXPECT_TRUE(
expected.IsSubtypeOf(Word32Type::LeastUpperBound(lhs, rhs, zone())));
};
{
const auto lhs = Word32Type::Range(100, 400, zone());
CheckLubIs(lhs, lhs, lhs);
CheckLubIs(lhs, Word32Type::Range(50, 350, zone()),
Word32Type::Range(50, 400, zone()));
CheckLubIs(lhs, Word32Type::Range(150, 600, zone()),
Word32Type::Range(100, 600, zone()));
CheckLubIs(lhs, Word32Type::Range(150, 350, zone()), lhs);
CheckLubIs(lhs, Word32Type::Range(350, 0, zone()),
Word32Type::Range(100, 0, zone()));
CheckLubIs(lhs, Word32Type::Range(400, 100, zone()), Word32Type::Any());
CheckLubIs(lhs, Word32Type::Range(600, 0, zone()),
Word32Type::Range(600, 400, zone()));
CheckLubIs(lhs, Word32Type::Range(300, 150, zone()), Word32Type::Any());
}
{
const auto lhs = Word32Type::Constant(18);
CheckLubIs(lhs, lhs, lhs);
CheckLubIs(lhs, Word32Type::Constant(1119),
Word32Type::Set({18, 1119}, zone()));
CheckLubIs(lhs, Word32Type::Constant(0), Word32Type::Set({0, 18}, zone()));
CheckLubIs(lhs, Word32Type::Range(40, 100, zone()),
Word32Type::Range(18, 100, zone()));
CheckLubIs(lhs, Word32Type::Range(4, 90, zone()),
Word32Type::Range(4, 90, zone()));
CheckLubIs(lhs, Word32Type::Set({0, 1, 2, 3}, zone()),
Word32Type::Set({0, 1, 2, 3, 18}, zone()));
CheckLubIs(
lhs, Word32Type::Constant(std::numeric_limits<uint32_t>::max()),
Word32Type::Set({18, std::numeric_limits<uint32_t>::max()}, zone()));
}
}
TEST_F(TurboshaftTypesTest, Word64LeastUpperBound) {
auto CheckLubIs = [&](const Word64Type& lhs, const Word64Type& rhs,
const Word64Type& expected) {
EXPECT_TRUE(
expected.IsSubtypeOf(Word64Type::LeastUpperBound(lhs, rhs, zone())));
};
{
const auto lhs = Word64Type::Range(100, 400, zone());
CheckLubIs(lhs, lhs, lhs);
CheckLubIs(lhs, Word64Type::Range(50, 350, zone()),
Word64Type::Range(50, 400, zone()));
CheckLubIs(lhs, Word64Type::Range(150, 600, zone()),
Word64Type::Range(100, 600, zone()));
CheckLubIs(lhs, Word64Type::Range(150, 350, zone()), lhs);
CheckLubIs(lhs, Word64Type::Range(350, 0, zone()),
Word64Type::Range(100, 0, zone()));
CheckLubIs(lhs, Word64Type::Range(400, 100, zone()), Word64Type::Any());
CheckLubIs(lhs, Word64Type::Range(600, 0, zone()),
Word64Type::Range(600, 400, zone()));
CheckLubIs(lhs, Word64Type::Range(300, 150, zone()), Word64Type::Any());
}
{
const auto lhs = Word64Type::Constant(18);
CheckLubIs(lhs, lhs, lhs);
CheckLubIs(lhs, Word64Type::Constant(1119),
Word64Type::Set({18, 1119}, zone()));
CheckLubIs(lhs, Word64Type::Constant(0), Word64Type::Set({0, 18}, zone()));
CheckLubIs(lhs, Word64Type::Range(40, 100, zone()),
Word64Type::Range(18, 100, zone()));
CheckLubIs(lhs, Word64Type::Range(4, 90, zone()),
Word64Type::Range(4, 90, zone()));
CheckLubIs(lhs, Word64Type::Range(0, 3, zone()),
Word64Type::Set({0, 1, 2, 3, 18}, zone()));
CheckLubIs(
lhs, Word64Type::Constant(std::numeric_limits<uint64_t>::max()),
Word64Type::Set({18, std::numeric_limits<uint64_t>::max()}, zone()));
}
}
TEST_F(TurboshaftTypesTest, Float32LeastUpperBound) {
auto CheckLubIs = [&](const Float32Type& lhs, const Float32Type& rhs,
const Float32Type& expected) {
EXPECT_TRUE(
expected.IsSubtypeOf(Float32Type::LeastUpperBound(lhs, rhs, zone())));
};
const auto kNaN = Float32Type::kNaN;
{
const auto lhs = Float32Type::Range(-32.19f, 94.07f, zone());
CheckLubIs(lhs, lhs, lhs);
CheckLubIs(lhs, Float32Type::Range(-32.19f, 94.07f, kNaN, zone()),
Float32Type::Range(-32.19f, 94.07f, kNaN, zone()));
CheckLubIs(lhs, Float32Type::NaN(),
Float32Type::Range(-32.19f, 94.07f, kNaN, zone()));
CheckLubIs(lhs, Float32Type::Constant(0.0f), lhs);
CheckLubIs(lhs, Float32Type::Range(-19.9f, 31.29f, zone()), lhs);
CheckLubIs(lhs, Float32Type::Range(-91.22f, -40.0f, zone()),
Float32Type::Range(-91.22f, 94.07f, zone()));
CheckLubIs(lhs, Float32Type::Range(0.0f, 1993.0f, zone()),
Float32Type::Range(-32.19f, 1993.0f, zone()));
CheckLubIs(lhs, Float32Type::Range(-100.0f, 100.0f, kNaN, zone()),
Float32Type::Range(-100.0f, 100.0f, kNaN, zone()));
}
{
const auto lhs = Float32Type::Constant(-0.04f);
CheckLubIs(lhs, lhs, lhs);
CheckLubIs(lhs, Float32Type::NaN(),
Float32Type::Set({-0.04f}, kNaN, zone()));
CheckLubIs(lhs, Float32Type::Constant(17.14f),
Float32Type::Set({-0.04f, 17.14f}, zone()));
CheckLubIs(lhs, Float32Type::Range(-75.4f, -12.7f, zone()),
Float32Type::Range(-75.4f, -0.04f, zone()));
CheckLubIs(lhs, Float32Type::Set({0.04f}, kNaN, zone()),
Float32Type::Set({-0.04f, 0.04f}, kNaN, zone()));
}
}
TEST_F(TurboshaftTypesTest, Float64LeastUpperBound) {
auto CheckLubIs = [&](const Float64Type& lhs, const Float64Type& rhs,
const Float64Type& expected) {
EXPECT_TRUE(
expected.IsSubtypeOf(Float64Type::LeastUpperBound(lhs, rhs, zone())));
};
const auto kNaN = Float64Type::kNaN;
{
const auto lhs = Float64Type::Range(-32.19, 94.07, zone());
CheckLubIs(lhs, lhs, lhs);
CheckLubIs(lhs, Float64Type::Range(-32.19, 94.07, kNaN, zone()),
Float64Type::Range(-32.19, 94.07, kNaN, zone()));
CheckLubIs(lhs, Float64Type::NaN(),
Float64Type::Range(-32.19, 94.07, kNaN, zone()));
CheckLubIs(lhs, Float64Type::Constant(0.0), lhs);
CheckLubIs(lhs, Float64Type::Range(-19.9, 31.29, zone()), lhs);
CheckLubIs(lhs, Float64Type::Range(-91.22, -40.0, zone()),
Float64Type::Range(-91.22, 94.07, zone()));
CheckLubIs(lhs, Float64Type::Range(0.0, 1993.0, zone()),
Float64Type::Range(-32.19, 1993.0, zone()));
CheckLubIs(lhs, Float64Type::Range(-100.0, 100.0, kNaN, zone()),
Float64Type::Range(-100.0, 100.0, kNaN, zone()));
}
{
const auto lhs = Float64Type::Constant(-0.04);
CheckLubIs(lhs, lhs, lhs);
CheckLubIs(lhs, Float64Type::NaN(),
Float64Type::Set({-0.04}, kNaN, zone()));
CheckLubIs(lhs, Float64Type::Constant(17.14),
Float64Type::Set({-0.04, 17.14}, zone()));
CheckLubIs(lhs, Float64Type::Range(-75.4, -12.7, zone()),
Float64Type::Range(-75.4, -0.04, zone()));
CheckLubIs(lhs, Float64Type::Set({0.04}, kNaN, zone()),
Float64Type::Set({-0.04, 0.04}, kNaN, zone()));
}
}
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,500 @@
// Copyright 2025 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.
#include "src/compiler/turboshaft/wasm-shuffle-reducer.h"
#include "src/base/vector.h"
#include "src/compiler/turboshaft/assembler.h"
#include "src/compiler/turboshaft/copying-phase.h"
#include "src/compiler/turboshaft/operations.h"
#include "src/compiler/turboshaft/representations.h"
#include "src/compiler/turboshaft/required-optimization-reducer.h"
#include "test/common/flag-utils.h"
#include "test/unittests/compiler/turboshaft/reducer-test.h"
namespace v8::internal::compiler::turboshaft {
#include "src/compiler/turboshaft/define-assembler-macros.inc"
TEST_F(ReducerTest, UnaryConvertLowShuffle) {
// Expected reduced shuffle lengths when used solely by the given op.
std::array test_list = {
Simd128UnaryOp::Kind::kI16x8SConvertI8x16Low,
Simd128UnaryOp::Kind::kI16x8UConvertI8x16Low,
Simd128UnaryOp::Kind::kI32x4SConvertI16x8Low,
Simd128UnaryOp::Kind::kI32x4UConvertI16x8Low,
Simd128UnaryOp::Kind::kI64x2SConvertI32x4Low,
Simd128UnaryOp::Kind::kI64x2UConvertI32x4Low,
};
for (auto kind : test_list) {
SCOPED_TRACE(kind);
OpIndex shuffle;
auto test = CreateFromGraph(1, [&kind, &shuffle](auto& Asm) {
auto left =
__ Simd128Splat(__ Word32Constant(1), Simd128SplatOp::Kind::kI32x4);
auto right =
__ Simd128Splat(__ Word32Constant(0), Simd128SplatOp::Kind::kI32x4);
constexpr uint8_t shuffle_bytes[kSimd128Size] = {0, 1, 2, 3, 4, 5, 6, 7,
0, 0, 0, 0, 0, 0, 0, 0};
shuffle = __ Simd128Shuffle(left, right, Simd128ShuffleOp::Kind::kI8x16,
shuffle_bytes);
__ Return(__ Simd128Unary(shuffle, kind));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_TRUE(analyzer.ShouldReduce());
EXPECT_EQ(analyzer.DemandedByteLanes(&test.graph().Get(shuffle)), 0xFF);
test.Run<WasmShuffleReducer>();
}
}
TEST_F(ReducerTest, UnaryConvertHighShuffle) {
// Expected reduced shuffle lengths when used only by the given op.
std::array test_list = {
Simd128UnaryOp::Kind::kI16x8SConvertI8x16High,
Simd128UnaryOp::Kind::kI16x8UConvertI8x16High,
Simd128UnaryOp::Kind::kI32x4SConvertI16x8High,
Simd128UnaryOp::Kind::kI32x4UConvertI16x8High,
Simd128UnaryOp::Kind::kI64x2SConvertI32x4High,
Simd128UnaryOp::Kind::kI64x2UConvertI32x4High,
};
for (auto kind : test_list) {
SCOPED_TRACE(kind);
OpIndex shuffle;
auto test = CreateFromGraph(1, [&kind, &shuffle](auto& Asm) {
auto left =
__ Simd128Splat(__ Word32Constant(1), Simd128SplatOp::Kind::kI32x4);
auto right =
__ Simd128Splat(__ Word32Constant(0), Simd128SplatOp::Kind::kI32x4);
constexpr uint8_t shuffle_bytes[kSimd128Size] = {0, 1, 2, 3, 4, 5, 6, 7,
0, 0, 0, 0, 0, 0, 0, 0};
shuffle = __ Simd128Shuffle(left, right, Simd128ShuffleOp::Kind::kI8x16,
shuffle_bytes);
__ Return(__ Simd128Unary(shuffle, kind));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_FALSE(analyzer.ShouldReduce());
test.Run<WasmShuffleReducer>();
}
}
TEST_F(ReducerTest, UnaryConvertTwoChainedShuffle) {
// Expected reduced shuffle lengths when used only by the first op, itself
// used only by the second.
std::array<std::tuple<Simd128UnaryOp::Kind, Simd128UnaryOp::Kind, uint8_t>, 6>
test_list = {{
{Simd128UnaryOp::Kind::kI16x8SConvertI8x16Low,
Simd128UnaryOp::Kind::kI32x4SConvertI16x8Low, 4},
{Simd128UnaryOp::Kind::kI16x8UConvertI8x16Low,
Simd128UnaryOp::Kind::kI32x4UConvertI16x8Low, 4},
{Simd128UnaryOp::Kind::kI32x4SConvertI16x8Low,
Simd128UnaryOp::Kind::kI64x2SConvertI32x4Low, 4},
{Simd128UnaryOp::Kind::kI32x4SConvertI16x8Low,
Simd128UnaryOp::Kind::kI64x2SConvertI32x4Low, 4},
{Simd128UnaryOp::Kind::kI16x8SConvertI8x16Low,
Simd128UnaryOp::Kind::kI32x4SConvertI16x8High, 8},
{Simd128UnaryOp::Kind::kI16x8UConvertI8x16High,
Simd128UnaryOp::Kind::kI32x4UConvertI16x8Low, 0},
}};
for (auto const& [first_kind, second_kind, expected_count] : test_list) {
OpIndex shuffle;
SCOPED_TRACE(first_kind);
SCOPED_TRACE(second_kind);
auto test = CreateFromGraph(1, [&first_kind, &second_kind,
&shuffle](auto& Asm) {
auto left =
__ Simd128Splat(__ Word32Constant(1), Simd128SplatOp::Kind::kI32x4);
auto right =
__ Simd128Splat(__ Word32Constant(0), Simd128SplatOp::Kind::kI32x4);
constexpr uint8_t shuffle_bytes[kSimd128Size] = {0, 1, 2, 3, 4, 5, 6, 7,
0, 0, 0, 0, 0, 0, 0, 0};
shuffle = __ Simd128Shuffle(left, right, Simd128ShuffleOp::Kind::kI8x16,
shuffle_bytes);
__ Return(
__ Simd128Unary(__ Simd128Unary(shuffle, first_kind), second_kind));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
if (expected_count == 0) {
EXPECT_FALSE(analyzer.ShouldReduce());
} else {
EXPECT_TRUE(analyzer.ShouldReduce());
auto maybe_bitset =
analyzer.DemandedByteLanes(&test.graph().Get(shuffle));
EXPECT_TRUE(maybe_bitset.has_value());
if (maybe_bitset.has_value()) {
if (expected_count == 8) {
EXPECT_EQ(maybe_bitset.value(), 0xFF);
} else if (expected_count == 4) {
EXPECT_EQ(maybe_bitset.value(), 0xF);
} else if (expected_count == 2) {
EXPECT_EQ(maybe_bitset.value(), 0x3);
}
}
}
test.Run<WasmShuffleReducer>();
}
}
TEST_F(ReducerTest, UnaryConvertThreeChainedShuffle) {
// Expected reduced shuffle lengths when used in a chain of three
// conversions, each with a single use.
std::array<std::tuple<Simd128UnaryOp::Kind, Simd128UnaryOp::Kind,
Simd128UnaryOp::Kind, uint8_t>,
2>
test_list = {{
{Simd128UnaryOp::Kind::kI16x8SConvertI8x16Low,
Simd128UnaryOp::Kind::kI32x4SConvertI16x8Low,
Simd128UnaryOp::Kind::kI64x2SConvertI32x4Low, 2},
{Simd128UnaryOp::Kind::kI16x8UConvertI8x16Low,
Simd128UnaryOp::Kind::kI32x4UConvertI16x8Low,
Simd128UnaryOp::Kind::kI64x2UConvertI32x4Low, 2},
}};
for (auto const& [first_kind, second_kind, third_kind, expected_count] :
test_list) {
SCOPED_TRACE(first_kind);
SCOPED_TRACE(second_kind);
OpIndex shuffle;
auto test = CreateFromGraph(1, [&first_kind, &second_kind, &third_kind,
&shuffle](auto& Asm) {
auto left =
__ Simd128Splat(__ Word32Constant(1), Simd128SplatOp::Kind::kI32x4);
auto right =
__ Simd128Splat(__ Word32Constant(0), Simd128SplatOp::Kind::kI32x4);
constexpr uint8_t shuffle_bytes[kSimd128Size] = {0, 1, 2, 3, 4, 5, 6, 7,
0, 0, 0, 0, 0, 0, 0, 0};
shuffle = __ Simd128Shuffle(left, right, Simd128ShuffleOp::Kind::kI8x16,
shuffle_bytes);
__ Return(__ Simd128Unary(
__ Simd128Unary(__ Simd128Unary(shuffle, first_kind), second_kind),
third_kind));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_TRUE(analyzer.ShouldReduce());
auto maybe_bitset = analyzer.DemandedByteLanes(&test.graph().Get(shuffle));
EXPECT_TRUE(maybe_bitset.has_value());
if (maybe_bitset.has_value()) {
EXPECT_EQ(maybe_bitset.value(), 0x3);
}
test.Run<WasmShuffleReducer>();
}
}
TEST_F(ReducerTest, BinaryExtLowShuffle) {
// Expected reduced shuffle lengths when used solely by the given op.
std::array test_list = {
Simd128BinopOp::Kind::kI16x8ExtMulLowI8x16S,
Simd128BinopOp::Kind::kI16x8ExtMulLowI8x16U,
Simd128BinopOp::Kind::kI32x4ExtMulLowI16x8S,
Simd128BinopOp::Kind::kI32x4ExtMulLowI16x8U,
Simd128BinopOp::Kind::kI64x2ExtMulLowI32x4S,
Simd128BinopOp::Kind::kI64x2ExtMulLowI32x4U,
};
for (auto kind : test_list) {
SCOPED_TRACE(kind);
OpIndex left_shuffle;
OpIndex right_shuffle;
auto test = CreateFromGraph(1, [&kind, &left_shuffle,
&right_shuffle](auto& Asm) {
auto ShuffleKind = Simd128ShuffleOp::Kind::kI8x16;
auto zero =
__ Simd128Splat(__ Word32Constant(0), Simd128SplatOp::Kind::kI32x4);
auto one =
__ Simd128Splat(__ Word32Constant(1), Simd128SplatOp::Kind::kI32x4);
auto two =
__ Simd128Splat(__ Word32Constant(2), Simd128SplatOp::Kind::kI32x4);
auto three =
__ Simd128Splat(__ Word32Constant(3), Simd128SplatOp::Kind::kI32x4);
constexpr uint8_t shuffle_bytes[kSimd128Size] = {0, 1, 2, 3, 4, 5, 6, 7,
0, 0, 0, 0, 0, 0, 0, 0};
left_shuffle = __ Simd128Shuffle(zero, one, ShuffleKind, shuffle_bytes);
right_shuffle = __ Simd128Shuffle(two, three, ShuffleKind, shuffle_bytes);
__ Return(__ Simd128Binop(left_shuffle, right_shuffle, kind));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_TRUE(analyzer.ShouldReduce());
EXPECT_EQ(analyzer.DemandedByteLanes(&test.graph().Get(left_shuffle)),
0xFF);
EXPECT_EQ(analyzer.DemandedByteLanes(&test.graph().Get(right_shuffle)),
0xFF);
test.Run<WasmShuffleReducer>();
}
}
TEST_F(ReducerTest, BinaryExtLowUnaryShuffle) {
// Expected reduced shuffle lengths when used solely by the given op.
std::array<std::tuple<Simd128BinopOp::Kind, Simd128UnaryOp::Kind, uint8_t>,
12>
test_list = {{
{Simd128BinopOp::Kind::kI16x8ExtMulLowI8x16S,
Simd128UnaryOp::Kind::kI32x4SConvertI16x8Low, 4},
{Simd128BinopOp::Kind::kI16x8ExtMulLowI8x16U,
Simd128UnaryOp::Kind::kI32x4UConvertI16x8Low, 4},
{Simd128BinopOp::Kind::kI32x4ExtMulLowI16x8S,
Simd128UnaryOp::Kind::kI64x2SConvertI32x4Low, 4},
{Simd128BinopOp::Kind::kI32x4ExtMulLowI16x8U,
Simd128UnaryOp::Kind::kI64x2UConvertI32x4Low, 4},
{Simd128BinopOp::Kind::kI16x8ExtMulHighI8x16S,
Simd128UnaryOp::Kind::kI32x4SConvertI16x8Low, 0},
{Simd128BinopOp::Kind::kI16x8ExtMulHighI8x16U,
Simd128UnaryOp::Kind::kI32x4UConvertI16x8Low, 0},
{Simd128BinopOp::Kind::kI32x4ExtMulHighI16x8S,
Simd128UnaryOp::Kind::kI64x2SConvertI32x4Low, 0},
{Simd128BinopOp::Kind::kI32x4ExtMulHighI16x8U,
Simd128UnaryOp::Kind::kI64x2UConvertI32x4Low, 0},
{Simd128BinopOp::Kind::kI16x8ExtMulLowI8x16S,
Simd128UnaryOp::Kind::kI32x4SConvertI16x8High, 8},
{Simd128BinopOp::Kind::kI16x8ExtMulLowI8x16U,
Simd128UnaryOp::Kind::kI32x4UConvertI16x8High, 8},
{Simd128BinopOp::Kind::kI32x4ExtMulLowI16x8S,
Simd128UnaryOp::Kind::kI64x2SConvertI32x4High, 8},
{Simd128BinopOp::Kind::kI32x4ExtMulLowI16x8U,
Simd128UnaryOp::Kind::kI64x2UConvertI32x4High, 8},
}};
for (auto const& [binop_kind, unop_kind, expected_count] : test_list) {
OpIndex left_shuffle;
OpIndex right_shuffle;
SCOPED_TRACE(binop_kind);
SCOPED_TRACE(unop_kind);
auto test = CreateFromGraph(1, [&binop_kind, unop_kind, &left_shuffle,
&right_shuffle](auto& Asm) {
auto ShuffleKind = Simd128ShuffleOp::Kind::kI8x16;
auto zero =
__ Simd128Splat(__ Word32Constant(0), Simd128SplatOp::Kind::kI32x4);
auto one =
__ Simd128Splat(__ Word32Constant(1), Simd128SplatOp::Kind::kI32x4);
auto two =
__ Simd128Splat(__ Word32Constant(2), Simd128SplatOp::Kind::kI32x4);
auto three =
__ Simd128Splat(__ Word32Constant(3), Simd128SplatOp::Kind::kI32x4);
constexpr uint8_t shuffle_bytes[kSimd128Size] = {0, 1, 2, 3, 4, 5, 6, 7,
0, 0, 0, 0, 0, 0, 0, 0};
left_shuffle = __ Simd128Shuffle(zero, one, ShuffleKind, shuffle_bytes);
right_shuffle = __ Simd128Shuffle(two, three, ShuffleKind, shuffle_bytes);
__ Return(__ Simd128Unary(
__ Simd128Binop(left_shuffle, right_shuffle, binop_kind), unop_kind));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
if (expected_count == 0) {
EXPECT_FALSE(analyzer.ShouldReduce());
} else {
EXPECT_TRUE(analyzer.ShouldReduce());
std::bitset<16> expected_byte_lane_mask =
expected_count == 8 ? 0xFF : 0xF;
EXPECT_EQ(analyzer.DemandedByteLanes(&test.graph().Get(left_shuffle)),
expected_byte_lane_mask);
EXPECT_EQ(analyzer.DemandedByteLanes(&test.graph().Get(right_shuffle)),
expected_byte_lane_mask);
test.Run<WasmShuffleReducer>();
}
}
}
#if V8_ENABLE_WASM_INTERLEAVED_MEM_OPS
namespace {
auto ShuffleKind = Simd128ShuffleOp::Kind::kI8x16;
constexpr uint8_t shuffle_bytes_even[kSimd128Size] = {
0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23};
constexpr uint8_t shuffle_bytes_odd[kSimd128Size] = {
8, 9, 10, 11, 12, 13, 14, 15, 24, 25, 26, 27, 28, 29, 30, 31};
} // namespace
TEST_F(ReducerTest, LoadInterleaveTwo) {
auto test = CreateFromGraph(2, [](auto& Asm) {
auto base = __ BitcastTaggedToWordPtr(Asm.GetParameter(0));
auto index = __ BitcastTaggedToWordPtr(Asm.GetParameter(1));
auto ld0 = __ Load(base, index, LoadOp::Kind::Protected(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), 0);
auto ld1 = __ Load(base, index, LoadOp::Kind::Protected(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), kSimd128Size);
auto even_shuffle =
__ Simd128Shuffle(ld0, ld1, ShuffleKind, shuffle_bytes_even);
auto odd_shuffle =
__ Simd128Shuffle(ld0, ld1, ShuffleKind, shuffle_bytes_odd);
__ Return(__ Simd128Binop(even_shuffle, odd_shuffle,
Simd128BinopOp::Kind::kF64x2Add));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_TRUE(analyzer.ShouldReduce());
}
TEST_F(ReducerTest, LoadInterleaveTwoNoIndex) {
auto test = CreateFromGraph(1, [](auto& Asm) {
auto ld0 = __ Load(Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), 0);
auto ld1 = __ Load(Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), kSimd128Size);
auto even_shuffle =
__ Simd128Shuffle(ld1, ld0, ShuffleKind, shuffle_bytes_even);
auto odd_shuffle =
__ Simd128Shuffle(ld1, ld0, ShuffleKind, shuffle_bytes_odd);
__ Return(__ Simd128Binop(even_shuffle, odd_shuffle,
Simd128BinopOp::Kind::kF64x2Mul));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_TRUE(analyzer.ShouldReduce());
}
TEST_F(ReducerTest, LoadInterleaveTwoWrongIndex) {
auto test = CreateFromGraph(2, [](auto& Asm) {
auto ld0 = __ Load(Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), 0);
auto index = __ BitcastTaggedToWordPtr(Asm.GetParameter(1));
auto ld1 = __ Load(Asm.GetParameter(0), index, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), kSimd128Size);
auto even_shuffle =
__ Simd128Shuffle(ld1, ld0, ShuffleKind, shuffle_bytes_even);
auto odd_shuffle =
__ Simd128Shuffle(ld1, ld0, ShuffleKind, shuffle_bytes_odd);
__ Return(__ Simd128Binop(even_shuffle, odd_shuffle,
Simd128BinopOp::Kind::kF64x2Mul));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_FALSE(analyzer.ShouldReduce());
}
TEST_F(ReducerTest, LoadInterleaveTwoNoIndexWrongOffset) {
auto test = CreateFromGraph(1, [](auto& Asm) {
auto base = __ BitcastTaggedToWordPtr(Asm.GetParameter(0));
auto ld0 = __ Load(base, {}, LoadOp::Kind::Protected(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), 0);
auto ld1 = __ Load(base, {}, LoadOp::Kind::Protected(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), 2 * kSimd128Size);
auto even_shuffle =
__ Simd128Shuffle(ld0, ld1, ShuffleKind, shuffle_bytes_even);
auto odd_shuffle =
__ Simd128Shuffle(ld0, ld1, ShuffleKind, shuffle_bytes_odd);
__ Return(__ Simd128Binop(even_shuffle, odd_shuffle,
Simd128BinopOp::Kind::kI64x2Mul));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_FALSE(analyzer.ShouldReduce());
}
TEST_F(ReducerTest, LoadInterleaveTwoNoIndexNotSameKind) {
auto test = CreateFromGraph(1, [](auto& Asm) {
auto ld0 = __ Load(Asm.GetParameter(0), {}, LoadOp::Kind::TaggedBase(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), 0);
auto base = __ BitcastTaggedToWordPtr(Asm.GetParameter(0));
auto ld1 = __ Load(base, {}, LoadOp::Kind::Protected(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), kSimd128Size);
auto even_shuffle =
__ Simd128Shuffle(ld0, ld1, ShuffleKind, shuffle_bytes_even);
auto odd_shuffle =
__ Simd128Shuffle(ld0, ld1, ShuffleKind, shuffle_bytes_odd);
__ Return(__ Simd128Binop(even_shuffle, odd_shuffle,
Simd128BinopOp::Kind::kI64x2Sub));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_FALSE(analyzer.ShouldReduce());
}
TEST_F(ReducerTest, LoadInterleaveTwoNoIndexNotLeftAndRight) {
auto test = CreateFromGraph(1, [](auto& Asm) {
auto base = __ BitcastTaggedToWordPtr(Asm.GetParameter(0));
auto ld0 = __ Load(base, {}, LoadOp::Kind::Protected(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), 0);
auto ld1 = __ Load(base, {}, LoadOp::Kind::Protected(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), kSimd128Size);
auto even_shuffle =
__ Simd128Shuffle(ld1, ld0, ShuffleKind, shuffle_bytes_even);
auto odd_shuffle =
__ Simd128Shuffle(ld0, ld1, ShuffleKind, shuffle_bytes_odd);
__ Return(__ Simd128Binop(even_shuffle, odd_shuffle,
Simd128BinopOp::Kind::kF64x2Mul));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_FALSE(analyzer.ShouldReduce());
}
TEST_F(ReducerTest, LoadInterleaveTwoNoIndexCantReschedule) {
auto test = CreateFromGraph(1, [](auto& Asm) {
auto base = __ BitcastTaggedToWordPtr(Asm.GetParameter(0));
auto ld0 = __ Load(base, {}, LoadOp::Kind::Protected(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), 0);
auto data = __ HeapConstant(Asm.factory().undefined_value());
__ Store(Asm.GetParameter(0), data, StoreOp::Kind::TaggedBase(),
MemoryRepresentation::TaggedPointer(),
WriteBarrierKind::kNoWriteBarrier, 0, true);
auto ld1 = __ Load(base, {}, LoadOp::Kind::Protected(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), kSimd128Size);
auto even_shuffle =
__ Simd128Shuffle(ld1, ld0, ShuffleKind, shuffle_bytes_even);
auto odd_shuffle =
__ Simd128Shuffle(ld1, ld0, ShuffleKind, shuffle_bytes_odd);
__ Return(__ Simd128Binop(even_shuffle, odd_shuffle,
Simd128BinopOp::Kind::kF64x2Mul));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_FALSE(analyzer.ShouldReduce());
}
TEST_F(ReducerTest, LoadInterleaveTwoWrongBlocks) {
auto test = CreateFromGraph(2, [](auto& Asm) {
Block* block_a = __ NewBlock();
Block* block_b = __ NewBlock();
Block* block_c = __ NewBlock();
Block* block_d = __ NewBlock();
__ Bind(block_a);
auto base = __ BitcastTaggedToWordPtr(Asm.GetParameter(0));
auto index = __ BitcastTaggedToWordPtr(Asm.GetParameter(1));
__ Goto(block_b);
__ Bind(block_b);
auto ld0 = __ Load(base, index, LoadOp::Kind::Protected(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), 0);
__ Goto(block_c);
__ Bind(block_c);
auto ld1 = __ Load(base, index, LoadOp::Kind::Protected(),
MemoryRepresentation::Simd128(),
RegisterRepresentation::Simd128(), kSimd128Size);
__ Goto(block_d);
__ Bind(block_d);
auto even_shuffle =
__ Simd128Shuffle(ld0, ld1, ShuffleKind, shuffle_bytes_even);
auto odd_shuffle =
__ Simd128Shuffle(ld0, ld1, ShuffleKind, shuffle_bytes_odd);
__ Return(__ Simd128Binop(even_shuffle, odd_shuffle,
Simd128BinopOp::Kind::kF64x2Add));
});
WasmShuffleAnalyzer analyzer(test.zone(), test.graph());
analyzer.Run();
EXPECT_FALSE(analyzer.ShouldReduce());
}
#endif // V8_ENABLE_WASM_INTERLEAVED_MEM_OPS
#include "src/compiler/turboshaft/undef-assembler-macros.inc"
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,187 @@
// Copyright 2024 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.
#include "src/base/vector.h"
#include "src/compiler/turboshaft/assembler.h"
#include "src/compiler/turboshaft/copying-phase.h"
#include "src/compiler/turboshaft/dead-code-elimination-reducer.h"
#include "src/compiler/turboshaft/machine-optimization-reducer.h"
#include "src/compiler/turboshaft/operations.h"
#include "src/compiler/turboshaft/representations.h"
#include "src/compiler/turboshaft/required-optimization-reducer.h"
#include "src/compiler/turboshaft/wasm-shuffle-reducer.h"
#include "test/common/flag-utils.h"
#include "test/unittests/compiler/turboshaft/reducer-test.h"
namespace v8::internal::compiler::turboshaft {
#include "src/compiler/turboshaft/define-assembler-macros.inc"
class WasmSimdTest : public ReducerTest {};
TEST_F(WasmSimdTest, UpperToLowerF32x4AddReduce) {
auto test = CreateFromGraph(1, [](auto& Asm) {
auto SplatKind = Simd128SplatOp::Kind::kF32x4;
auto AddKind = Simd128BinopOp::Kind::kF32x4Add;
auto ExtractKind = Simd128ExtractLaneOp::Kind::kF32x4;
auto ShuffleKind = Simd128ShuffleOp::Kind::kI8x16;
constexpr uint8_t upper_to_lower_1[kSimd128Size] = {
8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0};
constexpr uint8_t upper_to_lower_2[kSimd128Size] = {4, 5, 6, 7, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0};
V<Simd128> input = __ Simd128Splat(__ Float32Constant(1.0), SplatKind);
V<Simd128> first_shuffle =
__ Simd128Shuffle(input, input, ShuffleKind, upper_to_lower_1);
V<Simd128> first_add = __ Simd128Binop(input, first_shuffle, AddKind);
V<Simd128> second_shuffle =
__ Simd128Shuffle(first_add, first_add, ShuffleKind, upper_to_lower_2);
V<Simd128> second_add = __ Simd128Binop(first_add, second_shuffle, AddKind);
__ Return(__ Simd128ExtractLane(second_add, ExtractKind, 0));
});
test.Run<MachineOptimizationReducer>();
test.Run<DeadCodeEliminationReducer>();
// We can only match pairwise fp operations.
ASSERT_EQ(test.CountOp(Opcode::kSimd128Reduce), 0u);
}
TEST_F(WasmSimdTest, AlmostUpperToLowerI16x8AddReduce) {
auto test = CreateFromGraph(1, [](auto& Asm) {
auto SplatKind = Simd128SplatOp::Kind::kI16x8;
auto AddKind = Simd128BinopOp::Kind::kI16x8Add;
auto ExtractKind = Simd128ExtractLaneOp::Kind::kI16x8U;
auto ShuffleKind = Simd128ShuffleOp::Kind::kI8x16;
constexpr uint8_t almost_upper_to_lower_1[kSimd128Size] = {
0, 0, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0,
};
constexpr uint8_t upper_to_lower_2[kSimd128Size] = {4, 5, 6, 7, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0};
constexpr uint8_t upper_to_lower_3[kSimd128Size] = {2, 3, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0};
V<Simd128> input = __ Simd128Splat(Asm.GetParameter(0), SplatKind);
V<Simd128> first_shuffle =
__ Simd128Shuffle(input, input, ShuffleKind, almost_upper_to_lower_1);
V<Simd128> first_add = __ Simd128Binop(input, first_shuffle, AddKind);
V<Simd128> second_shuffle =
__ Simd128Shuffle(first_add, first_add, ShuffleKind, upper_to_lower_2);
V<Simd128> second_add = __ Simd128Binop(first_add, second_shuffle, AddKind);
V<Simd128> third_shuffle = __ Simd128Shuffle(second_add, second_add,
ShuffleKind, upper_to_lower_3);
V<Simd128> third_add = __ Simd128Binop(second_add, third_shuffle, AddKind);
__ Return(__ Simd128ExtractLane(third_add, ExtractKind, 0));
});
test.Run<MachineOptimizationReducer>();
test.Run<DeadCodeEliminationReducer>();
// The first shuffle is not the one we're looking for.
ASSERT_EQ(test.CountOp(Opcode::kSimd128Reduce), 0u);
}
TEST_F(WasmSimdTest, UpperToLowerI32x4AddReduce) {
auto test = CreateFromGraph(1, [](auto& Asm) {
auto SplatKind = Simd128SplatOp::Kind::kI32x4;
auto AddKind = Simd128BinopOp::Kind::kI32x4Add;
auto ExtractKind = Simd128ExtractLaneOp::Kind::kI32x4;
auto ShuffleKind = Simd128ShuffleOp::Kind::kI8x16;
constexpr uint8_t upper_to_lower_1[kSimd128Size] = {
8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0};
constexpr uint8_t upper_to_lower_2[kSimd128Size] = {4, 5, 6, 7, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0};
V<Simd128> input = __ Simd128Splat(Asm.GetParameter(0), SplatKind);
V<Simd128> first_shuffle =
__ Simd128Shuffle(input, input, ShuffleKind, upper_to_lower_1);
V<Simd128> first_add = __ Simd128Binop(input, first_shuffle, AddKind);
V<Simd128> second_shuffle =
__ Simd128Shuffle(first_add, first_add, ShuffleKind, upper_to_lower_2);
V<Simd128> second_add = __ Simd128Binop(first_add, second_shuffle, AddKind);
__ Return(__ Simd128ExtractLane(second_add, ExtractKind, 0));
});
test.Run<MachineOptimizationReducer>();
test.Run<DeadCodeEliminationReducer>();
#ifdef V8_TARGET_ARCH_ARM64
ASSERT_EQ(test.CountOp(Opcode::kSimd128Shuffle), 0u);
ASSERT_EQ(test.CountOp(Opcode::kSimd128Reduce), 1u);
ASSERT_EQ(test.CountOp(Opcode::kSimd128ExtractLane), 1u);
#else
ASSERT_EQ(test.CountOp(Opcode::kSimd128Reduce), 0u);
#endif
}
TEST_F(WasmSimdTest, PairwiseF32x4AddReduce) {
auto test = CreateFromGraph(1, [](auto& Asm) {
auto SplatKind = Simd128SplatOp::Kind::kF32x4;
auto AddKind = Simd128BinopOp::Kind::kF32x4Add;
auto ExtractKind = Simd128ExtractLaneOp::Kind::kF32x4;
auto ShuffleKind = Simd128ShuffleOp::Kind::kI8x16;
constexpr uint8_t upper_to_lower_1[kSimd128Size] = {
4, 5, 6, 7, 0, 0, 0, 0, 12, 13, 14, 15, 0, 0, 0, 0};
constexpr uint8_t upper_to_lower_2[kSimd128Size] = {
8, 9, 10, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
V<Simd128> input = __ Simd128Splat(__ Float32Constant(1.0), SplatKind);
V<Simd128> first_shuffle =
__ Simd128Shuffle(input, input, ShuffleKind, upper_to_lower_1);
V<Simd128> first_add = __ Simd128Binop(input, first_shuffle, AddKind);
V<Simd128> second_shuffle =
__ Simd128Shuffle(first_add, first_add, ShuffleKind, upper_to_lower_2);
V<Simd128> second_add = __ Simd128Binop(first_add, second_shuffle, AddKind);
__ Return(__ Simd128ExtractLane(second_add, ExtractKind, 0));
});
test.Run<MachineOptimizationReducer>();
test.Run<DeadCodeEliminationReducer>();
#ifdef V8_TARGET_ARCH_ARM64
ASSERT_EQ(test.CountOp(Opcode::kSimd128Shuffle), 0u);
ASSERT_EQ(test.CountOp(Opcode::kSimd128Reduce), 1u);
ASSERT_EQ(test.CountOp(Opcode::kSimd128ExtractLane), 1u);
#else
ASSERT_EQ(test.CountOp(Opcode::kSimd128Reduce), 0u);
#endif
}
TEST_F(WasmSimdTest, AlmostPairwiseF32x4AddReduce) {
auto test = CreateFromGraph(1, [](auto& Asm) {
auto SplatKind = Simd128SplatOp::Kind::kF32x4;
auto AddKind = Simd128BinopOp::Kind::kF32x4Add;
auto ExtractKind = Simd128ExtractLaneOp::Kind::kF32x4;
auto ShuffleKind = Simd128ShuffleOp::Kind::kI8x16;
constexpr uint8_t upper_to_lower_1[kSimd128Size] = {
4, 5, 6, 7, 0, 0, 0, 0, 12, 13, 14, 15, 0, 0, 0, 0};
constexpr uint8_t upper_to_lower_2[kSimd128Size] = {
8, 9, 10, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
V<Simd128> input = __ Simd128Splat(__ Float32Constant(1.0), SplatKind);
V<Simd128> first_shuffle =
__ Simd128Shuffle(input, input, ShuffleKind, upper_to_lower_1);
V<Simd128> first_add = __ Simd128Binop(input, first_shuffle, AddKind);
V<Simd128> second_shuffle =
__ Simd128Shuffle(first_add, first_add, ShuffleKind, upper_to_lower_2);
V<Simd128> tricksy_add = __ Simd128Binop(first_add, first_add, AddKind);
V<Simd128> second_add =
__ Simd128Binop(tricksy_add, second_shuffle, AddKind);
__ Return(__ Simd128ExtractLane(second_add, ExtractKind, 0));
});
test.Run<MachineOptimizationReducer>();
test.Run<DeadCodeEliminationReducer>();
// There's an additional addition.
ASSERT_EQ(test.CountOp(Opcode::kSimd128Reduce), 0u);
}
#include "src/compiler/turboshaft/undef-assembler-macros.inc"
} // namespace v8::internal::compiler::turboshaft