| // Copyright (c) 2020, 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 "vm/compiler/frontend/kernel_binary_flowgraph.h" |
| |
| #include "vm/compiler/backend/il_test_helper.h" |
| #include "vm/object.h" |
| #include "vm/unit_test.h" |
| |
| namespace dart { |
| |
| ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_ConstFoldStringConcats) { |
| // According to the Dart spec: |
| // "Adjacent strings are implicitly concatenated to form a single string |
| // literal." |
| const char* kScript = R"( |
| @pragma("vm:entry-point", "call") |
| test() { |
| var s = 'aaaa' |
| 'bbbb' |
| 'cccc'; |
| return s; |
| } |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| const auto& function = Function::Handle(GetFunction(root_library, "test")); |
| |
| Invoke(root_library, "test"); |
| |
| TestPipeline pipeline(function, CompilerPass::kJIT); |
| FlowGraph* flow_graph = pipeline.RunPasses({ |
| CompilerPass::kComputeSSA, |
| }); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| DartReturnInstr* ret = nullptr; |
| |
| ILMatcher cursor(flow_graph, entry); |
| // clang-format off |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMatchAndMoveFunctionEntry, |
| kMatchAndMoveCheckStackOverflow, |
| kMoveDebugStepChecks, |
| {kMatchDartReturn, &ret}, |
| })); |
| // clang-format on |
| |
| EXPECT(ret->value()->BindsToConstant()); |
| EXPECT(ret->value()->BoundConstant().IsString()); |
| const String& ret_str = String::Cast(ret->value()->BoundConstant()); |
| EXPECT(ret_str.Equals("aaaabbbbcccc")); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_FlattenNestedStringInterp) { |
| // We should collapse nested StringInterpolates: |
| const char* kScript = R"( |
| test(String s) { |
| return '$s' '${'d' 'e'}'; |
| } |
| main() => test('u'); |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| const auto& function = Function::Handle(GetFunction(root_library, "test")); |
| |
| Invoke(root_library, "main"); |
| |
| TestPipeline pipeline(function, CompilerPass::kJIT); |
| FlowGraph* flow_graph = pipeline.RunPasses({ |
| CompilerPass::kComputeSSA, |
| }); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| StoreIndexedInstr* store1 = nullptr; |
| StoreIndexedInstr* store2 = nullptr; |
| |
| ILMatcher cursor(flow_graph, entry); |
| // clang-format off |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMatchAndMoveFunctionEntry, |
| kMatchAndMoveCheckStackOverflow, |
| kMoveDebugStepChecks, |
| kMatchAndMoveCreateArray, |
| {kMatchAndMoveStoreIndexed, &store1}, |
| {kMatchAndMoveStoreIndexed, &store2}, |
| kMatchAndMoveRecordCoverage, |
| kMatchAndMoveStaticCall, |
| kMoveDebugStepChecks, |
| kMatchDartReturn, |
| })); |
| // clang-format on |
| |
| // StoreIndexed(tmp_array, 0, s) |
| EXPECT(store1->index()->BindsToConstant()); |
| EXPECT(store1->index()->BoundConstant().IsInteger()); |
| EXPECT(Integer::Cast(store1->index()->BoundConstant()).Value() == 0); |
| |
| EXPECT(!store1->value()->BindsToConstant()); |
| |
| // StoreIndexed(tmp_array, 1, "de") |
| EXPECT(store2->index()->BindsToConstant()); |
| EXPECT(store2->index()->BoundConstant().IsInteger()); |
| EXPECT(Integer::Cast(store2->index()->BoundConstant()).Value() == 1); |
| |
| EXPECT(store2->value()->BindsToConstant()); |
| EXPECT(store2->value()->BoundConstant().IsString()); |
| EXPECT(String::Cast(store2->value()->BoundConstant()).Equals("de")); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_DropEmptyStringInterp) { |
| // We should drop empty strings from StringInterpolates: |
| const char* kScript = R"( |
| test(s) { |
| return '' 'a' '$s' '' 'b' ''; |
| } |
| main() => test('u'); |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| const auto& function = Function::Handle(GetFunction(root_library, "test")); |
| |
| Invoke(root_library, "main"); |
| |
| TestPipeline pipeline(function, CompilerPass::kJIT); |
| FlowGraph* flow_graph = pipeline.RunPasses({ |
| CompilerPass::kComputeSSA, |
| }); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| StoreIndexedInstr* store1 = nullptr; |
| StoreIndexedInstr* store2 = nullptr; |
| StoreIndexedInstr* store3 = nullptr; |
| |
| ILMatcher cursor(flow_graph, entry); |
| // clang-format off |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMatchAndMoveFunctionEntry, |
| kMatchAndMoveCheckStackOverflow, |
| kMoveDebugStepChecks, |
| kMatchAndMoveCreateArray, |
| {kMatchAndMoveStoreIndexed, &store1}, |
| {kMatchAndMoveStoreIndexed, &store2}, |
| {kMatchAndMoveStoreIndexed, &store3}, |
| kMatchAndMoveRecordCoverage, |
| kMatchAndMoveStaticCall, |
| kMoveDebugStepChecks, |
| kMatchDartReturn, |
| })); |
| // clang-format on |
| |
| // StoreIndexed(tmp_array, 0, "ab") |
| EXPECT(store1->index()->BindsToConstant()); |
| EXPECT(store1->index()->BoundConstant().IsInteger()); |
| EXPECT(Integer::Cast(store1->index()->BoundConstant()).Value() == 0); |
| |
| EXPECT(store1->value()->BindsToConstant()); |
| EXPECT(store1->value()->BoundConstant().IsString()); |
| EXPECT(String::Cast(store1->value()->BoundConstant()).Equals("a")); |
| |
| // StoreIndexed(tmp_array, 1, s) |
| EXPECT(store2->index()->BindsToConstant()); |
| EXPECT(store2->index()->BoundConstant().IsInteger()); |
| EXPECT(Integer::Cast(store2->index()->BoundConstant()).Value() == 1); |
| |
| EXPECT(!store2->value()->BindsToConstant()); |
| |
| // StoreIndexed(tmp_array, 2, "b") |
| EXPECT(store3->index()->BindsToConstant()); |
| EXPECT(store3->index()->BoundConstant().IsInteger()); |
| EXPECT(Integer::Cast(store3->index()->BoundConstant()).Value() == 2); |
| |
| EXPECT(store3->value()->BindsToConstant()); |
| EXPECT(store3->value()->BoundConstant().IsString()); |
| EXPECT(String::Cast(store3->value()->BoundConstant()).Equals("b")); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_ConcatStringLits) { |
| // We should drop empty strings from StringInterpolates: |
| const char* kScript = R"( |
| test(s) { |
| return '' 'a' '' 'b' '$s' '' 'c' '' 'd' ''; |
| } |
| main() => test('u'); |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| const auto& function = Function::Handle(GetFunction(root_library, "test")); |
| |
| Invoke(root_library, "main"); |
| |
| TestPipeline pipeline(function, CompilerPass::kJIT); |
| FlowGraph* flow_graph = pipeline.RunPasses({ |
| CompilerPass::kComputeSSA, |
| }); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| StoreIndexedInstr* store1 = nullptr; |
| StoreIndexedInstr* store2 = nullptr; |
| StoreIndexedInstr* store3 = nullptr; |
| |
| ILMatcher cursor(flow_graph, entry); |
| // clang-format off |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMatchAndMoveFunctionEntry, |
| kMatchAndMoveCheckStackOverflow, |
| kMoveDebugStepChecks, |
| kMatchAndMoveCreateArray, |
| {kMatchAndMoveStoreIndexed, &store1}, |
| {kMatchAndMoveStoreIndexed, &store2}, |
| {kMatchAndMoveStoreIndexed, &store3}, |
| kMatchAndMoveRecordCoverage, |
| kMatchAndMoveStaticCall, |
| kMoveDebugStepChecks, |
| kMatchDartReturn, |
| })); |
| // clang-format on |
| |
| // StoreIndexed(tmp_array, 0, "ab") |
| EXPECT(store1->index()->BindsToConstant()); |
| EXPECT(store1->index()->BoundConstant().IsInteger()); |
| EXPECT(Integer::Cast(store1->index()->BoundConstant()).Value() == 0); |
| |
| EXPECT(store1->value()->BindsToConstant()); |
| EXPECT(store1->value()->BoundConstant().IsString()); |
| EXPECT(String::Cast(store1->value()->BoundConstant()).Equals("ab")); |
| |
| // StoreIndexed(tmp_array, 1, s) |
| EXPECT(store2->index()->BindsToConstant()); |
| EXPECT(store2->index()->BoundConstant().IsInteger()); |
| EXPECT(Integer::Cast(store2->index()->BoundConstant()).Value() == 1); |
| |
| EXPECT(!store2->value()->BindsToConstant()); |
| |
| // StoreIndexed(tmp_array, 2, "cd") |
| EXPECT(store3->index()->BindsToConstant()); |
| EXPECT(store3->index()->BoundConstant().IsInteger()); |
| EXPECT(Integer::Cast(store3->index()->BoundConstant()).Value() == 2); |
| |
| EXPECT(store3->value()->BindsToConstant()); |
| EXPECT(store3->value()->BoundConstant().IsString()); |
| EXPECT(String::Cast(store3->value()->BoundConstant()).Equals("cd")); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_InvariantFlagInListLiterals) { |
| const char* kScript = R"( |
| @pragma("vm:entry-point", "call") |
| test() { |
| return [...[], 42]; |
| } |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| const auto& function = Function::Handle(GetFunction(root_library, "test")); |
| |
| Invoke(root_library, "test"); |
| |
| TestPipeline pipeline(function, CompilerPass::kJIT); |
| FlowGraph* flow_graph = pipeline.RunPasses({ |
| CompilerPass::kComputeSSA, |
| }); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| InstanceCallInstr* call_add = nullptr; |
| |
| ILMatcher cursor(flow_graph, entry); |
| // clang-format off |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMatchAndMoveFunctionEntry, |
| kMatchAndMoveCheckStackOverflow, |
| kMoveDebugStepChecks, |
| kMatchAndMoveRecordCoverage, |
| kMatchAndMoveStaticCall, |
| kMatchAndMoveRecordCoverage, |
| kMatchAndMoveStaticCall, |
| kMatchAndMoveRecordCoverage, |
| {kMatchAndMoveInstanceCall, &call_add}, |
| kMoveDebugStepChecks, |
| kMatchDartReturn, |
| })); |
| // clang-format on |
| |
| EXPECT(call_add != nullptr); |
| EXPECT(call_add->function_name().Equals("add")); |
| EXPECT(call_add->entry_kind() == Code::EntryKind::kUnchecked); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE(StreamingFlowGraphBuilder_TypedClosureCall) { |
| // This test ensures that a typed closure call (i.e. the closure being called |
| // has a real function type as static type) ends up compiling to |
| // |
| // CheckNull()+ClosureCall() |
| // |
| // instead of |
| // |
| // InstanceCall(dyn:call) |
| // |
| // This is a regression test for a case where JIT support uses dynamic calls |
| // instead of typed closure calls if call-site attribute metadata is missing |
| // which is the case if an incremental kernel compiler is used. The |
| // [LoadTestScript] below uses IKG compiler, so this is a regression test for: |
| // |
| // - https://github.com/dart-lang/sdk/issues/46320 |
| // - https://github.com/dart-lang/sdk/issues/45421 |
| // |
| const char* kScript = R"( |
| int callClosure(int Function(int) fun, int value) => fun(value); |
| @pragma("vm:entry-point", "call") |
| test() => callClosure((int a) => a + 1, 10); |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| Invoke(root_library, "test"); |
| |
| const auto& callClosureFunction = |
| Function::Handle(GetFunction(root_library, "callClosure")); |
| TestPipeline pipeline(callClosureFunction, CompilerPass::kJIT); |
| FlowGraph* flow_graph = pipeline.RunPasses({ |
| CompilerPass::kComputeSSA, |
| }); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| ILMatcher cursor(flow_graph, entry, true); |
| // clang-format off |
| std::initializer_list<MatchCode> expected = { |
| kMatchAndMoveFunctionEntry, |
| kMatchAndMoveCheckStackOverflow, |
| kMoveDebugStepChecks, |
| kMatchAndMoveCheckNull, |
| kMatchAndMoveLoadField, |
| kMoveDebugStepChecks, |
| #if !defined(PRODUCT) |
| kMatchAndMoveRecordCoverage, |
| #endif |
| kMatchAndMoveClosureCall, |
| kMoveDebugStepChecks, |
| kMatchDartReturn, |
| }; |
| RELEASE_ASSERT(cursor.TryMatch(expected)); |
| // clang-format on |
| } |
| |
| ISOLATE_UNIT_TEST_CASE( |
| StreamingFlowGraphBuilder_StaticGetFinalFieldWithTrivialInitializer) { |
| const char* kScript = R"( |
| final int x = 0xFEEDFEED; |
| @pragma("vm:entry-point", "call") |
| test() { |
| return x; |
| } |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| const auto& function = Function::Handle(GetFunction(root_library, "test")); |
| |
| Invoke(root_library, "test"); |
| |
| TestPipeline pipeline(function, CompilerPass::kJIT); |
| FlowGraph* flow_graph = pipeline.RunPasses({ |
| CompilerPass::kComputeSSA, |
| }); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| DartReturnInstr* return_instr = nullptr; |
| |
| ILMatcher cursor(flow_graph, entry); |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMatchAndMoveFunctionEntry, |
| kMatchAndMoveCheckStackOverflow, |
| kMoveDebugStepChecks, |
| {kMatchDartReturn, &return_instr}, |
| })); |
| |
| EXPECT(return_instr != nullptr); |
| ConstantInstr* const_value = |
| return_instr->value()->definition()->AsConstant(); |
| EXPECT(const_value != nullptr); |
| EXPECT(const_value->value().IsInteger()); |
| EXPECT_EQ(0xFEEDFEED, Integer::Cast(const_value->value()).Value()); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE( |
| StreamingFlowGraphBuilder_DeeplyImmutableTypeCheckPresent) { |
| const char* kScript = R"( |
| @pragma("vm:deeply-immutable") |
| final class Foo { |
| final void Function(int) baz; |
| Foo(this.baz); |
| } |
| |
| @pragma("vm:entry-point", "call") |
| test() { |
| Foo((int x) { x = x + 1; } ); |
| } |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| EXPECT(ClassFinalizer::ProcessPendingClasses()); |
| const Class& foo = Class::Handle(GetClass(root_library, "Foo")); |
| const auto& error = foo.EnsureIsFinalized(thread); |
| const auto& constructor = Function::Handle( |
| foo.LookupConstructor(String::Handle(String::New("Foo.")))); |
| |
| EXPECT(error == Error::null()); |
| Invoke(root_library, "test"); |
| |
| TestPipeline pipeline(constructor, CompilerPass::kJIT); |
| FlowGraph* flow_graph = pipeline.RunPasses({ |
| CompilerPass::kComputeSSA, |
| }); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| ILMatcher cursor(flow_graph, entry); |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMatchAndMoveFunctionEntry, |
| kMatchAndMoveCheckStackOverflow, |
| kMoveDebugStepChecks, |
| kMatchAndMoveGuardFieldClass, |
| kMatchAndMoveCheckFieldImmutability, |
| kMatchAndMoveStoreField, |
| })); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE( |
| StreamingFlowGraphBuilder_DeeplyImmutableTypeCheckOmitted) { |
| const char* kScript = R"( |
| @pragma("vm:deeply-immutable") |
| final class Foo { |
| final int baz; |
| Foo(this.baz); |
| } |
| |
| @pragma("vm:entry-point", "call") |
| test() { |
| Foo(42); |
| } |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| EXPECT(ClassFinalizer::ProcessPendingClasses()); |
| const Class& foo = Class::Handle(GetClass(root_library, "Foo")); |
| const auto& error = foo.EnsureIsFinalized(thread); |
| const auto& constructor = Function::Handle( |
| foo.LookupConstructor(String::Handle(String::New("Foo.")))); |
| |
| EXPECT(error == Error::null()); |
| |
| Invoke(root_library, "test"); |
| |
| TestPipeline pipeline(constructor, CompilerPass::kJIT); |
| FlowGraph* flow_graph = pipeline.RunPasses({ |
| CompilerPass::kComputeSSA, |
| }); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| ILMatcher cursor(flow_graph, entry); |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMatchAndMoveFunctionEntry, |
| kMatchAndMoveCheckStackOverflow, |
| kMoveDebugStepChecks, |
| kMatchAndMoveGuardFieldClass, |
| kMatchAndMoveStoreField, |
| })); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE( |
| StreamingFlowGraphBuilder_MapDeeplyImmutableTypeCheckPresentAndFailing) { |
| const char* kScript = R"( |
| @pragma("vm:deeply-immutable") |
| final class Foo { |
| final Map map; |
| Foo(this.map); |
| } |
| |
| @pragma("vm:entry-point", "call") |
| test() { |
| final map = <int, int>{1: 2, 3: 4}; |
| bool caught = false; |
| try { |
| Foo(map); |
| } on ArgumentError catch(_) { |
| caught = true; |
| } |
| // plain Map should not pass deep-immutability check at this point. |
| if (!caught) { |
| throw Exception(); |
| } |
| } |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| EXPECT(ClassFinalizer::ProcessPendingClasses()); |
| const Class& foo = Class::Handle(GetClass(root_library, "Foo")); |
| const auto& error = foo.EnsureIsFinalized(thread); |
| const auto& constructor = Function::Handle( |
| foo.LookupConstructor(String::Handle(String::New("Foo.")))); |
| |
| EXPECT(error == Error::null()); |
| Invoke(root_library, "test"); |
| |
| TestPipeline pipeline(constructor, CompilerPass::kJIT); |
| FlowGraph* flow_graph = pipeline.RunPasses({ |
| CompilerPass::kComputeSSA, |
| }); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| ILMatcher cursor(flow_graph, entry); |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMatchAndMoveFunctionEntry, |
| kMatchAndMoveCheckStackOverflow, |
| kMoveDebugStepChecks, |
| kMatchAndMoveGuardFieldClass, |
| kMoveGlob, |
| kMatchAndMoveCheckFieldImmutability, |
| kMatchAndMoveStoreField, |
| })); |
| } |
| |
| ISOLATE_UNIT_TEST_CASE( |
| StreamingFlowGraphBuilder_ConstMapDeeplyImmutableTypeCheckPresent) { |
| const char* kScript = R"( |
| @pragma("vm:deeply-immutable") |
| final class Foo { |
| final Map map; |
| Foo(this.map); |
| } |
| |
| @pragma("vm:entry-point", "call") |
| test() { |
| const map = const <int, int>{1: 2, 3: 4}; |
| // const Map should pass deep-immutability check. |
| Foo(map); |
| } |
| )"; |
| |
| const auto& root_library = Library::Handle(LoadTestScript(kScript)); |
| EXPECT(ClassFinalizer::ProcessPendingClasses()); |
| const Class& foo = Class::Handle(GetClass(root_library, "Foo")); |
| const auto& error = foo.EnsureIsFinalized(thread); |
| const auto& constructor = Function::Handle( |
| foo.LookupConstructor(String::Handle(String::New("Foo.")))); |
| |
| EXPECT(error == Error::null()); |
| Invoke(root_library, "test"); |
| |
| TestPipeline pipeline(constructor, CompilerPass::kJIT); |
| FlowGraph* flow_graph = pipeline.RunPasses({ |
| CompilerPass::kComputeSSA, |
| }); |
| |
| auto entry = flow_graph->graph_entry()->normal_entry(); |
| EXPECT(entry != nullptr); |
| |
| ILMatcher cursor(flow_graph, entry); |
| RELEASE_ASSERT(cursor.TryMatch({ |
| kMatchAndMoveFunctionEntry, |
| kMatchAndMoveCheckStackOverflow, |
| kMoveDebugStepChecks, |
| kMatchAndMoveGuardFieldClass, |
| kMoveGlob, |
| kMatchAndMoveCheckFieldImmutability, |
| kMatchAndMoveStoreField, |
| })); |
| } |
| |
| } // namespace dart |