8330157: C2: Add a stress flag for bailouts

Reviewed-by: chagedorn, thartmann
This commit is contained in:
Daniel Skantz 2024-10-09 07:01:23 +00:00
parent d809bc0e21
commit d3f3c6a8cd
17 changed files with 201 additions and 34 deletions

View File

@ -398,7 +398,10 @@ PhaseCFG::PhaseCFG(Arena* arena, RootNode* root, Matcher& matcher)
Node *x = new GotoNode(nullptr);
x->init_req(0, x);
_goto = matcher.match_tree(x);
assert(_goto != nullptr, "");
assert(_goto != nullptr || C->failure_is_artificial(), "");
if (C->failing()) {
return;
}
_goto->set_req(0,_goto);
// Build the CFG in Reverse Post Order

View File

@ -70,6 +70,14 @@
develop(bool, StressMethodHandleLinkerInlining, false, \
"Stress inlining through method handle linkers") \
\
develop(bool, StressBailout, false, \
"Perform bailouts randomly at C2 failing() checks") \
\
develop(uint, StressBailoutMean, 100000, \
"The expected number of failing() checks made until " \
"a random bailout.") \
range(1, max_juint) \
\
develop(intx, OptoPrologueNops, 0, \
"Insert this many extra nop instructions " \
"in the prologue of every nmethod") \

View File

@ -479,6 +479,9 @@ void PhaseChaitin::Register_Allocate() {
}
uint new_max_lrg_id = Split(_lrg_map.max_lrg_id(), &split_arena); // Split spilling LRG everywhere
if (C->failing()) {
return;
}
_lrg_map.set_max_lrg_id(new_max_lrg_id);
// Bail out if unique gets too large (ie - unique > MaxNodeLimit - 2*NodeLimitFudgeFactor)
// or we failed to split
@ -551,6 +554,9 @@ void PhaseChaitin::Register_Allocate() {
return;
}
uint new_max_lrg_id = Split(_lrg_map.max_lrg_id(), &split_arena); // Split spilling LRG everywhere
if (C->failing()) {
return;
}
_lrg_map.set_max_lrg_id(new_max_lrg_id);
// Bail out if unique gets too large (ie - unique > MaxNodeLimit - 2*NodeLimitFudgeFactor)
C->check_node_count(2 * NodeLimitFudgeFactor, "out of nodes after split");

View File

@ -720,7 +720,7 @@ Compile::Compile( ciEnv* ci_env, ciMethod* target, int osr_bci,
}
if (StressLCM || StressGCM || StressIGVN || StressCCP ||
StressIncrementalInlining || StressMacroExpansion || StressUnstableIfTraps) {
StressIncrementalInlining || StressMacroExpansion || StressUnstableIfTraps || StressBailout) {
initialize_stress_seed(directive);
}
@ -798,7 +798,7 @@ Compile::Compile( ciEnv* ci_env, ciMethod* target, int osr_bci,
assert(failure_reason() != nullptr, "expect reason for parse failure");
stringStream ss;
ss.print("method parse failed: %s", failure_reason());
record_method_not_compilable(ss.as_string());
record_method_not_compilable(ss.as_string() DEBUG_ONLY(COMMA true));
return;
}
GraphKit kit(jvms);
@ -973,7 +973,7 @@ Compile::Compile( ciEnv* ci_env,
_types = new (comp_arena()) Type_Array(comp_arena());
_node_hash = new (comp_arena()) NodeHash(comp_arena(), 255);
if (StressLCM || StressGCM) {
if (StressLCM || StressGCM || StressBailout) {
initialize_stress_seed(directive);
}
@ -1018,6 +1018,7 @@ void Compile::Init(bool aliasing) {
#ifdef ASSERT
_phase_optimize_finished = false;
_phase_verify_ideal_loop = false;
_exception_backedge = false;
_type_verify = nullptr;
#endif
@ -1108,7 +1109,7 @@ void Compile::Init(bool aliasing) {
#ifdef ASSERT
// Verify that the current StartNode is valid.
void Compile::verify_start(StartNode* s) const {
assert(failing() || s == start(), "should be StartNode");
assert(failing_internal() || s == start(), "should be StartNode");
}
#endif
@ -1118,7 +1119,7 @@ void Compile::verify_start(StartNode* s) const {
* the ideal graph.
*/
StartNode* Compile::start() const {
assert (!failing(), "Must not have pending failure. Reason is: %s", failure_reason());
assert (!failing_internal() || C->failure_is_artificial(), "Must not have pending failure. Reason is: %s", failure_reason());
for (DUIterator_Fast imax, i = root()->fast_outs(imax); i < imax; i++) {
Node* start = root()->fast_out(i);
if (start->is_Start()) {
@ -2114,7 +2115,7 @@ void Compile::inline_incrementally(PhaseIterGVN& igvn) {
igvn_worklist()->ensure_empty(); // should be done with igvn
while (inline_incrementally_one()) {
assert(!failing(), "inconsistent");
assert(!failing_internal() || failure_is_artificial(), "inconsistent");
}
if (failing()) return;
@ -2157,7 +2158,7 @@ void Compile::process_late_inline_calls_no_inline(PhaseIterGVN& igvn) {
igvn_worklist()->ensure_empty(); // should be done with igvn
while (inline_incrementally_one()) {
assert(!failing(), "inconsistent");
assert(!failing_internal() || failure_is_artificial(), "inconsistent");
}
if (failing()) return;
@ -2944,6 +2945,9 @@ void Compile::Code_Gen() {
// Build a proper-looking CFG
PhaseCFG cfg(node_arena(), root(), matcher);
if (failing()) {
return;
}
_cfg = &cfg;
{
TracePhase tp("scheduler", &timers[_t_scheduler]);
@ -4329,7 +4333,7 @@ void Compile::verify_graph_edges(bool no_dead_code) {
// to backtrack and retry without subsuming loads. Other than this backtracking
// behavior, the Compile's failure reason is quietly copied up to the ciEnv
// by the logic in C2Compiler.
void Compile::record_failure(const char* reason) {
void Compile::record_failure(const char* reason DEBUG_ONLY(COMMA bool allow_multiple_failures)) {
if (log() != nullptr) {
log()->elem("failure reason='%s' phase='compile'", reason);
}
@ -4339,6 +4343,8 @@ void Compile::record_failure(const char* reason) {
if (CaptureBailoutInformation) {
_first_failure_details = new CompilationFailureInfo(reason);
}
} else {
assert(!StressBailout || allow_multiple_failures, "should have handled previous failure.");
}
if (!C->failure_reason_is(C2Compiler::retry_no_subsuming_loads())) {
@ -4366,7 +4372,9 @@ Compile::TracePhase::TracePhase(const char* name, elapsedTimer* accumulator)
}
Compile::TracePhase::~TracePhase() {
if (_compile->failing()) return;
if (_compile->failing_internal()) {
return; // timing code, not stressing bailouts.
}
#ifdef ASSERT
if (PrintIdealNodeCount) {
tty->print_cr("phase name='%s' nodes='%d' live='%d' live_graph_walk='%d'",
@ -5057,6 +5065,22 @@ bool Compile::randomized_select(int count) {
return (random() & RANDOMIZED_DOMAIN_MASK) < (RANDOMIZED_DOMAIN / count);
}
#ifdef ASSERT
// Failures are geometrically distributed with probability 1/StressBailoutMean.
bool Compile::fail_randomly() {
if ((random() % StressBailoutMean) != 0) {
return false;
}
record_failure("StressBailout");
return true;
}
bool Compile::failure_is_artificial() {
assert(failing_internal(), "should be failing");
return C->failure_reason_is("StressBailout");
}
#endif
CloneMap& Compile::clone_map() { return _clone_map; }
void Compile::set_clone_map(Dict* d) { _clone_map._dict = d; }
@ -5144,7 +5168,7 @@ void Compile::sort_macro_nodes() {
}
void Compile::print_method(CompilerPhaseType cpt, int level, Node* n) {
if (failing()) { return; }
if (failing_internal()) { return; } // failing_internal to not stress bailouts from printing code.
EventCompilerPhase event(UNTIMED);
if (event.should_commit()) {
CompilerEvent::PhaseEvent::post(event, C->_latest_stage_start_counter, cpt, C->_compile_id, level);

View File

@ -391,6 +391,8 @@ class Compile : public Phase {
DEBUG_ONLY(Unique_Node_List* _modified_nodes;) // List of nodes which inputs were modified
DEBUG_ONLY(bool _phase_optimize_finished;) // Used for live node verification while creating new nodes
DEBUG_ONLY(bool _phase_verify_ideal_loop;) // Are we in PhaseIdealLoop verification?
// Arenas for new-space and old-space nodes.
// Swapped between using _node_arena.
// The lifetime of the old-space nodes is during xform.
@ -786,6 +788,12 @@ private:
void set_post_loop_opts_phase() { _post_loop_opts_phase = true; }
void reset_post_loop_opts_phase() { _post_loop_opts_phase = false; }
#ifdef ASSERT
bool phase_verify_ideal_loop() const { return _phase_verify_ideal_loop; }
void set_phase_verify_ideal_loop() { _phase_verify_ideal_loop = true; }
void reset_phase_verify_ideal_loop() { _phase_verify_ideal_loop = false; }
#endif
bool allow_macro_nodes() { return _allow_macro_nodes; }
void reset_allow_macro_nodes() { _allow_macro_nodes = false; }
@ -815,7 +823,7 @@ private:
ciEnv* env() const { return _env; }
CompileLog* log() const { return _log; }
bool failing() const {
bool failing_internal() const {
return _env->failing() ||
_failure_reason.get() != nullptr;
}
@ -827,6 +835,27 @@ private:
const CompilationFailureInfo* first_failure_details() const { return _first_failure_details; }
bool failing() {
if (failing_internal()) {
return true;
}
#ifdef ASSERT
// Disable stress code for PhaseIdealLoop verification (would have cascading effects).
if (phase_verify_ideal_loop()) {
return false;
}
if (StressBailout) {
return fail_randomly();
}
#endif
return false;
}
#ifdef ASSERT
bool fail_randomly();
bool failure_is_artificial();
#endif
bool failure_reason_is(const char* r) const {
return (r == _failure_reason.get()) ||
(r != nullptr &&
@ -834,11 +863,11 @@ private:
strcmp(r, _failure_reason.get()) == 0);
}
void record_failure(const char* reason);
void record_method_not_compilable(const char* reason) {
void record_failure(const char* reason DEBUG_ONLY(COMMA bool allow_multiple_failures = false));
void record_method_not_compilable(const char* reason DEBUG_ONLY(COMMA bool allow_multiple_failures = false)) {
env()->record_method_not_compilable(reason);
// Record failure reason.
record_failure(reason);
record_failure(reason DEBUG_ONLY(COMMA allow_multiple_failures));
}
bool check_node_count(uint margin, const char* reason) {
if (oom()) {

View File

@ -1527,8 +1527,8 @@ void PhaseCFG::schedule_late(VectorSet &visited, Node_Stack &stack) {
C->record_failure(C2Compiler::retry_no_subsuming_loads());
} else {
// Bailout without retry when (early->_dom_depth > LCA->_dom_depth)
assert(false, "graph should be schedulable");
C->record_method_not_compilable("late schedule failed: incorrect graph");
assert(C->failure_is_artificial(), "graph should be schedulable");
C->record_method_not_compilable("late schedule failed: incorrect graph" DEBUG_ONLY(COMMA true));
}
return;
}
@ -1708,8 +1708,8 @@ void PhaseCFG::global_code_motion() {
Block* block = get_block(i);
if (!schedule_local(block, ready_cnt, visited, recalc_pressure_nodes)) {
if (!C->failure_reason_is(C2Compiler::retry_no_subsuming_loads())) {
assert(false, "local schedule failed");
C->record_method_not_compilable("local schedule failed");
assert(C->failure_is_artificial(), "local schedule failed");
C->record_method_not_compilable("local schedule failed" DEBUG_ONLY(COMMA true));
}
_regalloc = nullptr;
return;

View File

@ -340,7 +340,9 @@ static inline void add_one_req(Node* dstphi, Node* src) {
// having a control input of its exception map, rather than null. Such
// regions do not appear except in this function, and in use_exception_state.
void GraphKit::combine_exception_states(SafePointNode* ex_map, SafePointNode* phi_map) {
if (failing()) return; // dying anyway...
if (failing_internal()) {
return; // dying anyway...
}
JVMState* ex_jvms = ex_map->_jvms;
assert(ex_jvms->same_calls_as(phi_map->_jvms), "consistent call chains");
assert(ex_jvms->stkoff() == phi_map->_jvms->stkoff(), "matching locals");
@ -446,7 +448,7 @@ void GraphKit::combine_exception_states(SafePointNode* ex_map, SafePointNode* ph
//--------------------------use_exception_state--------------------------------
Node* GraphKit::use_exception_state(SafePointNode* phi_map) {
if (failing()) { stop(); return top(); }
if (failing_internal()) { stop(); return top(); }
Node* region = phi_map->control();
Node* hidden_merge_mark = root();
assert(phi_map->jvms()->map() == phi_map, "sanity: 1-1 relation");
@ -2056,7 +2058,9 @@ Node* GraphKit::uncommon_trap(int trap_request,
ciKlass* klass, const char* comment,
bool must_throw,
bool keep_exact_action) {
if (failing()) stop();
if (failing_internal()) {
stop();
}
if (stopped()) return nullptr; // trap reachable?
// Note: If ProfileTraps is true, and if a deopt. actually

View File

@ -82,7 +82,7 @@ class GraphKit : public Phase {
#ifdef ASSERT
~GraphKit() {
assert(failing() || !has_exceptions(),
assert(failing_internal() || !has_exceptions(),
"unless compilation failed, user must call transfer_exceptions_into_jvms");
}
#endif
@ -182,6 +182,7 @@ class GraphKit : public Phase {
// Tell if the compilation is failing.
bool failing() const { return C->failing(); }
bool failing_internal() const { return C->failing_internal(); }
// Set _map to null, signalling a stop to further bytecode execution.
// Preserve the map intact for future use, and return it back to the caller.

View File

@ -1204,7 +1204,7 @@ bool PhaseCFG::schedule_local(Block* block, GrowableArray<int>& ready_cnt, Vecto
// to the Compile object, and the C2Compiler will see it and retry.
C->record_failure(C2Compiler::retry_no_subsuming_loads());
} else {
assert(false, "graph should be schedulable");
assert(C->failure_is_artificial(), "graph should be schedulable");
}
// assert( phi_cnt == end_idx(), "did not schedule all" );
return false;

View File

@ -4935,7 +4935,9 @@ void PhaseIdealLoop::verify() const {
bool success = true;
PhaseIdealLoop phase_verify(_igvn, this);
if (C->failing()) return;
if (C->failing_internal()) {
return;
}
// Verify ctrl and idom of every node.
success &= verify_idom_and_nodes(C->root(), &phase_verify);

View File

@ -1128,7 +1128,9 @@ private:
_verify_only(verify_me == nullptr),
_mode(LoopOptsVerify),
_nodes_required(UINT_MAX) {
DEBUG_ONLY(C->set_phase_verify_ideal_loop();)
build_and_optimize();
DEBUG_ONLY(C->reset_phase_verify_ideal_loop();)
}
#endif

View File

@ -194,6 +194,9 @@ void Matcher::match( ) {
}
// One-time initialization of some register masks.
init_spill_mask( C->root()->in(1) );
if (C->failing()) {
return;
}
_return_addr_mask = return_addr();
#ifdef _LP64
// Pointers take 2 slots in 64-bit land
@ -287,10 +290,16 @@ void Matcher::match( ) {
// preserve area, locks & pad2.
OptoReg::Name reg1 = warp_incoming_stk_arg(vm_parm_regs[i].first());
if (C->failing()) {
return;
}
if( OptoReg::is_valid(reg1))
_calling_convention_mask[i].Insert(reg1);
OptoReg::Name reg2 = warp_incoming_stk_arg(vm_parm_regs[i].second());
if (C->failing()) {
return;
}
if( OptoReg::is_valid(reg2))
_calling_convention_mask[i].Insert(reg2);
@ -386,7 +395,7 @@ void Matcher::match( ) {
// Don't set control, it will confuse GCM since there are no uses.
// The control will be set when this node is used first time
// in find_base_for_derived().
assert(_mach_null != nullptr, "");
assert(_mach_null != nullptr || C->failure_is_artificial(), ""); // bailouts are handled below.
C->set_root(xroot->is_Root() ? xroot->as_Root() : nullptr);
@ -404,7 +413,7 @@ void Matcher::match( ) {
assert(C->failure_reason() != nullptr, "graph lost: reason unknown");
ss.print("graph lost: reason unknown");
}
C->record_method_not_compilable(ss.as_string());
C->record_method_not_compilable(ss.as_string() DEBUG_ONLY(COMMA true));
}
if (C->failing()) {
// delete old;
@ -1439,10 +1448,16 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) {
}
// Grab first register, adjust stack slots and insert in mask.
OptoReg::Name reg1 = warp_outgoing_stk_arg(first, begin_out_arg_area, out_arg_limit_per_call );
if (C->failing()) {
return nullptr;
}
if (OptoReg::is_valid(reg1))
rm->Insert( reg1 );
// Grab second register (if any), adjust stack slots and insert in mask.
OptoReg::Name reg2 = warp_outgoing_stk_arg(second, begin_out_arg_area, out_arg_limit_per_call );
if (C->failing()) {
return nullptr;
}
if (OptoReg::is_valid(reg2))
rm->Insert( reg2 );
} // End of for all arguments
@ -2679,6 +2694,10 @@ bool Matcher::gen_narrow_oop_implicit_null_checks() {
// Compute RegMask for an ideal register.
const RegMask* Matcher::regmask_for_ideal_register(uint ideal_reg, Node* ret) {
assert(!C->failing_internal() || C->failure_is_artificial(), "already failing.");
if (C->failing()) {
return nullptr;
}
const Type* t = Type::mreg2type[ideal_reg];
if (t == nullptr) {
assert(ideal_reg >= Op_VecA && ideal_reg <= Op_VecZ, "not a vector: %d", ideal_reg);
@ -2709,7 +2728,10 @@ const RegMask* Matcher::regmask_for_ideal_register(uint ideal_reg, Node* ret) {
default: ShouldNotReachHere();
}
MachNode* mspill = match_tree(spill);
assert(mspill != nullptr, "matching failed: %d", ideal_reg);
assert(mspill != nullptr || C->failure_is_artificial(), "matching failed: %d", ideal_reg);
if (C->failing()) {
return nullptr;
}
// Handle generic vector operand case
if (Matcher::supports_generic_vector_operands && t->isa_vect()) {
specialize_mach_node(mspill);
@ -2855,7 +2877,7 @@ bool Matcher::is_encode_and_store_pattern(const Node* n, const Node* m) {
#ifdef ASSERT
bool Matcher::verify_after_postselect_cleanup() {
assert(!C->failing(), "sanity");
assert(!C->failing_internal() || C->failure_is_artificial(), "sanity");
if (supports_generic_vector_operands) {
Unique_Node_List useful;
C->identify_useful_nodes(useful);

View File

@ -1715,7 +1715,7 @@ void PhaseOutput::fill_buffer(C2_MacroAssembler* masm, uint* blk_starts) {
node_offsets[n->_idx] = masm->offset();
}
#endif
assert(!C->failing(), "Should not reach here if failing.");
assert(!C->failing_internal() || C->failure_is_artificial(), "Should not reach here if failing.");
// "Normal" instruction case
DEBUG_ONLY(uint instr_offset = masm->offset());
@ -3393,7 +3393,7 @@ uint PhaseOutput::scratch_emit_size(const Node* n) {
n->emit(&masm, C->regalloc());
// Emitting into the scratch buffer should not fail
assert (!C->failing(), "Must not have pending failure. Reason is: %s", C->failure_reason());
assert(!C->failing_internal() || C->failure_is_artificial(), "Must not have pending failure. Reason is: %s", C->failure_reason());
if (is_branch) // Restore label.
n->as_MachBranch()->label_set(saveL, save_bnum);

View File

@ -426,7 +426,7 @@ class Parse : public GraphKit {
void set_parse_bci(int bci);
// Must this parse be aborted?
bool failing() { return C->failing(); }
bool failing() const { return C->failing_internal(); } // might have cascading effects, not stressing bailouts for now.
Block* rpo_at(int rpo) {
assert(0 <= rpo && rpo < _block_count, "oob");

View File

@ -306,8 +306,8 @@ static Node* clone_node(Node* def, Block *b, Compile* C) {
C->record_failure(C2Compiler::retry_no_subsuming_loads());
} else {
// Bailout without retry
assert(false, "RA Split failed: attempt to clone node with anti_dependence");
C->record_method_not_compilable("RA Split failed: attempt to clone node with anti_dependence");
assert(C->failure_is_artificial(), "RA Split failed: attempt to clone node with anti_dependence");
C->record_method_not_compilable("RA Split failed: attempt to clone node with anti_dependence" DEBUG_ONLY(COMMA true));
}
return nullptr;
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2024, 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.debug;
import java.util.Random;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.Utils;
/*
* @test
* @key stress randomness
* @bug 8330157
* @requires vm.debug == true & vm.compiler2.enabled & (vm.opt.AbortVMOnCompilationFailure == "null" | !vm.opt.AbortVMOnCompilationFailure)
* @summary Basic tests for bailout stress flag.
* @library /test/lib /
* @run driver compiler.debug.TestStressBailout
*/
public class TestStressBailout {
static void runTest(int invprob) throws Exception {
String[] procArgs = {"-Xcomp", "-XX:-TieredCompilation", "-XX:+StressBailout",
"-XX:StressBailoutMean=" + invprob, "-version"};
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(procArgs);
OutputAnalyzer out = new OutputAnalyzer(pb.start());
out.shouldHaveExitValue(0);
}
public static void main(String[] args) throws Exception {
Random r = Utils.getRandomInstance();
// Likely bail out on -version, for some low Mean value.
runTest(r.nextInt(1, 10));
// Higher value
runTest(r.nextInt(10, 1_000_000));
}
}

View File

@ -309,6 +309,13 @@ public class CtwRunner {
// allocation allocate lots of memory
"-XX:CompileCommand=memlimit,*.*,0"));
// Use this stress mode 10% of the time as it could make some long-running compilations likely to abort.
if (rng.nextInt(10) == 0) {
Args.add("-XX:+StressBailout");
Args.add("-XX:StressBailoutMean=100000");
Args.add("-XX:+CaptureBailoutInformation");
}
for (String arg : CTW_EXTRA_ARGS.split(",")) {
Args.add(arg);
}