/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.vm.compiler; import org.jnode.vm.JvmType; import org.jnode.vm.bytecode.BasicBlock; import org.jnode.vm.bytecode.ControlFlowGraph; import org.jnode.vm.classmgr.VmByteCode; import org.jnode.vm.classmgr.VmClassLoader; import org.jnode.vm.classmgr.VmConstClass; import org.jnode.vm.classmgr.VmConstMethodRef; import org.jnode.vm.classmgr.VmMethod; import org.jnode.vm.classmgr.VmPrimitiveClass; import org.jnode.vm.classmgr.VmType; import org.jnode.vm.facade.VmUtils; import org.jnode.vm.objects.Counter; /** * @author Ewout Prangsma (epr@users.sourceforge.net) */ public final class OptimizingBytecodeVisitor extends VerifyingCompilerBytecodeVisitor<InlineBytecodeVisitor> { /** * Maximum length of methods that will be inlined */ private static final int SIZE_LIMIT = 32; /** * Maximum depth of recursive inlining */ private static final int MAX_INLINE_DEPTH = -1; //5; /** * Common method entrypoints */ private final EntryPoints entryPoints; /** * The classloader */ private final VmClassLoader loader; /** * The method that is currently being visited */ private VmMethod method; /** * The current max locals of method (adjusted for inlined methods) */ private char maxLocals; /** * Diff to add to local indexes */ private char localDelta; /** * Has a return been visited during an inline */ private boolean visitedReturn = false; /** * How many nested inlines we're currently in (0 == no inline) */ private byte inlineDepth = 0; // Inter instruction optimization flags /** * Optimization flag constants. */ private static interface OptFlags { int ASTORE = 0x0001; int ISTORE = 0x0002; int LSTORE = 0x0004; int FSTORE = 0x0008; int DSTORE = 0x0010; int ALOAD = 0x0020; int ILOAD = 0x0040; int LLOAD = 0x0080; int FLOAD = 0x0100; int DLOAD = 0x0200; } /** * Optimize flags set by the current instruction */ private int optimizeFlags; /** * Optimize flags set by the previous instruction */ private int previousOptimizeFlags; /** * Index of the last xstore instruction */ private int storeIndex; /** * Index of the last xload instruction */ private int loadIndex; /** * Statistic counter for #inlined invokespecial's */ private static Counter inlineSpecialCounter = VmUtils.getVm().getCounter( "inlined-invokespecial"); /** * Statistic counter for #inlined invokespecial's */ private static Counter inlineStaticCounter = VmUtils.getVm().getCounter( "inlined-invokestatic"); /** * Statistic counter for #inlined invokespecial's */ private static Counter inlineVirtualCounter = VmUtils.getVm().getCounter( "inlined-invokevirtual"); /** * Statistic counter for astore/aload sequence */ private static Counter storeLoadCounter = VmUtils.getVm().getCounter( "store-load"); /** * Initialize this instance. * * @param entryPoints * @param delegate * @param loader */ public OptimizingBytecodeVisitor(EntryPoints entryPoints, InlineBytecodeVisitor delegate, VmClassLoader loader) { super(delegate); this.entryPoints = entryPoints; this.loader = loader; } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#startMethod(org.jnode.vm.classmgr.VmMethod) */ public void startMethod(VmMethod method) { this.method = method; this.maxLocals = (char) method.getBytecode().getNoLocals(); // Reset optimization flags this.optimizeFlags = 0; this.previousOptimizeFlags = 0; // Delegate call super.startMethod(method); } /** * @see org.jnode.vm.compiler.DelegatingCompilerBytecodeVisitor#startBasicBlock(org.jnode.vm.bytecode.BasicBlock) */ public void startBasicBlock(BasicBlock bb) { // Reset optimize flags this.previousOptimizeFlags = 0; this.optimizeFlags = 0; // Delegate call super.startBasicBlock(bb); } /** * @see org.jnode.vm.compiler.DelegatingCompilerBytecodeVisitor#startInstruction(int) */ public void startInstruction(int address) { // Set previos optimize flags & clear optimize flags this.previousOptimizeFlags = this.optimizeFlags; this.optimizeFlags = 0; // Delegate call super.startInstruction(address); } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_invokespecial(org.jnode.vm.classmgr.VmConstMethodRef) */ public void visit_invokespecial(VmConstMethodRef methodRef) { methodRef.resolve(loader); final VmMethod im = methodRef.getResolvedVmMethod(); if (!canInline(im)) { // Do not inline this call super.visit_invokespecial(methodRef); } else { verifyInvoke(methodRef); inlineSpecialCounter.inc(); inline(im); } } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_invokestatic(org.jnode.vm.classmgr.VmConstMethodRef) */ public void visit_invokestatic(VmConstMethodRef methodRef) { methodRef.resolve(loader); final VmMethod im = methodRef.getResolvedVmMethod(); if (!canInline(im)) { // Do not inline this call super.visit_invokestatic(methodRef); } else { verifyInvoke(methodRef); inlineStaticCounter.inc(); inline(im); } } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_invokevirtual(org.jnode.vm.classmgr.VmConstMethodRef) */ public void visit_invokevirtual(VmConstMethodRef methodRef) { methodRef.resolve(loader); final VmMethod im = methodRef.getResolvedVmMethod(); if (!canInline(im)) { // Do not inline this call super.visit_invokevirtual(methodRef); } else { verifyInvoke(methodRef); inlineVirtualCounter.inc(); inline(im); } } /** * Inline the given method into the current method * * @param im */ private void inline(VmMethod im) { // Save some variables final char oldLocalDelta = this.localDelta; final boolean oldVisitedReturn = this.visitedReturn; final VmMethod oldMethod = this.method; final InlineBytecodeVisitor ibv = getDelegate(); final VmByteCode bc = im.getBytecode(); // Calculate the new maxLocals final int imLocals = bc.getNoLocals(); // #Locals of the inlined method final int curLocals = oldMethod.getBytecode().getNoLocals(); // #Locals // of // the // current // method maxLocals = (char) Math.max(maxLocals, oldLocalDelta + curLocals + imLocals); // Set new variables this.localDelta += curLocals; this.visitedReturn = false; this.inlineDepth++; this.method = im; // Reset optimization flags this.optimizeFlags = 0; this.previousOptimizeFlags = 0; // Start the inlining ibv.startInlinedMethodHeader(im, maxLocals); // Store the arguments in the locals of the inlined method storeArgumentsToLocals(im, ibv, localDelta); // Start the inlining ibv.startInlinedMethodCode(im, maxLocals); // Emit a NOP so we can differentiate when a method is virtually empty if (inlineDepth > 1) { ibv.visit_nop(); } // Create the control flow graph ControlFlowGraph cfg = (ControlFlowGraph) bc.getCompilerData(); if (cfg == null) { cfg = new ControlFlowGraph(bc); bc.setCompilerData(cfg); } // Compile the code 1 basic block at a time final CompilerBytecodeParser parser = new CompilerBytecodeParser(bc, cfg, this); for (BasicBlock bb : cfg) { this.startBasicBlock(bb); parser.parse(bb.getStartPC(), bb.getEndPC(), false); this.endBasicBlock(); } if (!this.isReturnVisited()) { // Generate a dummy return to avoid breaking the compilers createDummyReturn(im, this); } // End the inlining ibv.endInlinedMethod(oldMethod); // Restore variables this.localDelta = oldLocalDelta; this.visitedReturn = oldVisitedReturn; this.inlineDepth--; this.method = oldMethod; // Reset optimization flags this.optimizeFlags = 0; this.previousOptimizeFlags = 0; } private void createDummyReturn(VmMethod im, CompilerBytecodeVisitor bcv) { if (im.isReturnVoid()) { bcv.visit_return(); } else if (im.isReturnObject()) { bcv.visit_aconst_null(); bcv.visit_areturn(); } else if (im.isReturnWide()) { if (im.getReturnType().getJvmType() == JvmType.DOUBLE) { // double bcv.visit_dconst(0.0); bcv.visit_dreturn(); } else { // long bcv.visit_lconst(0); bcv.visit_lreturn(); } } else { if (im.getReturnType().getJvmType() == JvmType.FLOAT) { // float bcv.visit_fconst(0.0f); bcv.visit_freturn(); } else { // int bcv.visit_iconst(0); bcv.visit_ireturn(); } } } /** * Pop the method arguments of the stack and store them in locals. * * @param im * @param ibv * @param localDelta */ private void storeArgumentsToLocals(VmMethod im, InlineBytecodeVisitor ibv, int localDelta) { final int cnt = im.getNoArguments(); int local = localDelta + im.getArgSlotCount() - 1; /* * if (im.isStatic()) { local--; } */ for (int i = cnt - 1; i >= 0; i--) { final VmType<?> argType = im.getArgumentType(i); // System.out.println("arg" + i + ": " + argType); if (argType.isPrimitive()) { final VmPrimitiveClass<?> pc = (VmPrimitiveClass<?>) argType; if (pc.isWide()) { local--; if (pc.isFloatingPoint()) { // double ibv.visit_dstore(local); } else { // long ibv.visit_lstore(local); } } else { if (pc.isFloatingPoint()) { // float ibv.visit_fstore(local); } else { // int ibv.visit_istore(local); } } } else { ibv.visit_astore(local); } local--; } if (!im.isStatic()) { // TODO add nullpointer check // Store this pointer. ibv.visit_astore(local); } } /** * Can the given method be inlined? * * @param method * @return */ private boolean canInline(VmMethod method) { // First determine if we CAN inline if (method.isNative() || method.isAbstract() || method.isSynchronized()) { return false; } if (!(method.isFinal() || method.isPrivate() || method.isStatic() || method .getDeclaringClass().isFinal())) { return false; } final VmType<?> declClass = method.getDeclaringClass(); if (declClass.isMagicType()) { return false; } if (!declClass.isAlwaysInitialized()) { return false; } final VmByteCode bc = method.getBytecode(); if (bc == null) { return false; } if (method.hasNoInlinePragma()) { return false; } if (bc.getNoExceptionHandlers() > 0) { return false; } // Now determine if we SHOULD inline if (!method.hasInlinePragma()) { if (inlineDepth >= MAX_INLINE_DEPTH) { return false; } if (bc.getLength() > SIZE_LIMIT) { return false; } } return true; } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_aload(int) */ public void visit_aload(int index) { index += localDelta; if (((previousOptimizeFlags & OptFlags.ASTORE) != 0) && (storeIndex == index)) { storeLoadCounter.inc(); super.visit_aloadStored(index); } else if (((previousOptimizeFlags & OptFlags.ALOAD) != 0) && (loadIndex == index)) { super.visit_dup(); } else { super.visit_aload(index); } loadIndex = index; optimizeFlags |= OptFlags.ALOAD; } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_areturn() */ public void visit_areturn() { if (inlineDepth == 0) { super.visit_areturn(); } else { visitedReturn = true; getDelegate().visit_inlinedReturn(JvmType.REFERENCE); } } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_astore(int) */ public void visit_astore(int index) { index += localDelta; this.optimizeFlags |= OptFlags.ASTORE; this.storeIndex = index; super.visit_astore(index); } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_dload(int) */ public void visit_dload(int index) { index += localDelta; if (((previousOptimizeFlags & OptFlags.DSTORE) != 0) && (storeIndex == index)) { storeLoadCounter.inc(); super.visit_dloadStored(index); } else if (((previousOptimizeFlags & OptFlags.DLOAD) != 0) && (loadIndex == index)) { super.visit_dup2(); } else { super.visit_dload(index); } loadIndex = index; optimizeFlags |= OptFlags.DLOAD; } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_dreturn() */ public void visit_dreturn() { if (inlineDepth == 0) { super.visit_dreturn(); } else { visitedReturn = true; getDelegate().visit_inlinedReturn(JvmType.DOUBLE); } } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_dstore(int) */ public void visit_dstore(int index) { index += localDelta; this.optimizeFlags |= OptFlags.DSTORE; this.storeIndex = index; super.visit_dstore(index); } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_fload(int) */ public void visit_fload(int index) { index += localDelta; if (((previousOptimizeFlags & OptFlags.FSTORE) != 0) && (storeIndex == index)) { storeLoadCounter.inc(); super.visit_floadStored(index); } else if (((previousOptimizeFlags & OptFlags.FLOAD) != 0) && (loadIndex == index)) { super.visit_dup(); } else { super.visit_fload(index); } loadIndex = index; optimizeFlags |= OptFlags.FLOAD; } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_freturn() */ public void visit_freturn() { if (inlineDepth == 0) { super.visit_freturn(); } else { visitedReturn = true; getDelegate().visit_inlinedReturn(JvmType.FLOAT); } } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_fstore(int) */ public void visit_fstore(int index) { index += localDelta; this.optimizeFlags |= OptFlags.FSTORE; this.storeIndex = index; super.visit_fstore(index); } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_iinc(int, int) */ public void visit_iinc(int index, int incValue) { super.visit_iinc(index + localDelta, incValue); } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_iload(int) */ public void visit_iload(int index) { index += localDelta; if (((previousOptimizeFlags & OptFlags.ISTORE) != 0) && (storeIndex == index)) { storeLoadCounter.inc(); super.visit_iloadStored(index); } else if (((previousOptimizeFlags & OptFlags.ILOAD) != 0) && (loadIndex == index)) { super.visit_dup(); } else { super.visit_iload(index); } loadIndex = index; optimizeFlags |= OptFlags.ILOAD; } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_ireturn() */ public void visit_ireturn() { if (inlineDepth == 0) { super.visit_ireturn(); } else { visitedReturn = true; getDelegate().visit_inlinedReturn(JvmType.INT); } } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_istore(int) */ public void visit_istore(int index) { index += localDelta; this.optimizeFlags |= OptFlags.ISTORE; this.storeIndex = index; super.visit_istore(index); } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_lload(int) */ public void visit_lload(int index) { index += localDelta; if (((previousOptimizeFlags & OptFlags.LSTORE) != 0) && (storeIndex == index)) { storeLoadCounter.inc(); super.visit_lloadStored(index); } else if (((previousOptimizeFlags & OptFlags.LLOAD) != 0) && (loadIndex == index)) { super.visit_dup2(); } else { super.visit_lload(index); } loadIndex = index; optimizeFlags |= OptFlags.LLOAD; } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_lreturn() */ public void visit_lreturn() { if (inlineDepth == 0) { super.visit_lreturn(); } else { visitedReturn = true; getDelegate().visit_inlinedReturn(JvmType.LONG); } } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_lstore(int) */ public void visit_lstore(int index) { index += localDelta; this.optimizeFlags |= OptFlags.LSTORE; this.storeIndex = index; super.visit_lstore(index); } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_ret(int) */ public void visit_ret(int index) { super.visit_ret(index + localDelta); } /** * @see org.jnode.vm.bytecode.BytecodeVisitor#visit_return() */ public void visit_return() { if (inlineDepth == 0) { super.visit_return(); } else { visitedReturn = true; getDelegate().visit_inlinedReturn(JvmType.VOID); } } /** * Have we visited a return statement? * * @return {@code true} if we have visited a return statement, {code false} otherwise. */ public boolean isReturnVisited() { return visitedReturn; } /** * @see org.jnode.vm.compiler.DelegatingCompilerBytecodeVisitor#visit_monitorenter() */ public void visit_monitorenter() { verifyMonitor(); inline(entryPoints.getMonitorEnterMethod()); } /** * @see org.jnode.vm.compiler.DelegatingCompilerBytecodeVisitor#visit_monitorexit() */ public void visit_monitorexit() { verifyMonitor(); inline(entryPoints.getMonitorExitMethod()); } /** * @see org.jnode.vm.compiler.DelegatingCompilerBytecodeVisitor#visit_new(org.jnode.vm.classmgr.VmConstClass) */ public void visit_new(VmConstClass clazz) { if (false) { // Inline call to {@link SoftByteCodes#allocObject} clazz.resolve(loader); /* Setup a call to SoftByteCodes.allocObject */ visit_ldc(clazz.getResolvedVmClass()); // vmClass visit_iconst(-1); // size inline(entryPoints.getAllocObjectMethod()); } else { super.visit_new(clazz); } } }