001    /*
002     *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003     *
004     *  This file is licensed to You under the Eclipse Public License (EPL);
005     *  You may not use this file except in compliance with the License. You
006     *  may obtain a copy of the License at
007     *
008     *      http://www.opensource.org/licenses/eclipse-1.0.php
009     *
010     *  See the COPYRIGHT.txt file distributed with this work for information
011     *  regarding copyright ownership.
012     */
013    package org.jikesrvm.compilers.opt.inlining;
014    
015    import static org.jikesrvm.compilers.opt.ir.Operators.IG_CLASS_TEST;
016    import static org.jikesrvm.compilers.opt.ir.Operators.IG_METHOD_TEST;
017    import static org.jikesrvm.compilers.opt.ir.Operators.IG_PATCH_POINT;
018    import static org.jikesrvm.compilers.opt.ir.Operators.INSTANCEOF_NOTNULL;
019    import static org.jikesrvm.compilers.opt.ir.Operators.INT_IFCMP;
020    import static org.jikesrvm.compilers.opt.ir.Operators.MUST_IMPLEMENT_INTERFACE;
021    
022    import java.util.Enumeration;
023    
024    import org.jikesrvm.VM;
025    import org.jikesrvm.adaptive.controller.Controller;
026    import org.jikesrvm.adaptive.database.AOSDatabase;
027    import org.jikesrvm.classloader.RVMClass;
028    import org.jikesrvm.classloader.RVMMethod;
029    import org.jikesrvm.classloader.NormalMethod;
030    import org.jikesrvm.classloader.RVMType;
031    import org.jikesrvm.classloader.TypeReference;
032    import org.jikesrvm.compilers.opt.ClassLoaderProxy;
033    import org.jikesrvm.compilers.opt.OptOptions;
034    import org.jikesrvm.compilers.opt.OptimizingCompilerException;
035    import org.jikesrvm.compilers.opt.bc2ir.BC2IR;
036    import org.jikesrvm.compilers.opt.bc2ir.GenerationContext;
037    import org.jikesrvm.compilers.opt.driver.OptConstants;
038    import org.jikesrvm.compilers.opt.ir.BasicBlock;
039    import org.jikesrvm.compilers.opt.ir.Call;
040    import org.jikesrvm.compilers.opt.ir.ExceptionHandlerBasicBlock;
041    import org.jikesrvm.compilers.opt.ir.ExceptionHandlerBasicBlockBag;
042    import org.jikesrvm.compilers.opt.ir.IR;
043    import org.jikesrvm.compilers.opt.ir.IfCmp;
044    import org.jikesrvm.compilers.opt.ir.InlineGuard;
045    import org.jikesrvm.compilers.opt.ir.InstanceOf;
046    import org.jikesrvm.compilers.opt.ir.Instruction;
047    import org.jikesrvm.compilers.opt.ir.Register;
048    import org.jikesrvm.compilers.opt.ir.TypeCheck;
049    import org.jikesrvm.compilers.opt.ir.operand.BranchProfileOperand;
050    import org.jikesrvm.compilers.opt.ir.operand.ConditionOperand;
051    import org.jikesrvm.compilers.opt.ir.operand.IntConstantOperand;
052    import org.jikesrvm.compilers.opt.ir.operand.MethodOperand;
053    import org.jikesrvm.compilers.opt.ir.operand.Operand;
054    import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand;
055    import org.jikesrvm.compilers.opt.ir.operand.TypeOperand;
056    
057    /**
058     * This class contains the high level logic for executing an inlining decision.
059     *
060     * @see InlineDecision
061     * @see GenerationContext
062     */
063    public class Inliner {
064    
065      /**
066       * The following flag enables debug counters and requires an adaptive boot
067       * image and flag "INSERT_DEBUGGING_COUNTERS" to be true. See instrumentation
068       * section of the user guide for more information.
069       */
070      private static final boolean COUNT_FAILED_GUARDS = false;
071    
072      /**
073       * Execute an inlining decision inlDec for the CALL instruction
074       * callSite that is contained in ir.
075       *
076       * @param inlDec the inlining decision to execute
077       * @param ir the governing IR
078       * @param callSite the call site to inline
079       */
080      public static void execute(InlineDecision inlDec, IR ir, Instruction callSite) {
081        // Find out where the call site is and isolate it in its own basic block.
082        BasicBlock bb = callSite.getBasicBlock().segregateInstruction(callSite, ir);
083        BasicBlock in = bb.prevBasicBlockInCodeOrder();
084        BasicBlock out = bb.nextBasicBlockInCodeOrder();
085        // Clear the sratch object of any register operands being
086        // passed as parameters.
087        // BC2IR uses this field for its own purposes, and will be confused
088        // if the scratch object has been used by someone else and not cleared.
089        for (int i = 0; i < Call.getNumberOfParams(callSite); i++) {
090          Operand arg = Call.getParam(callSite, i);
091          if (arg instanceof RegisterOperand) {
092            ((RegisterOperand) arg).scratchObject = null;
093          }
094        }
095        // We need to ensure that inlining the CALL instruction does not
096        // insert any new exceptional edges into the CFG that were not
097        // present before the inlining.  Note that inlining the CALL may
098        // introduce new CALLS, for which we don't know the exception
099        // behavior.  However, we know that any new PEIs introduced in the
100        // inlined code had better not add exceptional edges to the
101        // original CFG.  So, create a new ExceptionHandlerBasicBlockBag
102        // which will enforce this behavior.
103        ExceptionHandlerBasicBlock[] catchBlocks = new ExceptionHandlerBasicBlock[bb.getNumberOfExceptionalOut()];
104        Enumeration<BasicBlock> e = bb.getExceptionalOut();
105        for (int i = 0; i < catchBlocks.length; i++) {
106          catchBlocks[i] = (ExceptionHandlerBasicBlock) e.nextElement();
107        }
108        ExceptionHandlerBasicBlockBag bag = new ExceptionHandlerBasicBlockBag(catchBlocks, null);
109    
110        // Execute the inlining decision, updating ir.gc's state.
111        GenerationContext childgc = execute(inlDec, ir.gc, bag, callSite);
112        // Splice the callee into the caller's code order
113        ir.cfg.removeFromCFGAndCodeOrder(bb);
114        ir.cfg.breakCodeOrder(in, out);
115        ir.cfg.linkInCodeOrder(in, childgc.cfg.firstInCodeOrder());
116        ir.cfg.linkInCodeOrder(childgc.cfg.lastInCodeOrder(), out);
117        // Splice the callee into the caller's CFG
118        in.insertOut(childgc.prologue);
119        if (childgc.epilogue != null) {
120          childgc.epilogue.insertOut(out);
121        }
122      }
123    
124      /**
125       * Return a generation context that represents the
126       * execution of inlDec in the context <code>&lt;parent,ebag&gt;</code> for
127       * the call instruction callSite.
128       * <p> PRECONDITION: inlDec.isYes()
129       * <p> POSTCONDITIONS:
130       * Let gc be the returned generation context.
131       * <ul>
132       *  <li> gc.cfg.firstInCodeOrder is the entry to the inlined context
133       *  <li>gc.cfg.lastInCodeOrder is the exit from the inlined context
134       *  <li> GenerationContext.transferState(parent, child) has been called.
135       * </ul>
136       *
137       * @param inlDec the inlining decision to execute
138       * @param parent the caller generation context
139       * @param ebag exception handler scope for the caller
140       * @param callSite the callsite to execute
141       * @return a generation context that represents the execution of the
142       *         inline decision in the given context
143       */
144      public static GenerationContext execute(InlineDecision inlDec, GenerationContext parent,
145                                              ExceptionHandlerBasicBlockBag ebag, Instruction callSite) {
146        if (inlDec.needsGuard()) {
147          //Step 1: create the synthetic generation context we'll
148          // return to our caller.
149          GenerationContext container = GenerationContext.createSynthetic(parent, ebag);
150          container.cfg.breakCodeOrder(container.prologue, container.epilogue);
151          // Step 2: (a) Print a message (optional)
152          //         (b) Generate the child GC for each target
153          RVMMethod[] targets = inlDec.getTargets();
154          byte[] guards = inlDec.getGuards();
155          GenerationContext[] children = new GenerationContext[targets.length];
156          for (int i = 0; i < targets.length; i++) {
157            NormalMethod callee = (NormalMethod) targets[i];
158            // (a)
159            if (parent.options.PRINT_INLINE_REPORT) {
160              String guard = guards[i] == OptOptions.INLINE_GUARD_CLASS_TEST ? " (class test) " : " (method test) ";
161              VM.sysWrite("\tGuarded inline" + guard + " " + callee +
162                          " into " + callSite.position.getMethod() +
163                          " at bytecode " + callSite.bcIndex + "\n");
164            }
165            // (b)
166            children[i] = GenerationContext.createChildContext(parent, ebag, callee, callSite);
167            BC2IR.generateHIR(children[i]);
168            GenerationContext.transferState(parent, children[i]);
169          }
170          // Step 3: Merge together result from children into container.
171          //         Note: if the child ended with only exception control flow, then
172          //         child.result will be null, which we want to interpret as top.
173          //         Operand.meet interprets null as bottom, so we have to do some
174          //         special purpose coding wrapping the calls to Operand.meet.
175          if (Call.hasResult(callSite)) {
176            Register reg = Call.getResult(callSite).getRegister();
177            container.result = children[0].result;
178            for (int i = 1; i < targets.length; i++) {
179              if (children[i].result != null) {
180                container.result = (container.result == null) ? children[i].result : Operand.meet(container.result, children[i].result, reg);
181              }
182            }
183    
184    
185            if (!inlDec.OSRTestFailed()) {
186              // Account for the non-predicted case as well...
187              RegisterOperand failureCaseResult = Call.getResult(callSite).copyRO();
188              container.result = (container.result == null) ?  failureCaseResult : Operand.meet(container.result, failureCaseResult, reg);
189            }
190          }
191    
192          // Step 4: Create a block to contain a copy of the original call or an OSR_Yieldpoint
193          //         to cover the case that all predictions fail.
194          BasicBlock testFailed = new BasicBlock(callSite.bcIndex, callSite.position, parent.cfg);
195          testFailed.exceptionHandlers = ebag;
196    
197          if (COUNT_FAILED_GUARDS && Controller.options.INSERT_DEBUGGING_COUNTERS) {
198            // Get a dynamic count of how many times guards fail at runtime.
199            // Need a name for the event to count.  In this example, a
200            // separate counter for each method by using the method name
201            // as the event name.  You could also have used just the string
202            // "Guarded inline failed" to keep only one counter.
203            String eventName = "Guarded inline failed: " + callSite.position.getMethod().toString();
204            // Create instruction that will increment the counter
205            // corresponding to the named event.
206            Instruction counterInst = AOSDatabase.debuggingCounterData.getCounterInstructionForEvent(eventName);
207            testFailed.appendInstruction(counterInst);
208          }
209    
210          if (inlDec.OSRTestFailed()) {
211            // note where we're storing the osr barrier instruction
212            Instruction lastOsrBarrier = (Instruction)callSite.scratchObject;
213            Instruction s = BC2IR._osrHelper(lastOsrBarrier);
214            s.position = callSite.position;
215            s.bcIndex = callSite.bcIndex;
216            testFailed.appendInstruction(s);
217            testFailed.insertOut(parent.exit);
218          } else {
219            Instruction call = callSite.copyWithoutLinks();
220            Call.getMethod(call).setIsGuardedInlineOffBranch(true);
221            call.bcIndex = callSite.bcIndex;
222            call.position = callSite.position;
223            testFailed.appendInstruction(call);
224            testFailed.insertOut(container.epilogue);
225            // This is ugly....since we didn't call BC2IR to generate the
226            // block with callSite we have to initialize the block's exception
227            // behavior manually.
228            // We can't call createSubBlock to do it because callSite may not
229            // be in a basic block yet (when execute is invoked from
230            // BC2IR.maybeInlineMethod).
231            if (ebag != null) {
232              for (Enumeration<BasicBlock> e = ebag.enumerator(); e.hasMoreElements();) {
233                BasicBlock handler = e.nextElement();
234                testFailed.insertOut(handler);
235              }
236            }
237            testFailed.setCanThrowExceptions();
238            testFailed.setMayThrowUncaughtException();
239          }
240          container.cfg.linkInCodeOrder(testFailed, container.epilogue);
241          testFailed.setInfrequent();
242    
243          // Step 5: Patch together all the callees by generating guard blocks
244          BasicBlock firstIfBlock = testFailed;
245          // Note: We know that receiver must be a register
246          // operand (and not a string constant) because we are doing a
247          // guarded inlining....if it was a string constant we'd have
248          // been able to inline without a guard.
249          Operand receiver = Call.getParam(callSite, 0);
250          MethodOperand mo = Call.getMethod(callSite);
251          boolean isInterface = mo.isInterface();
252          if (isInterface) {
253            if (VM.BuildForIMTInterfaceInvocation) {
254              RVMType interfaceType = mo.getTarget().getDeclaringClass();
255              TypeReference recTypeRef = receiver.getType();
256              RVMClass recType = (RVMClass) recTypeRef.peekType();
257              // Attempt to avoid inserting the check by seeing if the
258              // known static type of the receiver implements the interface.
259              boolean requiresImplementsTest = true;
260              if (recType != null && recType.isResolved() && !recType.isInterface()) {
261                byte doesImplement = ClassLoaderProxy.includesType(interfaceType.getTypeRef(), recTypeRef);
262                requiresImplementsTest = doesImplement != OptConstants.YES;
263              }
264              if (requiresImplementsTest) {
265                RegisterOperand checkedReceiver = parent.temps.makeTemp(receiver);
266                Instruction dtc =
267                    TypeCheck.create(MUST_IMPLEMENT_INTERFACE,
268                        checkedReceiver,
269                        receiver.copy(),
270                        new TypeOperand(interfaceType),
271                        Call.getGuard(callSite).copy());
272                dtc.copyPosition(callSite);
273                checkedReceiver.refine(interfaceType.getTypeRef());
274                Call.setParam(callSite, 0, checkedReceiver.copyRO());
275                testFailed.prependInstruction(dtc);
276              }
277            }
278          }
279          // Basic idea of loop: Path together an if...else if.... else...
280          // chain from the bottom (testFailed). Some excessive cuteness
281          // to allow us to have multiple if blocks for a single
282          // "logical" test and to share test insertion for interfaces/virtuals.
283          for (int i = children.length - 1; i >= 0; i--, testFailed = firstIfBlock) {
284            firstIfBlock = new BasicBlock(callSite.bcIndex, callSite.position, parent.cfg);
285            firstIfBlock.exceptionHandlers = ebag;
286            BasicBlock lastIfBlock = firstIfBlock;
287            RVMMethod target = children[i].method;
288            Instruction tmp;
289    
290            if (isInterface) {
291              RVMClass callDeclClass = mo.getTarget().getDeclaringClass();
292              if (!callDeclClass.isInterface()) {
293                // Part of ensuring that we catch IncompatibleClassChangeErrors
294                // is making sure that we know that callDeclClass is an
295                // interface before we attempt to side-step the usual invoke
296                // interface sequence.
297                // If we don't know at least this much, we can't do the inlining.
298                // We used online profile data to tell us that the target was a
299                // frequently called method from this (interface invoke)
300                // callSite, so it would be truly bizarre for us to not be able
301                // to establish that callDeclClass is an interface now.
302                // If we were using static heuristics to do guarded inlining
303                // of interface calls, then we'd probably need to do the
304                // "right" thing and detect this situation
305                // before we generated all of the childCFG's and got them
306                // entangled into the parent (due to exceptional control flow).
307                // This potential entanglement is what forces us to bail on
308                // the entire compilation.
309                throw new OptimizingCompilerException(
310                    "Attempted guarded inline of invoke interface when decl class of target method may not be an interface");
311              }
312    
313              // We potentially have to generate IR to perform two tests here:
314              // (1) Does the receiver object implement callDeclClass?
315              // (2) Given that it does, is target the method that would
316              //     be invoked for this receiver?
317              // It is quite common to be able to answer (1) "YES" at compile
318              // time, in which case we only have to generate IR to establish
319              // (2) at runtime.
320              byte doesImplement = ClassLoaderProxy.
321                  includesType(callDeclClass.getTypeRef(), target.getDeclaringClass().getTypeRef());
322              if (doesImplement != OptConstants.YES) {
323                // We can't be sure at compile time that the receiver implements
324                // the interface. So, inject a test to make sure that it does.
325                // Unlike the above case, this can actually happen (when
326                // the method is inherited but only the child actually
327                // implements the interface).
328                if (parent.options.PRINT_INLINE_REPORT) {
329                  VM.sysWrite("\t\tRequired additional instanceof " + callDeclClass + " test\n");
330                }
331                firstIfBlock = new BasicBlock(callSite.bcIndex, callSite.position, parent.cfg);
332                firstIfBlock.exceptionHandlers = ebag;
333    
334                RegisterOperand instanceOfResult = parent.temps.makeTempInt();
335                tmp =
336                    InstanceOf.create(INSTANCEOF_NOTNULL,
337                                      instanceOfResult,
338                                      new TypeOperand(callDeclClass),
339                                      receiver.copy(),
340                                      Call.getGuard(callSite));
341                tmp.copyPosition(callSite);
342                firstIfBlock.appendInstruction(tmp);
343    
344                tmp =
345                    IfCmp.create(INT_IFCMP,
346                                 parent.temps.makeTempValidation(),
347                                 instanceOfResult.copyD2U(),
348                                 new IntConstantOperand(0),
349                                 ConditionOperand.EQUAL(),
350                                 testFailed.makeJumpTarget(),
351                                 BranchProfileOperand.unlikely());
352                tmp.copyPosition(callSite);
353                firstIfBlock.appendInstruction(tmp);
354    
355                firstIfBlock.insertOut(testFailed);
356                firstIfBlock.insertOut(lastIfBlock);
357                container.cfg.linkInCodeOrder(firstIfBlock, lastIfBlock);
358              }
359            }
360    
361            if (guards[i] == OptOptions.INLINE_GUARD_CLASS_TEST) {
362              tmp =
363                  InlineGuard.create(IG_CLASS_TEST,
364                                     receiver.copy(),
365                                     Call.getGuard(callSite).copy(),
366                                     new TypeOperand(target.getDeclaringClass()),
367                                     testFailed.makeJumpTarget(),
368                                     BranchProfileOperand.unlikely());
369            } else if (guards[i] == OptOptions.INLINE_GUARD_METHOD_TEST) {
370              // method test for interface requires additional check if
371              // the reciever's class is a subclass of inlined method's
372              // declaring class.
373              if (isInterface) {
374                RegisterOperand t = parent.temps.makeTempInt();
375                Instruction test =
376                    InstanceOf.create(INSTANCEOF_NOTNULL,
377                                      t,
378                                      new TypeOperand(target.getDeclaringClass().getTypeRef()),
379                                      receiver.copy());
380                test.copyPosition(callSite);
381                lastIfBlock.appendInstruction(test);
382    
383                Instruction cmp =
384                    IfCmp.create(INT_IFCMP,
385                                 parent.temps.makeTempValidation(),
386                                 t.copyD2U(),
387                                 new IntConstantOperand(0),
388                                 ConditionOperand.EQUAL(),
389                                 testFailed.makeJumpTarget(),
390                                 BranchProfileOperand.unlikely());
391                cmp.copyPosition(callSite);
392                lastIfBlock.appendInstruction(cmp);
393    
394                BasicBlock subclassTest = new BasicBlock(callSite.bcIndex, callSite.position, parent.cfg);
395    
396                lastIfBlock.insertOut(testFailed);
397                lastIfBlock.insertOut(subclassTest);
398    
399                container.cfg.linkInCodeOrder(lastIfBlock, subclassTest);
400    
401                lastIfBlock = subclassTest;
402              }
403    
404              tmp =
405                  InlineGuard.create(IG_METHOD_TEST,
406                                     receiver.copy(),
407                                     Call.getGuard(callSite).copy(),
408                                     MethodOperand.VIRTUAL(target.getMemberRef().asMethodReference(), target),
409                                     testFailed.makeJumpTarget(),
410                                     BranchProfileOperand.unlikely());
411            } else {
412              tmp =
413                  InlineGuard.create(IG_PATCH_POINT,
414                                     receiver.copy(),
415                                     Call.getGuard(callSite).copy(),
416                                     MethodOperand.VIRTUAL(target.getMemberRef().asMethodReference(), target),
417                                     testFailed.makeJumpTarget(),
418                                     inlDec.OSRTestFailed() ? BranchProfileOperand.never() : BranchProfileOperand.unlikely());
419            }
420            tmp.copyPosition(callSite);
421            lastIfBlock.appendInstruction(tmp);
422    
423            lastIfBlock.insertOut(testFailed);
424            lastIfBlock.insertOut(children[i].prologue);
425            container.cfg.linkInCodeOrder(lastIfBlock, children[i].cfg.firstInCodeOrder());
426            if (children[i].epilogue != null) {
427              children[i].epilogue.appendInstruction(container.epilogue.makeGOTO());
428              children[i].epilogue.insertOut(container.epilogue);
429            }
430            container.cfg.linkInCodeOrder(children[i].cfg.lastInCodeOrder(), testFailed);
431          }
432          //Step 6: finish by linking container.prologue & testFailed
433          container.prologue.insertOut(testFailed);
434          container.cfg.linkInCodeOrder(container.prologue, testFailed);
435          return container;
436        } else {
437          if (VM.VerifyAssertions) VM._assert(inlDec.getNumberOfTargets() == 1);
438          NormalMethod callee = (NormalMethod) inlDec.getTargets()[0];
439          if (parent.options.PRINT_INLINE_REPORT) {
440            VM.sysWrite("\tInline " + callee +
441                        " into " + callSite.position.getMethod() +
442                        " at bytecode " + callSite.bcIndex + "\n");
443          }
444          GenerationContext child = GenerationContext.
445              createChildContext(parent, ebag, callee, callSite);
446          BC2IR.generateHIR(child);
447          GenerationContext.transferState(parent, child);
448          return child;
449        }
450      }
451    }