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,95 @@
// Copyright 2014 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 "test/cctest/compiler/codegen-tester.h"
#include "src/base/overflowing-math.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/common/value-helper.h"
namespace v8 {
namespace internal {
namespace compiler {
void Int32BinopInputShapeTester::TestAllInputShapes() {
base::Vector<const int32_t> inputs = ValueHelper::int32_vector();
int num_int_inputs = static_cast<int>(inputs.size());
if (num_int_inputs > 16) num_int_inputs = 16; // limit to 16 inputs
for (int i = -2; i < num_int_inputs; i++) { // for all left shapes
for (int j = -2; j < num_int_inputs; j++) { // for all right shapes
if (i >= 0 && j >= 0) break; // No constant/constant combos
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* n0;
Node* n1;
// left = Parameter | Load | Constant
if (i == -2) {
n0 = p0;
} else if (i == -1) {
n0 = m.LoadFromPointer(&input_a, MachineType::Int32());
} else {
n0 = m.Int32Constant(inputs[i]);
}
// right = Parameter | Load | Constant
if (j == -2) {
n1 = p1;
} else if (j == -1) {
n1 = m.LoadFromPointer(&input_b, MachineType::Int32());
} else {
n1 = m.Int32Constant(inputs[j]);
}
gen->gen(&m, n0, n1);
if (i >= 0) {
input_a = inputs[i];
RunRight(&m);
} else if (j >= 0) {
input_b = inputs[j];
RunLeft(&m);
} else {
Run(&m);
}
}
}
}
void Int32BinopInputShapeTester::Run(RawMachineAssemblerTester<int32_t>* m) {
FOR_INT32_INPUTS(pl) {
FOR_INT32_INPUTS(pr) {
input_a = pl;
input_b = pr;
int32_t expect = gen->expected(input_a, input_b);
CHECK_EQ(expect, m->Call(input_a, input_b));
}
}
}
void Int32BinopInputShapeTester::RunLeft(
RawMachineAssemblerTester<int32_t>* m) {
FOR_UINT32_INPUTS(i) {
input_a = i;
int32_t expect = gen->expected(input_a, input_b);
CHECK_EQ(expect, m->Call(input_a, input_b));
}
}
void Int32BinopInputShapeTester::RunRight(
RawMachineAssemblerTester<int32_t>* m) {
FOR_UINT32_INPUTS(i) {
input_b = i;
int32_t expect = gen->expected(input_a, input_b);
CHECK_EQ(expect, m->Call(input_a, input_b));
}
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,534 @@
// Copyright 2014 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.
#ifndef V8_CCTEST_COMPILER_CODEGEN_TESTER_H_
#define V8_CCTEST_COMPILER_CODEGEN_TESTER_H_
#include "src/codegen/assembler.h"
#include "src/codegen/optimized-compilation-info.h"
#include "src/compiler/backend/instruction-selector.h"
#include "src/compiler/pipeline.h"
#include "src/compiler/raw-machine-assembler.h"
#include "src/objects/code-inl.h"
#include "test/cctest/cctest.h"
#include "test/common/call-tester.h"
namespace v8 {
namespace internal {
namespace compiler {
template <typename ReturnType>
class RawMachineAssemblerTester : public HandleAndZoneScope,
public CallHelper<ReturnType>,
public RawMachineAssembler {
public:
template <typename... ParamMachTypes>
explicit RawMachineAssemblerTester(ParamMachTypes... p)
: HandleAndZoneScope(kCompressGraphZone),
CallHelper<ReturnType>(
main_isolate(),
CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p...)),
RawMachineAssembler(
main_isolate(), main_zone()->template New<TFGraph>(main_zone()),
Linkage::GetSimplifiedCDescriptor(
main_zone(),
CSignature::New(main_zone(), MachineTypeForC<ReturnType>(),
p...),
CallDescriptor::kInitializeRootRegister),
MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags(),
InstructionSelector::AlignmentRequirements()) {}
template <typename... ParamMachTypes>
RawMachineAssemblerTester(CodeKind kind, ParamMachTypes... p)
: HandleAndZoneScope(kCompressGraphZone),
CallHelper<ReturnType>(
main_isolate(),
CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p...)),
RawMachineAssembler(
main_isolate(), main_zone()->template New<TFGraph>(main_zone()),
Linkage::GetSimplifiedCDescriptor(
main_zone(),
CSignature::New(main_zone(), MachineTypeForC<ReturnType>(),
p...),
CallDescriptor::kInitializeRootRegister),
MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags(),
InstructionSelector::AlignmentRequirements()),
kind_(kind) {}
~RawMachineAssemblerTester() override = default;
void CheckNumber(double expected, Tagged<Object> number) {
CHECK(Object::SameValue(*this->isolate()->factory()->NewNumber(expected),
number));
}
void CheckString(const char* expected, Tagged<Object> string) {
CHECK(Object::SameValue(
*this->isolate()->factory()->InternalizeUtf8String(expected), string));
}
void GenerateCode() { Generate(); }
Handle<Code> GetCode() {
Generate();
return code_.ToHandleChecked();
}
protected:
Address Generate() override {
if (code_.is_null()) {
Schedule* schedule = ExportForTest();
OptimizedCompilationInfo info(base::ArrayVector("testing"), main_zone(),
kind_);
code_ = Pipeline::GenerateCodeForTesting(
&info, main_isolate(), call_descriptor(), graph(),
AssemblerOptions::Default(main_isolate()), schedule);
}
return code_.ToHandleChecked()->instruction_start();
}
private:
CodeKind kind_ = CodeKind::FOR_TESTING;
MaybeHandle<Code> code_;
};
template <typename ReturnType>
class BufferedRawMachineAssemblerTester
: public RawMachineAssemblerTester<int32_t> {
public:
template <typename... ParamMachTypes>
explicit BufferedRawMachineAssemblerTester(ParamMachTypes... p)
: RawMachineAssemblerTester<int32_t>(
MachineType::Pointer(), ((void)p, MachineType::Pointer())...),
test_graph_signature_(
CSignature::New(this->main_zone(), MachineType::Int32(), p...)),
return_parameter_index_(sizeof...(p)) {
static_assert(sizeof...(p) <= arraysize(parameter_nodes_),
"increase parameter_nodes_ array");
std::array<MachineType, sizeof...(p)> p_arr{{p...}};
for (size_t i = 0; i < p_arr.size(); ++i) {
parameter_nodes_[i] = Load(p_arr[i], RawMachineAssembler::Parameter(i));
}
}
Address Generate() override { return RawMachineAssemblerTester::Generate(); }
// The BufferedRawMachineAssemblerTester does not pass parameters directly
// to the constructed IR graph. Instead it passes a pointer to the parameter
// to the IR graph, and adds Load nodes to the IR graph to load the
// parameters from memory. Thereby it is possible to pass 64 bit parameters
// to the IR graph.
Node* Parameter(size_t index) {
CHECK_GT(arraysize(parameter_nodes_), index);
return parameter_nodes_[index];
}
// The BufferedRawMachineAssemblerTester adds a Store node to the IR graph
// to store the graph's return value in memory. The memory address for the
// Store node is provided as a parameter. By storing the return value in
// memory it is possible to return 64 bit values.
void Return(Node* input) {
if (COMPRESS_POINTERS_BOOL && MachineTypeForC<ReturnType>().IsTagged()) {
// Since we are returning values via storing to off-heap location
// generate full-word store here.
Store(MachineType::PointerRepresentation(),
RawMachineAssembler::Parameter(return_parameter_index_), input,
kNoWriteBarrier);
} else {
Store(MachineTypeForC<ReturnType>().representation(),
RawMachineAssembler::Parameter(return_parameter_index_), input,
kNoWriteBarrier);
}
RawMachineAssembler::Return(Int32Constant(1234));
}
template <typename... Params>
ReturnType Call(Params... p) {
uintptr_t zap_data[] = {kZapValue, kZapValue};
ReturnType return_value;
static_assert(sizeof(return_value) <= sizeof(zap_data));
MemCopy(&return_value, &zap_data, sizeof(return_value));
CSignature::VerifyParams<Params...>(test_graph_signature_);
CallHelper<int32_t>::Call(reinterpret_cast<void*>(&p)...,
reinterpret_cast<void*>(&return_value));
return return_value;
}
private:
CSignature* test_graph_signature_;
Node* parameter_nodes_[4];
uint32_t return_parameter_index_;
};
template <>
class BufferedRawMachineAssemblerTester<void>
: public RawMachineAssemblerTester<void> {
public:
template <typename... ParamMachTypes>
explicit BufferedRawMachineAssemblerTester(ParamMachTypes... p)
: RawMachineAssemblerTester<void>(((void)p, MachineType::Pointer())...),
test_graph_signature_(
CSignature::New(RawMachineAssemblerTester<void>::main_zone(),
MachineType::None(), p...)) {
static_assert(sizeof...(p) <= arraysize(parameter_nodes_),
"increase parameter_nodes_ array");
std::array<MachineType, sizeof...(p)> p_arr{{p...}};
for (size_t i = 0; i < p_arr.size(); ++i) {
parameter_nodes_[i] = Load(p_arr[i], RawMachineAssembler::Parameter(i));
}
}
Address Generate() override { return RawMachineAssemblerTester::Generate(); }
// The BufferedRawMachineAssemblerTester does not pass parameters directly
// to the constructed IR graph. Instead it passes a pointer to the parameter
// to the IR graph, and adds Load nodes to the IR graph to load the
// parameters from memory. Thereby it is possible to pass 64 bit parameters
// to the IR graph.
Node* Parameter(size_t index) {
CHECK_GT(arraysize(parameter_nodes_), index);
return parameter_nodes_[index];
}
template <typename... Params>
void Call(Params... p) {
CSignature::VerifyParams<Params...>(test_graph_signature_);
CallHelper<void>::Call(reinterpret_cast<void*>(&p)...);
}
private:
CSignature* test_graph_signature_;
Node* parameter_nodes_[4];
};
static const bool USE_RESULT_BUFFER = true;
static const bool USE_RETURN_REGISTER = false;
static const int32_t CHECK_VALUE = 0x99BEEDCE;
// TODO(titzer): use the C-style calling convention, or any register-based
// calling convention for binop tests.
template <typename CType, bool use_result_buffer>
class BinopTester {
public:
explicit BinopTester(RawMachineAssemblerTester<int32_t>* tester,
MachineType type)
: T(tester),
param0(T->LoadFromPointer(&p0, type)),
param1(T->LoadFromPointer(&p1, type)),
type(type),
p0(static_cast<CType>(0)),
p1(static_cast<CType>(0)),
result(static_cast<CType>(0)) {}
RawMachineAssemblerTester<int32_t>* T;
Node* param0;
Node* param1;
CType call(CType a0, CType a1) {
p0 = a0;
p1 = a1;
if (use_result_buffer) {
CHECK_EQ(CHECK_VALUE, T->Call());
return result;
} else {
return static_cast<CType>(T->Call());
}
}
void AddReturn(Node* val) {
if (use_result_buffer) {
T->Store(type.representation(), T->PointerConstant(&result),
T->Int32Constant(0), val, kNoWriteBarrier);
T->Return(T->Int32Constant(CHECK_VALUE));
} else {
T->Return(val);
}
}
template <typename Ci, typename Cj, typename Fn>
void Run(const Ci& ci, const Cj& cj, const Fn& fn) {
typename Ci::const_iterator i;
typename Cj::const_iterator j;
for (i = ci.begin(); i != ci.end(); ++i) {
for (j = cj.begin(); j != cj.end(); ++j) {
CHECK_EQ(fn(*i, *j), this->call(*i, *j));
}
}
}
protected:
MachineType type;
CType p0;
CType p1;
CType result;
};
// A helper class for testing code sequences that take two int parameters and
// return an int value.
class Int32BinopTester : public BinopTester<int32_t, USE_RETURN_REGISTER> {
public:
explicit Int32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<int32_t, USE_RETURN_REGISTER>(tester,
MachineType::Int32()) {}
};
// A helper class for testing code sequences that take two int parameters and
// return an int value.
class Int64BinopTester : public BinopTester<int64_t, USE_RETURN_REGISTER> {
public:
explicit Int64BinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<int64_t, USE_RETURN_REGISTER>(tester,
MachineType::Int64()) {}
};
// A helper class for testing code sequences that take two uint parameters and
// return an uint value.
class Uint32BinopTester : public BinopTester<uint32_t, USE_RETURN_REGISTER> {
public:
explicit Uint32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<uint32_t, USE_RETURN_REGISTER>(tester,
MachineType::Uint32()) {}
uint32_t call(uint32_t a0, uint32_t a1) {
p0 = a0;
p1 = a1;
return static_cast<uint32_t>(T->Call());
}
};
// A helper class for testing code sequences that take two float parameters and
// return a float value.
class Float32BinopTester : public BinopTester<float, USE_RESULT_BUFFER> {
public:
explicit Float32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<float, USE_RESULT_BUFFER>(tester, MachineType::Float32()) {}
};
// A helper class for testing code sequences that take two double parameters and
// return a double value.
class Float64BinopTester : public BinopTester<double, USE_RESULT_BUFFER> {
public:
explicit Float64BinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<double, USE_RESULT_BUFFER>(tester, MachineType::Float64()) {
}
};
// A helper class for testing code sequences that take two pointer parameters
// and return a pointer value.
// TODO(titzer): pick word size of pointers based on V8_TARGET.
template <typename Type>
class PointerBinopTester : public BinopTester<Type, USE_RETURN_REGISTER> {
public:
explicit PointerBinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<Type, USE_RETURN_REGISTER>(tester, MachineType::Pointer()) {
}
};
// A helper class for testing code sequences that take two tagged parameters and
// return a tagged value.
template <typename Type>
class TaggedBinopTester : public BinopTester<Type, USE_RETURN_REGISTER> {
public:
explicit TaggedBinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<Type, USE_RETURN_REGISTER>(tester,
MachineType::AnyTagged()) {}
};
// A helper class for integer binary operations. Wraps a machine opcode and
// provides evaluation routines and the operators.
template <typename T>
class IntBinopWrapper {
public:
explicit IntBinopWrapper(IrOpcode::Value op) : opcode(op) {}
const Operator* op(MachineOperatorBuilder* machine) const {
switch (opcode) {
case IrOpcode::kInt32Add:
return machine->Int32Add();
case IrOpcode::kInt32Sub:
return machine->Int32Sub();
case IrOpcode::kInt32Mul:
return machine->Int32Mul();
case IrOpcode::kWord32And:
return machine->Word32And();
case IrOpcode::kWord32Or:
return machine->Word32Or();
case IrOpcode::kWord32Xor:
return machine->Word32Xor();
case IrOpcode::kInt64Add:
return machine->Int64Add();
case IrOpcode::kInt64Sub:
return machine->Int64Sub();
case IrOpcode::kInt64Mul:
return machine->Int64Mul();
case IrOpcode::kWord64And:
return machine->Word64And();
case IrOpcode::kWord64Or:
return machine->Word64Or();
case IrOpcode::kWord64Xor:
return machine->Word64Xor();
default:
UNREACHABLE();
}
}
T eval(T a, T b) const {
switch (opcode) {
case IrOpcode::kInt32Add:
case IrOpcode::kInt64Add:
return a + b;
case IrOpcode::kInt32Sub:
case IrOpcode::kInt64Sub:
return a - b;
case IrOpcode::kInt32Mul:
case IrOpcode::kInt64Mul:
return a * b;
case IrOpcode::kWord32And:
case IrOpcode::kWord64And:
return a & b;
case IrOpcode::kWord32Or:
case IrOpcode::kWord64Or:
return a | b;
case IrOpcode::kWord32Xor:
case IrOpcode::kWord64Xor:
return a ^ b;
default:
UNREACHABLE();
}
}
IrOpcode::Value opcode;
};
// A helper class for testing compares. Wraps a machine opcode and provides
// evaluation routines and the operators.
class CompareWrapper {
public:
explicit CompareWrapper(IrOpcode::Value op) : opcode(op) {}
Node* MakeNode(RawMachineAssemblerTester<int32_t>* m, Node* a,
Node* b) const {
return m->AddNode(op(m->machine()), a, b);
}
const Operator* op(MachineOperatorBuilder* machine) const {
switch (opcode) {
case IrOpcode::kWord32Equal:
return machine->Word32Equal();
case IrOpcode::kInt32LessThan:
return machine->Int32LessThan();
case IrOpcode::kInt32LessThanOrEqual:
return machine->Int32LessThanOrEqual();
case IrOpcode::kUint32LessThan:
return machine->Uint32LessThan();
case IrOpcode::kUint32LessThanOrEqual:
return machine->Uint32LessThanOrEqual();
case IrOpcode::kWord64Equal:
return machine->Word64Equal();
case IrOpcode::kInt64LessThan:
return machine->Int64LessThan();
case IrOpcode::kInt64LessThanOrEqual:
return machine->Int64LessThanOrEqual();
case IrOpcode::kUint64LessThan:
return machine->Uint64LessThan();
case IrOpcode::kUint64LessThanOrEqual:
return machine->Uint64LessThanOrEqual();
case IrOpcode::kFloat64Equal:
return machine->Float64Equal();
case IrOpcode::kFloat64LessThan:
return machine->Float64LessThan();
case IrOpcode::kFloat64LessThanOrEqual:
return machine->Float64LessThanOrEqual();
default:
UNREACHABLE();
}
}
bool Int32Compare(int32_t a, int32_t b) const {
switch (opcode) {
case IrOpcode::kWord32Equal:
return a == b;
case IrOpcode::kInt32LessThan:
return a < b;
case IrOpcode::kInt32LessThanOrEqual:
return a <= b;
case IrOpcode::kUint32LessThan:
return static_cast<uint32_t>(a) < static_cast<uint32_t>(b);
case IrOpcode::kUint32LessThanOrEqual:
return static_cast<uint32_t>(a) <= static_cast<uint32_t>(b);
default:
UNREACHABLE();
}
}
bool Int64Compare(int64_t a, int64_t b) const {
switch (opcode) {
case IrOpcode::kWord64Equal:
return a == b;
case IrOpcode::kInt64LessThan:
return a < b;
case IrOpcode::kInt64LessThanOrEqual:
return a <= b;
case IrOpcode::kUint64LessThan:
return static_cast<uint64_t>(a) < static_cast<uint64_t>(b);
case IrOpcode::kUint64LessThanOrEqual:
return static_cast<uint64_t>(a) <= static_cast<uint64_t>(b);
default:
UNREACHABLE();
}
}
bool Float64Compare(double a, double b) const {
switch (opcode) {
case IrOpcode::kFloat64Equal:
return a == b;
case IrOpcode::kFloat64LessThan:
return a < b;
case IrOpcode::kFloat64LessThanOrEqual:
return a <= b;
default:
UNREACHABLE();
}
}
IrOpcode::Value opcode;
};
// A small closure class to generate code for a function of two inputs that
// produces a single output so that it can be used in many different contexts.
// The {expected()} method should compute the expected output for a given
// pair of inputs.
template <typename T>
class BinopGen {
public:
virtual void gen(RawMachineAssemblerTester<int32_t>* m, Node* a, Node* b) = 0;
virtual T expected(T a, T b) = 0;
virtual ~BinopGen() = default;
};
// A helper class to generate various combination of input shape combinations
// and run the generated code to ensure it produces the correct results.
class Int32BinopInputShapeTester {
public:
explicit Int32BinopInputShapeTester(BinopGen<int32_t>* g)
: gen(g), input_a(0), input_b(0) {}
void TestAllInputShapes();
private:
BinopGen<int32_t>* gen;
int32_t input_a;
int32_t input_b;
void Run(RawMachineAssemblerTester<int32_t>* m);
void RunLeft(RawMachineAssemblerTester<int32_t>* m);
void RunRight(RawMachineAssemblerTester<int32_t>* m);
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_CCTEST_COMPILER_CODEGEN_TESTER_H_

View File

@ -0,0 +1,133 @@
// Copyright 2014 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 "test/cctest/compiler/function-tester.h"
#include "include/v8-function.h"
#include "src/api/api-inl.h"
#include "src/codegen/assembler.h"
#include "src/codegen/optimized-compilation-info.h"
#include "src/compiler/linkage.h"
#include "src/compiler/pipeline.h"
#include "src/execution/execution.h"
#include "src/handles/handles.h"
#include "src/objects/objects-inl.h"
#include "src/parsing/parse-info.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace compiler {
FunctionTester::FunctionTester(const char* source, uint32_t flags)
: isolate(main_isolate()),
function((v8_flags.allow_natives_syntax = true, NewFunction(source))),
flags_(flags) {
Compile(function);
const uint32_t supported_flags = OptimizedCompilationInfo::kInlining;
CHECK_EQ(0u, flags_ & ~supported_flags);
}
FunctionTester::FunctionTester(DirectHandle<Code> code, int param_count)
: isolate(main_isolate()),
function((v8_flags.allow_natives_syntax = true,
NewFunction(BuildFunction(param_count).c_str()))),
flags_(0) {
CHECK(!code.is_null());
CHECK(IsCode(*code));
Compile(function);
function->UpdateCode(*code);
}
FunctionTester::FunctionTester(DirectHandle<Code> code)
: FunctionTester(code, 0) {}
void FunctionTester::CheckThrows(Handle<Object> a) {
TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
MaybeDirectHandle<Object> no_result = Call(a);
CHECK(isolate->has_exception());
CHECK(try_catch.HasCaught());
CHECK(no_result.is_null());
}
void FunctionTester::CheckThrows(Handle<Object> a, Handle<Object> b) {
TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
MaybeDirectHandle<Object> no_result = Call(a, b);
CHECK(isolate->has_exception());
CHECK(try_catch.HasCaught());
CHECK(no_result.is_null());
}
v8::Local<v8::Message> FunctionTester::CheckThrowsReturnMessage(
Handle<Object> a, Handle<Object> b) {
TryCatch try_catch(reinterpret_cast<v8::Isolate*>(isolate));
MaybeDirectHandle<Object> no_result = Call(a, b);
CHECK(isolate->has_exception());
CHECK(try_catch.HasCaught());
CHECK(no_result.is_null());
CHECK(!try_catch.Message().IsEmpty());
return try_catch.Message();
}
void FunctionTester::CheckCall(DirectHandle<Object> expected, Handle<Object> a,
Handle<Object> b, Handle<Object> c,
Handle<Object> d) {
DirectHandle<Object> result = Call(a, b, c, d).ToHandleChecked();
CHECK(Object::SameValue(*expected, *result));
}
Handle<JSFunction> FunctionTester::NewFunction(const char* source) {
return Cast<JSFunction>(v8::Utils::OpenHandle(
*v8::Local<v8::Function>::Cast(CompileRun(source))));
}
DirectHandle<JSObject> FunctionTester::NewObject(const char* source) {
return Cast<JSObject>(
v8::Utils::OpenHandle(*v8::Local<v8::Object>::Cast(CompileRun(source))));
}
Handle<String> FunctionTester::Val(const char* string) {
return isolate->factory()->InternalizeUtf8String(string);
}
Handle<Object> FunctionTester::Val(double value) {
return isolate->factory()->NewNumber(value);
}
DirectHandle<Object> FunctionTester::infinity() {
return isolate->factory()->infinity_value();
}
DirectHandle<Object> FunctionTester::minus_infinity() {
return Val(-V8_INFINITY);
}
DirectHandle<Object> FunctionTester::nan() {
return isolate->factory()->nan_value();
}
Handle<Object> FunctionTester::undefined() {
return isolate->factory()->undefined_value();
}
DirectHandle<Object> FunctionTester::null() {
return isolate->factory()->null_value();
}
Handle<Object> FunctionTester::true_value() {
return isolate->factory()->true_value();
}
Handle<Object> FunctionTester::false_value() {
return isolate->factory()->false_value();
}
Handle<JSFunction> FunctionTester::Compile(Handle<JSFunction> f) {
Zone zone(isolate->allocator(), ZONE_NAME);
return Optimize(f, &zone, isolate, flags_);
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,142 @@
// Copyright 2014 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.
#ifndef V8_CCTEST_COMPILER_FUNCTION_TESTER_H_
#define V8_CCTEST_COMPILER_FUNCTION_TESTER_H_
#include "src/execution/execution.h"
#include "src/handles/handles.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
class CallInterfaceDescriptor;
class Isolate;
namespace compiler {
class FunctionTester : public InitializedHandleScope {
public:
explicit FunctionTester(const char* source, uint32_t flags = 0);
FunctionTester(DirectHandle<Code> code, int param_count);
// Assumes VoidDescriptor call interface.
explicit FunctionTester(DirectHandle<Code> code);
Isolate* isolate;
Handle<JSFunction> function;
MaybeHandle<Object> Call() {
return Execution::Call(isolate, function, undefined(), {});
}
template <typename Arg1, typename... Args>
MaybeHandle<Object> Call(Arg1 arg1, Args... args) {
const int nof_args = sizeof...(Args) + 1;
DirectHandle<Object> call_args[] = {arg1, args...};
return Execution::Call(isolate, function, undefined(),
{call_args, nof_args});
}
template <typename T, typename... Args>
DirectHandle<T> CallChecked(Args... args) {
DirectHandle<Object> result = Call(args...).ToHandleChecked();
return Cast<T>(result);
}
void CheckThrows(Handle<Object> a);
void CheckThrows(Handle<Object> a, Handle<Object> b);
v8::Local<v8::Message> CheckThrowsReturnMessage(Handle<Object> a,
Handle<Object> b);
void CheckCall(DirectHandle<Object> expected, Handle<Object> a,
Handle<Object> b, Handle<Object> c, Handle<Object> d);
void CheckCall(DirectHandle<Object> expected, Handle<Object> a,
Handle<Object> b, Handle<Object> c) {
return CheckCall(expected, a, b, c, undefined());
}
void CheckCall(DirectHandle<Object> expected, Handle<Object> a,
Handle<Object> b) {
return CheckCall(expected, a, b, undefined());
}
void CheckCall(DirectHandle<Object> expected, Handle<Object> a) {
CheckCall(expected, a, undefined());
}
void CheckCall(DirectHandle<Object> expected) {
CheckCall(expected, undefined());
}
void CheckCall(double expected, double a, double b) {
CheckCall(Val(expected), Val(a), Val(b));
}
void CheckTrue(Handle<Object> a) { CheckCall(true_value(), a); }
void CheckTrue(Handle<Object> a, Handle<Object> b) {
CheckCall(true_value(), a, b);
}
void CheckTrue(Handle<Object> a, Handle<Object> b, Handle<Object> c) {
CheckCall(true_value(), a, b, c);
}
void CheckTrue(Handle<Object> a, Handle<Object> b, Handle<Object> c,
Handle<Object> d) {
CheckCall(true_value(), a, b, c, d);
}
void CheckTrue(double a, double b) {
CheckCall(true_value(), Val(a), Val(b));
}
void CheckFalse(Handle<Object> a) { CheckCall(false_value(), a); }
void CheckFalse(Handle<Object> a, Handle<Object> b) {
CheckCall(false_value(), a, b);
}
void CheckFalse(double a, double b) {
CheckCall(false_value(), Val(a), Val(b));
}
Handle<JSFunction> NewFunction(const char* source);
DirectHandle<JSObject> NewObject(const char* source);
Handle<String> Val(const char* string);
Handle<Object> Val(double value);
DirectHandle<Object> infinity();
DirectHandle<Object> minus_infinity();
DirectHandle<Object> nan();
Handle<Object> undefined();
DirectHandle<Object> null();
Handle<Object> true_value();
Handle<Object> false_value();
private:
uint32_t flags_;
Handle<JSFunction> Compile(Handle<JSFunction> function);
std::string BuildFunction(int param_count) {
std::string function_string = "(function(";
if (param_count > 0) {
function_string += 'a';
for (int i = 1; i < param_count; i++) {
function_string += ',';
function_string += static_cast<char>('a' + i);
}
}
function_string += "){})";
return function_string;
}
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_CCTEST_COMPILER_FUNCTION_TESTER_H_

View File

@ -0,0 +1,43 @@
// Copyright 2019 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.
#ifndef V8_CCTEST_COMPILER_GRAPH_AND_BUILDERS_H_
#define V8_CCTEST_COMPILER_GRAPH_AND_BUILDERS_H_
#include "src/compiler/backend/instruction-selector.h"
#include "src/compiler/simplified-operator.h"
namespace v8 {
namespace internal {
namespace compiler {
class GraphAndBuilders {
public:
explicit GraphAndBuilders(Zone* zone)
: main_graph_(zone->New<TFGraph>(zone)),
main_common_(zone),
main_machine_(zone, MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags(),
InstructionSelector::AlignmentRequirements()),
main_simplified_(zone) {}
TFGraph* graph() const { return main_graph_; }
Zone* zone() const { return graph()->zone(); }
CommonOperatorBuilder* common() { return &main_common_; }
MachineOperatorBuilder* machine() { return &main_machine_; }
SimplifiedOperatorBuilder* simplified() { return &main_simplified_; }
protected:
// Prefixed with main_ to avoid naming conflicts.
TFGraph* main_graph_;
CommonOperatorBuilder main_common_;
MachineOperatorBuilder main_machine_;
SimplifiedOperatorBuilder main_simplified_;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_CCTEST_COMPILER_GRAPH_AND_BUILDERS_H_

View File

@ -0,0 +1,114 @@
// 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.
#ifndef V8_CCTEST_COMPILER_JS_HEAP_BROKER_H_
#define V8_CCTEST_COMPILER_JS_HEAP_BROKER_H_
namespace v8 {
namespace internal {
namespace compiler {
class CanonicalHandles {
public:
CanonicalHandles(Isolate* isolate, Zone* zone)
: isolate_(isolate),
canonical_handles_(std::make_unique<CanonicalHandlesMap>(
isolate->heap(), ZoneAllocationPolicy(zone))) {}
template <typename T>
IndirectHandle<T> Create(Tagged<T> object) {
CHECK_NOT_NULL(canonical_handles_);
auto find_result = canonical_handles_->FindOrInsert(object);
if (!find_result.already_exists) {
*find_result.entry = IndirectHandle<T>(object, isolate_).location();
}
return IndirectHandle<T>(*find_result.entry);
}
template <typename T>
IndirectHandle<T> Create(T object) {
static_assert(kTaggedCanConvertToRawObjects);
return Create(Tagged<T>(object));
}
template <typename T>
IndirectHandle<T> Create(IndirectHandle<T> handle) {
return Create(*handle);
}
template <typename T>
IndirectHandle<T> Create(DirectHandle<T> handle) {
return Create(*handle);
}
std::unique_ptr<CanonicalHandlesMap> Detach() {
DCHECK_NOT_NULL(canonical_handles_);
return std::move(canonical_handles_);
}
private:
Isolate* isolate_;
std::unique_ptr<CanonicalHandlesMap> canonical_handles_;
};
class JSHeapBrokerTestBase {
public:
JSHeapBrokerTestBase(Isolate* isolate, Zone* zone)
: broker_(isolate, zone),
broker_scope_(&broker_,
std::make_unique<CanonicalHandlesMap>(
isolate->heap(), ZoneAllocationPolicy(zone))),
current_broker_(&broker_) {
if (!PersistentHandlesScope::IsActive(isolate)) {
persistent_scope_.emplace(isolate);
}
}
JSHeapBrokerTestBase(Isolate* isolate, Zone* zone, CanonicalHandles&& handles)
: broker_(isolate, zone),
broker_scope_(&broker_, handles.Detach()),
current_broker_(&broker_) {
if (!PersistentHandlesScope::IsActive(isolate)) {
persistent_scope_.emplace(isolate);
}
}
~JSHeapBrokerTestBase() {
if (persistent_scope_) {
persistent_scope_->Detach();
}
}
JSHeapBroker* broker() { return &broker_; }
template <typename T>
IndirectHandle<T> CanonicalHandle(Tagged<T> object) {
return broker()->CanonicalPersistentHandle(object);
}
template <typename T>
IndirectHandle<T> CanonicalHandle(T object) {
static_assert(kTaggedCanConvertToRawObjects);
return CanonicalHandle(Tagged<T>(object));
}
template <typename T>
IndirectHandle<T> CanonicalHandle(IndirectHandle<T> handle) {
return CanonicalHandle(*handle);
}
template <typename T>
IndirectHandle<T> CanonicalHandle(DirectHandle<T> handle) {
return CanonicalHandle(*handle);
}
private:
JSHeapBroker broker_;
JSHeapBrokerScopeForTesting broker_scope_;
CurrentHeapBrokerScope current_broker_;
std::optional<PersistentHandlesScope> persistent_scope_;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_CCTEST_COMPILER_JS_HEAP_BROKER_H_

View File

@ -0,0 +1,401 @@
// Copyright 2021 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/bits.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/codegen-tester.h"
#include "test/common/value-helper.h"
namespace v8 {
namespace internal {
namespace compiler {
#if V8_TARGET_LITTLE_ENDIAN
#define LSB(addr, bytes) addr
#elif V8_TARGET_BIG_ENDIAN
#define LSB(addr, bytes) reinterpret_cast<uint8_t*>(addr + 1) - (bytes)
#else
#error "Unknown Architecture"
#endif
#define TEST_ATOMIC_LOAD_INTEGER(ctype, itype, mach_type, order) \
do { \
ctype buffer[1]; \
\
RawMachineAssemblerTester<ctype> m; \
Node* base = m.PointerConstant(&buffer[0]); \
Node* index = m.IntPtrConstant(0); \
AtomicLoadParameters params(mach_type, order); \
if (mach_type.MemSize() == 8) { \
m.Return(m.AtomicLoad64(params, base, index)); \
} else { \
m.Return(m.AtomicLoad(params, base, index)); \
} \
\
FOR_INPUTS(ctype, itype, i) { \
buffer[0] = i; \
CHECK_EQ(i, m.Call()); \
} \
} while (false)
TEST(AcquireLoadInteger) {
TEST_ATOMIC_LOAD_INTEGER(int8_t, int8, MachineType::Int8(),
AtomicMemoryOrder::kAcqRel);
TEST_ATOMIC_LOAD_INTEGER(uint8_t, uint8, MachineType::Uint8(),
AtomicMemoryOrder::kAcqRel);
TEST_ATOMIC_LOAD_INTEGER(int16_t, int16, MachineType::Int16(),
AtomicMemoryOrder::kAcqRel);
TEST_ATOMIC_LOAD_INTEGER(uint16_t, uint16, MachineType::Uint16(),
AtomicMemoryOrder::kAcqRel);
TEST_ATOMIC_LOAD_INTEGER(int32_t, int32, MachineType::Int32(),
AtomicMemoryOrder::kAcqRel);
TEST_ATOMIC_LOAD_INTEGER(uint32_t, uint32, MachineType::Uint32(),
AtomicMemoryOrder::kAcqRel);
#if V8_TARGET_ARCH_64_BIT
TEST_ATOMIC_LOAD_INTEGER(uint64_t, uint64, MachineType::Uint64(),
AtomicMemoryOrder::kAcqRel);
#endif
}
TEST(SeqCstLoadInteger) {
TEST_ATOMIC_LOAD_INTEGER(int8_t, int8, MachineType::Int8(),
AtomicMemoryOrder::kSeqCst);
TEST_ATOMIC_LOAD_INTEGER(uint8_t, uint8, MachineType::Uint8(),
AtomicMemoryOrder::kSeqCst);
TEST_ATOMIC_LOAD_INTEGER(int16_t, int16, MachineType::Int16(),
AtomicMemoryOrder::kSeqCst);
TEST_ATOMIC_LOAD_INTEGER(uint16_t, uint16, MachineType::Uint16(),
AtomicMemoryOrder::kSeqCst);
TEST_ATOMIC_LOAD_INTEGER(int32_t, int32, MachineType::Int32(),
AtomicMemoryOrder::kSeqCst);
TEST_ATOMIC_LOAD_INTEGER(uint32_t, uint32, MachineType::Uint32(),
AtomicMemoryOrder::kSeqCst);
#if V8_TARGET_ARCH_64_BIT
TEST_ATOMIC_LOAD_INTEGER(uint64_t, uint64, MachineType::Uint64(),
AtomicMemoryOrder::kSeqCst);
#endif
}
namespace {
// Mostly same as CHECK_EQ() but customized for compressed tagged values.
template <typename CType>
void CheckEq(CType in_value, CType out_value) {
CHECK_EQ(in_value, out_value);
}
#ifdef V8_COMPRESS_POINTERS
// Specializations for checking the result of compressing store.
template <>
void CheckEq<Tagged<Object>>(Tagged<Object> in_value,
Tagged<Object> out_value) {
// Compare only lower 32-bits of the value because tagged load/stores are
// 32-bit operations anyway.
CHECK_EQ(static_cast<Tagged_t>(in_value.ptr()),
static_cast<Tagged_t>(out_value.ptr()));
}
template <>
void CheckEq<Tagged<HeapObject>>(Tagged<HeapObject> in_value,
Tagged<HeapObject> out_value) {
return CheckEq<Tagged<Object>>(in_value, out_value);
}
template <>
void CheckEq<Tagged<Smi>>(Tagged<Smi> in_value, Tagged<Smi> out_value) {
return CheckEq<Tagged<Object>>(in_value, out_value);
}
#endif
template <typename T>
void InitBuffer(Tagged<T>* buffer, size_t length, MachineType type) {
const size_t kBufferSize = sizeof(Tagged<T>) * length;
// Tagged field loads require values to be properly tagged because of
// pointer decompression that may be happenning during load.
Isolate* isolate = CcTest::InitIsolateOnce();
Tagged<Smi>* smi_view = reinterpret_cast<Tagged<Smi>*>(&buffer[0]);
if (type.IsTaggedSigned()) {
for (size_t i = 0; i < length; i++) {
smi_view[i] = Smi::FromInt(static_cast<int>(i + kBufferSize) ^ 0xABCDEF0);
}
} else {
memcpy(&buffer[0], &isolate->roots_table(), kBufferSize);
if (!type.IsTaggedPointer()) {
// Also add some Smis if we are checking AnyTagged case.
for (size_t i = 0; i < length / 2; i++) {
smi_view[i] =
Smi::FromInt(static_cast<int>(i + kBufferSize) ^ 0xABCDEF0);
}
}
}
}
template <typename T>
void AtomicLoadTagged(MachineType type, AtomicMemoryOrder order) {
const int kNumElems = 16;
Tagged<T> buffer[kNumElems];
InitBuffer(buffer, kNumElems, type);
for (int i = 0; i < kNumElems; i++) {
BufferedRawMachineAssemblerTester<Tagged<T>> m;
Tagged<T>* base_pointer = &buffer[0];
if (COMPRESS_POINTERS_BOOL) {
base_pointer =
reinterpret_cast<Tagged<T>*>(LSB(base_pointer, kTaggedSize));
}
Node* base = m.PointerConstant(base_pointer);
Node* index = m.IntPtrConstant(i * sizeof(buffer[0]));
AtomicLoadParameters params(type, order);
Node* load;
if (kTaggedSize == 8) {
load = m.AtomicLoad64(params, base, index);
} else {
load = m.ChangeInt32ToIntPtr(m.AtomicLoad(params, base, index));
}
m.Return(load);
CheckEq<Tagged<T>>(buffer[i], m.Call());
}
}
} // namespace
TEST(AcquireLoadTagged) {
AtomicLoadTagged<Smi>(MachineType::TaggedSigned(),
AtomicMemoryOrder::kAcqRel);
AtomicLoadTagged<HeapObject>(MachineType::TaggedPointer(),
AtomicMemoryOrder::kAcqRel);
AtomicLoadTagged<Object>(MachineType::AnyTagged(),
AtomicMemoryOrder::kAcqRel);
}
TEST(SeqCstLoadTagged) {
AtomicLoadTagged<Smi>(MachineType::TaggedSigned(),
AtomicMemoryOrder::kSeqCst);
AtomicLoadTagged<HeapObject>(MachineType::TaggedPointer(),
AtomicMemoryOrder::kSeqCst);
AtomicLoadTagged<Object>(MachineType::AnyTagged(),
AtomicMemoryOrder::kSeqCst);
}
#define TEST_ATOMIC_STORE_INTEGER(ctype, itype, mach_type, order) \
do { \
ctype buffer[1]; \
buffer[0] = static_cast<ctype>(-1); \
\
BufferedRawMachineAssemblerTester<int32_t> m(mach_type); \
Node* value = m.Parameter(0); \
Node* base = m.PointerConstant(&buffer[0]); \
Node* index = m.IntPtrConstant(0); \
AtomicStoreParameters params(mach_type.representation(), kNoWriteBarrier, \
order); \
if (mach_type.MemSize() == 8) { \
m.AtomicStore64(params, base, index, value, nullptr); \
} else { \
m.AtomicStore(params, base, index, value); \
} \
\
int32_t OK = 0x29000; \
m.Return(m.Int32Constant(OK)); \
\
FOR_INPUTS(ctype, itype, i) { \
CHECK_EQ(OK, m.Call(i)); \
CHECK_EQ(i, buffer[0]); \
} \
} while (false)
TEST(ReleaseStoreInteger) {
TEST_ATOMIC_STORE_INTEGER(int8_t, int8, MachineType::Int8(),
AtomicMemoryOrder::kAcqRel);
TEST_ATOMIC_STORE_INTEGER(uint8_t, uint8, MachineType::Uint8(),
AtomicMemoryOrder::kAcqRel);
TEST_ATOMIC_STORE_INTEGER(int16_t, int16, MachineType::Int16(),
AtomicMemoryOrder::kAcqRel);
TEST_ATOMIC_STORE_INTEGER(uint16_t, uint16, MachineType::Uint16(),
AtomicMemoryOrder::kAcqRel);
TEST_ATOMIC_STORE_INTEGER(int32_t, int32, MachineType::Int32(),
AtomicMemoryOrder::kAcqRel);
TEST_ATOMIC_STORE_INTEGER(uint32_t, uint32, MachineType::Uint32(),
AtomicMemoryOrder::kAcqRel);
#if V8_TARGET_ARCH_64_BIT
TEST_ATOMIC_STORE_INTEGER(uint64_t, uint64, MachineType::Uint64(),
AtomicMemoryOrder::kAcqRel);
#endif
}
TEST(SeqCstStoreInteger) {
TEST_ATOMIC_STORE_INTEGER(int8_t, int8, MachineType::Int8(),
AtomicMemoryOrder::kSeqCst);
TEST_ATOMIC_STORE_INTEGER(uint8_t, uint8, MachineType::Uint8(),
AtomicMemoryOrder::kSeqCst);
TEST_ATOMIC_STORE_INTEGER(int16_t, int16, MachineType::Int16(),
AtomicMemoryOrder::kSeqCst);
TEST_ATOMIC_STORE_INTEGER(uint16_t, uint16, MachineType::Uint16(),
AtomicMemoryOrder::kSeqCst);
TEST_ATOMIC_STORE_INTEGER(int32_t, int32, MachineType::Int32(),
AtomicMemoryOrder::kSeqCst);
TEST_ATOMIC_STORE_INTEGER(uint32_t, uint32, MachineType::Uint32(),
AtomicMemoryOrder::kSeqCst);
#if V8_TARGET_ARCH_64_BIT
TEST_ATOMIC_STORE_INTEGER(uint64_t, uint64, MachineType::Uint64(),
AtomicMemoryOrder::kSeqCst);
#endif
}
namespace {
template <typename T>
void AtomicStoreTagged(MachineType type, AtomicMemoryOrder order) {
// This tests that tagged values are correctly transferred by atomic loads and
// stores from in_buffer to out_buffer. For each particular element in
// in_buffer, it is copied to a different index in out_buffer, and all other
// indices are zapped, to test instructions of the correct width are emitted.
const int kNumElems = 16;
Tagged<T> in_buffer[kNumElems];
Tagged<T> out_buffer[kNumElems];
uintptr_t zap_data[] = {kZapValue, kZapValue};
Tagged<T> zap_value;
static_assert(sizeof(Tagged<T>) <= sizeof(zap_data));
MemCopy(&zap_value, &zap_data, sizeof(Tagged<T>));
InitBuffer(in_buffer, kNumElems, type);
#ifdef V8_TARGET_BIG_ENDIAN
int offset = sizeof(Tagged<T>) - ElementSizeInBytes(type.representation());
#else
int offset = 0;
#endif
for (int32_t x = 0; x < kNumElems; x++) {
int32_t y = kNumElems - x - 1;
RawMachineAssemblerTester<int32_t> m;
int32_t OK = 0x29000 + x;
Node* in_base = m.PointerConstant(in_buffer);
Node* in_index = m.IntPtrConstant(x * sizeof(Tagged<T>) + offset);
Node* out_base = m.PointerConstant(out_buffer);
Node* out_index = m.IntPtrConstant(y * sizeof(Tagged<T>) + offset);
Node* load;
AtomicLoadParameters load_params(type, order);
AtomicStoreParameters store_params(type.representation(), kNoWriteBarrier,
order);
if (kTaggedSize == 4) {
load = m.AtomicLoad(load_params, in_base, in_index);
m.AtomicStore(store_params, out_base, out_index, load);
} else {
DCHECK(m.machine()->Is64());
load = m.AtomicLoad64(load_params, in_base, in_index);
m.AtomicStore64(store_params, out_base, out_index, load, nullptr);
}
m.Return(m.Int32Constant(OK));
for (int32_t z = 0; z < kNumElems; z++) {
out_buffer[z] = zap_value;
}
CHECK_NE(in_buffer[x], out_buffer[y]);
CHECK_EQ(OK, m.Call());
// Mostly same as CHECK_EQ() but customized for compressed tagged values.
CheckEq<Tagged<T>>(in_buffer[x], out_buffer[y]);
for (int32_t z = 0; z < kNumElems; z++) {
if (z != y) CHECK_EQ(zap_value, out_buffer[z]);
}
}
}
} // namespace
TEST(ReleaseStoreTagged) {
AtomicStoreTagged<Smi>(MachineType::TaggedSigned(),
AtomicMemoryOrder::kAcqRel);
AtomicStoreTagged<HeapObject>(MachineType::TaggedPointer(),
AtomicMemoryOrder::kAcqRel);
AtomicStoreTagged<Object>(MachineType::AnyTagged(),
AtomicMemoryOrder::kAcqRel);
}
TEST(SeqCstStoreTagged) {
AtomicStoreTagged<Smi>(MachineType::TaggedSigned(),
AtomicMemoryOrder::kSeqCst);
AtomicStoreTagged<HeapObject>(MachineType::TaggedPointer(),
AtomicMemoryOrder::kSeqCst);
AtomicStoreTagged<Object>(MachineType::AnyTagged(),
AtomicMemoryOrder::kSeqCst);
}
#if V8_TARGET_ARCH_32_BIT
namespace {
void TestAtomicPairLoadInteger(AtomicMemoryOrder order) {
uint64_t buffer[1];
uint32_t high;
uint32_t low;
BufferedRawMachineAssemblerTester<int32_t> m;
Node* base = m.PointerConstant(&buffer[0]);
Node* index = m.Int32Constant(0);
Node* pair_load = m.AtomicLoad64(
AtomicLoadParameters(MachineType::Uint64(), order), base, index);
m.StoreToPointer(&low, MachineRepresentation::kWord32,
m.Projection(0, pair_load));
m.StoreToPointer(&high, MachineRepresentation::kWord32,
m.Projection(1, pair_load));
int32_t OK = 0x29000;
m.Return(m.Int32Constant(OK));
FOR_UINT64_INPUTS(i) {
buffer[0] = i;
CHECK_EQ(OK, m.Call());
CHECK_EQ(i, make_uint64(high, low));
}
}
} // namespace
TEST(AcquirePairLoadInteger) {
TestAtomicPairLoadInteger(AtomicMemoryOrder::kAcqRel);
}
TEST(SeqCstPairLoadInteger) {
TestAtomicPairLoadInteger(AtomicMemoryOrder::kSeqCst);
}
namespace {
void TestAtomicPairStoreInteger(AtomicMemoryOrder order) {
uint64_t buffer[1];
BufferedRawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* base = m.PointerConstant(&buffer[0]);
Node* index = m.Int32Constant(0);
m.AtomicStore64(AtomicStoreParameters(MachineRepresentation::kWord64,
kNoWriteBarrier, order),
base, index, m.Parameter(0), m.Parameter(1));
int32_t OK = 0x29000;
m.Return(m.Int32Constant(OK));
FOR_UINT64_INPUTS(i) {
CHECK_EQ(OK, m.Call(static_cast<uint32_t>(i & 0xFFFFFFFF),
static_cast<uint32_t>(i >> 32)));
CHECK_EQ(i, buffer[0]);
}
}
} // namespace
TEST(ReleasePairStoreInteger) {
TestAtomicPairStoreInteger(AtomicMemoryOrder::kAcqRel);
}
TEST(SeqCstPairStoreInteger) {
TestAtomicPairStoreInteger(AtomicMemoryOrder::kSeqCst);
}
#endif // V8_TARGET_ARCH_32_BIT
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,145 @@
// Copyright 2014 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/diagnostics/basic-block-profiler.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/codegen-tester.h"
namespace v8 {
namespace internal {
namespace compiler {
class BasicBlockProfilerTest : public RawMachineAssemblerTester<int32_t> {
public:
BasicBlockProfilerTest()
: RawMachineAssemblerTester<int32_t>(MachineType::Int32()) {
v8_flags.turbo_profiling = true;
}
void ResetCounts() {
BasicBlockProfiler::Get()->ResetCounts(CcTest::i_isolate());
}
void Expect(size_t size, uint32_t* expected) {
const BasicBlockProfiler::DataList* l =
BasicBlockProfiler::Get()->data_list();
CHECK_NE(0, static_cast<int>(l->size()));
const BasicBlockProfilerData* data = l->back().get();
CHECK_EQ(static_cast<int>(size), static_cast<int>(data->n_blocks()));
const uint32_t* counts = data->counts();
for (size_t i = 0; i < size; ++i) {
CHECK_EQ(expected[i], counts[i]);
}
}
void SetCounts(size_t size, uint32_t* new_counts) {
const BasicBlockProfiler::DataList* l =
BasicBlockProfiler::Get()->data_list();
CHECK_NE(0, static_cast<int>(l->size()));
BasicBlockProfilerData* data = l->back().get();
CHECK_EQ(static_cast<int>(size), static_cast<int>(data->n_blocks()));
uint32_t* counts = const_cast<uint32_t*>(data->counts());
for (size_t i = 0; i < size; ++i) {
counts[i] = new_counts[i];
}
}
};
TEST(ProfileDiamond) {
BasicBlockProfilerTest m;
RawMachineLabel blocka, blockb, end;
m.Branch(m.Parameter(0), &blocka, &blockb);
m.Bind(&blocka);
m.Goto(&end);
m.Bind(&blockb);
m.Goto(&end);
m.Bind(&end);
m.Return(m.Int32Constant(0));
m.GenerateCode();
{
uint32_t expected[] = {0, 0, 0, 0, 0, 0, 0};
m.Expect(arraysize(expected), expected);
}
m.Call(0);
{
uint32_t expected[] = {1, 1, 1, 0, 0, 1, 0};
m.Expect(arraysize(expected), expected);
}
m.ResetCounts();
m.Call(1);
{
uint32_t expected[] = {1, 0, 0, 1, 1, 1, 0};
m.Expect(arraysize(expected), expected);
}
m.Call(0);
{
uint32_t expected[] = {2, 1, 1, 1, 1, 2, 0};
m.Expect(arraysize(expected), expected);
}
// Set the counters very high, to verify that they saturate rather than
// overflowing.
uint32_t near_overflow[] = {UINT32_MAX - 1,
UINT32_MAX - 1,
UINT32_MAX - 1,
UINT32_MAX - 1,
UINT32_MAX - 1,
UINT32_MAX - 1,
0};
m.SetCounts(arraysize(near_overflow), near_overflow);
m.Expect(arraysize(near_overflow), near_overflow);
m.Call(0);
m.Call(0);
{
uint32_t expected[] = {
UINT32_MAX, UINT32_MAX, UINT32_MAX, UINT32_MAX - 1,
UINT32_MAX - 1, UINT32_MAX, 0};
m.Expect(arraysize(expected), expected);
}
}
TEST(ProfileLoop) {
BasicBlockProfilerTest m;
RawMachineLabel header, body, end;
Node* one = m.Int32Constant(1);
m.Goto(&header);
m.Bind(&header);
Node* count = m.Phi(MachineRepresentation::kWord32, m.Parameter(0), one);
m.Branch(count, &body, &end);
m.Bind(&body);
count->ReplaceInput(1, m.Int32Sub(count, one));
m.Goto(&header);
m.Bind(&end);
m.Return(one);
m.GenerateCode();
{
uint32_t expected[] = {0, 0, 0, 0, 0, 0, 0};
m.Expect(arraysize(expected), expected);
}
uint32_t runs[] = {0, 1, 500, 10000};
for (size_t i = 0; i < arraysize(runs); i++) {
m.ResetCounts();
CHECK_EQ(1, m.Call(static_cast<int>(runs[i])));
uint32_t expected[] = {1, runs[i] + 1, runs[i], runs[i], 1, 1, 0};
m.Expect(arraysize(expected), expected);
}
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,890 @@
// Copyright 2014 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/overflowing-math.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/codegen-tester.h"
#include "test/common/value-helper.h"
namespace v8 {
namespace internal {
namespace compiler {
static IrOpcode::Value int32cmp_opcodes[] = {
IrOpcode::kWord32Equal, IrOpcode::kInt32LessThan,
IrOpcode::kInt32LessThanOrEqual, IrOpcode::kUint32LessThan,
IrOpcode::kUint32LessThanOrEqual};
TEST(BranchCombineWord32EqualZero_1) {
// Test combining a branch with x == 0
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
int32_t eq_constant = -1033;
int32_t ne_constant = 825118;
Node* p0 = m.Parameter(0);
RawMachineLabel blocka, blockb;
m.Branch(m.Word32Equal(p0, m.Int32Constant(0)), &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
int32_t expect = a == 0 ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineWord32EqualZero_chain) {
// Test combining a branch with a chain of x == 0 == 0 == 0 ...
int32_t eq_constant = -1133;
int32_t ne_constant = 815118;
for (int k = 0; k < 6; k++) {
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
Node* p0 = m.Parameter(0);
RawMachineLabel blocka, blockb;
Node* cond = p0;
for (int j = 0; j < k; j++) {
cond = m.Word32Equal(cond, m.Int32Constant(0));
}
m.Branch(cond, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
int32_t expect = (k & 1) == 1 ? (a == 0 ? eq_constant : ne_constant)
: (a == 0 ? ne_constant : eq_constant);
CHECK_EQ(expect, m.Call(a));
}
}
}
TEST(BranchCombineInt32LessThanZero_1) {
// Test combining a branch with x < 0
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
int32_t eq_constant = -1433;
int32_t ne_constant = 845118;
Node* p0 = m.Parameter(0);
RawMachineLabel blocka, blockb;
m.Branch(m.Int32LessThan(p0, m.Int32Constant(0)), &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
int32_t expect = a < 0 ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineUint32LessThan100_1) {
// Test combining a branch with x < 100
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32());
int32_t eq_constant = 1471;
int32_t ne_constant = 88845718;
Node* p0 = m.Parameter(0);
RawMachineLabel blocka, blockb;
m.Branch(m.Uint32LessThan(p0, m.Int32Constant(100)), &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
FOR_UINT32_INPUTS(a) {
int32_t expect = a < 100 ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineUint32LessThanOrEqual100_1) {
// Test combining a branch with x <= 100
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32());
int32_t eq_constant = 1479;
int32_t ne_constant = 77845719;
Node* p0 = m.Parameter(0);
RawMachineLabel blocka, blockb;
m.Branch(m.Uint32LessThanOrEqual(p0, m.Int32Constant(100)), &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
FOR_UINT32_INPUTS(a) {
int32_t expect = a <= 100 ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineZeroLessThanInt32_1) {
// Test combining a branch with 0 < x
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
int32_t eq_constant = -2033;
int32_t ne_constant = 225118;
Node* p0 = m.Parameter(0);
RawMachineLabel blocka, blockb;
m.Branch(m.Int32LessThan(m.Int32Constant(0), p0), &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
int32_t expect = 0 < a ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineInt32GreaterThanZero_1) {
// Test combining a branch with x > 0
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
int32_t eq_constant = -1073;
int32_t ne_constant = 825178;
Node* p0 = m.Parameter(0);
RawMachineLabel blocka, blockb;
m.Branch(m.Int32GreaterThan(p0, m.Int32Constant(0)), &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
int32_t expect = a > 0 ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineWord32EqualP) {
// Test combining a branch with an Word32Equal.
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
int32_t eq_constant = -1035;
int32_t ne_constant = 825018;
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
RawMachineLabel blocka, blockb;
m.Branch(m.Word32Equal(p0, p1), &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = a == b ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineWord32EqualI) {
int32_t eq_constant = -1135;
int32_t ne_constant = 925718;
for (int left = 0; left < 2; left++) {
FOR_INT32_INPUTS(a) {
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
Node* p0 = m.Int32Constant(a);
Node* p1 = m.Parameter(0);
RawMachineLabel blocka, blockb;
if (left == 1) m.Branch(m.Word32Equal(p0, p1), &blocka, &blockb);
if (left == 0) m.Branch(m.Word32Equal(p1, p0), &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
FOR_INT32_INPUTS(b) {
int32_t expect = a == b ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(b));
}
}
}
}
TEST(BranchCombineInt32CmpP) {
int32_t eq_constant = -1235;
int32_t ne_constant = 725018;
for (int op = 0; op < 2; op++) {
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
RawMachineLabel blocka, blockb;
if (op == 0) m.Branch(m.Int32LessThan(p0, p1), &blocka, &blockb);
if (op == 1) m.Branch(m.Int32LessThanOrEqual(p0, p1), &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = 0;
if (op == 0) expect = a < b ? eq_constant : ne_constant;
if (op == 1) expect = a <= b ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
}
TEST(BranchCombineInt32CmpI) {
int32_t eq_constant = -1175;
int32_t ne_constant = 927711;
for (int op = 0; op < 2; op++) {
FOR_INT32_INPUTS(a) {
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
Node* p0 = m.Int32Constant(a);
Node* p1 = m.Parameter(0);
RawMachineLabel blocka, blockb;
if (op == 0) m.Branch(m.Int32LessThan(p0, p1), &blocka, &blockb);
if (op == 1) m.Branch(m.Int32LessThanOrEqual(p0, p1), &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
FOR_INT32_INPUTS(b) {
int32_t expect = 0;
if (op == 0) expect = a < b ? eq_constant : ne_constant;
if (op == 1) expect = a <= b ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(b));
}
}
}
}
// Now come the sophisticated tests for many input shape combinations.
// Materializes a boolean (1 or 0) from a comparison.
class CmpMaterializeBoolGen : public BinopGen<int32_t> {
public:
CompareWrapper w;
bool invert;
CmpMaterializeBoolGen(IrOpcode::Value opcode, bool i)
: w(opcode), invert(i) {}
void gen(RawMachineAssemblerTester<int32_t>* m, Node* a, Node* b) override {
Node* cond = w.MakeNode(m, a, b);
if (invert) cond = m->Word32Equal(cond, m->Int32Constant(0));
m->Return(cond);
}
int32_t expected(int32_t a, int32_t b) override {
if (invert) return !w.Int32Compare(a, b) ? 1 : 0;
return w.Int32Compare(a, b) ? 1 : 0;
}
};
// Generates a branch and return one of two values from a comparison.
class CmpBranchGen : public BinopGen<int32_t> {
public:
CompareWrapper w;
bool invert;
bool true_first;
int32_t eq_constant;
int32_t ne_constant;
CmpBranchGen(IrOpcode::Value opcode, bool i, bool t, int32_t eq, int32_t ne)
: w(opcode), invert(i), true_first(t), eq_constant(eq), ne_constant(ne) {}
void gen(RawMachineAssemblerTester<int32_t>* m, Node* a, Node* b) override {
RawMachineLabel blocka, blockb;
Node* cond = w.MakeNode(m, a, b);
if (invert) cond = m->Word32Equal(cond, m->Int32Constant(0));
m->Branch(cond, &blocka, &blockb);
if (true_first) {
m->Bind(&blocka);
m->Return(m->Int32Constant(eq_constant));
m->Bind(&blockb);
m->Return(m->Int32Constant(ne_constant));
} else {
m->Bind(&blockb);
m->Return(m->Int32Constant(ne_constant));
m->Bind(&blocka);
m->Return(m->Int32Constant(eq_constant));
}
}
int32_t expected(int32_t a, int32_t b) override {
if (invert) return !w.Int32Compare(a, b) ? eq_constant : ne_constant;
return w.Int32Compare(a, b) ? eq_constant : ne_constant;
}
};
TEST(BranchCombineInt32CmpAllInputShapes_materialized) {
for (size_t i = 0; i < arraysize(int32cmp_opcodes); i++) {
CmpMaterializeBoolGen gen(int32cmp_opcodes[i], false);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineInt32CmpAllInputShapes_inverted_materialized) {
for (size_t i = 0; i < arraysize(int32cmp_opcodes); i++) {
CmpMaterializeBoolGen gen(int32cmp_opcodes[i], true);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineInt32CmpAllInputShapes_branch_true) {
for (int i = 0; i < static_cast<int>(arraysize(int32cmp_opcodes)); i++) {
CmpBranchGen gen(int32cmp_opcodes[i], false, false, 995 + i, -1011 - i);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineInt32CmpAllInputShapes_branch_false) {
for (int i = 0; i < static_cast<int>(arraysize(int32cmp_opcodes)); i++) {
CmpBranchGen gen(int32cmp_opcodes[i], false, true, 795 + i, -2011 - i);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineInt32CmpAllInputShapes_inverse_branch_true) {
for (int i = 0; i < static_cast<int>(arraysize(int32cmp_opcodes)); i++) {
CmpBranchGen gen(int32cmp_opcodes[i], true, false, 695 + i, -3011 - i);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineInt32CmpAllInputShapes_inverse_branch_false) {
for (int i = 0; i < static_cast<int>(arraysize(int32cmp_opcodes)); i++) {
CmpBranchGen gen(int32cmp_opcodes[i], true, true, 595 + i, -4011 - i);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineFloat64Compares) {
double inf = V8_INFINITY;
double nan = std::numeric_limits<double>::quiet_NaN();
double inputs[] = {0.0, 1.0, -1.0, -inf, inf, nan};
int32_t eq_constant = -1733;
int32_t ne_constant = 915118;
double input_a = 0.0;
double input_b = 0.0;
CompareWrapper cmps[] = {CompareWrapper(IrOpcode::kFloat64Equal),
CompareWrapper(IrOpcode::kFloat64LessThan),
CompareWrapper(IrOpcode::kFloat64LessThanOrEqual)};
for (size_t c = 0; c < arraysize(cmps); c++) {
CompareWrapper cmp = cmps[c];
for (int invert = 0; invert < 2; invert++) {
RawMachineAssemblerTester<int32_t> m;
Node* a = m.LoadFromPointer(&input_a, MachineType::Float64());
Node* b = m.LoadFromPointer(&input_b, MachineType::Float64());
RawMachineLabel blocka, blockb;
Node* cond = cmp.MakeNode(&m, a, b);
if (invert) cond = m.Word32Equal(cond, m.Int32Constant(0));
m.Branch(cond, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(eq_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(ne_constant));
for (size_t i = 0; i < arraysize(inputs); ++i) {
for (size_t j = 0; j < arraysize(inputs); ++j) {
input_a = inputs[i];
input_b = inputs[j];
int32_t expected =
invert ? (cmp.Float64Compare(input_a, input_b) ? ne_constant
: eq_constant)
: (cmp.Float64Compare(input_a, input_b) ? eq_constant
: ne_constant);
CHECK_EQ(expected, m.Call());
}
}
}
}
}
TEST(BranchCombineEffectLevel) {
// Test that the load doesn't get folded into the branch, as there's a store
// between them. See http://crbug.com/611976.
int32_t input = 0;
RawMachineAssemblerTester<int32_t> m;
Node* a = m.LoadFromPointer(&input, MachineType::Int32());
Node* compare = m.Word32And(a, m.Int32Constant(1));
Node* equal = m.Word32Equal(compare, m.Int32Constant(0));
m.StoreToPointer(&input, MachineRepresentation::kWord32, m.Int32Constant(1));
RawMachineLabel blocka, blockb;
m.Branch(equal, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(42));
m.Bind(&blockb);
m.Return(m.Int32Constant(0));
CHECK_EQ(42, m.Call());
}
TEST(BranchCombineInt32AddLessThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Int32Add(p0, p1);
Node* compare = m.Int32LessThan(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect =
(base::AddWithWraparound(a, b) < 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32AddGreaterThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Int32Add(p0, p1);
Node* compare = m.Int32GreaterThanOrEqual(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect =
(base::AddWithWraparound(a, b) >= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroGreaterThanAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Int32Add(p0, p1);
Node* compare = m.Int32GreaterThan(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect =
(0 > base::AddWithWraparound(a, b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroLessThanOrEqualAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Int32Add(p0, p1);
Node* compare = m.Int32LessThanOrEqual(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect =
(0 <= base::AddWithWraparound(a, b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32AddLessThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Int32Add(p0, p1);
Node* compare = m.Uint32LessThanOrEqual(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (a + b <= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32AddGreaterThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Int32Add(p0, p1);
Node* compare = m.Uint32GreaterThan(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (a + b > 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroGreaterThanOrEqualAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Int32Add(p0, p1);
Node* compare = m.Uint32GreaterThanOrEqual(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (0 >= a + b) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroLessThanAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Int32Add(p0, p1);
Node* compare = m.Uint32LessThan(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (0 < a + b) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineWord32AndLessThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Word32And(p0, p1);
Node* compare = m.Int32LessThan(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = ((a & b) < 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineWord32AndGreaterThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Word32And(p0, p1);
Node* compare = m.Int32GreaterThanOrEqual(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = ((a & b) >= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroGreaterThanAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Word32And(p0, p1);
Node* compare = m.Int32GreaterThan(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = (0 > (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroLessThanOrEqualAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Word32And(p0, p1);
Node* compare = m.Int32LessThanOrEqual(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = (0 <= (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32AndLessThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Word32And(p0, p1);
Node* compare = m.Uint32LessThanOrEqual(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = ((a & b) <= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32AndGreaterThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Word32And(p0, p1);
Node* compare = m.Uint32GreaterThan(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = ((a & b) > 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroGreaterThanOrEqualAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Word32And(p0, p1);
Node* compare = m.Uint32GreaterThanOrEqual(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (0 >= (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroLessThanAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* p0 = m.Parameter(0);
Node* p1 = m.Parameter(1);
Node* add = m.Word32And(p0, p1);
Node* compare = m.Uint32LessThan(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (0 < (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,184 @@
// Copyright 2021 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 "include/v8-function.h"
#include "src/flags/flags.h"
#include "test/cctest/test-api.h"
#include "test/common/node-observer-tester.h"
namespace v8 {
namespace internal {
namespace compiler {
void CompileRunWithNodeObserver(const std::string& js_code,
int32_t expected_result,
IrOpcode::Value initial_call_opcode,
IrOpcode::Value updated_call_opcode1,
IrOpcode::Value updated_call_opcode2) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope handle_scope(isolate);
v8_flags.allow_natives_syntax = true;
v8_flags.turbo_optimize_apply = true;
// Note: Make sure to not capture stack locations (e.g. `this`) here since
// these lambdas are executed on another thread.
ModificationObserver apply_call_observer(
[initial_call_opcode](const Node* node) {
CHECK_EQ(initial_call_opcode, node->opcode());
},
[updated_call_opcode1, updated_call_opcode2](
const Node* node,
const ObservableNodeState& old_state) -> NodeObserver::Observation {
if (updated_call_opcode1 == node->opcode()) {
return NodeObserver::Observation::kContinue;
} else {
CHECK(updated_call_opcode2 == node->opcode());
return NodeObserver::Observation::kStop;
}
});
{
ObserveNodeScope scope(reinterpret_cast<i::Isolate*>(isolate),
&apply_call_observer);
v8::Local<v8::Value> result_value = CompileRun(js_code.c_str());
CHECK(result_value->IsNumber());
int32_t result =
ConvertJSValue<int32_t>::Get(result_value, env.local()).ToChecked();
CHECK_EQ(result, expected_result);
}
}
TEST(ReduceJSCallWithArrayLike) {
CompileRunWithNodeObserver(
"function sum_js3(a, b, c) { return a + b + c; }"
"function foo(x, y, z) {"
" return %ObserveNode(sum_js3.apply(null, [x, y, z]));"
"}"
"%PrepareFunctionForOptimization(sum_js3);"
"%PrepareFunctionForOptimization(foo);"
"foo(41, 42, 43);"
"%OptimizeFunctionOnNextCall(foo);"
"foo(41, 42, 43);",
126, IrOpcode::kJSCall,
IrOpcode::kJSCall, // not JSCallWithArrayLike
IrOpcode::kPhi); // JSCall => Phi when the call is inlined.
}
TEST(ReduceJSCallWithSpread) {
CompileRunWithNodeObserver(
"function sum_js3(a, b, c) { return a + b + c; }"
"function foo(x, y, z) {"
" const numbers = [x, y, z];"
" return %ObserveNode(sum_js3(...numbers));"
"}"
"%PrepareFunctionForOptimization(sum_js3);"
"%PrepareFunctionForOptimization(foo);"
"foo(41, 42, 43);"
"%OptimizeFunctionOnNextCall(foo);"
"foo(41, 42, 43)",
126, IrOpcode::kJSCallWithSpread,
IrOpcode::kJSCall, // not JSCallWithSpread
IrOpcode::kPhi);
}
TEST(ReduceJSCreateClosure) {
CompileRunWithNodeObserver(
"function foo_closure() {"
" return function(a, b, c) {"
" return a + b + c;"
" }"
"}"
"const _foo_closure = foo_closure();"
"%PrepareFunctionForOptimization(_foo_closure);"
"function foo(x, y, z) {"
" return %ObserveNode(foo_closure().apply(null, [x, y, z]));"
"}"
"%PrepareFunctionForOptimization(foo_closure);"
"%PrepareFunctionForOptimization(foo);"
"foo(41, 42, 43);"
"%OptimizeFunctionOnNextCall(foo_closure);"
"%OptimizeFunctionOnNextCall(foo);"
"foo(41, 42, 43)",
126, IrOpcode::kJSCall,
IrOpcode::kJSCall, // not JSCallWithArrayLike
IrOpcode::kPhi);
}
TEST(ReduceJSCreateBoundFunction) {
CompileRunWithNodeObserver(
"function sum_js3(a, b, c) {"
" return this.x + a + b + c;"
"}"
"function foo(x, y ,z) {"
" return %ObserveNode(sum_js3.bind({x : 42}).apply(null, [ x, y, z ]));"
"}"
"%PrepareFunctionForOptimization(sum_js3);"
"%PrepareFunctionForOptimization(foo);"
"foo(41, 42, 43);"
"%OptimizeFunctionOnNextCall(foo);"
"foo(41, 42, 43)",
168, IrOpcode::kJSCall,
IrOpcode::kJSCall, // not JSCallWithArrayLike
IrOpcode::kPhi);
}
static void SumF(const v8::FunctionCallbackInfo<v8::Value>& info) {
CHECK(i::ValidateCallbackInfo(info));
ApiTestFuzzer::Fuzz();
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
int this_x = info.This()
->Get(context, v8_str("x"))
.ToLocalChecked()
->Int32Value(context)
.FromJust();
info.GetReturnValue().Set(v8_num(
info[0]->Int32Value(info.GetIsolate()->GetCurrentContext()).FromJust() +
info[1]->Int32Value(info.GetIsolate()->GetCurrentContext()).FromJust() +
this_x));
}
TEST(ReduceCAPICallWithArrayLike) {
LocalContext env;
v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate);
v8_flags.allow_natives_syntax = true;
v8_flags.turbo_optimize_apply = true;
Local<v8::FunctionTemplate> sum = v8::FunctionTemplate::New(isolate, SumF);
CHECK(env->Global()
->Set(env.local(), v8_str("sum"),
sum->GetFunction(env.local()).ToLocalChecked())
.FromJust());
Local<v8::FunctionTemplate> fun = v8::FunctionTemplate::New(isolate);
v8::Local<v8::String> class_name = v8_str("the_class_name");
fun->SetClassName(class_name);
Local<ObjectTemplate> templ1 = ObjectTemplate::New(isolate, fun);
templ1->Set(isolate, "x", v8_num(42));
templ1->Set(isolate, "foo", sum);
Local<v8::Object> instance1 =
templ1->NewInstance(env.local()).ToLocalChecked();
CHECK(env->Global()->Set(env.local(), v8_str("p"), instance1).FromJust());
std::string js_code =
"function bar(a, b) { return sum.apply(p, [a, b]); }"
"%PrepareFunctionForOptimization(bar);"
"bar(20, 22);"
"%OptimizeFunctionOnNextCall(bar);"
"bar(20, 22);";
v8::Local<v8::Value> result_value = CompileRun(js_code.c_str());
CHECK(result_value->IsNumber());
int32_t result =
ConvertJSValue<int32_t>::Get(result_value, env.local()).ToChecked();
CHECK_EQ(result, 84);
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,505 @@
// Copyright 2015 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/builtins/builtins-inl.h"
#include "src/compiler/code-assembler.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/opcodes.h"
#include "src/execution/isolate.h"
#include "src/objects/heap-number-inl.h"
#include "src/objects/js-function.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/compiler/function-tester.h"
#include "test/common/code-assembler-tester.h"
namespace v8 {
namespace internal {
namespace compiler {
namespace {
template <class T>
using TVariable = TypedCodeAssemblerVariable<T>;
TNode<Smi> SmiTag(CodeAssembler* m, TNode<IntPtrT> value) {
int32_t constant_value;
if (m->TryToInt32Constant(value, &constant_value) &&
Smi::IsValid(constant_value)) {
return m->SmiConstant(Smi::FromInt(constant_value));
}
return m->BitcastWordToTaggedSigned(
m->WordShl(value, m->IntPtrConstant(kSmiShiftSize + kSmiTagSize)));
}
Node* UndefinedConstant(CodeAssembler* m) {
return m->LoadRoot(RootIndex::kUndefinedValue);
}
Node* LoadObjectField(CodeAssembler* m, Node* object, int offset,
MachineType type = MachineType::AnyTagged()) {
return m->Load(type, object, m->IntPtrConstant(offset - kHeapObjectTag));
}
Node* LoadMap(CodeAssembler* m, Node* object) {
return LoadObjectField(m, object, JSObject::kMapOffset);
}
} // namespace
TEST(SimpleSmiReturn) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
m.Return(SmiTag(&m, m.IntPtrConstant(37)));
FunctionTester ft(asm_tester.GenerateCode());
CHECK_EQ(37, (*ft.CallChecked<Smi>()).value());
}
TEST(SimpleIntPtrReturn) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
int test;
m.Return(m.BitcastWordToTagged(
m.IntPtrConstant(reinterpret_cast<intptr_t>(&test))));
FunctionTester ft(asm_tester.GenerateCode());
MaybeDirectHandle<Object> result = ft.Call();
CHECK_EQ(reinterpret_cast<Address>(&test), (*result.ToHandleChecked()).ptr());
}
TEST(SimpleDoubleReturn) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
m.Return(m.NumberConstant(0.5));
FunctionTester ft(asm_tester.GenerateCode());
CHECK_EQ(0.5, ft.CallChecked<HeapNumber>()->value());
}
TEST(SimpleCallRuntime1Arg) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
TNode<Context> context =
m.HeapConstantNoHole(Handle<Context>(isolate->native_context()));
TNode<Smi> b = SmiTag(&m, m.IntPtrConstant(0));
m.Return(m.CallRuntime(Runtime::kIsSmi, context, b));
FunctionTester ft(asm_tester.GenerateCode());
CHECK(ft.CallChecked<Boolean>().is_identical_to(
isolate->factory()->true_value()));
}
TEST(SimpleTailCallRuntime1Arg) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
TNode<Context> context =
m.HeapConstantNoHole(Handle<Context>(isolate->native_context()));
TNode<Smi> b = SmiTag(&m, m.IntPtrConstant(0));
m.TailCallRuntime(Runtime::kIsSmi, context, b);
FunctionTester ft(asm_tester.GenerateCode());
CHECK(ft.CallChecked<Boolean>().is_identical_to(
isolate->factory()->true_value()));
}
TEST(SimpleCallRuntime2Arg) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
TNode<Context> context =
m.HeapConstantNoHole(Handle<Context>(isolate->native_context()));
TNode<Smi> a = SmiTag(&m, m.IntPtrConstant(2));
TNode<Smi> b = SmiTag(&m, m.IntPtrConstant(4));
m.Return(m.CallRuntime(Runtime::kAdd, context, a, b));
FunctionTester ft(asm_tester.GenerateCode());
CHECK_EQ(6, (*ft.CallChecked<Smi>()).value());
}
TEST(SimpleTailCallRuntime2Arg) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
TNode<Context> context =
m.HeapConstantNoHole(Handle<Context>(isolate->native_context()));
TNode<Smi> a = SmiTag(&m, m.IntPtrConstant(2));
TNode<Smi> b = SmiTag(&m, m.IntPtrConstant(4));
m.TailCallRuntime(Runtime::kAdd, context, a, b);
FunctionTester ft(asm_tester.GenerateCode());
CHECK_EQ(6, (*ft.CallChecked<Smi>()).value());
}
namespace {
Handle<JSFunction> CreateSumAllArgumentsFunction(FunctionTester* ft) {
const char* source =
"(function() {\n"
" var sum = 0 + this;\n"
" for (var i = 0; i < arguments.length; i++) {\n"
" sum += arguments[i];\n"
" }\n"
" return sum;\n"
"})";
return ft->NewFunction(source);
}
} // namespace
TEST(SimpleCallJSFunction0Arg) {
Isolate* isolate(CcTest::InitIsolateOnce());
const int kNumParams = 1;
CodeAssemblerTester asm_tester(isolate, JSParameterCount(kNumParams));
CodeAssembler m(asm_tester.state());
{
auto function = m.Parameter<JSFunction>(1);
auto context = m.GetJSContextParameter();
auto receiver = SmiTag(&m, m.IntPtrConstant(42));
TNode<Object> result =
m.CallJS(Builtins::Call(), context, function, receiver);
m.Return(result);
}
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
Handle<JSFunction> sum = CreateSumAllArgumentsFunction(&ft);
MaybeDirectHandle<Object> result = ft.Call(sum);
CHECK_EQ(Smi::FromInt(42), *result.ToHandleChecked());
}
TEST(SimpleCallJSFunction1Arg) {
Isolate* isolate(CcTest::InitIsolateOnce());
const int kNumParams = 1;
CodeAssemblerTester asm_tester(isolate, JSParameterCount(kNumParams));
CodeAssembler m(asm_tester.state());
{
auto function = m.Parameter<JSFunction>(1);
auto context = m.GetJSContextParameter();
auto receiver = SmiTag(&m, m.IntPtrConstant(42));
auto a = SmiTag(&m, m.IntPtrConstant(13));
TNode<Object> result =
m.CallJS(Builtins::Call(), context, function, receiver, a);
m.Return(result);
}
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
Handle<JSFunction> sum = CreateSumAllArgumentsFunction(&ft);
MaybeDirectHandle<Object> result = ft.Call(sum);
CHECK_EQ(Smi::FromInt(55), *result.ToHandleChecked());
}
TEST(SimpleCallJSFunction2Arg) {
Isolate* isolate(CcTest::InitIsolateOnce());
const int kNumParams = 2;
CodeAssemblerTester asm_tester(isolate, JSParameterCount(kNumParams));
CodeAssembler m(asm_tester.state());
{
auto function = m.Parameter<JSFunction>(1);
auto context = m.GetJSContextParameter();
auto receiver = SmiTag(&m, m.IntPtrConstant(42));
auto a = SmiTag(&m, m.IntPtrConstant(13));
auto b = SmiTag(&m, m.IntPtrConstant(153));
TNode<Object> result =
m.CallJS(Builtins::Call(), context, function, receiver, a, b);
m.Return(result);
}
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
Handle<JSFunction> sum = CreateSumAllArgumentsFunction(&ft);
MaybeDirectHandle<Object> result = ft.Call(sum);
CHECK_EQ(Smi::FromInt(208), *result.ToHandleChecked());
}
TEST(VariableMerge1) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
TVariable<Int32T> var1(&m);
CodeAssemblerLabel l1(&m), l2(&m), merge(&m);
TNode<Int32T> temp = m.Int32Constant(0);
var1 = temp;
m.Branch(m.Int32Constant(1), &l1, &l2);
m.Bind(&l1);
CHECK_EQ(var1.value(), temp);
m.Goto(&merge);
m.Bind(&l2);
CHECK_EQ(var1.value(), temp);
m.Goto(&merge);
m.Bind(&merge);
CHECK_EQ(var1.value(), temp);
}
TEST(VariableMerge2) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
TVariable<Int32T> var1(&m);
CodeAssemblerLabel l1(&m), l2(&m), merge(&m);
TNode<Int32T> temp = m.Int32Constant(0);
var1 = temp;
m.Branch(m.Int32Constant(1), &l1, &l2);
m.Bind(&l1);
CHECK_EQ(var1.value(), temp);
m.Goto(&merge);
m.Bind(&l2);
TNode<Int32T> temp2 = m.Int32Constant(2);
var1 = temp2;
CHECK_EQ(var1.value(), temp2);
m.Goto(&merge);
m.Bind(&merge);
CHECK_NE(var1.value(), temp);
}
TEST(VariableMerge3) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
TVariable<Int32T> var1(&m);
TVariable<Int32T> var2(&m);
CodeAssemblerLabel l1(&m), l2(&m), merge(&m);
TNode<Int32T> temp = m.Int32Constant(0);
var1 = temp;
var2 = temp;
m.Branch(m.Int32Constant(1), &l1, &l2);
m.Bind(&l1);
CHECK_EQ(var1.value(), temp);
m.Goto(&merge);
m.Bind(&l2);
TNode<Int32T> temp2 = m.Int32Constant(2);
var1 = temp2;
CHECK_EQ(var1.value(), temp2);
m.Goto(&merge);
m.Bind(&merge);
CHECK_NE(var1.value(), temp);
CHECK_NE(var1.value(), temp2);
CHECK_EQ(var2.value(), temp);
}
TEST(VariableMergeBindFirst) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
TVariable<Int32T> var1(&m);
CodeAssemblerLabel l1(&m), l2(&m), merge(&m, &var1), end(&m);
TNode<Int32T> temp = m.Int32Constant(0);
var1 = temp;
m.Branch(m.Int32Constant(1), &l1, &l2);
m.Bind(&l1);
CHECK_EQ(var1.value(), temp);
m.Goto(&merge);
m.Bind(&merge);
CHECK(var1.value() != temp);
CHECK_NOT_NULL(var1.value());
m.Goto(&end);
m.Bind(&l2);
TNode<Int32T> temp2 = m.Int32Constant(2);
var1 = temp2;
CHECK_EQ(var1.value(), temp2);
m.Goto(&merge);
m.Bind(&end);
CHECK(var1.value() != temp);
CHECK_NOT_NULL(var1.value());
}
TEST(VariableMergeSwitch) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
TVariable<Smi> var1(&m);
CodeAssemblerLabel l1(&m), l2(&m), default_label(&m);
CodeAssemblerLabel* labels[] = {&l1, &l2};
int32_t values[] = {1, 2};
TNode<Smi> temp1 = m.SmiConstant(0);
var1 = temp1;
m.Switch(m.Int32Constant(2), &default_label, values, labels, 2);
m.Bind(&l1);
CHECK_EQ(temp1, var1.value());
m.Return(temp1);
m.Bind(&l2);
CHECK_EQ(temp1, var1.value());
TNode<Smi> temp2 = m.SmiConstant(7);
var1 = temp2;
m.Goto(&default_label);
m.Bind(&default_label);
CHECK_EQ(IrOpcode::kPhi, (*var1.value()).opcode());
CHECK_EQ(2, (*var1.value()).op()->ValueInputCount());
CHECK_EQ(temp1, NodeProperties::GetValueInput(var1.value(), 0));
CHECK_EQ(temp2, NodeProperties::GetValueInput(var1.value(), 1));
m.Return(temp1);
}
TEST(SplitEdgeBranchMerge) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
CodeAssemblerLabel l1(&m), merge(&m);
m.Branch(m.Int32Constant(1), &l1, &merge);
m.Bind(&l1);
m.Goto(&merge);
m.Bind(&merge);
USE(asm_tester.GenerateCode());
}
TEST(SplitEdgeSwitchMerge) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
CodeAssemblerLabel l1(&m), l2(&m), l3(&m), default_label(&m);
CodeAssemblerLabel* labels[] = {&l1, &l2};
int32_t values[] = {1, 2};
m.Branch(m.Int32Constant(1), &l3, &l1);
m.Bind(&l3);
m.Switch(m.Int32Constant(2), &default_label, values, labels, 2);
m.Bind(&l1);
m.Goto(&l2);
m.Bind(&l2);
m.Goto(&default_label);
m.Bind(&default_label);
USE(asm_tester.GenerateCode());
}
TEST(TestToConstant) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
int32_t value32;
int64_t value64;
TNode<Int32T> a = m.Int32Constant(5);
CHECK(m.TryToInt32Constant(a, &value32));
CHECK(m.TryToInt64Constant(a, &value64));
TNode<Int64T> b = m.Int64Constant(static_cast<int64_t>(1) << 32);
CHECK(!m.TryToInt32Constant(b, &value32));
CHECK(m.TryToInt64Constant(b, &value64));
b = m.Int64Constant(13);
CHECK(m.TryToInt32Constant(b, &value32));
CHECK(m.TryToInt64Constant(b, &value64));
TNode<Int32T> c = m.Word32Shl(m.Int32Constant(13), m.Int32Constant(14));
CHECK(!m.TryToInt32Constant(c, &value32));
CHECK(!m.TryToInt64Constant(c, &value64));
TNode<IntPtrT> d = m.ReinterpretCast<IntPtrT>(UndefinedConstant(&m));
CHECK(!m.TryToInt32Constant(d, &value32));
CHECK(!m.TryToInt64Constant(d, &value64));
}
TEST(DeferredCodePhiHints) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
CodeAssemblerLabel block1(&m, CodeAssemblerLabel::kDeferred);
m.Goto(&block1);
m.Bind(&block1);
{
TVariable<Map> var_object(&m);
CodeAssemblerLabel loop(&m, &var_object);
var_object = m.CAST(LoadMap(&m, m.SmiConstant(0)));
m.Goto(&loop);
m.Bind(&loop);
{
TNode<Map> map = m.CAST(LoadMap(&m, var_object.value()));
var_object = map;
m.Goto(&loop);
}
}
CHECK(!asm_tester.GenerateCode().is_null());
}
TEST(TestOutOfScopeVariable) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
CodeAssemblerLabel block1(&m);
CodeAssemblerLabel block2(&m);
CodeAssemblerLabel block3(&m);
CodeAssemblerLabel block4(&m);
m.Branch(m.WordEqual(m.BitcastTaggedToWordForTagAndSmiBits(
m.UncheckedParameter<AnyTaggedT>(0)),
m.IntPtrConstant(0)),
&block1, &block4);
m.Bind(&block4);
{
TVariable<IntPtrT> var_object(&m);
m.Branch(m.WordEqual(m.BitcastTaggedToWordForTagAndSmiBits(
m.UncheckedParameter<AnyTaggedT>(0)),
m.IntPtrConstant(0)),
&block2, &block3);
m.Bind(&block2);
var_object = m.IntPtrConstant(55);
m.Goto(&block1);
m.Bind(&block3);
var_object = m.IntPtrConstant(66);
m.Goto(&block1);
}
m.Bind(&block1);
CHECK(!asm_tester.GenerateCode().is_null());
}
TEST(ExceptionHandler) {
Isolate* isolate(CcTest::InitIsolateOnce());
const int kNumParams = 0;
CodeAssemblerTester asm_tester(isolate, JSParameterCount(kNumParams));
CodeAssembler m(asm_tester.state());
TVariable<Object> var(m.SmiConstant(0), &m);
CodeAssemblerLabel exception(&m, {&var}, CodeAssemblerLabel::kDeferred);
{
ScopedExceptionHandler handler(&m, &exception, &var);
TNode<Context> context =
m.HeapConstantNoHole(Handle<Context>(isolate->native_context()));
m.CallRuntime(Runtime::kThrow, context, m.SmiConstant(2));
}
m.Return(m.SmiConstant(1));
m.Bind(&exception);
m.Return(var.value());
FunctionTester ft(asm_tester.GenerateCode(), kNumParams);
CHECK_EQ(2, (*ft.CallChecked<Smi>()).value());
}
TEST(TestCodeAssemblerCodeComment) {
#ifdef V8_CODE_COMMENTS
i::v8_flags.code_comments = true;
Isolate* isolate(CcTest::InitIsolateOnce());
const int kNumParams = 0;
CodeAssemblerTester asm_tester(isolate, JSParameterCount(kNumParams));
CodeAssembler m(asm_tester.state());
m.Comment("Comment1");
m.Return(m.SmiConstant(1));
DirectHandle<Code> code = asm_tester.GenerateCode();
CHECK_NE(code->code_comments(), kNullAddress);
CodeCommentsIterator it(code->code_comments(), code->code_comments_size());
CHECK(it.HasCurrent());
bool found_comment = false;
while (it.HasCurrent()) {
if (strncmp(it.GetComment(), "Comment1", strlen("Comment1")) == 0) {
found_comment = true;
}
it.Next();
}
CHECK(found_comment);
#endif // V8_CODE_COMMENTS
}
TEST(StaticAssert) {
Isolate* isolate(CcTest::InitIsolateOnce());
CodeAssemblerTester asm_tester(isolate);
CodeAssembler m(asm_tester.state());
m.StaticAssert(m.ReinterpretCast<BoolT>(m.Int32Constant(1)));
USE(asm_tester.GenerateCode());
}
} // namespace compiler
} // namespace internal
} // namespace v8

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,206 @@
// Copyright 2020 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 "include/v8-function.h"
#include "src/api/api-inl.h"
#include "src/codegen/compiler.h"
#include "src/codegen/optimized-compilation-info.h"
#include "src/compiler/pipeline.h"
#include "src/debug/debug.h"
#include "src/handles/handles.h"
#include "src/logging/counters.h"
#include "src/objects/js-function.h"
#include "src/objects/shared-function-info.h"
#include "src/utils/utils-inl.h"
#include "src/zone/zone.h"
#include "test/cctest/cctest.h"
#include "test/common/flag-utils.h"
namespace v8 {
namespace internal {
namespace compiler {
enum class SfiState {
Compiled,
DebugInfo,
PreparedForDebugExecution,
};
void ExpectSharedFunctionInfoState(Isolate* isolate,
Tagged<SharedFunctionInfo> sfi,
SfiState expectedState) {
Tagged<Object> function_data = sfi->GetTrustedData(isolate);
Tagged<HeapObject> script = sfi->script(kAcquireLoad);
switch (expectedState) {
case SfiState::Compiled:
CHECK(IsBytecodeArray(function_data) ||
(IsCode(function_data) &&
Cast<Code>(function_data)->kind() == CodeKind::BASELINE));
CHECK(IsScript(script));
break;
case SfiState::DebugInfo: {
CHECK(IsBytecodeArray(function_data) ||
(IsCode(function_data) &&
Cast<Code>(function_data)->kind() == CodeKind::BASELINE));
CHECK(IsScript(script));
Tagged<DebugInfo> debug_info = sfi->GetDebugInfo(isolate);
CHECK(!debug_info->HasInstrumentedBytecodeArray());
break;
}
case SfiState::PreparedForDebugExecution: {
CHECK(IsBytecodeArray(function_data));
CHECK(IsScript(script));
Tagged<DebugInfo> debug_info = sfi->GetDebugInfo(isolate);
CHECK(debug_info->HasInstrumentedBytecodeArray());
break;
}
}
}
class BackgroundCompilationThread final : public v8::base::Thread {
public:
BackgroundCompilationThread(Isolate* isolate,
base::Semaphore* sema_execute_start,
base::Semaphore* sema_execute_complete,
OptimizedCompilationJob* job)
: base::Thread(base::Thread::Options("BackgroundCompilationThread")),
isolate_(isolate),
sema_execute_start_(sema_execute_start),
sema_execute_complete_(sema_execute_complete),
job_(job) {}
void Run() override {
RuntimeCallStats stats(RuntimeCallStats::kWorkerThread);
LocalIsolate local_isolate(isolate_, ThreadKind::kBackground);
sema_execute_start_->Wait();
const CompilationJob::Status status =
job_->ExecuteJob(&stats, &local_isolate);
CHECK_EQ(status, CompilationJob::SUCCEEDED);
sema_execute_complete_->Signal();
}
private:
Isolate* isolate_;
base::Semaphore* sema_execute_start_;
base::Semaphore* sema_execute_complete_;
OptimizedCompilationJob* job_;
};
TEST(TestConcurrentSharedFunctionInfo) {
FlagScope<bool> allow_natives_syntax(&i::v8_flags.allow_natives_syntax, true);
HandleAndZoneScope scope;
Isolate* isolate = scope.main_isolate();
Zone zone(isolate->allocator(), ZONE_NAME);
HandleScope handle_scope(isolate);
const char* source_code =
"function f(x, y) { return x + y; }\n"
"function test(x) { return f(f(1, x), f(x, 1)); }\n"
"%PrepareFunctionForOptimization(f);\n"
"%PrepareFunctionForOptimization(test);\n"
"test(3);\n"
"test(-9);\n";
CompileRun(source_code);
// Get function "test"
Local<Function> function_test = Local<Function>::Cast(
CcTest::global()
->Get(CcTest::isolate()->GetCurrentContext(), v8_str("test"))
.ToLocalChecked());
Handle<JSFunction> test =
Cast<JSFunction>(v8::Utils::OpenHandle(*function_test));
Handle<SharedFunctionInfo> test_sfi(test->shared(), isolate);
DCHECK(test_sfi->HasBytecodeArray());
IsCompiledScope compiled_scope_test(*test_sfi, isolate);
JSFunction::EnsureFeedbackVector(isolate, test, &compiled_scope_test);
// Get function "f"
Local<Function> function_f = Local<Function>::Cast(
CcTest::global()
->Get(CcTest::isolate()->GetCurrentContext(), v8_str("f"))
.ToLocalChecked());
Handle<JSFunction> f = Cast<JSFunction>(v8::Utils::OpenHandle(*function_f));
Handle<SharedFunctionInfo> f_sfi(f->shared(), isolate);
DCHECK(f_sfi->HasBytecodeArray());
OptimizedCompilationInfo f_info(&zone, isolate, f_sfi, f,
CodeKind::TURBOFAN_JS);
DirectHandle<Code> f_code =
Pipeline::GenerateCodeForTesting(&f_info, isolate).ToHandleChecked();
f->UpdateOptimizedCode(isolate, *f_code);
IsCompiledScope compiled_scope_f(*f_sfi, isolate);
JSFunction::EnsureFeedbackVector(isolate, f, &compiled_scope_f);
ExpectSharedFunctionInfoState(isolate, *test_sfi, SfiState::Compiled);
auto job =
Pipeline::NewCompilationJob(isolate, test, CodeKind::TURBOFAN_JS, true);
// Prepare job.
{
CompilationHandleScope compilation(isolate, job->compilation_info());
job->compilation_info()->ReopenAndCanonicalizeHandlesInNewScope(isolate);
const CompilationJob::Status status = job->PrepareJob(isolate);
CHECK_EQ(status, CompilationJob::SUCCEEDED);
}
// Start a background thread to execute the compilation job.
base::Semaphore sema_execute_start(0);
base::Semaphore sema_execute_complete(0);
BackgroundCompilationThread thread(isolate, &sema_execute_start,
&sema_execute_complete, job.get());
CHECK(thread.Start());
sema_execute_start.Signal();
// Background thread is running, now mess with test's SFI.
ExpectSharedFunctionInfoState(isolate, *test_sfi, SfiState::Compiled);
// Compiled ==> DebugInfo
{
isolate->debug()->GetOrCreateDebugInfo(test_sfi);
ExpectSharedFunctionInfoState(isolate, *test_sfi, SfiState::DebugInfo);
}
for (int i = 0; i < 100; ++i) {
// DebugInfo ==> PreparedForDebugExecution
{
int breakpoint_id;
CHECK(isolate->debug()->SetBreakpointForFunction(
test_sfi, isolate->factory()->empty_string(), &breakpoint_id));
ExpectSharedFunctionInfoState(isolate, *test_sfi,
SfiState::PreparedForDebugExecution);
}
// PreparedForDebugExecution ==> DebugInfo
{
Tagged<DebugInfo> debug_info = test_sfi->GetDebugInfo(isolate);
debug_info->ClearBreakInfo(isolate);
ExpectSharedFunctionInfoState(isolate, *test_sfi, SfiState::DebugInfo);
}
}
sema_execute_complete.Wait();
thread.Join();
// Finalize job.
{
// Cannot assert successful completion here since concurrent modifications
// may have invalidated compilation dependencies (e.g. since the serialized
// JSFunctionRef no longer matches the actual JSFunction state).
const CompilationJob::Status status = job->FinalizeJob(isolate);
if (status == CompilationJob::SUCCEEDED) {
CHECK(job->compilation_info()->has_bytecode_array());
} else {
CHECK_EQ(status, CompilationJob::FAILED);
}
}
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,672 @@
// Copyright 2014 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/backend/gap-resolver.h"
#include "src/base/utils/random-number-generator.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace compiler {
const auto GetRegConfig = RegisterConfiguration::Default;
// Fragments the given FP operand into an equivalent set of FP operands to
// simplify ParallelMove equivalence testing.
void GetCanonicalOperands(const InstructionOperand& op,
std::vector<InstructionOperand>* fragments) {
CHECK_EQ(kFPAliasing, AliasingKind::kCombine);
CHECK(op.IsFPLocationOperand());
const LocationOperand& loc = LocationOperand::cast(op);
MachineRepresentation rep = loc.representation();
int base = -1;
int aliases = GetRegConfig()->GetAliases(
rep, 0, MachineRepresentation::kFloat32, &base);
CHECK_LT(0, aliases);
CHECK_GE(4, aliases);
int index = -1;
int step = 1;
if (op.IsFPRegister()) {
index = loc.register_code() * aliases;
} else {
index = loc.index();
step = -1;
}
for (int i = 0; i < aliases; i++) {
fragments->push_back(AllocatedOperand(loc.location_kind(),
MachineRepresentation::kFloat32,
index + i * step));
}
}
// Fake frame size. The test's stack operand indices should be below this value.
// Stack slots above this value correspond to temporaries pushed by the gap
// resolver to resolve move cycles, and are ignored when comparing interpreter
// states.
constexpr int kLastFrameSlotId = 1000;
// The state of our move interpreter is the mapping of operands to values. Note
// that the actual values don't really matter, all we care about is equality.
class InterpreterState {
public:
void ExecuteInParallel(const ParallelMove* moves) {
InterpreterState copy(*this);
for (const auto m : *moves) {
CHECK(!m->IsRedundant());
const InstructionOperand& src = m->source();
const InstructionOperand& dst = m->destination();
if (kFPAliasing == AliasingKind::kCombine && src.IsFPLocationOperand() &&
dst.IsFPLocationOperand()) {
// Canonicalize FP location-location moves by fragmenting them into
// an equivalent sequence of float32 moves, to simplify state
// equivalence testing.
std::vector<InstructionOperand> src_fragments;
GetCanonicalOperands(src, &src_fragments);
CHECK(!src_fragments.empty());
std::vector<InstructionOperand> dst_fragments;
GetCanonicalOperands(dst, &dst_fragments);
CHECK_EQ(src_fragments.size(), dst_fragments.size());
for (size_t i = 0; i < src_fragments.size(); ++i) {
write(dst_fragments[i], copy.read(src_fragments[i]));
}
continue;
}
// All other moves.
write(dst, copy.read(src));
}
}
void ExecuteMove(Zone* zone, InstructionOperand* source,
InstructionOperand* dest) {
ParallelMove* moves = zone->New<ParallelMove>(zone);
moves->AddMove(*source, *dest);
ExecuteInParallel(moves);
}
void MoveToTempLocation(InstructionOperand& source) {
scratch_ = KeyFor(source);
}
void MoveFromTempLocation(InstructionOperand& dst) {
AllocatedOperand src(scratch_.kind, scratch_.rep, scratch_.index);
if (kFPAliasing == AliasingKind::kCombine && src.IsFPLocationOperand() &&
dst.IsFPLocationOperand()) {
// Canonicalize FP location-location moves by fragmenting them into
// an equivalent sequence of float32 moves, to simplify state
// equivalence testing.
std::vector<InstructionOperand> src_fragments;
GetCanonicalOperands(src, &src_fragments);
CHECK(!src_fragments.empty());
std::vector<InstructionOperand> dst_fragments;
GetCanonicalOperands(dst, &dst_fragments);
CHECK_EQ(src_fragments.size(), dst_fragments.size());
for (size_t i = 0; i < src_fragments.size(); ++i) {
write(dst_fragments[i], KeyFor(src_fragments[i]));
}
return;
}
write(dst, scratch_);
}
bool operator==(const InterpreterState& other) const {
return values_ == other.values_;
}
// Clear stack operands above kLastFrameSlotId. They correspond to temporaries
// pushed by the gap resolver to break cycles.
void ClearTemps() {
auto it = values_.begin();
while (it != values_.end()) {
if (it->first.kind == LocationOperand::STACK_SLOT &&
it->first.index >= kLastFrameSlotId) {
it = values_.erase(it);
} else {
it++;
}
}
}
private:
// struct for mapping operands to a unique value, that makes it easier to
// detect illegal parallel moves, and to evaluate moves for equivalence. This
// is a one way transformation. All general register and slot operands are
// mapped to the default representation. FP registers and slots are mapped to
// float64 except on architectures with non-simple FP register aliasing, where
// the actual representation is used.
struct Key {
bool is_constant;
MachineRepresentation rep;
LocationOperand::LocationKind kind;
int index;
bool operator<(const Key& other) const {
if (this->is_constant != other.is_constant) {
return this->is_constant;
}
if (this->rep != other.rep) {
return this->rep < other.rep;
}
if (this->kind != other.kind) {
return this->kind < other.kind;
}
return this->index < other.index;
}
bool operator==(const Key& other) const {
return this->is_constant == other.is_constant && this->rep == other.rep &&
this->kind == other.kind && this->index == other.index;
}
};
// Internally, the state is a normalized permutation of Value pairs.
using Value = Key;
using OperandMap = std::map<Key, Value>;
Value read(const InstructionOperand& op) const {
OperandMap::const_iterator it = values_.find(KeyFor(op));
return (it == values_.end()) ? ValueFor(op) : it->second;
}
void write(const InstructionOperand& dst, Value v) {
if (v == ValueFor(dst)) {
values_.erase(KeyFor(dst));
} else {
values_[KeyFor(dst)] = v;
}
}
static Key KeyFor(const InstructionOperand& op) {
bool is_constant = op.IsConstant();
MachineRepresentation rep =
v8::internal::compiler::InstructionSequence::DefaultRepresentation();
LocationOperand::LocationKind kind;
int index;
if (!is_constant) {
const LocationOperand& loc_op = LocationOperand::cast(op);
// Preserve FP representation when FP register aliasing is complex.
// Otherwise, canonicalize to kFloat64.
if (IsFloatingPoint(loc_op.representation())) {
if (kFPAliasing == AliasingKind::kIndependent) {
rep = IsSimd128(loc_op.representation())
? MachineRepresentation::kSimd128
: MachineRepresentation::kFloat64;
} else if (kFPAliasing == AliasingKind::kOverlap) {
rep = MachineRepresentation::kFloat64;
} else {
rep = loc_op.representation();
}
}
if (loc_op.IsAnyRegister()) {
index = loc_op.register_code();
} else {
index = loc_op.index();
}
kind = loc_op.location_kind();
} else {
index = ConstantOperand::cast(op).virtual_register();
kind = LocationOperand::REGISTER;
}
Key key = {is_constant, rep, kind, index};
return key;
}
static Value ValueFor(const InstructionOperand& op) { return KeyFor(op); }
static InstructionOperand FromKey(Key key) {
if (key.is_constant) {
return ConstantOperand(key.index);
}
return AllocatedOperand(key.kind, key.rep, key.index);
}
friend std::ostream& operator<<(std::ostream& os,
const InterpreterState& is) {
const char* space = "";
for (auto& value : is.values_) {
InstructionOperand source = FromKey(value.second);
InstructionOperand destination = FromKey(value.first);
os << space << MoveOperands{source, destination};
space = " ";
}
return os;
}
OperandMap values_;
Key scratch_ = {};
};
// An abstract interpreter for moves, swaps and parallel moves.
class MoveInterpreter : public GapResolver::Assembler {
public:
explicit MoveInterpreter(Zone* zone) : zone_(zone) {}
AllocatedOperand Push(InstructionOperand* source) override {
auto rep = LocationOperand::cast(source)->representation();
int new_slots = ElementSizeInPointers(rep);
AllocatedOperand stack_slot(LocationOperand::STACK_SLOT, rep,
kLastFrameSlotId + sp_delta_ + new_slots);
ParallelMove* moves = zone_->New<ParallelMove>(zone_);
moves->AddMove(*source, stack_slot);
state_.ExecuteMove(zone_, source, &stack_slot);
sp_delta_ += new_slots;
return stack_slot;
}
void Pop(InstructionOperand* dest, MachineRepresentation rep) override {
int new_slots = ElementSizeInPointers(rep);
int temp_slot = kLastFrameSlotId + sp_delta_ + new_slots;
AllocatedOperand stack_slot(LocationOperand::STACK_SLOT, rep, temp_slot);
state_.ExecuteMove(zone_, &stack_slot, dest);
sp_delta_ -= new_slots;
}
void PopTempStackSlots() override {
sp_delta_ = 0;
state_.ClearTemps();
}
void MoveToTempLocation(InstructionOperand* source,
MachineRepresentation rep) final {
state_.MoveToTempLocation(*source);
}
void MoveTempLocationTo(InstructionOperand* dest,
MachineRepresentation rep) final {
state_.MoveFromTempLocation(*dest);
}
void SetPendingMove(MoveOperands* move) final {}
void AssembleMove(InstructionOperand* source,
InstructionOperand* destination) override {
ParallelMove* moves = zone_->New<ParallelMove>(zone_);
moves->AddMove(*source, *destination);
state_.ExecuteInParallel(moves);
}
void AssembleSwap(InstructionOperand* source,
InstructionOperand* destination) override {
ParallelMove* moves = zone_->New<ParallelMove>(zone_);
moves->AddMove(*source, *destination);
moves->AddMove(*destination, *source);
state_.ExecuteInParallel(moves);
}
void AssembleParallelMove(const ParallelMove* moves) {
state_.ExecuteInParallel(moves);
}
InterpreterState state() const { return state_; }
private:
Zone* const zone_;
InterpreterState state_;
int sp_delta_ = 0;
};
class ParallelMoveCreator : public HandleAndZoneScope {
public:
ParallelMoveCreator() : rng_(CcTest::random_number_generator()) {}
// Creates a ParallelMove with 'size' random MoveOperands. Note that illegal
// moves will be rejected, so the actual number of MoveOperands may be less.
ParallelMove* Create(int size) {
ParallelMove* parallel_move = main_zone()->New<ParallelMove>(main_zone());
// Valid ParallelMoves can't have interfering destination ops.
std::set<InstructionOperand, CompareOperandModuloType> destinations;
// Valid ParallelMoves can't have interfering source ops of different reps.
std::map<InstructionOperand, MachineRepresentation,
CompareOperandModuloType>
sources;
for (int i = 0; i < size; ++i) {
MachineRepresentation rep = RandomRepresentation();
MoveOperands mo(CreateRandomOperand(true, rep),
CreateRandomOperand(false, rep));
if (mo.IsRedundant()) continue;
const InstructionOperand& dst = mo.destination();
bool reject = false;
// On architectures where FP register aliasing is non-simple, update the
// destinations set with the float equivalents of the operand and check
// that all destinations are unique and do not alias each other.
if (kFPAliasing == AliasingKind::kCombine &&
mo.destination().IsFPLocationOperand()) {
std::vector<InstructionOperand> dst_fragments;
GetCanonicalOperands(dst, &dst_fragments);
CHECK(!dst_fragments.empty());
for (size_t j = 0; j < dst_fragments.size(); ++j) {
if (destinations.find(dst_fragments[j]) == destinations.end()) {
destinations.insert(dst_fragments[j]);
} else {
reject = true;
break;
}
}
// Update the sources map, and check that no FP source has multiple
// representations.
const InstructionOperand& src = mo.source();
if (src.IsFPRegister()) {
std::vector<InstructionOperand> src_fragments;
MachineRepresentation src_rep =
LocationOperand::cast(src).representation();
GetCanonicalOperands(src, &src_fragments);
CHECK(!src_fragments.empty());
for (size_t j = 0; j < src_fragments.size(); ++j) {
auto find_it = sources.find(src_fragments[j]);
if (find_it != sources.end() && find_it->second != src_rep) {
reject = true;
break;
}
sources.insert(std::make_pair(src_fragments[j], src_rep));
}
}
} else {
if (destinations.find(dst) == destinations.end()) {
destinations.insert(dst);
} else {
reject = true;
}
}
if (!reject) {
parallel_move->AddMove(mo.source(), mo.destination());
}
}
return parallel_move;
}
// Creates a ParallelMove from a list of operand pairs. Even operands are
// destinations, odd ones are sources.
ParallelMove* Create(const std::vector<InstructionOperand>& operand_pairs) {
ParallelMove* parallel_move = main_zone()->New<ParallelMove>(main_zone());
for (size_t i = 0; i < operand_pairs.size(); i += 2) {
const InstructionOperand& dst = operand_pairs[i];
const InstructionOperand& src = operand_pairs[i + 1];
parallel_move->AddMove(src, dst);
}
return parallel_move;
}
private:
MachineRepresentation RandomRepresentation() {
int index = rng_->NextInt(6);
switch (index) {
case 0:
return MachineRepresentation::kWord32;
case 1:
return MachineRepresentation::kWord64;
case 2:
return MachineRepresentation::kFloat32;
case 3:
return MachineRepresentation::kFloat64;
case 4:
return MachineRepresentation::kSimd128;
case 5:
return MachineRepresentation::kTagged;
}
UNREACHABLE();
}
// min(num_alloctable_general_registers for each arch) == 5 from
// assembler-ia32.h
const int kMaxIndex = 5;
const int kMaxIndices = kMaxIndex + 1;
// Non-FP slots shouldn't overlap FP slots.
// FP slots with different representations shouldn't overlap.
int GetValidSlotIndex(MachineRepresentation rep, int index) {
DCHECK_GE(kMaxIndex, index);
// The first group of slots are for non-FP values.
if (!IsFloatingPoint(rep)) return index;
// The next group are for float values.
int base = kMaxIndices;
if (rep == MachineRepresentation::kFloat32) return base + index;
// Double values.
base += kMaxIndices;
if (rep == MachineRepresentation::kFloat64) return base + index * 2;
// SIMD values
base += kMaxIndices * 2;
CHECK_EQ(MachineRepresentation::kSimd128, rep);
return base + index * 4;
}
InstructionOperand CreateRandomOperand(bool is_source,
MachineRepresentation rep) {
auto conf = RegisterConfiguration::Default();
auto GetValidRegisterCode = [&conf](MachineRepresentation rep, int index) {
switch (rep) {
case MachineRepresentation::kFloat32:
return conf->RegisterConfiguration::GetAllocatableFloatCode(index);
case MachineRepresentation::kFloat64:
return conf->RegisterConfiguration::GetAllocatableDoubleCode(index);
case MachineRepresentation::kSimd128:
return conf->RegisterConfiguration::GetAllocatableSimd128Code(index);
default:
return conf->RegisterConfiguration::GetAllocatableGeneralCode(index);
}
UNREACHABLE();
};
int index = rng_->NextInt(kMaxIndex);
// destination can't be Constant.
switch (rng_->NextInt(is_source ? 3 : 2)) {
case 0:
return AllocatedOperand(LocationOperand::STACK_SLOT, rep,
GetValidSlotIndex(rep, index));
case 1:
return AllocatedOperand(LocationOperand::REGISTER, rep,
GetValidRegisterCode(rep, index));
case 2:
return ConstantOperand(index);
}
UNREACHABLE();
}
private:
v8::base::RandomNumberGenerator* rng_;
};
void RunTest(ParallelMove* pm, Zone* zone) {
// Note: The gap resolver modifies the ParallelMove, so interpret first.
MoveInterpreter mi1(zone);
mi1.AssembleParallelMove(pm);
MoveInterpreter mi2(zone);
GapResolver resolver(&mi2);
resolver.Resolve(pm);
auto mi2_state = mi2.state();
CHECK_EQ(mi1.state(), mi2_state);
}
TEST(Aliasing) {
// On platforms with simple aliasing, these parallel moves are ill-formed.
if (kFPAliasing != AliasingKind::kCombine) return;
ParallelMoveCreator pmc;
Zone* zone = pmc.main_zone();
auto s0 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat32, 0);
auto s1 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat32, 1);
auto s2 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat32, 2);
auto s3 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat32, 3);
auto s4 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat32, 4);
auto d0 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat64, 0);
auto d1 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat64, 1);
auto d16 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat64, 16);
// Double slots must be odd to match frame allocation.
auto dSlot = AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kFloat64, 3);
// Cycles involving s- and d-registers.
{
std::vector<InstructionOperand> moves = {
s2, s0, // s2 <- s0
d0, d1 // d0 <- d1
};
RunTest(pmc.Create(moves), zone);
}
{
std::vector<InstructionOperand> moves = {
d0, d1, // d0 <- d1
s2, s0 // s2 <- s0
};
RunTest(pmc.Create(moves), zone);
}
{
std::vector<InstructionOperand> moves = {
s2, s1, // s2 <- s1
d0, d1 // d0 <- d1
};
RunTest(pmc.Create(moves), zone);
}
{
std::vector<InstructionOperand> moves = {
d0, d1, // d0 <- d1
s2, s1 // s2 <- s1
};
RunTest(pmc.Create(moves), zone);
}
// Two cycles involving a single d-register.
{
std::vector<InstructionOperand> moves = {
d0, d1, // d0 <- d1
s2, s1, // s2 <- s1
s3, s0 // s3 <- s0
};
RunTest(pmc.Create(moves), zone);
}
// Cycle with a float move that must be deferred until after swaps.
{
std::vector<InstructionOperand> moves = {
d0, d1, // d0 <- d1
s2, s0, // s2 <- s0
s3, s4 // s3 <- s4 must be deferred
};
RunTest(pmc.Create(moves), zone);
}
// Cycles involving s-registers and a non-aliased d-register.
{
std::vector<InstructionOperand> moves = {
d16, d0, // d16 <- d0
s1, s2, // s1 <- s2
d1, d16 // d1 <- d16
};
RunTest(pmc.Create(moves), zone);
}
{
std::vector<InstructionOperand> moves = {
s2, s1, // s1 <- s2
d0, d16, // d16 <- d0
d16, d1 // d1 <- d16
};
RunTest(pmc.Create(moves), zone);
}
{
std::vector<InstructionOperand> moves = {
d0, d16, // d0 <- d16
d16, d1, // s2 <- s0
s3, s0 // d0 <- d1
};
RunTest(pmc.Create(moves), zone);
}
// Cycle involving aliasing registers and a slot.
{
std::vector<InstructionOperand> moves = {
dSlot, d0, // dSlot <- d0
d1, dSlot, // d1 <- dSlot
s0, s3 // s0 <- s3
};
RunTest(pmc.Create(moves), zone);
}
}
// Test parallel moves that change the frame layout. These typically happen when
// preparing tail-calls.
TEST(ComplexParallelMoves) {
ParallelMoveCreator pmc;
Zone* zone = pmc.main_zone();
auto w64_2 = AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kWord64, 2);
auto w64_5 = AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kWord64, 5);
auto s128_1 = AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kSimd128, 1);
auto s128_4 = AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kSimd128, 4);
auto s128_5 = AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kSimd128, 5);
auto s128_2 = AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kSimd128, 2);
auto w64_3 = AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kWord64, 3);
auto w64_0 = AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kWord64, 0);
auto s128_6 = AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kSimd128, 6);
auto w64_6 = AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kWord64, 6);
auto s128_reg = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kSimd128, 0);
{
// A parallel move with multiple cycles that requires > 1 temporary
// location.
std::vector<InstructionOperand> moves = {
w64_2, w64_5, // -
s128_1, s128_4, // -
s128_5, s128_2, // -
w64_3, w64_0 // -
};
RunTest(pmc.Create(moves), zone);
}
// Regression test for https://crbug.com/1335537.
{
std::vector<InstructionOperand> moves = {
s128_5, s128_6, // -
s128_1, s128_6, // -
w64_6, w64_0 // -
};
RunTest(pmc.Create(moves), zone);
}
// A cycle with 2 moves that should not use a swap, because the
// interfering operands don't have the same base address.
{
std::vector<InstructionOperand> moves = {
s128_1, s128_reg, // -
s128_reg, s128_2 // -
};
RunTest(pmc.Create(moves), zone);
}
// Another cycle with 2 moves that should not use a swap, because the
// interfering operands don't have the same representation.
{
std::vector<InstructionOperand> moves = {
s128_2, s128_5, // -
w64_2, w64_5 // -
};
RunTest(pmc.Create(moves), zone);
}
}
TEST(FuzzResolver) {
ParallelMoveCreator pmc;
for (int size = 0; size < 80; ++size) {
for (int repeat = 0; repeat < 50; ++repeat) {
RunTest(pmc.Create(size), pmc.main_zone());
}
}
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,144 @@
// Copyright 2014 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/common-operator.h"
#include "src/compiler/compiler-source-position-table.h"
#include "src/compiler/js-operator.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node-origin-table.h"
#include "src/compiler/node.h"
#include "src/compiler/operator.h"
#include "src/compiler/schedule.h"
#include "src/compiler/scheduler.h"
#include "src/compiler/turbofan-graph-visualizer.h"
#include "src/compiler/turbofan-graph.h"
#include "src/compiler/verifier.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace compiler {
static Operator dummy_operator1(IrOpcode::kParameter, Operator::kNoWrite,
"dummy", 1, 0, 0, 1, 0, 0);
static Operator dummy_operator6(IrOpcode::kParameter, Operator::kNoWrite,
"dummy", 6, 0, 0, 1, 0, 0);
TEST(NodeWithNullInputReachableFromEnd) {
HandleAndZoneScope scope(kCompressGraphZone);
TFGraph graph(scope.main_zone());
CommonOperatorBuilder common(scope.main_zone());
Node* start = graph.NewNode(common.Start(0));
graph.SetStart(start);
Node* k = graph.NewNode(common.Int32Constant(0));
Node* phi =
graph.NewNode(common.Phi(MachineRepresentation::kTagged, 1), k, start);
phi->ReplaceInput(0, nullptr);
graph.SetEnd(phi);
SourcePositionTable table(&graph);
NodeOriginTable table2(&graph);
StdoutStream{} << AsJSON(graph, &table, &table2);
}
TEST(NodeWithNullControlReachableFromEnd) {
HandleAndZoneScope scope(kCompressGraphZone);
TFGraph graph(scope.main_zone());
CommonOperatorBuilder common(scope.main_zone());
Node* start = graph.NewNode(common.Start(0));
graph.SetStart(start);
Node* k = graph.NewNode(common.Int32Constant(0));
Node* phi =
graph.NewNode(common.Phi(MachineRepresentation::kTagged, 1), k, start);
phi->ReplaceInput(1, nullptr);
graph.SetEnd(phi);
SourcePositionTable table(&graph);
NodeOriginTable table2(&graph);
StdoutStream{} << AsJSON(graph, &table, &table2);
}
TEST(NodeWithNullInputReachableFromStart) {
HandleAndZoneScope scope(kCompressGraphZone);
TFGraph graph(scope.main_zone());
CommonOperatorBuilder common(scope.main_zone());
Node* start = graph.NewNode(common.Start(0));
graph.SetStart(start);
Node* k = graph.NewNode(common.Int32Constant(0));
Node* phi =
graph.NewNode(common.Phi(MachineRepresentation::kTagged, 1), k, start);
phi->ReplaceInput(0, nullptr);
graph.SetEnd(start);
SourcePositionTable table(&graph);
NodeOriginTable table2(&graph);
StdoutStream{} << AsJSON(graph, &table, &table2);
}
TEST(NodeWithNullControlReachableFromStart) {
HandleAndZoneScope scope(kCompressGraphZone);
TFGraph graph(scope.main_zone());
CommonOperatorBuilder common(scope.main_zone());
Node* start = graph.NewNode(common.Start(0));
graph.SetStart(start);
Node* merge = graph.NewNode(common.Merge(2), start, start);
merge->ReplaceInput(1, nullptr);
graph.SetEnd(merge);
SourcePositionTable table(&graph);
NodeOriginTable table2(&graph);
StdoutStream{} << AsJSON(graph, &table, &table2);
}
TEST(NodeNetworkOfDummiesReachableFromEnd) {
HandleAndZoneScope scope(kCompressGraphZone);
TFGraph graph(scope.main_zone());
CommonOperatorBuilder common(scope.main_zone());
Node* start = graph.NewNode(common.Start(0));
graph.SetStart(start);
Node* n2 = graph.NewNode(&dummy_operator1, graph.start());
Node* n3 = graph.NewNode(&dummy_operator1, graph.start());
Node* n4 = graph.NewNode(&dummy_operator1, n2);
Node* n5 = graph.NewNode(&dummy_operator1, n2);
Node* n6 = graph.NewNode(&dummy_operator1, n3);
Node* n7 = graph.NewNode(&dummy_operator1, n3);
Node* n8 = graph.NewNode(&dummy_operator1, n5);
Node* n9 = graph.NewNode(&dummy_operator1, n5);
Node* n10 = graph.NewNode(&dummy_operator1, n9);
Node* n11 = graph.NewNode(&dummy_operator1, n9);
Node* end_dependencies[6] = {n4, n8, n10, n11, n6, n7};
Node* end = graph.NewNode(&dummy_operator6, 6, end_dependencies);
graph.SetEnd(end);
SourcePositionTable table(&graph);
NodeOriginTable table2(&graph);
StdoutStream{} << AsJSON(graph, &table, &table2);
}
TEST(TestSourceIdAssigner) {
Handle<SharedFunctionInfo> shared1;
Handle<SharedFunctionInfo> shared2;
SourceIdAssigner assigner(2);
const int source_id1 = assigner.GetIdFor(shared1);
const int source_id2 = assigner.GetIdFor(shared2);
CHECK_EQ(source_id1, source_id2);
CHECK_EQ(source_id1, assigner.GetIdAt(0));
CHECK_EQ(source_id2, assigner.GetIdAt(1));
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,116 @@
// Copyright 2017 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/backend/instruction-scheduler.h"
#include "src/compiler/backend/instruction-selector-impl.h"
#include "src/compiler/backend/instruction-selector.h"
#include "src/compiler/backend/instruction.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace compiler {
using FlagsContinuation = FlagsContinuationT;
// Create InstructionBlocks with a single block.
InstructionBlocks* CreateSingleBlock(Zone* zone) {
InstructionBlock* block = zone->New<InstructionBlock>(
zone, RpoNumber::FromInt(0), RpoNumber::Invalid(), RpoNumber::Invalid(),
RpoNumber::Invalid(), false, false);
InstructionBlocks* blocks = zone->AllocateArray<InstructionBlocks>(1);
new (blocks) InstructionBlocks(1, block, zone);
return blocks;
}
// Wrapper around the InstructionScheduler.
class InstructionSchedulerTester {
public:
InstructionSchedulerTester()
: scope_(kCompressGraphZone),
blocks_(CreateSingleBlock(scope_.main_zone())),
sequence_(scope_.main_isolate(), scope_.main_zone(), blocks_),
scheduler_(scope_.main_zone(), &sequence_) {}
void StartBlock() { scheduler_.StartBlock(RpoNumber::FromInt(0)); }
void EndBlock() { scheduler_.EndBlock(RpoNumber::FromInt(0)); }
void AddInstruction(Instruction* instr) { scheduler_.AddInstruction(instr); }
void AddTerminator(Instruction* instr) { scheduler_.AddTerminator(instr); }
void CheckHasSideEffect(Instruction* instr) {
CHECK(scheduler_.HasSideEffect(instr));
}
void CheckIsDeopt(Instruction* instr) { CHECK(instr->IsDeoptimizeCall()); }
void CheckInSuccessors(Instruction* instr, Instruction* successor) {
InstructionScheduler::ScheduleGraphNode* node = GetNode(instr);
InstructionScheduler::ScheduleGraphNode* succ_node = GetNode(successor);
ZoneDeque<InstructionScheduler::ScheduleGraphNode*>& successors =
node->successors();
CHECK_NE(std::find(successors.begin(), successors.end(), succ_node),
successors.end());
}
Zone* zone() { return scope_.main_zone(); }
private:
InstructionScheduler::ScheduleGraphNode* GetNode(Instruction* instr) {
for (auto node : scheduler_.graph_) {
if (node->instruction() == instr) return node;
}
return nullptr;
}
HandleAndZoneScope scope_;
InstructionBlocks* blocks_;
InstructionSequence sequence_;
InstructionScheduler scheduler_;
};
// TODO(391750831, nicohartmann): Currently broken with Turboshaft backend.
// Needs to be ported.
#if 0
TEST(DeoptInMiddleOfBasicBlock) {
InstructionSchedulerTester tester;
Zone* zone = tester.zone();
tester.StartBlock();
InstructionCode jmp_opcode = kArchJmp;
Node* dummy_frame_state = Node::New(zone, 0, nullptr, 0, nullptr, false);
FlagsContinuation cont = FlagsContinuation::ForDeoptimizeForTesting(
kEqual, DeoptimizeReason::kUnknown, dummy_frame_state->id(),
FeedbackSource{}, dummy_frame_state);
jmp_opcode = cont.Encode(jmp_opcode);
Instruction* jmp_inst = Instruction::New(zone, jmp_opcode);
tester.CheckIsDeopt(jmp_inst);
tester.AddInstruction(jmp_inst);
Instruction* side_effect_inst = Instruction::New(zone, kArchPrepareTailCall);
tester.CheckHasSideEffect(side_effect_inst);
tester.AddInstruction(side_effect_inst);
Instruction* other_jmp_inst = Instruction::New(zone, jmp_opcode);
tester.CheckIsDeopt(other_jmp_inst);
tester.AddInstruction(other_jmp_inst);
Instruction* ret_inst = Instruction::New(zone, kArchRet);
tester.AddTerminator(ret_inst);
// Check that an instruction with a side effect is a successor of the deopt.
tester.CheckInSuccessors(jmp_inst, side_effect_inst);
// Check that the second deopt is a successor of the first deopt.
tester.CheckInSuccessors(jmp_inst, other_jmp_inst);
// Check that the second deopt is a successor of the side-effect instruction.
tester.CheckInSuccessors(side_effect_inst, other_jmp_inst);
// Check that the block terminator is a successor of all other instructions.
tester.CheckInSuccessors(jmp_inst, ret_inst);
tester.CheckInSuccessors(side_effect_inst, ret_inst);
tester.CheckInSuccessors(other_jmp_inst, ret_inst);
// Schedule block.
tester.EndBlock();
}
#endif
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,335 @@
// Copyright 2014 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/backend/code-generator.h"
#include "src/compiler/backend/instruction.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/linkage.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node.h"
#include "src/compiler/operator.h"
#include "src/compiler/schedule.h"
#include "src/compiler/scheduler.h"
#include "src/compiler/turbofan-graph.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace compiler {
using TestInstr = v8::internal::compiler::Instruction;
using TestInstrSeq = v8::internal::compiler::InstructionSequence;
// A testing helper for the register code abstraction.
class InstructionTester : public HandleAndZoneScope {
public: // We're all friends here.
InstructionTester()
: HandleAndZoneScope(kCompressGraphZone),
graph(zone()),
schedule(zone()),
common(zone()),
machine(zone()),
code(nullptr) {}
TFGraph graph;
Schedule schedule;
CommonOperatorBuilder common;
MachineOperatorBuilder machine;
TestInstrSeq* code;
Zone* zone() { return main_zone(); }
void allocCode() {
if (schedule.rpo_order()->size() == 0) {
// Compute the RPO order.
Scheduler::ComputeSpecialRPO(main_zone(), &schedule);
CHECK_NE(0u, schedule.rpo_order()->size());
}
InstructionBlocks* instruction_blocks =
TestInstrSeq::InstructionBlocksFor(main_zone(), &schedule);
code = main_zone()->New<TestInstrSeq>(main_isolate(), main_zone(),
instruction_blocks);
}
Node* Int32Constant(int32_t val) {
Node* node = graph.NewNode(common.Int32Constant(val));
schedule.AddNode(schedule.start(), node);
return node;
}
Node* Float64Constant(double val) {
Node* node = graph.NewNode(common.Float64Constant(val));
schedule.AddNode(schedule.start(), node);
return node;
}
Node* Parameter(int32_t which) {
Node* node = graph.NewNode(common.Parameter(which));
schedule.AddNode(schedule.start(), node);
return node;
}
Node* NewNode(BasicBlock* block) {
Node* node = graph.NewNode(common.Int32Constant(111));
schedule.AddNode(block, node);
return node;
}
int NewInstr() {
InstructionCode opcode = static_cast<InstructionCode>(110);
TestInstr* instr = TestInstr::New(zone(), opcode);
return code->AddInstruction(instr);
}
int NewNop() {
TestInstr* instr = TestInstr::New(zone(), kArchNop);
return code->AddInstruction(instr);
}
UnallocatedOperand Unallocated(int vreg) {
return UnallocatedOperand(UnallocatedOperand::REGISTER_OR_SLOT, vreg);
}
RpoNumber RpoFor(BasicBlock* block) {
return RpoNumber::FromInt(block->rpo_number());
}
InstructionBlock* BlockAt(BasicBlock* block) {
return code->InstructionBlockAt(RpoFor(block));
}
BasicBlock* GetBasicBlock(int instruction_index) {
const InstructionBlock* block =
code->GetInstructionBlock(instruction_index);
return schedule.rpo_order()->at(block->rpo_number().ToSize());
}
int first_instruction_index(BasicBlock* block) {
return BlockAt(block)->first_instruction_index();
}
int last_instruction_index(BasicBlock* block) {
return BlockAt(block)->last_instruction_index();
}
};
TEST(InstructionBasic) {
InstructionTester R;
for (int i = 0; i < 10; i++) {
R.Int32Constant(i); // Add some nodes to the graph.
}
BasicBlock* last = R.schedule.start();
for (int i = 0; i < 5; i++) {
BasicBlock* block = R.schedule.NewBasicBlock();
R.schedule.AddGoto(last, block);
last = block;
}
R.allocCode();
BasicBlockVector* blocks = R.schedule.rpo_order();
CHECK_EQ(static_cast<int>(blocks->size()), R.code->InstructionBlockCount());
for (auto block : *blocks) {
CHECK_EQ(block->rpo_number(), R.BlockAt(block)->rpo_number().ToInt());
CHECK(!block->loop_end());
}
}
TEST(InstructionGetBasicBlock) {
InstructionTester R;
BasicBlock* b0 = R.schedule.start();
BasicBlock* b1 = R.schedule.NewBasicBlock();
BasicBlock* b2 = R.schedule.NewBasicBlock();
BasicBlock* b3 = R.schedule.end();
R.schedule.AddGoto(b0, b1);
R.schedule.AddGoto(b1, b2);
R.schedule.AddGoto(b2, b3);
R.allocCode();
R.code->StartBlock(R.RpoFor(b0));
int i0 = R.NewInstr();
int i1 = R.NewInstr();
R.code->EndBlock(R.RpoFor(b0));
R.code->StartBlock(R.RpoFor(b1));
int i2 = R.NewInstr();
int i3 = R.NewInstr();
int i4 = R.NewInstr();
int i5 = R.NewInstr();
R.code->EndBlock(R.RpoFor(b1));
R.code->StartBlock(R.RpoFor(b2));
int i6 = R.NewInstr();
int i7 = R.NewInstr();
int i8 = R.NewInstr();
R.code->EndBlock(R.RpoFor(b2));
R.code->StartBlock(R.RpoFor(b3));
R.NewNop();
R.code->EndBlock(R.RpoFor(b3));
CHECK_EQ(b0, R.GetBasicBlock(i0));
CHECK_EQ(b0, R.GetBasicBlock(i1));
CHECK_EQ(b1, R.GetBasicBlock(i2));
CHECK_EQ(b1, R.GetBasicBlock(i3));
CHECK_EQ(b1, R.GetBasicBlock(i4));
CHECK_EQ(b1, R.GetBasicBlock(i5));
CHECK_EQ(b2, R.GetBasicBlock(i6));
CHECK_EQ(b2, R.GetBasicBlock(i7));
CHECK_EQ(b2, R.GetBasicBlock(i8));
CHECK_EQ(b0, R.GetBasicBlock(R.first_instruction_index(b0)));
CHECK_EQ(b0, R.GetBasicBlock(R.last_instruction_index(b0)));
CHECK_EQ(b1, R.GetBasicBlock(R.first_instruction_index(b1)));
CHECK_EQ(b1, R.GetBasicBlock(R.last_instruction_index(b1)));
CHECK_EQ(b2, R.GetBasicBlock(R.first_instruction_index(b2)));
CHECK_EQ(b2, R.GetBasicBlock(R.last_instruction_index(b2)));
CHECK_EQ(b3, R.GetBasicBlock(R.first_instruction_index(b3)));
CHECK_EQ(b3, R.GetBasicBlock(R.last_instruction_index(b3)));
}
TEST(InstructionIsGapAt) {
InstructionTester R;
BasicBlock* b0 = R.schedule.start();
R.schedule.AddReturn(b0, R.Int32Constant(1));
R.allocCode();
TestInstr* i0 = TestInstr::New(R.zone(), 100);
TestInstr* g = TestInstr::New(R.zone(), 103);
R.code->StartBlock(R.RpoFor(b0));
R.code->AddInstruction(i0);
R.code->AddInstruction(g);
R.code->EndBlock(R.RpoFor(b0));
CHECK_EQ(2, R.code->instructions().size());
}
TEST(InstructionIsGapAt2) {
InstructionTester R;
BasicBlock* b0 = R.schedule.start();
BasicBlock* b1 = R.schedule.end();
R.schedule.AddGoto(b0, b1);
R.schedule.AddReturn(b1, R.Int32Constant(1));
R.allocCode();
TestInstr* i0 = TestInstr::New(R.zone(), 100);
TestInstr* g = TestInstr::New(R.zone(), 103);
R.code->StartBlock(R.RpoFor(b0));
R.code->AddInstruction(i0);
R.code->AddInstruction(g);
R.code->EndBlock(R.RpoFor(b0));
TestInstr* i1 = TestInstr::New(R.zone(), 102);
TestInstr* g1 = TestInstr::New(R.zone(), 104);
R.code->StartBlock(R.RpoFor(b1));
R.code->AddInstruction(i1);
R.code->AddInstruction(g1);
R.code->EndBlock(R.RpoFor(b1));
CHECK_EQ(4, R.code->instructions().size());
}
TEST(InstructionAddGapMove) {
InstructionTester R;
BasicBlock* b0 = R.schedule.start();
R.schedule.AddReturn(b0, R.Int32Constant(1));
R.allocCode();
TestInstr* i0 = TestInstr::New(R.zone(), 100);
TestInstr* g = TestInstr::New(R.zone(), 103);
R.code->StartBlock(R.RpoFor(b0));
R.code->AddInstruction(i0);
R.code->AddInstruction(g);
R.code->EndBlock(R.RpoFor(b0));
CHECK_EQ(2, R.code->instructions().size());
int index = 0;
for (auto instr : R.code->instructions()) {
UnallocatedOperand op1 = R.Unallocated(index++);
UnallocatedOperand op2 = R.Unallocated(index++);
instr->GetOrCreateParallelMove(TestInstr::START, R.zone())
->AddMove(op1, op2);
ParallelMove* move = instr->GetParallelMove(TestInstr::START);
CHECK(move);
CHECK_EQ(1u, move->size());
MoveOperands* cur = move->at(0);
CHECK(op1.Equals(cur->source()));
CHECK(op2.Equals(cur->destination()));
}
}
TEST(InstructionOperands) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
{
TestInstr* i = TestInstr::New(&zone, 101);
CHECK_EQ(0, static_cast<int>(i->OutputCount()));
CHECK_EQ(0, static_cast<int>(i->InputCount()));
CHECK_EQ(0, static_cast<int>(i->TempCount()));
}
int vreg = 15;
InstructionOperand outputs[] = {
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg)};
InstructionOperand inputs[] = {
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg)};
InstructionOperand temps[] = {
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg),
UnallocatedOperand(UnallocatedOperand::MUST_HAVE_REGISTER, vreg)};
for (size_t i = 0; i < arraysize(outputs); i++) {
for (size_t j = 0; j < arraysize(inputs); j++) {
for (size_t k = 0; k < arraysize(temps); k++) {
TestInstr* m =
TestInstr::New(&zone, 101, i, outputs, j, inputs, k, temps);
CHECK(i == m->OutputCount());
CHECK(j == m->InputCount());
CHECK(k == m->TempCount());
for (size_t z = 0; z < i; z++) {
CHECK(outputs[z].Equals(*m->OutputAt(z)));
}
for (size_t z = 0; z < j; z++) {
CHECK(inputs[z].Equals(*m->InputAt(z)));
}
for (size_t z = 0; z < k; z++) {
CHECK(temps[z].Equals(*m->TempAt(z)));
}
}
}
}
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,411 @@
// Copyright 2014 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/codegen/assembler.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/node-properties.h"
#include "src/heap/factory-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/js-heap-broker-base.h"
#include "test/common/value-helper.h"
namespace v8 {
namespace internal {
namespace compiler {
class JSCacheTesterHelper {
protected:
explicit JSCacheTesterHelper(Zone* zone)
: main_graph_(zone),
main_common_(zone),
main_javascript_(zone),
main_machine_(zone) {}
TFGraph main_graph_;
CommonOperatorBuilder main_common_;
JSOperatorBuilder main_javascript_;
MachineOperatorBuilder main_machine_;
};
// TODO(dcarney): JSConstantCacheTester inherits from JSGraph???
class JSConstantCacheTester : public HandleAndZoneScope,
public JSCacheTesterHelper,
public JSGraph,
public JSHeapBrokerTestBase {
public:
JSConstantCacheTester()
: HandleAndZoneScope(kCompressGraphZone),
JSCacheTesterHelper(main_zone()),
JSGraph(main_isolate(), &main_graph_, &main_common_, &main_javascript_,
nullptr, &main_machine_),
JSHeapBrokerTestBase(main_isolate(), main_zone()) {
main_graph_.SetStart(main_graph_.NewNode(common()->Start(0)));
main_graph_.SetEnd(
main_graph_.NewNode(common()->End(1), main_graph_.start()));
}
DirectHandle<HeapObject> handle(Node* node) {
CHECK_EQ(IrOpcode::kHeapConstant, node->opcode());
return HeapConstantOf(node->op());
}
Factory* factory() { return main_isolate()->factory(); }
};
TEST(ZeroConstant1) {
JSConstantCacheTester T;
Node* zero = T.ZeroConstant();
CHECK_EQ(IrOpcode::kNumberConstant, zero->opcode());
CHECK_EQ(zero, T.ConstantNoHole(0));
CHECK_NE(zero, T.ConstantNoHole(-0.0));
CHECK_NE(zero, T.ConstantNoHole(1.0));
CHECK_NE(zero, T.ConstantNoHole(std::numeric_limits<double>::quiet_NaN()));
CHECK_NE(zero, T.Float64Constant(0));
CHECK_NE(zero, T.Int32Constant(0));
}
TEST(MinusZeroConstant) {
JSConstantCacheTester T;
Node* minus_zero = T.ConstantNoHole(-0.0);
Node* zero = T.ZeroConstant();
CHECK_EQ(IrOpcode::kNumberConstant, minus_zero->opcode());
CHECK_EQ(minus_zero, T.ConstantNoHole(-0.0));
CHECK_NE(zero, minus_zero);
double zero_value = OpParameter<double>(zero->op());
double minus_zero_value = OpParameter<double>(minus_zero->op());
CHECK(base::bit_cast<uint64_t>(0.0) == base::bit_cast<uint64_t>(zero_value));
CHECK(base::bit_cast<uint64_t>(-0.0) != base::bit_cast<uint64_t>(zero_value));
CHECK(base::bit_cast<uint64_t>(0.0) !=
base::bit_cast<uint64_t>(minus_zero_value));
CHECK(base::bit_cast<uint64_t>(-0.0) ==
base::bit_cast<uint64_t>(minus_zero_value));
}
TEST(ZeroConstant2) {
JSConstantCacheTester T;
Node* zero = T.ConstantNoHole(0);
CHECK_EQ(IrOpcode::kNumberConstant, zero->opcode());
CHECK_EQ(zero, T.ZeroConstant());
CHECK_NE(zero, T.ConstantNoHole(-0.0));
CHECK_NE(zero, T.ConstantNoHole(1.0));
CHECK_NE(zero, T.ConstantNoHole(std::numeric_limits<double>::quiet_NaN()));
CHECK_NE(zero, T.Float64Constant(0));
CHECK_NE(zero, T.Int32Constant(0));
}
TEST(OneConstant1) {
JSConstantCacheTester T;
Node* one = T.OneConstant();
CHECK_EQ(IrOpcode::kNumberConstant, one->opcode());
CHECK_EQ(one, T.ConstantNoHole(1));
CHECK_EQ(one, T.ConstantNoHole(1.0));
CHECK_NE(one, T.ConstantNoHole(1.01));
CHECK_NE(one, T.ConstantNoHole(-1.01));
CHECK_NE(one, T.ConstantNoHole(std::numeric_limits<double>::quiet_NaN()));
CHECK_NE(one, T.Float64Constant(1.0));
CHECK_NE(one, T.Int32Constant(1));
}
TEST(OneConstant2) {
JSConstantCacheTester T;
Node* one = T.ConstantNoHole(1);
CHECK_EQ(IrOpcode::kNumberConstant, one->opcode());
CHECK_EQ(one, T.OneConstant());
CHECK_EQ(one, T.ConstantNoHole(1.0));
CHECK_NE(one, T.ConstantNoHole(1.01));
CHECK_NE(one, T.ConstantNoHole(-1.01));
CHECK_NE(one, T.ConstantNoHole(std::numeric_limits<double>::quiet_NaN()));
CHECK_NE(one, T.Float64Constant(1.0));
CHECK_NE(one, T.Int32Constant(1));
}
TEST(Canonicalizations) {
JSConstantCacheTester T;
CHECK_EQ(T.ZeroConstant(), T.ZeroConstant());
CHECK_EQ(T.UndefinedConstant(), T.UndefinedConstant());
CHECK_EQ(T.TheHoleConstant(), T.TheHoleConstant());
CHECK_EQ(T.TrueConstant(), T.TrueConstant());
CHECK_EQ(T.FalseConstant(), T.FalseConstant());
CHECK_EQ(T.NullConstant(), T.NullConstant());
CHECK_EQ(T.ZeroConstant(), T.ZeroConstant());
CHECK_EQ(T.OneConstant(), T.OneConstant());
CHECK_EQ(T.NaNConstant(), T.NaNConstant());
}
TEST(NoAliasing) {
JSConstantCacheTester T;
Node* nodes[] = {T.UndefinedConstant(), T.TheHoleConstant(),
T.TrueConstant(), T.FalseConstant(),
T.NullConstant(), T.ZeroConstant(),
T.OneConstant(), T.NaNConstant(),
T.ConstantNoHole(21), T.ConstantNoHole(22.2)};
for (size_t i = 0; i < arraysize(nodes); i++) {
for (size_t j = 0; j < arraysize(nodes); j++) {
if (i != j) CHECK_NE(nodes[i], nodes[j]);
}
}
}
TEST(CanonicalizingNumbers) {
JSConstantCacheTester T;
FOR_FLOAT64_INPUTS(i) {
Node* node = T.ConstantNoHole(i);
for (int j = 0; j < 5; j++) {
CHECK_EQ(node, T.ConstantNoHole(i));
}
}
}
TEST(HeapNumbers) {
JSConstantCacheTester T;
FOR_FLOAT64_INPUTS(value) {
Handle<Object> num = T.CanonicalHandle(*T.factory()->NewNumber(value));
Handle<HeapNumber> heap =
T.CanonicalHandle(*T.factory()->NewHeapNumber(value));
Node* node1 = T.ConstantNoHole(value);
Node* node2 = T.ConstantNoHole(MakeRef(T.broker(), num), T.broker());
Node* node3 = T.ConstantNoHole(MakeRef(T.broker(), heap), T.broker());
CHECK_EQ(node1, node2);
CHECK_EQ(node1, node3);
}
}
TEST(OddballHandle) {
JSConstantCacheTester T;
CHECK_EQ(T.UndefinedConstant(),
T.ConstantNoHole(T.broker()->undefined_value(), T.broker()));
CHECK_EQ(T.TrueConstant(),
T.ConstantNoHole(T.broker()->true_value(), T.broker()));
CHECK_EQ(T.FalseConstant(),
T.ConstantNoHole(T.broker()->false_value(), T.broker()));
CHECK_EQ(T.NullConstant(),
T.ConstantNoHole(T.broker()->null_value(), T.broker()));
CHECK_EQ(T.NaNConstant(),
T.ConstantNoHole(T.broker()->nan_value(), T.broker()));
}
TEST(OddballValues) {
JSConstantCacheTester T;
CHECK_EQ(*T.factory()->undefined_value(), *T.handle(T.UndefinedConstant()));
CHECK_EQ(*T.factory()->the_hole_value(), *T.handle(T.TheHoleConstant()));
CHECK_EQ(*T.factory()->true_value(), *T.handle(T.TrueConstant()));
CHECK_EQ(*T.factory()->false_value(), *T.handle(T.FalseConstant()));
CHECK_EQ(*T.factory()->null_value(), *T.handle(T.NullConstant()));
}
TEST(ExternalReferences) {
// TODO(titzer): test canonicalization of external references.
}
static bool Contains(NodeVector* nodes, Node* n) {
for (size_t i = 0; i < nodes->size(); i++) {
if (nodes->at(i) == n) return true;
}
return false;
}
static void CheckGetCachedNodesContains(JSConstantCacheTester* T, Node* n) {
NodeVector nodes(T->main_zone());
T->GetCachedNodes(&nodes);
CHECK(Contains(&nodes, n));
}
TEST(JSGraph_GetCachedNodes1) {
JSConstantCacheTester T;
CheckGetCachedNodesContains(&T, T.TrueConstant());
CheckGetCachedNodesContains(&T, T.UndefinedConstant());
CheckGetCachedNodesContains(&T, T.TheHoleConstant());
CheckGetCachedNodesContains(&T, T.TrueConstant());
CheckGetCachedNodesContains(&T, T.FalseConstant());
CheckGetCachedNodesContains(&T, T.NullConstant());
CheckGetCachedNodesContains(&T, T.ZeroConstant());
CheckGetCachedNodesContains(&T, T.OneConstant());
CheckGetCachedNodesContains(&T, T.NaNConstant());
}
TEST(JSGraph_GetCachedNodes_int32) {
JSConstantCacheTester T;
int32_t constants[] = {0, 1, 1, 1, 1, 2, 3, 4, 11, 12, 13,
14, 55, -55, -44, -33, -22, -11, 16, 16, 17, 17,
18, 18, 19, 19, 20, 20, 21, 21, 22, 23, 24,
25, 15, 30, 31, 45, 46, 47, 48};
for (size_t i = 0; i < arraysize(constants); i++) {
size_t count_before = T.graph()->NodeCount();
NodeVector nodes_before(T.main_zone());
T.GetCachedNodes(&nodes_before);
Node* n = T.Int32Constant(constants[i]);
if (n->id() < count_before) {
// An old ID indicates a cached node. It should have been in the set.
CHECK(Contains(&nodes_before, n));
}
// Old or new, it should be in the cached set afterwards.
CheckGetCachedNodesContains(&T, n);
}
}
TEST(JSGraph_GetCachedNodes_float64) {
JSConstantCacheTester T;
double constants[] = {0, 11.1, 12.2, 13, 14, 55.5, -55.5, -44.4,
-33, -22, -11, 0, 11.1, 11.1, 12.3, 12.3,
11, 11, -33.3, -33.3, -22, -11};
for (size_t i = 0; i < arraysize(constants); i++) {
size_t count_before = T.graph()->NodeCount();
NodeVector nodes_before(T.main_zone());
T.GetCachedNodes(&nodes_before);
Node* n = T.Float64Constant(constants[i]);
if (n->id() < count_before) {
// An old ID indicates a cached node. It should have been in the set.
CHECK(Contains(&nodes_before, n));
}
// Old or new, it should be in the cached set afterwards.
CheckGetCachedNodesContains(&T, n);
}
}
TEST(JSGraph_GetCachedNodes_int64) {
JSConstantCacheTester T;
int32_t constants[] = {0, 11, 12, 13, 14, 55, -55, -44, -33,
-22, -11, 16, 16, 17, 17, 18, 18, 19,
19, 20, 20, 21, 21, 22, 23, 24, 25};
for (size_t i = 0; i < arraysize(constants); i++) {
size_t count_before = T.graph()->NodeCount();
NodeVector nodes_before(T.main_zone());
T.GetCachedNodes(&nodes_before);
Node* n = T.Int64Constant(constants[i]);
if (n->id() < count_before) {
// An old ID indicates a cached node. It should have been in the set.
CHECK(Contains(&nodes_before, n));
}
// Old or new, it should be in the cached set afterwards.
CheckGetCachedNodesContains(&T, n);
}
}
TEST(JSGraph_GetCachedNodes_number) {
JSConstantCacheTester T;
double constants[] = {0, 11.1, 12.2, 13, 14, 55.5, -55.5, -44.4,
-33, -22, -11, 0, 11.1, 11.1, 12.3, 12.3,
11, 11, -33.3, -33.3, -22, -11};
for (size_t i = 0; i < arraysize(constants); i++) {
size_t count_before = T.graph()->NodeCount();
NodeVector nodes_before(T.main_zone());
T.GetCachedNodes(&nodes_before);
Node* n = T.ConstantNoHole(constants[i]);
if (n->id() < count_before) {
// An old ID indicates a cached node. It should have been in the set.
CHECK(Contains(&nodes_before, n));
}
// Old or new, it should be in the cached set afterwards.
CheckGetCachedNodesContains(&T, n);
}
}
TEST(JSGraph_GetCachedNodes_external) {
JSConstantCacheTester T;
ExternalReference constants[] = {ExternalReference::address_of_min_int(),
ExternalReference::address_of_min_int(),
ExternalReference::address_of_min_int(),
ExternalReference::address_of_one_half(),
ExternalReference::address_of_one_half(),
ExternalReference::address_of_min_int(),
ExternalReference::address_of_the_hole_nan(),
ExternalReference::address_of_one_half()};
for (size_t i = 0; i < arraysize(constants); i++) {
size_t count_before = T.graph()->NodeCount();
NodeVector nodes_before(T.main_zone());
T.GetCachedNodes(&nodes_before);
Node* n = T.ExternalConstant(constants[i]);
if (n->id() < count_before) {
// An old ID indicates a cached node. It should have been in the set.
CHECK(Contains(&nodes_before, n));
}
// Old or new, it should be in the cached set afterwards.
CheckGetCachedNodesContains(&T, n);
}
}
TEST(JSGraph_GetCachedNodes_together) {
JSConstantCacheTester T;
Node* constants[] = {
T.TrueConstant(),
T.UndefinedConstant(),
T.TheHoleConstant(),
T.TrueConstant(),
T.FalseConstant(),
T.NullConstant(),
T.ZeroConstant(),
T.OneConstant(),
T.NaNConstant(),
T.Int32Constant(0),
T.Int32Constant(1),
T.Int64Constant(-2),
T.Int64Constant(-4),
T.Float64Constant(0.9),
T.Float64Constant(V8_INFINITY),
T.ConstantNoHole(0.99),
T.ConstantNoHole(1.11),
T.ExternalConstant(ExternalReference::address_of_one_half())};
NodeVector nodes(T.main_zone());
T.GetCachedNodes(&nodes);
for (size_t i = 0; i < arraysize(constants); i++) {
CHECK(Contains(&nodes, constants[i]));
}
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,761 @@
// Copyright 2014 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/codegen/tick-counter.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/compiler-source-position-table.h"
#include "src/compiler/js-context-specialization.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/js-operator.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/simplified-operator.h"
#include "src/heap/factory.h"
#include "src/objects/contexts.h"
#include "src/objects/objects-inl.h"
#include "src/objects/property.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/function-tester.h"
#include "test/cctest/compiler/js-heap-broker-base.h"
namespace v8 {
namespace internal {
namespace compiler {
class ContextSpecializationTester : public HandleAndZoneScope,
public JSHeapBrokerTestBase {
public:
explicit ContextSpecializationTester(Maybe<OuterContext> context)
: HandleAndZoneScope(kCompressGraphZone),
JSHeapBrokerTestBase(main_isolate(), main_zone()),
dependencies_(broker(), main_zone()),
graph_(main_zone()->New<TFGraph>(main_zone())),
common_(main_zone()),
javascript_(main_zone()),
machine_(main_zone()),
simplified_(main_zone()),
jsgraph_(main_isolate(), graph(), common(), &javascript_, &simplified_,
&machine_),
reducer_(main_zone(), graph(), &tick_counter_, broker()),
spec_(&reducer_, jsgraph(), broker(), context,
MaybeHandle<JSFunction>()) {}
ContextSpecializationTester(Maybe<OuterContext> context,
CanonicalHandles&& handles)
: HandleAndZoneScope(kCompressGraphZone),
JSHeapBrokerTestBase(main_isolate(), main_zone(), std::move(handles)),
dependencies_(broker(), main_zone()),
graph_(main_zone()->New<TFGraph>(main_zone())),
common_(main_zone()),
javascript_(main_zone()),
machine_(main_zone()),
simplified_(main_zone()),
jsgraph_(main_isolate(), graph(), common(), &javascript_, &simplified_,
&machine_),
reducer_(main_zone(), graph(), &tick_counter_, broker()),
spec_(&reducer_, jsgraph(), broker(), context,
MaybeHandle<JSFunction>()) {}
JSContextSpecialization* spec() { return &spec_; }
Isolate* isolate() { return main_isolate(); }
Factory* factory() { return main_isolate()->factory(); }
CommonOperatorBuilder* common() { return &common_; }
JSOperatorBuilder* javascript() { return &javascript_; }
SimplifiedOperatorBuilder* simplified() { return &simplified_; }
JSGraph* jsgraph() { return &jsgraph_; }
TFGraph* graph() { return graph_; }
void CheckChangesToValue(Node* node, DirectHandle<HeapObject> expected_value);
void CheckContextInputAndDepthChanges(
Node* node, DirectHandle<Context> expected_new_context_object,
size_t expected_new_depth);
void CheckContextInputAndDepthChanges(Node* node, Node* expected_new_context,
size_t expected_new_depth);
private:
CompilationDependencies dependencies_;
TickCounter tick_counter_;
TFGraph* graph_;
CommonOperatorBuilder common_;
JSOperatorBuilder javascript_;
MachineOperatorBuilder machine_;
SimplifiedOperatorBuilder simplified_;
JSGraph jsgraph_;
GraphReducer reducer_;
JSContextSpecialization spec_;
};
void ContextSpecializationTester::CheckChangesToValue(
Node* node, DirectHandle<HeapObject> expected_value) {
Reduction r = spec()->Reduce(node);
CHECK(r.Changed());
HeapObjectMatcher match(r.replacement());
CHECK(match.HasResolvedValue());
CHECK_EQ(*match.ResolvedValue(), *expected_value);
}
void ContextSpecializationTester::CheckContextInputAndDepthChanges(
Node* node, DirectHandle<Context> expected_new_context_object,
size_t expected_new_depth) {
ContextAccess access = ContextAccessOf(node->op());
Reduction r = spec()->Reduce(node);
CHECK(r.Changed());
Node* new_context = NodeProperties::GetContextInput(r.replacement());
CHECK_EQ(IrOpcode::kHeapConstant, new_context->opcode());
HeapObjectMatcher match(new_context);
CHECK_EQ(Cast<Context>(*match.ResolvedValue()), *expected_new_context_object);
ContextAccess new_access = ContextAccessOf(r.replacement()->op());
CHECK_EQ(new_access.depth(), expected_new_depth);
CHECK_EQ(new_access.index(), access.index());
CHECK_EQ(new_access.immutable(), access.immutable());
}
void ContextSpecializationTester::CheckContextInputAndDepthChanges(
Node* node, Node* expected_new_context, size_t expected_new_depth) {
ContextAccess access = ContextAccessOf(node->op());
Reduction r = spec()->Reduce(node);
CHECK(r.Changed());
Node* new_context = NodeProperties::GetContextInput(r.replacement());
CHECK_EQ(new_context, expected_new_context);
ContextAccess new_access = ContextAccessOf(r.replacement()->op());
CHECK_EQ(new_access.depth(), expected_new_depth);
CHECK_EQ(new_access.index(), access.index());
CHECK_EQ(new_access.immutable(), access.immutable());
}
namespace {
Handle<Context> NewContextForTesting(Isolate* isolate,
DirectHandle<Context> previous) {
DirectHandle<ScopeInfo> scope_info =
ScopeInfo::CreateForWithScope(isolate, {});
DirectHandle<JSObject> extension =
isolate->factory()->NewJSObjectWithNullProto();
return isolate->factory()->NewWithContext(previous, scope_info, extension);
}
Handle<Context> NewCanonicalContextForTesting(ContextSpecializationTester& t,
DirectHandle<Context> previous) {
DirectHandle<ScopeInfo> scope_info =
t.CanonicalHandle(*ScopeInfo::CreateForWithScope(t.isolate(), {}));
DirectHandle<JSObject> extension =
t.CanonicalHandle(*t.isolate()->factory()->NewJSObjectWithNullProto());
return t.CanonicalHandle(
*t.isolate()->factory()->NewWithContext(previous, scope_info, extension));
}
} // namespace
static const int slot_index = 5;
TEST(ReduceJSLoadContext0) {
ContextSpecializationTester t(Nothing<OuterContext>());
Node* start = t.graph()->NewNode(t.common()->Start(0));
t.graph()->SetStart(start);
// Make a context and initialize it a bit for this test.
Handle<Context> native = t.CanonicalHandle(*t.factory()->NewNativeContext());
DirectHandle<Context> subcontext1 = NewCanonicalContextForTesting(t, native);
Handle<Context> subcontext2 = NewCanonicalContextForTesting(t, subcontext1);
DirectHandle<Object> expected =
t.CanonicalHandle(*t.factory()->InternalizeUtf8String("gboy!"));
const int slot = 5;
native->set(slot, *expected);
Node* const_context =
t.jsgraph()->ConstantNoHole(MakeRef(t.broker(), native), t.broker());
Node* deep_const_context =
t.jsgraph()->ConstantNoHole(MakeRef(t.broker(), subcontext2), t.broker());
Node* param_context = t.graph()->NewNode(t.common()->Parameter(0), start);
{
// Mutable slot, constant context, depth = 0 => do nothing.
Node* load = t.graph()->NewNode(t.javascript()->LoadContext(0, 0, false),
const_context, start);
Reduction r = t.spec()->Reduce(load);
CHECK(!r.Changed());
}
{
// Mutable slot, non-constant context, depth = 0 => do nothing.
Node* load = t.graph()->NewNode(t.javascript()->LoadContext(0, 0, false),
param_context, start);
Reduction r = t.spec()->Reduce(load);
CHECK(!r.Changed());
}
{
// Mutable slot, constant context, depth > 0 => fold-in parent context.
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(2, Context::GLOBAL_EVAL_FUN_INDEX, false),
deep_const_context, start);
Reduction r = t.spec()->Reduce(load);
CHECK(r.Changed());
Node* new_context_input = NodeProperties::GetContextInput(r.replacement());
CHECK_EQ(IrOpcode::kHeapConstant, new_context_input->opcode());
HeapObjectMatcher match(new_context_input);
CHECK_EQ(*native, Cast<Context>(*match.ResolvedValue()));
ContextAccess access = ContextAccessOf(r.replacement()->op());
CHECK_EQ(Context::GLOBAL_EVAL_FUN_INDEX, static_cast<int>(access.index()));
CHECK_EQ(0, static_cast<int>(access.depth()));
CHECK_EQ(false, access.immutable());
}
{
// Immutable slot, constant context, depth = 0 => specialize.
Node* load = t.graph()->NewNode(t.javascript()->LoadContext(0, slot, true),
const_context, start);
Reduction r = t.spec()->Reduce(load);
CHECK(r.Changed());
CHECK(r.replacement() != load);
HeapObjectMatcher match(r.replacement());
CHECK(match.HasResolvedValue());
CHECK_EQ(*expected, *match.ResolvedValue());
}
// Clean up so that verifiers don't complain.
native->set(slot, Smi::zero());
}
TEST(ReduceJSLoadContext1) {
// The graph's context chain ends in the incoming context parameter:
//
// context2 <-- context1 <-- context0 (= Parameter(0))
ContextSpecializationTester t(Nothing<OuterContext>());
Node* start = t.graph()->NewNode(t.common()->Start(0));
t.graph()->SetStart(start);
ScopeInfoRef empty = t.broker()->empty_scope_info();
const i::compiler::Operator* create_function_context =
t.javascript()->CreateFunctionContext(empty, 42, FUNCTION_SCOPE);
Node* context0 = t.graph()->NewNode(t.common()->Parameter(0), start);
Node* context1 =
t.graph()->NewNode(create_function_context, context0, start, start);
Node* context2 =
t.graph()->NewNode(create_function_context, context1, start, start);
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(0, slot_index, false), context2, start);
CHECK(!t.spec()->Reduce(load).Changed());
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(0, slot_index, true), context2, start);
CHECK(!t.spec()->Reduce(load).Changed());
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(1, slot_index, false), context2, start);
t.CheckContextInputAndDepthChanges(load, context1, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(1, slot_index, true), context2, start);
t.CheckContextInputAndDepthChanges(load, context1, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(2, slot_index, false), context2, start);
t.CheckContextInputAndDepthChanges(load, context0, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(2, slot_index, true), context2, start);
t.CheckContextInputAndDepthChanges(load, context0, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(3, slot_index, false), context2, start);
t.CheckContextInputAndDepthChanges(load, context0, 1);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(3, slot_index, true), context2, start);
t.CheckContextInputAndDepthChanges(load, context0, 1);
}
}
TEST(ReduceJSLoadContext2) {
// The graph's context chain ends in a constant context (context_object1),
// which has another outer context (context_object0).
//
// context2 <-- context1 <-- context0 (=
// HeapConstantNoHole(context_object1))
// context_object1 <~~ context_object0
ContextSpecializationTester t(Nothing<OuterContext>());
Node* start = t.graph()->NewNode(t.common()->Start(0));
t.graph()->SetStart(start);
ScopeInfoRef empty = t.broker()->empty_scope_info();
const i::compiler::Operator* create_function_context =
t.javascript()->CreateFunctionContext(empty, 42, FUNCTION_SCOPE);
DirectHandle<HeapObject> slot_value0 =
t.CanonicalHandle(*t.factory()->InternalizeUtf8String("0"));
DirectHandle<HeapObject> slot_value1 =
t.CanonicalHandle(*t.factory()->InternalizeUtf8String("1"));
DirectHandle<Context> context_object0 =
t.CanonicalHandle(*t.factory()->NewNativeContext());
Handle<Context> context_object1 =
NewCanonicalContextForTesting(t, context_object0);
context_object0->set_extension(*slot_value0);
context_object1->set_extension(*slot_value1);
Node* context0 = t.jsgraph()->ConstantNoHole(
MakeRef(t.broker(), context_object1), t.broker());
Node* context1 =
t.graph()->NewNode(create_function_context, context0, start, start);
Node* context2 =
t.graph()->NewNode(create_function_context, context1, start, start);
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(0, slot_index, false), context2, start);
CHECK(!t.spec()->Reduce(load).Changed());
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(0, slot_index, true), context2, start);
CHECK(!t.spec()->Reduce(load).Changed());
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(1, slot_index, false), context2, start);
t.CheckContextInputAndDepthChanges(load, context1, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(1, slot_index, true), context2, start);
t.CheckContextInputAndDepthChanges(load, context1, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(2, slot_index, false), context2, start);
t.CheckContextInputAndDepthChanges(load, context0, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(2, Context::EXTENSION_INDEX, true),
context2, start);
t.CheckChangesToValue(load, slot_value1);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(3, slot_index, false), context2, start);
t.CheckContextInputAndDepthChanges(load, context_object0, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(3, Context::EXTENSION_INDEX, true),
context2, start);
t.CheckChangesToValue(load, slot_value0);
}
}
TEST(ReduceJSLoadContext3) {
// Like in ReduceJSLoadContext1, the graph's context chain ends in the
// incoming context parameter. However, this time we provide a concrete
// context for this parameter as the "specialization context". We choose
// context_object2 from ReduceJSLoadContext2 for this, so almost all test
// expectations are the same as in ReduceJSLoadContext2.
HandleAndZoneScope handle_zone_scope;
auto isolate = handle_zone_scope.main_isolate();
auto factory = isolate->factory();
DirectHandle<HeapObject> slot_value0 = factory->InternalizeUtf8String("0");
DirectHandle<HeapObject> slot_value1 = factory->InternalizeUtf8String("1");
CanonicalHandles canonical_handles(isolate, handle_zone_scope.main_zone());
DirectHandle<Context> context_object0 =
canonical_handles.Create(factory->NewNativeContext());
Handle<Context> context_object1 =
canonical_handles.Create(NewContextForTesting(isolate, context_object0));
context_object0->set_extension(*slot_value0);
context_object1->set_extension(*slot_value1);
ContextSpecializationTester t(Just(OuterContext(context_object1, 0)),
std::move(canonical_handles));
Node* start = t.graph()->NewNode(t.common()->Start(2));
t.graph()->SetStart(start);
ScopeInfoRef empty = t.broker()->empty_scope_info();
const i::compiler::Operator* create_function_context =
t.javascript()->CreateFunctionContext(empty, 42, FUNCTION_SCOPE);
Node* context0 = t.graph()->NewNode(t.common()->Parameter(0), start);
Node* context1 =
t.graph()->NewNode(create_function_context, context0, start, start);
Node* context2 =
t.graph()->NewNode(create_function_context, context1, start, start);
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(0, slot_index, false), context2, start);
CHECK(!t.spec()->Reduce(load).Changed());
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(0, slot_index, true), context2, start);
CHECK(!t.spec()->Reduce(load).Changed());
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(1, slot_index, false), context2, start);
t.CheckContextInputAndDepthChanges(load, context1, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(1, slot_index, true), context2, start);
t.CheckContextInputAndDepthChanges(load, context1, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(2, slot_index, false), context2, start);
t.CheckContextInputAndDepthChanges(load, context_object1, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(2, Context::EXTENSION_INDEX, true),
context2, start);
t.CheckChangesToValue(load, slot_value1);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(3, slot_index, false), context2, start);
t.CheckContextInputAndDepthChanges(load, context_object0, 0);
}
{
Node* load = t.graph()->NewNode(
t.javascript()->LoadContext(3, Context::EXTENSION_INDEX, true),
context2, start);
t.CheckChangesToValue(load, slot_value0);
}
}
TEST(ReduceJSStoreContext0) {
ContextSpecializationTester t(Nothing<OuterContext>());
Node* start = t.graph()->NewNode(t.common()->Start(0));
t.graph()->SetStart(start);
// Make a context and initialize it a bit for this test.
Handle<Context> native = t.CanonicalHandle(*t.factory()->NewNativeContext());
DirectHandle<Context> subcontext1 = NewCanonicalContextForTesting(t, native);
Handle<Context> subcontext2 = NewCanonicalContextForTesting(t, subcontext1);
DirectHandle<Object> expected =
t.CanonicalHandle(*t.factory()->InternalizeUtf8String("gboy!"));
const int slot = 5;
native->set(slot, *expected);
Node* const_context =
t.jsgraph()->ConstantNoHole(MakeRef(t.broker(), native), t.broker());
Node* deep_const_context =
t.jsgraph()->ConstantNoHole(MakeRef(t.broker(), subcontext2), t.broker());
Node* param_context = t.graph()->NewNode(t.common()->Parameter(0), start);
{
// Mutable slot, constant context, depth = 0 => do nothing.
Node* load = t.graph()->NewNode(t.javascript()->StoreContext(0, 0),
const_context, const_context, start, start);
Reduction r = t.spec()->Reduce(load);
CHECK(!r.Changed());
}
{
// Mutable slot, non-constant context, depth = 0 => do nothing.
Node* load = t.graph()->NewNode(t.javascript()->StoreContext(0, 0),
param_context, param_context, start, start);
Reduction r = t.spec()->Reduce(load);
CHECK(!r.Changed());
}
{
// Immutable slot, constant context, depth = 0 => do nothing.
Node* load = t.graph()->NewNode(t.javascript()->StoreContext(0, slot),
const_context, const_context, start, start);
Reduction r = t.spec()->Reduce(load);
CHECK(!r.Changed());
}
{
// Mutable slot, constant context, depth > 0 => fold-in parent context.
Node* load = t.graph()->NewNode(
t.javascript()->StoreContext(2, Context::GLOBAL_EVAL_FUN_INDEX),
deep_const_context, deep_const_context, start, start);
Reduction r = t.spec()->Reduce(load);
CHECK(r.Changed());
Node* new_context_input = NodeProperties::GetContextInput(r.replacement());
CHECK_EQ(IrOpcode::kHeapConstant, new_context_input->opcode());
HeapObjectMatcher match(new_context_input);
CHECK_EQ(*native, Cast<Context>(*match.ResolvedValue()));
ContextAccess access = ContextAccessOf(r.replacement()->op());
CHECK_EQ(Context::GLOBAL_EVAL_FUN_INDEX, static_cast<int>(access.index()));
CHECK_EQ(0, static_cast<int>(access.depth()));
CHECK_EQ(false, access.immutable());
}
// Clean up so that verifiers don't complain.
native->set(slot, Smi::zero());
}
TEST(ReduceJSStoreContext1) {
ContextSpecializationTester t(Nothing<OuterContext>());
Node* start = t.graph()->NewNode(t.common()->Start(0));
t.graph()->SetStart(start);
ScopeInfoRef empty = t.broker()->empty_scope_info();
const i::compiler::Operator* create_function_context =
t.javascript()->CreateFunctionContext(empty, 42, FUNCTION_SCOPE);
Node* context0 = t.graph()->NewNode(t.common()->Parameter(0), start);
Node* context1 =
t.graph()->NewNode(create_function_context, context0, start, start);
Node* context2 =
t.graph()->NewNode(create_function_context, context1, start, start);
{
Node* store =
t.graph()->NewNode(t.javascript()->StoreContext(0, slot_index),
context2, context2, start, start);
CHECK(!t.spec()->Reduce(store).Changed());
}
{
Node* store =
t.graph()->NewNode(t.javascript()->StoreContext(1, slot_index),
context2, context2, start, start);
t.CheckContextInputAndDepthChanges(store, context1, 0);
}
{
Node* store =
t.graph()->NewNode(t.javascript()->StoreContext(2, slot_index),
context2, context2, start, start);
t.CheckContextInputAndDepthChanges(store, context0, 0);
}
{
Node* store =
t.graph()->NewNode(t.javascript()->StoreContext(3, slot_index),
context2, context2, start, start);
t.CheckContextInputAndDepthChanges(store, context0, 1);
}
}
TEST(ReduceJSStoreContext2) {
ContextSpecializationTester t(Nothing<OuterContext>());
Node* start = t.graph()->NewNode(t.common()->Start(0));
t.graph()->SetStart(start);
ScopeInfoRef empty = t.broker()->empty_scope_info();
const i::compiler::Operator* create_function_context =
t.javascript()->CreateFunctionContext(empty, 42, FUNCTION_SCOPE);
DirectHandle<HeapObject> slot_value0 =
t.CanonicalHandle(*t.factory()->InternalizeUtf8String("0"));
DirectHandle<HeapObject> slot_value1 =
t.CanonicalHandle(*t.factory()->InternalizeUtf8String("1"));
DirectHandle<Context> context_object0 =
t.CanonicalHandle(*t.factory()->NewNativeContext());
Handle<Context> context_object1 =
NewCanonicalContextForTesting(t, context_object0);
context_object0->set_extension(*slot_value0);
context_object1->set_extension(*slot_value1);
Node* context0 = t.jsgraph()->ConstantNoHole(
MakeRef(t.broker(), context_object1), t.broker());
Node* context1 =
t.graph()->NewNode(create_function_context, context0, start, start);
Node* context2 =
t.graph()->NewNode(create_function_context, context1, start, start);
{
Node* store = t.graph()->NewNode(
t.javascript()->StoreContext(0, Context::EXTENSION_INDEX), context2,
context2, start, start);
CHECK(!t.spec()->Reduce(store).Changed());
}
{
Node* store = t.graph()->NewNode(
t.javascript()->StoreContext(1, Context::EXTENSION_INDEX), context2,
context2, start, start);
t.CheckContextInputAndDepthChanges(store, context1, 0);
}
{
Node* store = t.graph()->NewNode(
t.javascript()->StoreContext(2, Context::EXTENSION_INDEX), context2,
context2, start, start);
t.CheckContextInputAndDepthChanges(store, context0, 0);
}
{
Node* store = t.graph()->NewNode(
t.javascript()->StoreContext(3, Context::EXTENSION_INDEX), context2,
context2, start, start);
t.CheckContextInputAndDepthChanges(store, context_object0, 0);
}
}
TEST(ReduceJSStoreContext3) {
HandleAndZoneScope handle_zone_scope;
auto isolate = handle_zone_scope.main_isolate();
auto factory = isolate->factory();
CanonicalHandles canonical_handles(isolate, handle_zone_scope.main_zone());
DirectHandle<HeapObject> slot_value0 =
canonical_handles.Create(factory->InternalizeUtf8String("0"));
DirectHandle<HeapObject> slot_value1 =
canonical_handles.Create(factory->InternalizeUtf8String("1"));
DirectHandle<Context> context_object0 =
canonical_handles.Create(factory->NewNativeContext());
Handle<Context> context_object1 =
canonical_handles.Create(NewContextForTesting(isolate, context_object0));
context_object0->set_extension(*slot_value0);
context_object1->set_extension(*slot_value1);
ContextSpecializationTester t(Just(OuterContext(context_object1, 0)),
std::move(canonical_handles));
Node* start = t.graph()->NewNode(t.common()->Start(2));
t.graph()->SetStart(start);
ScopeInfoRef empty = t.broker()->empty_scope_info();
const i::compiler::Operator* create_function_context =
t.javascript()->CreateFunctionContext(empty, 42, FUNCTION_SCOPE);
Node* context0 = t.graph()->NewNode(t.common()->Parameter(0), start);
Node* context1 =
t.graph()->NewNode(create_function_context, context0, start, start);
Node* context2 =
t.graph()->NewNode(create_function_context, context1, start, start);
{
Node* store = t.graph()->NewNode(
t.javascript()->StoreContext(0, Context::EXTENSION_INDEX), context2,
context2, start, start);
CHECK(!t.spec()->Reduce(store).Changed());
}
{
Node* store = t.graph()->NewNode(
t.javascript()->StoreContext(1, Context::EXTENSION_INDEX), context2,
context2, start, start);
t.CheckContextInputAndDepthChanges(store, context1, 0);
}
{
Node* store = t.graph()->NewNode(
t.javascript()->StoreContext(2, Context::EXTENSION_INDEX), context2,
context2, start, start);
t.CheckContextInputAndDepthChanges(store, context_object1, 0);
}
{
Node* store = t.graph()->NewNode(
t.javascript()->StoreContext(3, Context::EXTENSION_INDEX), context2,
context2, start, start);
t.CheckContextInputAndDepthChanges(store, context_object0, 0);
}
}
TEST(SpecializeJSFunction_ToConstant1) {
FunctionTester T(
"(function() { var x = 1; function inc(a)"
" { return a + x; } return inc; })()");
T.CheckCall(1.0, 0.0, 0.0);
T.CheckCall(2.0, 1.0, 0.0);
T.CheckCall(2.1, 1.1, 0.0);
}
TEST(SpecializeJSFunction_ToConstant2) {
FunctionTester T(
"(function() { var x = 1.5; var y = 2.25; var z = 3.75;"
" function f(a) { return a - x + y - z; } return f; })()");
T.CheckCall(-3.0, 0.0, 0.0);
T.CheckCall(-2.0, 1.0, 0.0);
T.CheckCall(-1.9, 1.1, 0.0);
}
TEST(SpecializeJSFunction_ToConstant3) {
FunctionTester T(
"(function() { var x = -11.5; function inc()"
" { return (function(a) { return a + x; }); }"
" return inc(); })()");
T.CheckCall(-11.5, 0.0, 0.0);
T.CheckCall(-10.5, 1.0, 0.0);
T.CheckCall(-10.4, 1.1, 0.0);
}
TEST(SpecializeJSFunction_ToConstant_uninit) {
{
FunctionTester T(
"(function() { if (false) { var x = 1; } function inc(a)"
" { return x; } return inc; })()"); // x is undefined!
i::Isolate* isolate = CcTest::i_isolate();
CHECK(IsUndefined(*T.Call(T.Val(0.0), T.Val(0.0)).ToHandleChecked(),
isolate));
CHECK(IsUndefined(*T.Call(T.Val(2.0), T.Val(0.0)).ToHandleChecked(),
isolate));
CHECK(IsUndefined(*T.Call(T.Val(-2.1), T.Val(0.0)).ToHandleChecked(),
isolate));
}
{
FunctionTester T(
"(function() { if (false) { var x = 1; } function inc(a)"
" { return a + x; } return inc; })()"); // x is undefined!
CHECK(IsNaN(*T.Call(T.Val(0.0), T.Val(0.0)).ToHandleChecked()));
CHECK(IsNaN(*T.Call(T.Val(2.0), T.Val(0.0)).ToHandleChecked()));
CHECK(IsNaN(*T.Call(T.Val(-2.1), T.Val(0.0)).ToHandleChecked()));
}
}
} // namespace compiler
} // namespace internal
} // namespace v8

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,158 @@
// Copyright 2014 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 "include/v8-function.h"
#include "src/api/api-inl.h"
#include "src/codegen/code-factory.h"
#include "src/codegen/compiler.h"
#include "src/codegen/optimized-compilation-info.h"
#include "src/codegen/script-details.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/linkage.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node.h"
#include "src/compiler/operator.h"
#include "src/compiler/pipeline.h"
#include "src/compiler/schedule.h"
#include "src/compiler/turbofan-graph.h"
#include "src/objects/objects-inl.h"
#include "src/parsing/parse-info.h"
#include "src/zone/zone.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace compiler {
static Operator dummy_operator(IrOpcode::kParameter, Operator::kNoWrite,
"dummy", 0, 0, 0, 0, 0, 0);
// So we can get a real JS function.
static Handle<JSFunction> Compile(const char* source) {
Isolate* isolate = CcTest::i_isolate();
Handle<String> source_code = isolate->factory()
->NewStringFromUtf8(base::CStrVector(source))
.ToHandleChecked();
ScriptCompiler::CompilationDetails compilation_details;
DirectHandle<SharedFunctionInfo> shared =
Compiler::GetSharedFunctionInfoForScript(
isolate, source_code, ScriptDetails(),
v8::ScriptCompiler::kNoCompileOptions,
ScriptCompiler::kNoCacheNoReason, NOT_NATIVES_CODE,
&compilation_details)
.ToHandleChecked();
return Factory::JSFunctionBuilder{isolate, shared, isolate->native_context()}
.Build();
}
TEST(TestLinkageCreate) {
HandleAndZoneScope handles;
Handle<JSFunction> function = Compile("a + b");
Handle<SharedFunctionInfo> shared(function->shared(), handles.main_isolate());
OptimizedCompilationInfo info(handles.main_zone(), function->GetIsolate(),
shared, function, CodeKind::TURBOFAN_JS);
auto call_descriptor = Linkage::ComputeIncoming(info.zone(), &info);
CHECK(call_descriptor);
}
TEST(TestLinkageJSFunctionIncoming) {
const char* sources[] = {"(function() { })", "(function(a) { })",
"(function(a,b) { })", "(function(a,b,c) { })"};
for (int i = 0; i < 3; i++) {
HandleAndZoneScope handles;
Handle<JSFunction> function = Cast<JSFunction>(v8::Utils::OpenHandle(
*v8::Local<v8::Function>::Cast(CompileRun(sources[i]))));
Handle<SharedFunctionInfo> shared(function->shared(),
handles.main_isolate());
OptimizedCompilationInfo info(handles.main_zone(), function->GetIsolate(),
shared, function, CodeKind::TURBOFAN_JS);
auto call_descriptor = Linkage::ComputeIncoming(info.zone(), &info);
CHECK(call_descriptor);
CHECK_EQ(1 + i, static_cast<int>(call_descriptor->JSParameterCount()));
CHECK_EQ(1, static_cast<int>(call_descriptor->ReturnCount()));
CHECK_EQ(Operator::kNoProperties, call_descriptor->properties());
CHECK_EQ(true, call_descriptor->IsJSFunctionCall());
}
}
TEST(TestLinkageJSCall) {
HandleAndZoneScope handles;
Handle<JSFunction> function = Compile("a + c");
Handle<SharedFunctionInfo> shared(function->shared(), handles.main_isolate());
OptimizedCompilationInfo info(handles.main_zone(), function->GetIsolate(),
shared, function, CodeKind::TURBOFAN_JS);
for (int i = 0; i < 32; i++) {
auto call_descriptor = Linkage::GetJSCallDescriptor(
info.zone(), false, i, CallDescriptor::kNoFlags);
CHECK(call_descriptor);
CHECK_EQ(i, static_cast<int>(call_descriptor->JSParameterCount()));
CHECK_EQ(1, static_cast<int>(call_descriptor->ReturnCount()));
CHECK_EQ(Operator::kNoProperties, call_descriptor->properties());
CHECK_EQ(true, call_descriptor->IsJSFunctionCall());
}
}
TEST(TestLinkageRuntimeCall) {
// TODO(titzer): test linkage creation for outgoing runtime calls.
}
TEST(TestLinkageStubCall) {
// TODO(bbudge) Add tests for FP registers.
Isolate* isolate = CcTest::InitIsolateOnce();
Zone zone(isolate->allocator(), ZONE_NAME);
Callable callable = Builtins::CallableFor(isolate, Builtin::kToNumber);
OptimizedCompilationInfo info(base::ArrayVector("test"), &zone,
CodeKind::FOR_TESTING);
auto call_descriptor = Linkage::GetStubCallDescriptor(
&zone, callable.descriptor(), 0, CallDescriptor::kNoFlags,
Operator::kNoProperties);
CHECK(call_descriptor);
CHECK_EQ(0, static_cast<int>(call_descriptor->ParameterSlotCount()));
CHECK_EQ(1, static_cast<int>(call_descriptor->ReturnCount()));
CHECK_EQ(Operator::kNoProperties, call_descriptor->properties());
CHECK_EQ(false, call_descriptor->IsJSFunctionCall());
CHECK_EQ(call_descriptor->GetParameterType(0), MachineType::AnyTagged());
CHECK_EQ(call_descriptor->GetReturnType(0), MachineType::AnyTagged());
// TODO(titzer): test linkage creation for outgoing stub calls.
}
#if V8_ENABLE_WEBASSEMBLY
TEST(TestFPLinkageStubCall) {
Isolate* isolate = CcTest::InitIsolateOnce();
Zone zone(isolate->allocator(), ZONE_NAME);
Callable callable =
Builtins::CallableFor(isolate, Builtin::kWasmFloat64ToNumber);
OptimizedCompilationInfo info(base::ArrayVector("test"), &zone,
CodeKind::FOR_TESTING);
auto call_descriptor = Linkage::GetStubCallDescriptor(
&zone, callable.descriptor(), 0, CallDescriptor::kNoFlags,
Operator::kNoProperties);
CHECK(call_descriptor);
CHECK_EQ(0, static_cast<int>(call_descriptor->ParameterSlotCount()));
CHECK_EQ(1, static_cast<int>(call_descriptor->ParameterCount()));
CHECK_EQ(1, static_cast<int>(call_descriptor->ReturnCount()));
CHECK_EQ(Operator::kNoProperties, call_descriptor->properties());
CHECK_EQ(false, call_descriptor->IsJSFunctionCall());
CHECK_EQ(call_descriptor->GetInputType(1), MachineType::Float64());
CHECK(call_descriptor->GetInputLocation(1).IsRegister());
CHECK_EQ(call_descriptor->GetReturnType(0), MachineType::AnyTagged());
CHECK(call_descriptor->GetReturnLocation(0).IsRegister());
CHECK_EQ(call_descriptor->GetReturnLocation(0).GetLocation(),
kReturnRegister0.code());
}
#endif // V8_ENABLE_WEBASSEMBLY
} // namespace compiler
} // namespace internal
} // namespace v8

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,887 @@
// Copyright 2014 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/overflowing-math.h"
#include "src/base/utils/random-number-generator.h"
#include "src/codegen/tick-counter.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/machine-operator-reducer.h"
#include "src/compiler/operator-properties.h"
#include "src/compiler/turbofan-typer.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/common/value-helper.h"
namespace v8 {
namespace internal {
namespace compiler {
template <typename T>
const Operator* NewConstantOperator(CommonOperatorBuilder* common,
T value);
template <>
const Operator* NewConstantOperator<int32_t>(CommonOperatorBuilder* common,
int32_t value) {
return common->Int32Constant(value);
}
template <>
const Operator* NewConstantOperator<int64_t>(CommonOperatorBuilder* common,
int64_t value) {
return common->Int64Constant(value);
}
template <>
const Operator* NewConstantOperator<double>(CommonOperatorBuilder* common,
double value) {
return common->Float64Constant(value);
}
template <>
const Operator* NewConstantOperator<float>(CommonOperatorBuilder* common,
float value) {
return common->Float32Constant(value);
}
template <typename T>
T ValueOfOperator(const Operator* op);
template <>
int32_t ValueOfOperator<int32_t>(const Operator* op) {
CHECK_EQ(IrOpcode::kInt32Constant, op->opcode());
return OpParameter<int32_t>(op);
}
template <>
int64_t ValueOfOperator<int64_t>(const Operator* op) {
CHECK_EQ(IrOpcode::kInt64Constant, op->opcode());
return OpParameter<int64_t>(op);
}
template <>
float ValueOfOperator<float>(const Operator* op) {
CHECK_EQ(IrOpcode::kFloat32Constant, op->opcode());
return OpParameter<float>(op);
}
template <>
double ValueOfOperator<double>(const Operator* op) {
CHECK_EQ(IrOpcode::kFloat64Constant, op->opcode());
return OpParameter<double>(op);
}
class ReducerTester : public HandleAndZoneScope {
public:
explicit ReducerTester(int num_parameters = 0,
MachineOperatorBuilder::Flags flags =
MachineOperatorBuilder::kAllOptionalOps)
: HandleAndZoneScope(kCompressGraphZone),
isolate(main_isolate()),
binop(nullptr),
unop(nullptr),
machine(main_zone(), MachineType::PointerRepresentation(), flags),
common(main_zone()),
graph(main_zone()),
javascript(main_zone()),
jsgraph(isolate, &graph, &common, &javascript, nullptr, &machine),
maxuint32(Constant<int32_t>(kMaxUInt32)),
graph_reducer(main_zone(), &graph, &tick_counter, nullptr,
jsgraph.Dead()) {
Node* s = graph.NewNode(common.Start(num_parameters));
graph.SetStart(s);
}
Isolate* isolate;
TickCounter tick_counter;
const Operator* binop;
const Operator* unop;
MachineOperatorBuilder machine;
CommonOperatorBuilder common;
TFGraph graph;
JSOperatorBuilder javascript;
JSGraph jsgraph;
Node* maxuint32;
GraphReducer graph_reducer;
template <typename T>
Node* Constant(T value) {
return graph.NewNode(NewConstantOperator<T>(&common, value));
}
template <typename T>
const T ValueOf(const Operator* op) {
return ValueOfOperator<T>(op);
}
// Check that the reduction of this binop applied to constants {a} and {b}
// yields the {expect} value.
template <typename T>
void CheckFoldBinop(T expect, T a, T b) {
CheckFoldBinop<T>(expect, Constant<T>(a), Constant<T>(b));
}
// Check that the reduction of this binop applied to {a} and {b} yields
// the {expect} value.
template <typename T>
void CheckFoldBinop(T expect, Node* a, Node* b) {
CHECK(binop);
Node* n = CreateBinopNode(a, b);
MachineOperatorReducer reducer(
&graph_reducer, &jsgraph,
MachineOperatorReducer::kPropagateSignallingNan);
Reduction reduction = reducer.Reduce(n);
CHECK(reduction.Changed());
CHECK_NE(n, reduction.replacement());
// Deal with NaNs.
if (expect == expect) {
// We do not expect a NaN, check for equality.
CHECK_EQ(expect, ValueOf<T>(reduction.replacement()->op()));
} else {
// Check for NaN.
T result = ValueOf<T>(reduction.replacement()->op());
CHECK_NE(result, result);
}
}
// Check that the reduction of this binop applied to {a} and {b} yields
// the {expect} node.
void CheckBinop(Node* expect, Node* a, Node* b) {
CHECK(binop);
Node* n = CreateBinopNode(a, b);
MachineOperatorReducer reducer(
&graph_reducer, &jsgraph,
MachineOperatorReducer::kPropagateSignallingNan);
Reduction reduction = reducer.Reduce(n);
CHECK(reduction.Changed());
CHECK_EQ(expect, reduction.replacement());
}
// Check that the reduction of this binop applied to {left} and {right} yields
// this binop applied to {left_expect} and {right_expect}.
void CheckFoldBinop(Node* left_expect, Node* right_expect, Node* left,
Node* right) {
CHECK(binop);
Node* n = CreateBinopNode(left, right);
MachineOperatorReducer reducer(
&graph_reducer, &jsgraph,
MachineOperatorReducer::kPropagateSignallingNan);
Reduction reduction = reducer.Reduce(n);
CHECK(reduction.Changed());
CHECK_EQ(binop, reduction.replacement()->op());
CHECK_EQ(left_expect, reduction.replacement()->InputAt(0));
CHECK_EQ(right_expect, reduction.replacement()->InputAt(1));
}
// Check that the reduction of this binop applied to {left} and {right} yields
// the {op_expect} applied to {left_expect} and {right_expect}.
template <typename T>
void CheckFoldBinop(T left_expect, const Operator* op_expect,
Node* right_expect, Node* left, Node* right) {
CHECK(binop);
Node* n = CreateBinopNode(left, right);
MachineOperatorReducer reducer(
&graph_reducer, &jsgraph,
MachineOperatorReducer::kPropagateSignallingNan);
Reduction r = reducer.Reduce(n);
CHECK(r.Changed());
CHECK_EQ(op_expect->opcode(), r.replacement()->op()->opcode());
CHECK_EQ(left_expect, ValueOf<T>(r.replacement()->InputAt(0)->op()));
CHECK_EQ(right_expect, r.replacement()->InputAt(1));
}
// Check that the reduction of this binop applied to {left} and {right} yields
// the {op_expect} applied to {left_expect} and {right_expect}.
template <typename T>
void CheckFoldBinop(Node* left_expect, const Operator* op_expect,
T right_expect, Node* left, Node* right) {
CHECK(binop);
Node* n = CreateBinopNode(left, right);
MachineOperatorReducer reducer(
&graph_reducer, &jsgraph,
MachineOperatorReducer::kPropagateSignallingNan);
Reduction r = reducer.Reduce(n);
CHECK(r.Changed());
CHECK_EQ(op_expect->opcode(), r.replacement()->op()->opcode());
CHECK_EQ(OperatorProperties::GetTotalInputCount(op_expect),
r.replacement()->InputCount());
CHECK_EQ(left_expect, r.replacement()->InputAt(0));
CHECK_EQ(right_expect, ValueOf<T>(r.replacement()->InputAt(1)->op()));
}
// Check that if the given constant appears on the left, the reducer will
// swap it to be on the right.
template <typename T>
void CheckPutConstantOnRight(T constant) {
// TODO(titzer): CHECK(binop->HasProperty(Operator::kCommutative));
Node* p = Parameter();
Node* k = Constant<T>(constant);
{
Node* n = CreateBinopNode(k, p);
MachineOperatorReducer reducer(
&graph_reducer, &jsgraph,
MachineOperatorReducer::kPropagateSignallingNan);
Reduction reduction = reducer.Reduce(n);
CHECK(!reduction.Changed() || reduction.replacement() == n);
CHECK_EQ(p, n->InputAt(0));
CHECK_EQ(k, n->InputAt(1));
}
{
Node* n = CreateBinopNode(p, k);
MachineOperatorReducer reducer(
&graph_reducer, &jsgraph,
MachineOperatorReducer::kPropagateSignallingNan);
Reduction reduction = reducer.Reduce(n);
CHECK(!reduction.Changed());
CHECK_EQ(p, n->InputAt(0));
CHECK_EQ(k, n->InputAt(1));
}
}
// Check that if the given constant appears on the left, the reducer will
// *NOT* swap it to be on the right.
template <typename T>
void CheckDontPutConstantOnRight(T constant) {
CHECK(!binop->HasProperty(Operator::kCommutative));
Node* p = Parameter();
Node* k = Constant<T>(constant);
Node* n = CreateBinopNode(k, p);
MachineOperatorReducer reducer(
&graph_reducer, &jsgraph,
MachineOperatorReducer::kPropagateSignallingNan);
Reduction reduction = reducer.Reduce(n);
CHECK(!reduction.Changed());
CHECK_EQ(k, n->InputAt(0));
CHECK_EQ(p, n->InputAt(1));
}
Node* Parameter(int32_t index = 0) {
return graph.NewNode(common.Parameter(index), graph.start());
}
private:
Node* CreateBinopNode(Node* left, Node* right) {
if (binop->ControlInputCount() > 0) {
return graph.NewNode(binop, left, right, graph.start());
} else {
return graph.NewNode(binop, left, right);
}
}
};
TEST(ReduceWord32And) {
ReducerTester R;
R.binop = R.machine.Word32And();
FOR_INT32_INPUTS(x) {
FOR_INT32_INPUTS(y) { R.CheckFoldBinop<int32_t>(x & y, x, y); }
}
R.CheckPutConstantOnRight(33);
R.CheckPutConstantOnRight(44000);
Node* x = R.Parameter();
Node* zero = R.Constant<int32_t>(0);
Node* minus_1 = R.Constant<int32_t>(-1);
R.CheckBinop(zero, x, zero); // x & 0 => 0
R.CheckBinop(zero, zero, x); // 0 & x => 0
R.CheckBinop(x, x, minus_1); // x & -1 => 0
R.CheckBinop(x, minus_1, x); // -1 & x => 0
R.CheckBinop(x, x, x); // x & x => x
}
TEST(ReduceWord32Or) {
ReducerTester R;
R.binop = R.machine.Word32Or();
FOR_INT32_INPUTS(x) {
FOR_INT32_INPUTS(y) { R.CheckFoldBinop<int32_t>(x | y, x, y); }
}
R.CheckPutConstantOnRight(36);
R.CheckPutConstantOnRight(44001);
Node* x = R.Parameter();
Node* zero = R.Constant<int32_t>(0);
Node* minus_1 = R.Constant<int32_t>(-1);
R.CheckBinop(x, x, zero); // x & 0 => x
R.CheckBinop(x, zero, x); // 0 & x => x
R.CheckBinop(minus_1, x, minus_1); // x & -1 => -1
R.CheckBinop(minus_1, minus_1, x); // -1 & x => -1
R.CheckBinop(x, x, x); // x & x => x
}
TEST(ReduceWord32Xor) {
ReducerTester R;
R.binop = R.machine.Word32Xor();
FOR_INT32_INPUTS(x) {
FOR_INT32_INPUTS(y) { R.CheckFoldBinop<int32_t>(x ^ y, x, y); }
}
R.CheckPutConstantOnRight(39);
R.CheckPutConstantOnRight(4403);
Node* x = R.Parameter();
Node* zero = R.Constant<int32_t>(0);
R.CheckBinop(x, x, zero); // x ^ 0 => x
R.CheckBinop(x, zero, x); // 0 ^ x => x
R.CheckFoldBinop<int32_t>(0, x, x); // x ^ x => 0
}
TEST(ReduceWord32Shl) {
ReducerTester R;
R.binop = R.machine.Word32Shl();
// TODO(titzer): out of range shifts
FOR_INT32_INPUTS(x) {
for (int y = 0; y < 32; y++) {
R.CheckFoldBinop<int32_t>(base::ShlWithWraparound(x, y), x, y);
}
}
R.CheckDontPutConstantOnRight(44);
Node* x = R.Parameter();
Node* zero = R.Constant<int32_t>(0);
R.CheckBinop(x, x, zero); // x << 0 => x
}
TEST(ReduceWord64Shl) {
ReducerTester R;
R.binop = R.machine.Word64Shl();
FOR_INT64_INPUTS(x) {
for (int64_t y = 0; y < 64; y++) {
R.CheckFoldBinop<int64_t>(base::ShlWithWraparound(x, y), x, y);
}
}
R.CheckDontPutConstantOnRight(44);
Node* x = R.Parameter();
Node* zero = R.Constant<int64_t>(0);
R.CheckBinop(x, x, zero); // x << 0 => x
}
TEST(ReduceWord32Shr) {
ReducerTester R;
R.binop = R.machine.Word32Shr();
// TODO(titzer): test out of range shifts
FOR_UINT32_INPUTS(x) {
for (uint32_t y = 0; y < 32; y++) {
R.CheckFoldBinop<int32_t>(x >> y, x, y);
}
}
R.CheckDontPutConstantOnRight(44);
Node* x = R.Parameter();
Node* zero = R.Constant<int32_t>(0);
R.CheckBinop(x, x, zero); // x >>> 0 => x
}
TEST(ReduceWord64Shr) {
ReducerTester R;
R.binop = R.machine.Word64Shr();
FOR_UINT64_INPUTS(x) {
for (uint64_t y = 0; y < 64; y++) {
R.CheckFoldBinop<int64_t>(x >> y, x, y);
}
}
R.CheckDontPutConstantOnRight(44);
Node* x = R.Parameter();
Node* zero = R.Constant<int64_t>(0);
R.CheckBinop(x, x, zero); // x >>> 0 => x
}
TEST(ReduceWord32Sar) {
ReducerTester R;
R.binop = R.machine.Word32Sar();
// TODO(titzer): test out of range shifts
FOR_INT32_INPUTS(x) {
for (int32_t y = 0; y < 32; y++) {
R.CheckFoldBinop<int32_t>(x >> y, x, y);
}
}
R.CheckDontPutConstantOnRight(44);
Node* x = R.Parameter();
Node* zero = R.Constant<int32_t>(0);
R.CheckBinop(x, x, zero); // x >> 0 => x
}
TEST(ReduceWord64Sar) {
ReducerTester R;
R.binop = R.machine.Word64Sar();
FOR_INT64_INPUTS(x) {
for (int64_t y = 0; y < 64; y++) {
R.CheckFoldBinop<int64_t>(x >> y, x, y);
}
}
R.CheckDontPutConstantOnRight(44);
Node* x = R.Parameter();
Node* zero = R.Constant<int64_t>(0);
R.CheckBinop(x, x, zero); // x >> 0 => x
}
static void CheckJsShift(ReducerTester* R) {
CHECK(R->machine.Word32ShiftIsSafe());
Node* x = R->Parameter(0);
Node* y = R->Parameter(1);
Node* thirty_one = R->Constant<int32_t>(0x1F);
Node* y_and_thirty_one =
R->graph.NewNode(R->machine.Word32And(), y, thirty_one);
// If the underlying machine shift instructions 'and' their right operand
// with 0x1F then: x << (y & 0x1F) => x << y
R->CheckFoldBinop(x, y, x, y_and_thirty_one);
}
TEST(ReduceJsShifts) {
ReducerTester R(0, MachineOperatorBuilder::kWord32ShiftIsSafe);
R.binop = R.machine.Word32Shl();
CheckJsShift(&R);
R.binop = R.machine.Word32Shr();
CheckJsShift(&R);
R.binop = R.machine.Word32Sar();
CheckJsShift(&R);
}
TEST(Word32Equal) {
ReducerTester R;
R.binop = R.machine.Word32Equal();
FOR_INT32_INPUTS(x) {
FOR_INT32_INPUTS(y) { R.CheckFoldBinop<int32_t>(x == y ? 1 : 0, x, y); }
}
R.CheckPutConstantOnRight(48);
R.CheckPutConstantOnRight(-48);
Node* x = R.Parameter(0);
Node* y = R.Parameter(1);
Node* zero = R.Constant<int32_t>(0);
Node* sub = R.graph.NewNode(R.machine.Int32Sub(), x, y);
R.CheckFoldBinop<int32_t>(1, x, x); // x == x => 1
R.CheckFoldBinop(x, y, sub, zero); // x - y == 0 => x == y
R.CheckFoldBinop(x, y, zero, sub); // 0 == x - y => x == y
}
TEST(ReduceInt32Add) {
ReducerTester R;
R.binop = R.machine.Int32Add();
FOR_INT32_INPUTS(x) {
FOR_INT32_INPUTS(y) {
R.CheckFoldBinop<int32_t>(base::AddWithWraparound(x, y), x, y);
}
}
R.CheckPutConstantOnRight(41);
R.CheckPutConstantOnRight(4407);
Node* x = R.Parameter();
Node* zero = R.Constant<int32_t>(0);
R.CheckBinop(x, x, zero); // x + 0 => x
R.CheckBinop(x, zero, x); // 0 + x => x
}
TEST(ReduceInt64Add) {
ReducerTester R;
R.binop = R.machine.Int64Add();
FOR_INT64_INPUTS(x) {
FOR_INT64_INPUTS(y) {
R.CheckFoldBinop<int64_t>(base::AddWithWraparound(x, y), x, y);
}
}
R.CheckPutConstantOnRight(41);
Node* x = R.Parameter();
Node* zero = R.Constant<int64_t>(0);
R.CheckBinop(x, x, zero); // x + 0 => x
R.CheckBinop(x, zero, x); // 0 + x => x
}
TEST(ReduceInt32Sub) {
ReducerTester R;
R.binop = R.machine.Int32Sub();
FOR_INT32_INPUTS(x) {
FOR_INT32_INPUTS(y) {
R.CheckFoldBinop<int32_t>(base::SubWithWraparound(x, y), x, y);
}
}
R.CheckDontPutConstantOnRight(412);
Node* x = R.Parameter();
Node* zero = R.Constant<int32_t>(0);
R.CheckBinop(x, x, zero); // x - 0 => x
}
TEST(ReduceInt64Sub) {
ReducerTester R;
R.binop = R.machine.Int64Sub();
FOR_INT64_INPUTS(x) {
FOR_INT64_INPUTS(y) {
R.CheckFoldBinop<int64_t>(base::SubWithWraparound(x, y), x, y);
}
}
R.CheckDontPutConstantOnRight(42);
Node* x = R.Parameter();
Node* zero = R.Constant<int64_t>(0);
R.CheckBinop(x, x, zero); // x - 0 => x
R.CheckFoldBinop<int64_t>(0, x, x); // x - x => 0
Node* k = R.Constant<int64_t>(6);
R.CheckFoldBinop<int64_t>(x, R.machine.Int64Add(), -6, x,
k); // x - K => x + -K
}
TEST(ReduceInt32Mul) {
ReducerTester R;
R.binop = R.machine.Int32Mul();
FOR_INT32_INPUTS(x) {
FOR_INT32_INPUTS(y) {
R.CheckFoldBinop<int32_t>(base::MulWithWraparound(x, y), x, y);
}
}
R.CheckPutConstantOnRight(4111);
R.CheckPutConstantOnRight(-4407);
Node* x = R.Parameter();
Node* zero = R.Constant<int32_t>(0);
Node* one = R.Constant<int32_t>(1);
Node* minus_one = R.Constant<int32_t>(-1);
R.CheckBinop(zero, x, zero); // x * 0 => 0
R.CheckBinop(zero, zero, x); // 0 * x => 0
R.CheckBinop(x, x, one); // x * 1 => x
R.CheckBinop(x, one, x); // 1 * x => x
R.CheckFoldBinop<int32_t>(0, R.machine.Int32Sub(), x, minus_one,
x); // -1 * x => 0 - x
R.CheckFoldBinop<int32_t>(0, R.machine.Int32Sub(), x, x,
minus_one); // x * -1 => 0 - x
for (int32_t n = 1; n < 31; ++n) {
Node* multiplier = R.Constant<int32_t>(1 << n);
R.CheckFoldBinop<int32_t>(x, R.machine.Word32Shl(), n, x,
multiplier); // x * 2^n => x << n
R.CheckFoldBinop<int32_t>(x, R.machine.Word32Shl(), n, multiplier,
x); // 2^n * x => x << n
}
}
TEST(ReduceInt32Div) {
ReducerTester R;
R.binop = R.machine.Int32Div();
FOR_INT32_INPUTS(x) {
FOR_INT32_INPUTS(y) {
if (y == 0) continue; // TODO(titzer): test / 0
int32_t r = y == -1 ? base::NegateWithWraparound(x)
: x / y; // INT_MIN / -1 may explode in C
R.CheckFoldBinop<int32_t>(r, x, y);
}
}
R.CheckDontPutConstantOnRight(41111);
R.CheckDontPutConstantOnRight(-44071);
Node* x = R.Parameter();
Node* one = R.Constant<int32_t>(1);
Node* minus_one = R.Constant<int32_t>(-1);
R.CheckBinop(x, x, one); // x / 1 => x
// TODO(titzer): // 0 / x => 0 if x != 0
// TODO(titzer): // x / 2^n => x >> n and round
R.CheckFoldBinop<int32_t>(0, R.machine.Int32Sub(), x, x,
minus_one); // x / -1 => 0 - x
}
TEST(ReduceUint32Div) {
ReducerTester R;
R.binop = R.machine.Uint32Div();
FOR_UINT32_INPUTS(x) {
FOR_UINT32_INPUTS(y) {
if (y == 0) continue; // TODO(titzer): test / 0
R.CheckFoldBinop<int32_t>(x / y, x, y);
}
}
R.CheckDontPutConstantOnRight(41311);
R.CheckDontPutConstantOnRight(-44371);
Node* x = R.Parameter();
Node* one = R.Constant<int32_t>(1);
R.CheckBinop(x, x, one); // x / 1 => x
// TODO(titzer): // 0 / x => 0 if x != 0
for (uint32_t n = 1; n < 32; ++n) {
Node* divisor = R.Constant<int32_t>(1u << n);
R.CheckFoldBinop<int32_t>(x, R.machine.Word32Shr(), n, x,
divisor); // x / 2^n => x >> n
}
}
TEST(ReduceInt32Mod) {
ReducerTester R;
R.binop = R.machine.Int32Mod();
FOR_INT32_INPUTS(x) {
FOR_INT32_INPUTS(y) {
if (y == 0) continue; // TODO(titzer): test % 0
int32_t r = y == -1 ? 0 : x % y; // INT_MIN % -1 may explode in C
R.CheckFoldBinop<int32_t>(r, x, y);
}
}
R.CheckDontPutConstantOnRight(413);
R.CheckDontPutConstantOnRight(-4401);
Node* x = R.Parameter();
Node* one = R.Constant<int32_t>(1);
R.CheckFoldBinop<int32_t>(0, x, one); // x % 1 => 0
// TODO(titzer): // x % 2^n => x & 2^n-1 and round
}
TEST(ReduceUint32Mod) {
ReducerTester R;
R.binop = R.machine.Uint32Mod();
FOR_UINT32_INPUTS(x) {
FOR_UINT32_INPUTS(y) {
if (y == 0) continue; // TODO(titzer): test x % 0
R.CheckFoldBinop<int32_t>(x % y, x, y);
}
}
R.CheckDontPutConstantOnRight(417);
R.CheckDontPutConstantOnRight(-4371);
Node* x = R.Parameter();
Node* one = R.Constant<int32_t>(1);
R.CheckFoldBinop<int32_t>(0, x, one); // x % 1 => 0
for (uint32_t n = 1; n < 32; ++n) {
Node* divisor = R.Constant<int32_t>(1u << n);
R.CheckFoldBinop<int32_t>(x, R.machine.Word32And(), (1u << n) - 1, x,
divisor); // x % 2^n => x & 2^n-1
}
}
TEST(ReduceInt32LessThan) {
ReducerTester R;
R.binop = R.machine.Int32LessThan();
FOR_INT32_INPUTS(x) {
FOR_INT32_INPUTS(y) { R.CheckFoldBinop<int32_t>(x < y ? 1 : 0, x, y); }
}
R.CheckDontPutConstantOnRight(41399);
R.CheckDontPutConstantOnRight(-440197);
Node* x = R.Parameter(0);
R.CheckFoldBinop<int32_t>(0, x, x); // x < x => 0
}
TEST(ReduceInt32LessThanOrEqual) {
ReducerTester R;
R.binop = R.machine.Int32LessThanOrEqual();
FOR_INT32_INPUTS(x) {
FOR_INT32_INPUTS(y) { R.CheckFoldBinop<int32_t>(x <= y ? 1 : 0, x, y); }
}
FOR_INT32_INPUTS(i) { R.CheckDontPutConstantOnRight<int32_t>(i); }
Node* x = R.Parameter(0);
R.CheckFoldBinop<int32_t>(1, x, x); // x <= x => 1
}
TEST(ReduceUint32LessThan) {
ReducerTester R;
R.binop = R.machine.Uint32LessThan();
FOR_UINT32_INPUTS(x) {
FOR_UINT32_INPUTS(y) { R.CheckFoldBinop<int32_t>(x < y ? 1 : 0, x, y); }
}
R.CheckDontPutConstantOnRight(41399);
R.CheckDontPutConstantOnRight(-440197);
Node* x = R.Parameter();
Node* max = R.maxuint32;
Node* zero = R.Constant<int32_t>(0);
R.CheckFoldBinop<int32_t>(0, max, x); // M < x => 0
R.CheckFoldBinop<int32_t>(0, x, zero); // x < 0 => 0
R.CheckFoldBinop<int32_t>(0, x, x); // x < x => 0
}
TEST(ReduceUint32LessThanOrEqual) {
ReducerTester R;
R.binop = R.machine.Uint32LessThanOrEqual();
FOR_UINT32_INPUTS(x) {
FOR_UINT32_INPUTS(y) { R.CheckFoldBinop<int32_t>(x <= y ? 1 : 0, x, y); }
}
R.CheckDontPutConstantOnRight(41399);
R.CheckDontPutConstantOnRight(-440197);
Node* x = R.Parameter();
Node* max = R.maxuint32;
Node* zero = R.Constant<int32_t>(0);
R.CheckFoldBinop<int32_t>(1, x, max); // x <= M => 1
R.CheckFoldBinop<int32_t>(1, zero, x); // 0 <= x => 1
R.CheckFoldBinop<int32_t>(1, x, x); // x <= x => 1
}
TEST(ReduceLoadStore) {
ReducerTester R;
Node* base = R.Constant<int32_t>(11);
Node* index = R.Constant<int32_t>(4);
Node* load = R.graph.NewNode(R.machine.Load(MachineType::Int32()), base,
index, R.graph.start(), R.graph.start());
{
MachineOperatorReducer reducer(
&R.graph_reducer, &R.jsgraph,
MachineOperatorReducer::kPropagateSignallingNan);
Reduction reduction = reducer.Reduce(load);
CHECK(!reduction.Changed()); // loads should not be reduced.
}
{
Node* store =
R.graph.NewNode(R.machine.Store(StoreRepresentation(
MachineRepresentation::kWord32, kNoWriteBarrier)),
base, index, load, load, R.graph.start());
MachineOperatorReducer reducer(
&R.graph_reducer, &R.jsgraph,
MachineOperatorReducer::kPropagateSignallingNan);
Reduction reduction = reducer.Reduce(store);
CHECK(!reduction.Changed()); // stores should not be reduced.
}
}
TEST(ReduceFloat32Sub) {
ReducerTester R;
R.binop = R.machine.Float32Sub();
FOR_FLOAT32_INPUTS(x) {
FOR_FLOAT32_INPUTS(y) { R.CheckFoldBinop<float>(x - y, x, y); }
}
Node* x = R.Parameter();
Node* nan = R.Constant<float>(std::numeric_limits<float>::quiet_NaN());
// nan - x => nan
R.CheckFoldBinop(std::numeric_limits<float>::quiet_NaN(), nan, x);
// x - nan => nan
R.CheckFoldBinop(std::numeric_limits<float>::quiet_NaN(), x, nan);
}
TEST(ReduceFloat64Sub) {
ReducerTester R;
R.binop = R.machine.Float64Sub();
FOR_FLOAT64_INPUTS(x) {
FOR_FLOAT64_INPUTS(y) { R.CheckFoldBinop<double>(x - y, x, y); }
}
Node* x = R.Parameter();
Node* nan = R.Constant<double>(std::numeric_limits<double>::quiet_NaN());
// nan - x => nan
R.CheckFoldBinop(std::numeric_limits<double>::quiet_NaN(), nan, x);
// x - nan => nan
R.CheckFoldBinop(std::numeric_limits<double>::quiet_NaN(), x, nan);
}
// TODO(titzer): test MachineOperatorReducer for Word64And
// TODO(titzer): test MachineOperatorReducer for Word64Or
// TODO(titzer): test MachineOperatorReducer for Word64Xor
// TODO(titzer): test MachineOperatorReducer for Word64Equal
// TODO(titzer): test MachineOperatorReducer for Word64Not
// TODO(titzer): test MachineOperatorReducer for Int64Mul
// TODO(titzer): test MachineOperatorReducer for Int64UMul
// TODO(titzer): test MachineOperatorReducer for Int64Div
// TODO(titzer): test MachineOperatorReducer for Uint64Div
// TODO(titzer): test MachineOperatorReducer for Int64Mod
// TODO(titzer): test MachineOperatorReducer for Uint64Mod
// TODO(titzer): test MachineOperatorReducer for Int64Neg
// TODO(titzer): test MachineOperatorReducer for ChangeInt32ToFloat64
// TODO(titzer): test MachineOperatorReducer for ChangeFloat64ToInt32
// TODO(titzer): test MachineOperatorReducer for Float64Compare
// TODO(titzer): test MachineOperatorReducer for Float64Add
// TODO(titzer): test MachineOperatorReducer for Float64Sub
// TODO(titzer): test MachineOperatorReducer for Float64Mul
// TODO(titzer): test MachineOperatorReducer for Float64Div
// TODO(titzer): test MachineOperatorReducer for Float64Mod
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,411 @@
// Copyright 2014 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 <cmath>
#include <functional>
#include <limits>
#include <memory>
#include "src/base/bits.h"
#include "src/codegen/assembler.h"
#include "src/codegen/compiler.h"
#include "src/codegen/machine-type.h"
#include "src/codegen/macro-assembler.h"
#include "src/compiler/linkage.h"
#include "src/compiler/wasm-compiler.h"
#include "src/objects/objects-inl.h"
#include "src/wasm/function-compiler.h"
#include "src/wasm/wasm-code-pointer-table-inl.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-opcodes.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/codegen-tester.h"
#include "test/common/value-helper.h"
namespace v8 {
namespace internal {
namespace compiler {
namespace {
CallDescriptor* CreateCallDescriptor(Zone* zone, int return_count,
int param_count, MachineType type) {
wasm::FunctionSig::Builder builder(zone, return_count, param_count);
for (int i = 0; i < param_count; i++) {
builder.AddParam(wasm::ValueType::For(type));
}
for (int i = 0; i < return_count; i++) {
builder.AddReturn(wasm::ValueType::For(type));
}
return compiler::GetWasmCallDescriptor(zone, builder.Get(),
WasmCallKind::kWasmIndirectFunction);
}
Node* MakeConstant(RawMachineAssembler* m, MachineType type, int value) {
switch (type.representation()) {
case MachineRepresentation::kWord32:
return m->Int32Constant(static_cast<int32_t>(value));
case MachineRepresentation::kWord64:
return m->Int64Constant(static_cast<int64_t>(value));
case MachineRepresentation::kFloat32:
return m->Float32Constant(static_cast<float>(value));
case MachineRepresentation::kFloat64:
return m->Float64Constant(static_cast<double>(value));
default:
UNREACHABLE();
}
}
Node* Add(RawMachineAssembler* m, MachineType type, Node* a, Node* b) {
switch (type.representation()) {
case MachineRepresentation::kWord32:
return m->Int32Add(a, b);
case MachineRepresentation::kWord64:
return m->Int64Add(a, b);
case MachineRepresentation::kFloat32:
return m->Float32Add(a, b);
case MachineRepresentation::kFloat64:
return m->Float64Add(a, b);
default:
UNREACHABLE();
}
}
Node* Sub(RawMachineAssembler* m, MachineType type, Node* a, Node* b) {
switch (type.representation()) {
case MachineRepresentation::kWord32:
return m->Int32Sub(a, b);
case MachineRepresentation::kWord64:
return m->Int64Sub(a, b);
case MachineRepresentation::kFloat32:
return m->Float32Sub(a, b);
case MachineRepresentation::kFloat64:
return m->Float64Sub(a, b);
default:
UNREACHABLE();
}
}
Node* Mul(RawMachineAssembler* m, MachineType type, Node* a, Node* b) {
switch (type.representation()) {
case MachineRepresentation::kWord32:
return m->Int32Mul(a, b);
case MachineRepresentation::kWord64:
return m->Int64Mul(a, b);
case MachineRepresentation::kFloat32:
return m->Float32Mul(a, b);
case MachineRepresentation::kFloat64:
return m->Float64Mul(a, b);
default:
UNREACHABLE();
}
}
Node* ToInt32(RawMachineAssembler* m, MachineType type, Node* a) {
switch (type.representation()) {
case MachineRepresentation::kWord32:
return a;
case MachineRepresentation::kWord64:
return m->TruncateInt64ToInt32(a);
case MachineRepresentation::kFloat32:
return m->TruncateFloat32ToInt32(a, TruncateKind::kArchitectureDefault);
case MachineRepresentation::kFloat64:
return m->RoundFloat64ToInt32(a);
default:
UNREACHABLE();
}
}
std::shared_ptr<wasm::NativeModule> AllocateNativeModule(Isolate* isolate,
size_t code_size) {
auto module = std::make_shared<wasm::WasmModule>(wasm::kWasmOrigin);
module->num_declared_functions = 1;
// We have to add the code object to a NativeModule, because the
// WasmCallDescriptor assumes that code is on the native heap and not
// within a code object.
auto native_module = wasm::GetWasmEngine()->NewNativeModule(
isolate, wasm::WasmEnabledFeatures::All(), wasm::WasmDetectedFeatures{},
wasm::CompileTimeImports{}, std::move(module), code_size);
native_module->SetWireBytes({});
return native_module;
}
template <int kMinParamCount, int kMaxParamCount>
void TestReturnMultipleValues(MachineType type, int min_count, int max_count) {
for (int param_count : {kMinParamCount, kMaxParamCount}) {
for (int count = min_count; count < max_count; ++count) {
printf("\n==== type = %s, parameter_count = %d, count = %d ====\n\n\n",
MachineReprToString(type.representation()), param_count, count);
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
CallDescriptor* desc =
CreateCallDescriptor(&zone, count, param_count, type);
HandleAndZoneScope handles(kCompressGraphZone);
RawMachineAssembler m(
handles.main_isolate(),
handles.main_zone()->New<TFGraph>(handles.main_zone()), desc,
MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags());
// m.Parameter(0) is the WasmContext.
Node* p0 = m.Parameter(1);
Node* p1 = m.Parameter(2);
using Node_ptr = Node*;
std::unique_ptr<Node_ptr[]> returns(new Node_ptr[count]);
for (int i = 0; i < count; ++i) {
if (i % 3 == 0) returns[i] = Add(&m, type, p0, p1);
if (i % 3 == 1) returns[i] = Sub(&m, type, p0, p1);
if (i % 3 == 2) returns[i] = Mul(&m, type, p0, p1);
}
m.Return(count, returns.get());
OptimizedCompilationInfo info(base::ArrayVector("testing"),
handles.main_zone(),
CodeKind::WASM_FUNCTION);
DirectHandle<Code> code =
Pipeline::GenerateCodeForTesting(
&info, handles.main_isolate(), desc, m.graph(),
AssemblerOptions::Default(handles.main_isolate()),
m.ExportForTest())
.ToHandleChecked();
#ifdef ENABLE_DISASSEMBLER
if (v8_flags.print_code) {
StdoutStream os;
code->Disassemble("multi_value", os, handles.main_isolate());
}
#endif
const int a = 47, b = 12;
int expect = 0;
for (int i = 0, sign = +1; i < count; ++i) {
if (i % 3 == 0) expect += sign * (a + b);
if (i % 3 == 1) expect += sign * (a - b);
if (i % 3 == 2) expect += sign * (a * b);
if (i % 4 == 0) sign = -sign;
}
std::shared_ptr<wasm::NativeModule> module = AllocateNativeModule(
handles.main_isolate(), code->instruction_size());
wasm::WasmCodeRefScope wasm_code_ref_scope;
wasm::WasmCode* wasm_code =
module->AddCodeForTesting(code, desc->signature_hash());
WasmCodePointer code_pointer =
wasm::GetProcessWideWasmCodePointerTable()
->AllocateAndInitializeEntry(wasm_code->instruction_start(),
wasm_code->signature_hash());
RawMachineAssemblerTester<int32_t> mt(CodeKind::JS_TO_WASM_FUNCTION);
const int input_count = 2 + param_count;
Node* call_inputs[2 + kMaxParamCount];
call_inputs[0] = mt.IntPtrConstant(code_pointer.value());
// WasmContext dummy
call_inputs[1] = mt.PointerConstant(nullptr);
// Special inputs for the test.
call_inputs[2] = MakeConstant(&mt, type, a);
call_inputs[3] = MakeConstant(&mt, type, b);
for (int i = 2; i < param_count; i++) {
call_inputs[2 + i] = MakeConstant(&mt, type, i);
}
Node* ret_multi = mt.AddNode(mt.common()->Call(desc),
input_count, call_inputs);
Node* ret = MakeConstant(&mt, type, 0);
bool sign = false;
for (int i = 0; i < count; ++i) {
Node* x = (count == 1)
? ret_multi
: mt.AddNode(mt.common()->Projection(i), ret_multi);
ret = sign ? Sub(&mt, type, ret, x) : Add(&mt, type, ret, x);
if (i % 4 == 0) sign = !sign;
}
mt.Return(ToInt32(&mt, type, ret));
#ifdef ENABLE_DISASSEMBLER
if (v8_flags.print_code) {
StdoutStream os;
DirectHandle<Code> code2 = mt.GetCode();
code2->Disassemble("multi_value_call", os, handles.main_isolate());
}
#endif
CHECK_EQ(expect, mt.Call());
wasm::GetProcessWideWasmCodePointerTable()->FreeEntry(code_pointer);
}
}
}
} // namespace
// Use 9 parameters as a regression test or https://crbug.com/838098.
#define TEST_MULTI(Type, type) \
TEST(ReturnMultiple##Type) { TestReturnMultipleValues<2, 9>(type, 0, 20); }
// Create a frame larger than UINT16_MAX to force TF to use an extra register
// when popping the frame.
TEST(TestReturnMultipleValuesLargeFrame) {
TestReturnMultipleValues<wasm::kV8MaxWasmFunctionParams,
wasm::kV8MaxWasmFunctionParams>(MachineType::Int32(),
2, 3);
}
TEST_MULTI(Int32, MachineType::Int32())
#if (!V8_TARGET_ARCH_32_BIT)
TEST_MULTI(Int64, MachineType::Int64())
#endif
TEST_MULTI(Float32, MachineType::Float32())
TEST_MULTI(Float64, MachineType::Float64())
#undef TEST_MULTI
void ReturnLastValue(MachineType type) {
int slot_counts[] = {1, 2, 3, 600};
for (auto slot_count : slot_counts) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// The wasm-linkage provides 2 return registers at the moment, on all
// platforms.
const int return_count = 2 + slot_count;
CallDescriptor* desc = CreateCallDescriptor(&zone, return_count, 0, type);
HandleAndZoneScope handles(kCompressGraphZone);
RawMachineAssembler m(
handles.main_isolate(),
handles.main_zone()->New<TFGraph>(handles.main_zone()), desc,
MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags());
std::unique_ptr<Node* []> returns(new Node*[return_count]);
for (int i = 0; i < return_count; ++i) {
returns[i] = MakeConstant(&m, type, i);
}
m.Return(return_count, returns.get());
OptimizedCompilationInfo info(base::ArrayVector("testing"),
handles.main_zone(), CodeKind::WASM_FUNCTION);
DirectHandle<Code> code =
Pipeline::GenerateCodeForTesting(
&info, handles.main_isolate(), desc, m.graph(),
AssemblerOptions::Default(handles.main_isolate()),
m.ExportForTest())
.ToHandleChecked();
std::shared_ptr<wasm::NativeModule> module =
AllocateNativeModule(handles.main_isolate(), code->instruction_size());
wasm::WasmCodeRefScope wasm_code_ref_scope;
wasm::WasmCode* wasm_code =
module->AddCodeForTesting(code, desc->signature_hash());
WasmCodePointer code_pointer =
wasm::GetProcessWideWasmCodePointerTable()->AllocateAndInitializeEntry(
wasm_code->instruction_start(), wasm_code->signature_hash());
// Generate caller.
int expect = return_count - 1;
RawMachineAssemblerTester<int32_t> mt;
Node* inputs[] = {mt.IntPtrConstant(code_pointer.value()),
// WasmContext dummy
mt.PointerConstant(nullptr)};
Node* call = mt.AddNode(mt.common()->Call(desc), 2, inputs);
mt.Return(
ToInt32(&mt, type,
mt.AddNode(mt.common()->Projection(return_count - 1), call)));
CHECK_EQ(expect, mt.Call());
wasm::GetProcessWideWasmCodePointerTable()->FreeEntry(code_pointer);
}
}
TEST(ReturnLastValueInt32) { ReturnLastValue(MachineType::Int32()); }
#if (!V8_TARGET_ARCH_32_BIT)
TEST(ReturnLastValueInt64) { ReturnLastValue(MachineType::Int64()); }
#endif
TEST(ReturnLastValueFloat32) { ReturnLastValue(MachineType::Float32()); }
TEST(ReturnLastValueFloat64) { ReturnLastValue(MachineType::Float64()); }
void ReturnSumOfReturns(MachineType type) {
for (int unused_stack_slots = 0; unused_stack_slots <= 2;
++unused_stack_slots) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
// Let {unused_stack_slots + 1} returns be on the stack.
// The wasm-linkage provides 2 return registers at the moment, on all
// platforms.
const int return_count = 2 + unused_stack_slots + 1;
CallDescriptor* desc = CreateCallDescriptor(&zone, return_count, 0, type);
HandleAndZoneScope handles(kCompressGraphZone);
RawMachineAssembler m(
handles.main_isolate(),
handles.main_zone()->New<TFGraph>(handles.main_zone()), desc,
MachineType::PointerRepresentation(),
InstructionSelector::SupportedMachineOperatorFlags());
std::unique_ptr<Node* []> returns(new Node*[return_count]);
for (int i = 0; i < return_count; ++i) {
returns[i] = MakeConstant(&m, type, i);
}
m.Return(return_count, returns.get());
OptimizedCompilationInfo info(base::ArrayVector("testing"),
handles.main_zone(), CodeKind::WASM_FUNCTION);
DirectHandle<Code> code =
Pipeline::GenerateCodeForTesting(
&info, handles.main_isolate(), desc, m.graph(),
AssemblerOptions::Default(handles.main_isolate()),
m.ExportForTest())
.ToHandleChecked();
std::shared_ptr<wasm::NativeModule> module =
AllocateNativeModule(handles.main_isolate(), code->instruction_size());
wasm::WasmCodeRefScope wasm_code_ref_scope;
wasm::WasmCode* wasm_code =
module->AddCodeForTesting(code, desc->signature_hash());
WasmCodePointer code_pointer =
wasm::GetProcessWideWasmCodePointerTable()->AllocateAndInitializeEntry(
wasm_code->instruction_start(), wasm_code->signature_hash());
// Generate caller.
RawMachineAssemblerTester<int32_t> mt;
Node* call_inputs[] = {mt.IntPtrConstant(code_pointer.value()),
// WasmContext dummy
mt.PointerConstant(nullptr)};
Node* call = mt.AddNode(mt.common()->Call(desc), 2, call_inputs);
uint32_t expect = 0;
Node* result = mt.Int32Constant(0);
for (int i = 0; i < return_count; ++i) {
expect += i;
result = mt.Int32Add(
result,
ToInt32(&mt, type, mt.AddNode(mt.common()->Projection(i), call)));
}
mt.Return(result);
CHECK_EQ(expect, mt.Call());
wasm::GetProcessWideWasmCodePointerTable()->FreeEntry(code_pointer);
}
}
TEST(ReturnSumOfReturnsInt32) { ReturnSumOfReturns(MachineType::Int32()); }
#if (!V8_TARGET_ARCH_32_BIT)
TEST(ReturnSumOfReturnsInt64) { ReturnSumOfReturns(MachineType::Int64()); }
#endif
TEST(ReturnSumOfReturnsFloat32) { ReturnSumOfReturns(MachineType::Float32()); }
TEST(ReturnSumOfReturnsFloat64) { ReturnSumOfReturns(MachineType::Float64()); }
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,899 @@
// Copyright 2013 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 <functional>
#include "src/compiler/node.h"
#include "src/compiler/operator.h"
#include "src/compiler/turbofan-graph.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace compiler {
namespace node {
#define NONE reinterpret_cast<Node*>(1)
static Operator dummy_operator0(IrOpcode::kParameter, Operator::kNoWrite,
"dummy", 0, 0, 0, 1, 0, 0);
static Operator dummy_operator1(IrOpcode::kParameter, Operator::kNoWrite,
"dummy", 1, 0, 0, 1, 0, 0);
static Operator dummy_operator2(IrOpcode::kParameter, Operator::kNoWrite,
"dummy", 2, 0, 0, 1, 0, 0);
static Operator dummy_operator3(IrOpcode::kParameter, Operator::kNoWrite,
"dummy", 3, 0, 0, 1, 0, 0);
#define CHECK_USES(node, ...) \
do { \
Node* __array[] = {__VA_ARGS__}; \
int __size = \
__array[0] != NONE ? static_cast<int>(arraysize(__array)) : 0; \
CheckUseChain(node, __array, __size); \
} while (false)
namespace {
using NodeMSet = std::multiset<Node*, std::less<Node*>>;
void CheckUseChain(Node* node, Node** uses, int use_count) {
// Check ownership.
if (use_count == 1) CHECK(node->OwnedBy(uses[0]));
if (use_count > 1) {
Node* first_use = uses[0];
bool different_uses = false;
for (int i = 0; i < use_count; i++) {
if (uses[i] != first_use) {
different_uses = true;
break;
}
}
if (different_uses) {
// If there are different uses, check that node is not owned by any use.
for (int i = 0; i < use_count; i++) {
CHECK(!node->OwnedBy(uses[i]));
}
} else {
// If all uses are the same, check that node is owned by that use.
CHECK(node->OwnedBy(first_use));
}
}
// Check the self-reported use count.
CHECK_EQ(use_count, node->UseCount());
// Build the expectation set.
NodeMSet expect_set;
for (int i = 0; i < use_count; i++) {
expect_set.insert(uses[i]);
}
{
// Check that iterating over the uses gives the right counts.
NodeMSet use_set;
for (auto use : node->uses()) {
use_set.insert(use);
}
CHECK(expect_set == use_set);
}
{
// Check that iterating over the use edges gives the right counts,
// input indices, from(), and to() pointers.
NodeMSet use_set;
for (auto edge : node->use_edges()) {
CHECK_EQ(node, edge.to());
CHECK_EQ(node, edge.from()->InputAt(edge.index()));
use_set.insert(edge.from());
}
CHECK(expect_set == use_set);
}
{
// Check the use nodes actually have the node as inputs.
for (Node* use : node->uses()) {
size_t count = 0;
for (Node* input : use->inputs()) {
if (input == node) count++;
}
CHECK_EQ(count, expect_set.count(use));
}
}
}
void CheckInputs(Node* node, Node** inputs, int input_count) {
CHECK_EQ(input_count, node->InputCount());
// Check InputAt().
for (int i = 0; i < static_cast<int>(input_count); i++) {
CHECK_EQ(inputs[i], node->InputAt(i));
}
// Check input iterator.
int index = 0;
for (Node* input : node->inputs()) {
CHECK_EQ(inputs[index], input);
index++;
}
// Check use lists of inputs.
for (int i = 0; i < static_cast<int>(input_count); i++) {
Node* input = inputs[i];
if (!input) continue; // skip null inputs
bool found = false;
// Check regular use list.
for (Node* use : input->uses()) {
if (use == node) {
found = true;
break;
}
}
CHECK(found);
int count = 0;
// Check use edge list.
for (auto edge : input->use_edges()) {
if (edge.from() == node && edge.to() == input && edge.index() == i) {
count++;
}
}
CHECK_EQ(1, count);
}
}
} // namespace
#define CHECK_INPUTS(node, ...) \
do { \
Node* __array[] = {__VA_ARGS__}; \
int __size = \
__array[0] != NONE ? static_cast<int>(arraysize(__array)) : 0; \
CheckInputs(node, __array, __size); \
} while (false)
TEST(NodeUseIteratorReplaceUses) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
Node* n3 = graph.NewNode(&dummy_operator0);
CHECK_USES(n0, n1, n2);
CHECK_INPUTS(n1, n0);
CHECK_INPUTS(n2, n0);
n0->ReplaceUses(n3);
CHECK_USES(n0, NONE);
CHECK_USES(n1, NONE);
CHECK_USES(n2, NONE);
CHECK_USES(n3, n1, n2);
CHECK_INPUTS(n1, n3);
CHECK_INPUTS(n2, n3);
}
TEST(NodeUseIteratorReplaceUsesSelf) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
CHECK_USES(n0, n1);
CHECK_USES(n1, NONE);
n1->ReplaceInput(0, n1); // Create self-reference.
CHECK_USES(n0, NONE);
CHECK_USES(n1, n1);
Node* n2 = graph.NewNode(&dummy_operator0);
n1->ReplaceUses(n2);
CHECK_USES(n0, NONE);
CHECK_USES(n1, NONE);
CHECK_USES(n2, n1);
}
TEST(ReplaceInput) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator0);
Node* n3 = graph.NewNode(&dummy_operator3, n0, n1, n2);
Node* n4 = graph.NewNode(&dummy_operator0);
CHECK_USES(n0, n3);
CHECK_USES(n1, n3);
CHECK_USES(n2, n3);
CHECK_USES(n3, NONE);
CHECK_USES(n4, NONE);
CHECK_INPUTS(n3, n0, n1, n2);
n3->ReplaceInput(1, n4);
CHECK_USES(n1, NONE);
CHECK_USES(n4, n3);
CHECK_INPUTS(n3, n0, n4, n2);
}
TEST(OwnedBy) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
CHECK(!n0->OwnedBy(n1));
CHECK(!n1->OwnedBy(n0));
Node* n2 = graph.NewNode(&dummy_operator1, n0);
CHECK(n0->OwnedBy(n2));
CHECK(!n2->OwnedBy(n0));
Node* n3 = graph.NewNode(&dummy_operator1, n0);
CHECK(!n0->OwnedBy(n2));
CHECK(!n0->OwnedBy(n3));
CHECK(!n2->OwnedBy(n0));
CHECK(!n3->OwnedBy(n0));
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
CHECK(n0->OwnedBy(n1));
CHECK(!n1->OwnedBy(n0));
Node* n2 = graph.NewNode(&dummy_operator1, n0);
CHECK(!n0->OwnedBy(n1));
CHECK(!n0->OwnedBy(n2));
CHECK(!n1->OwnedBy(n0));
CHECK(!n1->OwnedBy(n2));
CHECK(!n2->OwnedBy(n0));
CHECK(!n2->OwnedBy(n1));
Node* n3 = graph.NewNode(&dummy_operator0);
n2->ReplaceInput(0, n3);
CHECK(n0->OwnedBy(n1));
CHECK(!n1->OwnedBy(n0));
CHECK(!n1->OwnedBy(n0));
CHECK(!n1->OwnedBy(n2));
CHECK(!n2->OwnedBy(n0));
CHECK(!n2->OwnedBy(n1));
CHECK(n3->OwnedBy(n2));
CHECK(!n2->OwnedBy(n3));
}
}
TEST(Uses) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
CHECK_USES(n0, n1);
CHECK_USES(n1, NONE);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
CHECK_USES(n0, n1, n2);
CHECK_USES(n2, NONE);
Node* n3 = graph.NewNode(&dummy_operator1, n0);
CHECK_USES(n0, n1, n2, n3);
CHECK_USES(n3, NONE);
}
TEST(Inputs) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
Node* n3 = graph.NewNode(&dummy_operator3, n0, n1, n2);
CHECK_INPUTS(n3, n0, n1, n2);
Node* n4 = graph.NewNode(&dummy_operator3, n0, n1, n2);
n3->AppendInput(graph.zone(), n4);
CHECK_INPUTS(n3, n0, n1, n2, n4);
CHECK_USES(n4, n3);
n3->AppendInput(graph.zone(), n4);
CHECK_INPUTS(n3, n0, n1, n2, n4, n4);
CHECK_USES(n4, n3, n3);
Node* n5 = graph.NewNode(&dummy_operator1, n4);
CHECK_USES(n4, n3, n3, n5);
}
TEST(InsertInputs) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
{
Node* node = graph.NewNode(&dummy_operator1, n0);
node->InsertInputs(graph.zone(), 0, 1);
node->ReplaceInput(0, n1);
CHECK_INPUTS(node, n1, n0);
}
{
Node* node = graph.NewNode(&dummy_operator1, n0);
node->InsertInputs(graph.zone(), 0, 2);
node->ReplaceInput(0, node);
node->ReplaceInput(1, n2);
CHECK_INPUTS(node, node, n2, n0);
}
{
Node* node = graph.NewNode(&dummy_operator3, n0, n1, n2);
node->InsertInputs(graph.zone(), 0, 1);
node->ReplaceInput(0, node);
CHECK_INPUTS(node, node, n0, n1, n2);
}
{
Node* node = graph.NewNode(&dummy_operator3, n0, n1, n2);
node->InsertInputs(graph.zone(), 1, 1);
node->ReplaceInput(1, node);
CHECK_INPUTS(node, n0, node, n1, n2);
}
{
Node* node = graph.NewNode(&dummy_operator3, n0, n1, n2);
node->InsertInputs(graph.zone(), 2, 1);
node->ReplaceInput(2, node);
CHECK_INPUTS(node, n0, n1, node, n2);
}
{
Node* node = graph.NewNode(&dummy_operator3, n0, n1, n2);
node->InsertInputs(graph.zone(), 2, 1);
node->ReplaceInput(2, node);
CHECK_INPUTS(node, n0, n1, node, n2);
}
{
Node* node = graph.NewNode(&dummy_operator3, n0, n1, n2);
node->InsertInputs(graph.zone(), 0, 4);
node->ReplaceInput(0, node);
node->ReplaceInput(1, node);
node->ReplaceInput(2, node);
node->ReplaceInput(3, node);
CHECK_INPUTS(node, node, node, node, node, n0, n1, n2);
}
{
Node* node = graph.NewNode(&dummy_operator3, n0, n1, n2);
node->InsertInputs(graph.zone(), 1, 4);
node->ReplaceInput(1, node);
node->ReplaceInput(2, node);
node->ReplaceInput(3, node);
node->ReplaceInput(4, node);
CHECK_INPUTS(node, n0, node, node, node, node, n1, n2);
}
{
Node* node = graph.NewNode(&dummy_operator3, n0, n1, n2);
node->InsertInputs(graph.zone(), 2, 4);
node->ReplaceInput(2, node);
node->ReplaceInput(3, node);
node->ReplaceInput(4, node);
node->ReplaceInput(5, node);
CHECK_INPUTS(node, n0, n1, node, node, node, node, n2);
}
}
TEST(RemoveInput) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
Node* n2 = graph.NewNode(&dummy_operator2, n0, n1);
CHECK_INPUTS(n0, NONE);
CHECK_INPUTS(n1, n0);
CHECK_INPUTS(n2, n0, n1);
CHECK_USES(n0, n1, n2);
n1->RemoveInput(0);
CHECK_INPUTS(n1, NONE);
CHECK_USES(n0, n2);
n2->RemoveInput(0);
CHECK_INPUTS(n2, n1);
CHECK_USES(n0, NONE);
CHECK_USES(n1, n2);
n2->RemoveInput(0);
CHECK_INPUTS(n2, NONE);
CHECK_USES(n0, NONE);
CHECK_USES(n1, NONE);
CHECK_USES(n2, NONE);
}
TEST(AppendInputsAndIterator) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
Node* n2 = graph.NewNode(&dummy_operator2, n0, n1);
CHECK_INPUTS(n0, NONE);
CHECK_INPUTS(n1, n0);
CHECK_INPUTS(n2, n0, n1);
CHECK_USES(n0, n1, n2);
Node* n3 = graph.NewNode(&dummy_operator0);
n2->AppendInput(graph.zone(), n3);
CHECK_INPUTS(n2, n0, n1, n3);
CHECK_USES(n3, n2);
}
TEST(NullInputsSimple) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
Node* n2 = graph.NewNode(&dummy_operator2, n0, n1);
CHECK_INPUTS(n0, NONE);
CHECK_INPUTS(n1, n0);
CHECK_INPUTS(n2, n0, n1);
CHECK_USES(n0, n1, n2);
n2->ReplaceInput(0, nullptr);
CHECK_INPUTS(n2, nullptr, n1);
CHECK_USES(n0, n1);
n2->ReplaceInput(1, nullptr);
CHECK_INPUTS(n2, nullptr, nullptr);
CHECK_USES(n1, NONE);
}
TEST(NullInputsAppended) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
Node* n3 = graph.NewNode(&dummy_operator1, n0);
n3->AppendInput(graph.zone(), n1);
n3->AppendInput(graph.zone(), n2);
CHECK_INPUTS(n3, n0, n1, n2);
CHECK_USES(n0, n1, n2, n3);
CHECK_USES(n1, n3);
CHECK_USES(n2, n3);
n3->ReplaceInput(1, nullptr);
CHECK_USES(n1, NONE);
CHECK_INPUTS(n3, n0, nullptr, n2);
}
TEST(ReplaceUsesFromAppendedInputs) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
Node* n3 = graph.NewNode(&dummy_operator0);
CHECK_INPUTS(n2, n0);
n2->AppendInput(graph.zone(), n1);
CHECK_INPUTS(n2, n0, n1);
CHECK_USES(n1, n2);
n2->AppendInput(graph.zone(), n0);
CHECK_INPUTS(n2, n0, n1, n0);
CHECK_USES(n1, n2);
CHECK_USES(n0, n2, n1, n2);
n0->ReplaceUses(n3);
CHECK_USES(n0, NONE);
CHECK_INPUTS(n2, n3, n1, n3);
CHECK_USES(n3, n2, n1, n2);
}
TEST(ReplaceInputMultipleUses) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
n2->ReplaceInput(0, n1);
CHECK_EQ(0, n0->UseCount());
CHECK_EQ(1, n1->UseCount());
Node* n3 = graph.NewNode(&dummy_operator1, n0);
n3->ReplaceInput(0, n1);
CHECK_EQ(0, n0->UseCount());
CHECK_EQ(2, n1->UseCount());
}
TEST(TrimInputCountInline) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
n1->TrimInputCount(1);
CHECK_INPUTS(n1, n0);
CHECK_USES(n0, n1);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
n1->TrimInputCount(0);
CHECK_INPUTS(n1, NONE);
CHECK_USES(n0, NONE);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator2, n0, n1);
n2->TrimInputCount(2);
CHECK_INPUTS(n2, n0, n1);
CHECK_USES(n0, n2);
CHECK_USES(n1, n2);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator2, n0, n1);
n2->TrimInputCount(1);
CHECK_INPUTS(n2, n0);
CHECK_USES(n0, n2);
CHECK_USES(n1, NONE);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator2, n0, n1);
n2->TrimInputCount(0);
CHECK_INPUTS(n2, NONE);
CHECK_USES(n0, NONE);
CHECK_USES(n1, NONE);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator2, n0, n0);
n2->TrimInputCount(1);
CHECK_INPUTS(n2, n0);
CHECK_USES(n0, n2);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator2, n0, n0);
n2->TrimInputCount(0);
CHECK_INPUTS(n2, NONE);
CHECK_USES(n0, NONE);
}
}
TEST(TrimInputCountOutOfLine1) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
n1->AppendInput(graph.zone(), n0);
CHECK_INPUTS(n1, n0);
CHECK_USES(n0, n1);
n1->TrimInputCount(1);
CHECK_INPUTS(n1, n0);
CHECK_USES(n0, n1);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
n1->AppendInput(graph.zone(), n0);
CHECK_EQ(1, n1->InputCount());
n1->TrimInputCount(0);
CHECK_EQ(0, n1->InputCount());
CHECK_EQ(0, n0->UseCount());
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator0);
n2->AppendInput(graph.zone(), n0);
n2->AppendInput(graph.zone(), n1);
CHECK_INPUTS(n2, n0, n1);
n2->TrimInputCount(2);
CHECK_INPUTS(n2, n0, n1);
CHECK_USES(n0, n2);
CHECK_USES(n1, n2);
CHECK_USES(n2, NONE);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator0);
n2->AppendInput(graph.zone(), n0);
n2->AppendInput(graph.zone(), n1);
CHECK_INPUTS(n2, n0, n1);
n2->TrimInputCount(1);
CHECK_INPUTS(n2, n0);
CHECK_USES(n0, n2);
CHECK_USES(n1, NONE);
CHECK_USES(n2, NONE);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator0);
n2->AppendInput(graph.zone(), n0);
n2->AppendInput(graph.zone(), n1);
CHECK_INPUTS(n2, n0, n1);
n2->TrimInputCount(0);
CHECK_INPUTS(n2, NONE);
CHECK_USES(n0, NONE);
CHECK_USES(n1, NONE);
CHECK_USES(n2, NONE);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator0);
n2->AppendInput(graph.zone(), n0);
n2->AppendInput(graph.zone(), n0);
CHECK_INPUTS(n2, n0, n0);
CHECK_USES(n0, n2, n2);
n2->TrimInputCount(1);
CHECK_INPUTS(n2, n0);
CHECK_USES(n0, n2);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator0);
n2->AppendInput(graph.zone(), n0);
n2->AppendInput(graph.zone(), n0);
CHECK_INPUTS(n2, n0, n0);
CHECK_USES(n0, n2, n2);
n2->TrimInputCount(0);
CHECK_INPUTS(n2, NONE);
CHECK_USES(n0, NONE);
}
}
TEST(TrimInputCountOutOfLine2) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
n2->AppendInput(graph.zone(), n1);
CHECK_INPUTS(n2, n0, n1);
n2->TrimInputCount(2);
CHECK_INPUTS(n2, n0, n1);
CHECK_USES(n0, n2);
CHECK_USES(n1, n2);
CHECK_USES(n2, NONE);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
n2->AppendInput(graph.zone(), n1);
CHECK_INPUTS(n2, n0, n1);
n2->TrimInputCount(1);
CHECK_INPUTS(n2, n0);
CHECK_USES(n0, n2);
CHECK_USES(n1, NONE);
CHECK_USES(n2, NONE);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
n2->AppendInput(graph.zone(), n1);
CHECK_INPUTS(n2, n0, n1);
n2->TrimInputCount(0);
CHECK_INPUTS(n2, NONE);
CHECK_USES(n0, NONE);
CHECK_USES(n1, NONE);
CHECK_USES(n2, NONE);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
n2->AppendInput(graph.zone(), n0);
CHECK_INPUTS(n2, n0, n0);
CHECK_USES(n0, n2, n2);
n2->TrimInputCount(1);
CHECK_INPUTS(n2, n0);
CHECK_USES(n0, n2);
CHECK_USES(n2, NONE);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n2 = graph.NewNode(&dummy_operator1, n0);
n2->AppendInput(graph.zone(), n0);
CHECK_EQ(2, n2->InputCount());
CHECK_EQ(2, n0->UseCount());
n2->TrimInputCount(0);
CHECK_EQ(0, n2->InputCount());
CHECK_EQ(0, n0->UseCount());
CHECK_EQ(0, n2->UseCount());
}
}
TEST(NullAllInputs) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
for (int i = 0; i < 2; i++) {
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
Node* n2;
if (i == 0) {
n2 = graph.NewNode(&dummy_operator2, n0, n1);
CHECK_INPUTS(n2, n0, n1);
} else {
n2 = graph.NewNode(&dummy_operator1, n0);
CHECK_INPUTS(n2, n0);
n2->AppendInput(graph.zone(), n1); // with out-of-line input.
CHECK_INPUTS(n2, n0, n1);
}
n0->NullAllInputs();
CHECK_INPUTS(n0, NONE);
CHECK_USES(n0, n1, n2);
n1->NullAllInputs();
CHECK_INPUTS(n1, nullptr);
CHECK_INPUTS(n2, n0, n1);
CHECK_USES(n0, n2);
n2->NullAllInputs();
CHECK_INPUTS(n1, nullptr);
CHECK_INPUTS(n2, nullptr, nullptr);
CHECK_USES(n0, NONE);
}
{
Node* n0 = graph.NewNode(&dummy_operator0);
Node* n1 = graph.NewNode(&dummy_operator1, n0);
n1->ReplaceInput(0, n1); // self-reference.
CHECK_INPUTS(n0, NONE);
CHECK_INPUTS(n1, n1);
CHECK_USES(n0, NONE);
CHECK_USES(n1, n1);
n1->NullAllInputs();
CHECK_INPUTS(n0, NONE);
CHECK_INPUTS(n1, nullptr);
CHECK_USES(n0, NONE);
CHECK_USES(n1, NONE);
}
}
TEST(AppendAndTrim) {
v8::internal::AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME, kCompressGraphZone);
TFGraph graph(&zone);
Node* nodes[] = {
graph.NewNode(&dummy_operator0), graph.NewNode(&dummy_operator0),
graph.NewNode(&dummy_operator0), graph.NewNode(&dummy_operator0),
graph.NewNode(&dummy_operator0)};
int max = static_cast<int>(arraysize(nodes));
Node* last = graph.NewNode(&dummy_operator0);
for (int i = 0; i < max; i++) {
last->AppendInput(graph.zone(), nodes[i]);
CheckInputs(last, nodes, i + 1);
for (int j = 0; j < max; j++) {
if (j <= i) CHECK_USES(nodes[j], last);
if (j > i) CHECK_USES(nodes[j], NONE);
}
CHECK_USES(last, NONE);
}
for (int i = max; i >= 0; i--) {
last->TrimInputCount(i);
CheckInputs(last, nodes, i);
for (int j = 0; j < i; j++) {
if (j < i) CHECK_USES(nodes[j], last);
if (j >= i) CHECK_USES(nodes[j], NONE);
}
CHECK_USES(last, NONE);
}
}
#undef NONE
#undef CHECK_USES
#undef CHECK_INPUTS
} // namespace node
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,289 @@
// Copyright 2013 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 <memory>
#include <sstream>
#include "src/compiler/operator.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace compiler {
#define NONE Operator::kNoProperties
#define FOLD Operator::kFoldable
TEST(TestOperator_Mnemonic) {
Operator op1(10, NONE, "ThisOne", 0, 0, 0, 0, 0, 0);
CHECK_EQ(0, strcmp(op1.mnemonic(), "ThisOne"));
Operator op2(11, NONE, "ThatOne", 0, 0, 0, 0, 0, 0);
CHECK_EQ(0, strcmp(op2.mnemonic(), "ThatOne"));
Operator1<int> op3(12, NONE, "Mnemonic1", 0, 0, 0, 1, 0, 0, 12333);
CHECK_EQ(0, strcmp(op3.mnemonic(), "Mnemonic1"));
Operator1<double> op4(13, NONE, "TheOther", 0, 0, 0, 1, 0, 0, 99.9);
CHECK_EQ(0, strcmp(op4.mnemonic(), "TheOther"));
}
TEST(TestOperator_Hash) {
Operator op1(17, NONE, "Another", 0, 0, 0, 0, 0, 0);
CHECK_EQ(17, static_cast<int>(op1.HashCode()));
Operator op2(18, NONE, "Falsch", 0, 0, 0, 0, 0, 0);
CHECK_EQ(18, static_cast<int>(op2.HashCode()));
}
TEST(TestOperator_Equals) {
Operator op1a(19, NONE, "Another1", 0, 0, 0, 0, 0, 0);
Operator op1b(19, FOLD, "Another2", 2, 0, 0, 2, 0, 0);
CHECK(op1a.Equals(&op1a));
CHECK(op1a.Equals(&op1b));
CHECK(op1b.Equals(&op1a));
CHECK(op1b.Equals(&op1b));
Operator op2a(20, NONE, "Falsch1", 0, 0, 0, 0, 0, 0);
Operator op2b(20, FOLD, "Falsch2", 1, 0, 0, 1, 0, 0);
CHECK(op2a.Equals(&op2a));
CHECK(op2a.Equals(&op2b));
CHECK(op2b.Equals(&op2a));
CHECK(op2b.Equals(&op2b));
CHECK(!op1a.Equals(&op2a));
CHECK(!op1a.Equals(&op2b));
CHECK(!op1b.Equals(&op2a));
CHECK(!op1b.Equals(&op2b));
CHECK(!op2a.Equals(&op1a));
CHECK(!op2a.Equals(&op1b));
CHECK(!op2b.Equals(&op1a));
CHECK(!op2b.Equals(&op1b));
}
static std::unique_ptr<char[]> OperatorToString(Operator* op) {
std::ostringstream os;
os << *op;
return std::unique_ptr<char[]>(StrDup(os.str().c_str()));
}
TEST(TestOperator_Print) {
Operator op1a(19, NONE, "Another1", 0, 0, 0, 0, 0, 0);
Operator op1b(19, FOLD, "Another2", 2, 0, 0, 2, 0, 0);
CHECK_EQ(0, strcmp("Another1", OperatorToString(&op1a).get()));
CHECK_EQ(0, strcmp("Another2", OperatorToString(&op1b).get()));
Operator op2a(20, NONE, "Flog1", 0, 0, 0, 0, 0, 0);
Operator op2b(20, FOLD, "Flog2", 1, 0, 0, 1, 0, 0);
CHECK_EQ(0, strcmp("Flog1", OperatorToString(&op2a).get()));
CHECK_EQ(0, strcmp("Flog2", OperatorToString(&op2b).get()));
}
TEST(TestOperator1int_Hash) {
Operator1<int> op1a(23, NONE, "Wolfie", 0, 0, 0, 0, 0, 0, 11);
Operator1<int> op1b(23, FOLD, "Doggie", 2, 0, 0, 2, 0, 0, 11);
CHECK(op1a.HashCode() == op1b.HashCode());
Operator1<int> op2a(24, NONE, "Arfie", 0, 0, 0, 0, 0, 0, 3);
Operator1<int> op2b(24, NONE, "Arfie", 0, 0, 0, 0, 0, 0, 4);
CHECK(op1a.HashCode() != op2a.HashCode());
CHECK(op2a.HashCode() != op2b.HashCode());
}
TEST(TestOperator1int_Equals) {
Operator1<int> op1a(23, NONE, "Scratchy", 0, 0, 0, 0, 0, 0, 11);
Operator1<int> op1b(23, FOLD, "Scratchy", 2, 0, 0, 2, 0, 0, 11);
CHECK(op1a.Equals(&op1a));
CHECK(op1a.Equals(&op1b));
CHECK(op1b.Equals(&op1a));
CHECK(op1b.Equals(&op1b));
Operator1<int> op2a(24, NONE, "Im", 0, 0, 0, 0, 0, 0, 3);
Operator1<int> op2b(24, NONE, "Im", 0, 0, 0, 0, 0, 0, 4);
CHECK(op2a.Equals(&op2a));
CHECK(!op2a.Equals(&op2b));
CHECK(!op2b.Equals(&op2a));
CHECK(op2b.Equals(&op2b));
CHECK(!op1a.Equals(&op2a));
CHECK(!op1a.Equals(&op2b));
CHECK(!op1b.Equals(&op2a));
CHECK(!op1b.Equals(&op2b));
CHECK(!op2a.Equals(&op1a));
CHECK(!op2a.Equals(&op1b));
CHECK(!op2b.Equals(&op1a));
CHECK(!op2b.Equals(&op1b));
Operator op3(25, NONE, "Weepy", 0, 0, 0, 0, 0, 0);
CHECK(!op1a.Equals(&op3));
CHECK(!op1b.Equals(&op3));
CHECK(!op2a.Equals(&op3));
CHECK(!op2b.Equals(&op3));
CHECK(!op3.Equals(&op1a));
CHECK(!op3.Equals(&op1b));
CHECK(!op3.Equals(&op2a));
CHECK(!op3.Equals(&op2b));
}
TEST(TestOperator1int_Print) {
Operator1<int> op1(12, NONE, "Op1Test", 0, 0, 0, 1, 0, 0, 0);
CHECK_EQ(0, strcmp("Op1Test[0]", OperatorToString(&op1).get()));
Operator1<int> op2(12, NONE, "Op1Test", 0, 0, 0, 1, 0, 0, 66666666);
CHECK_EQ(0, strcmp("Op1Test[66666666]", OperatorToString(&op2).get()));
Operator1<int> op3(12, NONE, "FooBar", 0, 0, 0, 1, 0, 0, 2347);
CHECK_EQ(0, strcmp("FooBar[2347]", OperatorToString(&op3).get()));
Operator1<int> op4(12, NONE, "BarFoo", 0, 0, 0, 1, 0, 0, -879);
CHECK_EQ(0, strcmp("BarFoo[-879]", OperatorToString(&op4).get()));
}
TEST(TestOperator1double_Hash) {
Operator1<double> op1a(23, NONE, "Wolfie", 0, 0, 0, 0, 0, 0, 11.77);
Operator1<double> op1b(23, FOLD, "Doggie", 2, 0, 0, 2, 0, 0, 11.77);
CHECK(op1a.HashCode() == op1b.HashCode());
Operator1<double> op2a(24, NONE, "Arfie", 0, 0, 0, 0, 0, 0, -6.7);
Operator1<double> op2b(24, NONE, "Arfie", 0, 0, 0, 0, 0, 0, -6.8);
CHECK(op1a.HashCode() != op2a.HashCode());
CHECK(op2a.HashCode() != op2b.HashCode());
}
TEST(TestOperator1doublePrint) {
Operator1<double> op1a(23, NONE, "Canary", 0, 0, 0, 0, 0, 0, 0.5);
Operator1<double> op1b(23, FOLD, "Finch", 2, 0, 0, 2, 0, 0, -1.5);
CHECK_EQ(0, strcmp("Canary[0.5]", OperatorToString(&op1a).get()));
CHECK_EQ(0, strcmp("Finch[-1.5]", OperatorToString(&op1b).get()));
}
TEST(TestOperator1double_Equals) {
Operator1<double> op1a(23, NONE, "Scratchy", 0, 0, 0, 0, 0, 0, 11.77);
Operator1<double> op1b(23, FOLD, "Scratchy", 2, 0, 0, 2, 0, 0, 11.77);
CHECK(op1a.Equals(&op1a));
CHECK(op1a.Equals(&op1b));
CHECK(op1b.Equals(&op1a));
CHECK(op1b.Equals(&op1b));
Operator1<double> op2a(24, NONE, "Im", 0, 0, 0, 0, 0, 0, 3.1);
Operator1<double> op2b(24, NONE, "Im", 0, 0, 0, 0, 0, 0, 3.2);
CHECK(op2a.Equals(&op2a));
CHECK(!op2a.Equals(&op2b));
CHECK(!op2b.Equals(&op2a));
CHECK(op2b.Equals(&op2b));
CHECK(!op1a.Equals(&op2a));
CHECK(!op1a.Equals(&op2b));
CHECK(!op1b.Equals(&op2a));
CHECK(!op1b.Equals(&op2b));
CHECK(!op2a.Equals(&op1a));
CHECK(!op2a.Equals(&op1b));
CHECK(!op2b.Equals(&op1a));
CHECK(!op2b.Equals(&op1b));
Operator1<double> op3(25, NONE, "Weepy", 0, 0, 0, 0, 0, 0, 1.1);
CHECK(!op1a.Equals(&op3));
CHECK(!op1b.Equals(&op3));
CHECK(!op2a.Equals(&op3));
CHECK(!op2b.Equals(&op3));
CHECK(!op3.Equals(&op1a));
CHECK(!op3.Equals(&op1b));
CHECK(!op3.Equals(&op2a));
CHECK(!op3.Equals(&op2b));
Operator1<double> op4a(24, NONE, "Bashful", 0, 0, 0, 0, 0, 0, 1.0);
Operator1<double> op4b(24, NONE, "Bashful", 0, 0, 0, 0, 0, 0, 1.0);
CHECK(op4a.Equals(&op4a));
CHECK(op4a.Equals(&op4b));
CHECK(op4b.Equals(&op4a));
CHECK(op4b.Equals(&op4b));
CHECK(!op3.Equals(&op4a));
CHECK(!op3.Equals(&op4b));
CHECK(!op3.Equals(&op4a));
CHECK(!op3.Equals(&op4b));
}
TEST(TestOpParameter_Operator1double) {
double values[] = {7777.5, -66, 0, 11, 0.1};
for (size_t i = 0; i < arraysize(values); i++) {
Operator1<double> op(33, NONE, "Scurvy", 0, 0, 0, 0, 0, 0, values[i]);
CHECK_EQ(values[i], OpParameter<double>(&op));
}
}
TEST(TestOpParameter_Operator1float) {
float values[] = {// thanks C++.
static_cast<float>(7777.5), static_cast<float>(-66),
static_cast<float>(0), static_cast<float>(11),
static_cast<float>(0.1)};
for (size_t i = 0; i < arraysize(values); i++) {
Operator1<float> op(33, NONE, "Scurvy", 0, 0, 0, 0, 0, 0, values[i]);
CHECK_EQ(values[i], OpParameter<float>(&op));
}
}
TEST(TestOpParameter_Operator1int) {
int values[] = {7777, -66, 0, 11, 1, 0x666AFF};
for (size_t i = 0; i < arraysize(values); i++) {
Operator1<int> op(33, NONE, "Scurvy", 0, 0, 0, 0, 0, 0, values[i]);
CHECK_EQ(values[i], OpParameter<int>(&op));
}
}
TEST(Operator_CountsOrder) {
Operator op(29, NONE, "Flashy", 11, 22, 33, 44, 55, 66);
CHECK_EQ(11, op.ValueInputCount());
CHECK_EQ(22, op.EffectInputCount());
CHECK_EQ(33, op.ControlInputCount());
CHECK_EQ(44, op.ValueOutputCount());
CHECK_EQ(55, op.EffectOutputCount());
CHECK_EQ(66, op.ControlOutputCount());
}
#undef NONE
#undef FOLD
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,746 @@
// Copyright 2014 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/compiler/access-info.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/representation-change.h"
#include "src/compiler/type-cache.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/codegen-tester.h"
#include "test/cctest/compiler/graph-and-builders.h"
#include "test/cctest/compiler/js-heap-broker-base.h"
#include "test/common/value-helper.h"
namespace v8 {
namespace internal {
namespace compiler {
class RepresentationChangerTester : public HandleAndZoneScope,
public GraphAndBuilders,
public JSHeapBrokerTestBase {
public:
explicit RepresentationChangerTester(int num_parameters = 0)
: HandleAndZoneScope(kCompressGraphZone),
GraphAndBuilders(main_zone()),
JSHeapBrokerTestBase(main_isolate(), main_zone()),
javascript_(main_zone()),
jsgraph_(main_isolate(), main_graph_, &main_common_, &javascript_,
&main_simplified_, &main_machine_),
changer_(&jsgraph_, broker(), nullptr) {
Node* s = graph()->NewNode(common()->Start(num_parameters));
graph()->SetStart(s);
}
JSOperatorBuilder javascript_;
JSGraph jsgraph_;
RepresentationChanger changer_;
Isolate* isolate() { return main_isolate(); }
TFGraph* graph() { return main_graph_; }
CommonOperatorBuilder* common() { return &main_common_; }
JSGraph* jsgraph() { return &jsgraph_; }
RepresentationChanger* changer() { return &changer_; }
// TODO(titzer): use ValueChecker / ValueUtil
void CheckInt32Constant(Node* n, int32_t expected) {
Int32Matcher m(n);
CHECK(m.HasResolvedValue());
CHECK_EQ(expected, m.ResolvedValue());
}
void CheckInt64Constant(Node* n, int64_t expected) {
Int64Matcher m(n);
CHECK(m.HasResolvedValue());
CHECK_EQ(expected, m.ResolvedValue());
}
void CheckUint32Constant(Node* n, uint32_t expected) {
Uint32Matcher m(n);
CHECK(m.HasResolvedValue());
CHECK_EQ(static_cast<int>(expected), static_cast<int>(m.ResolvedValue()));
}
void CheckFloat64Constant(Node* n, double expected) {
Float64Matcher m(n);
CHECK(m.HasResolvedValue());
CHECK_DOUBLE_EQ(expected, m.ResolvedValue());
}
void CheckFloat32Constant(Node* n, float expected) {
CHECK_EQ(IrOpcode::kFloat32Constant, n->opcode());
float fval = OpParameter<float>(n->op());
CHECK_FLOAT_EQ(expected, fval);
}
void CheckHeapConstant(Node* n, Tagged<HeapObject> expected) {
HeapObjectMatcher m(n);
CHECK(m.HasResolvedValue());
CHECK_EQ(expected, *m.ResolvedValue());
}
void CheckNumberConstant(Node* n, double expected) {
NumberMatcher m(n);
CHECK_EQ(IrOpcode::kNumberConstant, n->opcode());
CHECK(m.HasResolvedValue());
CHECK_DOUBLE_EQ(expected, m.ResolvedValue());
}
Node* Parameter(int index = 0) {
Node* n = graph()->NewNode(common()->Parameter(index), graph()->start());
NodeProperties::SetType(n, Type::Any());
return n;
}
Node* Return(Node* input) {
Node* n = graph()->NewNode(common()->Return(), jsgraph()->Int32Constant(0),
input, graph()->start(), graph()->start());
return n;
}
void CheckTypeError(MachineRepresentation from, Type from_type,
MachineRepresentation to) {
changer()->testing_type_errors_ = true;
changer()->type_error_ = false;
Node* n = Parameter(0);
Node* use = Return(n);
Node* c = changer()->GetRepresentationFor(n, from, from_type, use,
UseInfo(to, Truncation::None()));
CHECK(changer()->type_error_);
CHECK_EQ(n, c);
}
void CheckNop(MachineRepresentation from, Type from_type,
MachineRepresentation to) {
Node* n = Parameter(0);
Node* use = Return(n);
Node* c = changer()->GetRepresentationFor(n, from, from_type, use,
UseInfo(to, Truncation::None()));
CHECK_EQ(n, c);
}
};
const MachineType kMachineTypes[] = {
MachineType::Float32(), MachineType::Float64(), MachineType::Int8(),
MachineType::Uint8(), MachineType::Int16(), MachineType::Uint16(),
MachineType::Int32(), MachineType::Uint32(), MachineType::Int64(),
MachineType::Uint64(), MachineType::AnyTagged()};
TEST(BoolToBit_constant) {
RepresentationChangerTester r;
Node* true_node = r.jsgraph()->TrueConstant();
Node* true_use = r.Return(true_node);
Node* true_bit = r.changer()->GetRepresentationFor(
true_node, MachineRepresentation::kTagged, Type::None(), true_use,
UseInfo(MachineRepresentation::kBit, Truncation::None()));
r.CheckInt32Constant(true_bit, 1);
Node* false_node = r.jsgraph()->FalseConstant();
Node* false_use = r.Return(false_node);
Node* false_bit = r.changer()->GetRepresentationFor(
false_node, MachineRepresentation::kTagged, Type::None(), false_use,
UseInfo(MachineRepresentation::kBit, Truncation::None()));
r.CheckInt32Constant(false_bit, 0);
}
TEST(ToTagged_constant) {
RepresentationChangerTester r;
for (double i : ValueHelper::float64_vector()) {
Node* n = r.jsgraph()->ConstantNoHole(i);
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kFloat64, Type::None(), use,
UseInfo(MachineRepresentation::kTagged, Truncation::None()));
r.CheckNumberConstant(c, i);
}
for (int i : ValueHelper::int32_vector()) {
Node* n = r.jsgraph()->ConstantNoHole(i);
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kWord32, Type::Signed32(), use,
UseInfo(MachineRepresentation::kTagged, Truncation::None()));
r.CheckNumberConstant(c, i);
}
for (uint32_t i : ValueHelper::uint32_vector()) {
Node* n = r.jsgraph()->ConstantNoHole(i);
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kWord32, Type::Unsigned32(), use,
UseInfo(MachineRepresentation::kTagged, Truncation::None()));
r.CheckNumberConstant(c, i);
}
}
TEST(ToFloat64_constant) {
RepresentationChangerTester r;
for (double i : ValueHelper::float64_vector()) {
Node* n = r.jsgraph()->ConstantNoHole(i);
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kTagged, Type::None(), use,
UseInfo(MachineRepresentation::kFloat64, Truncation::None()));
r.CheckFloat64Constant(c, i);
}
for (int i : ValueHelper::int32_vector()) {
Node* n = r.jsgraph()->ConstantNoHole(i);
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kWord32, Type::Signed32(), use,
UseInfo(MachineRepresentation::kFloat64, Truncation::None()));
r.CheckFloat64Constant(c, i);
}
for (uint32_t i : ValueHelper::uint32_vector()) {
Node* n = r.jsgraph()->ConstantNoHole(i);
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kWord32, Type::Unsigned32(), use,
UseInfo(MachineRepresentation::kFloat64, Truncation::None()));
r.CheckFloat64Constant(c, i);
}
{
Node* n = r.jsgraph()->ConstantNoHole(0);
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kWord64, Type::Range(0, 0, r.zone()), use,
UseInfo(MachineRepresentation::kFloat64, Truncation::None()));
r.CheckFloat64Constant(c, 0);
}
}
static bool IsFloat32Int32(int32_t val) {
return val >= -(1 << 23) && val <= (1 << 23);
}
static bool IsFloat32Uint32(uint32_t val) { return val <= (1 << 23); }
TEST(ToFloat32_constant) {
RepresentationChangerTester r;
for (double i : ValueHelper::float32_vector()) {
Node* n = r.jsgraph()->ConstantNoHole(i);
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kTagged, Type::None(), use,
UseInfo(MachineRepresentation::kFloat32, Truncation::None()));
r.CheckFloat32Constant(c, i);
}
for (int i : ValueHelper::int32_vector()) {
if (!IsFloat32Int32(i)) continue;
Node* n = r.jsgraph()->ConstantNoHole(i);
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kWord32, Type::Signed32(), use,
UseInfo(MachineRepresentation::kFloat32, Truncation::None()));
r.CheckFloat32Constant(c, static_cast<float>(i));
}
for (uint32_t i : ValueHelper::uint32_vector()) {
if (!IsFloat32Uint32(i)) continue;
Node* n = r.jsgraph()->ConstantNoHole(i);
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kWord32, Type::Unsigned32(), use,
UseInfo(MachineRepresentation::kFloat32, Truncation::None()));
r.CheckFloat32Constant(c, static_cast<float>(i));
}
}
TEST(ToInt32_constant) {
RepresentationChangerTester r;
{
FOR_INT32_INPUTS(i) {
const double value = static_cast<double>(i);
Node* n = r.jsgraph()->ConstantNoHole(value);
NodeProperties::SetType(n, Type::Constant(value, r.zone()));
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kTagged, Type::Signed32(), use,
UseInfo(MachineRepresentation::kWord32, Truncation::None()));
r.CheckInt32Constant(c, i);
}
}
}
TEST(ToUint32_constant) {
RepresentationChangerTester r;
FOR_UINT32_INPUTS(i) {
const double value = static_cast<double>(i);
Node* n = r.jsgraph()->ConstantNoHole(value);
NodeProperties::SetType(n, Type::Constant(value, r.zone()));
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kTagged, Type::Unsigned32(), use,
UseInfo(MachineRepresentation::kWord32, Truncation::None()));
r.CheckUint32Constant(c, i);
}
}
TEST(ToInt64_constant) {
RepresentationChangerTester r;
FOR_INT32_INPUTS(i) {
const double value = static_cast<double>(i);
Node* n = r.jsgraph()->ConstantNoHole(value);
NodeProperties::SetType(n, Type::Constant(value, r.zone()));
Node* use = r.Return(n);
Node* c = r.changer()->GetRepresentationFor(
n, MachineRepresentation::kTagged, TypeCache::Get()->kSafeInteger, use,
UseInfo(MachineRepresentation::kWord64, Truncation::None()));
r.CheckInt64Constant(c, i);
}
}
static void CheckChange(IrOpcode::Value expected, MachineRepresentation from,
Type from_type, UseInfo use_info) {
RepresentationChangerTester r;
Node* n = r.Parameter();
Node* use = r.Return(n);
Node* c =
r.changer()->GetRepresentationFor(n, from, from_type, use, use_info);
CHECK_NE(c, n);
CHECK_EQ(expected, c->opcode());
CHECK_EQ(n, c->InputAt(0));
if (expected == IrOpcode::kCheckedFloat64ToInt32 ||
expected == IrOpcode::kCheckedFloat64ToInt64) {
CheckForMinusZeroMode mode =
from_type.Maybe(Type::MinusZero())
? use_info.minus_zero_check()
: CheckForMinusZeroMode::kDontCheckForMinusZero;
CHECK_EQ(mode, CheckMinusZeroParametersOf(c->op()).mode());
}
}
static void CheckChange(IrOpcode::Value expected, MachineRepresentation from,
Type from_type, MachineRepresentation to) {
CheckChange(expected, from, from_type, UseInfo(to, Truncation::Any()));
}
static void CheckTwoChanges(IrOpcode::Value expected2,
IrOpcode::Value expected1,
MachineRepresentation from, Type from_type,
MachineRepresentation to, UseInfo use_info) {
RepresentationChangerTester r;
Node* n = r.Parameter();
Node* use = r.Return(n);
Node* c1 =
r.changer()->GetRepresentationFor(n, from, from_type, use, use_info);
CHECK_NE(c1, n);
CHECK_EQ(expected1, c1->opcode());
Node* c2 = c1->InputAt(0);
CHECK_NE(c2, n);
CHECK_EQ(expected2, c2->opcode());
CHECK_EQ(n, c2->InputAt(0));
}
static void CheckTwoChanges(IrOpcode::Value expected2,
IrOpcode::Value expected1,
MachineRepresentation from, Type from_type,
MachineRepresentation to) {
CheckTwoChanges(expected2, expected1, from, from_type, to,
UseInfo(to, Truncation::None()));
}
static void CheckChange(IrOpcode::Value expected, MachineRepresentation from,
Type from_type, MachineRepresentation to,
UseInfo use_info) {
RepresentationChangerTester r;
Node* n = r.Parameter();
Node* use = r.Return(n);
Node* c =
r.changer()->GetRepresentationFor(n, from, from_type, use, use_info);
CHECK_NE(c, n);
CHECK_EQ(expected, c->opcode());
CHECK_EQ(n, c->InputAt(0));
}
TEST(Word64) {
CheckChange(IrOpcode::kChangeInt32ToInt64, MachineRepresentation::kWord8,
TypeCache::Get()->kInt8, MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeUint32ToUint64, MachineRepresentation::kWord8,
TypeCache::Get()->kUint8, MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeInt32ToInt64, MachineRepresentation::kWord16,
TypeCache::Get()->kInt16, MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeUint32ToUint64, MachineRepresentation::kWord16,
TypeCache::Get()->kUint16, MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeInt32ToInt64, MachineRepresentation::kWord32,
Type::Signed32(), MachineRepresentation::kWord64);
CheckChange(
IrOpcode::kChangeInt32ToInt64, MachineRepresentation::kWord32,
Type::Signed32OrMinusZero(), MachineRepresentation::kWord64,
UseInfo(MachineRepresentation::kWord64, Truncation::Any(kIdentifyZeros)));
CheckChange(IrOpcode::kChangeUint32ToUint64, MachineRepresentation::kWord32,
Type::Unsigned32(), MachineRepresentation::kWord64);
CheckChange(
IrOpcode::kChangeUint32ToUint64, MachineRepresentation::kWord32,
Type::Unsigned32OrMinusZero(), MachineRepresentation::kWord64,
UseInfo(MachineRepresentation::kWord64, Truncation::Any(kIdentifyZeros)));
CheckChange(IrOpcode::kTruncateInt64ToInt32, MachineRepresentation::kWord64,
Type::Signed32(), MachineRepresentation::kWord32);
CheckChange(IrOpcode::kTruncateInt64ToInt32, MachineRepresentation::kWord64,
Type::Unsigned32(), MachineRepresentation::kWord32);
CheckChange(IrOpcode::kTruncateInt64ToInt32, MachineRepresentation::kWord64,
TypeCache::Get()->kSafeInteger, MachineRepresentation::kWord32,
UseInfo::TruncatingWord32());
CheckChange(
IrOpcode::kCheckedInt64ToInt32, MachineRepresentation::kWord64,
TypeCache::Get()->kSafeInteger, MachineRepresentation::kWord32,
UseInfo::CheckedSigned32AsWord32(kIdentifyZeros, FeedbackSource()));
CheckChange(
IrOpcode::kCheckedUint64ToInt32, MachineRepresentation::kWord64,
TypeCache::Get()->kPositiveSafeInteger, MachineRepresentation::kWord32,
UseInfo::CheckedSigned32AsWord32(kIdentifyZeros, FeedbackSource()));
CheckChange(IrOpcode::kChangeFloat64ToInt64, MachineRepresentation::kFloat64,
Type::Signed32(), MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeFloat64ToInt64, MachineRepresentation::kFloat64,
Type::Unsigned32(), MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeFloat64ToInt64, MachineRepresentation::kFloat64,
TypeCache::Get()->kSafeInteger, MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeFloat64ToInt64, MachineRepresentation::kFloat64,
TypeCache::Get()->kDoubleRepresentableInt64,
MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeFloat64ToUint64, MachineRepresentation::kFloat64,
TypeCache::Get()->kDoubleRepresentableUint64,
MachineRepresentation::kWord64);
CheckChange(
IrOpcode::kCheckedFloat64ToInt64, MachineRepresentation::kFloat64,
Type::Number(), MachineRepresentation::kWord64,
UseInfo::CheckedSigned64AsWord64(kIdentifyZeros, FeedbackSource()));
CheckChange(IrOpcode::kChangeInt64ToFloat64, MachineRepresentation::kWord64,
Type::Signed32(), MachineRepresentation::kFloat64);
CheckChange(IrOpcode::kChangeInt64ToFloat64, MachineRepresentation::kWord64,
Type::Unsigned32(), MachineRepresentation::kFloat64);
CheckChange(IrOpcode::kChangeInt64ToFloat64, MachineRepresentation::kWord64,
TypeCache::Get()->kSafeInteger, MachineRepresentation::kFloat64);
CheckTwoChanges(IrOpcode::kChangeFloat32ToFloat64,
IrOpcode::kChangeFloat64ToInt64,
MachineRepresentation::kFloat32, Type::Signed32(),
MachineRepresentation::kWord64);
CheckTwoChanges(IrOpcode::kChangeFloat32ToFloat64,
IrOpcode::kChangeFloat64ToInt64,
MachineRepresentation::kFloat32, Type::Unsigned32(),
MachineRepresentation::kWord64);
CheckTwoChanges(IrOpcode::kChangeFloat32ToFloat64,
IrOpcode::kChangeFloat64ToInt64,
MachineRepresentation::kFloat32,
TypeCache::Get()->kDoubleRepresentableInt64,
MachineRepresentation::kWord64);
CheckTwoChanges(IrOpcode::kChangeFloat32ToFloat64,
IrOpcode::kChangeFloat64ToUint64,
MachineRepresentation::kFloat32,
TypeCache::Get()->kDoubleRepresentableUint64,
MachineRepresentation::kWord64);
CheckTwoChanges(
IrOpcode::kChangeFloat32ToFloat64, IrOpcode::kCheckedFloat64ToInt64,
MachineRepresentation::kFloat32, Type::Number(),
MachineRepresentation::kWord64,
UseInfo::CheckedSigned64AsWord64(kIdentifyZeros, FeedbackSource()));
CheckTwoChanges(IrOpcode::kChangeInt64ToFloat64,
IrOpcode::kTruncateFloat64ToFloat32,
MachineRepresentation::kWord64, Type::Signed32(),
MachineRepresentation::kFloat32);
CheckChange(IrOpcode::kChangeTaggedToInt64, MachineRepresentation::kTagged,
Type::Signed32(), MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeTaggedToInt64, MachineRepresentation::kTagged,
Type::Unsigned32(), MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeTaggedToInt64, MachineRepresentation::kTagged,
TypeCache::Get()->kSafeInteger, MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeTaggedToInt64, MachineRepresentation::kTagged,
TypeCache::Get()->kDoubleRepresentableInt64,
MachineRepresentation::kWord64);
CheckChange(IrOpcode::kChangeTaggedSignedToInt64,
MachineRepresentation::kTaggedSigned, Type::SignedSmall(),
MachineRepresentation::kWord64);
CheckChange(
IrOpcode::kCheckedTaggedToInt64, MachineRepresentation::kTagged,
Type::Number(), MachineRepresentation::kWord64,
UseInfo::CheckedSigned64AsWord64(kIdentifyZeros, FeedbackSource()));
CheckChange(
IrOpcode::kCheckedTaggedToInt64, MachineRepresentation::kTaggedPointer,
Type::Number(), MachineRepresentation::kWord64,
UseInfo::CheckedSigned64AsWord64(kIdentifyZeros, FeedbackSource()));
CheckTwoChanges(IrOpcode::kTruncateInt64ToInt32,
IrOpcode::kChangeInt31ToTaggedSigned,
MachineRepresentation::kWord64, Type::Signed31(),
MachineRepresentation::kTagged);
CheckTwoChanges(IrOpcode::kTruncateInt64ToInt32,
IrOpcode::kChangeInt32ToTagged,
MachineRepresentation::kWord64, Type::Signed32(),
MachineRepresentation::kTagged);
CheckTwoChanges(IrOpcode::kTruncateInt64ToInt32,
IrOpcode::kChangeUint32ToTagged,
MachineRepresentation::kWord64, Type::Unsigned32(),
MachineRepresentation::kTagged);
CheckChange(IrOpcode::kChangeInt64ToTagged, MachineRepresentation::kWord64,
TypeCache::Get()->kSafeInteger, MachineRepresentation::kTagged);
CheckChange(IrOpcode::kChangeUint64ToTagged, MachineRepresentation::kWord64,
TypeCache::Get()->kPositiveSafeInteger,
MachineRepresentation::kTagged);
CheckTwoChanges(IrOpcode::kTruncateInt64ToInt32,
IrOpcode::kChangeInt31ToTaggedSigned,
MachineRepresentation::kWord64, Type::Signed31(),
MachineRepresentation::kTaggedSigned);
if (SmiValuesAre32Bits()) {
CheckTwoChanges(IrOpcode::kTruncateInt64ToInt32,
IrOpcode::kChangeInt32ToTagged,
MachineRepresentation::kWord64, Type::Signed32(),
MachineRepresentation::kTaggedSigned);
}
CheckChange(IrOpcode::kCheckedInt64ToTaggedSigned,
MachineRepresentation::kWord64, TypeCache::Get()->kSafeInteger,
MachineRepresentation::kTaggedSigned,
UseInfo::CheckedSignedSmallAsTaggedSigned(FeedbackSource()));
CheckChange(IrOpcode::kCheckedUint64ToTaggedSigned,
MachineRepresentation::kWord64,
TypeCache::Get()->kPositiveSafeInteger,
MachineRepresentation::kTaggedSigned,
UseInfo::CheckedSignedSmallAsTaggedSigned(FeedbackSource()));
CheckTwoChanges(
IrOpcode::kChangeInt64ToFloat64, IrOpcode::kChangeFloat64ToTaggedPointer,
MachineRepresentation::kWord64, TypeCache::Get()->kSafeInteger,
MachineRepresentation::kTaggedPointer);
}
TEST(SingleChanges) {
CheckChange(IrOpcode::kChangeTaggedToBit, MachineRepresentation::kTagged,
Type::Boolean(), MachineRepresentation::kBit);
CheckChange(IrOpcode::kChangeBitToTagged, MachineRepresentation::kBit,
Type::Boolean(), MachineRepresentation::kTagged);
CheckChange(IrOpcode::kChangeInt31ToTaggedSigned,
MachineRepresentation::kWord32, Type::Signed31(),
MachineRepresentation::kTagged);
CheckChange(IrOpcode::kChangeInt32ToTagged, MachineRepresentation::kWord32,
Type::Signed32(), MachineRepresentation::kTagged);
CheckChange(IrOpcode::kChangeUint32ToTagged, MachineRepresentation::kWord32,
Type::Unsigned32(), MachineRepresentation::kTagged);
CheckChange(IrOpcode::kChangeFloat64ToTagged, MachineRepresentation::kFloat64,
Type::Number(), MachineRepresentation::kTagged);
CheckTwoChanges(IrOpcode::kChangeFloat64ToInt32,
IrOpcode::kChangeInt31ToTaggedSigned,
MachineRepresentation::kFloat64, Type::Signed31(),
MachineRepresentation::kTagged);
CheckTwoChanges(IrOpcode::kChangeFloat64ToInt32,
IrOpcode::kChangeInt32ToTagged,
MachineRepresentation::kFloat64, Type::Signed32(),
MachineRepresentation::kTagged);
CheckTwoChanges(IrOpcode::kChangeFloat64ToUint32,
IrOpcode::kChangeUint32ToTagged,
MachineRepresentation::kFloat64, Type::Unsigned32(),
MachineRepresentation::kTagged);
CheckChange(IrOpcode::kChangeTaggedToInt32, MachineRepresentation::kTagged,
Type::Signed32(), MachineRepresentation::kWord32);
CheckChange(IrOpcode::kChangeTaggedToUint32, MachineRepresentation::kTagged,
Type::Unsigned32(), MachineRepresentation::kWord32);
CheckChange(IrOpcode::kChangeTaggedToFloat64, MachineRepresentation::kTagged,
Type::Number(), MachineRepresentation::kFloat64);
CheckChange(IrOpcode::kTruncateTaggedToFloat64,
MachineRepresentation::kTagged, Type::NumberOrUndefined(),
UseInfo(MachineRepresentation::kFloat64,
Truncation::OddballAndBigIntToNumber()));
CheckChange(IrOpcode::kChangeTaggedToFloat64, MachineRepresentation::kTagged,
Type::Signed31(), MachineRepresentation::kFloat64);
// Int32,Uint32 <-> Float64 are actually machine conversions.
CheckChange(IrOpcode::kChangeInt32ToFloat64, MachineRepresentation::kWord32,
Type::Signed32(), MachineRepresentation::kFloat64);
CheckChange(IrOpcode::kChangeInt32ToFloat64, MachineRepresentation::kWord32,
Type::Signed32OrMinusZero(), MachineRepresentation::kFloat64,
UseInfo(MachineRepresentation::kFloat64,
Truncation::Any(kIdentifyZeros)));
CheckChange(IrOpcode::kChangeUint32ToFloat64, MachineRepresentation::kWord32,
Type::Unsigned32(), MachineRepresentation::kFloat64);
CheckChange(IrOpcode::kChangeFloat64ToInt32, MachineRepresentation::kFloat64,
Type::Signed32(), MachineRepresentation::kWord32);
CheckChange(IrOpcode::kChangeFloat64ToUint32, MachineRepresentation::kFloat64,
Type::Unsigned32(), MachineRepresentation::kWord32);
CheckChange(IrOpcode::kTruncateFloat64ToFloat32,
MachineRepresentation::kFloat64, Type::Number(),
MachineRepresentation::kFloat32);
// Int32,Uint32 <-> Float32 require two changes.
CheckTwoChanges(IrOpcode::kChangeInt32ToFloat64,
IrOpcode::kTruncateFloat64ToFloat32,
MachineRepresentation::kWord32, Type::Signed32(),
MachineRepresentation::kFloat32);
CheckTwoChanges(IrOpcode::kChangeUint32ToFloat64,
IrOpcode::kTruncateFloat64ToFloat32,
MachineRepresentation::kWord32, Type::Unsigned32(),
MachineRepresentation::kFloat32);
CheckTwoChanges(IrOpcode::kChangeFloat32ToFloat64,
IrOpcode::kChangeFloat64ToInt32,
MachineRepresentation::kFloat32, Type::Signed32(),
MachineRepresentation::kWord32);
CheckTwoChanges(IrOpcode::kChangeFloat32ToFloat64,
IrOpcode::kChangeFloat64ToUint32,
MachineRepresentation::kFloat32, Type::Unsigned32(),
MachineRepresentation::kWord32);
// Float32 <-> Tagged require two changes.
CheckTwoChanges(IrOpcode::kChangeFloat32ToFloat64,
IrOpcode::kChangeFloat64ToTagged,
MachineRepresentation::kFloat32, Type::Number(),
MachineRepresentation::kTagged);
CheckTwoChanges(IrOpcode::kChangeTaggedToFloat64,
IrOpcode::kTruncateFloat64ToFloat32,
MachineRepresentation::kTagged, Type::Number(),
MachineRepresentation::kFloat32);
}
TEST(SignednessInWord32) {
RepresentationChangerTester r;
CheckChange(IrOpcode::kChangeTaggedToInt32, MachineRepresentation::kTagged,
Type::Signed32(), MachineRepresentation::kWord32);
CheckChange(IrOpcode::kChangeTaggedToUint32, MachineRepresentation::kTagged,
Type::Unsigned32(), MachineRepresentation::kWord32);
CheckChange(IrOpcode::kChangeInt32ToFloat64, MachineRepresentation::kWord32,
Type::Signed32(), MachineRepresentation::kFloat64);
CheckChange(IrOpcode::kChangeFloat64ToInt32, MachineRepresentation::kFloat64,
Type::Signed32(), MachineRepresentation::kWord32);
CheckChange(IrOpcode::kTruncateFloat64ToWord32,
MachineRepresentation::kFloat64, Type::Number(),
MachineRepresentation::kWord32,
UseInfo(MachineRepresentation::kWord32, Truncation::Word32()));
CheckChange(IrOpcode::kCheckedTruncateTaggedToWord32,
MachineRepresentation::kTagged, Type::NonInternal(),
MachineRepresentation::kWord32,
UseInfo::CheckedNumberOrOddballAsWord32(FeedbackSource()));
CheckTwoChanges(IrOpcode::kChangeInt32ToFloat64,
IrOpcode::kTruncateFloat64ToFloat32,
MachineRepresentation::kWord32, Type::Signed32(),
MachineRepresentation::kFloat32);
CheckTwoChanges(IrOpcode::kChangeFloat32ToFloat64,
IrOpcode::kTruncateFloat64ToWord32,
MachineRepresentation::kFloat32, Type::Number(),
MachineRepresentation::kWord32);
CheckChange(
IrOpcode::kCheckedUint32ToInt32, MachineRepresentation::kWord32,
Type::Unsigned32(),
UseInfo::CheckedSigned32AsWord32(kIdentifyZeros, FeedbackSource()));
}
static void TestMinusZeroCheck(IrOpcode::Value expected, Type from_type) {
RepresentationChangerTester r;
CheckChange(
expected, MachineRepresentation::kFloat64, from_type,
UseInfo::CheckedSignedSmallAsWord32(kDistinguishZeros, FeedbackSource()));
CheckChange(
expected, MachineRepresentation::kFloat64, from_type,
UseInfo::CheckedSignedSmallAsWord32(kIdentifyZeros, FeedbackSource()));
CheckChange(
expected, MachineRepresentation::kFloat64, from_type,
UseInfo::CheckedSigned32AsWord32(kDistinguishZeros, FeedbackSource()));
CheckChange(
expected, MachineRepresentation::kFloat64, from_type,
UseInfo::CheckedSigned32AsWord32(kDistinguishZeros, FeedbackSource()));
}
TEST(MinusZeroCheck) {
TestMinusZeroCheck(IrOpcode::kCheckedFloat64ToInt32, Type::NumberOrOddball());
// PlainNumber cannot be minus zero so the minus zero check should be
// eliminated.
TestMinusZeroCheck(IrOpcode::kCheckedFloat64ToInt32, Type::PlainNumber());
}
TEST(Nops) {
RepresentationChangerTester r;
// X -> X is always a nop for any single representation X.
for (size_t i = 0; i < arraysize(kMachineTypes); i++) {
r.CheckNop(kMachineTypes[i].representation(), Type::Number(),
kMachineTypes[i].representation());
}
// 32-bit floats.
r.CheckNop(MachineRepresentation::kFloat32, Type::Number(),
MachineRepresentation::kFloat32);
// 32-bit words can be used as smaller word sizes and vice versa, because
// loads from memory implicitly sign or zero extend the value to the
// full machine word size, and stores implicitly truncate.
r.CheckNop(MachineRepresentation::kWord32, Type::Signed32(),
MachineRepresentation::kWord8);
r.CheckNop(MachineRepresentation::kWord32, Type::Signed32(),
MachineRepresentation::kWord16);
r.CheckNop(MachineRepresentation::kWord32, Type::Signed32(),
MachineRepresentation::kWord32);
r.CheckNop(MachineRepresentation::kWord8, Type::Signed32(),
MachineRepresentation::kWord32);
r.CheckNop(MachineRepresentation::kWord16, Type::Signed32(),
MachineRepresentation::kWord32);
// kRepBit (result of comparison) is implicitly a wordish thing.
r.CheckNop(MachineRepresentation::kBit, Type::Boolean(),
MachineRepresentation::kWord8);
r.CheckNop(MachineRepresentation::kBit, Type::Boolean(),
MachineRepresentation::kWord16);
r.CheckNop(MachineRepresentation::kBit, Type::Boolean(),
MachineRepresentation::kWord32);
}
TEST(TypeErrors) {
RepresentationChangerTester r;
// Floats cannot be implicitly converted to/from comparison conditions.
r.CheckTypeError(MachineRepresentation::kBit, Type::Number(),
MachineRepresentation::kFloat32);
r.CheckTypeError(MachineRepresentation::kBit, Type::Boolean(),
MachineRepresentation::kFloat32);
// Word64 is internal and shouldn't be implicitly converted.
r.CheckTypeError(MachineRepresentation::kWord64, Type::Internal(),
MachineRepresentation::kTagged);
r.CheckTypeError(MachineRepresentation::kTagged, Type::Number(),
MachineRepresentation::kWord64);
r.CheckTypeError(MachineRepresentation::kTagged, Type::Boolean(),
MachineRepresentation::kWord64);
r.CheckTypeError(MachineRepresentation::kWord64, Type::Internal(),
MachineRepresentation::kWord32);
r.CheckTypeError(MachineRepresentation::kWord32, Type::Number(),
MachineRepresentation::kWord64);
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,869 @@
// Copyright 2014 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/memory.h"
#include "src/codegen/external-reference.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/codegen-tester.h"
#include "test/common/value-helper.h"
#if V8_ENABLE_WEBASSEMBLY
#include "src/wasm/wasm-external-refs.h"
#endif // V8_ENABLE_WEBASSEMBLY
namespace v8 {
namespace internal {
namespace compiler {
template <typename InType, typename OutType, typename Iterable>
void TestExternalReference_ConvertOp(ExternalReference ref,
void (*wrapper)(Address),
Iterable inputs) {
BufferedRawMachineAssemblerTester<int32_t> m;
constexpr size_t kBufferSize = std::max(sizeof(InType), sizeof(OutType));
uint8_t buffer[kBufferSize] = {0};
Address buffer_addr = reinterpret_cast<Address>(buffer);
Node* function = m.ExternalConstant(ref);
m.CallCFunction(
function, MachineType::Pointer(),
std::make_pair(MachineType::Pointer(), m.PointerConstant(buffer)));
m.Return(m.Int32Constant(4356));
for (InType input : inputs) {
WriteUnalignedValue<InType>(buffer_addr, input);
CHECK_EQ(4356, m.Call());
OutType output = ReadUnalignedValue<OutType>(buffer_addr);
WriteUnalignedValue<InType>(buffer_addr, input);
wrapper(buffer_addr);
OutType expected_output = ReadUnalignedValue<OutType>(buffer_addr);
CHECK_EQ(expected_output, output);
}
}
template <typename InType, typename OutType, typename Iterable>
void TestExternalReference_ConvertOpWithOutputAndReturn(
ExternalReference ref, int32_t (*wrapper)(Address), Iterable inputs) {
BufferedRawMachineAssemblerTester<int32_t> m;
constexpr size_t kBufferSize = std::max(sizeof(InType), sizeof(OutType));
uint8_t buffer[kBufferSize] = {0};
Address buffer_addr = reinterpret_cast<Address>(buffer);
Node* function = m.ExternalConstant(ref);
m.Return(m.CallCFunction(
function, MachineType::Int32(),
std::make_pair(MachineType::Pointer(), m.PointerConstant(buffer))));
for (InType input : inputs) {
WriteUnalignedValue<InType>(buffer_addr, input);
int32_t ret = m.Call();
OutType output = ReadUnalignedValue<OutType>(buffer_addr);
WriteUnalignedValue<InType>(buffer_addr, input);
int32_t expected_ret = wrapper(buffer_addr);
OutType expected_output = ReadUnalignedValue<OutType>(buffer_addr);
CHECK_EQ(expected_ret, ret);
CHECK_EQ(expected_output, output);
}
}
template <typename InType, typename OutType, typename Iterable>
void TestExternalReference_ConvertOpWithReturn(ExternalReference ref,
OutType (*wrapper)(InType),
Iterable inputs) {
static_assert(std::is_same_v<uint32_t, InType> ||
std::is_same_v<uint64_t, InType>);
MachineType input_type = std::is_same_v<uint32_t, InType>
? MachineType::Uint32()
: MachineType::Uint64();
BufferedRawMachineAssemblerTester<uint32_t> m(input_type);
Node* function = m.ExternalConstant(ref);
m.Return(m.CallCFunction(function, MachineType::Int32(),
{{input_type, m.Parameter(0)}}));
for (InType input : inputs) {
OutType ret = m.Call(input);
OutType expected_ret = wrapper(input);
CHECK_EQ(expected_ret, ret);
}
}
template <typename Type>
bool isnan(Type value) {
return false;
}
template <>
bool isnan<float>(float value) {
return std::isnan(value);
}
template <>
bool isnan<double>(double value) {
return std::isnan(value);
}
template <typename Type, typename Iterable>
void TestExternalReference_UnOp(ExternalReference ref, void (*wrapper)(Address),
Iterable inputs) {
BufferedRawMachineAssemblerTester<int32_t> m;
constexpr size_t kBufferSize = sizeof(Type);
uint8_t buffer[kBufferSize] = {0};
Address buffer_addr = reinterpret_cast<Address>(buffer);
Node* function = m.ExternalConstant(ref);
m.CallCFunction(
function, MachineType::Int32(),
std::make_pair(MachineType::Pointer(), m.PointerConstant(buffer)));
m.Return(m.Int32Constant(4356));
for (Type input : inputs) {
WriteUnalignedValue<Type>(buffer_addr, input);
CHECK_EQ(4356, m.Call());
Type output = ReadUnalignedValue<Type>(buffer_addr);
WriteUnalignedValue<Type>(buffer_addr, input);
wrapper(buffer_addr);
Type expected_output = ReadUnalignedValue<Type>(buffer_addr);
if (isnan(expected_output) && isnan(output)) continue;
CHECK_EQ(expected_output, output);
}
}
template <typename Type, typename Iterable>
void TestExternalReference_BinOp(ExternalReference ref,
void (*wrapper)(Address), Iterable inputs) {
BufferedRawMachineAssemblerTester<int32_t> m;
constexpr size_t kBufferSize = 2 * sizeof(Type);
uint8_t buffer[kBufferSize] = {0};
Address buffer_addr = reinterpret_cast<Address>(buffer);
Node* function = m.ExternalConstant(ref);
m.CallCFunction(
function, MachineType::Int32(),
std::make_pair(MachineType::Pointer(), m.PointerConstant(buffer)));
m.Return(m.Int32Constant(4356));
for (Type input1 : inputs) {
for (Type input2 : inputs) {
WriteUnalignedValue<Type>(buffer_addr, input1);
WriteUnalignedValue<Type>(buffer_addr + sizeof(Type), input2);
CHECK_EQ(4356, m.Call());
Type output = ReadUnalignedValue<Type>(buffer_addr);
WriteUnalignedValue<Type>(buffer_addr, input1);
WriteUnalignedValue<Type>(buffer_addr + sizeof(Type), input2);
wrapper(buffer_addr);
Type expected_output = ReadUnalignedValue<Type>(buffer_addr);
if (isnan(expected_output) && isnan(output)) continue;
CHECK_EQ(expected_output, output);
}
}
}
template <typename Type, typename Iterable>
void TestExternalReference_BinOpWithReturn(ExternalReference ref,
int32_t (*wrapper)(Address),
Iterable inputs) {
BufferedRawMachineAssemblerTester<int32_t> m;
constexpr size_t kBufferSize = 2 * sizeof(Type);
uint8_t buffer[kBufferSize] = {0};
Address buffer_addr = reinterpret_cast<Address>(buffer);
Node* function = m.ExternalConstant(ref);
m.Return(m.CallCFunction(
function, MachineType::Int32(),
std::make_pair(MachineType::Pointer(), m.PointerConstant(buffer))));
for (Type input1 : inputs) {
for (Type input2 : inputs) {
WriteUnalignedValue<Type>(buffer_addr, input1);
WriteUnalignedValue<Type>(buffer_addr + sizeof(Type), input2);
int32_t ret = m.Call();
Type output = ReadUnalignedValue<Type>(buffer_addr);
WriteUnalignedValue<Type>(buffer_addr, input1);
WriteUnalignedValue<Type>(buffer_addr + sizeof(Type), input2);
int32_t expected_ret = wrapper(buffer_addr);
Type expected_output = ReadUnalignedValue<Type>(buffer_addr);
CHECK_EQ(expected_ret, ret);
if (isnan(expected_output) && isnan(output)) continue;
CHECK_EQ(expected_output, output);
}
}
}
#if V8_ENABLE_WEBASSEMBLY
TEST(RunCallF32Trunc) {
ExternalReference ref = ExternalReference::wasm_f32_trunc();
TestExternalReference_UnOp<float>(ref, wasm::f32_trunc_wrapper,
ValueHelper::float32_vector());
}
TEST(RunCallF32Floor) {
ExternalReference ref = ExternalReference::wasm_f32_floor();
TestExternalReference_UnOp<float>(ref, wasm::f32_floor_wrapper,
ValueHelper::float32_vector());
}
TEST(RunCallF32Ceil) {
ExternalReference ref = ExternalReference::wasm_f32_ceil();
TestExternalReference_UnOp<float>(ref, wasm::f32_ceil_wrapper,
ValueHelper::float32_vector());
}
TEST(RunCallF32RoundTiesEven) {
ExternalReference ref = ExternalReference::wasm_f32_nearest_int();
TestExternalReference_UnOp<float>(ref, wasm::f32_nearest_int_wrapper,
ValueHelper::float32_vector());
}
TEST(RunCallF64Trunc) {
ExternalReference ref = ExternalReference::wasm_f64_trunc();
TestExternalReference_UnOp<double>(ref, wasm::f64_trunc_wrapper,
ValueHelper::float64_vector());
}
TEST(RunCallF64Floor) {
ExternalReference ref = ExternalReference::wasm_f64_floor();
TestExternalReference_UnOp<double>(ref, wasm::f64_floor_wrapper,
ValueHelper::float64_vector());
}
TEST(RunCallF64Ceil) {
ExternalReference ref = ExternalReference::wasm_f64_ceil();
TestExternalReference_UnOp<double>(ref, wasm::f64_ceil_wrapper,
ValueHelper::float64_vector());
}
TEST(RunCallF64RoundTiesEven) {
ExternalReference ref = ExternalReference::wasm_f64_nearest_int();
TestExternalReference_UnOp<double>(ref, wasm::f64_nearest_int_wrapper,
ValueHelper::float64_vector());
}
TEST(RunCallInt64ToFloat32) {
ExternalReference ref = ExternalReference::wasm_int64_to_float32();
TestExternalReference_ConvertOp<int64_t, float>(
ref, wasm::int64_to_float32_wrapper, ValueHelper::int64_vector());
}
TEST(RunCallUint64ToFloat32) {
ExternalReference ref = ExternalReference::wasm_uint64_to_float32();
TestExternalReference_ConvertOp<uint64_t, float>(
ref, wasm::uint64_to_float32_wrapper, ValueHelper::uint64_vector());
}
TEST(RunCallInt64ToFloat64) {
ExternalReference ref = ExternalReference::wasm_int64_to_float64();
TestExternalReference_ConvertOp<int64_t, double>(
ref, wasm::int64_to_float64_wrapper, ValueHelper::int64_vector());
}
TEST(RunCallUint64ToFloat64) {
ExternalReference ref = ExternalReference::wasm_uint64_to_float64();
TestExternalReference_ConvertOp<uint64_t, double>(
ref, wasm::uint64_to_float64_wrapper, ValueHelper::uint64_vector());
}
TEST(RunCallFloat32ToInt64) {
ExternalReference ref = ExternalReference::wasm_float32_to_int64();
TestExternalReference_ConvertOpWithOutputAndReturn<float, int64_t>(
ref, wasm::float32_to_int64_wrapper, ValueHelper::float32_vector());
}
TEST(RunCallFloat32ToUint64) {
ExternalReference ref = ExternalReference::wasm_float32_to_uint64();
TestExternalReference_ConvertOpWithOutputAndReturn<float, uint64_t>(
ref, wasm::float32_to_uint64_wrapper, ValueHelper::float32_vector());
}
TEST(RunCallFloat64ToInt64) {
ExternalReference ref = ExternalReference::wasm_float64_to_int64();
TestExternalReference_ConvertOpWithOutputAndReturn<double, int64_t>(
ref, wasm::float64_to_int64_wrapper, ValueHelper::float64_vector());
}
TEST(RunCallFloat64ToUint64) {
ExternalReference ref = ExternalReference::wasm_float64_to_uint64();
TestExternalReference_ConvertOpWithOutputAndReturn<double, uint64_t>(
ref, wasm::float64_to_uint64_wrapper, ValueHelper::float64_vector());
}
TEST(RunCallInt64Div) {
ExternalReference ref = ExternalReference::wasm_int64_div();
TestExternalReference_BinOpWithReturn<int64_t>(ref, wasm::int64_div_wrapper,
ValueHelper::int64_vector());
}
TEST(RunCallInt64Mod) {
ExternalReference ref = ExternalReference::wasm_int64_mod();
TestExternalReference_BinOpWithReturn<int64_t>(ref, wasm::int64_mod_wrapper,
ValueHelper::int64_vector());
}
TEST(RunCallUint64Div) {
ExternalReference ref = ExternalReference::wasm_uint64_div();
TestExternalReference_BinOpWithReturn<uint64_t>(ref, wasm::uint64_div_wrapper,
ValueHelper::uint64_vector());
}
TEST(RunCallUint64Mod) {
ExternalReference ref = ExternalReference::wasm_uint64_mod();
TestExternalReference_BinOpWithReturn<uint64_t>(ref, wasm::uint64_mod_wrapper,
ValueHelper::uint64_vector());
}
TEST(RunCallWord32Ctz) {
ExternalReference ref = ExternalReference::wasm_word32_ctz();
TestExternalReference_ConvertOpWithReturn<uint32_t, uint32_t>(
ref, base::bits::CountTrailingZeros, ValueHelper::int32_vector());
}
TEST(RunCallWord64Ctz) {
// Word64 operations are not supported on 32 bit.
if (kSystemPointerSize == 4) return;
ExternalReference ref = ExternalReference::wasm_word64_ctz();
TestExternalReference_ConvertOpWithReturn<uint64_t, uint32_t>(
ref, base::bits::CountTrailingZeros, ValueHelper::int64_vector());
}
TEST(RunCallWord32Popcnt) {
ExternalReference ref = ExternalReference::wasm_word32_popcnt();
TestExternalReference_ConvertOpWithReturn<uint32_t, uint32_t>(
ref, base::bits::CountPopulation, ValueHelper::int32_vector());
}
TEST(RunCallWord64Popcnt) {
// Word64 operations are not supported on 32 bit.
if (kSystemPointerSize == 4) return;
ExternalReference ref = ExternalReference::wasm_word64_popcnt();
TestExternalReference_ConvertOpWithReturn<uint64_t, uint32_t>(
ref, base::bits::CountPopulation, ValueHelper::int64_vector());
}
TEST(RunCallFloat64Pow) {
ExternalReference ref = ExternalReference::wasm_float64_pow();
TestExternalReference_BinOp<double>(ref, wasm::float64_pow_wrapper,
ValueHelper::float64_vector());
}
#endif // V8_ENABLE_WEBASSEMBLY
template <typename T>
MachineType MachineTypeForCType() {
return MachineType::AnyTagged();
}
template <>
MachineType MachineTypeForCType<int64_t>() {
return MachineType::Int64();
}
template <>
MachineType MachineTypeForCType<int32_t>() {
return MachineType::Int32();
}
template <>
MachineType MachineTypeForCType<double>() {
return MachineType::Float64();
}
#define SIGNATURE_TYPES(TYPE, IDX, VALUE) MachineTypeForCType<TYPE>()
#define PARAM_PAIRS(TYPE, IDX, VALUE) \
std::make_pair(MachineTypeForCType<TYPE>(), m.Parameter(IDX))
#define CALL_ARGS(TYPE, IDX, VALUE) static_cast<TYPE>(VALUE)
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
union Int64OrDoubleUnion {
int64_t int64_t_value;
double double_value;
};
#define CHECK_ARG_I(TYPE, IDX, VALUE) \
(result = result && (arg##IDX.TYPE##_value == VALUE))
#define ReturnType v8::AnyCType
MachineType machine_type = MachineType::Int64();
#define CHECK_RESULT(CALL, EXPECT) \
v8::AnyCType ret = CALL; \
CHECK_EQ(ret.int64_value, EXPECT);
#define IF_SIMULATOR_ADD_SIGNATURE \
EncodedCSignature sig = m.call_descriptor()->ToEncodedCSignature(); \
m.main_isolate()->simulator_data()->AddSignatureForTargetForTesting( \
func_address, sig);
#else // def V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
#define IF_SIMULATOR_ADD_SIGNATURE
#ifdef V8_TARGET_ARCH_64_BIT
#define ReturnType int64_t
MachineType machine_type = MachineType::Int64();
#else // V8_TARGET_ARCH_64_BIT
#define ReturnType int32_t
MachineType machine_type = MachineType::Int32();
#endif // V8_TARGET_ARCH_64_BIT
#define CHECK_ARG_I(TYPE, IDX, VALUE) (result = result && (arg##IDX == VALUE))
#define CHECK_RESULT(CALL, EXPECT) \
int64_t ret = CALL; \
CHECK_EQ(ret, EXPECT);
#endif // V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
#define SIGNATURE_TEST(NAME, SIGNATURE, FUNC) \
TEST(NAME) { \
RawMachineAssemblerTester<ReturnType> m(SIGNATURE(SIGNATURE_TYPES)); \
\
Address func_address = FUNCTION_ADDR(&FUNC); \
ExternalReference::Type func_type = ExternalReference::FAST_C_CALL; \
ApiFunction func(func_address); \
ExternalReference ref = ExternalReference::Create(&func, func_type); \
\
IF_SIMULATOR_ADD_SIGNATURE \
\
Node* function = m.ExternalConstant(ref); \
m.Return(m.CallCFunction(function, machine_type, SIGNATURE(PARAM_PAIRS))); \
\
CHECK_RESULT(m.Call(SIGNATURE(CALL_ARGS)), 42); \
}
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
#define SIGNATURE_ONLY_INT(V) \
V(int64_t, 0, 0), V(int64_t, 1, 1), V(int64_t, 2, 2), V(int64_t, 3, 3), \
V(int64_t, 4, 4), V(int64_t, 5, 5), V(int64_t, 6, 6), V(int64_t, 7, 7), \
V(int64_t, 8, 8), V(int64_t, 9, 9)
Int64OrDoubleUnion func_only_int(
Int64OrDoubleUnion arg0, Int64OrDoubleUnion arg1, Int64OrDoubleUnion arg2,
Int64OrDoubleUnion arg3, Int64OrDoubleUnion arg4, Int64OrDoubleUnion arg5,
Int64OrDoubleUnion arg6, Int64OrDoubleUnion arg7, Int64OrDoubleUnion arg8,
Int64OrDoubleUnion arg9) {
#elif defined(V8_TARGET_ARCH_64_BIT)
#define SIGNATURE_ONLY_INT(V) \
V(int64_t, 0, 0), V(int64_t, 1, 1), V(int64_t, 2, 2), V(int64_t, 3, 3), \
V(int64_t, 4, 4), V(int64_t, 5, 5), V(int64_t, 6, 6), V(int64_t, 7, 7), \
V(int64_t, 8, 8), V(int64_t, 9, 9)
ReturnType func_only_int(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3,
int64_t arg4, int64_t arg5, int64_t arg6, int64_t arg7,
int64_t arg8, int64_t arg9) {
#else // defined(V8_TARGET_ARCH_64_BIT)
#define SIGNATURE_ONLY_INT(V) \
V(int32_t, 0, 0), V(int32_t, 1, 1), V(int32_t, 2, 2), V(int32_t, 3, 3), \
V(int32_t, 4, 4), V(int32_t, 5, 5), V(int32_t, 6, 6), V(int32_t, 7, 7), \
V(int32_t, 8, 8), V(int32_t, 9, 9)
ReturnType func_only_int(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3,
int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7,
int32_t arg8, int32_t arg9) {
#endif
bool result = true;
SIGNATURE_ONLY_INT(CHECK_ARG_I);
CHECK(result);
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion ret;
ret.int64_t_value = 42;
return ret;
#else
return 42;
#endif
}
SIGNATURE_TEST(RunCallWithSignatureOnlyInt, SIGNATURE_ONLY_INT, func_only_int)
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
#define SIGNATURE_ONLY_INT_20(V) \
V(int64_t, 0, 0), V(int64_t, 1, 1), V(int64_t, 2, 2), V(int64_t, 3, 3), \
V(int64_t, 4, 4), V(int64_t, 5, 5), V(int64_t, 6, 6), V(int64_t, 7, 7), \
V(int64_t, 8, 8), V(int64_t, 9, 9), V(int64_t, 10, 10), \
V(int64_t, 11, 11), V(int64_t, 12, 12), V(int64_t, 13, 13), \
V(int64_t, 14, 14), V(int64_t, 15, 15), V(int64_t, 16, 16), \
V(int64_t, 17, 17), V(int64_t, 18, 18), V(int64_t, 19, 19)
Int64OrDoubleUnion func_only_int_20(
Int64OrDoubleUnion arg0, Int64OrDoubleUnion arg1, Int64OrDoubleUnion arg2,
Int64OrDoubleUnion arg3, Int64OrDoubleUnion arg4, Int64OrDoubleUnion arg5,
Int64OrDoubleUnion arg6, Int64OrDoubleUnion arg7, Int64OrDoubleUnion arg8,
Int64OrDoubleUnion arg9, Int64OrDoubleUnion arg10, Int64OrDoubleUnion arg11,
Int64OrDoubleUnion arg12, Int64OrDoubleUnion arg13,
Int64OrDoubleUnion arg14, Int64OrDoubleUnion arg15,
Int64OrDoubleUnion arg16, Int64OrDoubleUnion arg17,
Int64OrDoubleUnion arg18, Int64OrDoubleUnion arg19) {
#elif defined(V8_TARGET_ARCH_64_BIT)
#define SIGNATURE_ONLY_INT_20(V) \
V(int64_t, 0, 0), V(int64_t, 1, 1), V(int64_t, 2, 2), V(int64_t, 3, 3), \
V(int64_t, 4, 4), V(int64_t, 5, 5), V(int64_t, 6, 6), V(int64_t, 7, 7), \
V(int64_t, 8, 8), V(int64_t, 9, 9), V(int64_t, 10, 10), \
V(int64_t, 11, 11), V(int64_t, 12, 12), V(int64_t, 13, 13), \
V(int64_t, 14, 14), V(int64_t, 15, 15), V(int64_t, 16, 16), \
V(int64_t, 17, 17), V(int64_t, 18, 18), V(int64_t, 19, 19)
ReturnType func_only_int_20(int64_t arg0, int64_t arg1, int64_t arg2,
int64_t arg3, int64_t arg4, int64_t arg5,
int64_t arg6, int64_t arg7, int64_t arg8,
int64_t arg9, int64_t arg10, int64_t arg11,
int64_t arg12, int64_t arg13, int64_t arg14,
int64_t arg15, int64_t arg16, int64_t arg17,
int64_t arg18, int64_t arg19) {
#else // defined(V8_TARGET_ARCH_64_BIT)
#define SIGNATURE_ONLY_INT_20(V) \
V(int32_t, 0, 0), V(int32_t, 1, 1), V(int32_t, 2, 2), V(int32_t, 3, 3), \
V(int32_t, 4, 4), V(int32_t, 5, 5), V(int32_t, 6, 6), V(int32_t, 7, 7), \
V(int32_t, 8, 8), V(int32_t, 9, 9), V(int32_t, 10, 10), \
V(int32_t, 11, 11), V(int32_t, 12, 12), V(int32_t, 13, 13), \
V(int32_t, 14, 14), V(int32_t, 15, 15), V(int32_t, 16, 16), \
V(int32_t, 17, 17), V(int32_t, 18, 18), V(int32_t, 19, 19)
ReturnType func_only_int_20(int32_t arg0, int32_t arg1, int32_t arg2,
int32_t arg3, int32_t arg4, int32_t arg5,
int32_t arg6, int32_t arg7, int32_t arg8,
int32_t arg9, int32_t arg10, int32_t arg11,
int32_t arg12, int32_t arg13, int32_t arg14,
int32_t arg15, int32_t arg16, int32_t arg17,
int32_t arg18, int32_t arg19) {
#endif
bool result = true;
SIGNATURE_ONLY_INT_20(CHECK_ARG_I);
CHECK(result);
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion ret;
ret.int64_t_value = 42;
return ret;
#else
return 42;
#endif
}
SIGNATURE_TEST(RunCallWithSignatureOnlyInt20, SIGNATURE_ONLY_INT_20,
func_only_int_20)
#ifdef V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
#define MIXED_SIGNATURE_SIMPLE(V) \
V(int64_t, 0, 0), V(double, 1, 1.5), V(int64_t, 2, 2)
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion test_api_func_simple(Int64OrDoubleUnion arg0,
Int64OrDoubleUnion arg1,
Int64OrDoubleUnion arg2) {
#else
ReturnType test_api_func_simple(int64_t arg0, double arg1, int64_t arg2) {
#endif
bool result = true;
MIXED_SIGNATURE_SIMPLE(CHECK_ARG_I);
CHECK(result);
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion ret;
ret.int64_t_value = 42;
return ret;
#else
return 42;
#endif
}
SIGNATURE_TEST(RunCallWithMixedSignatureSimple, MIXED_SIGNATURE_SIMPLE,
test_api_func_simple)
#define MIXED_SIGNATURE(V) \
V(int64_t, 0, 0), V(double, 1, 1.5), V(int64_t, 2, 2), V(double, 3, 3.5), \
V(int64_t, 4, 4), V(double, 5, 5.5), V(int64_t, 6, 6), \
V(double, 7, 7.5), V(int64_t, 8, 8), V(double, 9, 9.5), \
V(int64_t, 10, 10)
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion test_api_func(
Int64OrDoubleUnion arg0, Int64OrDoubleUnion arg1, Int64OrDoubleUnion arg2,
Int64OrDoubleUnion arg3, Int64OrDoubleUnion arg4, Int64OrDoubleUnion arg5,
Int64OrDoubleUnion arg6, Int64OrDoubleUnion arg7, Int64OrDoubleUnion arg8,
Int64OrDoubleUnion arg9, Int64OrDoubleUnion arg10) {
#else
ReturnType test_api_func(int64_t arg0, double arg1, int64_t arg2, double arg3,
int64_t arg4, double arg5, int64_t arg6, double arg7,
int64_t arg8, double arg9, int64_t arg10) {
#endif
bool result = true;
MIXED_SIGNATURE(CHECK_ARG_I);
CHECK(result);
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion ret;
ret.int64_t_value = 42;
return ret;
#else
return 42;
#endif
}
SIGNATURE_TEST(RunCallWithMixedSignature, MIXED_SIGNATURE, test_api_func)
#define MIXED_SIGNATURE_DOUBLE_INT(V) \
V(double, 0, 0.5), V(double, 1, 1.5), V(double, 2, 2.5), V(double, 3, 3.5), \
V(double, 4, 4.5), V(double, 5, 5.5), V(double, 6, 6.5), \
V(double, 7, 7.5), V(double, 8, 8.5), V(double, 9, 9.5), \
V(int64_t, 10, 10), V(int64_t, 11, 11), V(int64_t, 12, 12), \
V(int64_t, 13, 13), V(int64_t, 14, 14), V(int64_t, 15, 15), \
V(int64_t, 16, 16), V(int64_t, 17, 17), V(int64_t, 18, 18), \
V(int64_t, 19, 19)
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion func_mixed_double_int(
Int64OrDoubleUnion arg0, Int64OrDoubleUnion arg1, Int64OrDoubleUnion arg2,
Int64OrDoubleUnion arg3, Int64OrDoubleUnion arg4, Int64OrDoubleUnion arg5,
Int64OrDoubleUnion arg6, Int64OrDoubleUnion arg7, Int64OrDoubleUnion arg8,
Int64OrDoubleUnion arg9, Int64OrDoubleUnion arg10, Int64OrDoubleUnion arg11,
Int64OrDoubleUnion arg12, Int64OrDoubleUnion arg13,
Int64OrDoubleUnion arg14, Int64OrDoubleUnion arg15,
Int64OrDoubleUnion arg16, Int64OrDoubleUnion arg17,
Int64OrDoubleUnion arg18, Int64OrDoubleUnion arg19) {
#else
ReturnType func_mixed_double_int(double arg0, double arg1, double arg2,
double arg3, double arg4, double arg5,
double arg6, double arg7, double arg8,
double arg9, int64_t arg10, int64_t arg11,
int64_t arg12, int64_t arg13, int64_t arg14,
int64_t arg15, int64_t arg16, int64_t arg17,
int64_t arg18, int64_t arg19) {
#endif
bool result = true;
MIXED_SIGNATURE_DOUBLE_INT(CHECK_ARG_I);
CHECK(result);
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion ret;
ret.int64_t_value = 42;
return ret;
#else
return 42;
#endif
}
SIGNATURE_TEST(RunCallWithMixedSignatureDoubleInt, MIXED_SIGNATURE_DOUBLE_INT,
func_mixed_double_int)
#define MIXED_SIGNATURE_DOUBLE_INT_ALT(V) \
V(double, 0, 0.5) \
, V(int64_t, 1, 1), V(double, 2, 2.5), V(int64_t, 3, 3), V(double, 4, 4.5), \
V(int64_t, 5, 5), V(double, 6, 6.5), V(int64_t, 7, 7), \
V(double, 8, 8.5), V(int64_t, 9, 9), V(double, 10, 10.5), \
V(int64_t, 11, 11), V(double, 12, 12.5), V(int64_t, 13, 13), \
V(double, 14, 14.5), V(int64_t, 15, 15), V(double, 16, 16.5), \
V(int64_t, 17, 17), V(double, 18, 18.5), V(int64_t, 19, 19)
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion func_mixed_double_int_alt(
Int64OrDoubleUnion arg0, Int64OrDoubleUnion arg1, Int64OrDoubleUnion arg2,
Int64OrDoubleUnion arg3, Int64OrDoubleUnion arg4, Int64OrDoubleUnion arg5,
Int64OrDoubleUnion arg6, Int64OrDoubleUnion arg7, Int64OrDoubleUnion arg8,
Int64OrDoubleUnion arg9, Int64OrDoubleUnion arg10, Int64OrDoubleUnion arg11,
Int64OrDoubleUnion arg12, Int64OrDoubleUnion arg13,
Int64OrDoubleUnion arg14, Int64OrDoubleUnion arg15,
Int64OrDoubleUnion arg16, Int64OrDoubleUnion arg17,
Int64OrDoubleUnion arg18, Int64OrDoubleUnion arg19) {
#else
ReturnType func_mixed_double_int_alt(double arg0, int64_t arg1, double arg2,
int64_t arg3, double arg4, int64_t arg5,
double arg6, int64_t arg7, double arg8,
int64_t arg9, double arg10, int64_t arg11,
double arg12, int64_t arg13, double arg14,
int64_t arg15, double arg16, int64_t arg17,
double arg18, int64_t arg19) {
#endif
bool result = true;
MIXED_SIGNATURE_DOUBLE_INT_ALT(CHECK_ARG_I);
CHECK(result);
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion ret;
ret.int64_t_value = 42;
return ret;
#else
return 42;
#endif
}
SIGNATURE_TEST(RunCallWithMixedSignatureDoubleIntAlt,
MIXED_SIGNATURE_DOUBLE_INT_ALT, func_mixed_double_int_alt)
#define MIXED_SIGNATURE_INT_DOUBLE(V) \
V(int64_t, 0, 0), V(int64_t, 1, 1), V(int64_t, 2, 2), V(int64_t, 3, 3), \
V(int64_t, 4, 4), V(int64_t, 5, 5), V(int64_t, 6, 6), V(int64_t, 7, 7), \
V(int64_t, 8, 8), V(int64_t, 9, 9), V(double, 10, 10.5), \
V(double, 11, 11.5), V(double, 12, 12.5), V(double, 13, 13.5), \
V(double, 14, 14.5), V(double, 15, 15.5), V(double, 16, 16.5), \
V(double, 17, 17.5), V(double, 18, 18.5), V(double, 19, 19.5)
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion func_mixed_int_double(
Int64OrDoubleUnion arg0, Int64OrDoubleUnion arg1, Int64OrDoubleUnion arg2,
Int64OrDoubleUnion arg3, Int64OrDoubleUnion arg4, Int64OrDoubleUnion arg5,
Int64OrDoubleUnion arg6, Int64OrDoubleUnion arg7, Int64OrDoubleUnion arg8,
Int64OrDoubleUnion arg9, Int64OrDoubleUnion arg10, Int64OrDoubleUnion arg11,
Int64OrDoubleUnion arg12, Int64OrDoubleUnion arg13,
Int64OrDoubleUnion arg14, Int64OrDoubleUnion arg15,
Int64OrDoubleUnion arg16, Int64OrDoubleUnion arg17,
Int64OrDoubleUnion arg18, Int64OrDoubleUnion arg19) {
#else
ReturnType func_mixed_int_double(int64_t arg0, int64_t arg1, int64_t arg2,
int64_t arg3, int64_t arg4, int64_t arg5,
int64_t arg6, int64_t arg7, int64_t arg8,
int64_t arg9, double arg10, double arg11,
double arg12, double arg13, double arg14,
double arg15, double arg16, double arg17,
double arg18, double arg19) {
#endif
bool result = true;
MIXED_SIGNATURE_INT_DOUBLE(CHECK_ARG_I);
CHECK(result);
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion ret;
ret.int64_t_value = 42;
return ret;
#else
return 42;
#endif
}
SIGNATURE_TEST(RunCallWithMixedSignatureIntDouble, MIXED_SIGNATURE_INT_DOUBLE,
func_mixed_int_double)
#define MIXED_SIGNATURE_INT_DOUBLE_ALT(V) \
V(int64_t, 0, 0), V(double, 1, 1.5), V(int64_t, 2, 2), V(double, 3, 3.5), \
V(int64_t, 4, 4), V(double, 5, 5.5), V(int64_t, 6, 6), \
V(double, 7, 7.5), V(int64_t, 8, 8), V(double, 9, 9.5), \
V(int64_t, 10, 10), V(double, 11, 11.5), V(int64_t, 12, 12), \
V(double, 13, 13.5), V(int64_t, 14, 14), V(double, 15, 15.5), \
V(int64_t, 16, 16), V(double, 17, 17.5), V(int64_t, 18, 18), \
V(double, 19, 19.5)
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion func_mixed_int_double_alt(
Int64OrDoubleUnion arg0, Int64OrDoubleUnion arg1, Int64OrDoubleUnion arg2,
Int64OrDoubleUnion arg3, Int64OrDoubleUnion arg4, Int64OrDoubleUnion arg5,
Int64OrDoubleUnion arg6, Int64OrDoubleUnion arg7, Int64OrDoubleUnion arg8,
Int64OrDoubleUnion arg9, Int64OrDoubleUnion arg10, Int64OrDoubleUnion arg11,
Int64OrDoubleUnion arg12, Int64OrDoubleUnion arg13,
Int64OrDoubleUnion arg14, Int64OrDoubleUnion arg15,
Int64OrDoubleUnion arg16, Int64OrDoubleUnion arg17,
Int64OrDoubleUnion arg18, Int64OrDoubleUnion arg19) {
#else
ReturnType func_mixed_int_double_alt(int64_t arg0, double arg1, int64_t arg2,
double arg3, int64_t arg4, double arg5,
int64_t arg6, double arg7, int64_t arg8,
double arg9, int64_t arg10, double arg11,
int64_t arg12, double arg13, int64_t arg14,
double arg15, int64_t arg16, double arg17,
int64_t arg18, double arg19) {
#endif
bool result = true;
MIXED_SIGNATURE_INT_DOUBLE_ALT(CHECK_ARG_I);
CHECK(result);
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion ret;
ret.int64_t_value = 42;
return ret;
#else
return 42;
#endif
}
SIGNATURE_TEST(RunCallWithMixedSignatureIntDoubleAlt,
MIXED_SIGNATURE_INT_DOUBLE_ALT, func_mixed_int_double_alt)
#define SIGNATURE_ONLY_DOUBLE(V) \
V(double, 0, 0.5), V(double, 1, 1.5), V(double, 2, 2.5), V(double, 3, 3.5), \
V(double, 4, 4.5), V(double, 5, 5.5), V(double, 6, 6.5), \
V(double, 7, 7.5), V(double, 8, 8.5), V(double, 9, 9.5)
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion func_only_double(
Int64OrDoubleUnion arg0, Int64OrDoubleUnion arg1, Int64OrDoubleUnion arg2,
Int64OrDoubleUnion arg3, Int64OrDoubleUnion arg4, Int64OrDoubleUnion arg5,
Int64OrDoubleUnion arg6, Int64OrDoubleUnion arg7, Int64OrDoubleUnion arg8,
Int64OrDoubleUnion arg9) {
#else
ReturnType func_only_double(double arg0, double arg1, double arg2, double arg3,
double arg4, double arg5, double arg6, double arg7,
double arg8, double arg9) {
#endif
bool result = true;
SIGNATURE_ONLY_DOUBLE(CHECK_ARG_I);
CHECK(result);
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion ret;
ret.int64_t_value = 42;
return ret;
#else
return 42;
#endif
}
SIGNATURE_TEST(RunCallWithSignatureOnlyDouble, SIGNATURE_ONLY_DOUBLE,
func_only_double)
#define SIGNATURE_ONLY_DOUBLE_20(V) \
V(double, 0, 0.5), V(double, 1, 1.5), V(double, 2, 2.5), V(double, 3, 3.5), \
V(double, 4, 4.5), V(double, 5, 5.5), V(double, 6, 6.5), \
V(double, 7, 7.5), V(double, 8, 8.5), V(double, 9, 9.5), \
V(double, 10, 10.5), V(double, 11, 11.5), V(double, 12, 12.5), \
V(double, 13, 13.5), V(double, 14, 14.5), V(double, 15, 15.5), \
V(double, 16, 16.5), V(double, 17, 17.5), V(double, 18, 18.5), \
V(double, 19, 19.5)
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion func_only_double_20(
Int64OrDoubleUnion arg0, Int64OrDoubleUnion arg1, Int64OrDoubleUnion arg2,
Int64OrDoubleUnion arg3, Int64OrDoubleUnion arg4, Int64OrDoubleUnion arg5,
Int64OrDoubleUnion arg6, Int64OrDoubleUnion arg7, Int64OrDoubleUnion arg8,
Int64OrDoubleUnion arg9, Int64OrDoubleUnion arg10, Int64OrDoubleUnion arg11,
Int64OrDoubleUnion arg12, Int64OrDoubleUnion arg13,
Int64OrDoubleUnion arg14, Int64OrDoubleUnion arg15,
Int64OrDoubleUnion arg16, Int64OrDoubleUnion arg17,
Int64OrDoubleUnion arg18, Int64OrDoubleUnion arg19) {
#else
ReturnType func_only_double_20(double arg0, double arg1, double arg2,
double arg3, double arg4, double arg5,
double arg6, double arg7, double arg8,
double arg9, double arg10, double arg11,
double arg12, double arg13, double arg14,
double arg15, double arg16, double arg17,
double arg18, double arg19) {
#endif
bool result = true;
SIGNATURE_ONLY_DOUBLE_20(CHECK_ARG_I);
CHECK(result);
#ifdef V8_USE_SIMULATOR_WITH_GENERIC_C_CALLS
Int64OrDoubleUnion ret;
ret.int64_t_value = 42;
return ret;
#else
return 42;
#endif
}
SIGNATURE_TEST(RunCallWithSignatureOnlyDouble20, SIGNATURE_ONLY_DOUBLE_20,
func_only_double_20)
#endif // V8_ENABLE_FP_PARAMS_IN_C_LINKAGE
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,719 @@
// Copyright 2016 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 <cmath>
#include <functional>
#include <limits>
#include "src/base/bits.h"
#include "src/base/overflowing-math.h"
#include "src/base/template-utils.h"
#include "src/base/utils/random-number-generator.h"
#include "src/objects/objects-inl.h"
#include "src/objects/tagged.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/codegen-tester.h"
#include "test/common/value-helper.h"
namespace v8 {
namespace internal {
namespace compiler {
enum TestAlignment {
kAligned,
kUnaligned,
};
#if V8_TARGET_LITTLE_ENDIAN
#define LSB(addr, bytes) addr
#elif V8_TARGET_BIG_ENDIAN
#define LSB(addr, bytes) reinterpret_cast<uint8_t*>(addr + 1) - (bytes)
#else
#error "Unknown Architecture"
#endif
// This is America!
#define A_BILLION 1000000000ULL
#define A_GIG (1024ULL * 1024ULL * 1024ULL)
namespace {
uint8_t* ComputeOffset(void* real_address, int32_t offset) {
return reinterpret_cast<uint8_t*>(reinterpret_cast<Address>(real_address) -
offset);
}
void RunLoadInt32(const TestAlignment t) {
RawMachineAssemblerTester<int32_t> m;
int32_t p1 = 0; // loads directly from this location.
if (t == TestAlignment::kAligned) {
m.Return(m.LoadFromPointer(&p1, MachineType::Int32()));
} else if (t == TestAlignment::kUnaligned) {
m.Return(m.UnalignedLoadFromPointer(&p1, MachineType::Int32()));
} else {
UNREACHABLE();
}
FOR_INT32_INPUTS(i) {
p1 = i;
CHECK_EQ(p1, m.Call());
}
}
void RunLoadInt32Offset(TestAlignment t) {
int32_t p1 = 0; // loads directly from this location.
int32_t offsets[] = {-2000000, -100, -101, 1, 3,
7, 120, 2000, 2000000000, 0xFF};
for (size_t i = 0; i < arraysize(offsets); i++) {
RawMachineAssemblerTester<int32_t> m;
int32_t offset = offsets[i];
uint8_t* pointer = ComputeOffset(&p1, offset);
// generate load [#base + #index]
if (t == TestAlignment::kAligned) {
m.Return(m.LoadFromPointer(pointer, MachineType::Int32(), offset));
} else if (t == TestAlignment::kUnaligned) {
m.Return(
m.UnalignedLoadFromPointer(pointer, MachineType::Int32(), offset));
} else {
UNREACHABLE();
}
FOR_INT32_INPUTS(j) {
p1 = j;
CHECK_EQ(p1, m.Call());
}
}
}
void RunLoadStoreFloat32Offset(TestAlignment t) {
float p1 = 0.0f; // loads directly from this location.
float p2 = 0.0f; // and stores directly into this location.
FOR_INT32_INPUTS(i) {
int32_t magic =
base::AddWithWraparound(0x2342AABB, base::MulWithWraparound(i, 3));
RawMachineAssemblerTester<int32_t> m;
int32_t offset = i;
uint8_t* from = ComputeOffset(&p1, offset);
uint8_t* to = ComputeOffset(&p2, offset);
// generate load [#base + #index]
if (t == TestAlignment::kAligned) {
Node* load = m.Load(MachineType::Float32(), m.PointerConstant(from),
m.IntPtrConstant(offset));
m.Store(MachineRepresentation::kFloat32, m.PointerConstant(to),
m.IntPtrConstant(offset), load, kNoWriteBarrier);
} else if (t == TestAlignment::kUnaligned) {
Node* load =
m.UnalignedLoad(MachineType::Float32(), m.PointerConstant(from),
m.IntPtrConstant(offset));
m.UnalignedStore(MachineRepresentation::kFloat32, m.PointerConstant(to),
m.IntPtrConstant(offset), load);
} else {
UNREACHABLE();
}
m.Return(m.Int32Constant(magic));
FOR_FLOAT32_INPUTS(j) {
p1 = j;
p2 = j - 5;
CHECK_EQ(magic, m.Call());
CHECK_DOUBLE_EQ(p1, p2);
}
}
}
void RunLoadStoreFloat64Offset(TestAlignment t) {
double p1 = 0; // loads directly from this location.
double p2 = 0; // and stores directly into this location.
FOR_INT32_INPUTS(i) {
int32_t magic =
base::AddWithWraparound(0x2342AABB, base::MulWithWraparound(i, 3));
RawMachineAssemblerTester<int32_t> m;
int32_t offset = i;
uint8_t* from = ComputeOffset(&p1, offset);
uint8_t* to = ComputeOffset(&p2, offset);
// generate load [#base + #index]
if (t == TestAlignment::kAligned) {
Node* load = m.Load(MachineType::Float64(), m.PointerConstant(from),
m.IntPtrConstant(offset));
m.Store(MachineRepresentation::kFloat64, m.PointerConstant(to),
m.IntPtrConstant(offset), load, kNoWriteBarrier);
} else if (t == TestAlignment::kUnaligned) {
Node* load =
m.UnalignedLoad(MachineType::Float64(), m.PointerConstant(from),
m.IntPtrConstant(offset));
m.UnalignedStore(MachineRepresentation::kFloat64, m.PointerConstant(to),
m.IntPtrConstant(offset), load);
} else {
UNREACHABLE();
}
m.Return(m.Int32Constant(magic));
FOR_FLOAT64_INPUTS(j) {
p1 = j;
p2 = j - 5;
CHECK_EQ(magic, m.Call());
CHECK_DOUBLE_EQ(p1, p2);
}
}
}
} // namespace
TEST(RunLoadInt32) { RunLoadInt32(TestAlignment::kAligned); }
TEST(RunUnalignedLoadInt32) { RunLoadInt32(TestAlignment::kUnaligned); }
TEST(RunLoadInt32Offset) { RunLoadInt32Offset(TestAlignment::kAligned); }
TEST(RunUnalignedLoadInt32Offset) {
RunLoadInt32Offset(TestAlignment::kUnaligned);
}
TEST(RunLoadStoreFloat32Offset) {
RunLoadStoreFloat32Offset(TestAlignment::kAligned);
}
TEST(RunUnalignedLoadStoreFloat32Offset) {
RunLoadStoreFloat32Offset(TestAlignment::kUnaligned);
}
TEST(RunLoadStoreFloat64Offset) {
RunLoadStoreFloat64Offset(TestAlignment::kAligned);
}
TEST(RunUnalignedLoadStoreFloat64Offset) {
RunLoadStoreFloat64Offset(TestAlignment::kUnaligned);
}
namespace {
// Mostly same as CHECK_EQ() but customized for compressed tagged values.
template <typename CType>
void CheckEq(CType in_value, CType out_value) {
CHECK_EQ(in_value, out_value);
}
#ifdef V8_COMPRESS_POINTERS
// Specializations for checking the result of compressing store.
template <>
void CheckEq<Tagged<Object>>(Tagged<Object> in_value,
Tagged<Object> out_value) {
// Compare only lower 32-bits of the value because tagged load/stores are
// 32-bit operations anyway.
CHECK_EQ(static_cast<Tagged_t>(in_value.ptr()),
static_cast<Tagged_t>(out_value.ptr()));
}
template <>
void CheckEq<Tagged<HeapObject>>(Tagged<HeapObject> in_value,
Tagged<HeapObject> out_value) {
return CheckEq<Tagged<Object>>(in_value, out_value);
}
template <>
void CheckEq<Tagged<Smi>>(Tagged<Smi> in_value, Tagged<Smi> out_value) {
return CheckEq<Tagged<Object>>(in_value, out_value);
}
#endif
// Initializes the buffer with some raw data respecting requested representation
// of the values.
template <typename CType>
void InitBuffer(CType* buffer, size_t length, MachineType type) {
const size_t kBufferSize = sizeof(CType) * length;
if (!type.IsTagged()) {
uint8_t* raw = reinterpret_cast<uint8_t*>(buffer);
for (size_t i = 0; i < kBufferSize; i++) {
raw[i] = static_cast<uint8_t>((i + kBufferSize) ^ 0xAA);
}
return;
}
// Tagged field loads require values to be properly tagged because of
// pointer decompression that may be happenning during load.
Isolate* isolate = CcTest::InitIsolateOnce();
Tagged<Smi>* smi_view = reinterpret_cast<Tagged<Smi>*>(&buffer[0]);
if (type.IsTaggedSigned()) {
for (size_t i = 0; i < length; i++) {
smi_view[i] = Smi::FromInt(static_cast<int>(i + kBufferSize) ^ 0xABCDEF0);
}
} else {
memcpy(&buffer[0], &isolate->roots_table(), kBufferSize);
if (!type.IsTaggedPointer()) {
// Also add some Smis if we are checking AnyTagged case.
for (size_t i = 0; i < length / 2; i++) {
smi_view[i] =
Smi::FromInt(static_cast<int>(i + kBufferSize) ^ 0xABCDEF0);
}
}
}
}
template <typename CType>
void RunLoadImmIndex(MachineType type, TestAlignment t) {
const int kNumElems = 16;
CType buffer[kNumElems];
InitBuffer(buffer, kNumElems, type);
// Test with various large and small offsets.
for (int offset = -1; offset <= 200000; offset *= -5) {
for (int i = 0; i < kNumElems; i++) {
BufferedRawMachineAssemblerTester<CType> m;
CType* base_pointer = reinterpret_cast<CType*>(
ComputeOffset(&buffer[0], offset * sizeof(CType)));
#ifdef V8_COMPRESS_POINTERS
if (type.IsTagged()) {
// When pointer compression is enabled then we need to access only
// the lower 32-bit of the tagged value while the buffer contains
// full 64-bit values.
base_pointer = reinterpret_cast<CType*>(LSB(base_pointer, kTaggedSize));
}
#endif
Node* base = m.PointerConstant(base_pointer);
Node* index = m.Int32Constant((offset + i) * sizeof(buffer[0]));
if (t == TestAlignment::kAligned) {
m.Return(m.Load(type, base, index));
} else if (t == TestAlignment::kUnaligned) {
m.Return(m.UnalignedLoad(type, base, index));
} else {
UNREACHABLE();
}
CheckEq<CType>(buffer[i], m.Call());
}
}
}
template <typename CType>
void RunLoadStore(MachineType type, TestAlignment t) {
const int kNumElems = 16;
CType in_buffer[kNumElems];
CType out_buffer[kNumElems];
uintptr_t zap_data[] = {kZapValue, kZapValue};
CType zap_value;
static_assert(sizeof(CType) <= sizeof(zap_data));
MemCopy(&zap_value, &zap_data, sizeof(CType));
InitBuffer(in_buffer, kNumElems, type);
#ifdef V8_TARGET_BIG_ENDIAN
int offset = sizeof(CType) - ElementSizeInBytes(type.representation());
#else
int offset = 0;
#endif
for (int32_t x = 0; x < kNumElems; x++) {
int32_t y = kNumElems - x - 1;
RawMachineAssemblerTester<int32_t> m;
int32_t OK = 0x29000 + x;
Node* in_base = m.PointerConstant(in_buffer);
Node* in_index = m.IntPtrConstant(x * sizeof(CType) + offset);
Node* out_base = m.PointerConstant(out_buffer);
Node* out_index = m.IntPtrConstant(y * sizeof(CType) + offset);
if (t == TestAlignment::kAligned) {
Node* load = m.Load(type, in_base, in_index);
m.Store(type.representation(), out_base, out_index, load,
kNoWriteBarrier);
} else if (t == TestAlignment::kUnaligned) {
Node* load = m.UnalignedLoad(type, in_base, in_index);
m.UnalignedStore(type.representation(), out_base, out_index, load);
}
m.Return(m.Int32Constant(OK));
for (int32_t z = 0; z < kNumElems; z++) {
out_buffer[z] = zap_value;
}
CHECK_NE(in_buffer[x], out_buffer[y]);
CHECK_EQ(OK, m.Call());
// Mostly same as CHECK_EQ() but customized for compressed tagged values.
CheckEq<CType>(in_buffer[x], out_buffer[y]);
for (int32_t z = 0; z < kNumElems; z++) {
if (z != y) CHECK_EQ(zap_value, out_buffer[z]);
}
}
}
template <typename CType>
void RunUnalignedLoadStoreUnalignedAccess(MachineType type) {
CType in, out;
uint8_t in_buffer[2 * sizeof(CType)];
uint8_t out_buffer[2 * sizeof(CType)];
InitBuffer(&in, 1, type);
for (int x = 0; x < static_cast<int>(sizeof(CType)); x++) {
// Direct write to &in_buffer[x] may cause unaligned access in C++ code so
// we use MemCopy() to handle that.
MemCopy(&in_buffer[x], &in, sizeof(CType));
for (int y = 0; y < static_cast<int>(sizeof(CType)); y++) {
RawMachineAssemblerTester<int32_t> m;
int32_t OK = 0x29000 + x;
Node* in_base = m.PointerConstant(in_buffer);
Node* in_index = m.IntPtrConstant(x);
Node* load = m.UnalignedLoad(type, in_base, in_index);
Node* out_base = m.PointerConstant(out_buffer);
Node* out_index = m.IntPtrConstant(y);
m.UnalignedStore(type.representation(), out_base, out_index, load);
m.Return(m.Int32Constant(OK));
CHECK_EQ(OK, m.Call());
// Direct read of &out_buffer[y] may cause unaligned access in C++ code
// so we use MemCopy() to handle that.
MemCopy(&out, &out_buffer[y], sizeof(CType));
// Mostly same as CHECK_EQ() but customized for compressed tagged values.
CheckEq<CType>(in, out);
}
}
}
} // namespace
TEST(RunLoadImmIndex) {
RunLoadImmIndex<int8_t>(MachineType::Int8(), TestAlignment::kAligned);
RunLoadImmIndex<uint8_t>(MachineType::Uint8(), TestAlignment::kAligned);
RunLoadImmIndex<int16_t>(MachineType::Int16(), TestAlignment::kAligned);
RunLoadImmIndex<uint16_t>(MachineType::Uint16(), TestAlignment::kAligned);
RunLoadImmIndex<int32_t>(MachineType::Int32(), TestAlignment::kAligned);
RunLoadImmIndex<uint32_t>(MachineType::Uint32(), TestAlignment::kAligned);
RunLoadImmIndex<void*>(MachineType::Pointer(), TestAlignment::kAligned);
RunLoadImmIndex<Tagged<Smi>>(MachineType::TaggedSigned(),
TestAlignment::kAligned);
RunLoadImmIndex<Tagged<HeapObject>>(MachineType::TaggedPointer(),
TestAlignment::kAligned);
RunLoadImmIndex<Tagged<Object>>(MachineType::AnyTagged(),
TestAlignment::kAligned);
RunLoadImmIndex<float>(MachineType::Float32(), TestAlignment::kAligned);
RunLoadImmIndex<double>(MachineType::Float64(), TestAlignment::kAligned);
#if V8_TARGET_ARCH_64_BIT
RunLoadImmIndex<int64_t>(MachineType::Int64(), TestAlignment::kAligned);
#endif
// TODO(titzer): test various indexing modes.
}
TEST(RunUnalignedLoadImmIndex) {
RunLoadImmIndex<int16_t>(MachineType::Int16(), TestAlignment::kUnaligned);
RunLoadImmIndex<uint16_t>(MachineType::Uint16(), TestAlignment::kUnaligned);
RunLoadImmIndex<int32_t>(MachineType::Int32(), TestAlignment::kUnaligned);
RunLoadImmIndex<uint32_t>(MachineType::Uint32(), TestAlignment::kUnaligned);
RunLoadImmIndex<void*>(MachineType::Pointer(), TestAlignment::kUnaligned);
RunLoadImmIndex<float>(MachineType::Float32(), TestAlignment::kUnaligned);
RunLoadImmIndex<double>(MachineType::Float64(), TestAlignment::kUnaligned);
#if V8_TARGET_ARCH_64_BIT
RunLoadImmIndex<int64_t>(MachineType::Int64(), TestAlignment::kUnaligned);
#endif
// TODO(titzer): test various indexing modes.
}
TEST(RunLoadStore) {
RunLoadStore<int8_t>(MachineType::Int8(), TestAlignment::kAligned);
RunLoadStore<uint8_t>(MachineType::Uint8(), TestAlignment::kAligned);
RunLoadStore<int16_t>(MachineType::Int16(), TestAlignment::kAligned);
RunLoadStore<uint16_t>(MachineType::Uint16(), TestAlignment::kAligned);
RunLoadStore<int32_t>(MachineType::Int32(), TestAlignment::kAligned);
RunLoadStore<uint32_t>(MachineType::Uint32(), TestAlignment::kAligned);
RunLoadStore<void*>(MachineType::Pointer(), TestAlignment::kAligned);
RunLoadStore<Tagged<Smi>>(MachineType::TaggedSigned(),
TestAlignment::kAligned);
RunLoadStore<Tagged<HeapObject>>(MachineType::TaggedPointer(),
TestAlignment::kAligned);
RunLoadStore<Tagged<Object>>(MachineType::AnyTagged(),
TestAlignment::kAligned);
RunLoadStore<float>(MachineType::Float32(), TestAlignment::kAligned);
RunLoadStore<double>(MachineType::Float64(), TestAlignment::kAligned);
#if V8_TARGET_ARCH_64_BIT
RunLoadStore<int64_t>(MachineType::Int64(), TestAlignment::kAligned);
#endif
}
TEST(RunUnalignedLoadStore) {
RunLoadStore<int16_t>(MachineType::Int16(), TestAlignment::kUnaligned);
RunLoadStore<uint16_t>(MachineType::Uint16(), TestAlignment::kUnaligned);
RunLoadStore<int32_t>(MachineType::Int32(), TestAlignment::kUnaligned);
RunLoadStore<uint32_t>(MachineType::Uint32(), TestAlignment::kUnaligned);
RunLoadStore<void*>(MachineType::Pointer(), TestAlignment::kUnaligned);
RunLoadStore<float>(MachineType::Float32(), TestAlignment::kUnaligned);
RunLoadStore<double>(MachineType::Float64(), TestAlignment::kUnaligned);
#if V8_TARGET_ARCH_64_BIT
RunLoadStore<int64_t>(MachineType::Int64(), TestAlignment::kUnaligned);
#endif
}
TEST(RunUnalignedLoadStoreUnalignedAccess) {
RunUnalignedLoadStoreUnalignedAccess<int16_t>(MachineType::Int16());
RunUnalignedLoadStoreUnalignedAccess<uint16_t>(MachineType::Uint16());
RunUnalignedLoadStoreUnalignedAccess<int32_t>(MachineType::Int32());
RunUnalignedLoadStoreUnalignedAccess<uint32_t>(MachineType::Uint32());
RunUnalignedLoadStoreUnalignedAccess<void*>(MachineType::Pointer());
RunUnalignedLoadStoreUnalignedAccess<float>(MachineType::Float32());
RunUnalignedLoadStoreUnalignedAccess<double>(MachineType::Float64());
#if V8_TARGET_ARCH_64_BIT
RunUnalignedLoadStoreUnalignedAccess<int64_t>(MachineType::Int64());
#endif
}
namespace {
void RunLoadStoreSignExtend32(TestAlignment t) {
int32_t buffer[4];
RawMachineAssemblerTester<int32_t> m;
Node* load8 = m.LoadFromPointer(LSB(&buffer[0], 1), MachineType::Int8());
if (t == TestAlignment::kAligned) {
Node* load16 = m.LoadFromPointer(LSB(&buffer[0], 2), MachineType::Int16());
Node* load32 = m.LoadFromPointer(&buffer[0], MachineType::Int32());
m.StoreToPointer(&buffer[1], MachineRepresentation::kWord32, load8);
m.StoreToPointer(&buffer[2], MachineRepresentation::kWord32, load16);
m.StoreToPointer(&buffer[3], MachineRepresentation::kWord32, load32);
} else if (t == TestAlignment::kUnaligned) {
Node* load16 =
m.UnalignedLoadFromPointer(LSB(&buffer[0], 2), MachineType::Int16());
Node* load32 = m.UnalignedLoadFromPointer(&buffer[0], MachineType::Int32());
m.StoreToPointer(&buffer[1], MachineRepresentation::kWord32, load8);
m.UnalignedStoreToPointer(&buffer[2], MachineRepresentation::kWord32,
load16);
m.UnalignedStoreToPointer(&buffer[3], MachineRepresentation::kWord32,
load32);
} else {
UNREACHABLE();
}
m.Return(load8);
FOR_INT32_INPUTS(i) {
buffer[0] = i;
CHECK_EQ(static_cast<int8_t>(i & 0xFF), m.Call());
CHECK_EQ(static_cast<int8_t>(i & 0xFF), buffer[1]);
CHECK_EQ(static_cast<int16_t>(i & 0xFFFF), buffer[2]);
CHECK_EQ(i, buffer[3]);
}
}
void RunLoadStoreZeroExtend32(TestAlignment t) {
uint32_t buffer[4];
RawMachineAssemblerTester<uint32_t> m;
Node* load8 = m.LoadFromPointer(LSB(&buffer[0], 1), MachineType::Uint8());
if (t == TestAlignment::kAligned) {
Node* load16 = m.LoadFromPointer(LSB(&buffer[0], 2), MachineType::Uint16());
Node* load32 = m.LoadFromPointer(&buffer[0], MachineType::Uint32());
m.StoreToPointer(&buffer[1], MachineRepresentation::kWord32, load8);
m.StoreToPointer(&buffer[2], MachineRepresentation::kWord32, load16);
m.StoreToPointer(&buffer[3], MachineRepresentation::kWord32, load32);
} else if (t == TestAlignment::kUnaligned) {
Node* load16 =
m.UnalignedLoadFromPointer(LSB(&buffer[0], 2), MachineType::Uint16());
Node* load32 =
m.UnalignedLoadFromPointer(&buffer[0], MachineType::Uint32());
m.StoreToPointer(&buffer[1], MachineRepresentation::kWord32, load8);
m.UnalignedStoreToPointer(&buffer[2], MachineRepresentation::kWord32,
load16);
m.UnalignedStoreToPointer(&buffer[3], MachineRepresentation::kWord32,
load32);
}
m.Return(load8);
FOR_UINT32_INPUTS(i) {
buffer[0] = i;
CHECK_EQ((i & 0xFF), m.Call());
CHECK_EQ((i & 0xFF), buffer[1]);
CHECK_EQ((i & 0xFFFF), buffer[2]);
CHECK_EQ(i, buffer[3]);
}
}
} // namespace
TEST(RunLoadStoreSignExtend32) {
RunLoadStoreSignExtend32(TestAlignment::kAligned);
}
TEST(RunUnalignedLoadStoreSignExtend32) {
RunLoadStoreSignExtend32(TestAlignment::kUnaligned);
}
TEST(RunLoadStoreZeroExtend32) {
RunLoadStoreZeroExtend32(TestAlignment::kAligned);
}
TEST(RunUnalignedLoadStoreZeroExtend32) {
RunLoadStoreZeroExtend32(TestAlignment::kUnaligned);
}
#if V8_TARGET_ARCH_64_BIT
namespace {
void RunLoadStoreSignExtend64(TestAlignment t) {
if ((true)) return; // TODO(titzer): sign extension of loads to 64-bit.
int64_t buffer[5];
RawMachineAssemblerTester<int64_t> m;
Node* load8 = m.LoadFromPointer(LSB(&buffer[0], 1), MachineType::Int8());
if (t == TestAlignment::kAligned) {
Node* load16 = m.LoadFromPointer(LSB(&buffer[0], 2), MachineType::Int16());
Node* load32 = m.LoadFromPointer(LSB(&buffer[0], 4), MachineType::Int32());
Node* load64 = m.LoadFromPointer(&buffer[0], MachineType::Int64());
m.StoreToPointer(&buffer[1], MachineRepresentation::kWord64, load8);
m.StoreToPointer(&buffer[2], MachineRepresentation::kWord64, load16);
m.StoreToPointer(&buffer[3], MachineRepresentation::kWord64, load32);
m.StoreToPointer(&buffer[4], MachineRepresentation::kWord64, load64);
} else if (t == TestAlignment::kUnaligned) {
Node* load16 =
m.UnalignedLoadFromPointer(LSB(&buffer[0], 2), MachineType::Int16());
Node* load32 =
m.UnalignedLoadFromPointer(LSB(&buffer[0], 4), MachineType::Int32());
Node* load64 = m.UnalignedLoadFromPointer(&buffer[0], MachineType::Int64());
m.StoreToPointer(&buffer[1], MachineRepresentation::kWord64, load8);
m.UnalignedStoreToPointer(&buffer[2], MachineRepresentation::kWord64,
load16);
m.UnalignedStoreToPointer(&buffer[3], MachineRepresentation::kWord64,
load32);
m.UnalignedStoreToPointer(&buffer[4], MachineRepresentation::kWord64,
load64);
} else {
UNREACHABLE();
}
m.Return(load8);
FOR_INT64_INPUTS(i) {
buffer[0] = i;
CHECK_EQ(static_cast<int8_t>(i & 0xFF), m.Call());
CHECK_EQ(static_cast<int8_t>(i & 0xFF), buffer[1]);
CHECK_EQ(static_cast<int16_t>(i & 0xFFFF), buffer[2]);
CHECK_EQ(static_cast<int32_t>(i & 0xFFFFFFFF), buffer[3]);
CHECK_EQ(i, buffer[4]);
}
}
void RunLoadStoreZeroExtend64(TestAlignment t) {
if (kSystemPointerSize < 8) return;
uint64_t buffer[5];
RawMachineAssemblerTester<uint64_t> m;
Node* load8 = m.LoadFromPointer(LSB(&buffer[0], 1), MachineType::Uint8());
if (t == TestAlignment::kAligned) {
Node* load16 = m.LoadFromPointer(LSB(&buffer[0], 2), MachineType::Uint16());
Node* load32 = m.LoadFromPointer(LSB(&buffer[0], 4), MachineType::Uint32());
Node* load64 = m.LoadFromPointer(&buffer[0], MachineType::Uint64());
m.StoreToPointer(&buffer[1], MachineRepresentation::kWord64, load8);
m.StoreToPointer(&buffer[2], MachineRepresentation::kWord64, load16);
m.StoreToPointer(&buffer[3], MachineRepresentation::kWord64, load32);
m.StoreToPointer(&buffer[4], MachineRepresentation::kWord64, load64);
} else if (t == TestAlignment::kUnaligned) {
Node* load16 =
m.UnalignedLoadFromPointer(LSB(&buffer[0], 2), MachineType::Uint16());
Node* load32 =
m.UnalignedLoadFromPointer(LSB(&buffer[0], 4), MachineType::Uint32());
Node* load64 =
m.UnalignedLoadFromPointer(&buffer[0], MachineType::Uint64());
m.StoreToPointer(&buffer[1], MachineRepresentation::kWord64, load8);
m.UnalignedStoreToPointer(&buffer[2], MachineRepresentation::kWord64,
load16);
m.UnalignedStoreToPointer(&buffer[3], MachineRepresentation::kWord64,
load32);
m.UnalignedStoreToPointer(&buffer[4], MachineRepresentation::kWord64,
load64);
} else {
UNREACHABLE();
}
m.Return(load8);
FOR_UINT64_INPUTS(i) {
buffer[0] = i;
CHECK_EQ((i & 0xFF), m.Call());
CHECK_EQ((i & 0xFF), buffer[1]);
CHECK_EQ((i & 0xFFFF), buffer[2]);
CHECK_EQ((i & 0xFFFFFFFF), buffer[3]);
CHECK_EQ(i, buffer[4]);
}
}
} // namespace
TEST(RunLoadStoreSignExtend64) {
RunLoadStoreSignExtend64(TestAlignment::kAligned);
}
TEST(RunUnalignedLoadStoreSignExtend64) {
RunLoadStoreSignExtend64(TestAlignment::kUnaligned);
}
TEST(RunLoadStoreZeroExtend64) {
RunLoadStoreZeroExtend64(TestAlignment::kAligned);
}
TEST(RunUnalignedLoadStoreZeroExtend64) {
RunLoadStoreZeroExtend64(TestAlignment::kUnaligned);
}
#endif
namespace {
template <typename IntType>
void LoadStoreTruncation(MachineType kRepresentation, TestAlignment t) {
IntType input;
RawMachineAssemblerTester<int32_t> m;
Node* ap1;
if (t == TestAlignment::kAligned) {
Node* a = m.LoadFromPointer(&input, kRepresentation);
ap1 = m.Int32Add(a, m.Int32Constant(1));
m.StoreToPointer(&input, kRepresentation.representation(), ap1);
} else if (t == TestAlignment::kUnaligned) {
Node* a = m.UnalignedLoadFromPointer(&input, kRepresentation);
ap1 = m.Int32Add(a, m.Int32Constant(1));
m.UnalignedStoreToPointer(&input, kRepresentation.representation(), ap1);
} else {
UNREACHABLE();
}
m.Return(ap1);
const IntType max = std::numeric_limits<IntType>::max();
const IntType min = std::numeric_limits<IntType>::min();
// Test upper bound.
input = max;
CHECK_EQ(max + 1, m.Call());
CHECK_EQ(min, input);
// Test lower bound.
input = min;
CHECK_EQ(static_cast<IntType>(max + 2), m.Call());
CHECK_EQ(min + 1, input);
// Test all one byte values that are not one byte bounds.
for (int i = -127; i < 127; i++) {
input = i;
int expected = i >= 0 ? i + 1 : max + (i - min) + 2;
CHECK_EQ(static_cast<IntType>(expected), m.Call());
CHECK_EQ(static_cast<IntType>(i + 1), input);
}
}
} // namespace
TEST(RunLoadStoreTruncation) {
LoadStoreTruncation<int8_t>(MachineType::Int8(), TestAlignment::kAligned);
LoadStoreTruncation<int16_t>(MachineType::Int16(), TestAlignment::kAligned);
}
TEST(RunUnalignedLoadStoreTruncation) {
LoadStoreTruncation<int16_t>(MachineType::Int16(), TestAlignment::kUnaligned);
}
#undef LSB
#undef A_BILLION
#undef A_GIG
} // namespace compiler
} // namespace internal
} // namespace v8

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
// Copyright 2014 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/execution/isolate.h"
#include "test/cctest/compiler/function-tester.h"
namespace v8 {
namespace internal {
namespace compiler {
TEST(TerminateAtMethodEntry) {
FunctionTester T("(function(a,b) { return 23; })");
T.CheckCall(T.Val(23));
T.isolate->stack_guard()->RequestTerminateExecution();
T.CheckThrows(T.undefined(), T.undefined());
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,65 @@
// Copyright 2016 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 "include/v8config.h"
// Test enabled only on supported architectures.
#if V8_OS_LINUX && \
(defined(V8_TARGET_ARCH_X64) || defined(V8_TARGET_ARCH_ARM) || \
defined(V8_TARGET_ARCH_ARM64))
#include "src/flags/flags.h"
#include "src/objects/objects-inl.h"
#include "src/objects/objects.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/function-tester.h"
namespace v8 {
namespace internal {
namespace compiler {
TEST(RunUnwindingInfo) {
v8_flags.always_turbofan = true;
v8_flags.perf_prof_unwinding_info = true;
FunctionTester tester(
"(function (x) {\n"
" function f(x) { return x*x; }\n"
" return x > 0 ? x+1 : f(x);\n"
"})");
tester.Call(tester.Val(-1));
CHECK(tester.function->code(tester.main_isolate())->has_unwinding_info());
}
// TODO(ssanfilippo) Build low-level graph and check that state is correctly
// restored in the following situation:
//
// +-----------------+
// | no frame |---+
// check that a +-----------------+ |
// a noframe state | construct frame |<--+
// is restored here --> +-----------------+ |
// | construct frame |<--+
// +-----------------+
//
// Same for <construct>/<destruct>/<destruct> (a <construct> status is restored)
// TODO(ssanfilippo) Intentionally reach a BB with different initial states
// and check that the UnwindingInforWriter fails in debug mode:
//
// +----------------+
// +---| State A |
// | +----------------+
// | | State B != A |---+
// | +----------------+ |
// +-->| Failure here |<--+
// +----------------+
} // namespace compiler
} // namespace internal
} // namespace v8
#endif

View File

@ -0,0 +1,122 @@
// Copyright 2014 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/api/api-inl.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/compiler/function-tester.h"
namespace v8 {
namespace internal {
namespace compiler {
static const char* throws = nullptr;
static const char* load_tests[] = {"var x = a; r = x",
"123",
"0",
"var x = (r = x)",
"undefined",
"undefined",
"var x = (a?1:2); r = x",
"1",
"2",
"const x = a; r = x",
"123",
"0",
"const x = (a?3:4); r = x",
"3",
"4",
"'use strict'; const x = a; r = x",
"123",
"0",
"'use strict'; const x = (r = x)",
throws,
throws,
"'use strict'; const x = (a?5:6); r = x",
"5",
"6",
"'use strict'; let x = a; r = x",
"123",
"0",
"'use strict'; let x = (r = x)",
throws,
throws,
"'use strict'; let x = (a?7:8); r = x",
"7",
"8",
nullptr};
static const char* store_tests[] = {
"var x = 1; x = a; r = x", "123", "0", "var x = (a?(x=4,2):3); r = x", "2",
"3", "var x = (a?4:5); x = a; r = x", "123", "0",
// Assignments to 'const' are SyntaxErrors, handled by the parser,
// hence we cannot test them here because they are early errors.
"'use strict'; let x = 1; x = a; r = x", "123", "0",
"'use strict'; let x = (a?(x=4,2):3); r = x", throws, "3",
"'use strict'; let x = (a?4:5); x = a; r = x", "123", "0", nullptr};
static void RunVariableTests(const char* source, const char* tests[]) {
base::EmbeddedVector<char, 512> buffer;
for (int i = 0; tests[i] != nullptr; i += 3) {
SNPrintF(buffer, source, tests[i]);
PrintF("#%d: %s\n", i / 3, buffer.begin());
FunctionTester T(buffer.begin());
// Check function with non-falsey parameter.
if (tests[i + 1] != throws) {
DirectHandle<Object> r =
v8::Utils::OpenDirectHandle(*CompileRun(tests[i + 1]));
T.CheckCall(r, T.Val(123), T.Val("result"));
} else {
T.CheckThrows(T.Val(123), T.Val("result"));
}
// Check function with falsey parameter.
if (tests[i + 2] != throws) {
DirectHandle<Object> r =
v8::Utils::OpenDirectHandle(*CompileRun(tests[i + 2]));
T.CheckCall(r, T.Val(0.0), T.Val("result"));
} else {
T.CheckThrows(T.Val(0.0), T.Val("result"));
}
}
}
TEST(StackLoadVariables) {
const char* source = "(function(a,r) { %s; return r; })";
RunVariableTests(source, load_tests);
}
TEST(ContextLoadVariables) {
const char* source = "(function(a,r) { %s; function f() {x} return r; })";
RunVariableTests(source, load_tests);
}
TEST(StackStoreVariables) {
const char* source = "(function(a,r) { %s; return r; })";
RunVariableTests(source, store_tests);
}
TEST(ContextStoreVariables) {
const char* source = "(function(a,r) { %s; function f() {x} return r; })";
RunVariableTests(source, store_tests);
}
TEST(SelfReferenceVariable) {
FunctionTester T("(function self() { return self; })");
T.CheckCall(T.function);
CompileRun("var self = 'not a function'");
T.CheckCall(T.function);
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,500 @@
// 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 "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/codegen-tester.h"
#include "test/common/value-helper.h"
namespace v8::internal::compiler {
// Generates a binop arithmetic instruction, followed by an integer compare zero
// and select. This is to test a possible merge of the arithmetic op and the
// compare for use by the select. We test a matrix of configurations:
// - floating-point and integer select.
// - add, sub, mul, and, or and xor.
// - int32, uint32t, int64_t, uint64_t, float and double.
// - one or multiple users of the binary operation.
// - two different graph layouts (single block vs three blocks).
namespace {
enum GraphConfig { kOneUse, kTwoUsesOneBlock, kTwoUsesTwoBlocks };
constexpr GraphConfig graph_configs[] = {GraphConfig::kOneUse,
GraphConfig::kTwoUsesOneBlock,
GraphConfig::kTwoUsesTwoBlocks};
// kOneUse:
// (bin_res = binop lhs, rhs)
// (return (select (compare bin_res, zero, cond), tval, fval))
//
// kTwoUsesOneBlock:
// (bin_res = binop lhs, rhs)
// (return (add (select (compare bin_res, zero, cond), tval, fval), bin_res))
//
// kTwoUsesTwoBlocks:
// Same as above, but the final addition is conditionally executed in a
// different block.
// (bin_res = binop lhs, rhs)
// (select_res = (select (compare bin_res, zero, cond), tval, fval))
// (select_res >= tval)
// ? (return select_res)
// : (return (add select_res, bin_res))
template <typename CondType, typename ResultType>
class ConditionalSelectGen {
public:
ConditionalSelectGen(BufferedRawMachineAssemblerTester<ResultType>& m,
GraphConfig c, IrOpcode::Value icmp_op,
IrOpcode::Value bin_op)
: m_(m), config_(c), cmpw_(icmp_op), binw_(bin_op) {}
void BuildGraph(const Operator* select_op, Node* lhs, Node* rhs, Node* tval,
Node* fval) {
CompareAndSelect(select_op, lhs, rhs, tval, fval);
switch (config()) {
case GraphConfig::kOneUse:
m().Return(select());
break;
case GraphConfig::kTwoUsesOneBlock:
m().Return(AddBinopUse());
break;
case GraphConfig::kTwoUsesTwoBlocks:
m().Return(AddBranchAndUse());
break;
default:
UNREACHABLE();
}
}
void CompareAndSelect(const Operator* selectop, Node* lhs, Node* rhs,
Node* tval, Node* fval) {
Node* zero = Is32() ? m().Int32Constant(0) : m().Int64Constant(0);
bin_node_ = m().AddNode(binw().op(m().machine()), lhs, rhs);
Node* cond = m().AddNode(cmpw().op(m().machine()), bin_node(), zero);
select_ = m().AddNode(selectop, cond, tval, fval);
}
Node* AddBranchAndUse() {
const Operator* cond_op = nullptr;
switch (select()->opcode()) {
case IrOpcode::kFloat32Select:
cond_op = m().machine()->Float32LessThan();
break;
case IrOpcode::kFloat64Select:
cond_op = m().machine()->Float64LessThan();
break;
case IrOpcode::kWord32Select:
cond_op = m().machine()->Int32LessThan();
break;
case IrOpcode::kWord64Select:
cond_op = m().machine()->Int64LessThan();
break;
default:
UNREACHABLE();
}
DCHECK_NOT_NULL(cond_op);
Node* cond = m().AddNode(cond_op, select(), select()->InputAt(1));
m().Branch(cond, &blocka(), &blockb());
m().Bind(&blocka());
Node* res = AddBinopUse();
m().Return(res);
m().Bind(&blockb());
return select();
}
ResultType expected(CondType lhs, CondType rhs, ResultType tval,
ResultType fval) {
CondType bin_node_res = binw().eval(lhs, rhs);
ResultType res =
Is32() ? cmpw().Int32Compare(static_cast<uint32_t>(bin_node_res), 0)
? tval
: fval
: cmpw().Int64Compare(static_cast<uint64_t>(bin_node_res), 0) ? tval
: fval;
if (config() == GraphConfig::kTwoUsesTwoBlocks && res >= tval) {
return res;
}
if (config() != GraphConfig::kOneUse) {
res += static_cast<ResultType>(bin_node_res);
}
return res;
}
BufferedRawMachineAssemblerTester<ResultType>& m() { return m_; }
GraphConfig config() const { return config_; }
const IntBinopWrapper<CondType>& binw() const { return binw_; }
const CompareWrapper& cmpw() const { return cmpw_; }
Node* select() const { return select_; }
Node* bin_node() const { return bin_node_; }
RawMachineLabel& blocka() { return blocka_; }
RawMachineLabel& blockb() { return blockb_; }
virtual Node* AddBinopUse() = 0;
virtual bool Is32() const = 0;
private:
BufferedRawMachineAssemblerTester<ResultType>& m_;
GraphConfig config_;
CompareWrapper cmpw_;
IntBinopWrapper<CondType> binw_;
Node* bin_node_;
Node* select_;
RawMachineLabel blocka_, blockb_;
};
template <typename ResultType>
class UInt32ConditionalSelectGen
: public ConditionalSelectGen<uint32_t, ResultType> {
public:
using ConditionalSelectGen<uint32_t, ResultType>::ConditionalSelectGen;
Node* AddBinopUse() override {
BufferedRawMachineAssemblerTester<ResultType>& m = this->m();
Node* bin_node = this->bin_node();
Node* bin_node_use = nullptr;
Node* select = this->select();
const Operator* add_op = nullptr;
switch (select->opcode()) {
case IrOpcode::kFloat32Select:
bin_node_use = m.RoundUint32ToFloat32(bin_node);
add_op = m.machine()->Float32Add();
break;
case IrOpcode::kFloat64Select:
bin_node_use = m.ChangeUint32ToFloat64(bin_node);
add_op = m.machine()->Float64Add();
break;
case IrOpcode::kWord32Select:
bin_node_use = bin_node;
add_op = m.machine()->Int32Add();
break;
case IrOpcode::kWord64Select:
bin_node_use = m.ChangeUint32ToUint64(bin_node);
add_op = m.machine()->Int64Add();
break;
default:
UNREACHABLE();
}
DCHECK_NOT_NULL(bin_node_use);
DCHECK_NOT_NULL(add_op);
return m.AddNode(add_op, select, bin_node_use);
}
bool Is32() const override { return true; }
};
template <typename ResultType>
class UInt64ConditionalSelectGen
: public ConditionalSelectGen<uint64_t, ResultType> {
public:
using ConditionalSelectGen<uint64_t, ResultType>::ConditionalSelectGen;
Node* AddBinopUse() override {
BufferedRawMachineAssemblerTester<ResultType>& m = this->m();
Node* bin_node = this->bin_node();
Node* bin_node_use = nullptr;
Node* select = this->select();
const Operator* add_op;
switch (select->opcode()) {
case IrOpcode::kFloat32Select:
bin_node_use = m.RoundUint64ToFloat32(bin_node);
add_op = m.machine()->Float32Add();
break;
case IrOpcode::kFloat64Select:
bin_node_use = m.RoundUint64ToFloat64(bin_node);
add_op = m.machine()->Float64Add();
break;
case IrOpcode::kWord32Select:
bin_node_use = m.TruncateInt64ToInt32(bin_node);
add_op = m.machine()->Int32Add();
break;
case IrOpcode::kWord64Select:
bin_node_use = bin_node;
add_op = m.machine()->Int64Add();
break;
default:
UNREACHABLE();
}
DCHECK(bin_node_use);
DCHECK(add_op);
return m.AddNode(add_op, select, bin_node_use);
}
bool Is32() const override { return false; }
};
constexpr IrOpcode::Value int32_cmp_opcodes[] = {
IrOpcode::kWord32Equal, IrOpcode::kInt32LessThan,
IrOpcode::kInt32LessThanOrEqual, IrOpcode::kUint32LessThan,
IrOpcode::kUint32LessThanOrEqual};
constexpr IrOpcode::Value int32_bin_opcodes[] = {
IrOpcode::kInt32Add, IrOpcode::kInt32Sub, IrOpcode::kInt32Mul,
IrOpcode::kWord32And, IrOpcode::kWord32Or, IrOpcode::kWord32Xor,
};
TEST(Word32SelectCombineInt32CompareZero) {
RawMachineAssemblerTester<int32_t> features(MachineType::Uint32());
if (!features.machine()->Word32Select().IsSupported()) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int32_cmp_opcodes) {
for (auto bin : int32_bin_opcodes) {
BufferedRawMachineAssemblerTester<uint32_t> m(
MachineType::Uint32(), MachineType::Uint32(), MachineType::Int32(),
MachineType::Int32());
UInt32ConditionalSelectGen<uint32_t> gen(m, config, cmp, bin);
Node* lhs = m.Parameter(0);
Node* rhs = m.Parameter(1);
Node* tval = m.Parameter(2);
Node* fval = m.Parameter(3);
gen.BuildGraph(m.machine()->Word32Select().op(), lhs, rhs, tval, fval);
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
uint32_t expected = gen.expected(a, b, 2, 1);
uint32_t actual = m.Call(a, b, 2, 1);
CHECK_EQ(expected, actual);
}
}
}
}
}
}
TEST(Word64SelectCombineInt32CompareZero) {
RawMachineAssemblerTester<int32_t> features(MachineType::Int32());
if (!features.machine()->Word64Select().IsSupported()) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int32_cmp_opcodes) {
for (auto bin : int32_bin_opcodes) {
BufferedRawMachineAssemblerTester<uint64_t> m(
MachineType::Uint32(), MachineType::Uint32(), MachineType::Uint64(),
MachineType::Uint64());
UInt32ConditionalSelectGen<uint64_t> gen(m, config, cmp, bin);
Node* lhs = m.Parameter(0);
Node* rhs = m.Parameter(1);
Node* tval = m.Parameter(2);
Node* fval = m.Parameter(3);
gen.BuildGraph(m.machine()->Word64Select().op(), lhs, rhs, tval, fval);
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
uint64_t c = 2;
uint64_t d = 1;
uint64_t expected = gen.expected(a, b, c, d);
uint64_t actual = m.Call(a, b, c, d);
CHECK_EQ(expected, actual);
}
}
}
}
}
}
TEST(Float32SelectCombineInt32CompareZero) {
RawMachineAssemblerTester<uint32_t> features(MachineType::Uint32());
if (!features.machine()->Float32Select().IsSupported()) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int32_cmp_opcodes) {
for (auto bin : int32_bin_opcodes) {
BufferedRawMachineAssemblerTester<float> m(
MachineType::Uint32(), MachineType::Uint32(),
MachineType::Float32(), MachineType::Float32());
UInt32ConditionalSelectGen<float> gen(m, config, cmp, bin);
Node* lhs = m.Parameter(0);
Node* rhs = m.Parameter(1);
Node* tval = m.Parameter(2);
Node* fval = m.Parameter(3);
gen.BuildGraph(m.machine()->Float32Select().op(), lhs, rhs, tval, fval);
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
float expected = gen.expected(a, b, 2.0f, 1.0f);
float actual = m.Call(a, b, 2.0f, 1.0f);
CHECK_FLOAT_EQ(expected, actual);
}
}
}
}
}
}
TEST(Float64SelectCombineInt32CompareZero) {
RawMachineAssemblerTester<uint32_t> features(MachineType::Uint32());
if (!features.machine()->Float64Select().IsSupported()) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int32_cmp_opcodes) {
for (auto bin : int32_bin_opcodes) {
BufferedRawMachineAssemblerTester<double> m(
MachineType::Uint32(), MachineType::Uint32(),
MachineType::Float64(), MachineType::Float64());
UInt32ConditionalSelectGen<double> gen(m, config, cmp, bin);
Node* lhs = m.Parameter(0);
Node* rhs = m.Parameter(1);
Node* tval = m.Parameter(2);
Node* fval = m.Parameter(3);
gen.BuildGraph(m.machine()->Float64Select().op(), lhs, rhs, tval, fval);
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
double expected = gen.expected(a, b, 2.0, 1.0);
double actual = m.Call(a, b, 2.0, 1.0);
CHECK_DOUBLE_EQ(expected, actual);
}
}
}
}
}
}
constexpr IrOpcode::Value int64_bin_opcodes[] = {
IrOpcode::kInt64Add, IrOpcode::kInt64Sub, IrOpcode::kInt64Mul,
IrOpcode::kWord64And, IrOpcode::kWord64Or, IrOpcode::kWord64Xor,
};
constexpr IrOpcode::Value int64_cmp_opcodes[] = {
IrOpcode::kWord64Equal, IrOpcode::kInt64LessThan,
IrOpcode::kInt64LessThanOrEqual, IrOpcode::kUint64LessThan,
IrOpcode::kUint64LessThanOrEqual};
TEST(Word32SelectCombineInt64CompareZero) {
RawMachineAssemblerTester<int32_t> features(MachineType::Int32());
if (!features.machine()->Word32Select().IsSupported()) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int64_cmp_opcodes) {
for (auto bin : int64_bin_opcodes) {
BufferedRawMachineAssemblerTester<uint32_t> m(
MachineType::Uint64(), MachineType::Uint64(), MachineType::Int32(),
MachineType::Int32());
UInt64ConditionalSelectGen<uint32_t> gen(m, config, cmp, bin);
Node* lhs = m.Parameter(0);
Node* rhs = m.Parameter(1);
Node* tval = m.Parameter(2);
Node* fval = m.Parameter(3);
gen.BuildGraph(m.machine()->Word32Select().op(), lhs, rhs, tval, fval);
FOR_UINT64_INPUTS(a) {
FOR_UINT64_INPUTS(b) {
uint32_t expected = gen.expected(a, b, 2, 1);
uint32_t actual = m.Call(a, b, 2, 1);
CHECK_EQ(expected, actual);
}
}
}
}
}
}
TEST(Word64SelectCombineInt64CompareZero) {
RawMachineAssemblerTester<uint32_t> features(MachineType::Uint32());
if (!features.machine()->Word64Select().IsSupported()) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int64_cmp_opcodes) {
for (auto bin : int64_bin_opcodes) {
BufferedRawMachineAssemblerTester<uint64_t> m(
MachineType::Uint64(), MachineType::Uint64(), MachineType::Uint64(),
MachineType::Uint64());
UInt64ConditionalSelectGen<uint64_t> gen(m, config, cmp, bin);
Node* lhs = m.Parameter(0);
Node* rhs = m.Parameter(1);
Node* tval = m.Parameter(2);
Node* fval = m.Parameter(3);
gen.BuildGraph(m.machine()->Word64Select().op(), lhs, rhs, tval, fval);
FOR_UINT64_INPUTS(a) {
FOR_UINT64_INPUTS(b) {
uint64_t c = 2;
uint64_t d = 1;
uint64_t expected = gen.expected(a, b, c, d);
uint64_t actual = m.Call(a, b, c, d);
CHECK_EQ(expected, actual);
}
}
}
}
}
}
TEST(Float32SelectCombineInt64CompareZero) {
RawMachineAssemblerTester<uint32_t> features(MachineType::Uint32());
if (!features.machine()->Float32Select().IsSupported()) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int64_cmp_opcodes) {
for (auto bin : int64_bin_opcodes) {
BufferedRawMachineAssemblerTester<float> m(
MachineType::Uint64(), MachineType::Uint64(),
MachineType::Float32(), MachineType::Float32());
UInt64ConditionalSelectGen<float> gen(m, config, cmp, bin);
Node* lhs = m.Parameter(0);
Node* rhs = m.Parameter(1);
Node* tval = m.Parameter(2);
Node* fval = m.Parameter(3);
gen.BuildGraph(m.machine()->Float32Select().op(), lhs, rhs, tval, fval);
FOR_UINT64_INPUTS(a) {
FOR_UINT64_INPUTS(b) {
float expected = gen.expected(a, b, 2.0f, 1.0f);
float actual = m.Call(a, b, 2.0f, 1.0f);
CHECK_FLOAT_EQ(expected, actual);
}
}
}
}
}
}
TEST(Float64SelectCombineInt64CompareZero) {
RawMachineAssemblerTester<uint32_t> features(MachineType::Uint32());
if (!features.machine()->Float64Select().IsSupported()) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int64_cmp_opcodes) {
for (auto bin : int64_bin_opcodes) {
BufferedRawMachineAssemblerTester<double> m(
MachineType::Uint64(), MachineType::Uint64(),
MachineType::Float64(), MachineType::Float64());
UInt64ConditionalSelectGen<double> gen(m, config, cmp, bin);
Node* lhs = m.Parameter(0);
Node* rhs = m.Parameter(1);
Node* tval = m.Parameter(2);
Node* fval = m.Parameter(3);
gen.BuildGraph(m.machine()->Float64Select().op(), lhs, rhs, tval, fval);
FOR_UINT64_INPUTS(a) {
FOR_UINT64_INPUTS(b) {
double expected = gen.expected(a, b, 2.0, 1.0);
double actual = m.Call(a, b, 2.0, 1.0);
CHECK_DOUBLE_EQ(expected, actual);
}
}
}
}
}
}
} // end namespace
} // namespace v8::internal::compiler

View File

@ -0,0 +1,72 @@
// Copyright 2021 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/js-operator.h"
#include "test/cctest/cctest.h"
#include "test/common/flag-utils.h"
#include "test/common/node-observer-tester.h"
namespace v8 {
namespace internal {
namespace compiler {
TEST(TestVerifyType) {
FlagScope<bool> allow_natives_syntax(&i::v8_flags.allow_natives_syntax, true);
HandleAndZoneScope handle_scope;
Isolate* isolate = handle_scope.main_isolate();
Zone* zone = handle_scope.main_zone();
const char* source =
"function test(b) {\n"
" let x = -8;\n"
" if(b) x = 42;\n"
" return %ObserveNode(%VerifyType(x));\n"
"}\n"
"\n"
"%PrepareFunctionForOptimization(test);\n"
"test(false); test(true);\n"
"%OptimizeFunctionOnNextCall(test);\n"
"test(false);\n";
bool js_intrinsic_lowering_happened = false;
bool simplified_lowering_happened = false;
ModificationObserver observer(
[](const Node* node) {
CHECK_EQ(node->opcode(), IrOpcode::kJSCallRuntime);
CHECK_EQ(CallRuntimeParametersOf(node->op()).id(),
Runtime::kVerifyType);
},
[&](const Node* node, const ObservableNodeState& old_state) {
if (old_state.opcode() == IrOpcode::kJSCallRuntime &&
node->opcode() == IrOpcode::kVerifyType) {
// CallRuntime is lowered to VerifyType in JSIntrinsicLowering.
js_intrinsic_lowering_happened = true;
return NodeObserver::Observation::kContinue;
} else if (old_state.opcode() == IrOpcode::kVerifyType &&
node->opcode() == IrOpcode::kAssertType) {
// VerifyType is lowered to AssertType in SimplifiedLowering.
// AssertType asserts for the type of its value input.
Type asserted_type = OpParameter<Type>(node->op());
CHECK(asserted_type.Equals(Type::Range(-8, 42, zone)));
simplified_lowering_happened = true;
return NodeObserver::Observation::kStop;
} else if (old_state.opcode() == node->opcode()) {
return NodeObserver::Observation::kContinue;
} else {
// Every other lowering would be wrong, so fail the test.
UNREACHABLE();
}
});
compiler::ObserveNodeScope scope(isolate, &observer);
CompileRun(source);
CHECK(js_intrinsic_lowering_happened);
CHECK(simplified_lowering_happened);
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,91 @@
// 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 "test/cctest/compiler/turboshaft-codegen-tester.h"
#include "src/base/overflowing-math.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/common/value-helper.h"
namespace v8::internal::compiler::turboshaft {
void Int32BinopInputShapeTester::TestAllInputShapes() {
base::Vector<const int32_t> inputs = ValueHelper::int32_vector();
int num_int_inputs = static_cast<int>(inputs.size());
if (num_int_inputs > 16) num_int_inputs = 16; // limit to 16 inputs
for (int i = -2; i < num_int_inputs; i++) { // for all left shapes
for (int j = -2; j < num_int_inputs; j++) { // for all right shapes
if (i >= 0 && j >= 0) break; // No constant/constant combos
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex n0;
OpIndex n1;
// left = Parameter | Load | Constant
if (i == -2) {
n0 = p0;
} else if (i == -1) {
n0 = m.LoadFromPointer(&input_a, MachineType::Int32());
} else {
n0 = m.Word32Constant(inputs[i]);
}
// right = Parameter | Load | Constant
if (j == -2) {
n1 = p1;
} else if (j == -1) {
n1 = m.LoadFromPointer(&input_b, MachineType::Int32());
} else {
n1 = m.Word32Constant(inputs[j]);
}
gen->gen(&m, n0, n1);
if (i >= 0) {
input_a = inputs[i];
RunRight(&m);
} else if (j >= 0) {
input_b = inputs[j];
RunLeft(&m);
} else {
Run(&m);
}
}
}
}
void Int32BinopInputShapeTester::Run(RawMachineAssemblerTester<int32_t>* m) {
FOR_INT32_INPUTS(pl) {
FOR_INT32_INPUTS(pr) {
input_a = pl;
input_b = pr;
int32_t expect = gen->expected(input_a, input_b);
CHECK_EQ(expect, m->Call(input_a, input_b));
}
}
}
void Int32BinopInputShapeTester::RunLeft(
RawMachineAssemblerTester<int32_t>* m) {
FOR_UINT32_INPUTS(i) {
input_a = i;
int32_t expect = gen->expected(input_a, input_b);
CHECK_EQ(expect, m->Call(input_a, input_b));
}
}
void Int32BinopInputShapeTester::RunRight(
RawMachineAssemblerTester<int32_t>* m) {
FOR_UINT32_INPUTS(i) {
input_b = i;
int32_t expect = gen->expected(input_a, input_b);
CHECK_EQ(expect, m->Call(input_a, input_b));
}
}
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,635 @@
// 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.
#ifndef V8_CCTEST_COMPILER_TURBOSHAFT_CODEGEN_TESTER_H_
#define V8_CCTEST_COMPILER_TURBOSHAFT_CODEGEN_TESTER_H_
#include "src/codegen/assembler.h"
#include "src/codegen/optimized-compilation-info.h"
#include "src/common/globals.h"
#include "src/compiler/backend/instruction-selector.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/linkage.h"
#include "src/compiler/pipeline-data-inl.h"
#include "src/compiler/pipeline.h"
#include "src/compiler/turboshaft/assembler.h"
#include "src/compiler/turboshaft/instruction-selection-phase.h"
#include "src/compiler/turboshaft/load-store-simplification-reducer.h"
#include "src/compiler/turboshaft/phase.h"
#include "src/compiler/turboshaft/representations.h"
#include "src/compiler/zone-stats.h"
#include "src/objects/code-inl.h"
#include "test/cctest/cctest.h"
#include "test/common/call-tester.h"
namespace v8::internal::compiler::turboshaft {
using BaseAssembler = TSAssembler<LoadStoreSimplificationReducer>;
class DataHolder {
public:
template <typename... ParamMachTypes>
DataHolder(Isolate* isolate, Zone* zone, MachineType return_type,
ParamMachTypes... p)
: isolate_(isolate),
graph_zone_(zone),
info_(zone->New<OptimizedCompilationInfo>(base::ArrayVector("testing"),
zone, CodeKind::FOR_TESTING)),
zone_stats_(isolate->allocator()),
ts_pipeline_data_(&zone_stats_, turboshaft::TurboshaftPipelineKind::kJS,
isolate, info_, AssemblerOptions::Default(isolate)),
descriptor_(Linkage::GetSimplifiedCDescriptor(
zone, CSignature::New(zone, return_type, p...),
CallDescriptor::kInitializeRootRegister)) {
ts_pipeline_data_.InitializeGraphComponent(nullptr);
}
PipelineData& ts_pipeline_data() { return ts_pipeline_data_; }
Isolate* isolate() { return isolate_; }
Zone* zone() { return graph_zone_; }
Graph& graph() { return ts_pipeline_data_.graph(); }
CallDescriptor* call_descriptor() { return descriptor_; }
OptimizedCompilationInfo* info() { return info_; }
private:
Isolate* isolate_;
Zone* graph_zone_;
OptimizedCompilationInfo* info_;
// zone_stats_ must be destroyed after pipeline_data_, so it's declared
// before.
ZoneStats zone_stats_;
turboshaft::PipelineData ts_pipeline_data_;
CallDescriptor* descriptor_;
};
template <typename ReturnType>
class RawMachineAssemblerTester : public HandleAndZoneScope,
public CallHelper<ReturnType>,
public DataHolder,
public BaseAssembler {
public:
template <typename... ParamMachTypes>
explicit RawMachineAssemblerTester(ParamMachTypes... p)
: HandleAndZoneScope(kCompressGraphZone),
CallHelper<ReturnType>(
main_isolate(),
CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p...)),
DataHolder(main_isolate(), main_zone(), MachineTypeForC<ReturnType>(),
p...),
BaseAssembler(&DataHolder::ts_pipeline_data(), graph(), graph(),
zone()) {
Init();
}
template <typename... ParamMachTypes>
RawMachineAssemblerTester(CodeKind kind, ParamMachTypes... p)
: HandleAndZoneScope(kCompressGraphZone),
CallHelper<ReturnType>(
main_isolate(),
CSignature::New(main_zone(), MachineTypeForC<ReturnType>(), p...)),
DataHolder(main_isolate(), main_zone(), MachineTypeForC<ReturnType>(),
p...),
BaseAssembler(&DataHolder::ts_pipeline_data(), graph(), graph(),
zone()),
kind_(kind) {
Init();
}
~RawMachineAssemblerTester() override = default;
void CheckNumber(double expected, Tagged<Object> number) {
CHECK(Object::SameValue(*this->isolate()->factory()->NewNumber(expected),
number));
}
void CheckString(const char* expected, Tagged<Object> string) {
CHECK(Object::SameValue(
*this->isolate()->factory()->InternalizeUtf8String(expected), string));
}
void GenerateCode() { Generate(); }
DirectHandle<Code> GetCode() {
Generate();
return code_.ToHandleChecked();
}
using CallHelper<ReturnType>::Call;
using Assembler::Call;
// A few Assembler helpers.
using Assembler::Parameter;
OpIndex Parameter(int i) {
return Parameter(i, RegisterRepresentation::FromMachineType(
call_descriptor()->GetParameterType(i)));
}
OpIndex PointerConstant(void* value) {
return IntPtrConstant(reinterpret_cast<intptr_t>(value));
}
using Assembler::Load;
OpIndex LoadFromPointer(void* address, MachineType type, int32_t offset = 0) {
return Load(PointerConstant(address), LoadOp::Kind::RawAligned(),
MemoryRepresentation::FromMachineType(type), offset);
}
OpIndex Load(MachineType type, OpIndex base) {
MemoryRepresentation mem_rep = MemoryRepresentation::FromMachineType(type);
return Load(base, LoadOp::Kind::RawAligned(), mem_rep);
}
using Assembler::Store;
void StoreToPointer(void* address, MachineRepresentation rep, OpIndex value) {
// Otherwise, we can use an offset instead of an Index.
return Store(PointerConstant(address), value, StoreOp::Kind::RawAligned(),
MemoryRepresentation::FromMachineRepresentation(rep),
WriteBarrierKind::kNoWriteBarrier);
}
void Store(MachineRepresentation rep, OpIndex base, OpIndex value,
WriteBarrierKind write_barrier) {
MemoryRepresentation mem_rep =
MemoryRepresentation::FromMachineRepresentation(rep);
Store(base, value, StoreOp::Kind::RawAligned(), mem_rep, write_barrier);
}
V<Word32> Int32GreaterThan(V<Word32> a, V<Word32> b) {
return Int32LessThan(b, a);
}
V<Word32> Int32GreaterThanOrEqual(V<Word32> a, V<Word32> b) {
return Int32LessThanOrEqual(b, a);
}
V<Word32> Uint32GreaterThan(V<Word32> a, V<Word32> b) {
return Uint32LessThan(b, a);
}
V<Word32> Uint32GreaterThanOrEqual(V<Word32> a, V<Word32> b) {
return Uint32LessThanOrEqual(b, a);
}
protected:
Address Generate() override {
if (code_.is_null()) {
code_ = Pipeline::GenerateTurboshaftCodeForTesting(call_descriptor(),
&ts_pipeline_data());
}
return code_.ToHandleChecked()->instruction_start();
}
private:
void Init() {
// We bind a block right at the start so that the test can start emitting
// operations without always needing to bind a block first.
Block* start_block = NewBlock();
Bind(start_block);
// We emit the parameters now so that they appear at the beginning of the
// graph (because the register allocator doesn't like it when Parameters are
// not in the 1st block). Subsequent calls to `m.Parameter()` will reuse the
// Parameters created here, thanks to Turboshaft's parameter cache (see
// TurboshaftAssemblerOpInterface::Parameter).
for (size_t i = 0; i < call_descriptor()->ParameterCount(); i++) {
Parameter(static_cast<int>(i));
}
}
CodeKind kind_ = CodeKind::FOR_TESTING;
MaybeHandle<Code> code_;
};
template <typename ReturnType>
class BufferedRawMachineAssemblerTester
: public RawMachineAssemblerTester<int32_t> {
public:
template <typename... ParamMachTypes>
explicit BufferedRawMachineAssemblerTester(ParamMachTypes... p)
: RawMachineAssemblerTester<int32_t>(
MachineType::Pointer(), ((void)p, MachineType::Pointer())...),
test_graph_signature_(
CSignature::New(this->main_zone(), MachineType::Int32(), p...)) {
static_assert(sizeof...(p) <= arraysize(parameter_nodes_),
"increase parameter_nodes_ array");
std::array<MachineType, sizeof...(p)> p_arr{{p...}};
for (size_t i = 0; i < p_arr.size(); ++i) {
parameter_nodes_[i] = Load(
p_arr[i], RawMachineAssemblerTester::Parameter(static_cast<int>(i)));
}
return_param_ = RawMachineAssemblerTester::Parameter(sizeof...(p));
}
Address Generate() override { return RawMachineAssemblerTester::Generate(); }
// The BufferedRawMachineAssemblerTester does not pass parameters directly
// to the constructed IR graph. Instead it passes a pointer to the parameter
// to the IR graph, and adds Load nodes to the IR graph to load the
// parameters from memory. Thereby it is possible to pass 64 bit parameters
// to the IR graph.
OpIndex Parameter(size_t index) {
CHECK_GT(arraysize(parameter_nodes_), index);
return parameter_nodes_[index];
}
// The BufferedRawMachineAssemblerTester adds a Store node to the IR graph
// to store the graph's return value in memory. The memory address for the
// Store node is provided as a parameter. By storing the return value in
// memory it is possible to return 64 bit values.
void Return(OpIndex input) {
if (COMPRESS_POINTERS_BOOL && MachineTypeForC<ReturnType>().IsTagged()) {
// Since we are returning values via storing to off-heap location
// generate full-word store here.
Store(MachineType::PointerRepresentation(), return_param_,
BitcastTaggedToWordPtr(input), kNoWriteBarrier);
} else {
Store(MachineTypeForC<ReturnType>().representation(), return_param_,
input, kNoWriteBarrier);
}
BaseAssembler::Return(Word32Constant(1234));
}
template <typename... Params>
ReturnType Call(Params... p) {
uintptr_t zap_data[] = {kZapValue, kZapValue};
ReturnType return_value;
static_assert(sizeof(return_value) <= sizeof(zap_data));
MemCopy(&return_value, &zap_data, sizeof(return_value));
CSignature::VerifyParams<Params...>(test_graph_signature_);
CallHelper<int32_t>::Call(reinterpret_cast<void*>(&p)...,
reinterpret_cast<void*>(&return_value));
return return_value;
}
private:
CSignature* test_graph_signature_;
OpIndex parameter_nodes_[4];
OpIndex return_param_;
};
template <>
class BufferedRawMachineAssemblerTester<void>
: public RawMachineAssemblerTester<void> {
public:
template <typename... ParamMachTypes>
explicit BufferedRawMachineAssemblerTester(ParamMachTypes... p)
: RawMachineAssemblerTester<void>(((void)p, MachineType::Pointer())...),
test_graph_signature_(
CSignature::New(RawMachineAssemblerTester<void>::main_zone(),
MachineType::None(), p...)) {
static_assert(sizeof...(p) <= arraysize(parameter_nodes_),
"increase parameter_nodes_ array");
std::array<MachineType, sizeof...(p)> p_arr{{p...}};
for (size_t i = 0; i < p_arr.size(); ++i) {
parameter_nodes_[i] = Load(p_arr[i], Parameter(i));
}
}
Address Generate() override { return RawMachineAssemblerTester::Generate(); }
// The BufferedRawMachineAssemblerTester does not pass parameters directly
// to the constructed IR graph. Instead it passes a pointer to the parameter
// to the IR graph, and adds Load nodes to the IR graph to load the
// parameters from memory. Thereby it is possible to pass 64 bit parameters
// to the IR graph.
OpIndex Parameter(size_t index) {
CHECK_GT(arraysize(parameter_nodes_), index);
return parameter_nodes_[index];
}
template <typename... Params>
void Call(Params... p) {
CSignature::VerifyParams<Params...>(test_graph_signature_);
CallHelper<void>::Call(reinterpret_cast<void*>(&p)...);
}
private:
CSignature* test_graph_signature_;
OpIndex parameter_nodes_[4];
};
static const bool USE_RESULT_BUFFER = true;
static const bool USE_RETURN_REGISTER = false;
static const int32_t CHECK_VALUE = 0x99BEEDCE;
// TODO(titzer): use the C-style calling convention, or any register-based
// calling convention for binop tests.
template <typename CType, bool use_result_buffer>
class BinopTester {
public:
explicit BinopTester(RawMachineAssemblerTester<int32_t>* tester,
MachineType type)
: T(tester),
param0(T->LoadFromPointer(&p0, type)),
param1(T->LoadFromPointer(&p1, type)),
type(type),
p0(static_cast<CType>(0)),
p1(static_cast<CType>(0)),
result(static_cast<CType>(0)) {}
RawMachineAssemblerTester<int32_t>* T;
OpIndex param0;
OpIndex param1;
CType call(CType a0, CType a1) {
p0 = a0;
p1 = a1;
if (use_result_buffer) {
CHECK_EQ(CHECK_VALUE, T->Call());
return result;
} else {
return static_cast<CType>(T->Call());
}
}
void AddReturn(OpIndex val) {
if (use_result_buffer) {
T->Store(type.representation(), T->PointerConstant(&result),
T->Word32Constant(0), val, kNoWriteBarrier);
T->Return(T->Word32Constant(CHECK_VALUE));
} else {
T->Return(val);
}
}
template <typename Ci, typename Cj, typename Fn>
void Run(const Ci& ci, const Cj& cj, const Fn& fn) {
typename Ci::const_iterator i;
typename Cj::const_iterator j;
for (i = ci.begin(); i != ci.end(); ++i) {
for (j = cj.begin(); j != cj.end(); ++j) {
CHECK_EQ(fn(*i, *j), this->call(*i, *j));
}
}
}
protected:
MachineType type;
CType p0;
CType p1;
CType result;
};
// A helper class for testing code sequences that take two int parameters and
// return an int value.
class Int32BinopTester : public BinopTester<int32_t, USE_RETURN_REGISTER> {
public:
explicit Int32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<int32_t, USE_RETURN_REGISTER>(tester,
MachineType::Int32()) {}
};
// A helper class for testing code sequences that take two int parameters and
// return an int value.
class Int64BinopTester : public BinopTester<int64_t, USE_RETURN_REGISTER> {
public:
explicit Int64BinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<int64_t, USE_RETURN_REGISTER>(tester,
MachineType::Int64()) {}
};
// A helper class for testing code sequences that take two uint parameters and
// return an uint value.
class Uint32BinopTester : public BinopTester<uint32_t, USE_RETURN_REGISTER> {
public:
explicit Uint32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<uint32_t, USE_RETURN_REGISTER>(tester,
MachineType::Uint32()) {}
uint32_t call(uint32_t a0, uint32_t a1) {
p0 = a0;
p1 = a1;
return static_cast<uint32_t>(T->Call());
}
};
// A helper class for testing code sequences that take two float parameters and
// return a float value.
class Float32BinopTester : public BinopTester<float, USE_RESULT_BUFFER> {
public:
explicit Float32BinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<float, USE_RESULT_BUFFER>(tester, MachineType::Float32()) {}
};
// A helper class for testing code sequences that take two double parameters and
// return a double value.
class Float64BinopTester : public BinopTester<double, USE_RESULT_BUFFER> {
public:
explicit Float64BinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<double, USE_RESULT_BUFFER>(tester, MachineType::Float64()) {
}
};
// A helper class for testing code sequences that take two pointer parameters
// and return a pointer value.
// TODO(titzer): pick word size of pointers based on V8_TARGET.
template <typename Type>
class PointerBinopTester : public BinopTester<Type, USE_RETURN_REGISTER> {
public:
explicit PointerBinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<Type, USE_RETURN_REGISTER>(tester, MachineType::Pointer()) {
}
};
// A helper class for testing code sequences that take two tagged parameters and
// return a tagged value.
template <typename Type>
class TaggedBinopTester : public BinopTester<Type, USE_RETURN_REGISTER> {
public:
explicit TaggedBinopTester(RawMachineAssemblerTester<int32_t>* tester)
: BinopTester<Type, USE_RETURN_REGISTER>(tester,
MachineType::AnyTagged()) {}
};
#define BINOP_LIST(V) \
V(Word32Add) \
V(Word32Sub) \
V(Word32Mul) \
V(Word32BitwiseAnd) \
V(Word32BitwiseOr) \
V(Word32BitwiseXor) \
V(Word64Add) \
V(Word64Sub) \
V(Word64Mul) \
V(Word64BitwiseAnd) \
V(Word64BitwiseOr) \
V(Word64BitwiseXor)
enum class TurboshaftBinop {
#define DEF(kind) k##kind,
BINOP_LIST(DEF)
#undef DEF
};
// A helper class for integer binary operations. Wraps a machine opcode and
// provides evaluation routines and the operators.
template <typename T>
class IntBinopWrapper {
public:
explicit IntBinopWrapper(TurboshaftBinop op) : op(op) {}
OpIndex MakeNode(BaseAssembler& m, OpIndex a, OpIndex b) {
return MakeNode(&m, a, b);
}
OpIndex MakeNode(BaseAssembler* m, OpIndex a, OpIndex b) {
switch (op) {
#define CASE(kind) \
case TurboshaftBinop::k##kind: \
return m->kind(a, b);
BINOP_LIST(CASE)
#undef CASE
}
}
T eval(T a, T b) const {
switch (op) {
case TurboshaftBinop::kWord32Add:
case TurboshaftBinop::kWord64Add:
return a + b;
case TurboshaftBinop::kWord32Sub:
case TurboshaftBinop::kWord64Sub:
return a - b;
case TurboshaftBinop::kWord32Mul:
case TurboshaftBinop::kWord64Mul:
return a * b;
case TurboshaftBinop::kWord32BitwiseAnd:
case TurboshaftBinop::kWord64BitwiseAnd:
return a & b;
case TurboshaftBinop::kWord32BitwiseOr:
case TurboshaftBinop::kWord64BitwiseOr:
return a | b;
case TurboshaftBinop::kWord32BitwiseXor:
case TurboshaftBinop::kWord64BitwiseXor:
return a ^ b;
}
}
TurboshaftBinop op;
};
#define COMPARE_LIST(V) \
V(TaggedEqual) \
V(Word32Equal) \
V(Int32LessThan) \
V(Int32LessThanOrEqual) \
V(Uint32LessThan) \
V(Uint32LessThanOrEqual) \
V(Word64Equal) \
V(Int64LessThan) \
V(Int64LessThanOrEqual) \
V(Uint64LessThan) \
V(Uint64LessThanOrEqual) \
V(Float64Equal) \
V(Float64LessThan) \
V(Float64LessThanOrEqual)
enum class TurboshaftComparison {
#define DEF(kind) k##kind,
COMPARE_LIST(DEF)
#undef DEF
};
// A helper class for testing compares. Wraps a machine opcode and provides
// evaluation routines and the operators.
class CompareWrapper {
public:
explicit CompareWrapper(TurboshaftComparison op) : op(op) {}
V<Word32> MakeNode(BaseAssembler& m, OpIndex a, OpIndex b) {
return MakeNode(&m, a, b);
}
V<Word32> MakeNode(BaseAssembler* m, OpIndex a, OpIndex b) {
switch (op) {
#define CASE(kind) \
case TurboshaftComparison::k##kind: \
return m->kind(a, b);
COMPARE_LIST(CASE)
#undef CASE
}
}
bool Int32Compare(int32_t a, int32_t b) const {
switch (op) {
case TurboshaftComparison::kWord32Equal:
case TurboshaftComparison::kTaggedEqual:
return a == b;
case TurboshaftComparison::kInt32LessThan:
return a < b;
case TurboshaftComparison::kInt32LessThanOrEqual:
return a <= b;
case TurboshaftComparison::kUint32LessThan:
return static_cast<uint32_t>(a) < static_cast<uint32_t>(b);
case TurboshaftComparison::kUint32LessThanOrEqual:
return static_cast<uint32_t>(a) <= static_cast<uint32_t>(b);
default:
UNREACHABLE();
}
}
bool Int64Compare(int64_t a, int64_t b) const {
switch (op) {
case TurboshaftComparison::kWord64Equal:
case TurboshaftComparison::kTaggedEqual:
return a == b;
case TurboshaftComparison::kInt64LessThan:
return a < b;
case TurboshaftComparison::kInt64LessThanOrEqual:
return a <= b;
case TurboshaftComparison::kUint64LessThan:
return static_cast<uint64_t>(a) < static_cast<uint64_t>(b);
case TurboshaftComparison::kUint64LessThanOrEqual:
return static_cast<uint64_t>(a) <= static_cast<uint64_t>(b);
default:
UNREACHABLE();
}
}
bool Float64Compare(double a, double b) const {
switch (op) {
case TurboshaftComparison::kFloat64Equal:
return a == b;
case TurboshaftComparison::kFloat64LessThan:
return a < b;
case TurboshaftComparison::kFloat64LessThanOrEqual:
return a <= b;
default:
UNREACHABLE();
}
}
TurboshaftComparison op;
};
// A small closure class to generate code for a function of two inputs that
// produces a single output so that it can be used in many different contexts.
// The {expected()} method should compute the expected output for a given
// pair of inputs.
template <typename T>
class BinopGen {
public:
virtual void gen(RawMachineAssemblerTester<int32_t>* m, OpIndex a,
OpIndex b) = 0;
virtual T expected(T a, T b) = 0;
virtual ~BinopGen() = default;
};
// A helper class to generate various combination of input shape combinations
// and run the generated code to ensure it produces the correct results.
class Int32BinopInputShapeTester {
public:
explicit Int32BinopInputShapeTester(BinopGen<int32_t>* g)
: gen(g), input_a(0), input_b(0) {}
void TestAllInputShapes();
private:
BinopGen<int32_t>* gen;
int32_t input_a;
int32_t input_b;
void Run(RawMachineAssemblerTester<int32_t>* m);
void RunLeft(RawMachineAssemblerTester<int32_t>* m);
void RunRight(RawMachineAssemblerTester<int32_t>* m);
};
} // namespace v8::internal::compiler::turboshaft
#endif // V8_CCTEST_COMPILER_TURBOSHAFT_CODEGEN_TESTER_H_

View File

@ -0,0 +1,869 @@
// 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/overflowing-math.h"
#include "src/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/turboshaft-codegen-tester.h"
#include "test/common/value-helper.h"
namespace v8::internal::compiler::turboshaft {
static TurboshaftComparison int32cmp_opcodes[] = {
TurboshaftComparison::kWord32Equal, TurboshaftComparison::kInt32LessThan,
TurboshaftComparison::kInt32LessThanOrEqual,
TurboshaftComparison::kUint32LessThan,
TurboshaftComparison::kUint32LessThanOrEqual};
TEST(BranchCombineWord32EqualZero_1) {
// Test combining a branch with x == 0
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
int32_t eq_constant = -1033;
int32_t ne_constant = 825118;
OpIndex p0 = m.Parameter(0);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(m.Word32Equal(p0, m.Word32Constant(0)), blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
int32_t expect = a == 0 ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineWord32EqualZero_chain) {
// Test combining a branch with a chain of x == 0 == 0 == 0 ...
int32_t eq_constant = -1133;
int32_t ne_constant = 815118;
for (int k = 0; k < 6; k++) {
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
OpIndex p0 = m.Parameter(0);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
V<Word32> cond = p0;
for (int j = 0; j < k; j++) {
cond = m.Word32Equal(cond, m.Word32Constant(0));
}
m.Branch(cond, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
int32_t expect = (k & 1) == 1 ? (a == 0 ? eq_constant : ne_constant)
: (a == 0 ? ne_constant : eq_constant);
CHECK_EQ(expect, m.Call(a));
}
}
}
TEST(BranchCombineInt32LessThanZero_1) {
// Test combining a branch with x < 0
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
int32_t eq_constant = -1433;
int32_t ne_constant = 845118;
OpIndex p0 = m.Parameter(0);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(m.Int32LessThan(p0, m.Word32Constant(0)), blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
int32_t expect = a < 0 ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineUint32LessThan100_1) {
// Test combining a branch with x < 100
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32());
int32_t eq_constant = 1471;
int32_t ne_constant = 88845718;
OpIndex p0 = m.Parameter(0);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(m.Uint32LessThan(p0, m.Word32Constant(100)), blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
FOR_UINT32_INPUTS(a) {
int32_t expect = a < 100 ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineUint32LessThanOrEqual100_1) {
// Test combining a branch with x <= 100
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32());
int32_t eq_constant = 1479;
int32_t ne_constant = 77845719;
OpIndex p0 = m.Parameter(0);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(m.Uint32LessThanOrEqual(p0, m.Word32Constant(100)), blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
FOR_UINT32_INPUTS(a) {
int32_t expect = a <= 100 ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineZeroLessThanInt32_1) {
// Test combining a branch with 0 < x
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
int32_t eq_constant = -2033;
int32_t ne_constant = 225118;
OpIndex p0 = m.Parameter(0);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(m.Int32LessThan(m.Word32Constant(0), p0), blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
int32_t expect = 0 < a ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineInt32GreaterThanZero_1) {
// Test combining a branch with x > 0
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
int32_t eq_constant = -1073;
int32_t ne_constant = 825178;
OpIndex p0 = m.Parameter(0);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(m.Int32GreaterThan(p0, m.Word32Constant(0)), blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
int32_t expect = a > 0 ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a));
}
}
TEST(BranchCombineWord32EqualP) {
// Test combining a branch with an Word32Equal.
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
int32_t eq_constant = -1035;
int32_t ne_constant = 825018;
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(m.Word32Equal(p0, p1), blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = a == b ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineWord32EqualI) {
int32_t eq_constant = -1135;
int32_t ne_constant = 925718;
for (int left = 0; left < 2; left++) {
FOR_INT32_INPUTS(a) {
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
OpIndex p0 = m.Word32Constant(a);
OpIndex p1 = m.Parameter(0);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
if (left == 1) m.Branch(m.Word32Equal(p0, p1), blocka, blockb);
if (left == 0) m.Branch(m.Word32Equal(p1, p0), blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
FOR_INT32_INPUTS(b) {
int32_t expect = a == b ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(b));
}
}
}
}
TEST(BranchCombineInt32CmpP) {
int32_t eq_constant = -1235;
int32_t ne_constant = 725018;
for (int op = 0; op < 2; op++) {
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
if (op == 0) m.Branch(m.Int32LessThan(p0, p1), blocka, blockb);
if (op == 1) m.Branch(m.Int32LessThanOrEqual(p0, p1), blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = 0;
if (op == 0) expect = a < b ? eq_constant : ne_constant;
if (op == 1) expect = a <= b ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
}
TEST(BranchCombineInt32CmpI) {
int32_t eq_constant = -1175;
int32_t ne_constant = 927711;
for (int op = 0; op < 2; op++) {
FOR_INT32_INPUTS(a) {
RawMachineAssemblerTester<int32_t> m(MachineType::Int32());
OpIndex p0 = m.Word32Constant(a);
OpIndex p1 = m.Parameter(0);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
if (op == 0) m.Branch(m.Int32LessThan(p0, p1), blocka, blockb);
if (op == 1) m.Branch(m.Int32LessThanOrEqual(p0, p1), blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
FOR_INT32_INPUTS(b) {
int32_t expect = 0;
if (op == 0) expect = a < b ? eq_constant : ne_constant;
if (op == 1) expect = a <= b ? eq_constant : ne_constant;
CHECK_EQ(expect, m.Call(b));
}
}
}
}
// Now come the sophisticated tests for many input shape combinations.
// Materializes a boolean (1 or 0) from a comparison.
class CmpMaterializeBoolGen : public BinopGen<int32_t> {
public:
CompareWrapper w;
bool invert;
CmpMaterializeBoolGen(TurboshaftComparison op, bool i) : w(op), invert(i) {}
void gen(RawMachineAssemblerTester<int32_t>* m, OpIndex a,
OpIndex b) override {
OpIndex cond = w.MakeNode(m, a, b);
if (invert) cond = m->Word32Equal(cond, m->Word32Constant(0));
m->Return(cond);
}
int32_t expected(int32_t a, int32_t b) override {
if (invert) return !w.Int32Compare(a, b) ? 1 : 0;
return w.Int32Compare(a, b) ? 1 : 0;
}
};
// Generates a branch and return one of two values from a comparison.
class CmpBranchGen : public BinopGen<int32_t> {
public:
CompareWrapper w;
bool invert;
bool true_first;
int32_t eq_constant;
int32_t ne_constant;
CmpBranchGen(TurboshaftComparison op, bool i, bool t, int32_t eq, int32_t ne)
: w(op), invert(i), true_first(t), eq_constant(eq), ne_constant(ne) {}
void gen(RawMachineAssemblerTester<int32_t>* m, OpIndex a,
OpIndex b) override {
Block *blocka = m->NewBlock(), *blockb = m->NewBlock();
V<Word32> cond = w.MakeNode(m, a, b);
if (invert) cond = m->Word32Equal(cond, m->Word32Constant(0));
m->Branch(cond, blocka, blockb);
if (true_first) {
m->Bind(blocka);
m->Return(m->Word32Constant(eq_constant));
m->Bind(blockb);
m->Return(m->Word32Constant(ne_constant));
} else {
m->Bind(blockb);
m->Return(m->Word32Constant(ne_constant));
m->Bind(blocka);
m->Return(m->Word32Constant(eq_constant));
}
}
int32_t expected(int32_t a, int32_t b) override {
if (invert) return !w.Int32Compare(a, b) ? eq_constant : ne_constant;
return w.Int32Compare(a, b) ? eq_constant : ne_constant;
}
};
TEST(BranchCombineInt32CmpAllInputShapes_materialized) {
for (size_t i = 0; i < arraysize(int32cmp_opcodes); i++) {
CmpMaterializeBoolGen gen(int32cmp_opcodes[i], false);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineInt32CmpAllInputShapes_inverted_materialized) {
for (size_t i = 0; i < arraysize(int32cmp_opcodes); i++) {
CmpMaterializeBoolGen gen(int32cmp_opcodes[i], true);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineInt32CmpAllInputShapes_branch_true) {
for (int i = 0; i < static_cast<int>(arraysize(int32cmp_opcodes)); i++) {
CmpBranchGen gen(int32cmp_opcodes[i], false, false, 995 + i, -1011 - i);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineInt32CmpAllInputShapes_branch_false) {
for (int i = 0; i < static_cast<int>(arraysize(int32cmp_opcodes)); i++) {
CmpBranchGen gen(int32cmp_opcodes[i], false, true, 795 + i, -2011 - i);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineInt32CmpAllInputShapes_inverse_branch_true) {
for (int i = 0; i < static_cast<int>(arraysize(int32cmp_opcodes)); i++) {
CmpBranchGen gen(int32cmp_opcodes[i], true, false, 695 + i, -3011 - i);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineInt32CmpAllInputShapes_inverse_branch_false) {
for (int i = 0; i < static_cast<int>(arraysize(int32cmp_opcodes)); i++) {
CmpBranchGen gen(int32cmp_opcodes[i], true, true, 595 + i, -4011 - i);
Int32BinopInputShapeTester tester(&gen);
tester.TestAllInputShapes();
}
}
TEST(BranchCombineFloat64Compares) {
double inf = V8_INFINITY;
double nan = std::numeric_limits<double>::quiet_NaN();
double inputs[] = {0.0, 1.0, -1.0, -inf, inf, nan};
int32_t eq_constant = -1733;
int32_t ne_constant = 915118;
double input_a = 0.0;
double input_b = 0.0;
CompareWrapper cmps[] = {
CompareWrapper(TurboshaftComparison::kFloat64Equal),
CompareWrapper(TurboshaftComparison::kFloat64LessThan),
CompareWrapper(TurboshaftComparison::kFloat64LessThanOrEqual)};
for (size_t c = 0; c < arraysize(cmps); c++) {
CompareWrapper cmp = cmps[c];
for (int invert = 0; invert < 2; invert++) {
RawMachineAssemblerTester<int32_t> m;
OpIndex a = m.LoadFromPointer(&input_a, MachineType::Float64());
OpIndex b = m.LoadFromPointer(&input_b, MachineType::Float64());
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
V<Word32> cond = cmp.MakeNode(&m, a, b);
if (invert) cond = m.Word32Equal(cond, m.Word32Constant(0));
m.Branch(cond, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(eq_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(ne_constant));
for (size_t i = 0; i < arraysize(inputs); ++i) {
for (size_t j = 0; j < arraysize(inputs); ++j) {
input_a = inputs[i];
input_b = inputs[j];
int32_t expected =
invert ? (cmp.Float64Compare(input_a, input_b) ? ne_constant
: eq_constant)
: (cmp.Float64Compare(input_a, input_b) ? eq_constant
: ne_constant);
CHECK_EQ(expected, m.Call());
}
}
}
}
}
TEST(BranchCombineEffectLevel) {
// Test that the load doesn't get folded into the branch, as there's a store
// between them. See http://crbug.com/611976.
int32_t input = 0;
RawMachineAssemblerTester<int32_t> m;
OpIndex a = m.LoadFromPointer(&input, MachineType::Int32());
V<Word32> compare = m.Word32BitwiseAnd(a, m.Word32Constant(1));
V<Word32> equal = m.Word32Equal(compare, m.Word32Constant(0));
m.StoreToPointer(&input, MachineRepresentation::kWord32, m.Word32Constant(1));
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(equal, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(42));
m.Bind(blockb);
m.Return(m.Word32Constant(0));
CHECK_EQ(42, m.Call());
}
TEST(BranchCombineWord32AddLessThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32Add(p0, p1);
V<Word32> compare = m.Int32LessThan(add, m.Word32Constant(0));
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect =
(base::AddWithWraparound(a, b) < 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineWord32AddGreaterThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32Add(p0, p1);
V<Word32> compare = m.Int32GreaterThanOrEqual(add, m.Word32Constant(0));
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect =
(base::AddWithWraparound(a, b) >= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroGreaterThanAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32Add(p0, p1);
V<Word32> compare = m.Int32GreaterThan(m.Word32Constant(0), add);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect =
(0 > base::AddWithWraparound(a, b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroLessThanOrEqualAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32Add(p0, p1);
V<Word32> compare = m.Int32LessThanOrEqual(m.Word32Constant(0), add);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect =
(0 <= base::AddWithWraparound(a, b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUWord32AddLessThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32Add(p0, p1);
V<Word32> compare = m.Uint32LessThanOrEqual(add, m.Word32Constant(0));
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (a + b <= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUWord32AddGreaterThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32Add(p0, p1);
V<Word32> compare = m.Uint32GreaterThan(add, m.Word32Constant(0));
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (a + b > 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroGreaterThanOrEqualAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32Add(p0, p1);
V<Word32> compare = m.Uint32GreaterThanOrEqual(m.Word32Constant(0), add);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (0 >= a + b) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroLessThanAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32Add(p0, p1);
V<Word32> compare = m.Uint32LessThan(m.Word32Constant(0), add);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (0 < a + b) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineWord32BitwiseAndLessThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32BitwiseAnd(p0, p1);
V<Word32> compare = m.Int32LessThan(add, m.Word32Constant(0));
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = ((a & b) < 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineWord32BitwiseAndGreaterThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32BitwiseAnd(p0, p1);
V<Word32> compare = m.Int32GreaterThanOrEqual(add, m.Word32Constant(0));
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = ((a & b) >= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroGreaterThanAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32BitwiseAnd(p0, p1);
V<Word32> compare = m.Int32GreaterThan(m.Word32Constant(0), add);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = (0 > (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroLessThanOrEqualAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32BitwiseAnd(p0, p1);
V<Word32> compare = m.Int32LessThanOrEqual(m.Word32Constant(0), add);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_INT32_INPUTS(a) {
FOR_INT32_INPUTS(b) {
int32_t expect = (0 <= (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32AndLessThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32BitwiseAnd(p0, p1);
V<Word32> compare = m.Uint32LessThanOrEqual(add, m.Word32Constant(0));
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = ((a & b) <= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32AndGreaterThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32BitwiseAnd(p0, p1);
V<Word32> compare = m.Uint32GreaterThan(add, m.Word32Constant(0));
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = ((a & b) > 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroGreaterThanOrEqualAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32BitwiseAnd(p0, p1);
V<Word32> compare = m.Uint32GreaterThanOrEqual(m.Word32Constant(0), add);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (0 >= (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroLessThanAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
OpIndex p0 = m.Parameter(0);
OpIndex p1 = m.Parameter(1);
OpIndex add = m.Word32BitwiseAnd(p0, p1);
V<Word32> compare = m.Uint32LessThan(m.Word32Constant(0), add);
Block *blocka = m.NewBlock(), *blockb = m.NewBlock();
m.Branch(compare, blocka, blockb);
m.Bind(blocka);
m.Return(m.Word32Constant(t_constant));
m.Bind(blockb);
m.Return(m.Word32Constant(f_constant));
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
int32_t expect = (0 < (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
} // namespace v8::internal::compiler::turboshaft

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,522 @@
// 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/objects/objects-inl.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/turboshaft-codegen-tester.h"
#include "test/common/value-helper.h"
namespace v8::internal::compiler::turboshaft {
// Generates a binop arithmetic instruction, followed by an integer compare zero
// and select. This is to test a possible merge of the arithmetic op and the
// compare for use by the select. We test a matrix of configurations:
// - floating-point and integer select.
// - add, sub, mul, and, or and xor.
// - int32, uint32t, int64_t, uint64_t, float and double.
// - one or multiple users of the binary operation.
// - two different graph layouts (single block vs three blocks).
namespace {
enum GraphConfig { kOneUse, kTwoUsesOneBlock, kTwoUsesTwoBlocks };
constexpr GraphConfig graph_configs[] = {GraphConfig::kOneUse,
GraphConfig::kTwoUsesOneBlock,
GraphConfig::kTwoUsesTwoBlocks};
#define SELECT_OP_LIST(V) \
V(Word32Select) \
V(Word64Select) \
V(Float32Select) \
V(Float64Select)
enum class SelectOperator {
#define DEF(kind) k##kind,
SELECT_OP_LIST(DEF)
#undef DEF
};
bool SelectIsSupported(SelectOperator op) {
// SupportedOperations::Initialize is usually called by the Turboshaft
// Assembler, but some tests use this function before having created an
// Assembler, so we manually call it here to make sure that the
// SupportedOperations list is indeed initialized.
SupportedOperations::Initialize();
switch (op) {
case SelectOperator::kWord32Select:
return SupportedOperations::word32_select();
case SelectOperator::kWord64Select:
return SupportedOperations::word64_select();
case SelectOperator::kFloat32Select:
return SupportedOperations::float32_select();
case SelectOperator::kFloat64Select:
return SupportedOperations::float64_select();
}
}
// kOneUse:
// (bin_res = binop lhs, rhs)
// (return (select (compare bin_res, zero, cond), tval, fval))
//
// kTwoUsesOneBlock:
// (bin_res = binop lhs, rhs)
// (return (add (select (compare bin_res, zero, cond), tval, fval), bin_res))
//
// kTwoUsesTwoBlocks:
// Same as above, but the final addition is conditionally executed in a
// different block.
// (bin_res = binop lhs, rhs)
// (select_res = (select (compare bin_res, zero, cond), tval, fval))
// (select_res >= tval)
// ? (return select_res)
// : (return (add select_res, bin_res))
template <typename CondType, typename ResultType>
class ConditionalSelectGen {
public:
ConditionalSelectGen(BufferedRawMachineAssemblerTester<ResultType>& m,
GraphConfig c, TurboshaftComparison icmp_op,
TurboshaftBinop bin_op)
: m_(m),
config_(c),
cmpw_(icmp_op),
binw_(bin_op),
blocka_(m.NewBlock()),
blockb_(m.NewBlock()) {}
void BuildGraph(SelectOperator select_op, OpIndex lhs, OpIndex rhs,
OpIndex tval, OpIndex fval) {
CompareAndSelect(select_op, lhs, rhs, tval, fval);
switch (config()) {
case GraphConfig::kOneUse:
m().Return(select());
break;
case GraphConfig::kTwoUsesOneBlock:
m().Return(AddBinopUse());
break;
case GraphConfig::kTwoUsesTwoBlocks:
m().Return(AddBranchAndUse());
break;
default:
UNREACHABLE();
}
}
void CompareAndSelect(SelectOperator selectop, OpIndex lhs, OpIndex rhs,
OpIndex tval, OpIndex fval) {
OpIndex zero = Is32()
? OpIndex{m().Word32Constant(0)}
: OpIndex{m().Word64Constant(static_cast<uint64_t>(0))};
bin_node_ = binw().MakeNode(m(), lhs, rhs);
OpIndex cond = cmpw().MakeNode(m(), bin_node(), zero);
select_ = MakeSelect(selectop, cond, tval, fval);
select_op_ = selectop;
}
OpIndex AddBranchAndUse() {
OpIndex cond_second_input =
m().Get(select()).template Cast<SelectOp>().vtrue();
V<Word32> cond;
switch (select_op()) {
case SelectOperator::kFloat32Select:
cond = m().Float32LessThan(select(), cond_second_input);
break;
case SelectOperator::kFloat64Select:
cond = m().Float64LessThan(select(), cond_second_input);
break;
case SelectOperator::kWord32Select:
cond = m().Int32LessThan(select(), cond_second_input);
break;
case SelectOperator::kWord64Select:
cond = m().Int64LessThan(select(), cond_second_input);
break;
}
m().Branch(cond, blocka(), blockb());
m().Bind(blocka());
OpIndex res = AddBinopUse();
m().Return(res);
m().Bind(blockb());
return select();
}
ResultType expected(CondType lhs, CondType rhs, ResultType tval,
ResultType fval) {
CondType bin_node_res = binw().eval(lhs, rhs);
ResultType res =
Is32() ? cmpw().Int32Compare(static_cast<uint32_t>(bin_node_res), 0)
? tval
: fval
: cmpw().Int64Compare(static_cast<uint64_t>(bin_node_res), 0) ? tval
: fval;
if (config() == GraphConfig::kTwoUsesTwoBlocks && res >= tval) {
return res;
}
if (config() != GraphConfig::kOneUse) {
res += static_cast<ResultType>(bin_node_res);
}
return res;
}
BufferedRawMachineAssemblerTester<ResultType>& m() { return m_; }
GraphConfig config() const { return config_; }
IntBinopWrapper<CondType>& binw() { return binw_; }
CompareWrapper& cmpw() { return cmpw_; }
OpIndex select() const { return select_; }
SelectOperator select_op() const { return select_op_; }
OpIndex bin_node() const { return bin_node_; }
Block* blocka() { return blocka_; }
Block* blockb() { return blockb_; }
virtual OpIndex AddBinopUse() = 0;
virtual bool Is32() const = 0;
private:
OpIndex MakeSelect(SelectOperator op, OpIndex cond, OpIndex vtrue,
OpIndex vfalse) {
switch (op) {
#define CASE(kind) \
case SelectOperator::k##kind: \
return m().kind(cond, vtrue, vfalse);
SELECT_OP_LIST(CASE)
#undef CASE
}
}
BufferedRawMachineAssemblerTester<ResultType>& m_;
GraphConfig config_;
CompareWrapper cmpw_;
IntBinopWrapper<CondType> binw_;
OpIndex bin_node_;
OpIndex select_;
SelectOperator select_op_;
Block *blocka_, *blockb_;
};
template <typename ResultType>
class UInt32ConditionalSelectGen
: public ConditionalSelectGen<uint32_t, ResultType> {
public:
using ConditionalSelectGen<uint32_t, ResultType>::ConditionalSelectGen;
OpIndex AddBinopUse() override {
BufferedRawMachineAssemblerTester<ResultType>& m = this->m();
switch (this->select_op()) {
case SelectOperator::kFloat32Select:
return m.Float32Add(this->select(),
m.ChangeUint32ToFloat32(this->bin_node()));
case SelectOperator::kFloat64Select:
return m.Float64Add(this->select(),
m.ChangeUint32ToFloat64(this->bin_node()));
case SelectOperator::kWord32Select:
return m.Word32Add(this->select(), this->bin_node());
case SelectOperator::kWord64Select:
return m.Word64Add(this->select(),
m.ChangeUint32ToUint64(this->bin_node()));
}
}
bool Is32() const override { return true; }
};
template <typename ResultType>
class UInt64ConditionalSelectGen
: public ConditionalSelectGen<uint64_t, ResultType> {
public:
using ConditionalSelectGen<uint64_t, ResultType>::ConditionalSelectGen;
OpIndex AddBinopUse() override {
BufferedRawMachineAssemblerTester<ResultType>& m = this->m();
switch (this->select_op()) {
case SelectOperator::kFloat32Select:
return m.Float32Add(this->select(),
m.ChangeUint64ToFloat32(this->bin_node()));
case SelectOperator::kFloat64Select:
return m.Float64Add(this->select(),
m.ChangeUint64ToFloat64(this->bin_node()));
case SelectOperator::kWord32Select:
return m.Word32Add(this->select(),
m.TruncateWord64ToWord32(this->bin_node()));
case SelectOperator::kWord64Select:
return m.Word64Add(this->select(), this->bin_node());
}
}
bool Is32() const override { return false; }
};
constexpr TurboshaftComparison int32_cmp_opcodes[] = {
TurboshaftComparison::kWord32Equal, TurboshaftComparison::kInt32LessThan,
TurboshaftComparison::kInt32LessThanOrEqual,
TurboshaftComparison::kUint32LessThan,
TurboshaftComparison::kUint32LessThanOrEqual};
constexpr TurboshaftBinop int32_bin_opcodes[] = {
TurboshaftBinop::kWord32Add, TurboshaftBinop::kWord32Sub,
TurboshaftBinop::kWord32Mul, TurboshaftBinop::kWord32BitwiseAnd,
TurboshaftBinop::kWord32BitwiseOr, TurboshaftBinop::kWord32BitwiseXor,
};
TEST(Word32SelectCombineInt32CompareZero) {
if (!SelectIsSupported(SelectOperator::kWord32Select)) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int32_cmp_opcodes) {
for (auto bin : int32_bin_opcodes) {
BufferedRawMachineAssemblerTester<uint32_t> m(
MachineType::Uint32(), MachineType::Uint32(), MachineType::Int32(),
MachineType::Int32());
UInt32ConditionalSelectGen<uint32_t> gen(m, config, cmp, bin);
OpIndex lhs = m.Parameter(0);
OpIndex rhs = m.Parameter(1);
OpIndex tval = m.Parameter(2);
OpIndex fval = m.Parameter(3);
gen.BuildGraph(SelectOperator::kWord32Select, lhs, rhs, tval, fval);
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
uint32_t expected = gen.expected(a, b, 2, 1);
uint32_t actual = m.Call(a, b, 2, 1);
CHECK_EQ(expected, actual);
}
}
}
}
}
}
TEST(Word64SelectCombineInt32CompareZero) {
if (!SelectIsSupported(SelectOperator::kWord64Select)) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int32_cmp_opcodes) {
for (auto bin : int32_bin_opcodes) {
BufferedRawMachineAssemblerTester<uint64_t> m(
MachineType::Uint32(), MachineType::Uint32(), MachineType::Uint64(),
MachineType::Uint64());
UInt32ConditionalSelectGen<uint64_t> gen(m, config, cmp, bin);
OpIndex lhs = m.Parameter(0);
OpIndex rhs = m.Parameter(1);
OpIndex tval = m.Parameter(2);
OpIndex fval = m.Parameter(3);
gen.BuildGraph(SelectOperator::kWord64Select, lhs, rhs, tval, fval);
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
uint64_t c = 2;
uint64_t d = 1;
uint64_t expected = gen.expected(a, b, c, d);
uint64_t actual = m.Call(a, b, c, d);
CHECK_EQ(expected, actual);
}
}
}
}
}
}
TEST(Float32SelectCombineInt32CompareZero) {
if (!SelectIsSupported(SelectOperator::kFloat32Select)) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int32_cmp_opcodes) {
for (auto bin : int32_bin_opcodes) {
BufferedRawMachineAssemblerTester<float> m(
MachineType::Uint32(), MachineType::Uint32(),
MachineType::Float32(), MachineType::Float32());
UInt32ConditionalSelectGen<float> gen(m, config, cmp, bin);
OpIndex lhs = m.Parameter(0);
OpIndex rhs = m.Parameter(1);
OpIndex tval = m.Parameter(2);
OpIndex fval = m.Parameter(3);
gen.BuildGraph(SelectOperator::kFloat32Select, lhs, rhs, tval, fval);
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
float expected = gen.expected(a, b, 2.0f, 1.0f);
float actual = m.Call(a, b, 2.0f, 1.0f);
CHECK_FLOAT_EQ(expected, actual);
}
}
}
}
}
}
TEST(Float64SelectCombineInt32CompareZero) {
if (!SelectIsSupported(SelectOperator::kFloat64Select)) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int32_cmp_opcodes) {
for (auto bin : int32_bin_opcodes) {
BufferedRawMachineAssemblerTester<double> m(
MachineType::Uint32(), MachineType::Uint32(),
MachineType::Float64(), MachineType::Float64());
UInt32ConditionalSelectGen<double> gen(m, config, cmp, bin);
OpIndex lhs = m.Parameter(0);
OpIndex rhs = m.Parameter(1);
OpIndex tval = m.Parameter(2);
OpIndex fval = m.Parameter(3);
gen.BuildGraph(SelectOperator::kFloat64Select, lhs, rhs, tval, fval);
FOR_UINT32_INPUTS(a) {
FOR_UINT32_INPUTS(b) {
double expected = gen.expected(a, b, 2.0, 1.0);
double actual = m.Call(a, b, 2.0, 1.0);
CHECK_DOUBLE_EQ(expected, actual);
}
}
}
}
}
}
constexpr TurboshaftBinop int64_bin_opcodes[] = {
TurboshaftBinop::kWord64Add, TurboshaftBinop::kWord64Sub,
TurboshaftBinop::kWord64Mul, TurboshaftBinop::kWord64BitwiseAnd,
TurboshaftBinop::kWord64BitwiseOr, TurboshaftBinop::kWord64BitwiseXor,
};
constexpr TurboshaftComparison int64_cmp_opcodes[] = {
TurboshaftComparison::kWord64Equal, TurboshaftComparison::kInt64LessThan,
TurboshaftComparison::kInt64LessThanOrEqual,
TurboshaftComparison::kUint64LessThan,
TurboshaftComparison::kUint64LessThanOrEqual};
TEST(Word32SelectCombineInt64CompareZero) {
RawMachineAssemblerTester<int32_t> features(MachineType::Int32());
if (!SelectIsSupported(SelectOperator::kWord32Select)) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int64_cmp_opcodes) {
for (auto bin : int64_bin_opcodes) {
BufferedRawMachineAssemblerTester<uint32_t> m(
MachineType::Uint64(), MachineType::Uint64(), MachineType::Int32(),
MachineType::Int32());
UInt64ConditionalSelectGen<uint32_t> gen(m, config, cmp, bin);
OpIndex lhs = m.Parameter(0);
OpIndex rhs = m.Parameter(1);
OpIndex tval = m.Parameter(2);
OpIndex fval = m.Parameter(3);
gen.BuildGraph(SelectOperator::kWord32Select, lhs, rhs, tval, fval);
FOR_UINT64_INPUTS(a) {
FOR_UINT64_INPUTS(b) {
uint32_t expected = gen.expected(a, b, 2, 1);
uint32_t actual = m.Call(a, b, 2, 1);
CHECK_EQ(expected, actual);
}
}
}
}
}
}
TEST(Word64SelectCombineInt64CompareZero) {
RawMachineAssemblerTester<uint32_t> features(MachineType::Uint32());
if (!SelectIsSupported(SelectOperator::kWord64Select)) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int64_cmp_opcodes) {
for (auto bin : int64_bin_opcodes) {
BufferedRawMachineAssemblerTester<uint64_t> m(
MachineType::Uint64(), MachineType::Uint64(), MachineType::Uint64(),
MachineType::Uint64());
UInt64ConditionalSelectGen<uint64_t> gen(m, config, cmp, bin);
OpIndex lhs = m.Parameter(0);
OpIndex rhs = m.Parameter(1);
OpIndex tval = m.Parameter(2);
OpIndex fval = m.Parameter(3);
gen.BuildGraph(SelectOperator::kWord64Select, lhs, rhs, tval, fval);
FOR_UINT64_INPUTS(a) {
FOR_UINT64_INPUTS(b) {
uint64_t c = 2;
uint64_t d = 1;
uint64_t expected = gen.expected(a, b, c, d);
uint64_t actual = m.Call(a, b, c, d);
CHECK_EQ(expected, actual);
}
}
}
}
}
}
TEST(Float32SelectCombineInt64CompareZero) {
RawMachineAssemblerTester<uint32_t> features(MachineType::Uint32());
if (!SelectIsSupported(SelectOperator::kFloat32Select)) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int64_cmp_opcodes) {
for (auto bin : int64_bin_opcodes) {
BufferedRawMachineAssemblerTester<float> m(
MachineType::Uint64(), MachineType::Uint64(),
MachineType::Float32(), MachineType::Float32());
UInt64ConditionalSelectGen<float> gen(m, config, cmp, bin);
OpIndex lhs = m.Parameter(0);
OpIndex rhs = m.Parameter(1);
OpIndex tval = m.Parameter(2);
OpIndex fval = m.Parameter(3);
gen.BuildGraph(SelectOperator::kFloat32Select, lhs, rhs, tval, fval);
FOR_UINT64_INPUTS(a) {
FOR_UINT64_INPUTS(b) {
float expected = gen.expected(a, b, 2.0f, 1.0f);
float actual = m.Call(a, b, 2.0f, 1.0f);
CHECK_FLOAT_EQ(expected, actual);
}
}
}
}
}
}
TEST(Float64SelectCombineInt64CompareZero) {
RawMachineAssemblerTester<uint32_t> features(MachineType::Uint32());
if (!SelectIsSupported(SelectOperator::kFloat64Select)) {
return;
}
for (auto config : graph_configs) {
for (auto cmp : int64_cmp_opcodes) {
for (auto bin : int64_bin_opcodes) {
BufferedRawMachineAssemblerTester<double> m(
MachineType::Uint64(), MachineType::Uint64(),
MachineType::Float64(), MachineType::Float64());
UInt64ConditionalSelectGen<double> gen(m, config, cmp, bin);
OpIndex lhs = m.Parameter(0);
OpIndex rhs = m.Parameter(1);
OpIndex tval = m.Parameter(2);
OpIndex fval = m.Parameter(3);
gen.BuildGraph(SelectOperator::kFloat64Select, lhs, rhs, tval, fval);
FOR_UINT64_INPUTS(a) {
FOR_UINT64_INPUTS(b) {
double expected = gen.expected(a, b, 2.0, 1.0);
double actual = m.Call(a, b, 2.0, 1.0);
CHECK_DOUBLE_EQ(expected, actual);
}
}
}
}
}
}
} // end namespace
} // namespace v8::internal::compiler::turboshaft