// 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 "src/compiler/control-builders.h" #include "src/compiler/node-properties-inl.h" #include "src/compiler/pipeline.h" #include "src/compiler/simplified-lowering.h" #include "src/compiler/simplified-node-factory.h" #include "src/compiler/typer.h" #include "src/compiler/verifier.h" #include "src/execution.h" #include "src/parser.h" #include "src/rewriter.h" #include "src/scopes.h" #include "test/cctest/cctest.h" #include "test/cctest/compiler/codegen-tester.h" #include "test/cctest/compiler/graph-builder-tester.h" #include "test/cctest/compiler/value-helper.h" using namespace v8::internal; using namespace v8::internal::compiler; template class SimplifiedGraphBuilderTester : public GraphBuilderTester { public: SimplifiedGraphBuilderTester(MachineRepresentation p0 = kMachineLast, MachineRepresentation p1 = kMachineLast, MachineRepresentation p2 = kMachineLast, MachineRepresentation p3 = kMachineLast, MachineRepresentation p4 = kMachineLast) : GraphBuilderTester(p0, p1, p2, p3, p4) {} // Close graph and lower one node. void Lower(Node* node) { this->End(); Typer typer(this->zone()); CommonOperatorBuilder common(this->zone()); SourcePositionTable source_positions(this->graph()); JSGraph jsgraph(this->graph(), &common, &typer); SimplifiedLowering lowering(&jsgraph, &source_positions); if (node == NULL) { lowering.LowerAllNodes(); } else { lowering.Lower(node); } } // Close graph and lower all nodes. void LowerAllNodes() { Lower(NULL); } void StoreFloat64(Node* node, double* ptr) { Node* ptr_node = this->PointerConstant(ptr); this->Store(kMachineFloat64, ptr_node, node); } Node* LoadInt32(int32_t* ptr) { Node* ptr_node = this->PointerConstant(ptr); return this->Load(kMachineWord32, ptr_node); } Node* LoadUint32(uint32_t* ptr) { Node* ptr_node = this->PointerConstant(ptr); return this->Load(kMachineWord32, ptr_node); } Node* LoadFloat64(double* ptr) { Node* ptr_node = this->PointerConstant(ptr); return this->Load(kMachineFloat64, ptr_node); } Factory* factory() { return this->isolate()->factory(); } Heap* heap() { return this->isolate()->heap(); } }; class SimplifiedGraphBuilderJSTester : public SimplifiedGraphBuilderTester { public: SimplifiedGraphBuilderJSTester() : SimplifiedGraphBuilderTester(), f_(v8::Utils::OpenHandle(*v8::Handle::Cast(CompileRun( "(function() { 'use strict'; return 2.7123; })")))), swapped_(false) { set_current_context(HeapConstant(handle(f_->context()))); } template T* CallJS() { if (!swapped_) { Compile(); } Handle* args = NULL; MaybeHandle result = Execution::Call( isolate(), f_, factory()->undefined_value(), 0, args, false); return T::cast(*result.ToHandleChecked()); } private: void Compile() { CompilationInfoWithZone info(f_); CHECK(Parser::Parse(&info)); StrictMode strict_mode = info.function()->strict_mode(); info.SetStrictMode(strict_mode); info.SetOptimizing(BailoutId::None(), Handle(f_->code())); CHECK(Rewriter::Rewrite(&info)); CHECK(Scope::Analyze(&info)); CHECK_NE(NULL, info.scope()); Pipeline pipeline(&info); Linkage linkage(&info); Handle code = pipeline.GenerateCodeForMachineGraph(&linkage, graph()); CHECK(!code.is_null()); f_->ReplaceCode(*code); swapped_ = true; } Handle f_; bool swapped_; }; TEST(RunChangeTaggedToInt32) { SimplifiedGraphBuilderTester t(kMachineTagged); Node* x = t.ChangeTaggedToInt32(t.Parameter(0)); t.Return(x); t.Lower(x); // TODO(titzer): remove me. return; FOR_INT32_INPUTS(i) { int32_t input = *i; if (Smi::IsValid(input)) { int32_t result = t.Call(Smi::FromInt(input)); CHECK_EQ(input, result); } { Handle number = t.factory()->NewNumber(input); int32_t result = t.Call(*number); CHECK_EQ(input, result); } { Handle number = t.factory()->NewHeapNumber(input); int32_t result = t.Call(*number); CHECK_EQ(input, result); } } } TEST(RunChangeTaggedToUint32) { SimplifiedGraphBuilderTester t(kMachineTagged); Node* x = t.ChangeTaggedToUint32(t.Parameter(0)); t.Return(x); t.Lower(x); // TODO(titzer): remove me. return; FOR_UINT32_INPUTS(i) { uint32_t input = *i; if (Smi::IsValid(input)) { int32_t result = t.Call(Smi::FromInt(input)); CHECK_EQ(static_cast(input), result); } { Handle number = t.factory()->NewNumber(input); int32_t result = t.Call(*number); CHECK_EQ(static_cast(input), result); } { Handle number = t.factory()->NewHeapNumber(input); int32_t result = t.Call(*number); CHECK_EQ(static_cast(input), result); } } } TEST(RunChangeTaggedToFloat64) { SimplifiedGraphBuilderTester t(kMachineTagged); double result; Node* x = t.ChangeTaggedToFloat64(t.Parameter(0)); t.StoreFloat64(x, &result); t.Return(t.Int32Constant(0)); t.Lower(x); // TODO(titzer): remove me. return; { FOR_INT32_INPUTS(i) { int32_t input = *i; if (Smi::IsValid(input)) { t.Call(Smi::FromInt(input)); CHECK_EQ(input, static_cast(result)); } { Handle number = t.factory()->NewNumber(input); t.Call(*number); CHECK_EQ(input, static_cast(result)); } { Handle number = t.factory()->NewHeapNumber(input); t.Call(*number); CHECK_EQ(input, static_cast(result)); } } } { FOR_FLOAT64_INPUTS(i) { double input = *i; { Handle number = t.factory()->NewNumber(input); t.Call(*number); CHECK_EQ(input, result); } { Handle number = t.factory()->NewHeapNumber(input); t.Call(*number); CHECK_EQ(input, result); } } } } TEST(RunChangeBoolToBit) { SimplifiedGraphBuilderTester t(kMachineTagged); Node* x = t.ChangeBoolToBit(t.Parameter(0)); t.Return(x); t.Lower(x); if (!Pipeline::SupportedTarget()) return; { Object* true_obj = t.heap()->true_value(); int32_t result = t.Call(true_obj); CHECK_EQ(1, result); } { Object* false_obj = t.heap()->false_value(); int32_t result = t.Call(false_obj); CHECK_EQ(0, result); } } TEST(RunChangeBitToBool) { SimplifiedGraphBuilderTester t(kMachineTagged); Node* x = t.ChangeBitToBool(t.Parameter(0)); t.Return(x); t.Lower(x); // TODO(titzer): remove me. return; { Object* result = t.Call(1); Object* true_obj = t.heap()->true_value(); CHECK_EQ(true_obj, result); } { Object* result = t.Call(0); Object* false_obj = t.heap()->false_value(); CHECK_EQ(false_obj, result); } } TEST(RunChangeInt32ToTagged) { SimplifiedGraphBuilderJSTester t; int32_t input; Node* load = t.LoadInt32(&input); Node* x = t.ChangeInt32ToTagged(load); t.Return(x); t.Lower(x); // TODO(titzer): remove me. return; { FOR_INT32_INPUTS(i) { input = *i; HeapNumber* result = t.CallJS(); CHECK_EQ(static_cast(input), result->value()); } } { FOR_INT32_INPUTS(i) { input = *i; SimulateFullSpace(CcTest::heap()->new_space()); HeapNumber* result = t.CallJS(); CHECK_EQ(static_cast(input), result->value()); } } } TEST(RunChangeUint32ToTagged) { SimplifiedGraphBuilderJSTester t; uint32_t input; Node* load = t.LoadUint32(&input); Node* x = t.ChangeUint32ToTagged(load); t.Return(x); t.Lower(x); // TODO(titzer): remove me. return; { FOR_UINT32_INPUTS(i) { input = *i; HeapNumber* result = t.CallJS(); double expected = static_cast(input); CHECK_EQ(expected, result->value()); } } { FOR_UINT32_INPUTS(i) { input = *i; SimulateFullSpace(CcTest::heap()->new_space()); HeapNumber* result = t.CallJS(); double expected = static_cast(static_cast(input)); CHECK_EQ(expected, result->value()); } } } TEST(RunChangeFloat64ToTagged) { SimplifiedGraphBuilderJSTester t; double input; Node* load = t.LoadFloat64(&input); Node* x = t.ChangeFloat64ToTagged(load); t.Return(x); t.Lower(x); // TODO(titzer): remove me. return; { FOR_FLOAT64_INPUTS(i) { input = *i; HeapNumber* result = t.CallJS(); CHECK_EQ(input, result->value()); } } { FOR_FLOAT64_INPUTS(i) { input = *i; SimulateFullSpace(CcTest::heap()->new_space()); HeapNumber* result = t.CallJS(); CHECK_EQ(input, result->value()); } } } // TODO(dcarney): find a home for these functions. namespace { FieldAccess ForJSObjectMap() { FieldAccess access = {JSObject::kMapOffset, Handle(), Type::Any(), kMachineTagged}; return access; } FieldAccess ForJSObjectProperties() { FieldAccess access = {JSObject::kPropertiesOffset, Handle(), Type::Any(), kMachineTagged}; return access; } FieldAccess ForArrayBufferBackingStore() { FieldAccess access = { JSArrayBuffer::kBackingStoreOffset, Handle(), Type::UntaggedPtr(), MachineOperatorBuilder::pointer_rep(), }; return access; } ElementAccess ForFixedArrayElement() { ElementAccess access = {FixedArray::kHeaderSize, Type::Any(), kMachineTagged}; return access; } ElementAccess ForBackingStoreElement(MachineRepresentation rep) { ElementAccess access = {kNonHeapObjectHeaderSize, Type::Any(), rep}; return access; } } // Create a simple JSObject with a unique map. static Handle TestObject() { static int index = 0; char buffer[50]; v8::base::OS::SNPrintF(buffer, 50, "({'a_%d':1})", index++); return Handle::cast(v8::Utils::OpenHandle(*CompileRun(buffer))); } TEST(RunLoadMap) { SimplifiedGraphBuilderTester t(kMachineTagged); FieldAccess access = ForJSObjectMap(); Node* load = t.LoadField(access, t.Parameter(0)); t.Return(load); t.LowerAllNodes(); if (!Pipeline::SupportedTarget()) return; Handle src = TestObject(); Handle src_map(src->map()); Object* result = t.Call(*src); CHECK_EQ(*src_map, result); } TEST(RunStoreMap) { SimplifiedGraphBuilderTester t(kMachineTagged, kMachineTagged); FieldAccess access = ForJSObjectMap(); t.StoreField(access, t.Parameter(1), t.Parameter(0)); t.Return(t.Int32Constant(0)); t.LowerAllNodes(); if (!Pipeline::SupportedTarget()) return; Handle src = TestObject(); Handle src_map(src->map()); Handle dst = TestObject(); CHECK(src->map() != dst->map()); t.Call(*src_map, *dst); CHECK(*src_map == dst->map()); } TEST(RunLoadProperties) { SimplifiedGraphBuilderTester t(kMachineTagged); FieldAccess access = ForJSObjectProperties(); Node* load = t.LoadField(access, t.Parameter(0)); t.Return(load); t.LowerAllNodes(); if (!Pipeline::SupportedTarget()) return; Handle src = TestObject(); Handle src_props(src->properties()); Object* result = t.Call(*src); CHECK_EQ(*src_props, result); } TEST(RunLoadStoreMap) { SimplifiedGraphBuilderTester t(kMachineTagged, kMachineTagged); FieldAccess access = ForJSObjectMap(); Node* load = t.LoadField(access, t.Parameter(0)); t.StoreField(access, t.Parameter(1), load); t.Return(load); t.LowerAllNodes(); if (!Pipeline::SupportedTarget()) return; Handle src = TestObject(); Handle src_map(src->map()); Handle dst = TestObject(); CHECK(src->map() != dst->map()); Object* result = t.Call(*src, *dst); CHECK(result->IsMap()); CHECK_EQ(*src_map, result); CHECK(*src_map == dst->map()); } TEST(RunLoadStoreFixedArrayIndex) { SimplifiedGraphBuilderTester t(kMachineTagged); ElementAccess access = ForFixedArrayElement(); Node* load = t.LoadElement(access, t.Parameter(0), t.Int32Constant(0)); t.StoreElement(access, t.Parameter(0), t.Int32Constant(1), load); t.Return(load); t.LowerAllNodes(); if (!Pipeline::SupportedTarget()) return; Handle array = t.factory()->NewFixedArray(2); Handle src = TestObject(); Handle dst = TestObject(); array->set(0, *src); array->set(1, *dst); Object* result = t.Call(*array); CHECK_EQ(*src, result); CHECK_EQ(*src, array->get(0)); CHECK_EQ(*src, array->get(1)); } TEST(RunLoadStoreArrayBuffer) { SimplifiedGraphBuilderTester t(kMachineTagged); const int index = 12; FieldAccess access = ForArrayBufferBackingStore(); Node* backing_store = t.LoadField(access, t.Parameter(0)); ElementAccess buffer_access = ForBackingStoreElement(kMachineWord8); Node* load = t.LoadElement(buffer_access, backing_store, t.Int32Constant(index)); t.StoreElement(buffer_access, backing_store, t.Int32Constant(index + 1), load); t.Return(load); t.LowerAllNodes(); if (!Pipeline::SupportedTarget()) return; Handle array = t.factory()->NewJSArrayBuffer(); const int array_length = 2 * index; Runtime::SetupArrayBufferAllocatingData(t.isolate(), array, array_length); uint8_t* data = reinterpret_cast(array->backing_store()); for (int i = 0; i < array_length; i++) { data[i] = i; } int32_t result = t.Call(*array); CHECK_EQ(index, result); for (int i = 0; i < array_length; i++) { uint8_t expected = i; if (i == (index + 1)) expected = result; CHECK_EQ(data[i], expected); } } TEST(RunCopyFixedArray) { SimplifiedGraphBuilderTester t(kMachineTagged, kMachineTagged); const int kArraySize = 15; Node* one = t.Int32Constant(1); Node* index = t.Int32Constant(0); Node* limit = t.Int32Constant(kArraySize); t.environment()->Push(index); { LoopBuilder loop(&t); loop.BeginLoop(); // Loop exit condition. index = t.environment()->Top(); Node* condition = t.Int32LessThan(index, limit); loop.BreakUnless(condition); // src[index] = dst[index]. index = t.environment()->Pop(); ElementAccess access = ForFixedArrayElement(); Node* src = t.Parameter(0); Node* load = t.LoadElement(access, src, index); Node* dst = t.Parameter(1); t.StoreElement(access, dst, index, load); // index++ index = t.Int32Add(index, one); t.environment()->Push(index); // continue. loop.EndBody(); loop.EndLoop(); } index = t.environment()->Pop(); t.Return(index); t.LowerAllNodes(); if (!Pipeline::SupportedTarget()) return; Handle src = t.factory()->NewFixedArray(kArraySize); Handle src_copy = t.factory()->NewFixedArray(kArraySize); Handle dst = t.factory()->NewFixedArray(kArraySize); for (int i = 0; i < kArraySize; i++) { src->set(i, *TestObject()); src_copy->set(i, src->get(i)); dst->set(i, *TestObject()); CHECK_NE(src_copy->get(i), dst->get(i)); } CHECK_EQ(kArraySize, t.Call(*src, *dst)); for (int i = 0; i < kArraySize; i++) { CHECK_EQ(src_copy->get(i), dst->get(i)); } }