aboutsummaryrefslogtreecommitdiff
path: root/exec/java-exec/src/main/java/org/apache/drill/exec/compile/bytecode/InstructionModifier.java
blob: 291cf6baae7c203ffacda63b1e00997720cca379 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
/*
 * 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 org.apache.drill.shaded.guava.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();
  }
}