blob: a6892d10e48ec4e6961ac7d2ebd913d3554c5216 [file] [log] [blame]
/*
* 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();
}
}