diff --git a/compiler/compile/OMRCompilation.cpp b/compiler/compile/OMRCompilation.cpp index 014b7a9667d..134d4c67d28 100644 --- a/compiler/compile/OMRCompilation.cpp +++ b/compiler/compile/OMRCompilation.cpp @@ -52,8 +52,9 @@ #include "env/CompilerEnv.hpp" #include "env/CompileTimeProfiler.hpp" #include "env/IO.hpp" -#include "env/ObjectModel.hpp" #include "env/KnownObjectTable.hpp" +#include "env/ObjectModel.hpp" +#include "env/OMRRetainedMethodSet.hpp" #include "env/PersistentInfo.hpp" #include "env/StackMemoryRegion.hpp" #include "env/TRMemory.hpp" @@ -287,6 +288,7 @@ OMR::Compilation::Compilation( _bitVectorPool(self()), _typeLayoutMap((LayoutComparator()), LayoutAllocator(self()->region())), _currentILGenCallTarget(NULL), + _retainedMethods(NULL), _tlsManager(*self()) { if (target != NULL) @@ -924,6 +926,49 @@ static int32_t strHash(const char *str) return result; } +/** + * \brief Print all bond methods of the root retained method set to the log + */ +static void traceBondMethods(TR::Compilation *comp) + { + if (!comp->getOption(TR_TraceOptDetails) && !comp->getOption(TR_TraceCG)) + { + return; + } + + traceMsg(comp, "\nBond methods:\n"); + + TR_ResolvedMethod *m = NULL; + bool haveBondMethods = false; + auto bondIter = comp->retainedMethods()->bondMethods(); + while (bondIter.next(&m)) + { + haveBondMethods = true; + traceMsg( + comp, + " %p %.*s.%.*s%.*s\n", + m->getNonPersistentIdentifier(), + m->classNameLength(), + m->classNameChars(), + m->nameLength(), + m->nameChars(), + m->signatureLength(), + m->signatureChars()); + } + + if (comp->compileRelocatableCode()) + { + TR_ASSERT_FATAL(!haveBondMethods, "bonds are meaningless in AOT compilation"); + traceMsg(comp, " (to be determined at load time)\n"); + } + else if (!haveBondMethods) + { + traceMsg(comp, " (none)\n"); + } + + traceMsg(comp, "\n"); + } + int32_t OMR::Compilation::compile() { @@ -1101,6 +1146,8 @@ int32_t OMR::Compilation::compile() self()->failCompilation("Aborting after IL Gen due to TR_TOSS_IL"); } + traceBondMethods(self()); + if (_recompilationInfo) _recompilationInfo->beforeCodeGen(); @@ -1262,11 +1309,70 @@ TR_YesNoMaybe OMR::Compilation::isCpuExpensiveCompilation(int64_t threshold) void OMR::Compilation::performOptimizations() { - _optimizer = TR::Optimizer::createOptimizer(self(), self()->getJittedMethodSymbol(), false); if (_optimizer) + { _optimizer->optimize(); + + if (self()->getOption(TR_DontInlineUnloadableMethods)) + { + // Check the inlining table to make sure that all inlined methods are + // guaranteed to outlive this body. + bool trace = self()->getOption(TR_TraceRetainedMethods); + if (trace) + { + traceMsg(self(), "dontInlineUnloadableMethods: check inlining table\n"); + } + + // Keepalives must be taken into account, since without them it might + // be possible for some inlined methods to be unloaded earlier. + OMR::RetainedMethodSet *retainedMethods = + self()->retainedMethods()->withKeepalivesAttested(); + + for (uint32_t i = 0; i < self()->getNumInlinedCallSites(); i++) + { + // The bci identifies a call site in the bytecode of the outermost + // method or a previous inlined site, so we already know that the + // call site outlives the JIT body and it's therefore OK to attest + // for the call site. + // + // We need to attest here because the compilation's root retained + // method set doesn't account for any call site attestation that + // was done during inlining on an inline call path that required at + // least one keepalive. For example, if A calls B calls C calls D, + // and if B, C, D are all inlined, and if B needed a keepalive, and + // if there was something useful to attest at the C->D call site, + // that attestation will be missing from the root set. + // + TR_ByteCodeInfo bci = self()->getInlinedCallSite(i)._byteCodeInfo; + if (trace) + { + traceMsg( + self(), + "check inlined site %u, bci=%d:%d\n", + i, + bci.getCallerIndex(), + bci.getByteCodeIndex()); + } + + retainedMethods->attestLinkedCalleeWillRemainLoaded(bci); + + TR_ResolvedMethod *m = self()->getInlinedResolvedMethod(i); + TR_ASSERT_FATAL( + retainedMethods->willRemainLoaded(m), + "unexpectedly inlined method that could get unloaded separately:\n" + " %p %.*s.%.*s%.*s", + m->getPersistentIdentifier(), + m->classNameLength(), + m->classNameChars(), + m->nameLength(), + m->nameChars(), + m->signatureLength(), + m->signatureChars()); + } + } + } } bool OMR::Compilation::incInlineDepth(TR::ResolvedMethodSymbol * method, TR::Node *callNode, bool directCall, TR_VirtualGuardSelection *guard, TR_OpaqueClassBlock *receiverClass, TR_PrexArgInfo *argInfo) @@ -2749,3 +2855,22 @@ OMR::Compilation::insertNewFirstBlock() return newFirstBlock; } + +OMR::RetainedMethodSet * +OMR::Compilation::retainedMethods() + { + if (_retainedMethods == NULL) + { + _retainedMethods = self()->createRetainedMethods(self()->getMethodBeingCompiled()); + } + + return _retainedMethods; + } + +OMR::RetainedMethodSet * +OMR::Compilation::createRetainedMethods(TR_ResolvedMethod *method) + { + OMR::RetainedMethodSet *parent = NULL; + TR::Region ®ion = self()->trMemory()->heapMemoryRegion(); + return new (region) OMR::RetainedMethodSet(self(), method, parent); + } diff --git a/compiler/compile/OMRCompilation.hpp b/compiler/compile/OMRCompilation.hpp index 5a929d780c7..f47e5f7ca21 100644 --- a/compiler/compile/OMRCompilation.hpp +++ b/compiler/compile/OMRCompilation.hpp @@ -63,6 +63,7 @@ namespace OMR { typedef OMR::Compilation CompilationConnector; } #include "infra/Flags.hpp" #include "infra/Link.hpp" #include "infra/List.hpp" +#include "infra/set.hpp" #include "infra/Stack.hpp" #include "infra/ThreadLocal.hpp" #include "optimizer/Optimizations.hpp" @@ -108,6 +109,7 @@ namespace TR { class Optimizer; } namespace TR { class Recompilation; } namespace TR { class RegisterMappedSymbol; } namespace TR { class ResolvedMethodSymbol; } +namespace OMR { class RetainedMethodSet; } namespace TR { class Symbol; } namespace TR { class SymbolReference; } namespace TR { class SymbolReferenceTable; } @@ -1155,6 +1157,26 @@ class OMR_EXTENSIBLE Compilation */ void setCurrentILGenCallTarget(TR_CallTarget *x) { _currentILGenCallTarget = x; } + /** + * \brief + * Get the set of methods that will remain loaded while the JIT body + * resulting from this compilation is still running. + * + * \return the root retained method set for this compilation + */ + OMR::RetainedMethodSet *retainedMethods(); + + /** + * \brief + * Create a root OMR::RetainedMethodSet for the given method. + * + * This is used to initialize _retainedMethods. A project can override this + * to instantiate a subclass instead. + * + * \return the newly created root retained method set + */ + OMR::RetainedMethodSet *createRetainedMethods(TR_ResolvedMethod *method); + private: void resetVisitCounts(vcount_t, TR::ResolvedMethodSymbol *); int16_t restoreInlineDepthUntil(int32_t stopIndex, TR_ByteCodeInfo ¤tInfo); @@ -1364,6 +1386,8 @@ class OMR_EXTENSIBLE Compilation TR_CallTarget *_currentILGenCallTarget; + OMR::RetainedMethodSet *_retainedMethods; + /* * This must be last * NOTE: TLS for Compilation needs to be set before any object that may use it is initialized. diff --git a/compiler/control/OMROptions.cpp b/compiler/control/OMROptions.cpp index 6f6ecc9d67f..8d2e4b0841b 100644 --- a/compiler/control/OMROptions.cpp +++ b/compiler/control/OMROptions.cpp @@ -117,6 +117,11 @@ TR::OptionTable OMR::Options::_jitOptions[] = { "aggressive recompilation mechanism", TR::Options::setStaticNumeric, (intptr_t)&OMR::Options::_aggressiveRecompilationChances, 0, "F%d", NOT_IN_SUBSET}, {"aggressiveSwitchingToProfiling", "O\tAllow switching hot methods to profiling more aggressively", SET_OPTION_BIT(TR_AggressiveSwitchingToProfiling), "F" }, + {"allowJitBodyToOutliveInlinedCode", + "I\tAllow JIT body to outlive inlined code. " + "This avoids tracking which code will remain loaded. " + "The generated code may not be correct once inlined code is actually unloaded.", + SET_OPTION_BIT(TR_AllowJitBodyToOutliveInlinedCode), "F" }, {"allowVPRangeNarrowingBasedOnDeclaredType", "I\tallow value propagation to assume that integers declared " "narrower than 32-bits (boolean, byte, char, short) are in-range", @@ -646,6 +651,7 @@ TR::OptionTable OMR::Options::_jitOptions[] = { {"dontIncreaseCountsForNonBootstrapMethods", "M\t", RESET_OPTION_BIT(TR_IncreaseCountsForNonBootstrapMethods), "F", NOT_IN_SUBSET }, // Xjit: option {"dontInline=", "O{regex}\tlist of callee methods to not inline", TR::Options::setRegex, offsetof(OMR::Options, _dontInline), 0, "P"}, + {"dontInlineUnloadableMethods", "O\trefuse to inline methods that could be unloaded before the outermost method", SET_OPTION_BIT(TR_DontInlineUnloadableMethods), "F"}, {"dontJitIfSlotsSharedByRefAndNonRef", "O\tfail the compilation (in FSD mode) if a slot needs to be shared between an address and a nonaddress.", SET_OPTION_BIT(TR_DontJitIfSlotsSharedByRefAndNonRef), "F"}, {"dontLowerCountsForAotCold", "M\tDo not lower counts for cold aot runs", RESET_OPTION_BIT(TR_LowerCountsForAotCold), "F", NOT_IN_SUBSET }, {"dontRestrictInlinerDuringStartup", "O\tdo not restrict trivial inliner during startup", RESET_OPTION_BIT(TR_RestrictInlinerDuringStartup), "F", NOT_IN_SUBSET}, @@ -1277,6 +1283,7 @@ TR::OptionTable OMR::Options::_jitOptions[] = { {"traceRelocatableDataDetailsCG", "L\ttrace relocation data details when generating relocatable code", SET_OPTION_BIT(TR_TraceRelocatableDataDetailsCG), "P"}, {"traceRematerialization", "L\ttrace rematerialization", TR::Options::traceOptimization, rematerialization, 0, "P"}, {"traceReorderArrayIndexExpr", "L\ttrace reorder array index expressions", TR::Options::traceOptimization, reorderArrayIndexExpr, 0, "P"}, + {"traceRetainedMethods", "L\ttrace retained methods", SET_OPTION_BIT(TR_TraceRetainedMethods), "P" }, {"traceSamplingJProfiling", "L\ttrace samplingjProfiling", TR::Options::traceOptimization, samplingJProfiling, 0, "P"}, {"traceSEL", "L\ttrace sign extension load", TR::Options::traceOptimization, signExtendLoads, 0, "P"}, {"traceSequenceSimplification", "L\ttrace arithmetic sequence simplification", TR::Options::traceOptimization, expressionsSimplification, 0, "P"}, diff --git a/compiler/control/OMROptions.hpp b/compiler/control/OMROptions.hpp index fd6bec07002..a5345e32c4a 100644 --- a/compiler/control/OMROptions.hpp +++ b/compiler/control/OMROptions.hpp @@ -410,7 +410,7 @@ enum TR_CompilationOptions TR_DisableNewX86VolatileSupport = 0x04000000 + 10, // Available = 0x08000000 + 10, // Available = 0x10000000 + 10, - // Available = 0x20000000 + 10, + TR_AllowJitBodyToOutliveInlinedCode = 0x20000000 + 10, // Available = 0x40000000 + 10, TR_DisableDirectToJNIInline = 0x80000000 + 10, @@ -658,7 +658,7 @@ enum TR_CompilationOptions // Available = 0x00040000 + 19, // Available = 0x00080000 + 19, // Available = 0x00100000 + 19, - // Available = 0x00200000 + 19, + TR_TraceRetainedMethods = 0x00200000 + 19, // Available = 0x00400000 + 19, // Available = 0x00800000 + 19, TR_EnableRATPurging = 0x01000000 + 19, // enable periodic RAT table purging @@ -823,7 +823,7 @@ enum TR_CompilationOptions TR_PerfTool = 0x00010000 + 25, // Available = 0x00020000 + 25, TR_DisableBranchOnCount = 0x00040000 + 25, - // Available = 0x00080000 + 25, + TR_DontInlineUnloadableMethods = 0x00080000 + 25, TR_DisableLoopEntryAlignment = 0x00100000 + 25, TR_EnableLoopEntryAlignment = 0x00200000 + 25, TR_DisableLeafRoutineDetection = 0x00400000 + 25, diff --git a/compiler/env/CMakeLists.txt b/compiler/env/CMakeLists.txt index 6ba52fe0147..29afd0e6107 100644 --- a/compiler/env/CMakeLists.txt +++ b/compiler/env/CMakeLists.txt @@ -27,6 +27,7 @@ compiler_library(env ${CMAKE_CURRENT_LIST_DIR}/OMRArithEnv.cpp ${CMAKE_CURRENT_LIST_DIR}/OMRClassEnv.cpp ${CMAKE_CURRENT_LIST_DIR}/OMRDebugEnv.cpp + ${CMAKE_CURRENT_LIST_DIR}/OMRRetainedMethodSet.cpp ${CMAKE_CURRENT_LIST_DIR}/OMRVMEnv.cpp ${CMAKE_CURRENT_LIST_DIR}/OMRVMMethodEnv.cpp ${CMAKE_CURRENT_LIST_DIR}/SegmentAllocator.cpp diff --git a/compiler/env/OMRRetainedMethodSet.cpp b/compiler/env/OMRRetainedMethodSet.cpp new file mode 100644 index 00000000000..e60387d277d --- /dev/null +++ b/compiler/env/OMRRetainedMethodSet.cpp @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright IBM Corp. and others 2024 + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution + * and is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License, v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception [1] and GNU General Public + * License, version 2 with the OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] https://openjdk.org/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 OR GPL-2.0-only WITH OpenJDK-assembly-exception-1.0 + *******************************************************************************/ + +#include "env/OMRRetainedMethodSet.hpp" + +#include "compile/Compilation.hpp" +#include "compile/ResolvedMethod.hpp" +#include "infra/Assert.hpp" + +OMR::RetainedMethodSet::RetainedMethodSet( + TR::Compilation *comp, + TR_ResolvedMethod *method, + OMR::RetainedMethodSet *parent) + : _comp(comp) + , _method(method) + , _parent(parent) + , _keepaliveMethods(comp->trMemory()->heapMemoryRegion()) + , _bondMethods(comp->trMemory()->heapMemoryRegion()) + { + // empty + } + +void +OMR::RetainedMethodSet::keepalive() + { + flatten(FlattenMode_Keepalive); + } + +void +OMR::RetainedMethodSet::bond() + { + flatten(FlattenMode_Bond); + } + +void +OMR::RetainedMethodSet::flatten(FlattenMode mode) + { + TR_ASSERT_FATAL(_parent != NULL, "cannot flatten the root set"); + + if (_method == NULL) + { + return; // already done + } + + const char *flattenKindName = NULL; // only for tracing + if (_comp->getOption(TR_TraceRetainedMethods)) + { + flattenKindName = mode == FlattenMode_Keepalive ? "keepalive" : "bond"; + traceMsg( + _comp, + "RetainedMethodSet %p: %s %p %.*s.%.*s%.*s\n", + this, + flattenKindName, + _method->getNonPersistentIdentifier(), + _method->classNameLength(), + _method->classNameChars(), + _method->nameLength(), + _method->nameChars(), + _method->signatureLength(), + _method->signatureChars()); + } + + // If keepalive has already happened for the parent, then anything + // propagated to the parent now would fail to be further propagated up + // toward the root set. + TR_ASSERT_FATAL(_parent->_method != NULL, "must keepalive in bottom up order"); + + if (!_parent->willRemainLoaded(_method)) + { + void *key = unloadingKey(_method); + if (mode == FlattenMode_Keepalive) + { + _parent->_keepaliveMethods.insert(std::make_pair(key, _method)); + } + else + { + _parent->_bondMethods.insert(std::make_pair(key, _method)); + } + + if (_comp->getOption(TR_TraceRetainedMethods)) + { + traceMsg( + _comp, + "RetainedMethodSet %p: added %s method %p (key %p)\n", + _parent, + flattenKindName, + _method->getNonPersistentIdentifier(), + key); + } + } + + _parent->propagateMethods(_parent->_keepaliveMethods, _keepaliveMethods); + _parent->propagateMethods(_parent->_bondMethods, _bondMethods); + + // If unloadable methods are allowed to be inlined without a keepalive, then + // they will be inlined with a bond. Keepalives only matter if all bonds are + // satisfied, so it's important to ignore keepalives when determining what + // will remain loaded without a bond (for the purpose of determining whether + // to create a bond). + // + // NOTE: When TR_DontInlineUnloadableMethods is set, we still have to skip + // this for keepalives, even though there won't be any bonds. Otherwise, + // assignKeepaliveConstRefLabels() will think all keepalives are redundant. + // + if (mode == FlattenMode_Bond) + { + // Merge even if the parent already tells us that _method is guaranteed to + // remain loaded. There may have been code attested to remain loaded in this + // set but not yet in the parent. + mergeIntoParent(); + TR_ASSERT_FATAL( + _parent->willRemainLoaded(_method), + "method should now be guaranteed to remain loaded"); + } + + _method = NULL; + } + +OMR::RetainedMethodSet * +OMR::RetainedMethodSet::createChild(TR_ResolvedMethod *method) + { + TR_ASSERT_FATAL(false, "unimplemented: OMR::RetainedMethodSet::createChild"); + } + +OMR::RetainedMethodSet * +OMR::RetainedMethodSet::withKeepalivesAttested() + { + return this; + } + +void +OMR::RetainedMethodSet::mergeIntoParent() + { + TR_ASSERT_FATAL(false, "unimplemented: OMR::RetainedMethodSet::mergeIntoParent"); + } + +void * +OMR::RetainedMethodSet::unloadingKey(TR_ResolvedMethod *method) + { + return method->getNonPersistentIdentifier(); + } + +void +OMR::RetainedMethodSet::propagateMethods(MethodMap &dest, const MethodMap &src) + { + for (auto it = src.begin(), end = src.end(); it != end; it++) + { + void *key = it->first; + TR_ResolvedMethod *method = it->second; + if (!willRemainLoaded(method)) + { + dest.insert(std::make_pair(key, method)); + } + } + } diff --git a/compiler/env/OMRRetainedMethodSet.hpp b/compiler/env/OMRRetainedMethodSet.hpp new file mode 100644 index 00000000000..72d402cadea --- /dev/null +++ b/compiler/env/OMRRetainedMethodSet.hpp @@ -0,0 +1,526 @@ +/******************************************************************************* + * Copyright IBM Corp. and others 2024 + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution + * and is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License, v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception [1] and GNU General Public + * License, version 2 with the OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] https://openjdk.org/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 OR GPL-2.0-only WITH OpenJDK-assembly-exception-1.0 + *******************************************************************************/ + +#ifndef OMR_RETAINEDMETHODSET_INCL +#define OMR_RETAINEDMETHODSET_INCL + +#include +#include "env/jittypes.h" +#include "infra/map.hpp" + +namespace TR { class Compilation; } +class TR_ResolvedMethod; + +namespace OMR { + +/** + * \brief A RetainedMethodSet represents the set of methods that are guaranteed + * to remain live under some assumptions. + * + * The main such assumption is that some particular method is live. + * + * In a project that allows methods to be unloaded, this is useful to prevent a + * JIT body from outliving any code that has been inlined into it. For more on + * the motivation for such prevention, see the "Motivation" section below. + * + * The set is not necessarily complete. There may be methods that will remain + * loaded that cannot be or simply have not been identified as such. + * + * Each instance can have a parent and thereby belong to a tree. The parent + * represents the set of methods that are guaranteed to remain loaded in some + * surrounding context. This facility is used during inlining to track for each + * call path the set of methods that will be guaranteed to remain loaded on the + * assumption that that call path will be inlined. + * + * Often, the method named at a call site and the chosen call target will be + * guaranteed to remain loaded, in which case the RetainedMethodSet can be + * shared between the caller and the callee. But sometimes it's desireable to + * inline a method that could (as far as we know) be unloaded before the + * outermost method, and it may be justifiable in order to do so to arrange + * somehow for it to outlive the compiled code that will result from the + * current compilation. In such a case, we create a child RetainedMethodSet + * that represents the set of methods that will remain loaded at least as long + * as the JIT body, assuming that such an arrangement is made. When the method + * is actually inlined, it's necessary to commit to put that arrangement into + * effect, in a way flattening the child back into the parent. This flattening + * must be done in a bottom-up fashion, which is how physical inlining + * proceeds. + * + * This class does not know how to determine the relationships between methods + * in a given project. It is designed to be subclassed in any project that + * allows unloading. The default implementation assumes that all methods are + * guaranteed to remain live/loaded, which is suitable in projects that don't + * support unloading, or when unloading is disabled at runtime, or for + * debugging if unloading can be assumed not to occur. + * + * \b Motivation + * + * Why prevent the JIT body from outliving methods inlined into it? First code + * motion, and then the more proximate motivation of constant references. + * + * It has historically been possible for a JIT body to continue to run even + * after one of its inlined methods has been unloaded. A guard would be patched + * to prevent execution from entering the inlined body. However, this strategy + * of patching guards is by itself insufficient to allow for code motion. + * + * Code motion can cause an expression to be evaluated speculatively. The + * speculative location might no longer be protected by the expected guard, and + * the expression might not be safe to evaluate after unloading, e.g. it might + * use instanceof to check that an object is an instance of a type that no + * longer exists, or it might access a static field that no longer exists. + * + * Loop versioner has attempted to avoid the instanceof situation by checking + * isUnloadAssumptionRequired() in a way that's both unnecessarily conservative + * and most likely incomplete. It's easy to accidentally assume that as the JIT + * body continues to run, the inlined methods (or rather, whatever they refer + * to) will remain loaded. To prevent such assumptions from becoming bugs, we + * can make them hold. + * + * Additionally, using the information represented here, it may be possible to + * provide a more precise implementation of isUnloadAssumptionRequired() for + * cases where it will still be necessary, e.g. for profiled checkcast types. + * + * Now on to the topic of constant references. Historically "constant folding" + * of references has been done by leaving the original load in place but + * replacing its symref with an "improved" version that has a known object + * index. That is, the folding itself has been treated as an analysis result + * recorded in the IL - much like a node flag - rather than as a transformation + * that puts the IL into a form in which the constant value is naturally + * guaranteed, as we would for a primitive constant (e.g. iconst). As a result, + * it has been correct to "fold" a reference only if it's possible to guarantee + * that repeating the original operation (at its point of occurrence) will + * always produce the same result. However, there are cases where it is + * desirable to allow folding despite the possibility of a later mutation. For + * this reason (and another performance reason), constant references will allow + * the compiler to treat references in nearly the same way as primitives when + * they are constant. Instead of adding a known object index as an analysis + * result, we'll transform the node so that it always produces the expected + * reference. This will be achieved by creating a fresh immutable reference and + * loading from that instead of doing the original operation. See this OpenJ9 + * issue for more detail: + * + * https://github.com/eclipse-openj9/openj9/issues/16616 + * + * Please excuse the prevalence of Java in the examples below. It's the most + * expedient way to illustrate, but the concepts should be relatively general. + * + * With that context, consider the lifetime of a constant reference. It exists + * for the benefit of a particular JIT body, and as long as that JIT body might + * run, it might make use of the reference, so the reference must still exist. + * For simplicity, no attempt will be made to track "which part" of the JIT + * body needs the constant. And clearly once the JIT body can't run anymore, + * the constant reference will be unused, and there will be no point in its + * continued existence. So the constant reference should have exactly the same + * lifetime as the JIT body - or really, the bulk of the JIT body, ignoring the + * stub left behind by invalidation. And indeed the reference will be embedded + * in the JIT body. We can think of it as though the JIT body were an object + * and the constant reference were one of its fields. In particular, as long as + * the JIT body exists and is valid, the constant reference will keep its + * referent from being collected. + * + * For the sake of example, suppose that the unit of unloading is the class, + * and consider these Java classes: + * + * public abstract class AbstractFoo { + * public abstract void foo(); + * } + * + * public class Bar { + * public static void callFoo(AbstractFoo f) { f.foo(); } + * } + * + * public class Foo extends AbstractFoo { + * private static final Foo TOTAL = new Foo(); + * private int n; + * public void foo() { TOTAL.n++; n++; } + * ... + * } + * + * The program could dynamically load Foo, pass instances of it to callFoo() + * for a while, and then eventually stop using Foo altogether and allow it to + * be unloaded. + * + * If callFoo() is compiled and Foo.foo() is inlined into it (maybe based on + * profiling, or maybe because it's a single implementer), and if TOTAL is + * folded, and crucially if the JIT body of callFoo() is allowed to outlive + * Foo, then it will live as long as Bar, and there will essentially be a + * a hidden reference from Bar to Foo.TOTAL, which will prevent Foo from being + * unloaded. This example memory leak is very small, but there is no limit to + * the size of the object graph that could be leaked in this way. + * + * This is where RetainedMethodSet comes in. It will either prevent Foo.foo() + * from getting inlined (dontInlineUnloadableMethods), or with inlining it will + * take note that the JIT body can't be allowed to outlive Foo (bond). With + * cooperation from the downstream compiler, a runtime assumption will be + * created to invalidate the JIT body when Foo is unloaded. With cooperation + * from the GC, the constant reference belonging to callFoo() will be scanned + * only when Foo is still live. Once the GC fails to find that Foo is live, it + * will ignore this constant reference, so it won't prevent unloading, and Foo + * will be unloaded as expected. The unloading will invalidate the JIT body so + * that the ignored reference won't be subsequently used. + * + * Why is it ever okay to keep a hidden reference to an object if the original + * reference might change? i.e. why is inlining singled out? There are three + * reasons: + * + * 1. Without the problematic sort of inlining situation described above, there + * can be a leak only if the fields considered constant by the compiler + * are actually mutated later on. With the inlining, as can be seen from the + * example, it would be possible to leak memory without such mutation. + * + * 2. Keeping a hidden reference to the object is necessary in order to allow + * constant folding in the first place. If we can't do so, then we have no + * business folding constants at all. + * + * 3. With JIT bodies prevented from outliving their inlined methods, it will + * be possible to explain why a particular object is still referenced purely + * in terms of constant folding, e.g. Foo.foo() referred to this object via + * Foo.TOTAL, which was constant folded, and Foo.foo() still exists, so this + * object can still be referenced. We avoid unnecessarily allowing inlining + * to factor into this kind of justification. Though of course inlining will + * still affect which expressions the compiler sees, and therefore which it + * bothers to fold, and an effort will be made not to keep unnecessary + * references, it's extraneous in terms of justifying why the compiler + * should be \e allowed to keep a reference to an object. + * + * \e Keepalive. The keepalive facility is to be used in cases where a call + * site has been refined based on a known object that reflectively identifies a + * method to be called. This is essentially another kind of constant folding, + * but where the constant is a method instead of an IL value. Since we would be + * justified in keeping a reference to the reflective object, which would + * prevent the method it represents from being unloaded (while the JIT body is + * still live), we should be equally justified in arranging to keep a reference + * specifically in order to prevent the method from being unloaded. This is + * useful in cases where later mutation is possible but can be correctly + * ignored, e.g. when customizing a MethodHandle in Java. Without a keepalive, + * later mutation could cause the original method to become unreachable and to + * be unloaded, unnecessarily invalidating the JIT body into which it was + * inlined. + * + * Keepalives are needed separately from the original known objects because + * constant references will only be created for known objects that might be + * used at runtime, i.e. those that are still loaded somewhere in the IL at + * code generation time. + * + * In all analysis, bonds (which restrict the lifetime of the JIT body) take + * precedence over keepalives (which extend the lifetime of inlined methods). + * To see why, consider another Java example: + * + * public abstract class AbstractBase { + * public abstract void callee(); + * } + * + * public class Caller { + * public static void callTwice(AbstractBase b1, AbstractBase b2) { + * b1.callee(); + * b2.callee(); + * } + * } + * + * public class Derived1 extends AbstractBase { + * private static final MethodHandle DERIVED2_FOO = ...; // Derived2.foo() + * public void callee() { DERIVED2_FOO.invokeExact(); } + * public static void foo() { ... } + * } + * + * public class Derived2 extends AbstractBase { + * private static final MethodHandle DERIVED1_FOO = ...; // Derived1.foo() + * public void callee() { DERIVED1_FOO.invokeExact(); } + * public static void foo() { ... } + * } + * + * The program could dynamically load Derived1 and Derived2 (separately, so + * that it's only the MethodHandles tying their lifetimes together), spend a + * while passing instances of them as b1 and b2, resp., to callTwice(), and + * then eventually stop using them and allow them to be unloaded. + * + * Suppose that callTwice() is compiled, and that Derived1.callee() and + * Derived2.callee() are inlined at the call sites for b1 and b2, resp. + * If there is no further inlining, then they will be inlined with bonds, and + * unloading will work as normal. If OTOH the foo() calls were also inlined, + * they would be inlined with keepalives. If keepalives took precedence over + * bonds, then these keepalives would cause callTwice() to prevent unloading of + * both Derived1 and Derived2, even though they're supposed to be unloadable, + * and even though they would have been unloadable with less inlining. + * + * Because of the possibility of this kind of situation, all keepalives and + * bonds are collected toward the root set, but whenever a bond and a keepalive + * both apply to the same method, the bond is used and the keepalive is ignored. + * Essentially, if there is one call path that handles that method using bond, + * then a bond will be used even if other call paths handle the same method + * using keepalive. + * + * Keepalives are intended to be implemented as additional constant references. + * To see why this scheme successfully prevents the JIT body from outliving its + * inlined methods, note that every inlined method is covered by one of the + * following cases: + * + * 1. Analysis found that even without any intervention it's guaranteed to live + * at least as long as the outermost method, or + * + * 2. A bond will be used, or + * + * 3. A keepalive will be used. + * + * If the JIT body is live, then all inlined methods in case 1 and 2 are live + * as well. Those in case 1 simply can't be unloaded before the outermost + * method, and if one in case 2 were unloaded, then the JIT body would have + * been invalidated. Since the outermost method and all of the bonded methods + * are still loaded, the constant references are live, including all of the + * keepalive references for inlined methods in case 3, so those must still be + * loaded too. + */ +class RetainedMethodSet + { + private: + // The key is the unloadingKey() of the method. + // + // If any insertion into a MethodMap fails, then it already contains an + // equivalent entry, so we'll choose arbitrarily to keep the existing entry. + // + typedef TR::map MethodMap; + + public: + + /** + * \brief Construct a RetainedMethodSet. + * + * \param comp the compilation object + * \param method the assumed-live method + * \param parent the parent RetainedMethodSet + */ + RetainedMethodSet( + TR::Compilation *comp, + TR_ResolvedMethod *method, + OMR::RetainedMethodSet *parent); + + /** + * \brief Determine whether \p method is guaranteed to remain loaded. + * + * A positive result (true) is reliable, but because this set is generally + * incomplete, a negative result (false) could be spurious. + * + * The default implementation always gives a positive result. + * + * \param method the method in question + * \return true if it is known to remain loaded, false otherwise + */ + virtual bool willRemainLoaded(TR_ResolvedMethod *method) { return true; } + + /** + * \brief Inspect the call site corresponding to \p bci in the bytecode and + * attempt to extend this set in-place if possible. + * + * Because this set is generally incomplete, there may be methods that will + * in fact remain loaded but that are not yet known to do so, and some such + * methods may be evident when inspecting the call site. + * + * The default implementation does nothing. + * + * \param bci bytecode info of the call site to inspect + */ + virtual void attestLinkedCalleeWillRemainLoaded(TR_ByteCodeInfo bci) {} + + /** + * \brief Arrange to keep alive this set's assumed-live method. + * + * This set must be a child set, and it is flattened into its parent as + * appropriate. In particular, the parent will be updated to specify that + * this set's method is to be kept alive. + * + * Usually the method will not already be known to be guaranteed to remain + * loaded in the parent. But it's possible if the parent has been modified + * in the meantime since this child was created, e.g. due to an attestation + * or a bond. In this case, the parent does not necessarily need to + * represent the keepalive. + */ + virtual void keepalive(); + + /** + * \brief Arrange to invalidate the JIT body if this set's assumed-live + * method is unloaded. + * + * This set must be a child set, and it is flattened into its parent as + * appropriate. In particular, the parent will be updated to specify that + * this set's method is to be bonded. + * + * Usually the method will not already be known to be guaranteed to remain + * loaded in the parent. But it's possible if the parent has been modified + * in the meantime since this child was created, e.g. due to an attestation + * or a different bond. In this case, the parent does not necessarily need + * to represent the bond. + */ + virtual void bond(); + + /** + * \brief Create a new child of this RetainedMethodSet with \p method as its + * assumed-live method. + * + * Subclasses should override this to instantiate the appropriate type (most + * likely the overriding subclass itself). + * + * This method must only be called in cases where this RetainedMethodSet + * does not guarantee that \p method will remain loaded. The default + * implementation is an "unimplemented" assertion. + * + * \param method the assumed-live method + * \return a new instance of RetainedMethodSet + */ + virtual OMR::RetainedMethodSet *createChild(TR_ResolvedMethod *method); + + /** + * \brief Get a RetainedMethodSet representing all methods known to remain + * loaded in this set and also all of this set's keepalives. + * + * It is unspecified whether or not the result is a child set, so in general + * it must not be treated as one. + * + * It is possible to call attestLinkedCalleeWillRemainLoaded() on the result + * without modifying the original receiver. + * + * The default implementation simply returns the receiver. + * + * \param method the assumed-live method + * \return a new instance of RetainedMethodSet + */ + virtual OMR::RetainedMethodSet *withKeepalivesAttested(); + + /** + * \brief An iterator that yields some number of TR_ResolvedMethod pointers. + */ + class ResolvedMethodIter + { + private: + friend class OMR::RetainedMethodSet; + ResolvedMethodIter(const MethodMap &map) + : _cur(map.begin()), _end(map.end()) {} + + public: + + /** + * \brief Get the next TR_ResolvedMethod if there is one. + * \param[out] out the resulting TR_ReslovedMethod + * \return true if \p out was set to the next value, or false if there + * were no remaining values + */ + bool next(TR_ResolvedMethod **out) + { + if (_cur == _end) + { + *out = NULL; + return false; + } + else + { + *out = _cur->second; + _cur++; + return true; + } + } + + private: + MethodMap::const_iterator _cur; + MethodMap::const_iterator _end; + }; + + /** + * \brief Get the methods to keep alive at least as long as the JIT body. + * \return an iterator that yields the methods to keep alive + */ + ResolvedMethodIter keepaliveMethods() + { + return ResolvedMethodIter(_keepaliveMethods); + } + + /** + * \brief Get the methods to bond (limit the lifetime of the JIT body). + * \return an iterator that yields the methods to bond + */ + ResolvedMethodIter bondMethods() + { + return ResolvedMethodIter(_bondMethods); + } + + protected: + + /** + * \brief Merge the representation of this set into the parent. + * + * This is used when flattening to expand the set of methods known to remain + * loaded in the parent. It should be overridden in subclasses. The default + * implementation is an "unimplemented" assertion. + */ + virtual void mergeIntoParent(); + + /** + * \brief Determine the unloading key of \p method. + * + * For any two methods with the same unloading key, if one of the methods is + * later unloaded, the other must be unloaded along with it. There are no + * other restrictions on the value of the unloading key. + * + * The default implementation returns getNonPersistentIdentifier(), which is + * always a safe choice because any two TR_ResolvedMethod instances that + * share that value already fundamentally represent the same method. + * + * Subclasses can override this to provide a coarser partition as long as it + * satisfies the property stated above. A coarser partition will result in + * less redundancy in MethodMap. + * + * \param method the method + * \return the unloading key + */ + virtual void *unloadingKey(TR_ResolvedMethod *method); + + TR::Compilation *comp() { return _comp; } + TR_ResolvedMethod *method() { return _method; } + OMR::RetainedMethodSet *parent() { return _parent; } + + private: + + /** + * \brief Add each method in \p src to \p dest unless it willRemainLoaded(). + * + * \p dest must be either the _keepaliveMethods or _bondMethods of this set. + * + * \param dest the destination map + * \param src the source map + */ + void propagateMethods(MethodMap &dest, const MethodMap &src); + + enum FlattenMode + { + FlattenMode_Keepalive, + FlattenMode_Bond, + }; + + void flatten(FlattenMode mode); // implementation of keepalive and bond + + TR::Compilation * const _comp; + TR_ResolvedMethod *_method; + OMR::RetainedMethodSet *_parent; + MethodMap _keepaliveMethods; + MethodMap _bondMethods; + }; + +} // namespace TR + +#endif // OMR_RETAINEDMETHODSET_INCL diff --git a/compiler/il/OMRNode.cpp b/compiler/il/OMRNode.cpp index 18656d168c8..da4cc99f0ac 100644 --- a/compiler/il/OMRNode.cpp +++ b/compiler/il/OMRNode.cpp @@ -5894,7 +5894,18 @@ OMR::Node::setIsSafeForCGToFastPathUnsafeCall(bool v) _flags.set(unsafeFastPathCall); } +bool +OMR::Node::isCallThatWasRefinedFromKnownObject() + { + return self()->getOpCode().isCall() && _flags.testAny(wasRefinedFromKnownObject); + } +void +OMR::Node::setCallWasRefinedFromKnownObject() + { + TR_ASSERT_FATAL(self()->getOpCode().isCall(), "Opcode must be call"); + _flags.set(wasRefinedFromKnownObject); + } bool OMR::Node::containsCompressionSequence() diff --git a/compiler/il/OMRNode.hpp b/compiler/il/OMRNode.hpp index 4510ecab9a9..cb07b8c8e98 100644 --- a/compiler/il/OMRNode.hpp +++ b/compiler/il/OMRNode.hpp @@ -1141,6 +1141,8 @@ class OMR_EXTENSIBLE Node bool chkThrowInsertedByOSR(); // Flags used by call nodes + bool isCallThatWasRefinedFromKnownObject(); + void setCallWasRefinedFromKnownObject(); bool isTheVirtualCallNodeForAGuardedInlinedCall(); void setIsTheVirtualCallNodeForAGuardedInlinedCall(); void resetIsTheVirtualCallNodeForAGuardedInlinedCall(); @@ -1850,6 +1852,7 @@ class OMR_EXTENSIBLE Node ThrowInsertedByOSR = 0x00004000, // Flags used by call nodes + wasRefinedFromKnownObject = 0x00000400, // Symref came from constant folding. virtualCallNodeForAGuardedInlinedCall = 0x00000800, ///< virtual calls dontTransformArrayCopyCall = 0x00000800, ///< arraycopy call - static nodeIsRecognizedArrayCopyCall = 0x00010000, diff --git a/compiler/optimizer/CallInfo.hpp b/compiler/optimizer/CallInfo.hpp index a7a91641b53..5b08285d0f8 100644 --- a/compiler/optimizer/CallInfo.hpp +++ b/compiler/optimizer/CallInfo.hpp @@ -61,6 +61,8 @@ namespace TR { class Method; } namespace TR { class ResolvedMethodSymbol; } namespace TR { class SymbolReference; } namespace TR { class TreeTop; } +namespace OMR { class RetainedMethodSet; } + class TR_CallSite; struct TR_VirtualGuardSelection; @@ -227,6 +229,9 @@ struct TR_CallTarget : public TR_Link TR_PrexArgInfo *_prexArgInfo; // used by computePrexInfo to calculate prex on generatedIL and transform IL TR_PrexArgInfo *_ecsPrexArgInfo; // used by ECS and findInlineTargets to assist in choosing correct inline targets + OMR::RetainedMethodSet *_retainedMethods; + bool _needsBond; + /** * \brief Constant values that were observed during call target selection * within this particular call target, keyed on bytecode index. @@ -269,7 +274,9 @@ struct TR_CallTarget : public TR_Link TR_ByteCodeInfo & bcInfo, \ TR::Compilation *comp, \ int32_t depth, \ - bool allConsts) : \ + bool allConsts, \ + OMR::RetainedMethodSet *retainedMethods, \ + bool wasRefinedFromKnownObject) : \ BASE (callerResolvedMethod, \ callNodeTreeTop, \ parent, \ @@ -285,7 +292,9 @@ struct TR_CallTarget : public TR_Link bcInfo, \ comp, \ depth, \ - allConsts) + allConsts, \ + retainedMethods, \ + wasRefinedFromKnownObject) #define TR_CALLSITE_EMPTY_CONSTRUCTOR_BODY { }; @@ -330,7 +339,9 @@ class TR_CallSite : public TR_Link, private TR::Uncopyable TR_ByteCodeInfo & bcInfo, TR::Compilation *comp, int32_t depth, - bool allConsts); + bool allConsts, + OMR::RetainedMethodSet *retainedMethods, + bool wasRefinedFromKnownObject); TR_InlinerFailureReason getCallSiteFailureReason() { return _failureReason; } //Call Site Specific @@ -418,6 +429,9 @@ class TR_CallSite : public TR_Link, private TR::Uncopyable // we propagate from calltarget to callee callsite when appropriate in ecs TR_PrexArgInfo *_ecsPrexArgInfo; // used by ECS and findInlineTargets to assist in choosing correct inline targets + OMR::RetainedMethodSet *_retainedMethods; + bool _needsKeepalive; + //new findInlineTargets API virtual bool findCallSiteTarget (TR_CallStack *callStack, TR_InlinerBase* inliner); virtual const char* name () { return "TR_CallSite"; } diff --git a/compiler/optimizer/Inliner.cpp b/compiler/optimizer/Inliner.cpp index bd106947320..cee1fa3c174 100644 --- a/compiler/optimizer/Inliner.cpp +++ b/compiler/optimizer/Inliner.cpp @@ -55,6 +55,7 @@ #include "env/CompilerEnv.hpp" #include "env/ObjectModel.hpp" #include "env/PersistentInfo.hpp" +#include "env/OMRRetainedMethodSet.hpp" #include "env/StackMemoryRegion.hpp" #include "env/TRMemory.hpp" #include "env/jittypes.h" @@ -4230,8 +4231,18 @@ void TR_InlinerBase::applyPolicyToTargets(TR_CallStack *callStack, TR_CallSite * } } + if (comp()->getOption(TR_DontInlineUnloadableMethods) + && !callsite->_retainedMethods->willRemainLoaded(calltarget->_calleeMethod)) + { + tracer()->insertCounter(Unloadable_Callee, callsite->_callNodeTreeTop); + callsite->removecalltarget(i, tracer(), Unloadable_Callee); + i--; + continue; + } + int32_t bytecodeSize = getPolicy()->getInitialBytecodeSize(calltarget->_calleeMethod, calltarget->_calleeSymbol, comp()); + if (!forceInline(calltarget)) getUtil()->estimateAndRefineBytecodeSize(callsite, calltarget, callStack, bytecodeSize); @@ -4958,6 +4969,20 @@ bool TR_InlinerBase::inlineCallTarget2(TR_CallStack * callStack, TR_CallTarget * return true; } + // NOTE: There could be both a bond and a keepalive here, if e.g. the call + // site is an indirect call site that was refined based on constant folding, + // but the keepalive doesn't cover the call target. + + if (calltarget->_needsBond) + { + calltarget->_retainedMethods->bond(); + } + + if (calltarget->_myCallSite->_needsKeepalive) + { + calltarget->_myCallSite->_retainedMethods->keepalive(); + } + comp()->incInlinedCalls(); if (comp()->trace(OMR::inlining)) @@ -5627,6 +5652,8 @@ TR_CallTarget::TR_CallTarget(TR::Region &memRegion, _frequencyAdjustment(freqAdj), _prexArgInfo(NULL), _ecsPrexArgInfo(ecsPrexArgInfo), + _retainedMethods(callsite->_retainedMethods), + _needsBond(false), _requiredConsts(memRegion) { _weight=0; @@ -5646,6 +5673,17 @@ TR_CallTarget::TR_CallTarget(TR::Region &memRegion, _failureReason=InlineableTarget; _size=-1; _calleeMethodKind = TR::MethodSymbol::Virtual; + + TR_ASSERT_FATAL( + _calleeMethod != NULL, + "TR_CallTarget %p has no _calleeMethod. What are we supposed to inline?", + this); + + if (!_retainedMethods->willRemainLoaded(_calleeMethod)) + { + _retainedMethods = _retainedMethods->createChild(_calleeMethod); + _needsBond = true; // if this target is actually inlined + } } const char* @@ -5673,7 +5711,9 @@ TR_CallSite::TR_CallSite(TR_ResolvedMethod *callerResolvedMethod, TR_ByteCodeInfo & bcInfo, TR::Compilation *comp, int32_t depth, - bool allConsts) : + bool allConsts, + OMR::RetainedMethodSet *retainedMethods, + bool wasRefinedFromKnownObject) : _callerResolvedMethod(callerResolvedMethod), _callNodeTreeTop(callNodeTreeTop), _cursorTreeTop(NULL), @@ -5697,12 +5737,69 @@ TR_CallSite::TR_CallSite(TR_ResolvedMethod *callerResolvedMethod, _allConsts(allConsts), _mytargets(0, comp->allocator()), _myRemovedTargets(0, comp->allocator()), - _ecsPrexArgInfo(0) + _ecsPrexArgInfo(0), + _retainedMethods(retainedMethods), + _needsKeepalive(false) { _visitCount=0; _failureReason=InlineableTarget; _byteCodeIndex = bcInfo.getByteCodeIndex(); _callerBlock = NULL; + + // Because the retained method set is generally incomplete, look at the call + // site in the bytecode and take into account any linked callee. + // + // This inspects the call site independently of initialCalleeMethod so that + // it happens even if that hasn't been specified yet, and also to make sure + // that it's independent of any refinement that may have happened in the + // trees based on e.g. preexistence. + // + // However! this call site may be for a helper or intrinsic call node, which + // won't be inlined, and which doesn't necessarily correspond to a call in + // the bytecode. In that case, the bcInfo doesn't necessarily identify a + // call instruction, so avoid treating it as though it does. + // + if (callNode == NULL + || (!callNode->getSymbolReference()->getSymbol()->castToMethodSymbol()->isHelper() + && !comp->getSymRefTab()->isNonHelper(callNode->getSymbolReference()))) + { + retainedMethods->attestLinkedCalleeWillRemainLoaded(bcInfo); + } + + // Arrange to create a keepalive if needed based on known object refinement. + if (wasRefinedFromKnownObject) + { + // initialCalleeMethod was refined based on known object information, + // i.e. constants that we're allowed to retain, so we can also retain the + // callee. However, note that (as usual) for an indirect call the best + // target might still not be guaranteed to remain loaded, if it's defined + // by a subtype of potentially shorter lifespan. + // + // If we have a call node, then we might not yet have initialCalleeMethod, + // but it must have been refined in the trees, and the refined method is + // found from the symbol. + // + TR_ResolvedMethod *refinedMethod = _initialCalleeMethod; + if (callNode != NULL) + { + TR_ASSERT_FATAL_WITH_NODE( + callNode, + callNode->isCallThatWasRefinedFromKnownObject(), + "wasRefinedFromKnownObject parameter inconsistent with node flag"); + refinedMethod = + callNode->getSymbol()->getResolvedMethodSymbol()->getResolvedMethod(); + } + + TR_ASSERT_FATAL( + refinedMethod != NULL, + "initialCalleeMethod unspecified despite supposed known object refinement"); + + if (!_retainedMethods->willRemainLoaded(refinedMethod)) + { + _needsKeepalive = true; + _retainedMethods = retainedMethods->createChild(refinedMethod); + } + } } const char* diff --git a/compiler/optimizer/InlinerFailureReason.hpp b/compiler/optimizer/InlinerFailureReason.hpp index 60e70b97406..cc28a14e637 100644 --- a/compiler/optimizer/InlinerFailureReason.hpp +++ b/compiler/optimizer/InlinerFailureReason.hpp @@ -49,6 +49,7 @@ enum TR_InlinerFailureReason EH_Aware_Callee, DontInline_Callee, Not_InlineOnly_Callee, + Unloadable_Callee, Selective_Debugable_Callee, Exceeds_Size_Threshold, Unresolved_Callee, @@ -93,6 +94,7 @@ static const char *TR_InlinerFailureReasonStr [] = FailureReasonStr( EH_Aware_Callee ), FailureReasonStr( DontInline_Callee ), FailureReasonStr( Not_InlineOnly_Callee ), + FailureReasonStr( Unloadable_Callee ), FailureReasonStr( Selective_Debugable_Callee ), FailureReasonStr( Exceeds_Size_Threshold ), FailureReasonStr( Unresolved_Callee ), diff --git a/compiler/optimizer/LocalOpts.cpp b/compiler/optimizer/LocalOpts.cpp index f3747c4db85..8787dc094fd 100644 --- a/compiler/optimizer/LocalOpts.cpp +++ b/compiler/optimizer/LocalOpts.cpp @@ -7175,6 +7175,11 @@ bool TR_InvariantArgumentPreexistence::devirtualizeVirtualCall(TR::Node *node, T return false; } + // We don't need to consider the possibility of unloading the refined callee + // before this compilation's outermost method. This is a call to an instance + // method, so if the callee is unloaded, there will be no receiver object + // and the call will therefore be unreachable. + if (!performTransformation(comp(), "%sspecialize and devirtualize invoke [%p] on currently fixed or final parameter\n", optDetailString(), node)) return false; diff --git a/compiler/optimizer/VPHandlers.cpp b/compiler/optimizer/VPHandlers.cpp index 3c9a8479336..24fe3f37454 100644 --- a/compiler/optimizer/VPHandlers.cpp +++ b/compiler/optimizer/VPHandlers.cpp @@ -4930,6 +4930,11 @@ refineMethodSymbolInCall( TR_ResolvedMethod *resolvedMethod, int32_t offset) { + // We don't need to consider the possibility of unloading the refined callee + // before this compilation's outermost method. This is a call to an instance + // method, so if the callee is unloaded, there will be no receiver object + // and the call will therefore be unreachable. + TR::SymbolReference * newSymRef = vp->comp()->getSymRefTab()->findOrCreateMethodSymbol( symRef->getOwningMethodIndex(), -1, resolvedMethod, TR::MethodSymbol::Virtual); diff --git a/compiler/optimizer/abstractinterpreter/OMRIDTBuilder.cpp b/compiler/optimizer/abstractinterpreter/OMRIDTBuilder.cpp index 0962a957459..1b8685fc2d0 100644 --- a/compiler/optimizer/abstractinterpreter/OMRIDTBuilder.cpp +++ b/compiler/optimizer/abstractinterpreter/OMRIDTBuilder.cpp @@ -73,6 +73,8 @@ TR::IDT* OMR::IDTBuilder::buildIDT() bcInfo, comp(), -1, + false, + comp()->retainedMethods(), false); TR_CallTarget *rootCallTarget = new (region()) TR_CallTarget( diff --git a/compiler/ras/Debug.cpp b/compiler/ras/Debug.cpp index 25726eb4b5f..bc674aaf910 100644 --- a/compiler/ras/Debug.cpp +++ b/compiler/ras/Debug.cpp @@ -911,6 +911,7 @@ TR_Debug::nodePrintAllFlags(TR::Node *node, TR_PrettyPrinterString &output) FLAG(isInvalid8BitGlobalRegister, "invalid8BitGlobalRegister"); FLAG(isDirectMemoryUpdate, "directMemoryUpdate"); + FLAG(isCallThatWasRefinedFromKnownObject, "wasRefinedFromKnownObject"); FLAG(chkTheVirtualCallNodeForAGuardedInlinedCall, "virtualCallNodeForAGuardedInlinedCall"); FLAG(chkDontTransformArrayCopyCall, "dontTransformArrayCopyCall"); FLAG(chkNodeRecognizedArrayCopyCall, "nodeRecognizedArrayCopyCall"); diff --git a/compiler/runtime/OMRRuntimeAssumptions.hpp b/compiler/runtime/OMRRuntimeAssumptions.hpp index b0df1c51dc6..6e65a4da127 100644 --- a/compiler/runtime/OMRRuntimeAssumptions.hpp +++ b/compiler/runtime/OMRRuntimeAssumptions.hpp @@ -39,6 +39,7 @@ class TR_PatchJNICallSite; namespace TR { class PatchNOPedGuardSite; } namespace TR { class PatchMultipleNOPedGuardSites; } class TR_PreXRecompile; +class TR_ClassUnloadRecompile; class TR_RedefinedClassPicSite; class TR_UnloadedClassPicSite; @@ -240,6 +241,7 @@ class RuntimeAssumption virtual TR::PatchNOPedGuardSite *asPNGSite() { return 0; } virtual TR::PatchMultipleNOPedGuardSites *asPMNGSite() { return 0; } virtual TR_PreXRecompile *asPXRecompile() { return 0; } + virtual TR_ClassUnloadRecompile *asCURecompile() { return 0; } virtual TR_UnloadedClassPicSite *asUCPSite() { return 0; } virtual TR_RedefinedClassPicSite *asRCPSite() { return 0; } virtual TR_PatchJNICallSite *asPJNICSite() { return 0; }