| // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| package com.android.tools.r8.graph; |
| |
| import com.android.tools.r8.code.Instruction; |
| import com.android.tools.r8.code.ReturnVoid; |
| import com.android.tools.r8.code.SwitchPayload; |
| import com.android.tools.r8.dex.IndexedItemCollection; |
| import com.android.tools.r8.dex.MixedSectionCollection; |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.ValueNumberGenerator; |
| import com.android.tools.r8.ir.conversion.DexSourceCode; |
| import com.android.tools.r8.ir.conversion.IRBuilder; |
| import com.android.tools.r8.naming.ClassNameMapper; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.StringUtils; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| // DexCode corresponds to code item in dalvik/dex-format.html |
| public class DexCode extends Code { |
| |
| public final int registerSize; |
| public final int incomingRegisterSize; |
| public final int outgoingRegisterSize; |
| public final Try[] tries; |
| public final TryHandler[] handlers; |
| public final Instruction[] instructions; |
| |
| public final DexString highestSortingString; |
| private DexDebugInfo debugInfo; |
| |
| public DexCode( |
| int registerSize, |
| int insSize, |
| int outsSize, |
| Instruction[] instructions, |
| Try[] tries, |
| TryHandler[] handlers, |
| DexDebugInfo debugInfo, |
| DexString highestSortingString) { |
| this.incomingRegisterSize = insSize; |
| this.registerSize = registerSize; |
| this.outgoingRegisterSize = outsSize; |
| this.instructions = instructions; |
| this.tries = tries; |
| this.handlers = handlers; |
| this.debugInfo = debugInfo; |
| this.highestSortingString = highestSortingString; |
| hashCode(); // Cache the hash code eagerly. |
| } |
| |
| @Override |
| public boolean isDexCode() { |
| return true; |
| } |
| |
| @Override |
| public DexCode asDexCode() { |
| return this; |
| } |
| |
| public DexDebugInfo getDebugInfo() { |
| return debugInfo; |
| } |
| |
| public void setDebugInfo(DexDebugInfo debugInfo) { |
| this.debugInfo = debugInfo; |
| } |
| |
| public DexDebugInfo debugInfoWithAdditionalFirstParameter(DexString name) { |
| if (debugInfo == null) { |
| return null; |
| } |
| DexString[] parameters = debugInfo.parameters; |
| DexString[] newParameters = new DexString[parameters.length + 1]; |
| newParameters[0] = name; |
| System.arraycopy(parameters, 0, newParameters, 1, parameters.length); |
| return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events); |
| } |
| |
| public int codeSizeInBytes() { |
| Instruction last = instructions[instructions.length - 1]; |
| return last.getOffset() + last.getSize(); |
| } |
| |
| @Override |
| public int computeHashCode() { |
| return incomingRegisterSize * 2 |
| + registerSize * 3 |
| + outgoingRegisterSize * 5 |
| + Arrays.hashCode(instructions) * 7 |
| + ((debugInfo == null) ? 0 : debugInfo.hashCode()) * 11 |
| + Arrays.hashCode(tries) * 13 |
| + Arrays.hashCode(handlers) * 17; |
| } |
| |
| @Override |
| public boolean computeEquals(Object other) { |
| if (other instanceof DexCode) { |
| DexCode o = (DexCode) other; |
| if (incomingRegisterSize != o.incomingRegisterSize) { |
| return false; |
| } |
| if (registerSize != o.registerSize) { |
| return false; |
| } |
| if (outgoingRegisterSize != o.outgoingRegisterSize) { |
| return false; |
| } |
| if (debugInfo == null) { |
| if (o.debugInfo != null) { |
| return false; |
| } |
| } else { |
| if (!debugInfo.equals(o.debugInfo)) { |
| return false; |
| } |
| } |
| if (!Arrays.equals(tries, o.tries)) { |
| return false; |
| } |
| if (!Arrays.equals(handlers, o.handlers)) { |
| return false; |
| } |
| // Save the most expensive operation to last. |
| return Arrays.equals(instructions, o.instructions); |
| } |
| return false; |
| } |
| |
| boolean isEmptyVoidMethod() { |
| return instructions.length == 1 && instructions[0] instanceof ReturnVoid; |
| } |
| |
| @Override |
| public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) { |
| DexSourceCode source = new DexSourceCode(this, encodedMethod); |
| IRBuilder builder = new IRBuilder(encodedMethod, source, options); |
| return builder.build(); |
| } |
| |
| public IRCode buildIR( |
| DexEncodedMethod encodedMethod, |
| ValueNumberGenerator valueNumberGenerator, |
| InternalOptions options) { |
| DexSourceCode source = new DexSourceCode(this, encodedMethod); |
| IRBuilder builder = new IRBuilder(encodedMethod, source, valueNumberGenerator, options); |
| return builder.build(); |
| } |
| |
| @Override |
| public void registerReachableDefinitions(UseRegistry registry) { |
| for (Instruction insn : instructions) { |
| insn.registerUse(registry); |
| } |
| } |
| |
| public String toString() { |
| return toString(null, null); |
| } |
| |
| public String toString(DexEncodedMethod method, ClassNameMapper naming) { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("registers: ").append(registerSize); |
| builder.append(", inputs: ").append(incomingRegisterSize); |
| builder.append(", outputs: ").append(outgoingRegisterSize).append("\n"); |
| builder.append("------------------------------------------------------------\n"); |
| builder.append("inst# offset instruction arguments\n"); |
| builder.append("------------------------------------------------------------\n"); |
| DexDebugEntry debugInfo = null; |
| Iterator<DexDebugEntry> debugInfoIterator = Collections.emptyIterator(); |
| if (getDebugInfo() != null && method != null) { |
| debugInfoIterator = new DexDebugEntryBuilder(method, new DexItemFactory()).build().iterator(); |
| debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null; |
| } |
| int instructionNumber = 0; |
| for (Instruction insn : instructions) { |
| while (debugInfo != null && debugInfo.address == insn.getOffset()) { |
| builder.append(" ").append(debugInfo).append("\n"); |
| debugInfo = debugInfoIterator.hasNext() ? debugInfoIterator.next() : null; |
| } |
| StringUtils.appendLeftPadded(builder, Integer.toString(instructionNumber++), 5); |
| builder.append(": ").append(insn.toString(naming)).append('\n'); |
| } |
| if (debugInfoIterator.hasNext()) { |
| throw new Unreachable("Could not print all debug information."); |
| } |
| if (tries.length > 0) { |
| builder.append("Tries (numbers are offsets)\n"); |
| for (Try atry : tries) { |
| builder.append(" "); |
| builder.append(atry.toString()); |
| builder.append('\n'); |
| } |
| if (handlers != null) { |
| builder.append("Handlers (numbers are offsets)\n"); |
| for (int handlerIndex = 0; handlerIndex < handlers.length; handlerIndex++) { |
| TryHandler handler = handlers[handlerIndex]; |
| builder.append(" ").append(handlerIndex).append(": "); |
| builder.append(handler.toString()); |
| builder.append('\n'); |
| } |
| } |
| } |
| return builder.toString(); |
| } |
| |
| public String toSmaliString(ClassNameMapper naming) { |
| StringBuilder builder = new StringBuilder(); |
| // Find labeled targets. |
| Map<Integer, Instruction> payloadUsers = new HashMap<>(); |
| Set<Integer> labledTargets = new HashSet<>(); |
| // Collect payload users and labeled targets for non-payload instructions. |
| for (Instruction dex : instructions) { |
| int[] targets = dex.getTargets(); |
| if (targets != Instruction.NO_TARGETS && targets != Instruction.EXIT_TARGET) { |
| assert targets.length <= 2; |
| // For if instructions the second target is the fallthrough, for which no label is needed. |
| labledTargets.add(dex.getOffset() + targets[0]); |
| } else if (dex.hasPayload()) { |
| labledTargets.add(dex.getOffset() + dex.getPayloadOffset()); |
| payloadUsers.put(dex.getOffset() + dex.getPayloadOffset(), dex); |
| } |
| } |
| // Collect labeled targets for payload instructions. |
| for (Instruction dex : instructions) { |
| if (dex.isSwitchPayload()) { |
| Instruction payloadUser = payloadUsers.get(dex.getOffset()); |
| if (dex instanceof SwitchPayload) { |
| SwitchPayload payload = (SwitchPayload) dex; |
| for (int target : payload.switchTargetOffsets()) { |
| labledTargets.add(payloadUser.getOffset() + target); |
| } |
| } |
| } |
| } |
| // Generate smali for all instructions. |
| for (Instruction dex : instructions) { |
| if (labledTargets.contains(dex.getOffset())) { |
| builder.append(" :label_"); |
| builder.append(dex.getOffset()); |
| builder.append("\n"); |
| } |
| if (dex.isSwitchPayload()) { |
| Instruction payloadUser = payloadUsers.get(dex.getOffset()); |
| builder.append(dex.toSmaliString(payloadUser)).append('\n'); |
| } else { |
| builder.append(dex.toSmaliString(naming)).append('\n'); |
| } |
| } |
| if (tries.length > 0) { |
| builder.append("Tries (numbers are offsets)\n"); |
| for (Try atry : tries) { |
| builder.append(" "); |
| builder.append(atry.toString()); |
| builder.append('\n'); |
| } |
| if (handlers != null) { |
| builder.append("Handlers (numbers are offsets)\n"); |
| for (TryHandler handler : handlers) { |
| builder.append(handler.toString()); |
| builder.append('\n'); |
| } |
| } |
| } |
| return builder.toString(); |
| } |
| |
| public void collectIndexedItems(IndexedItemCollection indexedItems) { |
| for (Instruction insn : instructions) { |
| insn.collectIndexedItems(indexedItems); |
| } |
| if (debugInfo != null) { |
| debugInfo.collectIndexedItems(indexedItems); |
| } |
| if (handlers != null) { |
| for (TryHandler handler : handlers) { |
| handler.collectIndexedItems(indexedItems); |
| } |
| } |
| } |
| |
| public boolean usesExceptionHandling() { |
| return tries.length != 0; |
| } |
| |
| @Override |
| void collectMixedSectionItems(MixedSectionCollection mixedItems) { |
| if (mixedItems.add(this)) { |
| if (debugInfo != null) { |
| debugInfo.collectMixedSectionItems(mixedItems); |
| } |
| } |
| } |
| |
| public static class Try extends DexItem { |
| |
| public static final int NO_INDEX = -1; |
| |
| private final int handlerOffset; |
| public /* offset */ int startAddress; |
| public /* offset */ int instructionCount; |
| public int handlerIndex; |
| |
| public Try(int startAddress, int instructionCount, int handlerOffset) { |
| this.startAddress = startAddress; |
| this.instructionCount = instructionCount; |
| this.handlerOffset = handlerOffset; |
| this.handlerIndex = NO_INDEX; |
| } |
| |
| public void setHandlerIndex(Hashtable<Integer, Integer> map) { |
| handlerIndex = map.get(handlerOffset); |
| } |
| |
| public int hashCode() { |
| return startAddress * 2 + instructionCount * 3 + handlerIndex * 5; |
| } |
| |
| public boolean equals(Object other) { |
| if (this == other) { |
| return true; |
| } |
| if (other instanceof Try) { |
| Try o = (Try) other; |
| if (startAddress != o.startAddress) { |
| return false; |
| } |
| if (instructionCount != o.instructionCount) { |
| return false; |
| } |
| return handlerIndex == o.handlerIndex; |
| } |
| return false; |
| } |
| |
| public String toString() { |
| return "[" |
| + startAddress |
| + " .. " |
| + (startAddress + instructionCount - 1) |
| + "] -> " |
| + handlerIndex; |
| } |
| |
| @Override |
| void collectIndexedItems(IndexedItemCollection indexedItems) { |
| // Intentionally left empty. |
| } |
| |
| @Override |
| void collectMixedSectionItems(MixedSectionCollection mixedItems) { |
| // Should never be visited. |
| assert false; |
| } |
| |
| } |
| |
| public static class TryHandler extends DexItem { |
| |
| public static final int NO_HANDLER = -1; |
| |
| public final TypeAddrPair[] pairs; |
| public /* offset */ int catchAllAddr; |
| |
| public TryHandler(TypeAddrPair[] pairs, int catchAllAddr) { |
| this.pairs = pairs; |
| this.catchAllAddr = catchAllAddr; |
| } |
| |
| public int hashCode() { |
| return catchAllAddr + Arrays.hashCode(pairs) * 7; |
| } |
| |
| public boolean equals(Object other) { |
| if (this == other) { |
| return true; |
| } |
| if (other instanceof TryHandler) { |
| TryHandler o = (TryHandler) other; |
| if (catchAllAddr != o.catchAllAddr) { |
| return false; |
| } |
| return Arrays.equals(pairs, o.pairs); |
| } |
| return false; |
| } |
| |
| public void collectIndexedItems(IndexedItemCollection indexedItems) { |
| collectAll(indexedItems, pairs); |
| } |
| |
| @Override |
| void collectMixedSectionItems(MixedSectionCollection mixedItems) { |
| // Should never be visited. |
| assert false; |
| } |
| |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("[\n"); |
| for (TypeAddrPair pair : pairs) { |
| builder.append(" "); |
| builder.append(pair.type); |
| builder.append(" -> "); |
| builder.append(pair.addr); |
| builder.append("\n"); |
| } |
| if (catchAllAddr != NO_HANDLER) { |
| builder.append(" default -> "); |
| builder.append(catchAllAddr); |
| builder.append("\n"); |
| } |
| builder.append(" ]"); |
| return builder.toString(); |
| } |
| |
| public static class TypeAddrPair extends DexItem { |
| |
| public final DexType type; |
| public final /* offset */ int addr; |
| |
| public TypeAddrPair(DexType type, int addr) { |
| this.type = type; |
| this.addr = addr; |
| } |
| |
| public void collectIndexedItems(IndexedItemCollection indexedItems) { |
| type.collectIndexedItems(indexedItems); |
| } |
| |
| @Override |
| void collectMixedSectionItems(MixedSectionCollection mixedItems) { |
| // Should never be visited. |
| assert false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return type.hashCode() * 7 + addr; |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (this == other) { |
| return true; |
| } |
| if (other instanceof TypeAddrPair) { |
| TypeAddrPair o = (TypeAddrPair) other; |
| return type.equals(o.type) && addr == o.addr; |
| } |
| return false; |
| } |
| } |
| } |
| } |