From f2e56e4c18080616e8ef275a3d9c1da824efda26 Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Fri, 12 Dec 2025 21:12:09 +0000 Subject: [PATCH] 8372634: C2: Materialize type information from instanceof checks Reviewed-by: dlong, qamai, roland --- .../share/compiler/compilerDirectives.cpp | 14 + .../share/compiler/compilerDirectives.hpp | 1 + src/hotspot/share/compiler/compilerOracle.cpp | 4 + src/hotspot/share/compiler/compilerOracle.hpp | 4 + src/hotspot/share/compiler/methodMatcher.hpp | 1 + src/hotspot/share/opto/compile.hpp | 3 +- src/hotspot/share/opto/doCall.cpp | 2 +- src/hotspot/share/opto/parse2.cpp | 129 +++-- .../inlining/TestSubtypeCheckTypeInfo.java | 479 ++++++++++++++++++ .../klass/CastNullCheckDroppingsTest.java | 6 +- 10 files changed, 609 insertions(+), 34 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/inlining/TestSubtypeCheckTypeInfo.java diff --git a/src/hotspot/share/compiler/compilerDirectives.cpp b/src/hotspot/share/compiler/compilerDirectives.cpp index 5431b03f6a1..1cd8bd1b510 100644 --- a/src/hotspot/share/compiler/compilerDirectives.cpp +++ b/src/hotspot/share/compiler/compilerDirectives.cpp @@ -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) { diff --git a/src/hotspot/share/compiler/compilerDirectives.hpp b/src/hotspot/share/compiler/compilerDirectives.hpp index 27aafe06919..e4826b3056c 100644 --- a/src/hotspot/share/compiler/compilerDirectives.hpp +++ b/src/hotspot/share/compiler/compilerDirectives.hpp @@ -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; } diff --git a/src/hotspot/share/compiler/compilerOracle.cpp b/src/hotspot/share/compiler/compilerOracle.cpp index 23bb754f432..5bcd01a4d09 100644 --- a/src/hotspot/share/compiler/compilerOracle.cpp +++ b/src/hotspot/share/compiler/compilerOracle.cpp @@ -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); } diff --git a/src/hotspot/share/compiler/compilerOracle.hpp b/src/hotspot/share/compiler/compilerOracle.hpp index 09984705792..665f3b2fbfd 100644 --- a/src/hotspot/share/compiler/compilerOracle.hpp +++ b/src/hotspot/share/compiler/compilerOracle.hpp @@ -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); diff --git a/src/hotspot/share/compiler/methodMatcher.hpp b/src/hotspot/share/compiler/methodMatcher.hpp index ba70747ad26..62945129e90 100644 --- a/src/hotspot/share/compiler/methodMatcher.hpp +++ b/src/hotspot/share/compiler/methodMatcher.hpp @@ -100,6 +100,7 @@ public: enum InlineType { unknown_inline, dont_inline, + delay_inline, force_inline }; diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index f5611062f2c..45a3a4f548f 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -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); } diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index 5533b19897b..e4418631d17 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -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 diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index cb8073144a5..eac2b3e863a 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -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); + } } } diff --git a/test/hotspot/jtreg/compiler/inlining/TestSubtypeCheckTypeInfo.java b/test/hotspot/jtreg/compiler/inlining/TestSubtypeCheckTypeInfo.java new file mode 100644 index 00000000000..93767ca8416 --- /dev/null +++ b/test/hotspot/jtreg/compiler/inlining/TestSubtypeCheckTypeInfo.java @@ -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 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 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 output) { + Pattern compilation = Pattern.compile("^\\d+\\s+\\d+.*"); + StringBuilder inlineTree = new StringBuilder(); + Set passedTests = new HashSet(); + Set 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 passedTests, Set 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); + } + } +} diff --git a/test/hotspot/jtreg/compiler/intrinsics/klass/CastNullCheckDroppingsTest.java b/test/hotspot/jtreg/compiler/intrinsics/klass/CastNullCheckDroppingsTest.java index 2d2bf14d76f..17a4034350f 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/klass/CastNullCheckDroppingsTest.java +++ b/test/hotspot/jtreg/compiler/intrinsics/klass/CastNullCheckDroppingsTest.java @@ -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); }