// Copyright (c) 2019, 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. // // Unit tests specific to BCE (bounds check eliminaton), // which runs as part of range analysis optimizations. #include "vm/compiler/backend/range_analysis.h" #include "vm/compiler/backend/il.h" #include "vm/compiler/backend/il_printer.h" #include "vm/compiler/backend/il_test_helper.h" #include "vm/compiler/compiler_pass.h" #include "vm/object.h" #include "vm/unit_test.h" namespace dart { // Helper method to count number of bounds checks. static intptr_t CountBoundChecks(FlowGraph* flow_graph) { intptr_t count = 0; for (BlockIterator block_it = flow_graph->reverse_postorder_iterator(); !block_it.Done(); block_it.Advance()) { for (ForwardInstructionIterator it(block_it.Current()); !it.Done(); it.Advance()) { if (it.Current()->IsCheckBoundBase()) { count++; } } } return count; } // Helper method to build CFG, run BCE, and count number of // before/after bounds checks. static std::pair ApplyBCE(const char* script_chars, CompilerPass::PipelineMode mode) { // Load the script and exercise the code once // while exercising the given compiler passes. const auto& root_library = Library::Handle(LoadTestScript(script_chars)); Invoke(root_library, "main"); std::initializer_list passes = { CompilerPass::kComputeSSA, CompilerPass::kTypePropagation, CompilerPass::kApplyICData, CompilerPass::kInlining, CompilerPass::kTypePropagation, CompilerPass::kApplyICData, CompilerPass::kSelectRepresentations, CompilerPass::kCanonicalize, CompilerPass::kConstantPropagation, CompilerPass::kCSE, CompilerPass::kLICM, }; const auto& function = Function::Handle(GetFunction(root_library, "foo")); TestPipeline pipeline(function, mode); FlowGraph* flow_graph = pipeline.RunPasses(passes); // Count the number of before/after bounds checks. const intptr_t num_bc_before = CountBoundChecks(flow_graph); RangeAnalysis range_analysis(flow_graph); range_analysis.Analyze(); const intptr_t num_bc_after = CountBoundChecks(flow_graph); return {num_bc_before, num_bc_after}; } static void TestScriptJIT(const char* script_chars, intptr_t expected_before, intptr_t expected_after) { auto jit_result = ApplyBCE(script_chars, CompilerPass::kJIT); EXPECT_EQ(expected_before, jit_result.first); EXPECT_EQ(expected_after, jit_result.second); } // // BCE (bounds-check-elimination) tests. // ISOLATE_UNIT_TEST_CASE(BCECannotRemove) { const char* kScriptChars = R"( import 'dart:typed_data'; foo(Float64List l) { return l[0]; } main() { foo(new Float64List(1)); } )"; TestScriptJIT(kScriptChars, 1, 1); } ISOLATE_UNIT_TEST_CASE(BCERemoveOne) { const char* kScriptChars = R"( import 'dart:typed_data'; foo(Float64List l) { return l[1] + l[0]; } main() { foo(new Float64List(2)); } )"; TestScriptJIT(kScriptChars, 2, 1); } ISOLATE_UNIT_TEST_CASE(BCESimpleLoops) { const char* kScriptChars = R"( import 'dart:typed_data'; foo(Float64List l) { for (int i = 0; i < l.length; i++) { l[i] = 0; } for (int i = 10; i <= l.length - 5; i++) { l[i] = 1; } } main() { foo(new Float64List(100)); } )"; TestScriptJIT(kScriptChars, 2, 0); } ISOLATE_UNIT_TEST_CASE(BCESimpleLoopsDown) { const char* kScriptChars = R"( import 'dart:typed_data'; foo(Float64List l) { for (int i = l.length - 1; i >= 0; i--) { l[i] = 0; } for (int i = l.length - 5; i >= 10; i--) { l[i] = 1; } } main() { foo(new Float64List(100)); } )"; TestScriptJIT(kScriptChars, 2, 0); } ISOLATE_UNIT_TEST_CASE(BCEModulo) { const char* kScriptChars = R"( foo(int i) { var l = List.filled(3, 0); return l[i % 3] ?? l[i % (-3)]; } main() { foo(0); } )"; TestScriptJIT(kScriptChars, 2, 0); } ISOLATE_UNIT_TEST_CASE(BCELowerTriangular) { const char* kScriptChars = R"( import 'dart:typed_data'; foo(Float64List l) { for (int i = 0; i < l.length; i++) { for (int j = 0; j <= i; j++) { l[i] += l[j]; } } } main() { foo(new Float64List(100)); } )"; TestScriptJIT(kScriptChars, 2, 0); } ISOLATE_UNIT_TEST_CASE(BCEUpperTriangular) { const char* kScriptChars = R"( import 'dart:typed_data'; foo(Float64List l) { for (int i = 0; i < l.length; i++) { for (int j = i; j < l.length; j++) { l[i] += l[j]; } } } main() { foo(new Float64List(100)); } )"; TestScriptJIT(kScriptChars, 2, 0); } ISOLATE_UNIT_TEST_CASE(BCETriangularDown) { const char* kScriptChars = R"( import 'dart:typed_data'; foo(Float64List l) { for (int i = l.length - 1; i >= 0; i--) { for (int j = i; j >= 0; j--) { l[i] += l[j]; } } } main() { foo(new Float64List(100)); } )"; TestScriptJIT(kScriptChars, 2, 0); } ISOLATE_UNIT_TEST_CASE(BCENamedLength) { const char* kScriptChars = R"( import 'dart:typed_data'; Int8List foo(int count) { var x = new Int8List(count); for (int i = 0; i < count; i++) { x[i] = 0; } return x; } main() { foo(100); } )"; TestScriptJIT(kScriptChars, 1, 0); } ISOLATE_UNIT_TEST_CASE(BCEBubbleSort) { const char* kScriptChars = R"( import 'dart:typed_data'; foo(Float64List a) { int len = a.length; for (int i = len - 2; i >= 0; i--) { for (int j = 0; j <= i; j++) { var c = a[j]; var n = a[j + 1]; if (c > n) { a[j] = n; a[j + 1] = c; } } } } main() { foo(new Float64List(100)); } )"; TestScriptJIT(kScriptChars, 2, 0); } ISOLATE_UNIT_TEST_CASE(BCEArithmeticWrapAround) { const char* kScriptChars = R"( import 'dart:typed_data'; const kMax = 0x7fffffffffffffff; foo(Float64List a) { for (int i = kMax - 10; i < kMax; i++) { for (int j = i + 10; j < a.length; j++) { // Don't be fooled: j in [-minint, len). a[j] = 1; } } } main() { try { foo(new Float64List(100)); } catch (e) { } } )"; TestScriptJIT(kScriptChars, 1, 1); } ISOLATE_UNIT_TEST_CASE(BCEListNamedAndPlainLength) { const char* kScriptChars = R"( List foo(int count) { var x = new List.filled(count, 42); for (int i = 0; i < count; i++) { x[i] = 0; } for (int i = 0; i < x.length; i++) { x[i] = 0; } return x; } main() { foo(100); } )"; TestScriptJIT(kScriptChars, 2, 0); } } // namespace dart