/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Common Public License (CPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/cpl1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.jikesrvm.compilers.opt.ir; import java.util.Enumeration; import org.jikesrvm.VM; import org.jikesrvm.adaptive.controller.VM_Controller; import org.jikesrvm.adaptive.database.VM_AOSDatabase; import org.jikesrvm.classloader.VM_Class; import org.jikesrvm.classloader.VM_Method; import org.jikesrvm.classloader.VM_NormalMethod; import org.jikesrvm.classloader.VM_Type; import org.jikesrvm.classloader.VM_TypeReference; import org.jikesrvm.compilers.opt.OPT_ClassLoaderProxy; import org.jikesrvm.compilers.opt.OPT_Constants; import org.jikesrvm.compilers.opt.OPT_InlineDecision; import org.jikesrvm.compilers.opt.OPT_OptimizingCompilerException; import org.jikesrvm.compilers.opt.OPT_Options; import static org.jikesrvm.compilers.opt.ir.OPT_Operators.IG_CLASS_TEST; import static org.jikesrvm.compilers.opt.ir.OPT_Operators.IG_METHOD_TEST; import static org.jikesrvm.compilers.opt.ir.OPT_Operators.IG_PATCH_POINT; import static org.jikesrvm.compilers.opt.ir.OPT_Operators.INSTANCEOF_NOTNULL; import static org.jikesrvm.compilers.opt.ir.OPT_Operators.INT_IFCMP; import static org.jikesrvm.compilers.opt.ir.OPT_Operators.MUST_IMPLEMENT_INTERFACE; //TODO - Deal with Subarch /** * This class contains the high level logic for executing an inlining decision. * * * @see OPT_InlineDecision * @see OPT_GenerationContext */ public class OPT_Inliner { // The following flag requires an adaptive boot image and flag // "INSERT_DEBUGGING_COUNTERS" to be true. See instrumentation // section of the user guide for more information. private static final boolean COUNT_FAILED_GUARDS = false; /** * Execute an inlining decision inlDec for the CALL instruction * callSite that is contained in ir. * * @param inlDec the inlining decision to execute * @param ir the governing IR * @param callSite the call site to inline */ public static void execute(OPT_InlineDecision inlDec, OPT_IR ir, OPT_Instruction callSite) { // Find out where the call site is and isolate it in its own basic block. OPT_BasicBlock bb = callSite.getBasicBlock().segregateInstruction(callSite, ir); OPT_BasicBlock in = bb.prevBasicBlockInCodeOrder(); OPT_BasicBlock out = bb.nextBasicBlockInCodeOrder(); // Clear the sratch object of any register operands being // passed as parameters. // BC2IR uses this field for its own purposes, and will be confused // if the scratch object has been used by someone else and not cleared. for (int i = 0; i < Call.getNumberOfParams(callSite); i++) { OPT_Operand arg = Call.getParam(callSite, i); if (arg instanceof OPT_RegisterOperand) { ((OPT_RegisterOperand) arg).scratchObject = null; } } // We need to ensure that inlining the CALL instruction does not // insert any new exceptional edges into the CFG that were not // present before the inlining. Note that inlining the CALL may // introduce new CALLS, for which we don't know the exception // behavior. However, we know that any new PEIs introduced in the // inlined code had better not add exceptional edges to the // original CFG. So, create a new ExceptionHandlerBasicBlockBag // which will enforce this behavior. OPT_ExceptionHandlerBasicBlock[] catchBlocks = new OPT_ExceptionHandlerBasicBlock[bb.getNumberOfExceptionalOut()]; Enumeration<OPT_BasicBlock> e = bb.getExceptionalOut(); for (int i = 0; i < catchBlocks.length; i++) { catchBlocks[i] = (OPT_ExceptionHandlerBasicBlock) e.nextElement(); } OPT_ExceptionHandlerBasicBlockBag bag = new OPT_ExceptionHandlerBasicBlockBag(catchBlocks, null); // Execute the inlining decision, updating ir.gc's state. OPT_GenerationContext childgc = execute(inlDec, ir.gc, bag, callSite); // Splice the callee into the caller's code order ir.cfg.removeFromCFGAndCodeOrder(bb); ir.cfg.breakCodeOrder(in, out); ir.cfg.linkInCodeOrder(in, childgc.cfg.firstInCodeOrder()); ir.cfg.linkInCodeOrder(childgc.cfg.lastInCodeOrder(), out); // Splice the callee into the caller's CFG in.insertOut(childgc.prologue); if (childgc.epilogue != null) { childgc.epilogue.insertOut(out); } } /** * Return a generation context that represents the * execution of inlDec in the context <parent,ebag> for * the call instruction callSite. * <p> PRECONDITION: inlDec.isYes() * <p> POSTCONDITIONS: * Let gc be the returned generation context. * <ul> * <li> gc.cfg.firstInCodeOrder is the entry to the inlined context * <li>gc.cfg.lastInCodeOrder is the exit from the inlined context * <li> OPT_GenerationContext.transferState(parent, child) has been called. * </ul> * * @param inlDec the inlining decision to execute * @param parent the caller generation context * @param ebag exception handler scope for the caller * @param callSite the callsite to execute * @return a generation context that represents the execution of the * inline decision in the given context */ public static OPT_GenerationContext execute(OPT_InlineDecision inlDec, OPT_GenerationContext parent, OPT_ExceptionHandlerBasicBlockBag ebag, OPT_Instruction callSite) { if (inlDec.needsGuard()) { //Step 1: create the synthetic generation context we'll // return to our caller. OPT_GenerationContext container = OPT_GenerationContext.createSynthetic(parent, ebag); container.cfg.breakCodeOrder(container.prologue, container.epilogue); // Step 2: (a) Print a message (optional) // (b) Generate the child GC for each target VM_Method[] targets = inlDec.getTargets(); byte[] guards = inlDec.getGuards(); OPT_GenerationContext[] children = new OPT_GenerationContext[targets.length]; for (int i = 0; i < targets.length; i++) { VM_NormalMethod callee = (VM_NormalMethod) targets[i]; // (a) if (parent.options.PRINT_INLINE_REPORT) { String guard = guards[i] == OPT_Options.IG_CLASS_TEST ? " (class test) " : " (method test) "; VM.sysWrite("\tGuarded inline" + guard + " " + callee + " into " + callSite.position.getMethod() + " at bytecode " + callSite .bcIndex + "\n"); } // (b) children[i] = OPT_GenerationContext.createChildContext(parent, ebag, callee, callSite); OPT_BC2IR.generateHIR(children[i]); OPT_GenerationContext.transferState(parent, children[i]); } // Step 3: Merge together result from children into container. if (Call.hasResult(callSite)) { OPT_Register reg = Call.getResult(callSite).getRegister(); container.result = children[0].result; for (int i = 1; i < targets.length; i++) { container.result = OPT_Operand.meet(container.result, children[i].result, reg); } // Account for the non-predicted case as well... // Most likely this means that we shouldn't even bother // with the above meet operations // and simply pick up Call.getResult(callsite) directly. // SJF: However, it's good to keep this around; maybe // IPA will give us more information about the result. container.result = OPT_Operand.meet(container.result, Call.getResult(callSite), reg); } // Step 4: Create a block to contain a copy of the original call // in case all predictions fail. It falls through to container.epilogue OPT_BasicBlock testFailed = new OPT_BasicBlock(callSite.bcIndex, callSite.position, parent.cfg); testFailed.exceptionHandlers = ebag; OPT_Instruction call = callSite.copyWithoutLinks(); Call.getMethod(call).setIsGuardedInlineOffBranch(true); call.bcIndex = callSite.bcIndex; call.position = callSite.position; if (COUNT_FAILED_GUARDS && VM_Controller.options.INSERT_DEBUGGING_COUNTERS) { // Get a dynamic count of how many times guards fail at runtime. // Need a name for the event to count. In this example, a // separate counter for each method by using the method name // as the event name. You could also have used just the string // "Guarded inline failed" to keep only one counter. String eventName = "Guarded inline failed: " + callSite.position.getMethod().toString(); // Create instruction that will increment the counter // corresponding to the named event. OPT_Instruction counterInst = VM_AOSDatabase.debuggingCounterData.getCounterInstructionForEvent(eventName); testFailed.appendInstruction(counterInst); } if (inlDec.OSRTestFailed()) { // note where we're storing the osr barrier instruction OPT_Instruction lastOsrBarrier = (OPT_Instruction) callSite.scratchObject; OPT_Instruction s = OPT_BC2IR._osrHelper(lastOsrBarrier); s.position = callSite.position; s.bcIndex = callSite.bcIndex; testFailed.appendInstruction(s); } else { testFailed.appendInstruction(call); } testFailed.insertOut(container.epilogue); container.cfg.linkInCodeOrder(testFailed, container.epilogue); // This is ugly....since we didn't call BC2IR to generate the // block with callSite we have to initialize the block's exception // behavior manually. // We can't call createSubBlock to do it because callSite may not // be in a basic block yet (when execute is invoked from // BC2IR.maybeInlineMethod). if (ebag != null) { for (OPT_BasicBlockEnumeration e = ebag.enumerator(); e.hasMoreElements();) { OPT_BasicBlock handler = e.next(); testFailed.insertOut(handler); } } testFailed.setCanThrowExceptions(); testFailed.setMayThrowUncaughtException(); testFailed.setInfrequent(); // Step 5: Patch together all the callees by generating guard blocks OPT_BasicBlock firstIfBlock = testFailed; // Note: We know that receiver must be a register // operand (and not a string constant) because we are doing a // guarded inlining....if it was a string constant we'd have // been able to inline without a guard. OPT_Operand receiver = Call.getParam(callSite, 0); OPT_MethodOperand mo = Call.getMethod(callSite); boolean isInterface = mo.isInterface(); if (isInterface) { if (VM.BuildForIMTInterfaceInvocation || (VM.BuildForITableInterfaceInvocation && VM.DirectlyIndexedITables)) { VM_Type interfaceType = mo.getTarget().getDeclaringClass(); VM_TypeReference recTypeRef = receiver.getType(); VM_Class recType = (VM_Class) recTypeRef.peekType(); // Attempt to avoid inserting the check by seeing if the // known static type of the receiver implements the interface. boolean requiresImplementsTest = true; if (recType != null && recType.isResolved(false) && !recType.isInterface()) { byte doesImplement = OPT_ClassLoaderProxy.includesType(interfaceType.getTypeRef(), recTypeRef); requiresImplementsTest = doesImplement != OPT_Constants.YES; } if (requiresImplementsTest) { OPT_Instruction dtc = TypeCheck.create(MUST_IMPLEMENT_INTERFACE, receiver.copy(), new OPT_TypeOperand(interfaceType), Call.getGuard(callSite).copy()); dtc.copyPosition(callSite); testFailed.prependInstruction(dtc); } } } // Basic idea of loop: Path together an if...else if.... else... // chain from the bottom (testFailed). Some excessive cuteness // to allow us to have multiple if blocks for a single // "logical" test and to share test insertion for interfaces/virtuals. for (int i = children.length - 1; i >= 0; i--, testFailed = firstIfBlock) { firstIfBlock = new OPT_BasicBlock(callSite.bcIndex, callSite.position, parent.cfg); firstIfBlock.exceptionHandlers = ebag; OPT_BasicBlock lastIfBlock = firstIfBlock; VM_Method target = children[i].method; OPT_Instruction tmp; if (isInterface) { VM_Class callDeclClass = mo.getTarget().getDeclaringClass(); if (!callDeclClass.isInterface()) { // Part of ensuring that we catch IncompatibleClassChangeErrors // is making sure that we know that callDeclClass is an // interface before we attempt to side-step the usual invoke // interface sequence. // If we don't know at least this much, we can't do the inlining. // We used online profile data to tell us that the target was a // frequently called method from this (interface invoke) // callSite, so it would be truly bizarre for us to not be able // to establish that callDeclClass is an interface now. // If we were using static heuristics to do guarded inlining // of interface calls, then we'd probably need to do the // "right" thing and detect this situation // before we generated all of the childCFG's and got them // entangled into the parent (due to exceptional control flow). // This potential entanglement is what forces us to bail on // the entire compilation. throw new OPT_OptimizingCompilerException( "Attempted guarded inline of invoke interface when decl class of target method may not be an interface"); } // We potentially have to generate IR to perform two tests here: // (1) Does the receiver object implement callDeclClass? // (2) Given that it does, is target the method that would // be invoked for this receiver? // It is quite common to be able to answer (1) "YES" at compile // time, in which case we only have to generate IR to establish // (2) at runtime. byte doesImplement = OPT_ClassLoaderProxy. includesType(callDeclClass.getTypeRef(), target.getDeclaringClass().getTypeRef()); if (doesImplement != OPT_Constants.YES) { // We can't be sure at compile time that the receiver implements // the interface. So, inject a test to make sure that it does. // Unlike the above case, this can actually happen (when // the method is inherited but only the child actually // implements the interface). if (parent.options.PRINT_INLINE_REPORT) { VM.sysWrite("\t\tRequired additional instanceof " + callDeclClass + " test\n"); } firstIfBlock = new OPT_BasicBlock(callSite.bcIndex, callSite.position, parent.cfg); firstIfBlock.exceptionHandlers = ebag; OPT_RegisterOperand instanceOfResult = parent.temps.makeTempInt(); tmp = InstanceOf.create(INSTANCEOF_NOTNULL, instanceOfResult, new OPT_TypeOperand(callDeclClass), receiver.copy(), Call.getGuard(callSite)); tmp.copyPosition(callSite); firstIfBlock.appendInstruction(tmp); tmp = IfCmp.create(INT_IFCMP, parent.temps.makeTempValidation(), instanceOfResult.copyD2U(), new OPT_IntConstantOperand(0), OPT_ConditionOperand.EQUAL(), testFailed.makeJumpTarget(), OPT_BranchProfileOperand.unlikely()); tmp.copyPosition(callSite); firstIfBlock.appendInstruction(tmp); firstIfBlock.insertOut(testFailed); firstIfBlock.insertOut(lastIfBlock); container.cfg.linkInCodeOrder(firstIfBlock, lastIfBlock); } } if (guards[i] == OPT_Options.IG_CLASS_TEST) { tmp = InlineGuard.create(IG_CLASS_TEST, receiver.copy(), Call.getGuard(callSite).copy(), new OPT_TypeOperand(target.getDeclaringClass()), testFailed.makeJumpTarget(), OPT_BranchProfileOperand.unlikely()); } else if (guards[i] == OPT_Options.IG_METHOD_TEST) { // method test for interface requires additional check if // the reciever's class is a subclass of inlined method's // declaring class. if (isInterface) { OPT_RegisterOperand t = parent.temps.makeTempInt(); OPT_Instruction test = InstanceOf.create(INSTANCEOF_NOTNULL, t, new OPT_TypeOperand(target.getDeclaringClass().getTypeRef()), receiver.copy()); test.copyPosition(callSite); lastIfBlock.appendInstruction(test); OPT_Instruction cmp = IfCmp.create(INT_IFCMP, parent.temps.makeTempValidation(), t.copyD2U(), new OPT_IntConstantOperand(0), OPT_ConditionOperand.EQUAL(), testFailed.makeJumpTarget(), OPT_BranchProfileOperand.unlikely()); cmp.copyPosition(callSite); lastIfBlock.appendInstruction(cmp); OPT_BasicBlock subclassTest = new OPT_BasicBlock(callSite.bcIndex, callSite.position, parent.cfg); lastIfBlock.insertOut(testFailed); lastIfBlock.insertOut(subclassTest); container.cfg.linkInCodeOrder(lastIfBlock, subclassTest); lastIfBlock = subclassTest; } tmp = InlineGuard.create(IG_METHOD_TEST, receiver.copy(), Call.getGuard(callSite).copy(), OPT_MethodOperand.VIRTUAL(target.getMemberRef().asMethodReference(), target), testFailed.makeJumpTarget(), OPT_BranchProfileOperand.unlikely()); } else { tmp = InlineGuard.create(IG_PATCH_POINT, receiver.copy(), Call.getGuard(callSite).copy(), OPT_MethodOperand.VIRTUAL(target.getMemberRef().asMethodReference(), target), testFailed.makeJumpTarget(), OPT_BranchProfileOperand.unlikely()); } tmp.copyPosition(callSite); lastIfBlock.appendInstruction(tmp); lastIfBlock.insertOut(testFailed); lastIfBlock.insertOut(children[i].prologue); container.cfg.linkInCodeOrder(lastIfBlock, children[i].cfg.firstInCodeOrder()); if (children[i].epilogue != null) { children[i].epilogue.appendInstruction(container.epilogue.makeGOTO()); children[i].epilogue.insertOut(container.epilogue); } container.cfg.linkInCodeOrder(children[i].cfg.lastInCodeOrder(), testFailed); } //Step 6: finsh by linking container.prologue & testFailed container.prologue.insertOut(testFailed); container.cfg.linkInCodeOrder(container.prologue, testFailed); return container; } else { if (VM.VerifyAssertions) VM._assert(inlDec.getNumberOfTargets() == 1); VM_NormalMethod callee = (VM_NormalMethod) inlDec.getTargets()[0]; if (parent.options.PRINT_INLINE_REPORT) { VM.sysWrite("\tInline " + callee + " into " + callSite.position.getMethod() + " at bytecode " + callSite .bcIndex + "\n"); } OPT_GenerationContext child = OPT_GenerationContext. createChildContext(parent, ebag, callee, callSite); OPT_BC2IR.generateHIR(child); OPT_GenerationContext.transferState(parent, child); return child; } } }