diff --git a/src/hotspot/share/opto/convertnode.cpp b/src/hotspot/share/opto/convertnode.cpp index 304b0380753..c08e8591fb1 100644 --- a/src/hotspot/share/opto/convertnode.cpp +++ b/src/hotspot/share/opto/convertnode.cpp @@ -258,14 +258,30 @@ static inline bool long_ranges_overlap(jlong lo1, jlong hi1, // Two ranges overlap iff one range's low point falls in the other range. return (lo2 <= lo1 && lo1 <= hi2) || (lo1 <= lo2 && lo2 <= hi1); } + +// If there is an existing ConvI2L node with the given parent and type, return +// it. Otherwise, create and return a new one. Both reusing existing ConvI2L +// nodes and postponing the idealization of new ones are needed to avoid an +// explosion of recursive Ideal() calls when compiling long AddI chains. +static Node* find_or_make_convI2L(PhaseIterGVN* igvn, Node* parent, + const TypeLong* type) { + Node* n = new ConvI2LNode(parent, type); + Node* existing = igvn->hash_find_insert(n); + if (existing != NULL) { + n->destruct(igvn); + return existing; + } + return igvn->register_new_node_with_optimizer(n); +} #endif //------------------------------Ideal------------------------------------------ Node *ConvI2LNode::Ideal(PhaseGVN *phase, bool can_reshape) { + PhaseIterGVN *igvn = phase->is_IterGVN(); const TypeLong* this_type = this->type()->is_long(); Node* this_changed = NULL; - if (can_reshape) { + if (igvn != NULL) { // Do NOT remove this node's type assertion until no more loop ops can happen. if (phase->C->post_loop_opts_phase()) { const TypeInt* in_type = phase->type(in(1))->isa_int(); @@ -334,10 +350,9 @@ Node *ConvI2LNode::Ideal(PhaseGVN *phase, bool can_reshape) { Node* z = in(1); int op = z->Opcode(); if (op == Op_AddI || op == Op_SubI) { - if (!can_reshape) { - // Postpone this optimization to after parsing because with deep AddNode - // chains a large amount of dead ConvI2L nodes might be created that are - // not removed during parsing. As a result, we might hit the node limit. + if (igvn == NULL) { + // Postpone this optimization to iterative GVN, where we can handle deep + // AddI chains without an exponential number of recursive Ideal() calls. phase->record_for_igvn(this); return this_changed; } @@ -399,11 +414,8 @@ Node *ConvI2LNode::Ideal(PhaseGVN *phase, bool can_reshape) { } assert(rxlo == (int)rxlo && rxhi == (int)rxhi, "x should not overflow"); assert(rylo == (int)rylo && ryhi == (int)ryhi, "y should not overflow"); - Node* cx = phase->C->constrained_convI2L(phase, x, TypeInt::make(rxlo, rxhi, widen), NULL); - Node *hook = new Node(1); - hook->init_req(0, cx); // Add a use to cx to prevent him from dying - Node* cy = phase->C->constrained_convI2L(phase, y, TypeInt::make(rylo, ryhi, widen), NULL); - hook->destruct(phase); + Node* cx = find_or_make_convI2L(igvn, x, TypeLong::make(rxlo, rxhi, widen)); + Node* cy = find_or_make_convI2L(igvn, y, TypeLong::make(rylo, ryhi, widen)); switch (op) { case Op_AddI: return new AddLNode(cx, cy); case Op_SubI: return new SubLNode(cx, cy); diff --git a/test/hotspot/jtreg/compiler/conversions/TestMoveConvI2LThroughAddIs.java b/test/hotspot/jtreg/compiler/conversions/TestMoveConvI2LThroughAddIs.java new file mode 100644 index 00000000000..f93677b3754 --- /dev/null +++ b/test/hotspot/jtreg/compiler/conversions/TestMoveConvI2LThroughAddIs.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.conversions; + +import java.util.Random; +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8254317 + * @requires vm.compiler2.enabled + * @summary Exercises the optimization that moves integer-to-long conversions + * upwards through different shapes of integer addition + * subgraphs. Contains three small functional tests and two stress + * tests that resulted in a compilation time and memory explosion + * before fixing bug 8254317. The stress tests run with -Xbatch to wait + * for C2, so that a timeout or an out-of-memory error is triggered if + * there was an explosion. These tests use a timeout of 30s to catch + * the explosion earlier. + * @library /test/lib / + * @run main/othervm + * compiler.conversions.TestMoveConvI2LThroughAddIs functional + * @run main/othervm/timeout=30 -Xbatch + * compiler.conversions.TestMoveConvI2LThroughAddIs stress1 + * @run main/othervm/timeout=30 -Xbatch + * compiler.conversions.TestMoveConvI2LThroughAddIs stress2 + */ + +public class TestMoveConvI2LThroughAddIs { + + // Number of repetitions of each test. Should be sufficiently large for the + // method under test to be compiled with C2. + static final int N = 100_000; + + // Chain-shaped functional test. + static long testChain(boolean cnd) { + int a = cnd ? 1 : 2; + int b = a + a; + int c = b + b; + int d = c + c; + return d; + } + + // Tree-shaped functional test. + static long testTree(boolean cnd) { + int a0 = cnd ? 1 : 2; + int a1 = cnd ? 1 : 2; + int a2 = cnd ? 1 : 2; + int a3 = cnd ? 1 : 2; + int a4 = cnd ? 1 : 2; + int a5 = cnd ? 1 : 2; + int a6 = cnd ? 1 : 2; + int a7 = cnd ? 1 : 2; + int b0 = a0 + a1; + int b1 = a2 + a3; + int b2 = a4 + a5; + int b3 = a6 + a7; + int c0 = b0 + b1; + int c1 = b2 + b3; + int d = c0 + c1; + return d; + } + + // DAG-shaped functional test. + static long testDAG(boolean cnd) { + int a0 = cnd ? 1 : 2; + int a1 = cnd ? 1 : 2; + int a2 = cnd ? 1 : 2; + int a3 = cnd ? 1 : 2; + int b0 = a0 + a1; + int b1 = a1 + a2; + int b2 = a2 + a3; + int c0 = b0 + b1; + int c1 = b1 + b2; + int d = c0 + c1; + return d; + } + + // Chain-shaped stress test. Before fixing bug 8254317, this test would + // result in an out-of-memory error after minutes running. + static long testStress1(boolean cnd) { + // C2 infers a finite, small value range for a. Note that there are + // different ways to achieve this, for example a might take the value of + // the induction variable in an outer counted loop. + int a = cnd ? 1 : 2; + // C2 fully unrolls this loop, creating a long chain of AddIs. + for (int i = 0; i < 28; i++) { + a = a + a; + } + // C2 places a ConvI2L at the end of the AddI chain. + return a; + } + + // DAG-shaped stress test. Before fixing bug 8254317, this test would result + // in an out-of-memory error after minutes running. + static long testStress2(boolean cnd) { + int a = cnd ? 1 : 2; + int b = a; + int c = a + a; + for (int i = 0; i < 20; i++) { + b = b + c; + c = b + c; + } + int d = b + c; + return d; + } + + public static void main(String[] args) { + // We use a random number generator to avoid constant propagation in C2 + // and produce a variable ("a" in the different tests) with a finite, + // small value range. + Random rnd = new Random(); + switch(args[0]) { + case "functional": + // Small, functional tests. + for (int i = 0; i < N; i++) { + boolean cnd = rnd.nextBoolean(); + Asserts.assertEQ(testChain(cnd), cnd ? 8L : 16L); + Asserts.assertEQ(testTree(cnd), cnd ? 8L : 16L); + Asserts.assertEQ(testDAG(cnd), cnd ? 8L : 16L); + } + break; + case "stress1": + // Chain-shaped stress test. + for (int i = 0; i < N; i++) { + boolean cnd = rnd.nextBoolean(); + Asserts.assertEQ(testStress1(cnd), + cnd ? 268435456L : 536870912L); + } + break; + case "stress2": + // DAG-shaped stress test. + for (int i = 0; i < N; i++) { + boolean cnd = rnd.nextBoolean(); + Asserts.assertEQ(testStress2(cnd), + cnd ? 701408733L : 1402817466L); + } + break; + default: + System.out.println("invalid mode"); + } + } +}