// Copyright (c) 2018, 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.

import "package:expect/expect.dart";

// We want to run each test with and without inlining of the target functions.
// We accomplish this by using VM options in the yes-inlining variant to set the
// "enable_inlining" constant variable to true. This maximizes code sharing
// between the two variants, which are otherwise identical.
const pragma? NeverInline = const bool.fromEnvironment("enable_inlining")
    ? null
    : pragma('vm:never-inline');

// In AOT we need to force some functions to be inlined since we only build the
// unchecked entry-point when inlining.
const pragma? AlwaysInline = const bool.fromEnvironment("enable_inlining")
    ? pragma('vm:prefer-inline')
    : null;

// All these tests can be run in test mode or in benchmark mode. In benchmark
// mode, there is introspection is omitted and the tests runs for many more
// iterations.
const bool benchmarkMode = const bool.fromEnvironment("benchmark_mode");

class TargetCalls {
  int checked = 0;
  int unchecked = 0;

  // Leave a little room for some cases which always use the checked entry, like
  // lazy compile stub.
  static const int wiggle = 10;

  void expectChecked(int iterations) {
    print("$checked, $unchecked");
    Expect.isTrue(checked >= iterations - wiggle && unchecked == 0);
  }

  void expectUnchecked(int iterations) {
    print("$checked, $unchecked");
    Expect.isTrue(checked <= wiggle && unchecked >= iterations - wiggle);
  }
}

TargetCalls entryPoint = TargetCalls();
TargetCalls tearoffEntryPoint = TargetCalls();

_validateHelper(int ep, TargetCalls? calls) {
  calls ??= entryPoint;

  if (ep < 0 || ep > 2) {
    Expect.fail("ERROR: invalid entry-point ($ep) passed by VM.");
  }
  if (ep == 0) {
    calls.checked++;
  } else {
    calls.unchecked++;
  }
}

void _validateFn(String _, int ep) => _validateHelper(ep, null);

// Invocation of tearoffs go through a tearoff wrapper. We want to independently
// test which entrypoint was used for the tearoff wrapper vs. which was used for
// actual target.
_validateTearoffFn(String name, int ep) {
  _validateHelper(
    ep,
    name.endsWith("#tearoff") ? tearoffEntryPoint : entryPoint,
  );
}

@pragma("vm:entry-point", "get")
const validate = benchmarkMode ? null : _validateFn;

@pragma("vm:entry-point", "get")
const validateTearoff = benchmarkMode ? null : _validateTearoffFn;
