| // Copyright (c) 2017, 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 static com.android.tools.r8.graph.DexCode.FAKE_THIS_PREFIX; |
| import static com.android.tools.r8.graph.DexCode.FAKE_THIS_SUFFIX; |
| |
| import com.android.tools.r8.cf.CfPrinter; |
| import com.android.tools.r8.cf.code.CfFrame; |
| import com.android.tools.r8.cf.code.CfIinc; |
| import com.android.tools.r8.cf.code.CfInstruction; |
| import com.android.tools.r8.cf.code.CfLabel; |
| import com.android.tools.r8.cf.code.CfLoad; |
| import com.android.tools.r8.cf.code.CfPosition; |
| import com.android.tools.r8.cf.code.CfReturnVoid; |
| import com.android.tools.r8.cf.code.CfTryCatch; |
| import com.android.tools.r8.errors.InvalidDebugInfoException; |
| import com.android.tools.r8.errors.Unimplemented; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.Position; |
| import com.android.tools.r8.ir.code.ValueNumberGenerator; |
| import com.android.tools.r8.ir.conversion.CfSourceCode; |
| import com.android.tools.r8.ir.conversion.IRBuilder; |
| import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; |
| import com.android.tools.r8.ir.optimize.InliningConstraints; |
| import com.android.tools.r8.naming.ClassNameMapper; |
| import com.android.tools.r8.naming.NamingLens; |
| import com.android.tools.r8.origin.Origin; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.google.common.base.Strings; |
| import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap; |
| import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; |
| import java.util.BitSet; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map.Entry; |
| import org.objectweb.asm.Label; |
| import org.objectweb.asm.MethodVisitor; |
| |
| public class CfCode extends Code { |
| |
| public static class LocalVariableInfo { |
| |
| private final int index; |
| private final DebugLocalInfo local; |
| private final CfLabel start; |
| private CfLabel end; |
| |
| public LocalVariableInfo(int index, DebugLocalInfo local, CfLabel start) { |
| this.index = index; |
| this.local = local; |
| this.start = start; |
| } |
| |
| public LocalVariableInfo(int index, DebugLocalInfo local, CfLabel start, CfLabel end) { |
| this(index, local, start); |
| setEnd(end); |
| } |
| |
| public void setEnd(CfLabel end) { |
| assert this.end == null; |
| assert end != null; |
| this.end = end; |
| } |
| |
| public int getIndex() { |
| return index; |
| } |
| |
| public DebugLocalInfo getLocal() { |
| return local; |
| } |
| |
| public CfLabel getStart() { |
| return start; |
| } |
| |
| public CfLabel getEnd() { |
| return end; |
| } |
| |
| @Override |
| public String toString() { |
| return "" + index + " => " + local; |
| } |
| } |
| |
| // The original holder is a reference to the holder type that the method was in from input and |
| // for which the invokes still refer to. The holder is needed to determine if an invoke-special |
| // maps to an invoke-direct or invoke-super. |
| // TODO(b/135969130): Make IR building lense aware and avoid caching the holder type. |
| private final DexType originalHolder; |
| |
| private final int maxStack; |
| private final int maxLocals; |
| public List<CfInstruction> instructions; |
| private final List<CfTryCatch> tryCatchRanges; |
| private final List<LocalVariableInfo> localVariables; |
| |
| public CfCode( |
| DexType originalHolder, |
| int maxStack, |
| int maxLocals, |
| List<CfInstruction> instructions, |
| List<CfTryCatch> tryCatchRanges, |
| List<LocalVariableInfo> localVariables) { |
| this.originalHolder = originalHolder; |
| this.maxStack = maxStack; |
| this.maxLocals = maxLocals; |
| this.instructions = instructions; |
| this.tryCatchRanges = tryCatchRanges; |
| this.localVariables = localVariables; |
| } |
| |
| public DexType getOriginalHolder() { |
| return originalHolder; |
| } |
| |
| public int getMaxStack() { |
| return maxStack; |
| } |
| |
| public int getMaxLocals() { |
| return maxLocals; |
| } |
| |
| public List<CfTryCatch> getTryCatchRanges() { |
| return tryCatchRanges; |
| } |
| |
| public List<CfInstruction> getInstructions() { |
| return Collections.unmodifiableList(instructions); |
| } |
| |
| public List<LocalVariableInfo> getLocalVariables() { |
| return Collections.unmodifiableList(localVariables); |
| } |
| |
| @Override |
| public int estimatedSizeForInlining() { |
| return countNonStackOperations(Integer.MAX_VALUE); |
| } |
| |
| @Override |
| public boolean estimatedSizeForInliningAtMost(int threshold) { |
| return countNonStackOperations(threshold) <= threshold; |
| } |
| |
| private int countNonStackOperations(int threshold) { |
| int result = 0; |
| for (CfInstruction instruction : instructions) { |
| if (instruction.emitsIR()) { |
| result++; |
| if (result > threshold) { |
| break; |
| } |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean isCfCode() { |
| return true; |
| } |
| |
| @Override |
| public CfCode asCfCode() { |
| return this; |
| } |
| |
| private boolean shouldAddParameterNames(DexEncodedMethod method, AppView<?> appView) { |
| // Don't add parameter information if the code already has full debug information. |
| // Note: This fast path can cause a method to loose its parameter info, if the debug info turned |
| // out to be invalid during IR building. |
| if (appView.options().debug) { |
| return false; |
| } |
| assert localVariables.isEmpty(); |
| if (!method.hasParameterInfo()) { |
| return false; |
| } |
| // If tree shaking, only keep annotations on kept methods. |
| if (appView.appInfo().hasLiveness() |
| && !appView.appInfo().withLiveness().isPinned(method.method)) { |
| return false; |
| } |
| return true; |
| } |
| |
| public void write( |
| DexEncodedMethod method, |
| MethodVisitor visitor, |
| NamingLens namingLens, |
| AppView<?> appView, |
| int classFileVersion) { |
| InternalOptions options = appView.options(); |
| CfLabel parameterLabel = null; |
| if (shouldAddParameterNames(method, appView)) { |
| parameterLabel = new CfLabel(); |
| parameterLabel.write(visitor, namingLens); |
| } |
| for (CfInstruction instruction : instructions) { |
| if (instruction instanceof CfFrame |
| && (classFileVersion <= 49 |
| || (classFileVersion == 50 && !options.shouldKeepStackMapTable()))) { |
| continue; |
| } |
| instruction.write(visitor, namingLens); |
| } |
| visitor.visitEnd(); |
| visitor.visitMaxs(maxStack, maxLocals); |
| for (CfTryCatch tryCatch : tryCatchRanges) { |
| Label start = tryCatch.start.getLabel(); |
| Label end = tryCatch.end.getLabel(); |
| for (int i = 0; i < tryCatch.guards.size(); i++) { |
| DexType guard = tryCatch.guards.get(i); |
| Label target = tryCatch.targets.get(i).getLabel(); |
| visitor.visitTryCatchBlock( |
| start, |
| end, |
| target, |
| guard == options.itemFactory.throwableType |
| ? null |
| : namingLens.lookupInternalName(guard)); |
| } |
| } |
| if (parameterLabel != null) { |
| assert localVariables.isEmpty(); |
| for (Entry<Integer, DebugLocalInfo> entry : method.getParameterInfo().entrySet()) { |
| writeLocalVariableEntry( |
| visitor, namingLens, entry.getValue(), parameterLabel, parameterLabel, entry.getKey()); |
| } |
| } else { |
| for (LocalVariableInfo local : localVariables) { |
| writeLocalVariableEntry( |
| visitor, namingLens, local.local, local.start, local.end, local.index); |
| } |
| } |
| } |
| |
| private void writeLocalVariableEntry( |
| MethodVisitor visitor, |
| NamingLens namingLens, |
| DebugLocalInfo info, |
| CfLabel start, |
| CfLabel end, |
| int index) { |
| visitor.visitLocalVariable( |
| info.name.toString(), |
| namingLens.lookupDescriptor(info.type).toString(), |
| info.signature == null ? null : info.signature.toString(), |
| start.getLabel(), |
| end.getLabel(), |
| index); |
| } |
| |
| @Override |
| protected int computeHashCode() { |
| throw new Unimplemented(); |
| } |
| |
| @Override |
| protected boolean computeEquals(Object other) { |
| throw new Unimplemented(); |
| } |
| |
| @Override |
| public boolean isEmptyVoidMethod() { |
| for (CfInstruction insn : instructions) { |
| if (!(insn instanceof CfReturnVoid) |
| && !(insn instanceof CfLabel) |
| && !(insn instanceof CfPosition)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public IRCode buildIR(DexEncodedMethod encodedMethod, AppView<?> appView, Origin origin) { |
| return internalBuildPossiblyWithLocals( |
| encodedMethod, encodedMethod, appView, null, null, origin); |
| } |
| |
| @Override |
| public IRCode buildInliningIR( |
| DexEncodedMethod context, |
| DexEncodedMethod encodedMethod, |
| AppView<?> appView, |
| ValueNumberGenerator valueNumberGenerator, |
| Position callerPosition, |
| Origin origin) { |
| assert valueNumberGenerator != null; |
| assert callerPosition != null; |
| return internalBuildPossiblyWithLocals( |
| context, encodedMethod, appView, valueNumberGenerator, callerPosition, origin); |
| } |
| |
| // First build entry. Will either strip locals or build with locals. |
| private IRCode internalBuildPossiblyWithLocals( |
| DexEncodedMethod context, |
| DexEncodedMethod encodedMethod, |
| AppView<?> appView, |
| ValueNumberGenerator generator, |
| Position callerPosition, |
| Origin origin) { |
| if (!encodedMethod.keepLocals(appView.options())) { |
| return internalBuild( |
| Collections.emptyList(), |
| context, |
| encodedMethod, |
| appView, |
| generator, |
| callerPosition, |
| origin); |
| } else { |
| return internalBuildWithLocals( |
| context, encodedMethod, appView, generator, callerPosition, origin); |
| } |
| } |
| |
| // When building with locals, on invalid debug info, retry build without locals info. |
| private IRCode internalBuildWithLocals( |
| DexEncodedMethod context, |
| DexEncodedMethod encodedMethod, |
| AppView<?> appView, |
| ValueNumberGenerator generator, |
| Position callerPosition, |
| Origin origin) { |
| try { |
| return internalBuild( |
| Collections.unmodifiableList(localVariables), |
| context, |
| encodedMethod, |
| appView, |
| generator, |
| callerPosition, |
| origin); |
| } catch (InvalidDebugInfoException e) { |
| appView.options().warningInvalidDebugInfo(encodedMethod, origin, e); |
| return internalBuild( |
| Collections.emptyList(), |
| context, |
| encodedMethod, |
| appView, |
| generator, |
| callerPosition, |
| origin); |
| } |
| } |
| |
| // Inner-most subroutine for building. Must only be called by the two internalBuildXYZ above. |
| private IRCode internalBuild( |
| List<CfCode.LocalVariableInfo> localVariables, |
| DexEncodedMethod context, |
| DexEncodedMethod encodedMethod, |
| AppView<?> appView, |
| ValueNumberGenerator generator, |
| Position callerPosition, |
| Origin origin) { |
| CfSourceCode source = |
| new CfSourceCode( |
| this, |
| localVariables, |
| encodedMethod, |
| appView.graphLense().getOriginalMethodSignature(encodedMethod.method), |
| callerPosition, |
| origin, |
| appView); |
| return new IRBuilder(encodedMethod, appView, source, origin, generator).build(context); |
| } |
| |
| @Override |
| public void registerCodeReferences(DexEncodedMethod method, UseRegistry registry) { |
| for (CfInstruction instruction : instructions) { |
| instruction.registerUse(registry, method.method.holder); |
| } |
| for (CfTryCatch tryCatch : tryCatchRanges) { |
| for (DexType guard : tryCatch.guards) { |
| registry.registerTypeReference(guard); |
| } |
| } |
| } |
| |
| @Override |
| public Int2ReferenceMap<DebugLocalInfo> collectParameterInfo( |
| DexEncodedMethod encodedMethod, AppView<?> appView) { |
| CfLabel firstLabel = null; |
| for (CfInstruction instruction : instructions) { |
| if (instruction instanceof CfLabel) { |
| firstLabel = (CfLabel) instruction; |
| break; |
| } |
| } |
| if (firstLabel == null) { |
| return DexEncodedMethod.NO_PARAMETER_INFO; |
| } |
| if (!appView.options().hasProguardConfiguration() |
| || !appView.options().getProguardConfiguration().isKeepParameterNames()) { |
| return DexEncodedMethod.NO_PARAMETER_INFO; |
| } |
| // The enqueuer might build IR to trace reflective behaviour. At that point liveness is not |
| // known, so be conservative with collection parameter name information. |
| if (appView.appInfo().hasLiveness() |
| && !appView.appInfo().withLiveness().isPinned(encodedMethod.method)) { |
| return DexEncodedMethod.NO_PARAMETER_INFO; |
| } |
| |
| // Collect the local slots used for parameters. |
| BitSet localSlotsForParameters = new BitSet(0); |
| int nextLocalSlotsForParameters = 0; |
| if (!encodedMethod.isStatic()) { |
| localSlotsForParameters.set(nextLocalSlotsForParameters++); |
| } |
| for (DexType type : encodedMethod.method.proto.parameters.values) { |
| localSlotsForParameters.set(nextLocalSlotsForParameters); |
| nextLocalSlotsForParameters += type.isLongType() || type.isDoubleType() ? 2 : 1; |
| } |
| // Collect the first piece of local variable information for each argument local slot, |
| // assuming that that does actually describe the parameter (name, type and possibly |
| // signature). |
| Int2ReferenceMap<DebugLocalInfo> parameterInfo = |
| new Int2ReferenceArrayMap<>(localSlotsForParameters.cardinality()); |
| for (LocalVariableInfo node : localVariables) { |
| if (node.start == firstLabel |
| && localSlotsForParameters.get(node.index) |
| && !parameterInfo.containsKey(node.index)) { |
| parameterInfo.put( |
| node.index, new DebugLocalInfo(node.local.name, node.local.type, node.local.signature)); |
| } |
| } |
| return parameterInfo; |
| } |
| |
| @Override |
| public void registerArgumentReferences(DexEncodedMethod method, ArgumentUse registry) { |
| DexProto proto = method.method.proto; |
| boolean isStatic = method.accessFlags.isStatic(); |
| int argumentCount = proto.parameters.values.length + (isStatic ? 0 : 1); |
| Int2IntArrayMap indexToNumber = new Int2IntArrayMap(argumentCount); |
| { |
| int index = 0; |
| int number = 0; |
| if (!isStatic) { |
| indexToNumber.put(index++, number++); |
| } |
| for (DexType value : proto.parameters.values) { |
| indexToNumber.put(index++, number++); |
| if (value.isLongType() || value.isDoubleType()) { |
| index++; |
| } |
| } |
| } |
| assert indexToNumber.size() == argumentCount; |
| for (CfInstruction instruction : instructions) { |
| int index = -1; |
| if (instruction instanceof CfLoad) { |
| index = ((CfLoad) instruction).getLocalIndex(); |
| } else if (instruction instanceof CfIinc) { |
| index = ((CfIinc) instruction).getLocalIndex(); |
| } else { |
| continue; |
| } |
| if (index >= 0) { |
| registry.register(indexToNumber.get(index)); |
| } |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return new CfPrinter(this).toString(); |
| } |
| |
| @Override |
| public String toString(DexEncodedMethod method, ClassNameMapper naming) { |
| return new CfPrinter(this, method, naming).toString(); |
| } |
| |
| public ConstraintWithTarget computeInliningConstraint( |
| DexEncodedMethod encodedMethod, |
| AppView<AppInfoWithLiveness> appView, |
| GraphLense graphLense, |
| DexType invocationContext) { |
| |
| InliningConstraints inliningConstraints = new InliningConstraints(appView, graphLense); |
| if (appView.options().isInterfaceMethodDesugaringEnabled()) { |
| // TODO(b/120130831): Conservatively need to say "no" at this point if there are invocations |
| // to static interface methods. This should be fixed by making sure that the desugared |
| // versions of default and static interface methods are present in the application during |
| // IR processing. |
| inliningConstraints.disallowStaticInterfaceMethodCalls(); |
| } |
| |
| // Model a synchronized method as having a monitor instruction. |
| ConstraintWithTarget constraint = |
| encodedMethod.accessFlags.isSynchronized() |
| ? inliningConstraints.forMonitor() |
| : ConstraintWithTarget.ALWAYS; |
| |
| if (constraint == ConstraintWithTarget.NEVER) { |
| return constraint; |
| } |
| for (CfInstruction insn : instructions) { |
| constraint = |
| ConstraintWithTarget.meet( |
| constraint, |
| insn.inliningConstraint(inliningConstraints, invocationContext, graphLense, appView), |
| appView); |
| if (constraint == ConstraintWithTarget.NEVER) { |
| return constraint; |
| } |
| } |
| if (!tryCatchRanges.isEmpty()) { |
| // Model a try-catch as a move-exception instruction. |
| constraint = |
| ConstraintWithTarget.meet(constraint, inliningConstraints.forMoveException(), appView); |
| } |
| return constraint; |
| } |
| |
| void addFakeThisParameter(DexItemFactory factory) { |
| if (localVariables == null || localVariables.isEmpty()) { |
| // We have no debugging info in the code. |
| return; |
| } |
| int largestPrefix = 1; |
| int existingThisIndex = -1; |
| for (int i = 0; i < localVariables.size(); i++) { |
| LocalVariableInfo localVariable = localVariables.get(i); |
| largestPrefix = |
| Math.max(largestPrefix, DexCode.getLargestPrefix(factory, localVariable.local.name)); |
| if (localVariable.local.name.toString().equals("this")) { |
| existingThisIndex = i; |
| } |
| } |
| if (existingThisIndex < 0) { |
| return; |
| } |
| String fakeThisName = Strings.repeat(FAKE_THIS_PREFIX, largestPrefix + 1) + FAKE_THIS_SUFFIX; |
| DebugLocalInfo debugLocalInfo = |
| new DebugLocalInfo(factory.createString(fakeThisName), this.originalHolder, null); |
| LocalVariableInfo thisLocalInfo = localVariables.get(existingThisIndex); |
| this.localVariables.set( |
| existingThisIndex, |
| new LocalVariableInfo( |
| thisLocalInfo.index, debugLocalInfo, thisLocalInfo.start, thisLocalInfo.end)); |
| } |
| } |