text: // 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<bool> 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<bool> 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<bool> 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<C%s> array = List<C%s>.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<bool> sfs(&FLAG_trace_write_barrier_elimination, true));
    const char* kScript = R"(
        foo() {
          final root = List<dynamic>.filled(128, null);
          List<dynamic> last = root;
          for (int i = 0; i < 10 * 1024; ++i) {
            final nc = List<dynamic>.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());
  }
  
  ISOLATE_UNIT_TEST_CASE(IRTest_WriteBarrierElimination_LoadLateField) {
    DEBUG_ONLY(
        SetFlagScope<bool> sfs(&FLAG_trace_write_barrier_elimination, true));
    const char* kScript = R"(
      class A {
        late var x = new B();
      }
  
      class B {
      }
  
      class C {
        C(this.a, this.b);
        A a;
        B b;
      }
  
      foo(A a) => C(a, a.x);
  
      main() { foo(A()); }
      )";
  
    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);
  
    StoreInstanceFieldInstr* store1 = nullptr;
    StoreInstanceFieldInstr* store2 = nullptr;
  
    ILMatcher cursor(flow_graph, entry);
    RELEASE_ASSERT(cursor.TryMatch(
        {
            kMatchAndMoveAllocateObject,
            kMatchAndMoveLoadField,
            {kMatchAndMoveStoreInstanceField, &store1},
            {kMatchAndMoveStoreInstanceField, &store2},
        },
        kMoveGlob));
  
    EXPECT(store1->ShouldEmitStoreBarrier());
    EXPECT(store2->ShouldEmitStoreBarrier());
  }
  
  }  // namespace dart
  