Files
sdk/runtime/vm/compiler/backend/loops_test.cc
T
Vyacheslav Egorov 4e027fae97 [vm/compiler] Force unboxing of int phis on 64-bit platforms
More specifically on 64-bit platforms, when other heuristic fail:

- in AOT mode unbox all int typed phis.
- in JIT mode unbox all non-Smi int typed phis i.e. mixed
  Mint/Smi phis.

This brings treatment of int phis closer to how we treat other
boxed phis.

This considerably improves AOT code quality for loops which
our current conservative heuristic does not currently hit,
for example:

  void foo(int start, int end) {
    for (var i = start; i < end; i++) {
    }
  }

Code like this would use a boxed Phi previously which does not make
sense.

In JIT mode to really benefit from aggressive unboxing we
might need to shift from over-reliance on Smi operations, e.g.
when phi is unboxed we want to cascade that through and unbox
operations which are performed on it transitively. This requires
much more sophisticated algorithm and is outside of scope for this
change.

In AOT we in general treat all arithmetic operations as operations
over 64-bit int values so unboxing phis makes more sense.

As a result of this change we see small improvement in code size:

  flutter_gallery_app_so_gzip_size -0.2392%

TEST=ci


Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-x64-try
Change-Id: Iae9a16f353e53f649da4e665d2812ad47099a674
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/187860
Commit-Queue: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
2021-03-11 22:20:51 +00:00

529 lines
14 KiB
C++

// 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.
//
// Unit tests specific to loops and induction variables.
// Note, try to avoid relying on information that is subject
// to change (block ids, variable numbers, etc.) in order
// to make this test less sensitive to unrelated changes.
#include "vm/compiler/backend/loops.h"
#include "vm/compiler/backend/il_printer.h"
#include "vm/compiler/backend/il_test_helper.h"
#include "vm/compiler/backend/inliner.h"
#include "vm/compiler/backend/type_propagator.h"
#include "vm/compiler/compiler_pass.h"
#include "vm/compiler/frontend/kernel_to_il.h"
#include "vm/compiler/jit/jit_call_specializer.h"
#include "vm/log.h"
#include "vm/object.h"
#include "vm/parser.h"
#include "vm/symbols.h"
#include "vm/unit_test.h"
namespace dart {
// Helper method to construct an induction debug string for loop hierarchy.
void TestString(BaseTextBuffer* f,
LoopInfo* loop,
const GrowableArray<BlockEntryInstr*>& preorder) {
for (; loop != nullptr; loop = loop->next()) {
intptr_t depth = loop->NestingDepth();
f->Printf("%*c[%" Pd "\n", static_cast<int>(2 * depth), ' ', loop->id());
for (BitVector::Iterator block_it(loop->blocks()); !block_it.Done();
block_it.Advance()) {
BlockEntryInstr* block = preorder[block_it.Current()];
if (block->IsJoinEntry()) {
for (PhiIterator it(block->AsJoinEntry()); !it.Done(); it.Advance()) {
InductionVar* induc = loop->LookupInduction(it.Current());
if (induc != nullptr) {
// Obtain the debug string for induction and bounds.
f->Printf("%*c%s", static_cast<int>(2 * depth), ' ',
induc->ToCString());
for (auto bound : induc->bounds()) {
f->Printf(" %s", bound.limit_->ToCString());
}
f->AddString("\n");
}
}
}
for (ForwardInstructionIterator it(block); !it.Done(); it.Advance()) {
InductionVar* induc =
loop->LookupInduction(it.Current()->AsDefinition());
if (InductionVar::IsInduction(induc)) {
f->Printf("%*c%s\n", static_cast<int>(2 * depth), ' ',
induc->ToCString());
}
}
}
TestString(f, loop->inner(), preorder);
f->Printf("%*c]\n", static_cast<int>(2 * depth), ' ');
}
}
// Helper method to build CFG and compute induction.
static const char* ComputeInduction(Thread* thread, const char* script_chars) {
// Load the script and exercise the code once.
const auto& root_library = Library::Handle(LoadTestScript(script_chars));
Invoke(root_library, "main");
std::initializer_list<CompilerPass::Id> passes = {
CompilerPass::kComputeSSA,
CompilerPass::kTypePropagation,
CompilerPass::kApplyICData,
CompilerPass::kTypePropagation,
CompilerPass::kSelectRepresentations,
CompilerPass::kTypePropagation,
CompilerPass::kCanonicalize,
};
const auto& function = Function::Handle(GetFunction(root_library, "foo"));
TestPipeline pipeline(function, CompilerPass::kJIT);
FlowGraph* flow_graph = pipeline.RunPasses(passes);
// Build loop hierarchy and find induction.
const LoopHierarchy& hierarchy = flow_graph->GetLoopHierarchy();
hierarchy.ComputeInduction();
flow_graph->RemoveRedefinitions(); // don't query later
// Construct and return a debug string for testing.
char buffer[1024];
BufferFormatter f(buffer, sizeof(buffer));
TestString(&f, hierarchy.top(), flow_graph->preorder());
return Thread::Current()->zone()->MakeCopyOfString(buffer);
}
//
// Induction tests.
//
ISOLATE_UNIT_TEST_CASE(BasicInductionUp) {
const char* script_chars =
R"(
foo() {
for (int i = 0; i < 100; i++) {
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(0 + 1 * i) 100\n" // phi
" LIN(1 + 1 * i)\n" // add
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(BasicInductionDown) {
const char* script_chars =
R"(
foo() {
for (int i = 100; i > 0; i--) {
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(100 + -1 * i) 0\n" // phi
" LIN(99 + -1 * i)\n" // sub
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(BasicInductionStepUp) {
const char* script_chars =
R"(
foo() {
for (int i = 10; i < 100; i += 2) {
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(10 + 2 * i)\n" // phi
" LIN(12 + 2 * i)\n" // add
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(BasicInductionStepDown) {
const char* script_chars =
R"(
foo() {
for (int i = 100; i >= 0; i -= 7) {
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(100 + -7 * i)\n" // phi
" LIN(93 + -7 * i)\n" // sub
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(BasicInductionLoopNest) {
const char* script_chars =
R"(
foo() {
for (int i = 0; i < 100; i++) {
for (int j = 1; j < 100; j++) {
for (int k = 2; k < 100; k++) {
}
}
}
}
main() {
foo();
}
)";
const char* expected =
" [2\n"
" LIN(0 + 1 * i) 100\n" // i
" LIN(1 + 1 * i)\n"
" [1\n"
" LIN(1 + 1 * i) 100\n" // j
" LIN(2 + 1 * i)\n"
" [0\n"
" LIN(2 + 1 * i) 100\n" // k
" LIN(3 + 1 * i)\n"
" ]\n"
" ]\n"
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(ChainInduction) {
const char* script_chars =
R"(
foo() {
int j = 1;
for (int i = 0; i < 100; i++) {
j += 5;
j += 7;
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(1 + 12 * i)\n" // phi (j)
" LIN(0 + 1 * i) 100\n" // phi
" LIN(6 + 12 * i)\n" // j-add
" LIN(13 + 12 * i)\n" // j-add
" LIN(1 + 1 * i)\n" // add
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(TwoWayInduction) {
const char* script_chars =
R"(
foo() {
int j = 123;
for (int i = 0; i < 100; i++) {
if (i == 10) {
j += 3;
} else {
j += 3;
}
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(123 + 3 * i)\n" // phi (j)
" LIN(0 + 1 * i) 100\n" // phi
" LIN(126 + 3 * i)\n" // j-true
" LIN(126 + 3 * i)\n" // j-false
" LIN(1 + 1 * i)\n" // add
" LIN(126 + 3 * i)\n" // phi (j)
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(DerivedInduction) {
const char* script_chars =
R"(
foo() {
for (int i = 1; i < 100; i++) {
int a = i + 3;
int b = i - 5;
int c = i * 7;
int d = - i;
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(1 + 1 * i) 100\n" // phi
" LIN(4 + 1 * i)\n" // a
" LIN(-4 + 1 * i)\n" // b
" LIN(7 + 7 * i)\n" // c
" LIN(-1 + -1 * i)\n" // d
" LIN(2 + 1 * i)\n" // add
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(WrapAroundAndDerived) {
const char* script_chars =
R"(
foo() {
int w = 99;
for (int i = 0; i < 100; i++) {
int a = w + 3;
int b = w - 5;
int c = w * 7;
int d = - w;
w = i;
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" WRAP(99, LIN(0 + 1 * i))\n" // phi (w)
" LIN(0 + 1 * i) 100\n" // phi (i)
" WRAP(102, LIN(3 + 1 * i))\n" // a
" WRAP(94, LIN(-5 + 1 * i))\n" // b
" WRAP(693, LIN(0 + 7 * i))\n" // c
" WRAP(-99, LIN(0 + -1 * i))\n" // d
" LIN(1 + 1 * i)\n" // add
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(PeriodicAndDerived) {
const char* script_chars =
R"(
foo() {
int p1 = 3;
int p2 = 5;
for (int i = 0; i < 100; i++) {
int a = p1 + 3;
int b = p1 - 5;
int c = p1 * 7;
int d = - p1;
p1 = - p1;
p2 = 100 - p2;
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" PERIOD(3, -3)\n" // phi(p1)
" PERIOD(5, 95)\n" // phi(p2)
" LIN(0 + 1 * i) 100\n" // phi
" PERIOD(6, 0)\n" // a
" PERIOD(-2, -8)\n" // b
" PERIOD(21, -21)\n" // c
" PERIOD(-3, 3)\n" // d
" PERIOD(-3, 3)\n" // p1
" PERIOD(95, 5)\n" // p2
" LIN(1 + 1 * i)\n" // add
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
//
// Bound specific tests.
//
ISOLATE_UNIT_TEST_CASE(NonStrictConditionUp) {
const char* script_chars =
R"(
foo() {
for (int i = 0; i <= 100; i++) {
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(0 + 1 * i) 101\n" // phi
" LIN(1 + 1 * i)\n" // add
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(NonStrictConditionUpWrap) {
const char* script_chars =
R"(
foo() {
for (int i = 0x7ffffffffffffffe; i <= 0x7fffffffffffffff; i++) {
if (i < 0) break;
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(9223372036854775806 + 1 * i)\n" // phi
#if !defined(TARGET_ARCH_IS_64_BIT)
" LIN(9223372036854775806 + 1 * i)\n" // (un)boxing
" LIN(9223372036854775806 + 1 * i)\n"
" LIN(9223372036854775806 + 1 * i)\n"
#endif // !defined(TARGET_ARCH_IS_64_BIT)
" LIN(9223372036854775807 + 1 * i)\n" // add
#if !defined(TARGET_ARCH_IS_64_BIT)
" LIN(9223372036854775807 + 1 * i)\n" // unbox
#endif // !defined(TARGET_ARCH_IS_64_BIT)
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(NonStrictConditionDown) {
const char* script_chars =
R"(
foo() {
for (int i = 100; i >= 0; i--) {
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(100 + -1 * i) -1\n" // phi
" LIN(99 + -1 * i)\n" // add
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(NonStrictConditionDownWrap) {
const char* script_chars =
R"(
foo() {
for (int i = 0x8000000000000001; i >= 0x8000000000000000; i--) {
if (i > 0) break;
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(-9223372036854775807 + -1 * i)\n" // phi
#if !defined(TARGET_ARCH_IS_64_BIT)
" LIN(-9223372036854775807 + -1 * i)\n" // (un)boxing
" LIN(-9223372036854775807 + -1 * i)\n"
" LIN(-9223372036854775807 + -1 * i)\n"
#endif // !defined(TARGET_ARCH_IS_64_BIT)
" LIN(-9223372036854775808 + -1 * i)\n" // sub
#if !defined(TARGET_ARCH_IS_64_BIT)
" LIN(-9223372036854775808 + -1 * i)\n" // unbox
#endif // !defined(TARGET_ARCH_IS_64_BIT)
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(NotEqualConditionUp) {
const char* script_chars =
R"(
foo() {
for (int i = 10; i != 20; i++) {
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(10 + 1 * i) 20\n" // phi
" LIN(11 + 1 * i)\n" // add
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(NotEqualConditionDown) {
const char* script_chars =
R"(
foo() {
for (int i = 20; i != 10; i--) {
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(20 + -1 * i) 10\n" // phi
" LIN(19 + -1 * i)\n" // sub
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(SecondExitUp) {
const char* script_chars =
R"(
foo() {
for (int i = 0; i < 100; i++) {
if (i >= 50) break;
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(0 + 1 * i) 100 50\n" // phi
" LIN(1 + 1 * i)\n" // add
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
ISOLATE_UNIT_TEST_CASE(SecondExitDown) {
const char* script_chars =
R"(
foo() {
for (int i = 100; i > 0; i--) {
if (i <= 10) break;
}
}
main() {
foo();
}
)";
const char* expected =
" [0\n"
" LIN(100 + -1 * i) 0 10\n" // phi
" LIN(99 + -1 * i)\n" // sub
" ]\n";
EXPECT_STREQ(expected, ComputeInduction(thread, script_chars));
}
} // namespace dart