// 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 <vector>

#include "vm/compiler/backend/il.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 {

ISOLATE_UNIT_TEST_CASE(ReachabilityFence_Simple) {
  // clang-format off
  const char* kScript = R"(
      import 'dart:_internal' show reachabilityFence;

      int someGlobal = 0;

      class A {
        int? a;
      }

      void someFunction(int arg) {
        someGlobal += arg;
      }

      main() {
        final object = A()..a = 10;
        someFunction(object.a!);
        reachabilityFence(object);
      }
      )";
  // clang-format on

  const auto& root_library = Library::Handle(LoadTestScript(kScript));

  Invoke(root_library, "main");

  const auto& function = Function::Handle(GetFunction(root_library, "main"));
  TestPipeline pipeline(function, CompilerPass::kJIT);
  FlowGraph* flow_graph = pipeline.RunPasses({});
  ASSERT(flow_graph != nullptr);

  auto entry = flow_graph->graph_entry()->normal_entry();
  EXPECT(entry != nullptr);

  //  v2 <- AllocateObject(A <not-aliased>) T{A}
  //  ...
  //  [use field of object v2]
  //  ReachabilityFence(v2)
  AllocateObjectInstr* allocate_object = nullptr;
  ReachabilityFenceInstr* fence = nullptr;

  ILMatcher cursor(flow_graph, entry);
  RELEASE_ASSERT(cursor.TryMatch({
      kMoveGlob,
      // Allocate the object.
      {kMatchAndMoveAllocateObject, &allocate_object},
      kMoveGlob,
      // The call.
      kMatchAndMoveStoreStaticField,
      // The fence should not be moved before the call.
      {kMatchAndMoveReachabilityFence, &fence},
  }));

  EXPECT(fence->value()->definition() == allocate_object);
}

ISOLATE_UNIT_TEST_CASE(ReachabilityFence_Loop) {
  // clang-format off
  const char* kScript = R"(
      import 'dart:_internal' show reachabilityFence;

      int someGlobal = 0;

      class A {
        int? a;
      }

      @pragma('vm:never-inline')
      A makeSomeA() {
        return A()..a = 10;
      }

      void someFunction(int arg) {
        someGlobal += arg;
      }

      main() {
        final object = makeSomeA();
        for(int i = 0; i < 100000; i++) {
          someFunction(object.a!);
          reachabilityFence(object);
        }
      }
      )";
  // clang-format on

  const auto& root_library = Library::Handle(LoadTestScript(kScript));

  Invoke(root_library, "main");

  const auto& function = Function::Handle(GetFunction(root_library, "main"));
  TestPipeline pipeline(function, CompilerPass::kJIT);
  FlowGraph* flow_graph = pipeline.RunPasses({});
  ASSERT(flow_graph != nullptr);

  auto entry = flow_graph->graph_entry()->normal_entry();
  EXPECT(entry != nullptr);

  StaticCallInstr* object = nullptr;
  LoadFieldInstr* field_load = nullptr;
  ReachabilityFenceInstr* fence = nullptr;

  ILMatcher cursor(flow_graph, entry);
  RELEASE_ASSERT(cursor.TryMatch(
      {
          // Get the object from some method
          {kMatchAndMoveStaticCall, &object},
          // Load the field outside the loop.
          {kMatchAndMoveLoadField, &field_load},
          // Go into the loop.
          kMatchAndMoveBranchTrue,
          // The fence should not be moved outside of the loop.
          {kMatchAndMoveReachabilityFence, &fence},
      },
      /*insert_before=*/kMoveGlob));

  EXPECT(field_load->instance()->definition() == object);
  EXPECT(fence->value()->definition() == object);
}

ISOLATE_UNIT_TEST_CASE(ReachabilityFence_NoCanonicalize) {
  // clang-format off
  const char* kScript = R"(
      import 'dart:_internal' show reachabilityFence;

      int someGlobal = 0;

      class A {
        int? a;
      }

      @pragma('vm:never-inline')
      A makeSomeA() {
        return A()..a = 10;
      }

      void someFunction(int arg) {
        someGlobal += arg;
      }

      main() {
        final object = makeSomeA();
        reachabilityFence(object);
        for(int i = 0; i < 100000; i++) {
          someFunction(object.a!);
          reachabilityFence(object);
        }
        reachabilityFence(object);
        reachabilityFence(object);
      }
      )";
  // clang-format on

  const auto& root_library = Library::Handle(LoadTestScript(kScript));

  Invoke(root_library, "main");

  const auto& function = Function::Handle(GetFunction(root_library, "main"));
  TestPipeline pipeline(function, CompilerPass::kJIT);
  FlowGraph* flow_graph = pipeline.RunPasses({});
  ASSERT(flow_graph != nullptr);

  auto entry = flow_graph->graph_entry()->normal_entry();
  EXPECT(entry != nullptr);

  StaticCallInstr* object = nullptr;
  ReachabilityFenceInstr* fence1 = nullptr;
  ReachabilityFenceInstr* fence2 = nullptr;
  ReachabilityFenceInstr* fence3 = nullptr;
  ReachabilityFenceInstr* fence4 = nullptr;

  ILMatcher cursor(flow_graph, entry);
  RELEASE_ASSERT(cursor.TryMatch(
      {
          {kMatchAndMoveStaticCall, &object},
          {kMatchAndMoveReachabilityFence, &fence1},
          kMatchAndMoveBranchTrue,
          {kMatchAndMoveReachabilityFence, &fence2},
          kMatchAndMoveBranchFalse,
          {kMatchAndMoveReachabilityFence, &fence3},
          {kMatchAndMoveReachabilityFence, &fence4},
      },
      /*insert_before=*/kMoveGlob));

  EXPECT(fence1->value()->definition() == object);
  EXPECT(fence2->value()->definition() == object);
  EXPECT(fence3->value()->definition() == object);
  EXPECT(fence4->value()->definition() == object);
}

}  // namespace dart
