| // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include <vector> |
| |
| #include "vm/closure_functions_cache.h" |
| #include "vm/compiler/backend/il_printer.h" |
| #include "vm/compiler/backend/il_test_helper.h" |
| #include "vm/compiler/call_specializer.h" |
| #include "vm/compiler/compiler_pass.h" |
| #include "vm/object.h" |
| #include "vm/unit_test.h" |
| |
| namespace dart { |
| |
| #if defined(DART_PRECOMPILER) |
| |
| // This test asserts that we are inlining accesses to typed data interfaces |
| // (e.g. Uint8List) if there are no instantiated 3rd party classes. |
| ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_Inlining) { |
| const char* kScript = |
| R"( |
| import 'dart:typed_data'; |
| |
| foo(Uint8List list, int from) { |
| if (from >= list.length) { |
| return list[from]; |
| } |
| } |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| const auto& function = Function::Handle(GetFunction(root_library, "foo")); |
| |
| TestPipeline pipeline(function, CompilerPass::kAOT); |
| FlowGraph* flow_graph = pipeline.RunPasses({}); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| CheckNullInstr* check_null = nullptr; |
| LoadFieldInstr* load_field = nullptr; |
| GenericCheckBoundInstr* bounds_check = nullptr; |
| Instruction* load_untagged = nullptr; |
| LoadIndexedInstr* load_indexed = nullptr; |
| |
| ILMatcher cursor(flow_graph, entry); |
| if (IsolateGroup::Current()->null_safety()) { |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMoveGlob, |
| {kMatchAndMoveLoadField, &load_field}, |
| kMoveGlob, |
| kMatchAndMoveBranchTrue, |
| kMoveGlob, |
| {kMatchAndMoveGenericCheckBound, &bounds_check}, |
| {kMatchAndMoveLoadUntagged, &load_untagged}, |
| kMoveParallelMoves, |
| {kMatchAndMoveLoadIndexed, &load_indexed}, |
| kMoveGlob, |
| kMatchReturn, |
| })); |
| } else { |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMoveGlob, |
| {kMatchAndMoveCheckNull, &check_null}, |
| {kMatchAndMoveLoadField, &load_field}, |
| kMoveGlob, |
| kMatchAndMoveBranchTrue, |
| kMoveGlob, |
| {kMatchAndMoveGenericCheckBound, &bounds_check}, |
| {kMatchAndMoveLoadUntagged, &load_untagged}, |
| kMoveParallelMoves, |
| {kMatchAndMoveLoadIndexed, &load_indexed}, |
| kMoveGlob, |
| kMatchReturn, |
| })); |
| } |
| |
| EXPECT(load_field->InputAt(0)->definition()->IsParameter()); |
| EXPECT(bounds_check->length() |
| ->definition() |
| ->OriginalDefinitionIgnoreBoxingAndConstraints() == load_field); |
| EXPECT(load_untagged->InputAt(0)->definition()->IsParameter()); |
| EXPECT(load_indexed->InputAt(0)->definition() == load_untagged); |
| } |
| |
| // This test asserts that we are inlining get:length, [] and []= for all typed |
| // data interfaces. It also ensures that the asserted IR actually works by |
| // exercising it. |
| ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_FunctionalGetSet) { |
| const char* kTemplate = |
| R"( |
| import 'dart:typed_data'; |
| |
| void reverse%s(%s list) { |
| final length = list.length; |
| final halfLength = length >> 1; |
| for (int i = 0; i < halfLength; ++i) { |
| final tmp = list[length-i-1]; |
| list[length-i-1] = list[i]; |
| list[i] = tmp; |
| } |
| } |
| )"; |
| |
| char script_buffer[1024]; |
| char uri_buffer[1024]; |
| char function_name[1024]; |
| auto& lib = Library::Handle(); |
| auto& function = Function::Handle(); |
| |
| auto check_il = [&](const char* name) { |
| // Fill in the template with the [name]. |
| Utils::SNPrint(script_buffer, sizeof(script_buffer), kTemplate, name, name); |
| Utils::SNPrint(uri_buffer, sizeof(uri_buffer), "file:///reverse-%s.dart", |
| name); |
| Utils::SNPrint(function_name, sizeof(function_name), "reverse%s", name); |
| |
| // Create a new library, load the function and compile it using our AOT |
| // pipeline. |
| lib = LoadTestScript(script_buffer, nullptr, uri_buffer); |
| function = GetFunction(lib, function_name); |
| TestPipeline pipeline(function, CompilerPass::kAOT); |
| FlowGraph* flow_graph = pipeline.RunPasses({}); |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| |
| // Ensure the IL matches what we expect. |
| ILMatcher cursor(flow_graph, entry); |
| if (IsolateGroup::Current()->null_safety()) { |
| EXPECT(cursor.TryMatch({ |
| // Before loop |
| kMoveGlob, |
| kMatchAndMoveLoadField, |
| kMoveGlob, |
| kMatchAndMoveBranchTrue, |
| |
| // Loop |
| kMoveGlob, |
| // Load 1 |
| kMatchAndMoveGenericCheckBound, |
| kMoveGlob, |
| kMatchAndMoveLoadUntagged, |
| kMoveParallelMoves, |
| kMatchAndMoveLoadIndexed, |
| kMoveGlob, |
| // Load 2 |
| kMatchAndMoveGenericCheckBound, |
| kMoveGlob, |
| kMatchAndMoveLoadUntagged, |
| kMoveParallelMoves, |
| kMatchAndMoveLoadIndexed, |
| kMoveGlob, |
| // Store 1 |
| kMatchAndMoveCheckWritable, |
| kMoveParallelMoves, |
| kMatchAndMoveLoadUntagged, |
| kMoveParallelMoves, |
| kMatchAndMoveStoreIndexed, |
| kMoveGlob, |
| // Store 2 |
| kMoveParallelMoves, |
| kMatchAndMoveLoadUntagged, |
| kMoveParallelMoves, |
| kMatchAndMoveStoreIndexed, |
| kMoveGlob, |
| |
| // Exit the loop. |
| kMatchAndMoveBranchFalse, |
| kMoveGlob, |
| kMatchReturn, |
| })); |
| } else { |
| EXPECT(cursor.TryMatch({ |
| // Before loop |
| kMoveGlob, |
| kMatchAndMoveCheckNull, |
| kMatchAndMoveLoadField, |
| kMoveGlob, |
| kMatchAndMoveBranchTrue, |
| |
| // Loop |
| kMoveGlob, |
| // Load 1 |
| kMatchAndMoveGenericCheckBound, |
| kMoveGlob, |
| kMatchAndMoveLoadUntagged, |
| kMoveParallelMoves, |
| kMatchAndMoveLoadIndexed, |
| kMoveGlob, |
| // Load 2 |
| kMatchAndMoveGenericCheckBound, |
| kMoveGlob, |
| kMatchAndMoveLoadUntagged, |
| kMoveParallelMoves, |
| kMatchAndMoveLoadIndexed, |
| kMoveGlob, |
| // Store 1 |
| kMatchAndMoveCheckWritable, |
| kMoveParallelMoves, |
| kMatchAndMoveLoadUntagged, |
| kMoveParallelMoves, |
| kMatchAndMoveStoreIndexed, |
| kMoveGlob, |
| // Store 2 |
| kMoveParallelMoves, |
| kMatchAndMoveLoadUntagged, |
| kMoveParallelMoves, |
| kMatchAndMoveStoreIndexed, |
| kMoveGlob, |
| |
| // Exit the loop. |
| kMatchAndMoveBranchFalse, |
| kMoveGlob, |
| kMatchReturn, |
| })); |
| } |
| }; |
| |
| check_il("Uint8List"); |
| check_il("Int8List"); |
| check_il("Uint8ClampedList"); |
| check_il("Int16List"); |
| check_il("Uint16List"); |
| check_il("Int32List"); |
| check_il("Uint32List"); |
| check_il("Int64List"); |
| check_il("Uint64List"); |
| check_il("Float32List"); |
| check_il("Float64List"); |
| } |
| |
| // This test asserts that we get errors if receiver, index or value are null. |
| ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_FunctionalIndexError) { |
| const char* kTemplate = |
| R"( |
| import 'dart:typed_data'; |
| void set%s(%s list, int index, %s value) { |
| list[index] = value; |
| } |
| )"; |
| |
| char script_buffer[1024]; |
| char uri_buffer[1024]; |
| char function_name[1024]; |
| auto& lib = Library::Handle(); |
| auto& function = Function::Handle(); |
| auto& arguments = Array::Handle(); |
| auto& result = Object::Handle(); |
| |
| const intptr_t kIndex = 1; |
| const intptr_t kLastStage = 3; |
| |
| auto run_test = [&](const char* name, const char* type, |
| const TypedDataBase& data, const Object& value, |
| int stage) { |
| // Fill in the template with the [name]. |
| Utils::SNPrint(script_buffer, sizeof(script_buffer), kTemplate, name, name, |
| type); |
| Utils::SNPrint(uri_buffer, sizeof(uri_buffer), "file:///set-%s.dart", name); |
| Utils::SNPrint(function_name, sizeof(function_name), "set%s", name); |
| |
| // Create a new library, load the function and compile it using our AOT |
| // pipeline. |
| lib = LoadTestScript(script_buffer, nullptr, uri_buffer); |
| function = GetFunction(lib, function_name); |
| TestPipeline pipeline(function, CompilerPass::kAOT); |
| FlowGraph* flow_graph = pipeline.RunPasses({}); |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| |
| // Ensure the IL matches what we expect. |
| ILMatcher cursor(flow_graph, entry, /*trace=*/true); |
| if (IsolateGroup::Current()->null_safety()) { |
| EXPECT(cursor.TryMatch({ |
| // LoadField length |
| kMoveGlob, |
| kMatchAndMoveLoadField, |
| |
| // Bounds check |
| kMoveGlob, |
| kMatchAndMoveGenericCheckBound, |
| |
| // Store value. |
| kMoveGlob, |
| kMatchAndMoveLoadUntagged, |
| kMoveParallelMoves, |
| kMatchAndMoveOptionalUnbox, |
| kMoveParallelMoves, |
| kMatchAndMoveStoreIndexed, |
| |
| // Return |
| kMoveGlob, |
| kMatchReturn, |
| })); |
| } else { |
| EXPECT(cursor.TryMatch({ |
| // Receiver null check |
| kMoveGlob, |
| kMatchAndMoveCheckNull, |
| |
| // Index null check |
| kMoveGlob, |
| kMatchAndMoveCheckNull, |
| |
| // Value null check |
| kMoveGlob, |
| kMatchAndMoveCheckNull, |
| |
| // LoadField length |
| kMoveGlob, |
| kMatchAndMoveLoadField, |
| |
| // Bounds check |
| kMoveGlob, |
| kMatchAndMoveGenericCheckBound, |
| |
| // Store value. |
| kMoveGlob, |
| kMatchAndMoveLoadUntagged, |
| kMoveParallelMoves, |
| kMatchAndMoveOptionalUnbox, |
| kMoveParallelMoves, |
| kMatchAndMoveStoreIndexed, |
| |
| // Return |
| kMoveGlob, |
| kMatchReturn, |
| })); |
| } |
| |
| // Compile the graph and attach the code. |
| pipeline.CompileGraphAndAttachFunction(); |
| |
| arguments = Array::New(3); |
| arguments.SetAt(0, stage == 0 ? Object::null_object() : data); |
| arguments.SetAt( |
| 1, stage == 1 ? Object::null_object() : Smi::Handle(Smi::New(kIndex))); |
| arguments.SetAt(2, stage == 2 ? Object::null_object() : value); |
| result = DartEntry::InvokeFunction(function, arguments); |
| |
| // Ensure we didn't deoptimize to unoptimized code. |
| EXPECT(function.unoptimized_code() == Code::null()); |
| |
| if (stage == kLastStage) { |
| // The last stage must be successful |
| EXPECT(result.IsNull()); |
| } else { |
| // Ensure we get an error. |
| EXPECT(result.IsUnhandledException()); |
| result = UnhandledException::Cast(result).exception(); |
| } |
| }; |
| |
| const auto& uint8_list = |
| TypedData::Handle(TypedData::New(kTypedDataUint8ArrayCid, 16)); |
| const auto& uint8c_list = |
| TypedData::Handle(TypedData::New(kTypedDataUint8ClampedArrayCid, 16)); |
| const auto& int16_list = |
| TypedData::Handle(TypedData::New(kTypedDataInt16ArrayCid, 16)); |
| const auto& uint16_list = |
| TypedData::Handle(TypedData::New(kTypedDataUint16ArrayCid, 16)); |
| const auto& int32_list = |
| TypedData::Handle(TypedData::New(kTypedDataInt32ArrayCid, 16)); |
| const auto& uint32_list = |
| TypedData::Handle(TypedData::New(kTypedDataUint32ArrayCid, 16)); |
| const auto& int64_list = |
| TypedData::Handle(TypedData::New(kTypedDataInt64ArrayCid, 16)); |
| const auto& uint64_list = |
| TypedData::Handle(TypedData::New(kTypedDataUint64ArrayCid, 16)); |
| const auto& float32_list = |
| TypedData::Handle(TypedData::New(kTypedDataFloat32ArrayCid, 16)); |
| const auto& float64_list = |
| TypedData::Handle(TypedData::New(kTypedDataFloat64ArrayCid, 16)); |
| const auto& int8_list = |
| TypedData::Handle(TypedData::New(kTypedDataInt8ArrayCid, 16)); |
| const auto& int_value = Integer::Handle(Integer::New(42)); |
| const auto& float_value = Double::Handle(Double::New(4.2)); |
| // With null safety nulls cannot be passed as non-nullable arguments, so |
| // skip all error stages and only run the last stage. |
| const intptr_t first_stage = |
| IsolateGroup::Current()->null_safety() ? kLastStage : 0; |
| for (intptr_t stage = first_stage; stage <= kLastStage; ++stage) { |
| run_test("Uint8List", "int", int8_list, int_value, stage); |
| run_test("Int8List", "int", uint8_list, int_value, stage); |
| run_test("Uint8ClampedList", "int", uint8c_list, int_value, stage); |
| run_test("Int16List", "int", int16_list, int_value, stage); |
| run_test("Uint16List", "int", uint16_list, int_value, stage); |
| run_test("Int32List", "int", int32_list, int_value, stage); |
| run_test("Uint32List", "int", uint32_list, int_value, stage); |
| run_test("Int64List", "int", int64_list, int_value, stage); |
| run_test("Uint64List", "int", uint64_list, int_value, stage); |
| run_test("Float32List", "double", float32_list, float_value, stage); |
| run_test("Float64List", "double", float64_list, float_value, stage); |
| } |
| } |
| |
| #endif // defined(DART_PRECOMPILER) |
| |
| } // namespace dart |