8313713: Allow CompileCommand flag to specify compilation level

Reviewed-by: kvn, asmehra
This commit is contained in:
Kirill Shirokov 2026-05-10 16:17:17 +00:00 committed by Vladimir Kozlov
parent 3c1af6b9c8
commit c35c32502d
19 changed files with 1903 additions and 98 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2026, 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
@ -3558,7 +3558,7 @@ const char* GraphBuilder::check_can_parse(ciMethod* callee) const {
// negative filter: should callee NOT be inlined? returns null, ok to inline, or rejection msg
const char* GraphBuilder::should_not_inline(ciMethod* callee) const {
if ( compilation()->directive()->should_not_inline(callee)) return "disallowed by CompileCommand";
if ( compilation()->directive()->should_not_inline(callee, compilation()->env()->comp_level())) return "disallowed by CompileCommand";
if ( callee->dont_inline()) return "don't inline by annotation";
return nullptr;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2026, 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
@ -814,23 +814,32 @@ CompileTask* CompilationPolicy::select_task(CompileQueue* compile_queue, JavaThr
max_method = max_task->method();
}
methodHandle max_method_h(THREAD, max_method);
if (max_task != nullptr && max_method != nullptr) {
methodHandle max_method_h(THREAD, max_method);
if (max_task != nullptr && max_task->comp_level() == CompLevel_full_profile && TieredStopAtLevel > CompLevel_full_profile &&
max_method != nullptr && is_method_profiled(max_method_h) && !Arguments::is_compiler_only()) {
max_task->set_comp_level(CompLevel_limited_profile);
if (max_task->comp_level() == CompLevel_full_profile && TieredStopAtLevel > CompLevel_full_profile &&
is_method_profiled(max_method_h) && !Arguments::is_compiler_only()) {
if (CompileBroker::compilation_is_complete(max_method_h, max_task->osr_bci(), CompLevel_limited_profile)) {
if (PrintTieredEvents) {
print_event(REMOVE_FROM_QUEUE, max_method, max_method, max_task->osr_bci(), (CompLevel)max_task->comp_level());
CompilerDirectiveMatcher directive_matcher(max_method_h, CompLevel_limited_profile);
bool exclude_limited_profile = directive_matcher.directive_set()->ExcludeOption;
if (!exclude_limited_profile) {
max_task->set_comp_level(CompLevel_limited_profile);
max_task->transfer_directive(directive_matcher);
if (CompileBroker::compilation_is_complete(max_method_h, max_task->osr_bci(), CompLevel_limited_profile)) {
if (PrintTieredEvents) {
print_event(REMOVE_FROM_QUEUE, max_method, max_method, max_task->osr_bci(), (CompLevel)max_task->comp_level());
}
compile_queue->remove_and_mark_stale(max_task);
max_method->clear_queued_for_compilation();
return nullptr;
}
if (PrintTieredEvents) {
print_event(UPDATE_IN_QUEUE, max_method, max_method, max_task->osr_bci(), (CompLevel)max_task->comp_level());
}
}
compile_queue->remove_and_mark_stale(max_task);
max_method->clear_queued_for_compilation();
return nullptr;
}
if (PrintTieredEvents) {
print_event(UPDATE_IN_QUEUE, max_method, max_method, max_task->osr_bci(), (CompLevel)max_task->comp_level());
}
}
return max_task;

View File

@ -1382,7 +1382,7 @@ nmethod* CompileBroker::compile_method(const methodHandle& method, int osr_bci,
}
#endif
CompilerDirectiveMatcher matcher(method, comp);
CompilerDirectiveMatcher matcher(method, comp_level);
// CompileBroker::compile_method can trap and can have pending async exception.
nmethod* nm = CompileBroker::compile_method(method, osr_bci, comp_level, hot_count, compile_reason, matcher.directive_set(), THREAD);
return nm;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2026, 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
@ -57,7 +57,7 @@ CompileTask::CompileTask(int compile_id,
_nm_insts_size(0),
_comp_level(comp_level),
_compiler(CompileBroker::compiler(comp_level)),
_comp_directive_matcher(method, _compiler),
_comp_directive_matcher(method, static_cast<CompLevel>(comp_level)),
JVMCI_ONLY(_has_waiter(_compiler->is_jvmci()) COMMA)
JVMCI_ONLY(_blocking_jvmci_compile_state(nullptr) COMMA)
_num_inlined_bytecodes(0),

View File

@ -131,6 +131,7 @@ class CompileTask : public CHeapObj<mtCompiler> {
bool is_blocking() const { return _is_blocking; }
bool is_success() const { return _is_success; }
DirectiveSet* directive() const { return _comp_directive_matcher.directive_set(); }
void transfer_directive(CompilerDirectiveMatcher& matcher) { _comp_directive_matcher.transfer_from(matcher); }
CompileReason compile_reason() const { return _compile_reason; }
CodeSection::csize_t nm_content_size() { return _nm_content_size; }
void set_nm_content_size(CodeSection::csize_t size) { _nm_content_size = size; }

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2026, 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
@ -25,6 +25,7 @@
#include "ci/ciMethod.hpp"
#include "ci/ciUtilities.inline.hpp"
#include "compiler/abstractCompiler.hpp"
#include "compiler/compileBroker.hpp"
#include "compiler/compilerDefinitions.inline.hpp"
#include "compiler/compilerDirectives.hpp"
#include "compiler/compilerOracle.hpp"
@ -378,7 +379,7 @@ class DirectiveSetPtr {
// - if some option is changed we need to copy directiveset since it no longer can be shared
// - Need to free copy after use
// - Requires a modified bit so we don't overwrite options that is set by directives
DirectiveSet* DirectiveSet::compilecommand_compatibility_init(const methodHandle& method) {
DirectiveSet* DirectiveSet::compilecommand_compatibility_init(const methodHandle& method, int comp_level) {
// Early bail out - checking all options is expensive - we rely on them not being used
// Only set a flag if it has not been modified and value changes.
// Only copy set if a flag needs to be set
@ -397,7 +398,7 @@ DirectiveSet* DirectiveSet::compilecommand_compatibility_init(const methodHandle
// All CompileCommands are not equal so this gets a bit verbose
// When CompileCommands have been refactored less clutter will remain.
if (CompilerOracle::should_break_at(method)) {
if (CompilerOracle::should_break_at(method, static_cast<CompLevel>(comp_level))) {
// If the directives didn't have 'BreakAtCompile' or 'BreakAtExecute',
// the sub-command 'Break' of the 'CompileCommand' would become effective.
if (!_modified[BreakAtCompileIndex]) {
@ -414,13 +415,13 @@ DirectiveSet* DirectiveSet::compilecommand_compatibility_init(const methodHandle
}
}
if (CompilerOracle::should_print(method)) {
if (CompilerOracle::should_print(method, static_cast<CompLevel>(comp_level))) {
if (!_modified[PrintAssemblyIndex]) {
set.cloned()->PrintAssemblyOption = true;
}
}
// Exclude as in should not compile == Enabled
if (CompilerOracle::should_exclude(method)) {
if (CompilerOracle::should_exclude(method, static_cast<CompLevel>(comp_level))) {
if (!_modified[ExcludeIndex]) {
set.cloned()->ExcludeOption = true;
}
@ -547,7 +548,7 @@ bool DirectiveSet::should_inline(ciMethod* inlinee) {
return false;
}
bool DirectiveSet::should_not_inline(ciMethod* inlinee) {
bool DirectiveSet::should_not_inline(ciMethod* inlinee, int comp_level) {
inlinee->check_is_loaded();
VM_ENTRY_MARK;
methodHandle mh(THREAD, inlinee->get_Method());
@ -556,7 +557,7 @@ bool DirectiveSet::should_not_inline(ciMethod* inlinee) {
return matches_inline(mh, InlineMatcher::dont_inline);
}
if (!CompilerDirectivesIgnoreCompileCommandsOption) {
return CompilerOracle::should_not_inline(mh);
return CompilerOracle::should_not_inline(mh, static_cast<CompLevel>(comp_level));
}
return false;
}
@ -755,7 +756,7 @@ void DirectivesStack::release(DirectiveSet* set) {
assert(set != nullptr, "Never nullptr");
MutexLocker locker(DirectivesStack_lock, Mutex::_no_safepoint_check_flag);
if (set->is_exclusive_copy()) {
// Old CompilecCmmands forced us to create an exclusive copy
// Old CompileCommands forced us to create an exclusive copy
delete set;
} else {
assert(set->directive() != nullptr, "Never nullptr");
@ -772,8 +773,9 @@ void DirectivesStack::release(CompilerDirectives* dir) {
}
}
DirectiveSet* DirectivesStack::getMatchingDirective(const methodHandle& method, AbstractCompiler *comp) {
DirectiveSet* DirectivesStack::getMatchingDirective(const methodHandle& method, int comp_level) {
assert(_depth > 0, "Must never be empty");
AbstractCompiler* comp = CompileBroker::compiler(comp_level);
DirectiveSet* match = nullptr;
{
@ -798,5 +800,5 @@ DirectiveSet* DirectivesStack::getMatchingDirective(const methodHandle& method,
guarantee(match != nullptr, "There should always be a default directive that matches");
// Check for legacy compile commands update, without DirectivesStack_lock
return match->compilecommand_compatibility_init(method);
return match->compilecommand_compatibility_init(method, comp_level);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2026, 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
@ -115,7 +115,7 @@ private:
static int _depth;
static void pop_inner(); // no lock version of pop
static DirectiveSet* getMatchingDirective(const methodHandle& mh, AbstractCompiler* comp);
static DirectiveSet* getMatchingDirective(const methodHandle& mh, int comp_level);
static DirectiveSet* getDefaultDirective(AbstractCompiler* comp);
static void release(DirectiveSet* set);
static void release(CompilerDirectives* dir);
@ -145,10 +145,10 @@ public:
bool parse_and_add_inline(char* str, const char*& error_msg);
void append_inline(InlineMatcher* m);
bool should_inline(ciMethod* inlinee);
bool should_not_inline(ciMethod* inlinee);
bool should_not_inline(ciMethod* inlinee, int comp_level);
bool should_delay_inline(ciMethod* inlinee);
void print_inline(outputStream* st);
DirectiveSet* compilecommand_compatibility_init(const methodHandle& method);
DirectiveSet* compilecommand_compatibility_init(const methodHandle& method, int comp_level);
bool is_exclusive_copy() { return _directive == nullptr; }
bool matches_inline(const methodHandle& method, int inline_action);
static DirectiveSet* clone(DirectiveSet const* src);
@ -335,21 +335,35 @@ public:
class CompilerDirectiveMatcher {
private:
DirectiveSet* _match;
void release_match() {
if (_match != nullptr) {
DirectivesStack::release(_match);
_match = nullptr;
}
}
public:
// Use this constructor to get default directive
CompilerDirectiveMatcher(AbstractCompiler* comp) {
_match = DirectivesStack::getDefaultDirective(comp);
}
CompilerDirectiveMatcher(const methodHandle& mh, AbstractCompiler* comp) {
_match = DirectivesStack::getMatchingDirective(mh, comp);
CompilerDirectiveMatcher(const methodHandle& mh, int comp_level) {
_match = DirectivesStack::getMatchingDirective(mh, comp_level);
}
~CompilerDirectiveMatcher() {
DirectivesStack::release(_match);
release_match();
}
DirectiveSet* directive_set() const { return _match; }
void transfer_from(CompilerDirectiveMatcher& src) {
release_match();
_match = src._match;
src._match = nullptr;
}
};
#endif // SHARE_COMPILER_COMPILERDIRECTIVES_HPP

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2026, 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
@ -60,6 +60,37 @@ static const char* const default_compile_commands[] = {
#endif
nullptr };
// CompLevel | -XX:CompileCommand bitmask
// ----------------------------------------------------
// 0 (interpreter) | N/A
// 1 (C1) | 1
// 2 (C1 + counters) | 10
// 3 (C1 + counters + mdo) | 100
// 4 (C2/JVMCI) | 1000
// All C1 levels | 111
// All levels | 1111
static const int comp_level_bitmask[CompLevel_count] = {0, 1, 10, 100, 1000};
static const int comp_level_bitmask_all_levels = 1111;
static const intx default_comp_level_argument = comp_level_bitmask_all_levels;
inline bool bitmask_applies_to_comp_level(int bitmask, int comp_level) {
assert(comp_level > CompLevel_none && comp_level < CompLevel_count, "CompLevel out of bounds");
return (bitmask / comp_level_bitmask[comp_level]) % 10 == 1;
}
static bool is_valid_comp_level_bitmask(intx bitmask) {
if (bitmask < 0 || bitmask > comp_level_bitmask_all_levels) {
return false;
}
for (; bitmask != 0; bitmask /= 10) {
if (bitmask % 10 > 1) {
return false;
}
}
return true;
}
static const char* optiontype_names[] = {
#define enum_of_types(type, name) name,
OPTION_TYPES(enum_of_types)
@ -456,36 +487,56 @@ template bool CompilerOracle::option_matches_type<bool>(CompileCommandEnum optio
template bool CompilerOracle::option_matches_type<ccstr>(CompileCommandEnum option, ccstr& value);
template bool CompilerOracle::option_matches_type<double>(CompileCommandEnum option, double& value);
bool CompilerOracle::applies_to_comp_level(const methodHandle& method, CompileCommandEnum command, CompLevel current_level) {
if (current_level == CompLevel_none) {
return false;
}
intx bitmask = 0;
if (!has_option_value(method, command, bitmask)) {
return false;
}
// Since we don't have bitmask for interpreter level (0), but still need to call CompilerOracle::should_print()
// from collect_profiled_methods() in java.cpp, a special value of CompLevel_any produces a match with any bitmask, even 0
return current_level == CompLevel_any
|| bitmask_applies_to_comp_level(bitmask, current_level);
}
bool CompilerOracle::has_option(const methodHandle& method, CompileCommandEnum option) {
bool value = false;
has_option_value(method, option, value);
return value;
}
bool CompilerOracle::should_exclude(const methodHandle& method) {
if (check_predicate(CompileCommandEnum::Exclude, method)) {
bool CompilerOracle::should_exclude(const methodHandle& method, const CompLevel level) {
if (has_exclude(method, level)) {
return true;
}
if (has_command(CompileCommandEnum::CompileOnly)) {
return !check_predicate(CompileCommandEnum::CompileOnly, method);
return !applies_to_comp_level(method, CompileCommandEnum::CompileOnly, level);
}
return false;
}
bool CompilerOracle::has_exclude(const methodHandle& method, const CompLevel level) {
return applies_to_comp_level(method, CompileCommandEnum::Exclude, level);
}
bool CompilerOracle::should_inline(const methodHandle& method) {
return (check_predicate(CompileCommandEnum::Inline, method));
}
bool CompilerOracle::should_not_inline(const methodHandle& method) {
return check_predicate(CompileCommandEnum::DontInline, method) || check_predicate(CompileCommandEnum::Exclude, method);
bool CompilerOracle::should_not_inline(const methodHandle& method, const CompLevel level) {
return check_predicate(CompileCommandEnum::DontInline, method) || has_exclude(method, level);
}
bool CompilerOracle::should_delay_inline(const methodHandle& method) {
return (check_predicate(CompileCommandEnum::DelayInline, method));
}
bool CompilerOracle::should_print(const methodHandle& method) {
return check_predicate(CompileCommandEnum::Print, method);
bool CompilerOracle::should_print(const methodHandle& method, const CompLevel level) {
return applies_to_comp_level(method, CompileCommandEnum::Print, level);
}
bool CompilerOracle::should_print_methods() {
@ -505,8 +556,8 @@ bool CompilerOracle::should_log(const methodHandle& method) {
return (check_predicate(CompileCommandEnum::Log, method));
}
bool CompilerOracle::should_break_at(const methodHandle& method) {
return check_predicate(CompileCommandEnum::Break, method);
bool CompilerOracle::should_break_at(const methodHandle& method, const CompLevel level) {
return applies_to_comp_level(method, CompileCommandEnum::Break, level);
}
void CompilerOracle::tag_blackhole_if_possible(const methodHandle& method) {
@ -678,6 +729,19 @@ static void usage() {
tty->print_cr("from inlining, whereas the 'compileonly' command only excludes methods from");
tty->print_cr("top-level compilations (i.e. they can still be inlined into other compilation units).");
tty->cr();
tty->print_cr("Compilation levels can be specified in the 'compileonly', 'exclude', 'print',");
tty->print_cr("and 'break' commands using a binary bitmask as an optional value:");
tty->print_cr(" -XX:CompileCommand=exclude,java/*.*,1011 -XX:CompileCommand=print,java/*.*,100");
tty->cr();
tty->print_cr("The bitmask is calculated by summing the desired compilation level values:");
tty->print_cr(" C1 without profiling = 1");
tty->print_cr(" C1 with limited profiling = 10");
tty->print_cr(" C1 with full profiling = 100");
tty->print_cr(" C2 = 1000");
tty->cr();
tty->print_cr("Note: Excluding specific compilation levels may disrupt normal state transitions");
tty->print_cr("between the levels, as the VM will not automatically work around the excluded ones.");
tty->cr();
};
static int skip_whitespace(char* &line) {
@ -712,7 +776,7 @@ static bool parseMemLimit(const char* line, intx& value, int& bytes_read, char*
size_t s = 0;
char* end;
if (!parse_integer<size_t>(line, &end, &s)) {
jio_snprintf(errorbuf, buf_size, "MemLimit: invalid value");
jio_snprintf(errorbuf, buf_size, ": invalid integer: '%.20s'", line);
return false;
}
bytes_read = (int)(end - line);
@ -726,7 +790,7 @@ static bool parseMemLimit(const char* line, intx& value, int& bytes_read, char*
// ok, this is the default
bytes_read += 5;
} else {
jio_snprintf(errorbuf, buf_size, "MemLimit: invalid option");
jio_snprintf(errorbuf, buf_size, ": invalid suffix: '%.6s'", end);
return false;
}
}
@ -751,7 +815,7 @@ static bool parseMemStat(const char* line, uintx& value, int& bytes_read, char*
});
#undef IF_ENUM_STRING
jio_snprintf(errorbuf, buf_size, "MemStat: invalid option");
jio_snprintf(errorbuf, buf_size, ": invalid option: '%.8s'", line);
return false;
}
@ -763,21 +827,42 @@ static bool scan_value(enum OptionType type, char* line, int& total_bytes_read,
const char* type_str = optiontype2name(type);
int skipped = skip_whitespace(line);
total_bytes_read += skipped;
char parse_error_buf[80] = {};
if (type == OptionType::Intx) {
intx value;
bool success = false;
if (option == CompileCommandEnum::MemLimit) {
// Special parsing for MemLimit
success = parseMemLimit(line, value, bytes_read, errorbuf, buf_size);
} else {
// Is it a raw number?
success = sscanf(line, "%zd%n", &value, &bytes_read) == 1;
switch (option) {
case CompileCommandEnum::MemLimit:
// Special parsing for MemLimit
success = parseMemLimit(line, value, bytes_read, parse_error_buf, sizeof(parse_error_buf));
break;
case CompileCommandEnum::Break:
case CompileCommandEnum::CompileOnly:
case CompileCommandEnum::Exclude:
case CompileCommandEnum::Print:
// In the commands above the parameter used to be a boolean. Now it is an int (a compilation level mask).
// For compatibility with previous versions we keep it optional. If user did not specify the mask, assume default value
if (*line == '\0') {
value = default_comp_level_argument;
success = true;
} else {
success = sscanf(line, "%zd%n", &value, &bytes_read) == 1;
if (success && !is_valid_comp_level_bitmask(value)) {
jio_snprintf(parse_error_buf, sizeof(parse_error_buf), ": invalid compilation level bitmask '%.*s'", bytes_read, line);
success = false;
}
}
break;
default:
// Is it a raw number?
success = sscanf(line, "%zd%n", &value, &bytes_read) == 1;
}
if (success) {
total_bytes_read += bytes_read;
return register_command(matcher, option, errorbuf, buf_size, value);
} else {
jio_snprintf(errorbuf, buf_size, "Value cannot be read for option '%s' of type '%s'", ccname, type_str);
jio_snprintf(errorbuf, buf_size, "Value cannot be read for option '%s' of type '%s'%s", ccname, type_str, parse_error_buf);
return false;
}
} else if (type == OptionType::Uintx) {
@ -785,7 +870,7 @@ static bool scan_value(enum OptionType type, char* line, int& total_bytes_read,
bool success = false;
if (option == CompileCommandEnum::MemStat) {
// Special parsing for MemStat
success = parseMemStat(line, value, bytes_read, errorbuf, buf_size);
success = parseMemStat(line, value, bytes_read, parse_error_buf, sizeof(parse_error_buf));
} else {
// parse as raw number
success = sscanf(line, "%zu%n", &value, &bytes_read) == 1;
@ -794,7 +879,7 @@ static bool scan_value(enum OptionType type, char* line, int& total_bytes_read,
total_bytes_read += bytes_read;
return register_command(matcher, option, errorbuf, buf_size, value);
} else {
jio_snprintf(errorbuf, buf_size, "Value cannot be read for option '%s' of type '%s'", ccname, type_str);
jio_snprintf(errorbuf, buf_size, "Value cannot be read for option '%s' of type '%s'%s", ccname, type_str, parse_error_buf);
return false;
}
} else if (type == OptionType::Ccstr) {
@ -1089,17 +1174,25 @@ bool CompilerOracle::parse_from_line(char* line) {
return false;
}
return true;
} else if (option == CompileCommandEnum::MemStat) {
// MemStat default action is to collect data but to not print
if (!register_command(matcher, option, error_buf, sizeof(error_buf), (uintx)MemStatAction::collect)) {
}
switch (option) {
case CompileCommandEnum::Break:
case CompileCommandEnum::CompileOnly:
case CompileCommandEnum::Exclude:
case CompileCommandEnum::Print:
break;
case CompileCommandEnum::MemStat:
// MemStat default action is to collect data but to not print
if (!register_command(matcher, option, error_buf, sizeof(error_buf), (uintx)MemStatAction::collect)) {
print_parse_error(error_buf, original.get());
return false;
}
return true;
default:
jio_snprintf(error_buf, sizeof(error_buf), " Option '%s' is not followed by a value", option2name(option));
print_parse_error(error_buf, original.get());
return false;
}
return true;
} else {
jio_snprintf(error_buf, sizeof(error_buf), " Option '%s' is not followed by a value", option2name(option));
print_parse_error(error_buf, original.get());
return false;
}
}
if (!scan_value(type, line, bytes_read, matcher, option, error_buf, sizeof(error_buf))) {
@ -1209,7 +1302,7 @@ bool CompilerOracle::parse_compile_only(char* line) {
if (method_pattern != nullptr) {
TypedMethodOptionMatcher* matcher = TypedMethodOptionMatcher::parse_method_pattern(method_pattern, error_buf, sizeof(error_buf));
if (matcher != nullptr) {
if (register_command(matcher, CompileCommandEnum::CompileOnly, error_buf, sizeof(error_buf), true)) {
if (register_command(matcher, CompileCommandEnum::CompileOnly, error_buf, sizeof(error_buf), default_comp_level_argument)) {
continue;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2026, 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
@ -25,6 +25,7 @@
#ifndef SHARE_COMPILER_COMPILERORACLE_HPP
#define SHARE_COMPILER_COMPILERORACLE_HPP
#include "compiler/compilerDirectives.hpp"
#include "memory/allStatic.hpp"
#include "oops/oopsHierarchy.hpp"
#include "utilities/istream.hpp"
@ -49,14 +50,14 @@ class methodHandle;
option(Help, "help", Unknown) \
option(Quiet, "quiet", Unknown) \
option(Log, "log", Bool) \
option(Print, "print", Bool) \
option(Print, "print", Intx) \
option(Inline, "inline", Bool) \
option(DelayInline, "delayinline", Bool) \
option(DontInline, "dontinline", Bool) \
option(Blackhole, "blackhole", Bool) \
option(CompileOnly, "compileonly", Bool)\
option(Exclude, "exclude", Bool) \
option(Break, "break", Bool) \
option(CompileOnly, "compileonly", Intx) \
option(Exclude, "exclude", Intx) \
option(Break, "break", Intx) \
option(BreakAtExecute, "BreakAtExecute", Bool) \
option(BreakAtCompile, "BreakAtCompile", Bool) \
option(MemLimit, "MemLimit", Intx) \
@ -135,6 +136,9 @@ class CompilerOracle : AllStatic {
static bool parse_from_input(inputStream::Input* input,
parse_from_line_fn_t* parse_from_line);
static bool has_exclude(const methodHandle& method, CompLevel level);
static bool applies_to_comp_level(const methodHandle& method, CompileCommandEnum command, CompLevel current_level);
public:
// True if the command file has been specified or is implicit
static bool has_command_file();
@ -143,14 +147,15 @@ class CompilerOracle : AllStatic {
static bool parse_from_file();
// Tells whether we to exclude compilation of method
static bool should_exclude(const methodHandle& method);
static bool should_exclude(const methodHandle & method, CompLevel level);
static bool be_quiet() { return _quiet; }
// Tells whether we want to inline this method
static bool should_inline(const methodHandle& method);
// Tells whether we want to disallow inlining of this method
static bool should_not_inline(const methodHandle& method);
static bool should_not_inline(const methodHandle& method, CompLevel level);
// Tells whether we want to delay inlining of this method
static bool should_delay_inline(const methodHandle& method);
@ -159,13 +164,14 @@ class CompilerOracle : AllStatic {
static bool changes_current_thread(const methodHandle& method);
// Tells whether we should print the assembly for this method
static bool should_print(const methodHandle& method);
// If level == CompLevel_none or CompLevel_any, returns true if there is a print command with any mask
static bool should_print(const methodHandle& method, CompLevel level);
// Tells whether we should log the compilation data for this method
static bool should_log(const methodHandle& method);
// Tells whether to break when compiling method
static bool should_break_at(const methodHandle& method);
static bool should_break_at(const methodHandle& method, CompLevel level);
// Tells whether there are any methods to print for print_method_statistics()
static bool should_print_methods();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2026, 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
@ -818,7 +818,7 @@ JVMCI::CodeInstallResult CodeInstaller::install(JVMCICompiler* compiler,
cb = nm;
if (compile_state == nullptr) {
// This compile didn't come through the CompileBroker so perform the printing here
CompilerDirectiveMatcher matcher(method, compiler);
CompilerDirectiveMatcher matcher(method, CompLevel_full_optimization);
nm->maybe_print_nmethod(matcher.directive_set());
// Since this compilation didn't pass through the broker it wasn't logged yet.

View File

@ -585,7 +585,7 @@ C2V_END
C2V_VMENTRY_0(jboolean, hasNeverInlineDirective,(JNIEnv* env, jobject, ARGUMENT_PAIR(method)))
methodHandle method (THREAD, UNPACK_PAIR(Method, method));
return !Inline || CompilerOracle::should_not_inline(method) || method->dont_inline();
return !Inline || CompilerOracle::should_not_inline(method, CompLevel_full_optimization) || method->dont_inline();
C2V_END
C2V_VMENTRY_0(jboolean, shouldInlineMethod,(JNIEnv* env, jobject, ARGUMENT_PAIR(method)))

View File

@ -234,7 +234,7 @@ bool InlineTree::should_not_inline(ciMethod* callee_method, ciMethod* caller_met
return false;
}
if (C->directive()->should_not_inline(callee_method)) {
if (C->directive()->should_not_inline(callee_method, CompLevel_full_optimization)) {
set_msg("disallowed by CompileCommand");
return true;
}

View File

@ -872,11 +872,11 @@ WB_ENTRY(jboolean, WB_IsMethodCompiled(JNIEnv* env, jobject o, jobject method, j
return !code->is_marked_for_deoptimization();
WB_END
static bool is_excluded_for_compiler(AbstractCompiler* comp, methodHandle& mh) {
static bool is_excluded_for_compiler(AbstractCompiler* comp, int comp_level, methodHandle& mh) {
if (comp == nullptr) {
return true;
}
CompilerDirectiveMatcher matcher(mh, comp);
CompilerDirectiveMatcher matcher(mh, comp_level);
return matcher.directive_set()->ExcludeOption;
}
@ -902,8 +902,10 @@ WB_ENTRY(jboolean, WB_IsMethodCompilable(JNIEnv* env, jobject o, jobject method,
// to exclude a compilation of 'method'.
if (comp_level == CompLevel_any) {
// Both compilers could have ExcludeOption set. Check all combinations.
bool excluded_c1 = is_excluded_for_compiler(CompileBroker::compiler1(), mh);
bool excluded_c2 = is_excluded_for_compiler(CompileBroker::compiler2(), mh);
bool excluded_c1 = is_excluded_for_compiler(CompileBroker::compiler1(), CompLevel_simple, mh)
&& is_excluded_for_compiler(CompileBroker::compiler1(), CompLevel_limited_profile, mh)
&& is_excluded_for_compiler(CompileBroker::compiler1(), CompLevel_full_profile, mh);
bool excluded_c2 = is_excluded_for_compiler(CompileBroker::compiler2(), CompLevel_full_optimization, mh);
if (excluded_c1 && excluded_c2) {
// Compilation of 'method' excluded by both compilers.
return false;
@ -914,9 +916,11 @@ WB_ENTRY(jboolean, WB_IsMethodCompilable(JNIEnv* env, jobject o, jobject method,
return can_be_compiled_at_level(mh, is_osr, CompLevel_full_optimization);
} else if (excluded_c2) {
// C2 only has ExcludeOption set: Check if compilable with C1.
return can_be_compiled_at_level(mh, is_osr, CompLevel_simple);
return can_be_compiled_at_level(mh, is_osr, CompLevel_simple)
|| can_be_compiled_at_level(mh, is_osr, CompLevel_limited_profile)
|| can_be_compiled_at_level(mh, is_osr, CompLevel_full_profile);
}
} else if (comp_level > CompLevel_none && is_excluded_for_compiler(CompileBroker::compiler((int)comp_level), mh)) {
} else if (comp_level > CompLevel_none && is_excluded_for_compiler(CompileBroker::compiler((int)comp_level), comp_level, mh)) {
// Compilation of 'method' excluded by compiler used for 'comp_level'.
return false;
}
@ -952,7 +956,7 @@ WB_ENTRY(jboolean, WB_IsIntrinsicAvailable(JNIEnv* env, jobject o, jobject metho
compilation_context_id = reflected_method_to_jmid(thread, env, compilation_context);
CHECK_JNI_EXCEPTION_(env, JNI_FALSE);
methodHandle cch(THREAD, Method::checked_resolve_jmethod_id(compilation_context_id));
CompilerDirectiveMatcher matcher(cch, comp);
CompilerDirectiveMatcher matcher(cch, compLevel);
return comp->is_intrinsic_available(mh, matcher.directive_set());
} else {
// Calling with null matches default directive
@ -1132,7 +1136,7 @@ bool WhiteBox::compile_method(Method* method, int comp_level, int bci, JavaThrea
// Check if compilation is blocking
methodHandle mh(THREAD, method);
CompilerDirectiveMatcher matcher(mh, comp);
CompilerDirectiveMatcher matcher(mh, comp_level);
bool is_blocking = !matcher.directive_set()->BackgroundCompilationOption;
// Compile method and check result
@ -1151,7 +1155,7 @@ bool WhiteBox::compile_method(Method* method, int comp_level, int bci, JavaThrea
} else if (mh->lookup_osr_nmethod_for(bci, comp_level, false) != nullptr) {
return true;
}
tty->print("WB error: failed to %s compile at level %d method ", is_blocking ? "blocking" : "", comp_level);
tty->print("WB error: failed to%s compile at level %d method ", is_blocking ? " blocking" : "", comp_level);
mh->print_short_name(tty);
tty->cr();
if (is_blocking && is_queued) {
@ -1184,7 +1188,7 @@ WB_ENTRY(jboolean, WB_ShouldPrintAssembly(JNIEnv* env, jobject o, jobject method
CHECK_JNI_EXCEPTION_(env, JNI_FALSE);
methodHandle mh(THREAD, Method::checked_resolve_jmethod_id(jmid));
CompilerDirectiveMatcher matcher(mh, CompileBroker::compiler(comp_level));
CompilerDirectiveMatcher matcher(mh, comp_level);
return matcher.directive_set()->PrintAssemblyOption;
WB_END

View File

@ -114,11 +114,16 @@ static int compare_methods(Method** a, Method** b) {
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
inline CompLevel method_code_comp_level(const Method* m) {
const nmethod* code = m->code();
return code != nullptr ? static_cast<CompLevel>(code->comp_level()) : CompLevel_any;
}
static void collect_profiled_methods(Method* m) {
Thread* thread = Thread::current();
methodHandle mh(thread, m);
if ((m->method_data() != nullptr) &&
(PrintMethodData || CompilerOracle::should_print(mh))) {
(PrintMethodData || CompilerOracle::should_print(mh, method_code_comp_level(m)))) {
collected_profiled_methods->push(m);
}
}

View File

@ -3194,7 +3194,7 @@ void AdapterHandlerLibrary::create_native_wrapper(const methodHandle& method) {
}
}
CompilerDirectiveMatcher matcher(method, CompileBroker::compiler(CompLevel_simple));
CompilerDirectiveMatcher matcher(method, CompLevel_simple);
if (matcher.directive_set()->PrintAssemblyOption) {
nm->print_code();
}

View File

@ -0,0 +1,69 @@
/*
* Copyright Amazon.com Inc. 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 8313713
* @summary Test if the following CompileCommand options support compilation
* level bitmask argument: break, compileonly, exclude, print
* @library /test/lib
* @run main ${test.main.class}
*/
package compiler.compilercontrol.commands;
import jdk.test.lib.process.ProcessTools;
import java.util.List;
public class CompileLevelParseTest {
private static final List<String> commandsWithCompileLevel = List.of("break", "compileonly", "exclude", "print");
private static final List<String> compLevels = List.of("0", "1", "11", "111", "10", "100", "101", "1000", "1111");
private static final List<String> invalidCompLevels = List.of("-9223372036854775808", "-1", "-1111", "10000", "2", "20000", "01012",
"91", "9", "c1", "true", "false");
private static final String DEFAULT_COMP_LEVEL = "1111";
private static final String METHOD_EXP = "java/lang/Object.toString";
public static void main(String[] args) throws Exception {
for (String cmd : commandsWithCompileLevel) {
ProcessTools.executeTestJava("-XX:CompileCommand=" + cmd + "," + METHOD_EXP, "-version")
.shouldHaveExitValue(0)
.shouldNotContain("CompileCommand: An error occurred during parsing")
.shouldContain("CompileCommand: " + cmd + " " + METHOD_EXP + " intx " + cmd + " = " + DEFAULT_COMP_LEVEL); // should be registered
for (String level : compLevels) {
ProcessTools.executeTestJava("-XX:CompileCommand=" + cmd + "," + METHOD_EXP + "," + level, "-version")
.shouldHaveExitValue(0)
.shouldNotContain("CompileCommand: An error occurred during parsing")
.shouldContain("CompileCommand: " + cmd + " " + METHOD_EXP + " intx " + cmd + " = " + level); // should be registered
}
// Note that values like "1suffix" are still accepted
for (String incorrectLevel : invalidCompLevels) {
ProcessTools.executeTestJava("-XX:CompileCommand=" + cmd + "," + METHOD_EXP + "," +incorrectLevel, "-version")
.shouldHaveExitValue(1)
.shouldContain("CompileCommand: An error occurred during parsing")
.shouldNotContain("CompileCommand: " + cmd + " " + METHOD_EXP + " intx " + cmd + " = " + incorrectLevel);
}
}
}
}

View File

@ -0,0 +1,514 @@
/*
* Copyright Amazon.com Inc. 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 8313713
* @summary Test -XX:CompileCommand=exclude and compileonly with different compilation levels,
* monitoring compilation events in VM -XX:+PrintCompilation and -XX:+PrintTieredEvents output
* @requires vm.compMode != "Xint" & vm.flavor == "server"
* & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null)
* & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null)
* @library /test/lib
* @run main ${test.main.class} runner
*/
package compiler.compilercontrol.commands;
import jdk.test.lib.Asserts;
import jdk.test.lib.management.InputArguments;
import jdk.test.lib.process.ProcessTools;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CompileLevelPrintTest {
static final Method TEST_METHOD;
static {
try {
TEST_METHOD = Testee.class.getDeclaredMethod("compiledMethod", new Class[] {int.class});
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
static final String TEST_METHOD_NAME_DOT = TEST_METHOD.getDeclaringClass().getName().replace('.', '/')
+ "." + TEST_METHOD.getName();
static final String TEST_METHOD_NAME_DBL_COLON = TEST_METHOD.getDeclaringClass().getName()
+ "::" + TEST_METHOD.getName();
static final String TEST_METHOD_SIGNATURE = TEST_METHOD_NAME_DBL_COLON + "(";
static final String TESTEE_WAITING_FOR_START_CMD = "==> waiting for start command";
static final String START_CMD = "start";
static final String STOP_CMD = "stop";
static final boolean DEBUG_OUTPUT = false;
static int TIMEOUT_SEC = 30;
static class TesteeState {
final CountDownLatch waitingForStartTest = new CountDownLatch(1);
final AtomicInteger compiler1QueueSize = new AtomicInteger();
final AtomicInteger compiler2QueueSize = new AtomicInteger(0);
final Set<String> compileCommandsReported = Collections.synchronizedSet(new HashSet<>());
volatile Set<String> testMethodCompiledAtLevel = Collections.synchronizedSet(new HashSet<>());
final Set<String> testMethodExcludedAtLevel = Collections.synchronizedSet(new HashSet<>());
volatile Set<String> testMethodPrintedAtLevel = Collections.synchronizedSet(new HashSet<>());
volatile boolean testMethodMDOPrinted = false;
@Override
public String toString() {
return "TesteeState{" +
"\n compileCommandsReported=" + compileCommandsReported +
"\n testMethodCompiledAtLevel=" + testMethodCompiledAtLevel +
"\n testMethodExcludedAtLevel=" + testMethodExcludedAtLevel +
"\n testMethodPrintedAtLevel=" + testMethodPrintedAtLevel +
"\n testMethodMDOPrinted=" + testMethodMDOPrinted +
"\n}";
}
}
public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
// Use the same launcher to avoid double launch cost compared to multiple jtreg @test annotations
if (args.length > 0 && "runner".equals(args[0])) {
if (Arrays.asList(InputArguments.getVmInputArgs()).contains("-Xcomp")) {
TIMEOUT_SEC *= 3;
}
if (Arrays.asList(InputArguments.getVmInputArgs()).contains("-XX:-TieredCompilation")) {
// If we have -XX:-TieredCompilation, we check only for C2 compilation
// A space is printed instead of compile level
Runner.run("compileonly", 1, 1, 1, Set.of(), Set.of("4"), true, false);
Runner.run("compileonly", 10, 1, 1, Set.of(), Set.of("4"), true, false);
Runner.run("compileonly", 100, 1, 1, Set.of(), Set.of("4"), true, false);
Runner.run("compileonly", 1000, 1000, 4, Set.of(" "), Set.of(), true, false);
Runner.run("compileonly", 1100, 1100, 4, Set.of(" "), Set.of(), true, false);
Runner.run("exclude", 1110, 1, 1, Set.of(), Set.of("4"), true, false);
Runner.run("exclude", 1101, 10, 2, Set.of(), Set.of("4"), true, false);
Runner.run("exclude", 1011, 100, 3, Set.of(), Set.of("4"), true, false);
Runner.run("exclude", 111, 1000, 4, Set.of(" "), Set.of(), true, false);
} else {
// -XX:+TieredCompilation
Runner.run("compileonly", 1, 1, 1, Set.of("1"), Set.of(), false, true);
Runner.run("compileonly", 10, 10, 2, Set.of("2"), Set.of(), true, true);
Runner.run("compileonly", 100, 100, 3, Set.of("3"), Set.of(), true, true);
Runner.run("compileonly", 1000, 1000, 4, Set.of("4"), Set.of("3"), true, true);
Runner.run("compileonly", 1100, 1100, 4, Set.of("3", "4"), Set.of(), true, true);
Runner.run("exclude", 1110, 1, 1, Set.of("1"), Set.of(), false, true);
Runner.run("exclude", 1101, 10, 2, Set.of("2"), Set.of(), true, true);
Runner.run("exclude", 1011, 100, 3, Set.of("3"), Set.of(), true, true);
Runner.run("exclude", 111, 1000, 4, Set.of("4"), Set.of("3"), true, true);
}
} else if (args.length > 1 && "parse-logs".equals(args[0])) {
// For test troubleshooting: if test has failed due to regexp matching,
// try parsing testee-<pid>.out and hotspot_pid<pid>.log and adjust the patterns
TesteeState testeeState = new TesteeState();
for (int i = 1; i < args.length; i++) {
Runner.matchMessagesInHotspotLog(args[i], testeeState);
}
IO.println(testeeState.toString());
} else {
Testee.run();
}
}
static class Runner {
private static final int LAST_N_LINES_COUNT = 5;
private static final Pattern reCompileCommand = Pattern.compile(
"CompileCommand: (.*)");
private static final Pattern reTieredEvent = Pattern.compile(
"[0-9.]+: \\[(call|loop|compile|force-compile|remove-from-queue|update-in-queue|reprofile|make-not-entrant) "
+ "level=\\d \\[([^]]+)] @-?\\d+ queues=(\\d+),(\\d+).*]");
private static final Pattern reCompilation = Pattern.compile(
"(\\d+) (C1|C2|no compiler): *(\\d+) ([ %][ s][ !][ b][ n]) ([-0-4 ]) +([^ ]+).*");
private static final Pattern reExcludeCompile = Pattern.compile(
".*made not compilable on level (\\d) +([^ ]+) .* excluded by CompileCommand");
private static final Pattern reCompiledMethod = Pattern.compile(
".*-{35} Assembly -{35}\\n(?:\\[[0-9.]+s]\\[warning]\\[os] Loading hsdis library failed\\n)?\\nCompiled method \\((?:c1|c2)\\) (\\d+) ([Cc][12]): *"
+ "(\\d+) ([ %][ s][ !][ b][ n]) ([-0-4 ]) +([^ ]+) +(@ -?[0-9]+ +)?\\(\\d+ bytes\\)", Pattern.DOTALL);
private static final Pattern reMethodData = Pattern.compile(
".*-{72}\\nstatic ([^\\n]+)\\n *interpreter_invocation_count: *\\d+\\n *invocation_counter: *\\d+", Pattern.DOTALL);
private static final Pattern reEndOfLog = Pattern.compile("<hotspot_log_done .*/>");
public static void run(String compileCmd,
int cmdCompLevel,
int printCmdCompLevel,
int tieredStopAtLevel,
Set<String> expectedCompLevel,
Set<String> expectExcludedAtLevels,
boolean expectMDOPrinted,
boolean tieredCompilation)
throws IOException, InterruptedException {
IO.println("\n########> Testing " + compileCmd + " " + cmdCompLevel);
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(
"-XX:+UnlockDiagnosticVMOptions",
"-XX:+PrintCompilation",
"-XX:+CIPrintCompilerName",
"-XX:+PrintTieredEvents",
"-XX:+LogVMOutput",
"-XX:+LogCompilation",
"-XX:" + (tieredCompilation ? "+" : "-") + "TieredCompilation",
"-XX:TieredStopAtLevel=" + tieredStopAtLevel,
"-XX:CompileCommand=" + compileCmd + "," + TEST_METHOD_NAME_DBL_COLON + "," + cmdCompLevel,
"-XX:CompileCommand=print," + TEST_METHOD_NAME_DBL_COLON + "," + printCmdCompLevel,
CompileLevelPrintTest.class.getName());
try (Process process = pb.start();
BufferedWriter processInput = process.outputWriter();
BufferedReader processOutput = process.inputReader();
BufferedReader processErrOut = process.errorReader()) {
long startNanos = System.nanoTime();
try {
IO.println("##> Testee PID: " + process.pid());
TesteeState testeeState = new TesteeState();
Thread stdoutParser = startDaemonThread(() ->
matchVmMessages(processOutput, testeeState, "", "testee-" + process.pid() + ".out"));
Thread stderrParser = startDaemonThread(() ->
matchTesteeMessages(processErrOut, testeeState, "testee-" + process.pid() + ".err"));
IO.println("##> Waiting for testee to get ready for the start command");
if (!testeeState.waitingForStartTest.await(TIMEOUT_SEC, TimeUnit.SECONDS)) {
throw new RuntimeException("No start signal from testee");
}
Asserts.assertTrue(waitUntil(() -> !process.isAlive()
|| (testeeState.compiler1QueueSize.get() < 5
&& testeeState.compiler2QueueSize.get() < 5)),
"Compiler queue is still not empty");
Asserts.assertTrue(testeeState.compileCommandsReported.contains(
compileCmd + " " + TEST_METHOD_NAME_DOT + " intx " + compileCmd + " = " + cmdCompLevel),
"'CompileCommand: " + compileCmd + "...' was not printed");
Asserts.assertTrue(testeeState.compileCommandsReported.contains(
"print " + TEST_METHOD_NAME_DOT + " intx print = " + printCmdCompLevel),
"'CompileCommand: print ...' was not printed");
IO.println("##> Order testee to start");
processInput.write(START_CMD); processInput.newLine(); processInput.flush();
waitUntil(() -> !process.isAlive()
|| (!expectedCompLevel.isEmpty() && !expectExcludedAtLevels.isEmpty()
&& expectedCompLevel.equals(testeeState.testMethodCompiledAtLevel)
&& expectedCompLevel.equals(testeeState.testMethodPrintedAtLevel)
&& expectExcludedAtLevels.equals(testeeState.testMethodExcludedAtLevel)));
if (process.isAlive()) {
IO.println("##> Required messages have been found in testee output, now stop it");
processInput.write(STOP_CMD + "\n");
processInput.flush();
processInput.close();
}
Asserts.assertEquals(0, process.waitFor());
stdoutParser.join();
stderrParser.join();
// Process stdout can be garbled: pieces of different messages can be intertwined and regexps may
// intermittently fail to match the messages.
// Now parse Hotspot log file to re-match them. Duplicates are OK.
matchMessagesInHotspotLog("hotspot_pid" + process.pid() + ".log", testeeState);
Asserts.assertEquals(expectedCompLevel, testeeState.testMethodCompiledAtLevel,
"Test method was not compiled at required level (" + expectedCompLevel + ")");
Asserts.assertEquals(expectedCompLevel, testeeState.testMethodPrintedAtLevel,
"Test method assembly was not printed at required level (" + expectedCompLevel + ")");
Asserts.assertEquals(expectExcludedAtLevels, testeeState.testMethodExcludedAtLevel,
"Test method compilation was not excluded at required levels (" + expectExcludedAtLevels + ")");
Asserts.assertEquals(expectMDOPrinted, testeeState.testMethodMDOPrinted,
"Test method MDO was" + (expectMDOPrinted ? " NOT" : "") + " printed");
IO.println("########> Test passed");
} catch (Exception ex) {
IO.println("########> Test failed");
ex.printStackTrace();
process.destroyForcibly();
throw ex;
} finally {
IO.println("########> Elapsed " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos) + " ms");
}
}
}
static void matchMessagesInHotspotLog(String logFileName, TesteeState testeeState) throws IOException {
IO.println("##> Parsing " + logFileName + " to match possibly missed messages");
try (BufferedReader reader = new BufferedReader(new FileReader(logFileName))) {
matchVmMessages(reader, testeeState, "Log: ", null);
}
}
private static void matchVmMessages(BufferedReader testeeOutput, TesteeState testeeState, String logPrefix, String fileName) {
try (Writer outWriter = fileName != null ? new BufferedWriter(new FileWriter(fileName)) : null) {
String line;
LinkedList<String> lastNLines = new LinkedList<>();
boolean endOfLog = false;
while (!endOfLog && (line = testeeOutput.readLine()) != null) {
if (outWriter != null) {
outWriter.write(line);
outWriter.write('\n');
}
line = line.trim();
lastNLines.addLast(line);
while (lastNLines.size() > LAST_N_LINES_COUNT) {
lastNLines.removeFirst();
}
String lastNLinesStr = String.join("\n", lastNLines);
Matcher matcher;
String msg = "";
if ((matcher = reCompileCommand.matcher(line)).matches()) {
testeeState.compileCommandsReported.add(matcher.group(1));
msg = "Compile command reported: " + matcher.group(1);
} else if ((matcher = reTieredEvent.matcher(line)).matches()) {
testeeState.compiler1QueueSize.set(Integer.parseInt(matcher.group(3)));
testeeState.compiler2QueueSize.set(Integer.parseInt(matcher.group(4)));
} else if ((matcher = reCompilation.matcher(line)).matches()) {
if ("C1".equalsIgnoreCase(matcher.group(2))) {
testeeState.compiler1QueueSize.decrementAndGet();
} else {
testeeState.compiler2QueueSize.decrementAndGet();
}
if (matcher.group(6).contains(TEST_METHOD_NAME_DBL_COLON)) {
testeeState.testMethodCompiledAtLevel.add(matcher.group(5));
msg = "Test method compiled:"
+ " compiler=" + matcher.group(2)
+ " level=" + matcher.group(5)
+ " compilation#=" + matcher.group(3)
+ " flags=" + matcher.group(4).trim()
+ " name=" + matcher.group(6);
}
} else if ((matcher = reCompiledMethod.matcher(lastNLinesStr)).matches()) {
if (matcher.group(6).contains(TEST_METHOD_NAME_DBL_COLON)) {
testeeState.testMethodPrintedAtLevel.add(matcher.group(5));
msg = "Test method assembly printed:"
+ " compiler=" + matcher.group(2)
+ " level=" + matcher.group(5)
+ " compilation#=" + matcher.group(3)
+ " flags=" + matcher.group(4).trim()
+ " name=" + matcher.group(6)
+ " bci=" + (matcher.group(7) != null ? matcher.group(7).trim() : "");
}
} else if ((matcher = reExcludeCompile.matcher(line)).matches()) {
if (matcher.group(2).contains(TEST_METHOD_NAME_DBL_COLON)) {
testeeState.testMethodExcludedAtLevel.add(matcher.group(1));
msg = "Test method not compilable:"
+ " level=" + matcher.group(1)
+ " name=" + matcher.group(2);
}
} else if ((matcher = reMethodData.matcher(lastNLinesStr)).matches()) {
if (matcher.group(1).contains(TEST_METHOD_SIGNATURE)) {
testeeState.testMethodMDOPrinted = true;
msg = "Test method data:"
+ " name=" + matcher.group(1);
}
} else if (reEndOfLog.matcher(line).matches()) {
endOfLog = true;
msg = "End of log";
}
if (!msg.isEmpty()) {
msg = "##> " + logPrefix + msg;
IO.println(msg);
if (outWriter != null) {
outWriter.write(msg);
outWriter.write('\n');
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
private static void matchTesteeMessages(BufferedReader testeeErrorOutput, TesteeState testeeState, String fileName) {
try (BufferedWriter outWriter = new BufferedWriter(new FileWriter(fileName))) {
String line;
while ((line = testeeErrorOutput.readLine()) != null) {
outWriter.write(line);
outWriter.newLine();
line = line.trim();
if (TESTEE_WAITING_FOR_START_CMD.equals(line)) {
IO.println("##> Testee is waiting for start command");
testeeState.waitingForStartTest.countDown();
} else if (line.startsWith("==>")) {
IO.println(line);
} else if (line.startsWith("Exception in thread ") || line.startsWith("at ")) {
IO.println("==>" + line);
} else if (DEBUG_OUTPUT) {
IO.println("Did not parse stderr: " + line);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
static class Testee {
private static final CountDownLatch startCmd = new CountDownLatch(1);
private static final CountDownLatch stopCmd = new CountDownLatch(1);
static void run() throws IOException, InterruptedException {
System.err.println("==> entering testee()");
try {
startDaemonThread(Testee::inputMonitor);
if (stopCmd.getCount() == 0) {
return;
}
// Print 3 times, since the output can be intermixed with
System.err.println(TESTEE_WAITING_FOR_START_CMD);
if (!startCmd.await(TIMEOUT_SEC + 1, TimeUnit.SECONDS)) {
System.err.println("==> 'start' command was not given in stdin");
return;
}
if (stopCmd.getCount() == 0) {
return;
}
System.err.println("==> starting test");
runTestCode();
} finally {
System.err.println("==> exiting testee()");
}
}
private static void inputMonitor() {
try (BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in))) {
String line;
while ((line = stdin.readLine()) != null) {
line = line.trim();
System.err.println("==> STDIN: " + line);
switch (line) {
case START_CMD:
startCmd.countDown();
break;
case STOP_CMD:
stopCmd.countDown();
break;
default:
System.err.println("==> ERROR: unknown command");
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
// For Tier4 600 invocation of this method with avg 25 loops for each should be enough
// to trigger Tier4CompilationThreshold=15000
private static void compiledMethod(final int a) {
int r = 0;
for (int i = 0; i < a % 50; i++) {
r ^= i;
}
if (r == 42) {
System.err.println("MAGIC!");
}
}
private static boolean longLoop() {
// To trigger compilation, 100-200 should be enough for C1 and 600-700 for C2
for (int i = 0; i < 10000; i++) {
compiledMethod(i);
if ((i & 0xf) == 0 && stopCmd.getCount() == 0) {
System.err.println("==> Bailing out of compiledMethod() at iteration " + i);
return true;
}
}
return false;
}
private static void runTestCode() {
for (int i = 0; i < 100; i++) {
if (longLoop()) {
return;
}
}
}
}
static Thread startDaemonThread(Runnable code) {
Thread t = new Thread(code);
t.setDaemon(true);
t.start();
return t;
}
static boolean waitUntil(BooleanSupplier condition) throws InterruptedException {
for (int maxWait = TIMEOUT_SEC * 5; maxWait > 0; --maxWait) {
if (condition.getAsBoolean()) {
return true;
}
Thread.sleep(200);
}
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2026, 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
@ -73,16 +73,16 @@ public class ClearDirectivesFileStackTest extends AbstractTestBase {
// skip invalid command
command = Command.COMPILEONLY;
}
CompileCommand compileCommand = new CompileCommand(command,
CompileCommand compileCommand = new CompileCommand(command, true,
methodDescriptor, cmdGen.generateCompiler(),
Scenario.Type.DIRECTIVE);
builder.add(compileCommand);
}
// clear the stack
builder.add(new JcmdCommand(Command.NONEXISTENT, null, null,
builder.add(new JcmdCommand(Command.NONEXISTENT, true, null, null,
Scenario.Type.JCMD, Scenario.JcmdType.CLEAR));
// print all directives after the clear
builder.add(new JcmdCommand(Command.NONEXISTENT, null, null,
builder.add(new JcmdCommand(Command.NONEXISTENT, true, null, null,
Scenario.Type.JCMD, Scenario.JcmdType.PRINT));
Scenario scenario = builder.build();
scenario.execute();