8345067: C2: enable implicit null checks for ZGC reads
Reviewed-by: aboldtch, kvn, epeter
This commit is contained in:
parent
6c616c71ec
commit
91f12600d2
@ -3921,6 +3921,10 @@ ins_attrib ins_alignment(4); // Required alignment attribute (must
|
|||||||
// compute_padding() function must be
|
// compute_padding() function must be
|
||||||
// provided for the instruction
|
// provided for the instruction
|
||||||
|
|
||||||
|
// Whether this node is expanded during code emission into a sequence of
|
||||||
|
// instructions and the first instruction can perform an implicit null check.
|
||||||
|
ins_attrib ins_is_late_expanded_null_check_candidate(false);
|
||||||
|
|
||||||
//----------OPERANDS-----------------------------------------------------------
|
//----------OPERANDS-----------------------------------------------------------
|
||||||
// Operand definitions must precede instruction definitions for correct parsing
|
// Operand definitions must precede instruction definitions for correct parsing
|
||||||
// in the ADLC because operands constitute user defined types which are used in
|
// in the ADLC because operands constitute user defined types which are used in
|
||||||
|
@ -106,6 +106,13 @@ instruct zLoadP(iRegPNoSp dst, memory8 mem, rFlagsReg cr)
|
|||||||
match(Set dst (LoadP mem));
|
match(Set dst (LoadP mem));
|
||||||
predicate(UseZGC && !needs_acquiring_load(n) && n->as_Load()->barrier_data() != 0);
|
predicate(UseZGC && !needs_acquiring_load(n) && n->as_Load()->barrier_data() != 0);
|
||||||
effect(TEMP dst, KILL cr);
|
effect(TEMP dst, KILL cr);
|
||||||
|
// The main load is a candidate to implement implicit null checks, as long as
|
||||||
|
// legitimize_address() does not require a preceding lea instruction to
|
||||||
|
// materialize the memory operand. The absence of a preceding lea instruction
|
||||||
|
// is guaranteed for immLoffset8 memory operands, because these do not lead to
|
||||||
|
// out-of-range offsets (see definition of immLoffset8). Fortunately,
|
||||||
|
// immLoffset8 memory operands are the most common ones in practice.
|
||||||
|
ins_is_late_expanded_null_check_candidate(opnd_array(1)->opcode() == INDOFFL8);
|
||||||
|
|
||||||
ins_cost(4 * INSN_COST);
|
ins_cost(4 * INSN_COST);
|
||||||
|
|
||||||
@ -117,7 +124,11 @@ instruct zLoadP(iRegPNoSp dst, memory8 mem, rFlagsReg cr)
|
|||||||
// Fix up any out-of-range offsets.
|
// Fix up any out-of-range offsets.
|
||||||
assert_different_registers(rscratch2, as_Register($mem$$base));
|
assert_different_registers(rscratch2, as_Register($mem$$base));
|
||||||
assert_different_registers(rscratch2, $dst$$Register);
|
assert_different_registers(rscratch2, $dst$$Register);
|
||||||
ref_addr = __ legitimize_address(ref_addr, 8, rscratch2);
|
int size = 8;
|
||||||
|
assert(!this->is_late_expanded_null_check_candidate() ||
|
||||||
|
!MacroAssembler::legitimize_address_requires_lea(ref_addr, size),
|
||||||
|
"an instruction that can be used for implicit null checking should emit the candidate memory access first");
|
||||||
|
ref_addr = __ legitimize_address(ref_addr, size, rscratch2);
|
||||||
}
|
}
|
||||||
__ ldr($dst$$Register, ref_addr);
|
__ ldr($dst$$Register, ref_addr);
|
||||||
z_load_barrier(masm, this, ref_addr, $dst$$Register, rscratch1);
|
z_load_barrier(masm, this, ref_addr, $dst$$Register, rscratch1);
|
||||||
|
@ -129,16 +129,21 @@ class MacroAssembler: public Assembler {
|
|||||||
a.lea(this, r);
|
a.lea(this, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether materializing the given address for a LDR/STR requires an
|
||||||
|
// additional lea instruction.
|
||||||
|
static bool legitimize_address_requires_lea(const Address &a, int size) {
|
||||||
|
return a.getMode() == Address::base_plus_offset &&
|
||||||
|
!Address::offset_ok_for_immed(a.offset(), exact_log2(size));
|
||||||
|
}
|
||||||
|
|
||||||
/* Sometimes we get misaligned loads and stores, usually from Unsafe
|
/* Sometimes we get misaligned loads and stores, usually from Unsafe
|
||||||
accesses, and these can exceed the offset range. */
|
accesses, and these can exceed the offset range. */
|
||||||
Address legitimize_address(const Address &a, int size, Register scratch) {
|
Address legitimize_address(const Address &a, int size, Register scratch) {
|
||||||
if (a.getMode() == Address::base_plus_offset) {
|
if (legitimize_address_requires_lea(a, size)) {
|
||||||
if (! Address::offset_ok_for_immed(a.offset(), exact_log2(size))) {
|
block_comment("legitimize_address {");
|
||||||
block_comment("legitimize_address {");
|
lea(scratch, a);
|
||||||
lea(scratch, a);
|
block_comment("} legitimize_address");
|
||||||
block_comment("} legitimize_address");
|
return Address(scratch);
|
||||||
return Address(scratch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,7 @@ instruct zLoadP(iRegPdst dst, memoryAlg4 mem, flagsRegCR0 cr0)
|
|||||||
%{
|
%{
|
||||||
match(Set dst (LoadP mem));
|
match(Set dst (LoadP mem));
|
||||||
effect(TEMP_DEF dst, KILL cr0);
|
effect(TEMP_DEF dst, KILL cr0);
|
||||||
|
ins_is_late_expanded_null_check_candidate(true);
|
||||||
ins_cost(MEMORY_REF_COST);
|
ins_cost(MEMORY_REF_COST);
|
||||||
|
|
||||||
predicate((UseZGC && n->as_Load()->barrier_data() != 0)
|
predicate((UseZGC && n->as_Load()->barrier_data() != 0)
|
||||||
@ -160,6 +161,7 @@ instruct zLoadP_acq(iRegPdst dst, memoryAlg4 mem, flagsRegCR0 cr0)
|
|||||||
%{
|
%{
|
||||||
match(Set dst (LoadP mem));
|
match(Set dst (LoadP mem));
|
||||||
effect(TEMP_DEF dst, KILL cr0);
|
effect(TEMP_DEF dst, KILL cr0);
|
||||||
|
ins_is_late_expanded_null_check_candidate(true);
|
||||||
ins_cost(3 * MEMORY_REF_COST);
|
ins_cost(3 * MEMORY_REF_COST);
|
||||||
|
|
||||||
// Predicate on instruction order is implicitly present due to the predicate of the cheaper zLoadP operation
|
// Predicate on instruction order is implicitly present due to the predicate of the cheaper zLoadP operation
|
||||||
|
@ -4036,6 +4036,10 @@ ins_attrib ins_field_cbuf_insts_offset(-1);
|
|||||||
ins_attrib ins_field_load_ic_hi_node(0);
|
ins_attrib ins_field_load_ic_hi_node(0);
|
||||||
ins_attrib ins_field_load_ic_node(0);
|
ins_attrib ins_field_load_ic_node(0);
|
||||||
|
|
||||||
|
// Whether this node is expanded during code emission into a sequence of
|
||||||
|
// instructions and the first instruction can perform an implicit null check.
|
||||||
|
ins_attrib ins_is_late_expanded_null_check_candidate(false);
|
||||||
|
|
||||||
//----------OPERANDS-----------------------------------------------------------
|
//----------OPERANDS-----------------------------------------------------------
|
||||||
// Operand definitions must precede instruction definitions for correct
|
// Operand definitions must precede instruction definitions for correct
|
||||||
// parsing in the ADLC because operands constitute user defined types
|
// parsing in the ADLC because operands constitute user defined types
|
||||||
|
@ -96,6 +96,7 @@ instruct zLoadP(iRegPNoSp dst, memory mem, iRegPNoSp tmp, rFlagsReg cr)
|
|||||||
match(Set dst (LoadP mem));
|
match(Set dst (LoadP mem));
|
||||||
predicate(UseZGC && n->as_Load()->barrier_data() != 0);
|
predicate(UseZGC && n->as_Load()->barrier_data() != 0);
|
||||||
effect(TEMP dst, TEMP tmp, KILL cr);
|
effect(TEMP dst, TEMP tmp, KILL cr);
|
||||||
|
ins_is_late_expanded_null_check_candidate(true);
|
||||||
|
|
||||||
ins_cost(4 * DEFAULT_COST);
|
ins_cost(4 * DEFAULT_COST);
|
||||||
|
|
||||||
|
@ -2619,6 +2619,10 @@ ins_attrib ins_alignment(4); // Required alignment attribute (must
|
|||||||
// compute_padding() function must be
|
// compute_padding() function must be
|
||||||
// provided for the instruction
|
// provided for the instruction
|
||||||
|
|
||||||
|
// Whether this node is expanded during code emission into a sequence of
|
||||||
|
// instructions and the first instruction can perform an implicit null check.
|
||||||
|
ins_attrib ins_is_late_expanded_null_check_candidate(false);
|
||||||
|
|
||||||
//----------OPERANDS-----------------------------------------------------------
|
//----------OPERANDS-----------------------------------------------------------
|
||||||
// Operand definitions must precede instruction definitions for correct parsing
|
// Operand definitions must precede instruction definitions for correct parsing
|
||||||
// in the ADLC because operands constitute user defined types which are used in
|
// in the ADLC because operands constitute user defined types which are used in
|
||||||
|
@ -118,6 +118,10 @@ instruct zLoadP(rRegP dst, memory mem, rFlagsReg cr)
|
|||||||
predicate(UseZGC && n->as_Load()->barrier_data() != 0);
|
predicate(UseZGC && n->as_Load()->barrier_data() != 0);
|
||||||
match(Set dst (LoadP mem));
|
match(Set dst (LoadP mem));
|
||||||
effect(TEMP dst, KILL cr);
|
effect(TEMP dst, KILL cr);
|
||||||
|
// The main load is a candidate to implement implicit null checks. The
|
||||||
|
// barrier's slow path includes an identical reload, which does not need to be
|
||||||
|
// registered in the exception table because it is dominated by the main one.
|
||||||
|
ins_is_late_expanded_null_check_candidate(true);
|
||||||
|
|
||||||
ins_cost(125);
|
ins_cost(125);
|
||||||
|
|
||||||
|
@ -2055,6 +2055,10 @@ ins_attrib ins_alignment(1); // Required alignment attribute (must
|
|||||||
// compute_padding() function must be
|
// compute_padding() function must be
|
||||||
// provided for the instruction
|
// provided for the instruction
|
||||||
|
|
||||||
|
// Whether this node is expanded during code emission into a sequence of
|
||||||
|
// instructions and the first instruction can perform an implicit null check.
|
||||||
|
ins_attrib ins_is_late_expanded_null_check_candidate(false);
|
||||||
|
|
||||||
//----------OPERANDS-----------------------------------------------------------
|
//----------OPERANDS-----------------------------------------------------------
|
||||||
// Operand definitions must precede instruction definitions for correct parsing
|
// Operand definitions must precede instruction definitions for correct parsing
|
||||||
// in the ADLC because operands constitute user defined types which are used in
|
// in the ADLC because operands constitute user defined types which are used in
|
||||||
|
@ -1626,6 +1626,8 @@ void ArchDesc::declareClasses(FILE *fp) {
|
|||||||
while (attr != nullptr) {
|
while (attr != nullptr) {
|
||||||
if (strcmp (attr->_ident, "ins_is_TrapBasedCheckNode") == 0) {
|
if (strcmp (attr->_ident, "ins_is_TrapBasedCheckNode") == 0) {
|
||||||
fprintf(fp, " virtual bool is_TrapBasedCheckNode() const { return %s; }\n", attr->_val);
|
fprintf(fp, " virtual bool is_TrapBasedCheckNode() const { return %s; }\n", attr->_val);
|
||||||
|
} else if (strcmp (attr->_ident, "ins_is_late_expanded_null_check_candidate") == 0) {
|
||||||
|
fprintf(fp, " virtual bool is_late_expanded_null_check_candidate() const { return %s; }\n", attr->_val);
|
||||||
} else if (strcmp (attr->_ident, "ins_cost") != 0 &&
|
} else if (strcmp (attr->_ident, "ins_cost") != 0 &&
|
||||||
strncmp(attr->_ident, "ins_field_", 10) != 0 &&
|
strncmp(attr->_ident, "ins_field_", 10) != 0 &&
|
||||||
// Must match function in node.hpp: return type bool, no prefix "ins_".
|
// Must match function in node.hpp: return type bool, no prefix "ins_".
|
||||||
|
@ -464,6 +464,14 @@ class PhaseCFG : public Phase {
|
|||||||
Node* catch_cleanup_find_cloned_def(Block* use_blk, Node* def, Block* def_blk, int n_clone_idx);
|
Node* catch_cleanup_find_cloned_def(Block* use_blk, Node* def, Block* def_blk, int n_clone_idx);
|
||||||
void catch_cleanup_inter_block(Node *use, Block *use_blk, Node *def, Block *def_blk, int n_clone_idx);
|
void catch_cleanup_inter_block(Node *use, Block *use_blk, Node *def, Block *def_blk, int n_clone_idx);
|
||||||
|
|
||||||
|
// Ensure that n happens at b or above, i.e. at a block that dominates b.
|
||||||
|
// We expect n to be an orphan node without further inputs.
|
||||||
|
void ensure_node_is_at_block_or_above(Node* n, Block* b);
|
||||||
|
|
||||||
|
// Move node n from its current placement into the end of block b.
|
||||||
|
// Move also outgoing Mach projections.
|
||||||
|
void move_node_and_its_projections_to_block(Node* n, Block* b);
|
||||||
|
|
||||||
// Detect implicit-null-check opportunities. Basically, find null checks
|
// Detect implicit-null-check opportunities. Basically, find null checks
|
||||||
// with suitable memory ops nearby. Use the memory op to do the null check.
|
// with suitable memory ops nearby. Use the memory op to do the null check.
|
||||||
// I can generate a memory op if there is not one nearby.
|
// I can generate a memory op if there is not one nearby.
|
||||||
|
@ -76,6 +76,36 @@ static bool needs_explicit_null_check_for_read(Node *val) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PhaseCFG::move_node_and_its_projections_to_block(Node* n, Block* b) {
|
||||||
|
assert(!is_CFG(n), "cannot move CFG node");
|
||||||
|
Block* old = get_block_for_node(n);
|
||||||
|
old->find_remove(n);
|
||||||
|
b->add_inst(n);
|
||||||
|
map_node_to_block(n, b);
|
||||||
|
// Check for Mach projections that also need to be moved.
|
||||||
|
for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) {
|
||||||
|
Node* out = n->fast_out(i);
|
||||||
|
if (!out->is_MachProj()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
assert(!n->is_MachProj(), "nested projections are not allowed");
|
||||||
|
move_node_and_its_projections_to_block(out, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhaseCFG::ensure_node_is_at_block_or_above(Node* n, Block* b) {
|
||||||
|
assert(!is_CFG(n), "cannot move CFG node");
|
||||||
|
Block* current = get_block_for_node(n);
|
||||||
|
if (current->dominates(b)) {
|
||||||
|
return; // n is already placed above b, do nothing.
|
||||||
|
}
|
||||||
|
// We only expect nodes without further inputs, like MachTemp or load Base.
|
||||||
|
assert(n->req() == 0 || (n->req() == 1 && n->in(0) == (Node*)C->root()),
|
||||||
|
"need for recursive hoisting not expected");
|
||||||
|
assert(b->dominates(current), "precondition: can only move n to b if b dominates n");
|
||||||
|
move_node_and_its_projections_to_block(n, b);
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------implicit_null_check----------------------------
|
//------------------------------implicit_null_check----------------------------
|
||||||
// Detect implicit-null-check opportunities. Basically, find null checks
|
// Detect implicit-null-check opportunities. Basically, find null checks
|
||||||
// with suitable memory ops nearby. Use the memory op to do the null check.
|
// with suitable memory ops nearby. Use the memory op to do the null check.
|
||||||
@ -160,12 +190,14 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo
|
|||||||
Node *m = val->out(i);
|
Node *m = val->out(i);
|
||||||
if( !m->is_Mach() ) continue;
|
if( !m->is_Mach() ) continue;
|
||||||
MachNode *mach = m->as_Mach();
|
MachNode *mach = m->as_Mach();
|
||||||
if (mach->barrier_data() != 0) {
|
if (mach->barrier_data() != 0 &&
|
||||||
|
!mach->is_late_expanded_null_check_candidate()) {
|
||||||
// Using memory accesses with barriers to perform implicit null checks is
|
// Using memory accesses with barriers to perform implicit null checks is
|
||||||
// not supported. These operations might expand into multiple assembly
|
// only supported if these are explicit marked as emitting a candidate
|
||||||
// instructions during code emission, including new memory accesses (e.g.
|
// memory access instruction at their initial address. If not marked as
|
||||||
// in G1's pre-barrier), which would invalidate the implicit null
|
// such, barrier-tagged operations might expand into one or several memory
|
||||||
// exception table.
|
// access instructions located at arbitrary offsets from the initial
|
||||||
|
// address, which would invalidate the implicit null exception table.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
was_store = false;
|
was_store = false;
|
||||||
@ -321,6 +353,14 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo
|
|||||||
// Ignore DecodeN val which could be hoisted to where needed.
|
// Ignore DecodeN val which could be hoisted to where needed.
|
||||||
if( is_decoden ) continue;
|
if( is_decoden ) continue;
|
||||||
}
|
}
|
||||||
|
if (mach->in(j)->is_MachTemp()) {
|
||||||
|
assert(mach->in(j)->outcnt() == 1, "MachTemp nodes should not be shared");
|
||||||
|
// Ignore MachTemp inputs, they can be safely hoisted with the candidate.
|
||||||
|
// MachTemp nodes have no inputs themselves and are only used to reserve
|
||||||
|
// a scratch register for the implementation of the node (e.g. in
|
||||||
|
// late-expanded GC barriers).
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// Block of memory-op input
|
// Block of memory-op input
|
||||||
Block *inb = get_block_for_node(mach->in(j));
|
Block *inb = get_block_for_node(mach->in(j));
|
||||||
Block *b = block; // Start from nul check
|
Block *b = block; // Start from nul check
|
||||||
@ -388,38 +428,24 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo
|
|||||||
// Hoist it up to the end of the test block together with its inputs if they exist.
|
// Hoist it up to the end of the test block together with its inputs if they exist.
|
||||||
for (uint i = 2; i < val->req(); i++) {
|
for (uint i = 2; i < val->req(); i++) {
|
||||||
// DecodeN has 2 regular inputs + optional MachTemp or load Base inputs.
|
// DecodeN has 2 regular inputs + optional MachTemp or load Base inputs.
|
||||||
Node *temp = val->in(i);
|
// Inputs of val may already be early enough, but if not move them together with val.
|
||||||
Block *tempb = get_block_for_node(temp);
|
ensure_node_is_at_block_or_above(val->in(i), block);
|
||||||
if (!tempb->dominates(block)) {
|
|
||||||
assert(block->dominates(tempb), "sanity check: temp node placement");
|
|
||||||
// We only expect nodes without further inputs, like MachTemp or load Base.
|
|
||||||
assert(temp->req() == 0 || (temp->req() == 1 && temp->in(0) == (Node*)C->root()),
|
|
||||||
"need for recursive hoisting not expected");
|
|
||||||
tempb->find_remove(temp);
|
|
||||||
block->add_inst(temp);
|
|
||||||
map_node_to_block(temp, block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
valb->find_remove(val);
|
|
||||||
block->add_inst(val);
|
|
||||||
map_node_to_block(val, block);
|
|
||||||
// DecodeN on x86 may kill flags. Check for flag-killing projections
|
|
||||||
// that also need to be hoisted.
|
|
||||||
for (DUIterator_Fast jmax, j = val->fast_outs(jmax); j < jmax; j++) {
|
|
||||||
Node* n = val->fast_out(j);
|
|
||||||
if( n->is_MachProj() ) {
|
|
||||||
get_block_for_node(n)->find_remove(n);
|
|
||||||
block->add_inst(n);
|
|
||||||
map_node_to_block(n, block);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
move_node_and_its_projections_to_block(val, block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move any MachTemp inputs to the end of the test block.
|
||||||
|
for (uint i = 0; i < best->req(); i++) {
|
||||||
|
Node* n = best->in(i);
|
||||||
|
if (n == nullptr || !n->is_MachTemp()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ensure_node_is_at_block_or_above(n, block);
|
||||||
|
}
|
||||||
|
|
||||||
// Hoist the memory candidate up to the end of the test block.
|
// Hoist the memory candidate up to the end of the test block.
|
||||||
Block *old_block = get_block_for_node(best);
|
move_node_and_its_projections_to_block(best, block);
|
||||||
old_block->find_remove(best);
|
|
||||||
block->add_inst(best);
|
|
||||||
map_node_to_block(best, block);
|
|
||||||
|
|
||||||
// Move the control dependence if it is pinned to not-null block.
|
// Move the control dependence if it is pinned to not-null block.
|
||||||
// Don't change it in other cases: null or dominating control.
|
// Don't change it in other cases: null or dominating control.
|
||||||
@ -429,17 +455,6 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo
|
|||||||
best->set_req(0, proj->in(0)->in(0));
|
best->set_req(0, proj->in(0)->in(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for flag-killing projections that also need to be hoisted
|
|
||||||
// Should be DU safe because no edge updates.
|
|
||||||
for (DUIterator_Fast jmax, j = best->fast_outs(jmax); j < jmax; j++) {
|
|
||||||
Node* n = best->fast_out(j);
|
|
||||||
if( n->is_MachProj() ) {
|
|
||||||
get_block_for_node(n)->find_remove(n);
|
|
||||||
block->add_inst(n);
|
|
||||||
map_node_to_block(n, block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// proj==Op_True --> ne test; proj==Op_False --> eq test.
|
// proj==Op_True --> ne test; proj==Op_False --> eq test.
|
||||||
// One of two graph shapes got matched:
|
// One of two graph shapes got matched:
|
||||||
// (IfTrue (If (Bool NE (CmpP ptr null))))
|
// (IfTrue (If (Bool NE (CmpP ptr null))))
|
||||||
|
@ -386,6 +386,13 @@ public:
|
|||||||
|
|
||||||
// Returns true if this node is a check that can be implemented with a trap.
|
// Returns true if this node is a check that can be implemented with a trap.
|
||||||
virtual bool is_TrapBasedCheckNode() const { return false; }
|
virtual bool is_TrapBasedCheckNode() const { return false; }
|
||||||
|
|
||||||
|
// Whether this node is expanded during code emission into a sequence of
|
||||||
|
// instructions and the first instruction can perform an implicit null check.
|
||||||
|
virtual bool is_late_expanded_null_check_candidate() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void set_removed() { add_flag(Flag_is_removed_by_peephole); }
|
void set_removed() { add_flag(Flag_is_removed_by_peephole); }
|
||||||
bool get_removed() { return (flags() & Flag_is_removed_by_peephole) != 0; }
|
bool get_removed() { return (flags() & Flag_is_removed_by_peephole) != 0; }
|
||||||
|
|
||||||
|
@ -2015,8 +2015,10 @@ void PhaseOutput::FillExceptionTables(uint cnt, uint *call_returns, uint *inct_s
|
|||||||
|
|
||||||
// Handle implicit null exception table updates
|
// Handle implicit null exception table updates
|
||||||
if (n->is_MachNullCheck()) {
|
if (n->is_MachNullCheck()) {
|
||||||
assert(n->in(1)->as_Mach()->barrier_data() == 0,
|
MachNode* access = n->in(1)->as_Mach();
|
||||||
"Implicit null checks on memory accesses with barriers are not yet supported");
|
assert(access->barrier_data() == 0 ||
|
||||||
|
access->is_late_expanded_null_check_candidate(),
|
||||||
|
"Implicit null checks on memory accesses with barriers are only supported on nodes explicitly marked as null-check candidates");
|
||||||
uint block_num = block->non_connector_successor(0)->_pre_order;
|
uint block_num = block->non_connector_successor(0)->_pre_order;
|
||||||
_inc_table.append(inct_starts[inct_cnt++], blk_labels[block_num].loc_pos());
|
_inc_table.append(inct_starts[inct_cnt++], blk_labels[block_num].loc_pos());
|
||||||
continue;
|
continue;
|
||||||
|
@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025, 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.gcbarriers;
|
||||||
|
|
||||||
|
import compiler.lib.ir_framework.*;
|
||||||
|
import java.lang.invoke.VarHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import jdk.test.lib.Asserts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @summary Test that implicit null checks are generated as expected for
|
||||||
|
different GC memory accesses.
|
||||||
|
* @library /test/lib /
|
||||||
|
* @run driver compiler.gcbarriers.TestImplicitNullChecks
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
public class TestImplicitNullChecks {
|
||||||
|
|
||||||
|
static class Outer {
|
||||||
|
Object f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class OuterWithVolatileField {
|
||||||
|
volatile Object f;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final VarHandle fVarHandle;
|
||||||
|
static {
|
||||||
|
MethodHandles.Lookup l = MethodHandles.lookup();
|
||||||
|
try {
|
||||||
|
fVarHandle = l.findVarHandle(Outer.class, "f", Object.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
TestFramework.runWithFlags("-XX:CompileCommand=inline,java.lang.ref.*::*",
|
||||||
|
"-XX:-TieredCompilation");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
|
||||||
|
counts = {IRNode.NULL_CHECK, "1"},
|
||||||
|
phase = CompilePhase.FINAL_CODE)
|
||||||
|
static Object testLoad(Outer o) {
|
||||||
|
return o.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// On aarch64, volatile loads always use indirect memory operands, which
|
||||||
|
// leads to a pattern that cannot be exploited by the current C2 analysis.
|
||||||
|
// On PPC64, volatile loads are preceded by membar_volatile instructions,
|
||||||
|
// which also inhibits the current C2 analysis.
|
||||||
|
@IR(applyIfPlatformAnd = {"aarch64", "false", "ppc", "false"},
|
||||||
|
applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
|
||||||
|
counts = {IRNode.NULL_CHECK, "1"},
|
||||||
|
phase = CompilePhase.FINAL_CODE)
|
||||||
|
static Object testLoadVolatile(OuterWithVolatileField o) {
|
||||||
|
return o.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Run(test = {"testLoad",
|
||||||
|
"testLoadVolatile"},
|
||||||
|
mode = RunMode.STANDALONE)
|
||||||
|
static void runLoadTests() {
|
||||||
|
{
|
||||||
|
Outer o = new Outer();
|
||||||
|
// Trigger compilation with implicit null check.
|
||||||
|
for (int i = 0; i < 10_000; i++) {
|
||||||
|
testLoad(o);
|
||||||
|
}
|
||||||
|
// Trigger null pointer exception.
|
||||||
|
o = null;
|
||||||
|
boolean nullPointerException = false;
|
||||||
|
try {
|
||||||
|
testLoad(o);
|
||||||
|
} catch (NullPointerException e) { nullPointerException = true; }
|
||||||
|
Asserts.assertTrue(nullPointerException);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
OuterWithVolatileField o = new OuterWithVolatileField();
|
||||||
|
// Trigger compilation with implicit null check.
|
||||||
|
for (int i = 0; i < 10_000; i++) {
|
||||||
|
testLoadVolatile(o);
|
||||||
|
}
|
||||||
|
// Trigger null pointer exception.
|
||||||
|
o = null;
|
||||||
|
boolean nullPointerException = false;
|
||||||
|
try {
|
||||||
|
testLoadVolatile(o);
|
||||||
|
} catch (NullPointerException e) { nullPointerException = true; }
|
||||||
|
Asserts.assertTrue(nullPointerException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// G1 and ZGC stores cannot be currently used to implement implicit null
|
||||||
|
// checks, because they expand into multiple memory access instructions that
|
||||||
|
// are not necessarily located at the initial instruction start address.
|
||||||
|
@IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
|
||||||
|
failOn = IRNode.NULL_CHECK,
|
||||||
|
phase = CompilePhase.FINAL_CODE)
|
||||||
|
static void testStore(Outer o, Object o1) {
|
||||||
|
o.f = o1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Run(test = {"testStore"})
|
||||||
|
static void runStoreTests() {
|
||||||
|
{
|
||||||
|
Outer o = new Outer();
|
||||||
|
Object o1 = new Object();
|
||||||
|
testStore(o, o1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// G1 and ZGC compare-and-exchange operations cannot be currently used to
|
||||||
|
// implement implicit null checks, because they expand into multiple memory
|
||||||
|
// access instructions that are not necessarily located at the initial
|
||||||
|
// instruction start address. The same holds for testCompareAndSwap and
|
||||||
|
// testGetAndSet below.
|
||||||
|
@IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
|
||||||
|
failOn = IRNode.NULL_CHECK,
|
||||||
|
phase = CompilePhase.FINAL_CODE)
|
||||||
|
static Object testCompareAndExchange(Outer o, Object oldVal, Object newVal) {
|
||||||
|
return fVarHandle.compareAndExchange(o, oldVal, newVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
|
||||||
|
failOn = IRNode.NULL_CHECK,
|
||||||
|
phase = CompilePhase.FINAL_CODE)
|
||||||
|
static boolean testCompareAndSwap(Outer o, Object oldVal, Object newVal) {
|
||||||
|
return fVarHandle.compareAndSet(o, oldVal, newVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
|
||||||
|
failOn = IRNode.NULL_CHECK,
|
||||||
|
phase = CompilePhase.FINAL_CODE)
|
||||||
|
static Object testGetAndSet(Outer o, Object newVal) {
|
||||||
|
return fVarHandle.getAndSet(o, newVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Run(test = {"testCompareAndExchange",
|
||||||
|
"testCompareAndSwap",
|
||||||
|
"testGetAndSet"})
|
||||||
|
static void runAtomicTests() {
|
||||||
|
{
|
||||||
|
Outer o = new Outer();
|
||||||
|
Object oldVal = new Object();
|
||||||
|
Object newVal = new Object();
|
||||||
|
testCompareAndExchange(o, oldVal, newVal);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Outer o = new Outer();
|
||||||
|
Object oldVal = new Object();
|
||||||
|
Object newVal = new Object();
|
||||||
|
testCompareAndSwap(o, oldVal, newVal);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Outer o = new Outer();
|
||||||
|
Object oldVal = new Object();
|
||||||
|
Object newVal = new Object();
|
||||||
|
testGetAndSet(o, newVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
// G1 reference loads use indirect memory operands, which leads to a pattern
|
||||||
|
// that cannot be exploited by the current C2 analysis. The same holds for
|
||||||
|
// testLoadWeakReference.
|
||||||
|
@IR(applyIf = {"UseZGC", "true"},
|
||||||
|
counts = {IRNode.NULL_CHECK, "1"},
|
||||||
|
phase = CompilePhase.FINAL_CODE)
|
||||||
|
static Object testLoadSoftReference(SoftReference<Object> ref) {
|
||||||
|
return ref.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@IR(applyIf = {"UseZGC", "true"},
|
||||||
|
counts = {IRNode.NULL_CHECK, "1"},
|
||||||
|
phase = CompilePhase.FINAL_CODE)
|
||||||
|
static Object testLoadWeakReference(WeakReference<Object> ref) {
|
||||||
|
return ref.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Run(test = {"testLoadSoftReference",
|
||||||
|
"testLoadWeakReference"})
|
||||||
|
static void runReferenceTests() {
|
||||||
|
{
|
||||||
|
Object o1 = new Object();
|
||||||
|
SoftReference<Object> sref = new SoftReference<Object>(o1);
|
||||||
|
Object o2 = testLoadSoftReference(sref);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
Object o1 = new Object();
|
||||||
|
WeakReference<Object> wref = new WeakReference<Object>(o1);
|
||||||
|
Object o2 = testLoadWeakReference(wref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1499,6 +1499,11 @@ public class IRNode {
|
|||||||
trapNodes(NULL_ASSERT_TRAP, "null_assert");
|
trapNodes(NULL_ASSERT_TRAP, "null_assert");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final String NULL_CHECK = PREFIX + "NULL_CHECK" + POSTFIX;
|
||||||
|
static {
|
||||||
|
machOnlyNameRegex(NULL_CHECK, "NullCheck");
|
||||||
|
}
|
||||||
|
|
||||||
public static final String NULL_CHECK_TRAP = PREFIX + "NULL_CHECK_TRAP" + POSTFIX;
|
public static final String NULL_CHECK_TRAP = PREFIX + "NULL_CHECK_TRAP" + POSTFIX;
|
||||||
static {
|
static {
|
||||||
trapNodes(NULL_CHECK_TRAP, "null_check");
|
trapNodes(NULL_CHECK_TRAP, "null_check");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user