Skip to content

Commit

Permalink
[JIT] enable polymorphic inlining and devirtualization
Browse files Browse the repository at this point in the history
Summary: inline and devirtualize more receivers in polymorphic conditions

Testing: jtreg and ci

Reviewers: maoliang, Kuaiwei

Issue: #917
  • Loading branch information
weixlu committed Jan 22, 2025
1 parent 36a8a63 commit 5134d44
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 12 deletions.
9 changes: 5 additions & 4 deletions src/hotspot/share/ci/ciCallProfile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,19 @@
// This class is used to determine the frequently called method
// at some call site
class ciCallProfile : StackObj {
public:
enum { MAX_MORPHISM_LIMIT = 8 }; // Max call site's morphism we care about
private:
// Fields are initialized directly by ciMethod::call_profile_at_bci.
friend class ciMethod;
friend class ciMethodHandle;

enum { MorphismLimit = 2 }; // Max call site's morphism we care about
int _limit; // number of receivers have been determined
int _morphism; // determined call site's morphism
int _count; // # times has this call been executed
int _receiver_count[MorphismLimit + 1]; // # times receivers have been seen
ciMethod* _method[MorphismLimit + 1]; // receivers methods
ciKlass* _receiver[MorphismLimit + 1]; // receivers (exact)
int _receiver_count[MAX_MORPHISM_LIMIT + 1]; // # times receivers have been seen
ciMethod* _method[MAX_MORPHISM_LIMIT + 1]; // receivers methods
ciKlass* _receiver[MAX_MORPHISM_LIMIT + 1]; // receivers (exact)

ciCallProfile() {
_limit = 0;
Expand Down
4 changes: 2 additions & 2 deletions src/hotspot/share/ci/ciMethod.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,8 +515,8 @@ ciCallProfile ciMethod::call_profile_at_bci(int bci) {
// The call site count is > 0 in the case of a polymorphic virtual call.
if (morphism > 0 && morphism == result._limit) {
// The morphism <= MorphismLimit.
if ((morphism < ciCallProfile::MorphismLimit) ||
(morphism == ciCallProfile::MorphismLimit && count == 0)) {
if ((morphism == 1) ||
(morphism <= MorphismLimit && count == 0)) {
#ifdef ASSERT
if (count > 0) {
this->print_short_name(tty);
Expand Down
44 changes: 38 additions & 6 deletions src/hotspot/share/opto/doCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool
}
if (receiver_method == NULL &&
(have_major_receiver || morphism == 1 ||
(morphism == 2 && UseBimorphicInlining))) {
(morphism == 2 && UseBimorphicInlining) ||
(morphism >= 2 && PolymorphicInlining))) {
// receiver_method = profile.method();
// Profiles do not suggest methods now. Look it up in the major receiver.
receiver_method = callee->resolve_invoke(jvms->method()->holder(),
Expand All @@ -253,19 +254,37 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool
if (next_receiver_method != NULL) {
next_hit_cg = this->call_generator(next_receiver_method,
vtable_index, !call_does_dispatch, jvms,
allow_inline, prof_factor);
allow_inline && (morphism == 2), prof_factor);
if (next_hit_cg != NULL && !next_hit_cg->is_inline() &&
have_major_receiver && UseOnlyInlinedBimorphic) {
// Skip if we can't inline second receiver's method
next_hit_cg = NULL;
}
}
}
int polymorphic_devirtualize = morphism >= 2 && PolymorphicInlining ? morphism : 0;
bool polymorphic_recompile = PolymorphicInlining && next_hit_cg != NULL;
CallGenerator* hit_cg_devirtual[ciCallProfile::MAX_MORPHISM_LIMIT + 1] = {0};
for (int i = 2; i < polymorphic_devirtualize; i++) {
ciMethod*receiver_method_devirtual = callee->resolve_invoke(jvms->method()->holder(),
profile.receiver(i));
if (receiver_method_devirtual != NULL && !(receiver_method_devirtual->is_native() && cg_intrinsic)) {
hit_cg_devirtual[i] = this->call_generator(
receiver_method_devirtual,
vtable_index, !call_does_dispatch, jvms,
false, prof_factor);
if (hit_cg_devirtual[i] == NULL) {
polymorphic_recompile = false;
}
}
}
CallGenerator* miss_cg;
Deoptimization::DeoptReason reason = (morphism == 2
Deoptimization::DeoptReason reason = (polymorphic_recompile
? Deoptimization::Reason_polymorphic
: (morphism == 2
? Deoptimization::Reason_bimorphic
: Deoptimization::reason_class_check(speculative_receiver_type != NULL));
if ((morphism == 1 || (morphism == 2 && next_hit_cg != NULL)) &&
: Deoptimization::reason_class_check(speculative_receiver_type != NULL)));
if ((morphism == 1 || (morphism == 2 && next_hit_cg != NULL) || polymorphic_recompile) &&
!too_many_traps_or_recompiles(caller, bci, reason)
) {
// Generate uncommon trap for class check failure path
Expand All @@ -275,9 +294,22 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool
} else {
// Generate virtual call for class check failure path
// in case of polymorphic virtual call site.
miss_cg = CallGenerator::for_virtual_call(callee, vtable_index);
if (PolymorphicInlining && cg_intrinsic != NULL) {
miss_cg = cg_intrinsic;
} else {
miss_cg = CallGenerator::for_virtual_call(callee, vtable_index);
}
}
if (miss_cg != NULL) {
for (int i = polymorphic_devirtualize - 1; i >= 2; i--) {
if (hit_cg_devirtual[i] != NULL) {
assert(speculative_receiver_type == NULL, "shouldn't end up here if we used speculation");
trace_type_profile(C, jvms->method(), jvms->depth() - 1, jvms->bci(), next_receiver_method, profile.receiver(i), site_count, profile.receiver_count(i));
// We don't need to record dependency on a receiver here and below.
// Whenever we inline, the dependency is added by Parse::Parse().
miss_cg = CallGenerator::for_predicted_call(profile.receiver(i), miss_cg, hit_cg_devirtual[i], PROB_MAX);
}
}
if (next_hit_cg != NULL) {
assert(speculative_receiver_type == NULL, "shouldn't end up here if we used speculation");
trace_type_profile(C, jvms->method(), jvms->depth() - 1, jvms->bci(), next_receiver_method, profile.receiver(1), site_count, profile.receiver_count(1));
Expand Down
15 changes: 15 additions & 0 deletions src/hotspot/share/runtime/arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4188,6 +4188,21 @@ jint Arguments::parse(const JavaVMInitArgs* initial_cmd_args) {
PropertyList_add(&_system_properties, new SystemProperty("java.math.BigDecimal.optimization", "true", true));
}

// user explicitly set MorphismLimit
if (!FLAG_IS_DEFAULT(MorphismLimit) && MorphismLimit > 2) {
FLAG_SET_ERGO(bool, PolymorphicInlining, true);
FLAG_SET_ERGO(intx, TypeProfileWidth, 8);
if (MorphismLimit > 8) {
FLAG_SET_ERGO(uintx, MorphismLimit, 8);
warning("support MorphismLimit up to 8.");
}
}

if (PolymorphicInlining && FLAG_IS_DEFAULT(MorphismLimit)) {
FLAG_SET_ERGO(intx, TypeProfileWidth, 8);
FLAG_SET_ERGO(uintx, MorphismLimit, 6);
}

// Set object alignment values.
set_object_alignment();

Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/runtime/deoptimization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2246,6 +2246,7 @@ const char* Deoptimization::_trap_reason_name[] = {
"array_check",
"intrinsic" JVMCI_ONLY("_or_type_checked_inlining"),
"bimorphic" JVMCI_ONLY("_or_optimized_type_check"),
"polymorphic",
"profile_predicate",
"unloaded",
"uninitialized",
Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/runtime/deoptimization.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class Deoptimization : AllStatic {
Reason_optimized_type_check = Reason_bimorphic,
#endif

Reason_polymorphic, // saw unexpected object class in polymorphic inlining (@bci)
Reason_profile_predicate, // compiler generated predicate moved from frequent branch in a loop failed

// recorded per method
Expand Down
7 changes: 7 additions & 0 deletions src/hotspot/share/runtime/globals_ext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@
"*: all matched files." \
"*.log,*.txt: match given extension files." \
"/x/a.log,/y/b.log: match by full file path") \
\
product(bool, PolymorphicInlining, false, \
"Inline caching multiple type of receivers") \
\
product(uintx, MorphismLimit, 2, \
"Max call site's morphism we care about") \
\
//add new AJDK specific flags here


Expand Down
97 changes: 97 additions & 0 deletions test/hotspot/jtreg/compiler/inlining/TestPolymorphicInlining.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2025, Alibaba Group Holding Limited. 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 TestPolymorphicInlining
* @summary invoke tripple morphic method and see if either one is inlined.
* @library /test/lib
* @run main TestPolymorphicInlining
*/

import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;

public class TestPolymorphicInlining {
public static void main(String[] args) throws Exception {
ProcessBuilder pb1 = ProcessTools.createJavaProcessBuilder(
"-XX:+UnlockDiagnosticVMOptions",
"-XX:+PrintInlining",
"-XX:+PolymorphicInlining",
Test.class.getName());
OutputAnalyzer output1 = new OutputAnalyzer(pb1.start());
output1.shouldMatch("TestPolymorphicInlining\\$Test\\$Child\\d::foo.*inline");
output1.shouldHaveExitValue(0);

ProcessBuilder pb2 = ProcessTools.createJavaProcessBuilder(
"-XX:+UnlockDiagnosticVMOptions",
"-XX:+PrintInlining",
"-XX:-PolymorphicInlining",
Test.class.getName());
OutputAnalyzer output2 = new OutputAnalyzer(pb2.start());
output2.shouldNotContain("TestPolymorphicInlining$Test$Child");
output2.shouldHaveExitValue(0);
}
static class Test {
private final int x = -1;
private static int n = 1000000;
private final Parent c1 = new Child1();
private final Parent c2 = new Child2();
private final Parent c3 = new Child3();

class Parent {
int foo() {
return x;
}
}

class Child1 extends Parent {
int foo() {
return x;
}
}

class Child2 extends Parent {
int foo() {
return x;
}
}

class Child3 extends Parent {
int foo() {
return x;
}
}

int invoke(Parent p) {
return p.foo();
}

public static void main(String[] args) throws Exception {
Test test = new Test();
int x = 0;
while (n-- > 0) {
x += test.invoke(test.c1) + test.invoke(test.c2) + test.invoke(test.c3);
}
}
}
}

0 comments on commit 5134d44

Please sign in to comment.