// 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 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()); } } // namespace dart