| /* |
| * |
| * 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.flex.abc.optimize; |
| |
| import static org.apache.flex.abc.ABCConstants.*; |
| |
| import org.apache.flex.abc.instructionlist.InstructionList; |
| import org.apache.flex.abc.semantics.ECMASupport; |
| import org.apache.flex.abc.semantics.Instruction; |
| import org.apache.flex.abc.semantics.InstructionFactory; |
| import org.apache.flex.abc.semantics.Label; |
| import org.apache.flex.abc.visitors.DelegatingMethodBodyVisitor; |
| import org.apache.flex.abc.visitors.IMethodBodyVisitor; |
| |
| import java.util.ArrayList; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| /** |
| * IMethodBodyVisitor that will do peephole optimization of the method body. |
| * TODO: Not copmlete yet - should be equivalent to the peephole optimizer in |
| * MXMLC 4.5 Optimizations implemented: Eliminate unnecessary convert_d |
| * instructions Eliminate unnecessary convert_b instructions Eliminate |
| * unnecessary convert_i instructions Eliminate unnecessary convert_s |
| * instructions Eliminate unnecessary convert_u instructions Replace callprops |
| * where the return value is unused, with callpropvoid |
| */ |
| public class PeepholeOptimizerMethodBodyVisitor extends DelegatingMethodBodyVisitor |
| { |
| /** |
| * How many instructions we can look back on |
| */ |
| private static final int PEEPHOLE_WINDOW_SIZE = 4; |
| |
| /** |
| * Constant that means we don't have any labels in the peephole window |
| */ |
| private static final int NO_LABEL = -1; |
| |
| public PeepholeOptimizerMethodBodyVisitor(IMethodBodyVisitor delegate) |
| { |
| super(delegate); |
| } |
| |
| /** |
| * Hold a small window of previous instructions so that the optimizer can |
| * rewind and rewrite small sections of ABC |
| */ |
| private List<InstructionInfo> instructions = new LinkedList<InstructionInfo>(); |
| |
| /** |
| * The index of the instruction with the most recently seen label - we can't |
| * look back past a label. |
| */ |
| private int lastLabelSeen = NO_LABEL; |
| |
| /** |
| * If we delete instructions that have labeling actions associated with |
| * them, we have to apply those labeling instructions to whatever |
| * instruction ends up replacing the deleted instruction(s). This list holds |
| * those labels. |
| */ |
| private List<Label> labelsFromDeletedInsns = null; |
| |
| /** |
| * basically holds a function object that sends instructions on to the right |
| * place once the peephole optimizer is finished. This will either send the |
| * instructions on to the next IMethodBodyVisitor, or to a new |
| * InstructionList |
| */ |
| private InstructionFinisher finisher = new DelegateFinisher(); |
| |
| /** |
| * Resets the entire method body at once. The Peephole optimizer will |
| * process the entire instruction list, performing it's optimizations, |
| * before passing the InstructionList down to the next IMethodBodyVisitor. So |
| * any MethodBodyVisitors that the optimizer delegates to will receive a new |
| * InstructionList, with optimizations, instead of the InstructionList |
| * passed in. |
| * |
| * @param new_list The new Instructions for the method. |
| */ |
| @Override |
| public void visitInstructionList(InstructionList new_list) |
| { |
| // for the delegates that run after this IVisitor, they will be processing |
| // a new copy of the instruction list, which will have the optimized opcodes |
| InstructionList newInstructions = new InstructionList(); |
| InstructionFinisher old = this.finisher; |
| |
| // Create a new finisher, which will put the instruction into a new InstructionList |
| // instead of passing them on to the next delegate |
| this.finisher = new InstructionListFinisher(newInstructions); |
| for (Instruction inst : new_list.getInstructions()) |
| { |
| visitInstruction(inst); |
| } |
| // Make sure we flush any remaining instructions to the IL |
| flush(); |
| |
| // delegate the call with the new instruction list |
| super.visitInstructionList(newInstructions); |
| |
| // Reset the finisher to the delegating one |
| this.finisher = old; |
| } |
| |
| @Override |
| public void visitInstruction(int opcode) |
| { |
| visitInstruction(InstructionFactory.getInstruction(opcode)); |
| |
| } |
| |
| @Override |
| public void visitInstruction(int opcode, int immediate_operand) |
| { |
| visitInstruction(InstructionFactory.getInstruction(opcode, immediate_operand)); |
| } |
| |
| @Override |
| public void visitInstruction(int opcode, Object[] operands) |
| { |
| visitInstruction(InstructionFactory.getInstruction(opcode, operands)); |
| } |
| |
| @Override |
| public void visitInstruction(int opcode, Object single_operand) |
| { |
| visitInstruction(InstructionFactory.getInstruction(opcode, single_operand)); |
| } |
| |
| @Override |
| /** |
| * Add the instruction to our peephole window and performs any optimizations. |
| * |
| * The optimizations are performed on the instructions already in the window - we do them |
| * now, and not when they are added, because we need to know if any labels targeted the instructions |
| * and we don't know that when we first see the instruction (but we do when we see the next instruction) |
| * @param instruction the Instruction to add |
| */ |
| public void visitInstruction(Instruction instruction) |
| { |
| // Optimize the instructions we already have |
| processPreviousInstructions(); |
| |
| // add the instruction, but don't do any optimizations yet |
| addInstruction(instruction); |
| } |
| |
| /** |
| * Perform optimizations on the instructions currently held in the window. |
| * This is so we don't do the optimizations until we are sure a particular |
| * instruction was or was not the target of a jump. If it was a target, then |
| * we don't do the optimizations as we don't have enough information to |
| * determine all the ways control might flow to that instruction. |
| */ |
| private void processPreviousInstructions() |
| { |
| InstructionInfo last = previous(0); |
| InstructionInfo secondLast = previous(1); |
| |
| // Check if we have a labelCurrent on the last instruction, or a labelNext on the previous instruction |
| if (last.getLabelCurrents().isEmpty() && secondLast.getLabelNexts().isEmpty()) |
| { |
| switch (last.getOpcode()) |
| { |
| case OP_convert_b: |
| { |
| op_convert_b(last); |
| break; |
| } |
| case OP_convert_d: |
| { |
| op_convert_d(last); |
| break; |
| } |
| case OP_convert_i: |
| { |
| op_convert_i(last); |
| break; |
| } |
| case OP_convert_u: |
| { |
| op_convert_u(last); |
| break; |
| } |
| case OP_convert_s: |
| { |
| op_convert_s(last); |
| break; |
| } |
| case OP_getproperty: |
| { |
| op_getproperty(last); |
| break; |
| } |
| case OP_iffalse: |
| { |
| op_iffalse(last); |
| break; |
| } |
| case OP_iftrue: |
| { |
| op_iftrue(last); |
| break; |
| } |
| case OP_pop: |
| { |
| op_pop(last); |
| break; |
| } |
| case OP_nop: |
| { |
| delete(0); |
| break; |
| } |
| case OP_getlocal: |
| { |
| OP_getlocal(last); |
| break; |
| } |
| case OP_returnvoid: |
| { |
| op_returnvoid(last); |
| break; |
| } |
| default: |
| { |
| break; |
| } |
| } |
| } |
| } |
| |
| public void visitEnd() |
| { |
| flush(); |
| super.visitEnd(); |
| } |
| |
| /** |
| * Flush any remaining instructions |
| */ |
| private void flush() |
| { |
| processPreviousInstructions(); |
| |
| // finish any remaining instructions |
| for (InstructionInfo info : instructions) |
| { |
| finishInstruction(info); |
| } |
| |
| instructions.clear(); |
| } |
| |
| /** |
| * Bind a Label object (not to be confused with the AVM OP_label |
| * instruction) to the last-visited ABC instruction in this method. |
| * |
| * @pre visitInstruction() must have been called at least once, i.e., there |
| * must be a last-visited ABC instruction. |
| */ |
| public void labelCurrent(Label l) |
| { |
| jumpOptimizations(l, LabelKind.LABEL_CURRENT); |
| |
| int idx = getLastInstructionIndex(); |
| InstructionInfo instructionInfo = instructions.get(idx); |
| lastLabelSeen = idx; |
| instructionInfo.addLabelCurrent(l); |
| } |
| |
| /** |
| * Enum to pass around to indicate what kind of labeling operation we are |
| * performing |
| */ |
| private static enum LabelKind |
| { |
| LABEL_CURRENT, |
| LABEL_NEXT |
| } |
| |
| /** |
| * Helper method to perform optimizations around jumps when we get a |
| * labeling operation. Shared with labelNext and labelCurrent methods |
| * |
| * @param l the Label object passed into the label operation |
| * @param kind what kind of labeling operation are we doing (labelNext or |
| * labelCurrent) |
| */ |
| private void jumpOptimizations(Label l, LabelKind kind) |
| { |
| // Need to start looking at different indexes for label current vs. label next |
| // this is because for a label next, the next instruction won't have come in yet. |
| int idx = kind == LabelKind.LABEL_CURRENT ? 1 : 0; |
| |
| InstructionInfo prev = previous(idx); |
| switch (prev.getOpcode()) |
| { |
| case OP_jump: |
| { |
| InstructionInfo prev2 = previous(idx + 1); |
| Instruction insn = prev2.getInstruction(); |
| if (insn != null && insn.isBranch() && insn.getTarget() == l) |
| { |
| // If the previous instructions were an if that jumped here, and it |
| // only jumped over another jump, then we can invert the if instruction |
| // and save a jump |
| // iffalse L1, jump L2, L1 -> iftrue L2, L1 |
| Instruction newIf = invertIf(prev2, prev); |
| if (newIf != null) |
| { |
| if (kind == LabelKind.LABEL_CURRENT) |
| { |
| // labelCurrent, so we need to preserve the last instruction |
| Instruction[] newInsns = {newIf, previous(0).getInstruction()}; |
| replace(idx + 1, newInsns); |
| } |
| else |
| { |
| // labelNext so we can just delete the last instruction |
| replace(idx + 1, newIf); |
| } |
| } |
| } |
| // If the previous instruction was a jump, and it just jumped |
| // to the next instruction, then we can remove the jump and just fall |
| // through |
| // jump L1, L1 -> L1 |
| else if (prev.getOperand(0) == l) |
| { |
| if (kind == LabelKind.LABEL_NEXT) |
| // can just delete the jump because we don't have the next instruction yet |
| delete(idx); |
| else |
| // replace the jump with its target |
| replace(idx, previous(0).getInstruction()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Helper method to invert if expr target jump other-target to if not expr |
| * other-target Used to optimize some common patterns with if's and jumps |
| */ |
| private Instruction invertIf(InstructionInfo oldIf, InstructionInfo oldJump) |
| { |
| Instruction newIf = null; |
| switch (oldIf.getOpcode()) |
| { |
| case OP_ifeq: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_ifne, oldJump.getInstruction()); |
| break; |
| } |
| case OP_ifstricteq: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_ifstrictne, oldJump.getInstruction()); |
| break; |
| } |
| case OP_ifstrictne: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_ifstricteq, oldJump.getInstruction()); |
| break; |
| } |
| case OP_ifge: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_ifnge, oldJump.getInstruction()); |
| break; |
| } |
| case OP_ifgt: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_ifngt, oldJump.getInstruction()); |
| break; |
| } |
| case OP_iflt: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_ifnlt, oldJump.getInstruction()); |
| break; |
| } |
| case OP_ifle: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_ifnle, oldJump.getInstruction()); |
| break; |
| } |
| case OP_ifne: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_ifeq, oldJump.getInstruction()); |
| break; |
| } |
| case OP_ifnge: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_ifge, oldJump.getInstruction()); |
| break; |
| } |
| case OP_ifngt: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_ifgt, oldJump.getInstruction()); |
| break; |
| } |
| case OP_ifnlt: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_iflt, oldJump.getInstruction()); |
| break; |
| } |
| case OP_ifnle: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_ifle, oldJump.getInstruction()); |
| break; |
| } |
| case OP_iftrue: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_iffalse, oldJump.getInstruction()); |
| break; |
| } |
| case OP_iffalse: |
| { |
| newIf = InstructionFactory.createModifiedInstruction(OP_iftrue, oldJump.getInstruction()); |
| break; |
| } |
| } |
| |
| return newIf; |
| } |
| |
| /** |
| * Helper method to get the index of the last instruction in the window |
| * |
| * @return the index of the last instruction |
| */ |
| private int getLastInstructionIndex() |
| { |
| return instructions.size() - 1; |
| } |
| |
| /** |
| * Bind a Label object (not to be confused with the AVM OP_label |
| * instruction) to the next ABC instruction that gets visited in this method |
| * |
| * @post visitInstruction() must be called at least once after labelNext, |
| * i.e., there must be a next instruction. |
| */ |
| public void labelNext(Label l) |
| { |
| jumpOptimizations(l, LabelKind.LABEL_NEXT); |
| |
| int idx = getLastInstructionIndex(); |
| instructions.get(idx).addLabelNext(l); |
| lastLabelSeen = idx + 1; |
| } |
| |
| /** |
| * Fetch a previous instruction from the peephole window |
| * |
| * @param i the index of the previous instruction. 0 is the last instruction |
| * processed (so the immediately preceeding instruction), and |
| * PEEPHOLE_WINDOW_SIZE-1 is the oldest instruction that can be fetched. |
| * @return the InstructionInfo for some previous instruction, or the |
| * noInstruction object if i falls outside of the peephole window |
| */ |
| private InstructionInfo previous(int i) |
| { |
| int size = instructions.size(); |
| int idx = i + 1; |
| if (size >= idx) |
| { |
| int realIdx = size - idx; |
| |
| // Can't look back past a label |
| if (indexBeforeLabel(realIdx)) |
| return noInstruction; |
| |
| return instructions.get(realIdx); |
| } |
| return noInstruction; |
| } |
| |
| /** |
| * Helper method to determine if the index passed in occurrs before the last |
| * label instruction in the window |
| */ |
| private boolean indexBeforeLabel(int idx) |
| { |
| return lastLabelSeen != NO_LABEL |
| // if we did a label next we can still look back until that |
| // next instruction comes in |
| && lastLabelSeen < PEEPHOLE_WINDOW_SIZE |
| && idx < lastLabelSeen; |
| } |
| |
| /** |
| * Replace the previous instruction(s), from the end of the peephole window |
| * up to i with a new Instruction. It will replace all the previous |
| * instructions through i. Must not be called with an index that falls |
| * outside of the peephole window. |
| * |
| * @param i the index of the previous instruction to replace. Follows the |
| * same indexing strategy as the previous() method 0 is the last instruction |
| * in the window, 1 is two instructions ago, up to PEEPHOLE_WINDOW_SIZE-1 |
| * which is the oldest instruction that can be replaced. |
| * @param insn The new instruction to insert at i |
| */ |
| private void replace(int i, Instruction insn) |
| { |
| Instruction[] newInsns = {insn}; |
| replace(i, newInsns); |
| } |
| |
| /** |
| * Replace the previous instruction(s), from the end of the peephole window |
| * up to i with a new Instruction sequence. It will replace all the previous |
| * instructions through i. Must not be called with an index that falls |
| * outside of the peephole window. |
| * |
| * @param i the index of the previous instruction to replace. Follows the |
| * same indexing strategy as the previous() method 0 is the last instruction |
| * in the window, 1 is two instructions ago, up to PEEPHOLE_WINDOW_SIZE-1 |
| * which is the oldest instruction that can be replaced. |
| * @param insns The new instruction sequence to insert at i |
| */ |
| private void replace(int i, Instruction[] insns) |
| { |
| if (insns.length == 0) |
| return; |
| |
| int size = instructions.size(); |
| int idx = i + 1; |
| if (size >= idx) |
| { |
| int realIdx = size - idx; |
| |
| assert !indexBeforeLabel(realIdx) : "Attempting to replace instruction sequence that spans a label"; |
| |
| // replace the instruction |
| InstructionInfo info = instructions.get(realIdx); |
| info.setInstruction(insns[0]); |
| |
| // Delete any remaining instructions, saving the list of labelNexts from the last deleted |
| // instruction |
| List<Label> labelNexts = null; |
| for (int r = size - 1; r >= (realIdx + 1); --r) |
| { |
| InstructionInfo temp = instructions.remove(r); |
| if (r == size - 1) |
| { |
| // Save the labelNexts from the last deleted instruction |
| labelNexts = temp.getLabelNexts(); |
| } |
| } |
| |
| // Add the remaining instructions |
| for (int r = 1, l = insns.length; r < l; ++r) |
| addInstruction(insns[r]); |
| |
| if (labelNexts != null) |
| { |
| // Apply any saved labelNexts to the last instruction added |
| for (Label l : labelNexts) |
| labelNext(l); |
| } |
| } |
| else |
| { |
| assert false : "Trying to replace instructions that fall outside the peephole window"; |
| } |
| } |
| |
| /** |
| * Delete the previous instruction(s), from the end of the peephole window |
| * up to i. Must not be called with an index that falls outside of the |
| * peephole window. |
| * |
| * @param i the index of the previous instruction to replace. Follows the |
| * same indexing strategy as the previous() method 0 is the last instruction |
| * in the window, 1 is two instructions ago, up to PEEPHOLE_WINDOW_SIZE-1 |
| * which is the oldest instruction that can be replaced. |
| */ |
| private void delete(int i) |
| { |
| int size = instructions.size(); |
| int idx = i + 1; |
| if (size >= idx) |
| { |
| int realIdx = size - idx; |
| |
| assert !indexBeforeLabel(realIdx) : "Attempting to delete instruction sequence that spans a label"; |
| |
| List<Label> labels = null; |
| |
| for (int r = size - 1; r >= realIdx; --r) |
| { |
| InstructionInfo temp = instructions.remove(r); |
| |
| if (r == size - 1) |
| { |
| // Save any label nexts from the last deleted instruction |
| List<Label> labelNexts = temp.getLabelNexts(); |
| if (!labelNexts.isEmpty()) |
| { |
| if (labels == null) |
| labels = new ArrayList<Label>(); |
| labels.addAll(labelNexts); |
| } |
| } |
| else if (r == realIdx) |
| { |
| // save any label currents from the first deleted instruction |
| List<Label> labelCurrents = temp.getLabelCurrents(); |
| if (!labelCurrents.isEmpty()) |
| { |
| if (labels == null) |
| labels = new ArrayList<Label>(); |
| labels.addAll(labelCurrents); |
| } |
| } |
| } |
| if (labels != null) |
| { |
| if (labelsFromDeletedInsns == null) |
| labelsFromDeletedInsns = labels; |
| else |
| labelsFromDeletedInsns.addAll(labels); |
| } |
| } |
| else |
| { |
| assert false : "Trying to delete instructions that fall outside the peephole window"; |
| } |
| } |
| |
| /** |
| * InstructionInfo to represent noInstruction - used when we look back past |
| * the size of the peephole window |
| */ |
| private static InstructionInfo noInstruction = new InstructionInfo(); |
| |
| /** |
| * Optimizations for OP_convert_b: equals, convert_b -> equals strictequals, |
| * convert_b -> strictequals not, convert_b -> not greaterthan, convert_b -> |
| * greaterthan lessthan, convert_b -> lessthan greaterequals, convert_b -> |
| * greaterequals lessequals, convert_b -> lessequals istype, convert_b -> |
| * istype istypelate, convert_b -> istypelate instanceof, convert_b -> |
| * instanceof deleteproperty, convert_d -> deleteproperty in, convert_d -> |
| * in convert_b, convert_b -> convert_b pushtrue, convert_b -> pushtrue |
| * pushfalse, convert_b -> pushfalse |
| * |
| * @param i The convert_b instruction |
| */ |
| private void op_convert_b(InstructionInfo i) |
| { |
| InstructionInfo prev = previous(1); |
| switch (prev.getOpcode()) |
| { |
| case OP_equals: |
| case OP_strictequals: |
| case OP_not: |
| case OP_greaterthan: |
| case OP_lessthan: |
| case OP_greaterequals: |
| case OP_lessequals: |
| case OP_istype: |
| case OP_istypelate: |
| case OP_instanceof: |
| case OP_deleteproperty: |
| case OP_in: |
| case OP_convert_b: |
| case OP_pushtrue: |
| case OP_pushfalse: |
| { |
| // result is already a boolean, just erase the op_convert_b |
| delete(0); |
| break; |
| } |
| default: |
| { |
| // nothing to do - instruction has already been added |
| } |
| } |
| } |
| |
| /** |
| * Optimizations for OP_convert_d: convert_d, convert_d -> convert_d |
| * pushbyte n, convert_d -> pushdouble n pushint n, convert_d -> pushdouble |
| * n pushuint n, convert_d -> pushdouble n pushdouble n, convert_d -> |
| * pushdouble n pushnan, convert_d -> pushnan lf32, convert_d -> lf32 lf64, |
| * convert_d -> lf64 |
| * |
| * @param i The convert_d instruction |
| */ |
| private void op_convert_d(InstructionInfo i) |
| { |
| InstructionInfo prev = previous(1); |
| switch (prev.getOpcode()) |
| { |
| case OP_pushbyte: |
| { |
| // replace pushbyte, convert d with pushdouble - should be faster |
| replace(1, InstructionFactory.getInstruction(OP_pushdouble, new Double(convertByteImmediateToDouble(prev.getImmediate())))); |
| break; |
| } |
| case OP_pushint: |
| case OP_pushuint: |
| { |
| // replace pushint , convert d with pushdouble - should be faster |
| replace(1, InstructionFactory.getInstruction(OP_pushdouble, new Double(((Number)prev.getOperand(0)).doubleValue()))); |
| break; |
| } |
| case OP_pushdouble: |
| case OP_pushnan: |
| case OP_lf32: |
| case OP_lf64: |
| case OP_convert_d: |
| { |
| // result is already a double, just erase the op_convert_d |
| delete(0); |
| break; |
| } |
| default: |
| { |
| // nothing to do - instruction has already been added |
| } |
| } |
| } |
| |
| /** |
| * Optimizations for OP_convert_i: convert_i, convert_i -> convert_i |
| * coerce_i, convert_i -> coerce_i OP_bitand, convert_i -> OP_bitand |
| * OP_bitor, convert_i -> OP_bitor OP_bitxor, convert_i -> OP_bitxor lshift, |
| * convert_i -> lshift rshift, convert_i -> rshift add_i, convert_i -> add_i |
| * subtract_i, convert_i -> subtract_i increment_i, convert_i -> increment_i |
| * decrement_i, convert_i -> decrement_i multiply_i, convert_i -> multiply_i |
| * pushbyte, convert_i -> pushbyte pushshort, convert_i -> pushshort |
| * pushint, convert_i -> pushint li8, convert_i -> li8 li16, convert_i -> |
| * li16 li32, convert_i -> li32 sxi1, convert_i -> sxi1 sxi8, convert_i -> |
| * sxi8 sxi16, convert_i -> sxi16 |
| * |
| * @param i The convert_b instruction |
| */ |
| private void op_convert_i(InstructionInfo i) |
| { |
| InstructionInfo prev = previous(1); |
| switch (prev.getOpcode()) |
| { |
| case OP_convert_i: |
| case OP_coerce_i: |
| case OP_bitand: |
| case OP_bitor: |
| case OP_bitxor: |
| case OP_lshift: |
| case OP_rshift: |
| case OP_add_i: |
| case OP_subtract_i: |
| case OP_increment_i: |
| case OP_decrement_i: |
| case OP_multiply_i: |
| case OP_pushbyte: |
| case OP_pushshort: |
| case OP_pushint: |
| case OP_li8: |
| case OP_li16: |
| case OP_li32: |
| case OP_sxi1: |
| case OP_sxi8: |
| case OP_sxi16: |
| { |
| // result is already an int, just erase the op_convert_i |
| delete(0); |
| break; |
| } |
| default: |
| { |
| // nothing to do - instruction has already been added |
| } |
| } |
| } |
| |
| /** |
| * Optimizations for OP_convert_s: coerce_s, convert_s -> coerce_s |
| * convert_s, convert_s -> convert_s pushstring, convert_s -> pushstring |
| * typeof, convert_s -> typeof |
| * |
| * @param i The convert_b instruction |
| */ |
| private void op_convert_s(InstructionInfo i) |
| { |
| InstructionInfo prev = previous(1); |
| switch (prev.getOpcode()) |
| { |
| case OP_coerce_s: |
| case OP_convert_s: |
| case OP_pushstring: |
| case OP_typeof: |
| { |
| // result is already a String, just erase the op_convert_s |
| delete(0); |
| break; |
| } |
| default: |
| { |
| // nothing to do - instruction has already been added |
| } |
| } |
| } |
| |
| /** |
| * Optimizations for OP_convert_i: convert_u, convert_u -> convert_u |
| * pushuint, convert_u -> pushuint |
| * |
| * @param i The convert_b instruction |
| */ |
| private void op_convert_u(InstructionInfo i) |
| { |
| InstructionInfo prev = previous(1); |
| switch (prev.getOpcode()) |
| { |
| case OP_convert_u: |
| case OP_pushuint: |
| { |
| // result is already a uint, just erase the op_convert_u |
| delete(0); |
| break; |
| } |
| default: |
| { |
| // nothing to do - instruction has already been added |
| } |
| } |
| } |
| |
| /** |
| * Optimizations for OP_getproperty: findpropstrict name, getproperty name |
| * -> getlex name |
| * |
| * @param i the getproperty instruction |
| */ |
| private void op_getproperty(InstructionInfo i) |
| { |
| InstructionInfo prev = previous(1); |
| switch (prev.getOpcode()) |
| { |
| case OP_findpropstrict: |
| { |
| // can optimize findpropstrict, followed by getprop of the same name |
| // if there are any instructions btwn the findpropstrict and getprop (such as to compute a runtime |
| // multiname), we won't match this pattern |
| if (i.getOperand(0).equals(prev.getOperand(0))) |
| replace(1, InstructionFactory.createModifiedInstruction(OP_getlex, prev.getInstruction())); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Optimizations for OP_iffalse: convert_b, iffalse -> iffalse equals, |
| * iffalse -> ifne strictequals, iffalse -> strictne lessthen, iffalse -> |
| * ifnlt lessequals, iffalse -> ifnle greaterthan, iffalse -> ifngt |
| * greaterequals, iffalse -> ifnge pushfalse, iffalse -> jump pushtrue, |
| * iffalse -> nothing strictequals, not, iffalse -> ifstrictequals equals, |
| * not, iffalse -> ifeq lessthan, not, iffalse -> iflt lessequals, not, |
| * iffalse -> ifle greaterthan, not, iffalse -> ifgt greaterequals, |
| * not,iffalse -> ifge not, iffalse -> iftrue |
| * |
| * @param i the iffalse instruction |
| */ |
| private void op_iffalse(InstructionInfo i) |
| { |
| InstructionInfo prev = previous(1); |
| |
| // Check if we know what's on the stack. If so, we don't need |
| // to do a conditional branch |
| switch(isTrueInstructionInfo(prev)) |
| { |
| case TRUE: |
| delete(1); // if we can't branch, just eat the push / iffalse instructions |
| return; |
| case FALSE: |
| replace(1, InstructionFactory.createModifiedInstruction(OP_jump, i.getInstruction())); |
| // If we always branch, then replace the push / iffalse with an uncontidional jump |
| return; |
| case DONT_KNOW: |
| break; // continue on if we don't know it's a constant boolean |
| default: |
| assert false; |
| |
| } |
| switch (prev.getOpcode()) |
| { |
| case OP_convert_b: |
| { |
| // replace with just iffalse |
| replace(1, i.getInstruction()); |
| break; |
| } |
| case OP_equals: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_ifne, i.getInstruction())); |
| break; |
| } |
| case OP_strictequals: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_ifstrictne, i.getInstruction())); |
| break; |
| } |
| case OP_lessthan: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_ifnlt, i.getInstruction())); |
| break; |
| } |
| case OP_lessequals: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_ifnle, i.getInstruction())); |
| break; |
| } |
| case OP_greaterthan: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_ifngt, i.getInstruction())); |
| break; |
| } |
| case OP_greaterequals: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_ifnge, i.getInstruction())); |
| break; |
| } |
| |
| case OP_not: |
| { |
| InstructionInfo prev2 = previous(2); |
| switch (prev2.getOpcode()) |
| { |
| case OP_strictequals: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_ifstricteq, i.getInstruction())); |
| break; |
| } |
| case OP_equals: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_ifeq, i.getInstruction())); |
| break; |
| } |
| case OP_lessthan: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_iflt, i.getInstruction())); |
| break; |
| } |
| case OP_lessequals: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_ifle, i.getInstruction())); |
| break; |
| } |
| case OP_greaterthan: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_ifgt, i.getInstruction())); |
| break; |
| } |
| case OP_greaterequals: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_ifge, i.getInstruction())); |
| break; |
| } |
| default: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_iftrue, i.getInstruction())); |
| break; |
| } |
| } |
| break; |
| } |
| |
| default: |
| { |
| // nothing to do, instruction was already added |
| } |
| } |
| } |
| |
| |
| /** |
| * Optimizations for OP_iftrue: convert_b, iftrue -> iftrue equals, iftrue |
| * -> ifeq strictequals, iftrue -> ifstricteq lessthen, iftrue -> iflt |
| * lessequals, iftrue -> ifle greaterthan, iftrue -> ifgt greaterequals, |
| * iftrue -> ifge pushfalse, iftrue -> jump pushtrue, iftrue -> nothing |
| * strictequals, not, iftrue -> ifstrictne equals, not, iftrue -> ifne |
| * lessthan, not, iftrue -> ifnlt lessequals, not, iftrue -> ifnle |
| * greaterthan, not, iftrue -> ifngt greaterequals, not,iftrue -> ifnge not, |
| * iftrue -> iffalse |
| * |
| * @param i the iftrue instruction |
| */ |
| private void op_iftrue(InstructionInfo i) |
| { |
| InstructionInfo prev = previous(1); |
| |
| |
| switch(isTrueInstructionInfo(prev)) |
| { |
| case FALSE: |
| delete(1); |
| return; |
| case TRUE: |
| replace(1, InstructionFactory.createModifiedInstruction(OP_jump, i.getInstruction())); |
| return; |
| case DONT_KNOW: |
| break; // continue on if we don't know it's a constant boolean |
| default: |
| assert false; |
| |
| } |
| switch (prev.getOpcode()) |
| { |
| case OP_convert_b: |
| { |
| replace(1, i.getInstruction()); |
| break; |
| } |
| case OP_equals: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_ifeq, i.getInstruction())); |
| break; |
| } |
| case OP_strictequals: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_ifstricteq, i.getInstruction())); |
| break; |
| } |
| case OP_lessthan: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_iflt, i.getInstruction())); |
| break; |
| } |
| case OP_lessequals: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_ifle, i.getInstruction())); |
| break; |
| } |
| case OP_greaterthan: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_ifgt, i.getInstruction())); |
| break; |
| } |
| case OP_greaterequals: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_ifge, i.getInstruction())); |
| break; |
| } |
| |
| case OP_not: |
| { |
| InstructionInfo prev2 = previous(2); |
| switch (prev2.getOpcode()) |
| { |
| case OP_strictequals: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_ifstrictne, i.getInstruction())); |
| break; |
| } |
| case OP_equals: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_ifne, i.getInstruction())); |
| break; |
| } |
| case OP_lessthan: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_ifnlt, i.getInstruction())); |
| break; |
| } |
| case OP_lessequals: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_ifnle, i.getInstruction())); |
| break; |
| } |
| case OP_greaterthan: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_ifngt, i.getInstruction())); |
| break; |
| } |
| case OP_greaterequals: |
| { |
| replace(2, InstructionFactory.createModifiedInstruction(OP_ifnge, i.getInstruction())); |
| break; |
| } |
| default: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_iffalse, i.getInstruction())); |
| break; |
| } |
| } |
| break; |
| } |
| |
| default: |
| // nothing to do, instruction was already added |
| } |
| } |
| |
| private enum ConstantBoolean { TRUE, FALSE, DONT_KNOW } |
| /** |
| * evaluates an instruction and determines if it will result in true or false on TOS. |
| * |
| * @param i is an instruction to analyze |
| * @return whether TOS is true, false, or not known |
| */ |
| private static ConstantBoolean isTrueInstructionInfo(InstructionInfo i) |
| { |
| ConstantBoolean ret = ConstantBoolean.DONT_KNOW; |
| switch(i.getOpcode()) |
| { |
| case OP_pushtrue: |
| ret = ConstantBoolean.TRUE; |
| break; |
| case OP_pushfalse: |
| ret = ConstantBoolean.FALSE; |
| break; |
| case OP_pushbyte: |
| { |
| int value = i.getImmediate(); |
| assert value >= 0; |
| ret = ECMASupport.toBoolean(value) ? ConstantBoolean.TRUE : ConstantBoolean.FALSE; |
| break; |
| } |
| case OP_pushint: |
| { |
| int value = (Integer)i.getOperand(0); |
| ret = ECMASupport.toBoolean(value) ? ConstantBoolean.TRUE : ConstantBoolean.FALSE; |
| break; |
| } |
| case OP_pushuint: |
| { |
| long value = (Long)i.getOperand(0); |
| ret = ECMASupport.toBoolean(value) ? ConstantBoolean.TRUE : ConstantBoolean.FALSE; |
| break; |
| } |
| case OP_pushstring: |
| { |
| String value = i.getOperand(0).toString(); |
| ret = ECMASupport.toBoolean(value) ? ConstantBoolean.TRUE : ConstantBoolean.FALSE; |
| break; |
| } |
| case OP_pushnull: |
| ret = ConstantBoolean.FALSE; |
| break; |
| } |
| return ret; |
| } |
| |
| |
| /** |
| * Optimizations for OP_pop: callprop, pop -> callpropvoid callsuper, pop -> |
| * callsupervoid |
| */ |
| private void op_pop(InstructionInfo i) |
| { |
| InstructionInfo prev = previous(1); |
| switch (prev.getOpcode()) |
| { |
| case OP_callproperty: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_callpropvoid, prev.getInstruction())); |
| break; |
| } |
| case OP_callsuper: |
| { |
| replace(1, InstructionFactory.createModifiedInstruction(OP_callsupervoid, prev.getInstruction())); |
| break; |
| } |
| default: |
| { |
| // nothing to do, instruction has already been added |
| } |
| } |
| } |
| |
| /** |
| * Optimizations for OP_returnvoid: returnvoid, returnvoid -> returnvoid |
| * getlocal0, pushscope, returnvoid -> returnvoid (insn without side |
| * effect), returnvoid -> returnvoid |
| */ |
| private void op_returnvoid(InstructionInfo i) |
| { |
| InstructionInfo prev = previous(1); |
| switch (prev.getOpcode()) |
| { |
| case OP_returnvoid: |
| { |
| delete(0); |
| break; |
| } |
| // Any instruction without side effects that |
| // the CG is likely to generate can go here. |
| case OP_pop: |
| { |
| replace(1, i.getInstruction()); |
| break; |
| } |
| case OP_pushscope: |
| { |
| if (previous(2).getOpcode() == OP_getlocal0) |
| // Trivial function. |
| replace(2, i.getInstruction()); |
| break; |
| } |
| default: |
| { |
| // nothing to do, instruction has already been added |
| } |
| } |
| } |
| |
| /** |
| * Optimizations for OP_getlocal: setlocal N, getlocal N -> dup, setlocal N |
| */ |
| private void OP_getlocal(InstructionInfo i) |
| { |
| InstructionInfo prev = previous(1); |
| InstructionInfo cur = previous(0); |
| |
| switch (prev.getOpcode()) |
| { |
| case OP_setlocal: |
| { |
| if (cur.getInstruction().getImmediate() != prev.getInstruction().getImmediate()) |
| break; // set,get of different locals |
| |
| Instruction[] newInsns = {InstructionFactory.getInstruction(OP_dup), prev.getInstruction()}; |
| replace(1, newInsns); |
| break; |
| } |
| default: |
| { |
| // nothing to do, instruction has already been added |
| } |
| } |
| } |
| |
| /** |
| * convert the immediate of a pushbyte to a double value. |
| */ |
| double convertByteImmediateToDouble(int i) |
| { |
| byte b = (byte)i; |
| return (double)b; |
| } |
| |
| /** |
| * Add an instruction to the peephole window. If the window is already full |
| * then the first instruction will be removed, and passed on to the next |
| * delegate to make room for the new instruction |
| * |
| * @param insn the Instruction to add |
| */ |
| private void addInstruction(Instruction insn) |
| { |
| int size = instructions.size(); |
| InstructionInfo info = null; |
| if (size == PEEPHOLE_WINDOW_SIZE) |
| { |
| // reuse the InstructionInfo if we can |
| info = instructions.remove(0); |
| finishInstruction(info); |
| |
| if (lastLabelSeen != NO_LABEL) |
| --lastLabelSeen; |
| } |
| else |
| { |
| info = new InstructionInfo(); |
| } |
| info.reset(insn); |
| instructions.add(info); |
| |
| if (labelsFromDeletedInsns != null) |
| { |
| // If we saved any labels from a deleted instruction sequence, |
| // apply them here. |
| // They will always be label currents no matter what the started out |
| // as - if we deleted something with a labelNext, this is still the next instruction |
| // and if we deleted something with a labelCurrent then this instruction is the new current instruction |
| for (Label l : labelsFromDeletedInsns) |
| labelCurrent(l); |
| labelsFromDeletedInsns = null; |
| } |
| } |
| |
| /** |
| * Called when the peephole optimizer is finished with an instruction. This |
| * method will pass the instruction through to the next IMethodBodyVisitor, |
| * and call any needed labeling methods. |
| * |
| * @param info The InstructionInfo that the optimizer is done with |
| */ |
| private void finishInstruction(InstructionInfo info) |
| { |
| finisher.visitInstruction(info.getInstruction()); |
| |
| for (Label l : info.getLabelCurrents()) |
| { |
| finisher.labelCurrent(l); |
| } |
| |
| for (Label l : info.getLabelNexts()) |
| { |
| finisher.labelNext(l); |
| } |
| } |
| |
| /** |
| * Helper class to hold info about an Instruction. In addition to the |
| * Instruction, it also keeps track of any labelNext, or labelCurrent |
| * instructions that must be applied to the Instruction once it is passed on |
| * to the next IMethodBodyVisitor |
| */ |
| private static class InstructionInfo |
| { |
| InstructionInfo() |
| { |
| } |
| |
| /** |
| * Hold the instruction |
| */ |
| private Instruction insn; |
| |
| /** |
| * label(s) to use for a labelNext operation once we are done with the |
| * instruction |
| */ |
| private List<Label> labelNexts = new ArrayList<Label>(); |
| |
| /** |
| * label(s) to use for a labelCurrent operation once we are done with |
| * the instruction |
| */ |
| private List<Label> labelCurrents = new ArrayList<Label>(); |
| |
| /** |
| * Reset the Instruction info to its default state with a new |
| * Instruction. This method will clear any labelNext/labelCurrent data. |
| * This is so we can reuse the InstructionInfo instances. |
| */ |
| void reset(Instruction i) |
| { |
| this.insn = i; |
| labelNexts.clear(); |
| labelCurrents.clear(); |
| } |
| |
| /** |
| * Change the Instruction this InstructionInfo points at. This is useful |
| * for rewriting instructions - instead of deleting and adding a new |
| * InstructionInfo, we can just rest the Instruction. This will not |
| * clear any labelNext/labelCurrent data - If we are replacing an |
| * Instruction then the label methods should still happen on the new |
| * instruction that replaced the old one. |
| */ |
| void setInstruction(Instruction i) |
| { |
| this.insn = i; |
| } |
| |
| /** |
| * @return the Instruction this instance holds |
| */ |
| public Instruction getInstruction() |
| { |
| return insn; |
| } |
| |
| /** |
| * Add a Label to be used as an argument for labelCurrent when this |
| * instruction is passed on to the next visitor. |
| * |
| * @param l the label |
| */ |
| public void addLabelCurrent(Label l) |
| { |
| this.labelCurrents.add(l); |
| } |
| |
| /** |
| * Add a Label to be used as an argument for labelNext when this |
| * instruction is passed on to the next visitor. |
| * |
| * @param l the label |
| */ |
| public void addLabelNext(Label l) |
| { |
| this.labelNexts.add(l); |
| } |
| |
| /** |
| * @return A List of Labels to be used for labelCurrent, returns the |
| * empty list if there are none |
| */ |
| public List<Label> getLabelCurrents() |
| { |
| return labelCurrents; |
| } |
| |
| /** |
| * @return A List of Labels to be used for labelNext, returns the empty |
| * list if there are none |
| */ |
| public List<Label> getLabelNexts() |
| { |
| return labelNexts; |
| } |
| |
| /** |
| * @return the opcode of the Instruction, or -1 if there is no |
| * instruction |
| */ |
| public int getOpcode() |
| { |
| return insn != null ? insn.getOpcode() : -1; |
| } |
| |
| /** |
| * @return the immediate for the instruction, or -1 if there is no |
| * instruction |
| */ |
| public int getImmediate() |
| { |
| return insn != null ? insn.getImmediate() : -1; |
| } |
| |
| /** |
| * Get the operand at index i from the underlying instruction |
| * |
| * @param i the index of the operand to fetch |
| * @return the operand at index i, or null if there is no instruction |
| */ |
| public Object getOperand(int i) |
| { |
| return insn != null ? insn.getOperand(i) : null; |
| } |
| } |
| |
| /** |
| * Interface so we can do different things when we are done with an |
| * instruction |
| */ |
| static interface InstructionFinisher |
| { |
| void visitInstruction(Instruction i); |
| |
| void labelNext(Label l); |
| |
| void labelCurrent(Label l); |
| } |
| |
| /** |
| * Passes the instruction and any label calls on to the next |
| * IMethodBodyVisitor |
| */ |
| class DelegateFinisher implements InstructionFinisher |
| { |
| public void visitInstruction(Instruction i) |
| { |
| PeepholeOptimizerMethodBodyVisitor.super.visitInstruction(i); |
| } |
| |
| public void labelNext(Label l) |
| { |
| PeepholeOptimizerMethodBodyVisitor.super.labelNext(l); |
| } |
| |
| public void labelCurrent(Label l) |
| { |
| PeepholeOptimizerMethodBodyVisitor.super.labelCurrent(l); |
| } |
| } |
| |
| /** |
| * Puts the instructions into a InstructionList instead of passing them on |
| * to the next IMethodBodyVisitor |
| */ |
| static class InstructionListFinisher implements InstructionFinisher |
| { |
| public InstructionListFinisher(InstructionList list) |
| { |
| this.list = list; |
| } |
| |
| private InstructionList list; |
| |
| public void visitInstruction(Instruction i) |
| { |
| list.addInstruction(i); |
| } |
| |
| public void labelNext(Label l) |
| { |
| list.labelNext(l); |
| } |
| |
| public void labelCurrent(Label l) |
| { |
| list.labelCurrent(l); |
| } |
| } |
| } |