| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.drill.exec.compile.bytecode; |
| |
| import java.util.HashMap; |
| |
| import org.apache.drill.exec.compile.CompilationConfig; |
| import org.apache.drill.exec.compile.bytecode.ValueHolderIden.ValueHolderSub; |
| import org.objectweb.asm.Label; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.Type; |
| import org.objectweb.asm.tree.analysis.BasicValue; |
| import org.objectweb.asm.tree.analysis.Frame; |
| |
| import com.carrotsearch.hppc.IntIntHashMap; |
| import com.carrotsearch.hppc.IntObjectHashMap; |
| import com.carrotsearch.hppc.cursors.IntIntCursor; |
| import com.carrotsearch.hppc.cursors.IntObjectCursor; |
| import com.google.common.base.Preconditions; |
| |
| public class InstructionModifier extends MethodVisitor { |
| private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(InstructionModifier.class); |
| |
| /* Map from old (reference) local variable index to new local variable information. */ |
| private final IntObjectHashMap<ValueHolderIden.ValueHolderSub> oldToNew = new IntObjectHashMap<>(); |
| |
| private final IntIntHashMap oldLocalToFirst = new IntIntHashMap(); |
| |
| private final DirectSorter adder; |
| private int lastLineNumber = 0; // the last line number seen |
| private final TrackingInstructionList list; |
| private final String name; |
| private final String desc; |
| private final String signature; |
| |
| private int stackIncrease = 0; // how much larger we have to make the stack |
| |
| public InstructionModifier(final int access, final String name, final String desc, |
| final String signature, final String[] exceptions, final TrackingInstructionList list, |
| final MethodVisitor inner) { |
| super(CompilationConfig.ASM_API_VERSION, new DirectSorter(access, desc, inner)); |
| this.name = name; |
| this.desc = desc; |
| this.signature = signature; |
| this.list = list; |
| this.adder = (DirectSorter) mv; |
| } |
| |
| public int getLastLineNumber() { |
| return lastLineNumber; |
| } |
| |
| private static ReplacingBasicValue filterReplacement(final BasicValue basicValue) { |
| if (basicValue instanceof ReplacingBasicValue) { |
| final ReplacingBasicValue replacingValue = (ReplacingBasicValue) basicValue; |
| if (replacingValue.isReplaceable()) { |
| return replacingValue; |
| } |
| } |
| |
| return null; |
| } |
| |
| private ReplacingBasicValue getLocal(final int var) { |
| final BasicValue basicValue = list.currentFrame.getLocal(var); |
| return filterReplacement(basicValue); |
| } |
| |
| /** |
| * Peek at a value in the current frame's stack, counting down from the top. |
| * |
| * @param depth how far down to peek; 0 is the top of the stack, 1 is the |
| * first element down, etc |
| * @return the value on the stack, or null if it isn't a ReplacingBasciValue |
| */ |
| private ReplacingBasicValue peekFromTop(final int depth) { |
| Preconditions.checkArgument(depth >= 0); |
| final Frame<BasicValue> frame = list.currentFrame; |
| final BasicValue basicValue = frame.getStack((frame.getStackSize() - 1) - depth); |
| return filterReplacement(basicValue); |
| } |
| |
| /** |
| * Get the value of a function return if it is a ReplacingBasicValue. |
| * |
| * <p>Assumes that we're in the middle of processing an INVOKExxx instruction. |
| * |
| * @return the value that will be on the top of the stack after the function returns |
| */ |
| private ReplacingBasicValue getFunctionReturn() { |
| final Frame<BasicValue> nextFrame = list.nextFrame; |
| final BasicValue basicValue = nextFrame.getStack(nextFrame.getStackSize() - 1); |
| return filterReplacement(basicValue); |
| } |
| |
| @Override |
| public void visitInsn(final int opcode) { |
| switch (opcode) { |
| case Opcodes.DUP: |
| /* |
| * Pattern: |
| * BigIntHolder out5 = new BigIntHolder(); |
| * |
| * Original bytecode: |
| * NEW org/apache/drill/exec/expr/holders/BigIntHolder |
| * DUP |
| * INVOKESPECIAL org/apache/drill/exec/expr/holders/BigIntHolder.<init> ()V |
| * ASTORE 6 # the index of the out5 local variable (which is a reference) |
| * |
| * Desired replacement: |
| * ICONST_0 |
| * ISTORE 12 |
| * |
| * In the original, the holder's objectref will be used twice: once for the |
| * constructor call, and then to be stored. Since the holder has been replaced |
| * with one or more locals to hold its members, we don't allocate or store it. |
| * he NEW and the ASTORE are replaced via some stateless changes elsewhere; here |
| * we need to eliminate the DUP. |
| * |
| * TODO: there may be other reasons for a DUP to appear in the instruction stream, |
| * such as reuse of a common subexpression that the compiler optimizer has |
| * eliminated. This pattern may also be used for non-holders. We need to be |
| * more certain of the source of the DUP, and whether the surrounding context matches |
| * the above. |
| */ |
| if (peekFromTop(0) != null) { |
| return; // don't emit the DUP |
| } |
| break; |
| |
| case Opcodes.DUP_X1: { |
| /* |
| * There are a number of patterns that lead to this instruction being generated. Here |
| * are some examples: |
| * |
| * Pattern: |
| * 129: out.start = out.end = text.end; |
| * |
| * Original bytecode: |
| * L9 |
| * LINENUMBER 129 L9 |
| * ALOAD 7 |
| * ALOAD 7 |
| * ALOAD 8 |
| * GETFIELD org/apache/drill/exec/expr/holders/VarCharHolder.end : I |
| * DUP_X1 |
| * PUTFIELD org/apache/drill/exec/expr/holders/VarCharHolder.end : I |
| * PUTFIELD org/apache/drill/exec/expr/holders/VarCharHolder.start : I |
| * |
| * Desired replacement: |
| * L9 |
| * LINENUMBER 129 L9 |
| * ILOAD 17 |
| * DUP |
| * ISTORE 14 |
| * ISTORE 13 |
| * |
| * At this point, the ALOAD/GETFIELD and ALOAD/PUTFIELD combinations have |
| * been replaced by the ILOAD and ISTOREs. However, there is still the DUP_X1 |
| * in the instruction stream. In this case, it is duping the fetched holder |
| * member so that it can be stored again. We still need to do that, but because |
| * the ALOADed objectrefs are no longer on the stack, we don't need to duplicate |
| * the value lower down in the stack anymore, but can instead DUP it where it is. |
| * (Similarly, if the fetched field was a long or double, the same applies for |
| * the expected DUP2_X1.) |
| * |
| * There's also a similar pattern for zeroing values: |
| * Pattern: |
| * 170: out.start = out.end = 0; |
| * |
| * Original bytecode: |
| * L20 |
| * LINENUMBER 170 L20 |
| * ALOAD 13 |
| * ALOAD 13 |
| * ICONST_0 |
| * DUP_X1 |
| * PUTFIELD org/apache/drill/exec/expr/holders/VarCharHolder.end : I |
| * PUTFIELD org/apache/drill/exec/expr/holders/VarCharHolder.start : I |
| * |
| * Desired replacement: |
| * L20 |
| * LINENUMBER 170 L20 |
| * ICONST_0 |
| * DUP |
| * ISTORE 17 |
| * ISTORE 16 |
| * |
| * |
| * There's also another pattern that involves DUP_X1 |
| * Pattern: |
| * 1177: out.buffer.setByte(out.end++, currentByte); |
| * |
| * We're primarily interested in the out.end++ -- the post-increment of |
| * a holder member. |
| * |
| * Original bytecode: |
| * L694 |
| * LINENUMBER 1177 L694 |
| * ALOAD 212 |
| * GETFIELD org/apache/drill/exec/expr/holders/VarCharHolder.buffer : Lio/netty/buffer/DrillBuf; |
| * ALOAD 212 |
| * DUP |
| * > GETFIELD org/apache/drill/exec/expr/holders/VarCharHolder.end : I |
| * > DUP_X1 |
| * > ICONST_1 |
| * > IADD |
| * > PUTFIELD org/apache/drill/exec/expr/holders/VarCharHolder.end : I |
| * ILOAD 217 |
| * INVOKEVIRTUAL io/netty/buffer/DrillBuf.setByte (II)Lio/netty/buffer/ByteBuf; |
| * POP |
| * |
| * This fragment includes the entirety of the line 1177 above, but we're only concerned with |
| * the lines marked with '>' on the left; the rest were kept for context, because it is the |
| * use of the pre-incremented value as a function argument that is generating the DUP_X1 -- |
| * the DUP_X1 is how the value is preserved before incrementing. |
| * |
| * The common element in these patterns is that the stack has an objectref and a value that will |
| * be stored via a PUTFIELD. The value has to be used in other contexts, so it is to be DUPed, and |
| * stashed away under the objectref. In the case where the objectref belongs to a holder that will |
| * be gone as a result of scalar replacement, then the objectref won't be present, so we don't need |
| * the _X1 option. |
| * |
| * If we're replacing the holder under the value being duplicated, then we don't need to put the |
| * DUPed value back under it, because it won't be present in the stack. We can just use a plain DUP. |
| */ |
| final ReplacingBasicValue rbValue = peekFromTop(1); |
| if (rbValue != null) { |
| super.visitInsn(Opcodes.DUP); |
| return; |
| } |
| break; |
| } |
| |
| case Opcodes.DUP2_X1: { |
| /* |
| * See the comments above for DUP_X1, which also apply here; this just handles long and double |
| * values, which are twice as large, in the same way. |
| */ |
| if (peekFromTop(0) != null) { |
| throw new IllegalStateException("top of stack should be 2nd part of a long or double"); |
| } |
| final ReplacingBasicValue rbValue = peekFromTop(2); |
| if (rbValue != null) { |
| super.visitInsn(Opcodes.DUP2); |
| return; |
| } |
| break; |
| } |
| } |
| |
| // if we get here, emit the original instruction |
| super.visitInsn(opcode); |
| } |
| |
| @Override |
| public void visitTypeInsn(final int opcode, final String type) { |
| /* |
| * This includes NEW, NEWARRAY, CHECKCAST, or INSTANCEOF. |
| * |
| * TODO: aren't we just trying to eliminate NEW (and possibly NEWARRAY)? |
| * It looks like we'll currently pick those up because we'll only have |
| * replaced the values for those, but we might find other reasons to replace |
| * things, in which case this will be too broad. |
| */ |
| final ReplacingBasicValue r = getFunctionReturn(); |
| if (r != null) { |
| final ValueHolderSub sub = r.getIden().getHolderSub(adder); |
| oldToNew.put(r.getIndex(), sub); |
| } else { |
| super.visitTypeInsn(opcode, type); |
| } |
| } |
| |
| @Override |
| public void visitLineNumber(final int line, final Label start) { |
| lastLineNumber = line; |
| super.visitLineNumber(line, start); |
| } |
| |
| @Override |
| public void visitVarInsn(final int opcode, final int var) { |
| ReplacingBasicValue v; |
| if (opcode == Opcodes.ASTORE && (v = peekFromTop(0)) != null) { |
| final ValueHolderSub from = oldToNew.get(v.getIndex()); |
| |
| final ReplacingBasicValue current = getLocal(var); |
| // if local var is set, then transfer to it to the existing holders in the local position. |
| if (current != null) { |
| final ValueHolderSub newSub = oldToNew.get(current.getIndex()); |
| if (newSub.iden() == from.iden()) { |
| final int targetFirst = newSub.first(); |
| from.transfer(this, targetFirst); |
| return; |
| } |
| } |
| |
| // if local var is not set, then check map to see if existing holders are mapped to local var. |
| if (oldLocalToFirst.containsKey(var)) { |
| final ValueHolderSub sub = oldToNew.get(oldLocalToFirst.get(var)); |
| if (sub.iden() == from.iden()) { |
| // if they are, then transfer to that. |
| from.transfer(this, sub.first()); |
| return; |
| } |
| } |
| |
| // map from variables to global space for future use. |
| oldLocalToFirst.put(var, v.getIndex()); |
| return; |
| } else if (opcode == Opcodes.ALOAD && (v = getLocal(var)) != null) { |
| /* |
| * Not forwarding this removes a now unnecessary ALOAD for a holder. The required LOAD/STORE |
| * sequences will be generated by the ASTORE code above. |
| */ |
| return; |
| } |
| |
| super.visitVarInsn(opcode, var); |
| } |
| |
| void directVarInsn(final int opcode, final int var) { |
| adder.directVarInsn(opcode, var); |
| } |
| |
| @Override |
| public void visitMaxs(final int maxStack, final int maxLocals) { |
| super.visitMaxs(maxStack + stackIncrease, maxLocals); |
| } |
| |
| @Override |
| public void visitFieldInsn(final int opcode, final String owner, |
| final String name, final String desc) { |
| int stackDepth = 0; |
| ReplacingBasicValue value; |
| switch (opcode) { |
| case Opcodes.PUTFIELD: |
| value = peekFromTop(stackDepth++); |
| if (value != null) { |
| if (filterReplacement(value) == null) { |
| super.visitFieldInsn(opcode, owner, name, desc); |
| return; |
| } else { |
| /* |
| * We are trying to store a replaced variable in an external context, |
| * we need to generate an instance and transfer it out. |
| */ |
| final ValueHolderSub sub = oldToNew.get(value.getIndex()); |
| final int additionalStack = sub.transferToExternal(adder, owner, name, desc); |
| if (additionalStack > stackIncrease) { |
| stackIncrease = additionalStack; |
| } |
| return; |
| } |
| } |
| // $FALL-THROUGH$ |
| |
| case Opcodes.GETFIELD: |
| // if falling through from PUTFIELD, this gets the 2nd item down |
| value = peekFromTop(stackDepth); |
| if (value != null) { |
| if (filterReplacement(value) != null) { |
| final ValueHolderSub sub = oldToNew.get(value.getIndex()); |
| if (sub != null) { |
| sub.addInsn(name, this, opcode); |
| return; |
| } |
| } |
| } |
| /* FALLTHROUGH */ |
| } |
| |
| // if we get here, emit the field reference as-is |
| super.visitFieldInsn(opcode, owner, name, desc); |
| } |
| |
| @Override |
| public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc) { |
| /* |
| * This method was deprecated in the switch from api version ASM4 to ASM5. |
| * If we ever go back (via CompilationConfig.ASM_API_VERSION), we need to |
| * duplicate the work from the other overloaded version of this method. |
| */ |
| assert CompilationConfig.ASM_API_VERSION == Opcodes.ASM4; |
| throw new RuntimeException("this method is deprecated"); |
| } |
| |
| @Override |
| public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf) { |
| // this version of visitMethodInsn() came after ASM4 |
| assert CompilationConfig.ASM_API_VERSION != Opcodes.ASM4; |
| |
| final int argCount = Type.getArgumentTypes(desc).length; |
| if (opcode != Opcodes.INVOKESTATIC) { |
| final ReplacingBasicValue thisRef = peekFromTop(argCount); |
| |
| if (thisRef != null) { |
| /* |
| * If the this reference is a holder, we need to initialize the variables |
| * that replaced it; that amounts to replacing its constructor call with |
| * variable initializations. |
| */ |
| if (name.equals("<init>")) { |
| oldToNew.get(thisRef.getIndex()).init(adder); |
| return; |
| } else { |
| /* |
| * This is disallowed because the holder variables are being ignored in |
| * favor of the locals we've replaced them with. |
| */ |
| throw new IllegalStateException("You can't call a method on a value holder."); |
| } |
| } |
| } |
| |
| /* |
| * If we got here, we're not calling a method on a holder. |
| * |
| * Does the function being called return a holder? |
| */ |
| if (Type.getReturnType(desc) != Type.VOID_TYPE) { |
| ReplacingBasicValue functionReturn = getFunctionReturn(); |
| if (functionReturn != null) { |
| /* |
| * The return of this method is an actual instance of the object we're escaping. |
| * Update so that it gets mapped correctly. |
| */ |
| super.visitMethodInsn(opcode, owner, name, desc, itf); |
| functionReturn.markFunctionReturn(); |
| return; |
| } |
| } |
| |
| /* |
| * Holders can't be passed as arguments to methods, because their contents aren't |
| * maintained; we use the locals instead. Therefore, complain if any arguments are holders. |
| */ |
| for (int argDepth = argCount - 1; argDepth >= 0; --argDepth) { |
| ReplacingBasicValue argValue = peekFromTop(argDepth); |
| if (argValue != null) { |
| throw new IllegalStateException( |
| String.format("Holder types are not allowed to be passed between methods. " + |
| "Ran across problem attempting to invoke method '%s' on line number %d", |
| name, lastLineNumber)); |
| } |
| } |
| |
| // if we get here, emit this function call as it was |
| super.visitMethodInsn(opcode, owner, name, desc, itf); |
| } |
| |
| @Override |
| public void visitEnd() { |
| if (logger.isTraceEnabled()) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("InstructionModifier "); |
| sb.append(name); |
| sb.append(' '); |
| sb.append(signature); |
| sb.append('\n'); |
| if ((desc != null) && !desc.isEmpty()) { |
| sb.append(" desc: "); |
| sb.append(desc); |
| sb.append('\n'); |
| } |
| |
| int idenId = 0; // used to generate unique ids for the ValueHolderIden's |
| int itemCount = 0; // counts up the number of items found |
| final HashMap<ValueHolderIden, Integer> seenIdens = new HashMap<>(); // iden -> idenId |
| sb.append(" .oldToNew:\n"); |
| for (final IntObjectCursor<ValueHolderIden.ValueHolderSub> ioc : oldToNew) { |
| final ValueHolderIden iden = ioc.value.iden(); |
| if (!seenIdens.containsKey(iden)) { |
| seenIdens.put(iden, ++idenId); |
| sb.append("ValueHolderIden[" + idenId + "]:\n"); |
| iden.dump(sb); |
| } |
| |
| sb.append(" " + ioc.key + " => " + ioc.value + '[' + seenIdens.get(iden) + "]\n"); |
| ++itemCount; |
| } |
| |
| sb.append(" .oldLocalToFirst:\n"); |
| for (final IntIntCursor iic : oldLocalToFirst) { |
| sb.append(" " + iic.key + " => " + iic.value + '\n'); |
| ++itemCount; |
| } |
| |
| if (itemCount > 0) { |
| logger.debug(sb.toString()); |
| } |
| } |
| |
| super.visitEnd(); |
| } |
| } |