8231412: C2: InitializeNode::detect_init_independence() bails out on simple IR shapes
Avoids early bailout of capturing a field store to remove unnecessary zeroing in simple methods containing only non-escaping objects. Reviewed-by: roland, thartmann
This commit is contained in:
parent
13b4952f11
commit
465f4c1200
@ -3534,37 +3534,51 @@ intptr_t InitializeNode::get_store_offset(Node* st, PhaseTransform* phase) {
|
|||||||
// within the initialization without creating a vicious cycle, such as:
|
// within the initialization without creating a vicious cycle, such as:
|
||||||
// { Foo p = new Foo(); p.next = p; }
|
// { Foo p = new Foo(); p.next = p; }
|
||||||
// True for constants and parameters and small combinations thereof.
|
// True for constants and parameters and small combinations thereof.
|
||||||
bool InitializeNode::detect_init_independence(Node* n, int& count) {
|
bool InitializeNode::detect_init_independence(Node* value, PhaseGVN* phase) {
|
||||||
if (n == NULL) return true; // (can this really happen?)
|
ResourceMark rm;
|
||||||
if (n->is_Proj()) n = n->in(0);
|
Unique_Node_List worklist;
|
||||||
if (n == this) return false; // found a cycle
|
worklist.push(value);
|
||||||
if (n->is_Con()) return true;
|
|
||||||
if (n->is_Start()) return true; // params, etc., are OK
|
|
||||||
if (n->is_Root()) return true; // even better
|
|
||||||
|
|
||||||
Node* ctl = n->in(0);
|
uint complexity_limit = 20;
|
||||||
if (ctl != NULL && !ctl->is_top()) {
|
for (uint j = 0; j < worklist.size(); j++) {
|
||||||
if (ctl->is_Proj()) ctl = ctl->in(0);
|
if (j >= complexity_limit) {
|
||||||
if (ctl == this) return false;
|
return false; // Bail out if processed too many nodes
|
||||||
|
}
|
||||||
|
|
||||||
// If we already know that the enclosing memory op is pinned right after
|
Node* n = worklist.at(j);
|
||||||
// the init, then any control flow that the store has picked up
|
if (n == NULL) continue; // (can this really happen?)
|
||||||
// must have preceded the init, or else be equal to the init.
|
if (n->is_Proj()) n = n->in(0);
|
||||||
// Even after loop optimizations (which might change control edges)
|
if (n == this) return false; // found a cycle
|
||||||
// a store is never pinned *before* the availability of its inputs.
|
if (n->is_Con()) continue;
|
||||||
if (!MemNode::all_controls_dominate(n, this))
|
if (n->is_Start()) continue; // params, etc., are OK
|
||||||
return false; // failed to prove a good control
|
if (n->is_Root()) continue; // even better
|
||||||
}
|
|
||||||
|
|
||||||
// Check data edges for possible dependencies on 'this'.
|
// There cannot be any dependency if 'n' is a CFG node that dominates the current allocation
|
||||||
if ((count += 1) > 20) return false; // complexity limit
|
if (n->is_CFG() && phase->is_dominator(n, allocation())) {
|
||||||
for (uint i = 1; i < n->req(); i++) {
|
continue;
|
||||||
Node* m = n->in(i);
|
}
|
||||||
if (m == NULL || m == n || m->is_top()) continue;
|
|
||||||
uint first_i = n->find_edge(m);
|
Node* ctl = n->in(0);
|
||||||
if (i != first_i) continue; // process duplicate edge just once
|
if (ctl != NULL && !ctl->is_top()) {
|
||||||
if (!detect_init_independence(m, count)) {
|
if (ctl->is_Proj()) ctl = ctl->in(0);
|
||||||
return false;
|
if (ctl == this) return false;
|
||||||
|
|
||||||
|
// If we already know that the enclosing memory op is pinned right after
|
||||||
|
// the init, then any control flow that the store has picked up
|
||||||
|
// must have preceded the init, or else be equal to the init.
|
||||||
|
// Even after loop optimizations (which might change control edges)
|
||||||
|
// a store is never pinned *before* the availability of its inputs.
|
||||||
|
if (!MemNode::all_controls_dominate(n, this))
|
||||||
|
return false; // failed to prove a good control
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check data edges for possible dependencies on 'this'.
|
||||||
|
for (uint i = 1; i < n->req(); i++) {
|
||||||
|
Node* m = n->in(i);
|
||||||
|
if (m == NULL || m == n || m->is_top()) continue;
|
||||||
|
|
||||||
|
// Only process data inputs once
|
||||||
|
worklist.push(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3575,7 +3589,7 @@ bool InitializeNode::detect_init_independence(Node* n, int& count) {
|
|||||||
// an initialization. Returns zero if a check fails.
|
// an initialization. Returns zero if a check fails.
|
||||||
// On success, returns the (constant) offset to which the store applies,
|
// On success, returns the (constant) offset to which the store applies,
|
||||||
// within the initialized memory.
|
// within the initialized memory.
|
||||||
intptr_t InitializeNode::can_capture_store(StoreNode* st, PhaseTransform* phase, bool can_reshape) {
|
intptr_t InitializeNode::can_capture_store(StoreNode* st, PhaseGVN* phase, bool can_reshape) {
|
||||||
const int FAIL = 0;
|
const int FAIL = 0;
|
||||||
if (st->req() != MemNode::ValueIn + 1)
|
if (st->req() != MemNode::ValueIn + 1)
|
||||||
return FAIL; // an inscrutable StoreNode (card mark?)
|
return FAIL; // an inscrutable StoreNode (card mark?)
|
||||||
@ -3597,8 +3611,8 @@ intptr_t InitializeNode::can_capture_store(StoreNode* st, PhaseTransform* phase,
|
|||||||
return FAIL; // mismatched access
|
return FAIL; // mismatched access
|
||||||
}
|
}
|
||||||
Node* val = st->in(MemNode::ValueIn);
|
Node* val = st->in(MemNode::ValueIn);
|
||||||
int complexity_count = 0;
|
|
||||||
if (!detect_init_independence(val, complexity_count))
|
if (!detect_init_independence(val, phase))
|
||||||
return FAIL; // stored value must be 'simple enough'
|
return FAIL; // stored value must be 'simple enough'
|
||||||
|
|
||||||
// The Store can be captured only if nothing after the allocation
|
// The Store can be captured only if nothing after the allocation
|
||||||
@ -3796,7 +3810,7 @@ Node* InitializeNode::make_raw_address(intptr_t offset,
|
|||||||
// rawstore1 rawstore2)
|
// rawstore1 rawstore2)
|
||||||
//
|
//
|
||||||
Node* InitializeNode::capture_store(StoreNode* st, intptr_t start,
|
Node* InitializeNode::capture_store(StoreNode* st, intptr_t start,
|
||||||
PhaseTransform* phase, bool can_reshape) {
|
PhaseGVN* phase, bool can_reshape) {
|
||||||
assert(stores_are_sane(phase), "");
|
assert(stores_are_sane(phase), "");
|
||||||
|
|
||||||
if (start < 0) return NULL;
|
if (start < 0) return NULL;
|
||||||
|
@ -1387,11 +1387,11 @@ public:
|
|||||||
|
|
||||||
// See if this store can be captured; return offset where it initializes.
|
// See if this store can be captured; return offset where it initializes.
|
||||||
// Return 0 if the store cannot be moved (any sort of problem).
|
// Return 0 if the store cannot be moved (any sort of problem).
|
||||||
intptr_t can_capture_store(StoreNode* st, PhaseTransform* phase, bool can_reshape);
|
intptr_t can_capture_store(StoreNode* st, PhaseGVN* phase, bool can_reshape);
|
||||||
|
|
||||||
// Capture another store; reformat it to write my internal raw memory.
|
// Capture another store; reformat it to write my internal raw memory.
|
||||||
// Return the captured copy, else NULL if there is some sort of problem.
|
// Return the captured copy, else NULL if there is some sort of problem.
|
||||||
Node* capture_store(StoreNode* st, intptr_t start, PhaseTransform* phase, bool can_reshape);
|
Node* capture_store(StoreNode* st, intptr_t start, PhaseGVN* phase, bool can_reshape);
|
||||||
|
|
||||||
// Find captured store which corresponds to the range [start..start+size).
|
// Find captured store which corresponds to the range [start..start+size).
|
||||||
// Return my own memory projection (meaning the initial zero bits)
|
// Return my own memory projection (meaning the initial zero bits)
|
||||||
@ -1414,7 +1414,7 @@ public:
|
|||||||
|
|
||||||
Node* make_raw_address(intptr_t offset, PhaseTransform* phase);
|
Node* make_raw_address(intptr_t offset, PhaseTransform* phase);
|
||||||
|
|
||||||
bool detect_init_independence(Node* n, int& count);
|
bool detect_init_independence(Node* value, PhaseGVN* phase);
|
||||||
|
|
||||||
void coalesce_subword_stores(intptr_t header_size, Node* size_in_bytes,
|
void coalesce_subword_stores(intptr_t header_size, Node* size_in_bytes,
|
||||||
PhaseGVN* phase);
|
PhaseGVN* phase);
|
||||||
|
@ -899,7 +899,7 @@ bool PhaseGVN::is_dominator_helper(Node *d, Node *n, bool linear_only) {
|
|||||||
while (d != n) {
|
while (d != n) {
|
||||||
n = IfNode::up_one_dom(n, linear_only);
|
n = IfNode::up_one_dom(n, linear_only);
|
||||||
i++;
|
i++;
|
||||||
if (n == NULL || i >= 10) {
|
if (n == NULL || i >= 100) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8231412
|
||||||
|
* @summary The enhancement eliminates all allocations in the loop body of test() due to an improved field zeroing elimination dominance check.
|
||||||
|
* @run main/othervm -XX:-TieredCompilation -XX:CompileCommand=compileonly,compiler.escapeAnalysis.TestEliminateAllocation::test
|
||||||
|
* compiler.escapeAnalysis.TestEliminateAllocation
|
||||||
|
*/
|
||||||
|
|
||||||
|
package compiler.escapeAnalysis;
|
||||||
|
|
||||||
|
public class TestEliminateAllocation {
|
||||||
|
|
||||||
|
public static int a = 20;
|
||||||
|
public static int b = 30;
|
||||||
|
public static int c = 40;
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The resulting IR for the loop body contains 2 allocations, one Wrapper and an int array
|
||||||
|
* The array field store in the Wrapper object 'wrapper.arr = arr' cannot be capturued due to an early bail out.
|
||||||
|
* Therefore, the initial value of wrapper.arr is null.
|
||||||
|
* As a result, the escape analysis marks the array allocation as not scalar replaceable:
|
||||||
|
* 'wrapper.arr' which is null is merged with the int array object in the assignment 'wrapper.arr = arr'.
|
||||||
|
* Both null and the int array are treated as different objects. As a result the array allocation cannot be eliminated.
|
||||||
|
*
|
||||||
|
* The new enhancement does not bail out early anymore and therefore escape analysis does not mark it as
|
||||||
|
* not scalar replaceable. This results in elimination of all allocations in this method.
|
||||||
|
*/
|
||||||
|
do {
|
||||||
|
int[] arr = new int[] { a / b / c };
|
||||||
|
Wrapper wrapper = new Wrapper();
|
||||||
|
wrapper.setArr(arr);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
while (i < 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] strArr) {
|
||||||
|
TestEliminateAllocation _instance = new TestEliminateAllocation();
|
||||||
|
for (int i = 0; i < 10_000; i++ ) {
|
||||||
|
_instance.test();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Wrapper {
|
||||||
|
int[] arr;
|
||||||
|
void setArr(int... many) { arr = many; }
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user