Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion src/hotspot/cpu/x86/peephole_x86_64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,29 @@ bool Peephole::lea_remove_redundant(Block* block, int block_index, PhaseCFG* cfg
}

// We now have verified that the decode is redundant and can be removed with a peephole.

// Before rewiring, check that the new base won't create a register conflict.
// The new base (lea_address) might share a register with a dependant_lea.
// If the dependant_lea overwrites that register and the base is still live
// (used in successor blocks), this creates a conflict detected by verify_good_schedule.
Node* new_base = lea_derived_oop->in(AddPNode::Address);
OptoReg::Name new_base_reg = ra_->get_reg_first(new_base);
if (OptoReg::is_valid(new_base_reg)) {
for (DUIterator_Fast imax, i = decode->fast_outs(imax); i < imax; i++) {
Node* dependant_lea = decode->fast_out(i);
if (dependant_lea->is_Mach() && dependant_lea->as_Mach()->ideal_Opcode() == Op_AddP) {
OptoReg::Name lea_reg = ra_->get_reg_first(dependant_lea);
OptoReg::Name lea_reg2 = ra_->get_reg_second(dependant_lea);
// If the new base register overlaps with any register slot of
// the dependant lea's output, the rewiring would create a conflict
// when the lea overwrites the base pointer's register.
if (new_base_reg == lea_reg || new_base_reg == lea_reg2) {
return false;
}
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I experimented with this so I could understand it better, and something seems to be off. The above code disables the optimization even if the only dependant_lea is the lone lea_derived_oop, in which case it would seem to be OK because we aren't replacing the base with a "new base".
Also, I tried disabling the above change, and the test below still passes.

// Remove the projection
block->find_remove(proj);
cfg_->map_node_to_block(proj, nullptr);
Expand All @@ -346,7 +369,7 @@ bool Peephole::lea_remove_redundant(Block* block, int block_index, PhaseCFG* cfg
for (DUIterator_Fast imax, i = decode->fast_outs(imax); i < imax; i++) {
Node* dependant_lea = decode->fast_out(i);
if (dependant_lea->is_Mach() && dependant_lea->as_Mach()->ideal_Opcode() == Op_AddP) {
dependant_lea->set_req(AddPNode::Base, lea_derived_oop->in(AddPNode::Address));
dependant_lea->set_req(AddPNode::Base, new_base);
Copy link
Copy Markdown
Contributor

@mhaessig mhaessig Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary that the base input of a leaP... is non-null? Given that the leaP... does not appear in an oop map, can we make the base input nullptr, so we don't need to worry about register conflict anymore?

You are absolutely right, @merykitty. The AddPNode::Base is only for the oop map and does not even get matched. This peephole is for cleaning up decodes that nevver get used in an oop map so rewiring is not even necessary. I wish I had thought of this a year ago...

The following does the trick (inspired by AddPNode::make_off_heap since using nullptr does not work):

Suggested change
dependant_lea->set_req(AddPNode::Base, new_base);
dependant_lea->set_req(AddPNode::Base, Compile::current()->top());

By not introducing a dependency, there will also not be a conflict.

// This deleted something in the out array, hence adjust i, imax.
--i;
--imax;
Expand Down
17 changes: 11 additions & 6 deletions test/hotspot/jtreg/compiler/codegen/TestRedundantLea.java
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,11 @@ class StoreNTest {
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=2"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "false"})
// Test that the peephole worked for leaPCompressedOopOffset
@IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL},
// Test that the peephole worked for leaPCompressedOopOffset.
// The peephole may bail out if the new base pointer's register
// would conflict with a dependent lea's output register, so we
// allow up to the non-peephole count.
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "<=2"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "true"})
// Test that the peephole removes a spill.
Expand Down Expand Up @@ -340,8 +343,9 @@ public void testPhiSpill() {
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=2"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "false"})
// Test that the peephole worked for leaPCompressedOopOffset
@IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL},
// Test that the peephole worked for leaPCompressedOopOffset.
// The peephole may bail out due to register conflicts.
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "<=2"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "true"})
public void testNoAlloc() {
Expand All @@ -357,8 +361,9 @@ public void testNoAlloc() {
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "=1"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "false"})
// Test that the peephole worked for leaPCompressedOopOffset
@IR(failOn = {IRNode.DECODE_HEAP_OOP_NOT_NULL},
// Test that the peephole worked for leaPCompressedOopOffset.
// The peephole may bail out due to register conflicts.
@IR(counts = {IRNode.DECODE_HEAP_OOP_NOT_NULL, "<=1"},
phase = {CompilePhase.FINAL_CODE},
applyIf = {"OptoPeephole", "true"})
public void testNoAllocSameArray() {
Expand Down