8372634: C2: Materialize type information from instanceof checks

Reviewed-by: dlong, qamai, roland
This commit is contained in:
Vladimir Ivanov 2025-12-12 21:12:09 +00:00
parent 4e9525ef36
commit f2e56e4c18
10 changed files with 609 additions and 34 deletions

View File

@ -561,6 +561,20 @@ bool DirectiveSet::should_not_inline(ciMethod* inlinee) {
return false;
}
bool DirectiveSet::should_delay_inline(ciMethod* inlinee) {
inlinee->check_is_loaded();
VM_ENTRY_MARK;
methodHandle mh(THREAD, inlinee->get_Method());
if (_inlinematchers != nullptr) {
return matches_inline(mh, InlineMatcher::delay_inline);
}
if (!CompilerDirectivesIgnoreCompileCommandsOption) {
return CompilerOracle::should_delay_inline(mh);
}
return false;
}
bool DirectiveSet::parse_and_add_inline(char* str, const char*& error_msg) {
InlineMatcher* m = InlineMatcher::parse_inline_pattern(str, error_msg);
if (m != nullptr) {

View File

@ -142,6 +142,7 @@ public:
void append_inline(InlineMatcher* m);
bool should_inline(ciMethod* inlinee);
bool should_not_inline(ciMethod* inlinee);
bool should_delay_inline(ciMethod* inlinee);
void print_inline(outputStream* st);
DirectiveSet* compilecommand_compatibility_init(const methodHandle& method);
bool is_exclusive_copy() { return _directive == nullptr; }

View File

@ -480,6 +480,10 @@ bool CompilerOracle::should_not_inline(const methodHandle& method) {
return check_predicate(CompileCommandEnum::DontInline, method) || check_predicate(CompileCommandEnum::Exclude, method);
}
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);
}

View File

@ -51,6 +51,7 @@ class methodHandle;
option(Log, "log", Bool) \
option(Print, "print", Bool) \
option(Inline, "inline", Bool) \
option(DelayInline, "delayinline", Bool) \
option(DontInline, "dontinline", Bool) \
option(Blackhole, "blackhole", Bool) \
option(CompileOnly, "compileonly", Bool)\
@ -150,6 +151,9 @@ class CompilerOracle : AllStatic {
// Tells whether we want to disallow inlining of this method
static bool should_not_inline(const methodHandle& method);
// Tells whether we want to delay inlining of this method
static bool should_delay_inline(const methodHandle& method);
// Tells whether this method changes Thread.currentThread()
static bool changes_current_thread(const methodHandle& method);

View File

@ -100,6 +100,7 @@ public:
enum InlineType {
unknown_inline,
dont_inline,
delay_inline,
force_inline
};

View File

@ -984,7 +984,8 @@ public:
JVMState* jvms, bool allow_inline, float profile_factor, ciKlass* speculative_receiver_type = nullptr,
bool allow_intrinsics = true);
bool should_delay_inlining(ciMethod* call_method, JVMState* jvms) {
return should_delay_string_inlining(call_method, jvms) ||
return C->directive()->should_delay_inline(call_method) ||
should_delay_string_inlining(call_method, jvms) ||
should_delay_boxing_inlining(call_method, jvms) ||
should_delay_vector_inlining(call_method, jvms);
}

View File

@ -192,7 +192,7 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool
// Try inlining a bytecoded method:
if (!call_does_dispatch) {
InlineTree* ilt = InlineTree::find_subtree_from_root(this->ilt(), jvms->caller(), jvms->method());
bool should_delay = C->should_delay_inlining();
bool should_delay = C->should_delay_inlining() || C->directive()->should_delay_inline(callee);
if (ilt->ok_to_inline(callee, jvms, profile, should_delay)) {
CallGenerator* cg = CallGenerator::for_inline(callee, expected_uses);
// For optimized virtual calls assert at runtime that receiver object

View File

@ -41,6 +41,7 @@
#include "opto/opaquenode.hpp"
#include "opto/parse.hpp"
#include "opto/runtime.hpp"
#include "opto/subtypenode.hpp"
#include "runtime/deoptimization.hpp"
#include "runtime/sharedRuntime.hpp"
@ -1719,38 +1720,108 @@ static Node* extract_obj_from_klass_load(PhaseGVN* gvn, Node* n) {
return obj;
}
// Matches exact and inexact type check IR shapes during parsing.
// On successful match, returns type checked object node and its type after successful check
// as out parameters.
static bool match_type_check(PhaseGVN& gvn,
BoolTest::mask btest,
Node* con, const Type* tcon,
Node* val, const Type* tval,
Node** obj, const TypeOopPtr** cast_type) { // out-parameters
// Look for opportunities to sharpen the type of a node whose klass is compared with a constant klass.
// The constant klass being tested against can come from many bytecode instructions (implicitly or explicitly),
// and also from profile data used by speculative casts.
if (btest == BoolTest::eq && tcon->isa_klassptr()) {
// Found:
// Bool(CmpP(LoadKlass(obj._klass), ConP(Foo.klass)), [eq])
// or the narrowOop equivalent.
(*obj) = extract_obj_from_klass_load(&gvn, val);
(*cast_type) = tcon->isa_klassptr()->as_instance_type();
return true; // found
}
// Match an instanceof check.
// During parsing its IR shape is not canonicalized yet.
//
// obj superklass
// | |
// SubTypeCheck
// |
// Bool [eq] / [ne]
// |
// If
// / \
// T F
// \ /
// Region
// \ ConI ConI
// \ | /
// val -> Phi ConI <- con
// \ /
// CmpI
// |
// Bool [btest]
// |
//
if (tval->isa_int() && val->is_Phi() && val->in(0)->as_Region()->is_diamond()) {
RegionNode* diamond = val->in(0)->as_Region();
IfNode* if1 = diamond->in(1)->in(0)->as_If();
BoolNode* b1 = if1->in(1)->isa_Bool();
if (b1 != nullptr && b1->in(1)->isa_SubTypeCheck()) {
assert(b1->_test._test == BoolTest::eq ||
b1->_test._test == BoolTest::ne, "%d", b1->_test._test);
ProjNode* success_proj = if1->proj_out(b1->_test._test == BoolTest::eq ? 1 : 0);
int idx = diamond->find_edge(success_proj);
assert(idx == 1 || idx == 2, "");
Node* vcon = val->in(idx);
assert(val->find_edge(con) > 0, "");
if ((btest == BoolTest::eq && vcon == con) || (btest == BoolTest::ne && vcon != con)) {
SubTypeCheckNode* sub = b1->in(1)->as_SubTypeCheck();
Node* obj_or_subklass = sub->in(SubTypeCheckNode::ObjOrSubKlass);
Node* superklass = sub->in(SubTypeCheckNode::SuperKlass);
if (gvn.type(obj_or_subklass)->isa_oopptr()) {
const TypeKlassPtr* klass_ptr_type = gvn.type(superklass)->is_klassptr();
const TypeKlassPtr* improved_klass_ptr_type = klass_ptr_type->try_improve();
(*obj) = obj_or_subklass;
(*cast_type) = improved_klass_ptr_type->cast_to_exactness(false)->as_instance_type();
return true; // found
}
}
}
}
return false; // not found
}
void Parse::sharpen_type_after_if(BoolTest::mask btest,
Node* con, const Type* tcon,
Node* val, const Type* tval) {
// Look for opportunities to sharpen the type of a node
// whose klass is compared with a constant klass.
if (btest == BoolTest::eq && tcon->isa_klassptr()) {
Node* obj = extract_obj_from_klass_load(&_gvn, val);
const TypeOopPtr* con_type = tcon->isa_klassptr()->as_instance_type();
if (obj != nullptr && (con_type->isa_instptr() || con_type->isa_aryptr())) {
// Found:
// Bool(CmpP(LoadKlass(obj._klass), ConP(Foo.klass)), [eq])
// or the narrowOop equivalent.
const Type* obj_type = _gvn.type(obj);
const TypeOopPtr* tboth = obj_type->join_speculative(con_type)->isa_oopptr();
if (tboth != nullptr && tboth->klass_is_exact() && tboth != obj_type &&
tboth->higher_equal(obj_type)) {
// obj has to be of the exact type Foo if the CmpP succeeds.
int obj_in_map = map()->find_edge(obj);
JVMState* jvms = this->jvms();
if (obj_in_map >= 0 &&
(jvms->is_loc(obj_in_map) || jvms->is_stk(obj_in_map))) {
TypeNode* ccast = new CheckCastPPNode(control(), obj, tboth);
const Type* tcc = ccast->as_Type()->type();
assert(tcc != obj_type && tcc->higher_equal(obj_type), "must improve");
// Delay transform() call to allow recovery of pre-cast value
// at the control merge.
_gvn.set_type_bottom(ccast);
record_for_igvn(ccast);
// Here's the payoff.
replace_in_map(obj, ccast);
}
}
Node* obj = nullptr;
const TypeOopPtr* cast_type = nullptr;
// Insert a cast node with a narrowed type after a successful type check.
if (match_type_check(_gvn, btest, con, tcon, val, tval,
&obj, &cast_type)) {
assert(obj != nullptr && cast_type != nullptr, "missing type check info");
const Type* obj_type = _gvn.type(obj);
const TypeOopPtr* tboth = obj_type->join_speculative(cast_type)->isa_oopptr();
if (tboth != nullptr && tboth != obj_type && tboth->higher_equal(obj_type)) {
int obj_in_map = map()->find_edge(obj);
JVMState* jvms = this->jvms();
if (obj_in_map >= 0 &&
(jvms->is_loc(obj_in_map) || jvms->is_stk(obj_in_map))) {
TypeNode* ccast = new CheckCastPPNode(control(), obj, tboth);
const Type* tcc = ccast->as_Type()->type();
assert(tcc != obj_type && tcc->higher_equal(obj_type), "must improve");
// Delay transform() call to allow recovery of pre-cast value
// at the control merge.
_gvn.set_type_bottom(ccast);
record_for_igvn(ccast);
// Here's the payoff.
replace_in_map(obj, ccast);
}
}
}

View File

@ -0,0 +1,479 @@
/*
* 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.
*/
/**
* @test
* @summary 8372634
*
* @requires vm.flagless
* @library /test/lib /
*
* @run driver compiler.inlining.TestSubtypeCheckTypeInfo
*/
package compiler.inlining;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestSubtypeCheckTypeInfo {
static final Class<TestSubtypeCheckTypeInfo> THIS_CLASS = TestSubtypeCheckTypeInfo.class;
static final String TEST_CLASS_NAME = THIS_CLASS.getName();
public static void main(String[] args) throws Exception {
ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(
"-XX:+IgnoreUnrecognizedVMOptions", "-showversion",
"-XX:-TieredCompilation", "-Xbatch", "-XX:CICompilerCount=1",
"-XX:+PrintCompilation", "-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintInlining",
"-XX:CompileCommand=quiet",
"-XX:CompileCommand=compileonly," + TEST_CLASS_NAME + "::test*",
"-XX:CompileCommand=delayinline," + TEST_CLASS_NAME + "::lateInline*",
TestSubtypeCheckTypeInfo.Launcher.class.getName());
OutputAnalyzer analyzer = new OutputAnalyzer(pb.start());
analyzer.shouldHaveExitValue(0);
// The test is applicable only to C2 (present in Server VM).
if (analyzer.getStderr().contains("Server VM")) {
List<String> output = analyzer.asLinesWithoutVMWarnings();
parseOutput(output);
System.out.println("TEST PASSED");
}
}
static class Launcher {
public static void main(String[] args) {
runTestCase(TestSubtypeCheckTypeInfo::testInstanceOf);
runTestCase(TestSubtypeCheckTypeInfo::testInstanceOfCondPre);
runTestCase(TestSubtypeCheckTypeInfo::testInstanceOfCondPost);
runTestCase(TestSubtypeCheckTypeInfo::testIsInstance);
runTestCase(TestSubtypeCheckTypeInfo::testIsInstanceCondPre);
runTestCase(TestSubtypeCheckTypeInfo::testIsInstanceCondPost);
runTestCase(TestSubtypeCheckTypeInfo::testInstanceOfLate);
runTestCase(TestSubtypeCheckTypeInfo::testInstanceOfLateCondPre);
runTestCase(TestSubtypeCheckTypeInfo::testInstanceOfLateCondPost);
runTestCase(TestSubtypeCheckTypeInfo::testIsInstanceLate);
runTestCase(TestSubtypeCheckTypeInfo::testIsInstanceLateCondPre);
runTestCase(TestSubtypeCheckTypeInfo::testIsInstanceLateCondPost);
runTestCase(TestSubtypeCheckTypeInfo::testInstanceOfCondLate);
runTestCase(TestSubtypeCheckTypeInfo::testInstanceOfCondLatePre);
runTestCase(TestSubtypeCheckTypeInfo::testInstanceOfCondLatePost);
runTestCase(TestSubtypeCheckTypeInfo::testIsInstanceCondLate);
runTestCase(TestSubtypeCheckTypeInfo::testIsInstanceCondLatePre);
runTestCase(TestSubtypeCheckTypeInfo::testIsInstanceCondLatePost);
runTestCase(TestSubtypeCheckTypeInfo::testInstanceOfNulls);
runTestCase(TestSubtypeCheckTypeInfo::testIsInstanceNulls);
}
}
/* =========================================================== */
@InlineSuccess
// @ 8 compiler.inlining.TestSubtypeCheckTypeInfo$B::m (1 bytes) inline (hot)
static void testInstanceOf(A o, boolean cond) {
if (o instanceof B) {
o.m();
}
}
@InlineSuccess
// @ 12 compiler.inlining.TestSubtypeCheckTypeInfo$B::m (1 bytes) inline (hot)
static void testInstanceOfCondPre(A o, boolean cond) {
if (cond && (o instanceof B)) {
o.m();
}
}
@InlineSuccess
// @ 12 compiler.inlining.TestSubtypeCheckTypeInfo$B::m (1 bytes) inline (hot)
static void testInstanceOfCondPost(A o, boolean cond) {
if ((o instanceof B) && cond) {
o.m();
}
}
/* =========================================================== */
@InlineSuccess
// Inlining _isInstance on constant Class compiler/inlining/TestSubtypeCheckTypeInfo$B
// @ 3 java.lang.Class::isInstance (0 bytes) (intrinsic)
// @ 10 compiler.inlining.TestSubtypeCheckTypeInfo$B::m (1 bytes) inline (hot)
static void testIsInstance(A o, boolean cond) {
if (B.class.isInstance(o)) {
o.m();
}
}
@InlineSuccess
// Inlining _isInstance on constant Class compiler/inlining/TestSubtypeCheckTypeInfo$B
// @ 7 java.lang.Class::isInstance (0 bytes) (intrinsic)
// @ 14 compiler.inlining.TestSubtypeCheckTypeInfo$B::m (1 bytes) inline (hot)
static void testIsInstanceCondPre(A o, boolean cond) {
if (cond && B.class.isInstance(o)) {
o.m();
}
}
@InlineSuccess
// @ 3 java.lang.Class::isInstance (0 bytes) (intrinsic)
// @ 14 compiler.inlining.TestSubtypeCheckTypeInfo$B::m (1 bytes) inline (hot)
static void testIsInstanceCondPost(A o, boolean cond) {
if (B.class.isInstance(o) && cond) {
o.m();
}
}
/* =========================================================== */
@InlineSuccess
// @ 5 compiler.inlining.TestSubtypeCheckTypeInfo::lateInline (9 bytes) inline (hot) late inline succeeded
// @ 5 compiler.inlining.TestSubtypeCheckTypeInfo$B::m (1 bytes) inline (hot)
static void testInstanceOfLate(A o, boolean cond) {
// if (o instanceof B) { o.m(); }
lateInline(o, o instanceof B);
}
@InlineFailure
// @ 17 compiler.inlining.TestSubtypeCheckTypeInfo::lateInline (9 bytes) inline (hot) late inline succeeded
// @ 5 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testInstanceOfLateCondPre(A o, boolean cond) {
// if (cond && o instanceof B) { o.m(); }
lateInline(o, cond && (o instanceof B));
}
@InlineFailure
// @ 17 compiler.inlining.TestSubtypeCheckTypeInfo::lateInline (9 bytes) inline (hot) late inline succeeded
// @ 5 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testInstanceOfLateCondPost(A o, boolean cond) {
// if ((o instanceof B) && cond) { o.m(); }
lateInline(o, (o instanceof B) && cond);
}
/* =========================================================== */
@InlineSuccess
// Inlining _isInstance on constant Class compiler/inlining/TestSubtypeCheckTypeInfo$B
// @ 4 java.lang.Class::isInstance (0 bytes) (intrinsic)
// @ 7 compiler.inlining.TestSubtypeCheckTypeInfo::lateInline (9 bytes) inline (hot) late inline succeeded
// @ 5 compiler.inlining.TestSubtypeCheckTypeInfo$B::m (1 bytes) inline (hot)
static void testIsInstanceLate(A o, boolean cond) {
// if (B.class.isInstance(o)) { o.m(); }
lateInline(o, B.class.isInstance(o));
}
@InlineFailure
// Inlining _isInstance on constant Class compiler/inlining/TestSubtypeCheckTypeInfo$B
// @ 8 java.lang.Class::isInstance (0 bytes) (intrinsic)
// @ 19 compiler.inlining.TestSubtypeCheckTypeInfo::lateInline (9 bytes) inline (hot) late inline succeeded
// @ 5 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testIsInstanceLateCondPre(A o, boolean cond) {
// if (cond && B.class.isInstance(o)) { o.m(); }
lateInline(o, cond && (B.class.isInstance(o)));
}
@InlineFailure
// Inlining _isInstance on constant Class compiler/inlining/TestSubtypeCheckTypeInfo$B
// @ 4 java.lang.Class::isInstance (0 bytes) (intrinsic)
// @ 19 compiler.inlining.TestSubtypeCheckTypeInfo::lateInline (9 bytes) inline (hot) late inline succeeded
// @ 5 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testIsInstanceLateCondPost(A o, boolean cond) {
// if (B.class.isInstance(o) && cond) { o.m(); }
lateInline(o, (B.class.isInstance(o) && cond));
}
/* =========================================================== */
@InlineFailure
// @ 2 compiler.inlining.TestSubtypeCheckTypeInfo::lateInlineInstanceOfCondPre (17 bytes) inline (hot) late inline succeeded
// @ 9 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testInstanceOfCondLate(A a, boolean cond) {
if (lateInlineInstanceOfCondPre(a, true)) {
a.m();
}
}
@InlineFailure
// @ 2 compiler.inlining.TestSubtypeCheckTypeInfo::lateInlineInstanceOfCondPre (17 bytes) inline (hot) late inline succeeded
// @ 9 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testInstanceOfCondLatePre(A a, boolean cond) {
if (lateInlineInstanceOfCondPre(a, cond)) {
a.m();
}
}
@InlineFailure
// @ 2 compiler.inlining.TestSubtypeCheckTypeInfo::lateInlineInstanceOfCondPost (17 bytes) inline (hot) late inline succeeded
// @ 9 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testInstanceOfCondLatePost(A a, boolean cond) {
if (lateInlineInstanceOfCondPost(a, cond)) {
a.m();
}
}
/* =========================================================== */
@InlineFailure
// Inlining _isInstance on constant Class compiler/inlining/TestSubtypeCheckTypeInfo$B
// @ 2 compiler.inlining.TestSubtypeCheckTypeInfo::lateInlineIsInstanceCondPre (19 bytes) inline (hot) late inline succeeded
// @ 7 java.lang.Class::isInstance (0 bytes) (intrinsic)
// @ 9 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testIsInstanceCondLate(A a, boolean cond) {
if (lateInlineIsInstanceCondPre(a, true)) {
a.m();
}
}
@InlineFailure
// Inlining _isInstance on constant Class compiler/inlining/TestSubtypeCheckTypeInfo$B
// @ 2 compiler.inlining.TestSubtypeCheckTypeInfo::lateInlineIsInstanceCondPre (19 bytes) inline (hot) late inline succeeded
// @ 7 java.lang.Class::isInstance (0 bytes) (intrinsic)
// @ 9 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testIsInstanceCondLatePre(A a, boolean cond) {
if (lateInlineIsInstanceCondPre(a, cond)) {
a.m();
}
}
@InlineFailure
// Inlining _isInstance on constant Class compiler/inlining/TestSubtypeCheckTypeInfo$B
// @ 2 compiler.inlining.TestSubtypeCheckTypeInfo::lateInlineIsInstanceCondPost (19 bytes) inline (hot) late inline succeeded
// @ 3 java.lang.Class::isInstance (0 bytes) (intrinsic)
// @ 9 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testIsInstanceCondLatePost(A a, boolean cond) {
if (lateInlineIsInstanceCondPost(a, cond)) {
a.m();
}
}
/* =========================================================== */
@InlineFailure
// @ 20 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testInstanceOfNulls(A o, boolean cond) {
A recv = (cond ? o : null);
if (recv instanceof B) {
o.m();
}
}
@InlineFailure
//Inlining _isInstance on constant Class compiler/inlining/TestSubtypeCheckTypeInfo$B
// @ 13 java.lang.Class::isInstance (0 bytes) (intrinsic)
// @ 20 compiler.inlining.TestSubtypeCheckTypeInfo$A::m (0 bytes) failed to inline: virtual call
static void testIsInstanceNulls(A o, boolean cond) {
A recv = (cond ? o : null);
if (B.class.isInstance(recv)) {
o.m();
}
}
/* =========================================================== */
static abstract class A {
public abstract void m();
}
static abstract class B extends A {
public void m() {}
}
static class C extends A {
public void m() {}
}
static void lateInline(A o, boolean cond) {
if (cond) {
o.m();
}
}
static boolean lateInlineInstanceOfCondPre(A o, boolean cond) {
return cond && (o instanceof B);
}
static boolean lateInlineInstanceOfCondPost(A o, boolean cond) {
return (o instanceof B) && cond;
}
static boolean lateInlineIsInstanceCondPre(A o, boolean cond) {
return cond && B.class.isInstance(o);
}
static boolean lateInlineIsInstanceCondPost(A o, boolean cond) {
return B.class.isInstance(o) && cond;
}
/* =========================================================== */
static final String INLINE_SUCCESS_MESSAGE = "B::m (1 bytes) inline (hot)";
static final String INLINE_FAILURE_MESSAGE = "A::m (0 bytes) failed to inline: virtual call";
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InlineSuccess {
String[] shouldContain() default INLINE_SUCCESS_MESSAGE;
String[] shouldNotContain() default INLINE_FAILURE_MESSAGE;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InlineFailure {
String[] shouldContain() default INLINE_FAILURE_MESSAGE;
String[] shouldNotContain() default INLINE_SUCCESS_MESSAGE;
}
/* =========================================================== */
// Parse compilation log (-XX:+PrintCompilation -XX:+PrintInlining output).
static void parseOutput(List<String> output) {
Pattern compilation = Pattern.compile("^\\d+\\s+\\d+.*");
StringBuilder inlineTree = new StringBuilder();
Set<String> passedTests = new HashSet();
Set<String> failedTests = new HashSet();
for (String line : output) {
// Detect start of next compilation.
if (compilation.matcher(line).matches()) {
// Parse output for previous compilation.
validateInliningOutput(inlineTree.toString(), passedTests, failedTests);
inlineTree = new StringBuilder(); // reset
}
inlineTree.append(line);
}
// Process last compilation
validateInliningOutput(inlineTree.toString(), passedTests, failedTests);
if (!failedTests.isEmpty()) {
String msg = String.format("TEST FAILED: %d test cases failed", failedTests.size());
throw new AssertionError(msg);
} else if (passedTests.size() != totalTestCount()) {
String msg = String.format("TEST FAILED: %d out of %d test cases passed", passedTests.size(), totalTestCount());
throw new AssertionError(msg);
}
}
// Sample:
// 213 42 b compiler.inlining.TestSubtypeCheckTypeInfo::testIsInstanceCondLatePost (13 bytes)
static final Pattern TEST_CASE = Pattern.compile("^\\d+\\s+\\d+\\s+b\\s+" + TEST_CLASS_NAME + "::(\\w+) .*");
static boolean validateInliningOutput(String inlineTree, Set<String> passedTests, Set<String> failedTests) {
Matcher m = TEST_CASE.matcher(inlineTree);
if (m.matches()) {
String testName = m.group(1);
System.out.print(testName);
try {
Method testMethod = TestSubtypeCheckTypeInfo.class.getDeclaredMethod(testName, A.class, boolean.class);
if (validate(inlineTree, testMethod.getAnnotation(InlineSuccess.class)) &&
validate(inlineTree, testMethod.getAnnotation(InlineFailure.class))) {
System.out.println(": SUCCESS");
passedTests.add(testName);
return true;
} else {
failedTests.add(testName);
return false;
}
} catch (NoSuchMethodException e) {
System.out.println(": FAILURE: Missing test info for " + testName + ": " + inlineTree);
throw new InternalError(e);
}
} else {
return false; // not a test method; ignored
}
}
static boolean validate(String message, InlineSuccess ann) {
if (ann != null) {
return validatePatterns(message, ann.shouldContain(), ann.shouldNotContain());
}
return true; // no patterns to validate
}
static boolean validate(String message, InlineFailure ann) {
if (ann != null) {
return validatePatterns(message, ann.shouldContain(), ann.shouldNotContain());
}
return true; // no patterns to validate
}
static boolean validatePatterns(String message, String[] shouldContain, String[] shouldNotContain) {
for (String pattern : shouldContain) {
if (!message.contains(pattern)) {
System.out.printf(": FAILURE: '%s' not found in '%s'\n", pattern, message);
return false;
}
}
for (String pattern : shouldNotContain) {
if (message.contains(pattern)) {
System.out.printf(": FAILURE: '%s' found in '%s'\n", pattern, message);
return false;
}
}
return true;
}
static int totalTestCount() {
int count = 0;
for (Method m : THIS_CLASS.getDeclaredMethods()) {
if (m.isAnnotationPresent(InlineSuccess.class) || m.isAnnotationPresent(InlineFailure.class)) {
String testName = m.getName();
if (testName.startsWith("test")) {
count++;
} else {
throw new InternalError("wrong test name: " + testName);
}
}
}
return count;
}
/* =========================================================== */
interface TestCase {
void run(A o, boolean cond);
}
static void runTestCase(TestCase t) {
A[] receivers = new A[] { new B() {}, new B() {}, new B() {}, new C() {}, new C() {}};
for (int i = 0; i < 20_000; i++) {
// Pollute type profile and branch frequencies.
A recv = receivers[i % receivers.length];
boolean cond = (i % 2 == 0);
t.run(recv, cond);
}
}
}

View File

@ -132,13 +132,13 @@ public class CastNullCheckDroppingsTest {
t.runTest(methodClassCastNull, false, svalue);
t.runTest(methodNullClassCast, false, svalue);
t.runTest(methodClassCastObj, false, svalue);
t.runTest(methodObjClassCast, false, svalue);
t.runTest(methodObjClassCast, false, svalue);
t.runTest(methodClassCastInt, false, svalue);
t.runTest(methodIntClassCast, true, svalue);
t.runTest(methodIntClassCast, false, svalue);
t.runTest(methodClassCastint, false, svalue);
t.runTest(methodintClassCast, false, svalue);
t.runTest(methodClassCastPrim, false, svalue);
t.runTest(methodPrimClassCast, true, svalue);
t.runTest(methodPrimClassCast, false, svalue);
t.runTest(methodVarClassCast, true, objClass);
}