// 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 "platform/assert.h" #include "vm/compiler/backend/il_printer.h" #include "vm/compiler/backend/il_test_helper.h" #include "vm/unit_test.h" namespace dart { DEBUG_ONLY(DECLARE_FLAG(bool, trace_write_barrier_elimination);) ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_JoinSuccessors) { DEBUG_ONLY( SetFlagScope sfs(&FLAG_trace_write_barrier_elimination, true)); const char* nullable_tag = TestCase::NullableTag(); const char* null_assert_tag = TestCase::NullAssertTag(); // This is a regression test for a bug where we were using // JoinEntry::SuccessorCount() to determine the number of outgoing blocks // from the join block. JoinEntry::SuccessorCount() is in fact always 0; // JoinEntry::last_instruction()->SuccessorCount() should be used instead. // clang-format off auto kScript = Utils::CStringUniquePtr( OS::SCreate(nullptr, R"( class C { int%s value; C%s next; C%s prev; } @pragma("vm:never-inline") fn() {} foo(int x) { C%s prev = C(); C%s next; while (x --> 0) { next = C(); next%s.prev = prev; prev?.next = next; prev = next; fn(); } return next; } main() { foo(10); } )", nullable_tag, nullable_tag, nullable_tag, nullable_tag, nullable_tag, null_assert_tag), std::free); // clang-format on const auto& root_library = Library::Handle(LoadTestScript(kScript.get())); Invoke(root_library, "main"); const auto& function = Function::Handle(GetFunction(root_library, "foo")); TestPipeline pipeline(function, CompilerPass::kJIT); FlowGraph* flow_graph = pipeline.RunPasses({}); auto entry = flow_graph->graph_entry()->normal_entry(); EXPECT(entry != nullptr); StoreInstanceFieldInstr* store1 = nullptr; StoreInstanceFieldInstr* store2 = nullptr; ILMatcher cursor(flow_graph, entry); RELEASE_ASSERT(cursor.TryMatch({ kMoveGlob, kMatchAndMoveGoto, kMoveGlob, kMatchAndMoveBranchTrue, kMoveGlob, {kMatchAndMoveStoreInstanceField, &store1}, kMoveGlob, {kMatchAndMoveStoreInstanceField, &store2}, })); EXPECT(store1->ShouldEmitStoreBarrier() == false); EXPECT(store2->ShouldEmitStoreBarrier() == true); } ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_AtLeastOnce) { DEBUG_ONLY( SetFlagScope sfs(&FLAG_trace_write_barrier_elimination, true)); // Ensure that we process every block at least once during the analysis // phase so that the out-sets will be initialized. If we don't process // each block at least once, the store "c.next = n" will be marked // NoWriteBarrier. // clang-format off auto kScript = Utils::CStringUniquePtr(OS::SCreate(nullptr, R"( class C { %s C next; } @pragma("vm:never-inline") fn() {} foo(int x) { C c = C(); C n = C(); if (x > 5) { fn(); } c.next = n; return c; } main() { foo(0); foo(10); } )", TestCase::LateTag()), std::free); // clang-format on const auto& root_library = Library::Handle(LoadTestScript(kScript.get())); Invoke(root_library, "main"); const auto& function = Function::Handle(GetFunction(root_library, "foo")); TestPipeline pipeline(function, CompilerPass::kJIT); FlowGraph* flow_graph = pipeline.RunPasses({}); auto entry = flow_graph->graph_entry()->normal_entry(); EXPECT(entry != nullptr); StoreInstanceFieldInstr* store = nullptr; ILMatcher cursor(flow_graph, entry); RELEASE_ASSERT(cursor.TryMatch({ kMoveGlob, kMatchAndMoveBranchFalse, kMoveGlob, kMatchAndMoveGoto, kMoveGlob, {kMatchAndMoveStoreInstanceField, &store}, })); EXPECT(store->ShouldEmitStoreBarrier() == true); } ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_Arrays) { DEBUG_ONLY( SetFlagScope sfs(&FLAG_trace_write_barrier_elimination, true)); const char* nullable_tag = TestCase::NullableTag(); // Test that array allocations are not considered usable after a // may-trigger-GC instruction (in this case CheckStackOverflow), unlike // normal allocations, which are only interruped by a Dart call. // clang-format off auto kScript = Utils::CStringUniquePtr(OS::SCreate(nullptr, R"( class C { %s C next; } @pragma("vm:never-inline") fn() {} foo(int x) { C c = C(); C n = C(); List array = List.filled(1, null); while (x --> 0) { c.next = n; n = c; c = C(); } array[0] = c; return array; } main() { foo(10); } )", TestCase::LateTag(), nullable_tag, nullable_tag), std::free); // clang-format on const auto& root_library = Library::Handle(LoadTestScript(kScript.get())); Invoke(root_library, "main"); const auto& function = Function::Handle(GetFunction(root_library, "foo")); TestPipeline pipeline(function, CompilerPass::kJIT); FlowGraph* flow_graph = pipeline.RunPasses({}); auto entry = flow_graph->graph_entry()->normal_entry(); EXPECT(entry != nullptr); StoreInstanceFieldInstr* store_into_c = nullptr; StoreIndexedInstr* store_into_array = nullptr; ILMatcher cursor(flow_graph, entry); RELEASE_ASSERT(cursor.TryMatch({ kMoveGlob, kMatchAndMoveGoto, kMoveGlob, kMatchAndMoveBranchTrue, kMoveGlob, {kMatchAndMoveStoreInstanceField, &store_into_c}, kMoveGlob, kMatchAndMoveGoto, kMoveGlob, kMatchAndMoveBranchFalse, kMoveGlob, {kMatchAndMoveStoreIndexed, &store_into_array}, })); EXPECT(store_into_c->ShouldEmitStoreBarrier() == false); EXPECT(store_into_array->ShouldEmitStoreBarrier() == true); } ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_Regress43786) { DEBUG_ONLY( SetFlagScope sfs(&FLAG_trace_write_barrier_elimination, true)); const char* kScript = R"( foo() { final root = List.filled(128, null); List last = root; for (int i = 0; i < 10 * 1024; ++i) { final nc = List.filled(128, null); last[0] = nc; last = nc; } } main() { foo(); } )"; const auto& root_library = Library::Handle(LoadTestScript(kScript)); Invoke(root_library, "main"); const auto& function = Function::Handle(GetFunction(root_library, "foo")); TestPipeline pipeline(function, CompilerPass::kJIT); FlowGraph* flow_graph = pipeline.RunPasses({}); auto entry = flow_graph->graph_entry()->normal_entry(); EXPECT(entry != nullptr); StoreIndexedInstr* store_into_phi = nullptr; ILMatcher cursor(flow_graph, entry); RELEASE_ASSERT(cursor.TryMatch( { kMatchAndMoveCreateArray, kMatchAndMoveGoto, kMatchAndMoveBranchTrue, kMatchAndMoveCreateArray, {kMatchAndMoveStoreIndexed, &store_into_phi}, }, kMoveGlob)); EXPECT(store_into_phi->array()->definition()->IsPhi()); EXPECT(store_into_phi->ShouldEmitStoreBarrier()); } } // namespace dart