1 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file 2 // for details. All rights reserved. Use of this source code is governed by a 3 // BSD-style license that can be found in the LICENSE file. 4 package com.android.tools.r8.ir.optimize; 5 6 import com.android.tools.r8.errors.CompilationError; 7 import com.android.tools.r8.graph.AppInfo; 8 import com.android.tools.r8.graph.DexClass; 9 import com.android.tools.r8.graph.DexEncodedField; 10 import com.android.tools.r8.graph.DexEncodedMethod; 11 import com.android.tools.r8.graph.DexField; 12 import com.android.tools.r8.graph.DexItem; 13 import com.android.tools.r8.graph.DexMethod; 14 import com.android.tools.r8.graph.DexType; 15 import com.android.tools.r8.ir.code.ConstNumber; 16 import com.android.tools.r8.ir.code.ConstType; 17 import com.android.tools.r8.ir.code.IRCode; 18 import com.android.tools.r8.ir.code.InstancePut; 19 import com.android.tools.r8.ir.code.Instruction; 20 import com.android.tools.r8.ir.code.InstructionIterator; 21 import com.android.tools.r8.ir.code.InvokeMethod; 22 import com.android.tools.r8.ir.code.MoveType; 23 import com.android.tools.r8.ir.code.StaticGet; 24 import com.android.tools.r8.ir.code.StaticPut; 25 import com.android.tools.r8.ir.code.Value; 26 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness; 27 import com.android.tools.r8.shaking.ProguardMemberRule; 28 29 public class MemberValuePropagation { 30 31 private final AppInfo appInfo; 32 private final AppInfoWithLiveness liveSet; 33 34 private enum RuleType { 35 NONE, 36 ASSUME_NO_SIDE_EFFECTS, 37 ASSUME_VALUES 38 } 39 40 private class ProguardMemberRuleLookup { 41 42 final RuleType type; 43 final ProguardMemberRule rule; 44 ProguardMemberRuleLookup(RuleType type, ProguardMemberRule rule)45 ProguardMemberRuleLookup(RuleType type, ProguardMemberRule rule) { 46 this.type = type; 47 this.rule = rule; 48 } 49 } 50 MemberValuePropagation(AppInfo appInfo)51 public MemberValuePropagation(AppInfo appInfo) { 52 this.appInfo = appInfo; 53 this.liveSet = appInfo.withLiveness(); 54 } 55 lookupMemberRule(DexItem item)56 private ProguardMemberRuleLookup lookupMemberRule(DexItem item) { 57 if (liveSet == null) { 58 return null; 59 } 60 ProguardMemberRule rule = liveSet.noSideEffects.get(item); 61 if (rule != null) { 62 return new ProguardMemberRuleLookup(RuleType.ASSUME_NO_SIDE_EFFECTS, rule); 63 } 64 rule = liveSet.assumedValues.get(item); 65 if (rule != null) { 66 return new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule); 67 } 68 return null; 69 } 70 constantReplacementFromProguardRule( ProguardMemberRule rule, IRCode code, Instruction instruction)71 private Instruction constantReplacementFromProguardRule( 72 ProguardMemberRule rule, IRCode code, Instruction instruction) { 73 // Check if this value can be assumed constant. 74 Instruction replacement = null; 75 MoveType moveType = instruction.outValue().outType(); 76 if (rule != null && rule.hasReturnValue() && rule.getReturnValue().isSingleValue()) { 77 assert moveType != MoveType.OBJECT; 78 Value value = code.createValue(moveType, instruction.getDebugInfo()); 79 replacement = new ConstNumber( 80 ConstType.fromMoveType(moveType), value, rule.getReturnValue().getSingleValue()); 81 } 82 if (replacement == null && 83 rule != null && rule.hasReturnValue() && rule.getReturnValue().isField()) { 84 DexField field = rule.getReturnValue().getField(); 85 DexEncodedField staticField = appInfo.lookupStaticTarget(field.clazz, field); 86 if (staticField != null) { 87 Value value = code.createValue(moveType, instruction.getDebugInfo()); 88 replacement = staticField.staticValue.asConstInstruction(false, value); 89 } else { 90 throw new CompilationError(field.clazz.toSourceString() + "." + field.name.toString() + 91 " used in assumevalues rule does not exist."); 92 } 93 } 94 return replacement; 95 } 96 setValueRangeFromProguardRule(ProguardMemberRule rule, Value value)97 private void setValueRangeFromProguardRule(ProguardMemberRule rule, Value value) { 98 if (rule.hasReturnValue() && rule.getReturnValue().isValueRange()) { 99 assert !rule.getReturnValue().isSingleValue(); 100 value.setValueRange(rule.getReturnValue().getValueRange()); 101 } 102 } 103 replaceInstructionFromProguardRule(RuleType ruleType, InstructionIterator iterator, Instruction current, Instruction replacement)104 private void replaceInstructionFromProguardRule(RuleType ruleType, InstructionIterator iterator, 105 Instruction current, Instruction replacement) { 106 if (ruleType == RuleType.ASSUME_NO_SIDE_EFFECTS) { 107 iterator.replaceCurrentInstruction(replacement); 108 } else { 109 if (current.outValue() != null) { 110 assert replacement.outValue() != null; 111 current.outValue().replaceUsers(replacement.outValue()); 112 } 113 iterator.add(replacement); 114 } 115 } 116 117 /** 118 * Replace invoke targets and field accesses with constant values where possible. 119 * <p> 120 * Also assigns value ranges to values where possible. 121 */ rewriteWithConstantValues(IRCode code)122 public void rewriteWithConstantValues(IRCode code) { 123 InstructionIterator iterator = code.instructionIterator(); 124 while (iterator.hasNext()) { 125 Instruction current = iterator.next(); 126 if (current.isInvokeMethod()) { 127 InvokeMethod invoke = current.asInvokeMethod(); 128 DexMethod invokedMethod = invoke.getInvokedMethod(); 129 DexType invokedHolder = invokedMethod.getHolder(); 130 if (!invokedHolder.isClassType()) { 131 continue; 132 } 133 DexEncodedMethod definition = appInfo.lookup(invoke.getType(), invokedMethod); 134 135 // Process invokes marked as having no side effects. 136 boolean invokeReplaced = false; 137 ProguardMemberRuleLookup lookup = lookupMemberRule(definition); 138 if (lookup != null) { 139 if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS 140 && (invoke.outValue() == null || invoke.outValue().numberOfAllUsers() == 0)) { 141 iterator.remove(); 142 invokeReplaced = true; 143 } else { 144 // Check to see if a constant value can be assumed. 145 Instruction replacement = 146 constantReplacementFromProguardRule(lookup.rule, code, invoke); 147 if (replacement != null) { 148 replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement); 149 invokeReplaced = true; 150 } else { 151 // Check to see if a value range can be assumed. 152 setValueRangeFromProguardRule(lookup.rule, current.outValue()); 153 } 154 } 155 } 156 157 // If no Proguard rule could replace the instruction check for knowledge about the 158 // return value. 159 if (!invokeReplaced && liveSet != null && invoke.outValue() != null) { 160 DexEncodedMethod target = invoke.computeSingleTarget(liveSet); 161 if (target != null) { 162 if (target.getOptimizationInfo().neverReturnsNull()) { 163 invoke.outValue().markNeverNull(); 164 } 165 if (target.getOptimizationInfo().returnsConstant()) { 166 long constant = target.getOptimizationInfo().getReturnedConstant(); 167 MoveType moveType = invoke.outType(); 168 if (moveType == MoveType.OBJECT) { 169 assert constant == 0; 170 moveType = MoveType.SINGLE; 171 } 172 Value value = code.createValue(moveType); 173 // TODO(ager): Attempt to get a more precise const type from the method analysis? 174 Instruction knownConstReturn = 175 new ConstNumber(ConstType.fromMoveType(moveType), value, constant); 176 invoke.outValue().replaceUsers(value); 177 iterator.add(knownConstReturn); 178 } 179 } 180 } 181 } else if (current.isInstancePut()) { 182 InstancePut instancePut = current.asInstancePut(); 183 DexField field = instancePut.getField(); 184 DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field); 185 if (target != null) { 186 // Remove writes to dead (i.e. never read) fields. 187 if (!isFieldRead(target, false) && instancePut.object().isNeverNull()) { 188 iterator.remove(); 189 } 190 } 191 } else if (current.isStaticGet()) { 192 StaticGet staticGet = current.asStaticGet(); 193 DexField field = staticGet.getField(); 194 Instruction replacement = null; 195 DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field); 196 ProguardMemberRuleLookup lookup = null; 197 if (target != null) { 198 // Check if a this value is known const. 199 replacement = target.valueAsConstInstruction(appInfo, staticGet.dest()); 200 if (replacement == null) { 201 lookup = lookupMemberRule(target); 202 if (lookup != null) { 203 replacement = constantReplacementFromProguardRule(lookup.rule, code, staticGet); 204 } 205 } 206 if (replacement == null) { 207 // If no const replacement was found, at least store the range information. 208 if (lookup != null) { 209 setValueRangeFromProguardRule(lookup.rule, staticGet.dest()); 210 } 211 } 212 if (replacement != null) { 213 // Ignore assumenosideeffects for fields. 214 if (lookup != null && lookup.type == RuleType.ASSUME_VALUES) { 215 replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement); 216 } else { 217 iterator.replaceCurrentInstruction(replacement); 218 } 219 } 220 } 221 } else if (current.isStaticPut()) { 222 StaticPut staticPut = current.asStaticPut(); 223 DexField field = staticPut.getField(); 224 DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field); 225 if (target != null) { 226 // Remove writes to dead (i.e. never read) fields. 227 if (!isFieldRead(target, true)) { 228 iterator.remove(); 229 } 230 } 231 } 232 } 233 assert code.isConsistentSSA(); 234 } 235 isFieldRead(DexEncodedField field, boolean isStatic)236 private boolean isFieldRead(DexEncodedField field, boolean isStatic) { 237 // Without live set information we cannot tell and assume true. 238 if (liveSet == null 239 || liveSet.fieldsRead.contains(field.field) 240 || liveSet.pinnedItems.contains(field)) { 241 return true; 242 } 243 // For library classes we don't know whether a field is read. 244 DexClass holder = appInfo.definitionFor(field.field.clazz); 245 return holder == null || holder.isLibraryClass(); 246 } 247 } 248