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

// Verifies that compiler can remove bounds checks when told by pragma.

import 'package:expect/expect.dart';
import 'package:vm/testing/il_matchers.dart';

import 'dart:typed_data';

@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
int direct_uint8list_has_bounds_check(Uint8List list) {
  int result = 0;
  for (int i = 0; i < 10; i++) {
    result = list[i];
  }
  return result;
}

@pragma('vm:unsafe:no-bounds-checks')
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
int direct_uint8list_no_bounds_check(Uint8List list) {
  int result = 0;
  for (int i = 0; i < 10; i++) {
    result = list[i];
  }
  return result;
}

@pragma('vm:prefer-inline')
int helper_uint8list_no_pragma(Uint8List list) {
  int result = 0;
  for (int i = 0; i < 10; i++) {
    result = list[i];
  }
  return result;
}

@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
int inlined_uint8list_has_bounds_check(Uint8List list) {
  if (helper_uint8list_no_pragma(list) == 42) {
    return 42;
  }
  return 0;
}

@pragma('vm:prefer-inline')
@pragma('vm:unsafe:no-bounds-checks')
int helper_uint8list_pragma(Uint8List list) {
  int result = 0;
  for (int i = 0; i < 10; i++) {
    result = list[i];
  }
  return result;
}

// For whatever reason, this passes even without the fix, despite real-world
// example in CFE (AbstractScanner.select) as well as a test in the VM
// (runtime/vm/compiler/backend/redundancy_elimination_test.cc,
// BoundsCheckElimination_Pragma_Inline) needs the fix.
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
int inlined_uint8list_no_bounds_check(Uint8List list, bool b) {
  if (b) {
    return helper_uint8list_pragma(list);
  } else {
    return helper_uint8list_pragma(list) + 42;
  }
}

@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
int direct_string_has_bounds_check(String s) {
  int result = 0;
  for (int i = 0; i < 10; i++) {
    result = s.codeUnitAt(i);
  }
  return result;
}

@pragma('vm:unsafe:no-bounds-checks')
@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
int direct_string_no_bounds_check(String s) {
  int result = 0;
  for (int i = 0; i < 10; i++) {
    result = s.codeUnitAt(i);
  }
  return result;
}

@pragma('vm:prefer-inline')
int helper_string_no_pragma(String s) {
  int result = 0;
  for (int i = 0; i < 10; i++) {
    result = s.codeUnitAt(i);
  }
  return result;
}

@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
int inlined_string_has_bounds_check(String s) {
  if (helper_string_no_pragma(s) == 42) {
    return 42;
  }
  return 0;
}

@pragma('vm:prefer-inline')
@pragma('vm:unsafe:no-bounds-checks')
int helper_string_pragma(String s) {
  int result = 0;
  for (int i = 0; i < 10; i++) {
    result = s.codeUnitAt(i);
  }
  return result;
}

@pragma('vm:never-inline')
@pragma('vm:testing:print-flow-graph')
int inlined_string_no_bounds_check(String s) {
  if (helper_string_pragma(s) == 42) {
    return 42;
  }
  return 0;
}

void main() {
  // Uint8List access directly.
  direct_uint8list_has_bounds_check(new Uint8List(20));
  direct_uint8list_no_bounds_check(new Uint8List(20));

  // Uint8List access via inlined function.
  inlined_uint8list_has_bounds_check(new Uint8List(20));
  inlined_uint8list_no_bounds_check(
    new Uint8List(20),
    new Uint8List(20).toString().length.isEven,
  );

  // String access directly.
  direct_string_has_bounds_check(new Uint8List(20).toString());
  direct_string_no_bounds_check(new Uint8List(20).toString());

  // String access via inlined function.
  inlined_string_has_bounds_check(new Uint8List(20).toString());
  inlined_string_no_bounds_check(new Uint8List(20).toString());
}

void extractAllInstructions(dynamic data, List<String> into) {
  if (data is Map) {
    for (var entry in data.entries) {
      if (entry.key == "o" && entry.value is String) {
        into.add(entry.value);
      } else {
        extractAllInstructions(entry.value, into);
      }
    }
  } else if (data is List) {
    for (var entry in data) {
      extractAllInstructions(entry, into);
    }
  } else {
    if (data is int || data is String) {
      // ok
    } else {
      print("Notice: Unhandled data: ${data.runtimeType}: $data");
    }
  }
}

bool hasCheckBounds(FlowGraph graph) {
  List<String> ils = [];
  extractAllInstructions(graph.blocks(), ils);
  for (String il in ils) {
    if (il == "GenericCheckBound") return true;
  }
  return false;
}

void matchIL$direct_uint8list_has_bounds_check(FlowGraph graph) {
  Expect.isTrue(hasCheckBounds(graph), "should have bounds checks");
}

void matchIL$direct_uint8list_no_bounds_check(FlowGraph graph) {
  Expect.isFalse(hasCheckBounds(graph), "should not have bounds checks");
}

void matchIL$inlined_uint8list_has_bounds_check(FlowGraph graph) {
  Expect.isTrue(hasCheckBounds(graph), "should have bounds checks");
}

void matchIL$inlined_uint8list_no_bounds_check(FlowGraph graph) {
  Expect.isFalse(hasCheckBounds(graph), "should not have bounds checks");
}

void matchIL$direct_string_has_bounds_check(FlowGraph graph) {
  Expect.isTrue(hasCheckBounds(graph), "should have bounds checks");
}

void matchIL$direct_string_no_bounds_check(FlowGraph graph) {
  Expect.isFalse(hasCheckBounds(graph), "should not have bounds checks");
}

void matchIL$inlined_string_has_bounds_check(FlowGraph graph) {
  Expect.isTrue(hasCheckBounds(graph), "should have bounds checks");
}

void matchIL$inlined_string_no_bounds_check(FlowGraph graph) {
  Expect.isFalse(hasCheckBounds(graph), "should not have bounds checks");
}
