1 // Copyright (c) 2016, 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.graph; 5 6 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_PACKAGE_PRIVATE; 7 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_PRIVATE; 8 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_PUBLIC; 9 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE; 10 11 import com.android.tools.r8.code.Const; 12 import com.android.tools.r8.code.ConstString; 13 import com.android.tools.r8.code.ConstStringJumbo; 14 import com.android.tools.r8.code.Instruction; 15 import com.android.tools.r8.code.InvokeDirect; 16 import com.android.tools.r8.code.InvokeStatic; 17 import com.android.tools.r8.code.InvokeSuper; 18 import com.android.tools.r8.code.NewInstance; 19 import com.android.tools.r8.code.Throw; 20 import com.android.tools.r8.dex.Constants; 21 import com.android.tools.r8.dex.IndexedItemCollection; 22 import com.android.tools.r8.dex.MixedSectionCollection; 23 import com.android.tools.r8.ir.code.IRCode; 24 import com.android.tools.r8.ir.code.Invoke; 25 import com.android.tools.r8.ir.code.MoveType; 26 import com.android.tools.r8.ir.code.ValueNumberGenerator; 27 import com.android.tools.r8.ir.conversion.DexBuilder; 28 import com.android.tools.r8.ir.optimize.Inliner.Constraint; 29 import com.android.tools.r8.ir.regalloc.RegisterAllocator; 30 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode; 31 import com.android.tools.r8.ir.synthetic.SynthesizedCode; 32 import com.android.tools.r8.logging.Log; 33 import com.android.tools.r8.naming.ClassNameMapper; 34 import com.android.tools.r8.naming.MemberNaming.MethodSignature; 35 import com.android.tools.r8.naming.MemberNaming.Signature; 36 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness; 37 import com.android.tools.r8.utils.InternalOptions; 38 39 public class DexEncodedMethod extends KeyedDexItem<DexMethod> { 40 41 public enum CompilationState 42 43 { 44 NOT_PROCESSED, 45 PROCESSED_NOT_INLINING_CANDIDATE, 46 // Code only contains instructions that access public entities. 47 PROCESSED_INLINING_CANDIDATE_PUBLIC, 48 // Code only contains instructions that access public and package private entities. 49 PROCESSED_INLINING_CANDIDATE_PACKAGE_PRIVATE, 50 // Code also contains instructions that access public entities. 51 PROCESSED_INLINING_CANDIDATE_PRIVATE, 52 } 53 54 public static final DexEncodedMethod[] EMPTY_ARRAY = new DexEncodedMethod[]{}; 55 public static final DexEncodedMethod SENTINEL = 56 new DexEncodedMethod(null, null, null, null, null); 57 58 public final DexMethod method; 59 public final DexAccessFlags accessFlags; 60 public DexAnnotationSet annotations; 61 public DexAnnotationSetRefList parameterAnnotations; 62 private Code code; 63 private CompilationState compilationState = CompilationState.NOT_PROCESSED; 64 private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT; 65 DexEncodedMethod(DexMethod method, DexAccessFlags accessFlags, DexAnnotationSet annotations, DexAnnotationSetRefList parameterAnnotations, Code code)66 public DexEncodedMethod(DexMethod method, DexAccessFlags accessFlags, 67 DexAnnotationSet annotations, DexAnnotationSetRefList parameterAnnotations, Code code) { 68 this.method = method; 69 this.accessFlags = accessFlags; 70 this.annotations = annotations; 71 this.parameterAnnotations = parameterAnnotations; 72 this.code = code; 73 assert code == null || !accessFlags.isAbstract(); 74 } 75 isProcessed()76 public boolean isProcessed() { 77 return compilationState != CompilationState.NOT_PROCESSED; 78 } 79 cannotInline()80 public boolean cannotInline() { 81 return compilationState == CompilationState.NOT_PROCESSED 82 || compilationState == CompilationState.PROCESSED_NOT_INLINING_CANDIDATE; 83 } 84 isInliningCandidate(DexEncodedMethod container, boolean alwaysInline)85 public boolean isInliningCandidate(DexEncodedMethod container, boolean alwaysInline) { 86 if (container.accessFlags.isStatic() && container.accessFlags.isConstructor()) { 87 // This will probably never happen but never inline a class initializer. 88 return false; 89 } 90 if (alwaysInline) { 91 // Only inline constructor iff holder classes are equal. 92 if (!accessFlags.isStatic() && accessFlags.isConstructor()) { 93 return container.method.getHolder() == method.getHolder(); 94 } 95 return true; 96 } 97 switch (compilationState) { 98 case PROCESSED_INLINING_CANDIDATE_PUBLIC: 99 return true; 100 case PROCESSED_INLINING_CANDIDATE_PACKAGE_PRIVATE: 101 return container.method.getHolder().isSamePackage(method.getHolder()); 102 // TODO(bak): Expand check for package private access: 103 case PROCESSED_INLINING_CANDIDATE_PRIVATE: 104 return container.method.getHolder() == method.getHolder(); 105 default: 106 return false; 107 } 108 } 109 isPublicInlining()110 public boolean isPublicInlining() { 111 return compilationState == PROCESSED_INLINING_CANDIDATE_PUBLIC; 112 } 113 markProcessed(Constraint state)114 public boolean markProcessed(Constraint state) { 115 CompilationState prevCompilationState = compilationState; 116 switch (state) { 117 case ALWAYS: 118 compilationState = PROCESSED_INLINING_CANDIDATE_PUBLIC; 119 break; 120 case PACKAGE: 121 compilationState = PROCESSED_INLINING_CANDIDATE_PACKAGE_PRIVATE; 122 break; 123 case PRIVATE: 124 compilationState = PROCESSED_INLINING_CANDIDATE_PRIVATE; 125 break; 126 case NEVER: 127 compilationState = PROCESSED_NOT_INLINING_CANDIDATE; 128 break; 129 } 130 return prevCompilationState != compilationState; 131 } 132 markNotProcessed()133 public void markNotProcessed() { 134 compilationState = CompilationState.NOT_PROCESSED; 135 } 136 buildIR(InternalOptions options)137 public IRCode buildIR(InternalOptions options) { 138 return code == null ? null : code.buildIR(this, options); 139 } 140 buildIR(ValueNumberGenerator valueNumberGenerator, InternalOptions options)141 public IRCode buildIR(ValueNumberGenerator valueNumberGenerator, InternalOptions options) { 142 return code == null 143 ? null 144 : code.asDexCode().buildIR(this, valueNumberGenerator, options); 145 } 146 setCode( IRCode ir, RegisterAllocator registerAllocator, DexItemFactory dexItemFactory)147 public void setCode( 148 IRCode ir, RegisterAllocator registerAllocator, DexItemFactory dexItemFactory) { 149 final DexBuilder builder = new DexBuilder(ir, registerAllocator, dexItemFactory); 150 code = builder.build(method.proto.parameters.values.length); 151 } 152 153 // Replaces the dex code in the method by setting code to result of compiling the IR. setCode(IRCode ir, RegisterAllocator registerAllocator, DexItemFactory dexItemFactory, DexString firstJumboString)154 public void setCode(IRCode ir, RegisterAllocator registerAllocator, 155 DexItemFactory dexItemFactory, DexString firstJumboString) { 156 final DexBuilder builder = 157 new DexBuilder(ir, registerAllocator, dexItemFactory, firstJumboString); 158 code = builder.build(method.proto.parameters.values.length); 159 } 160 toString()161 public String toString() { 162 return "Encoded method " + method; 163 } 164 165 @Override collectIndexedItems(IndexedItemCollection indexedItems)166 public void collectIndexedItems(IndexedItemCollection indexedItems) { 167 method.collectIndexedItems(indexedItems); 168 if (code != null) { 169 code.collectIndexedItems(indexedItems); 170 } 171 annotations.collectIndexedItems(indexedItems); 172 parameterAnnotations.collectIndexedItems(indexedItems); 173 } 174 175 @Override collectMixedSectionItems(MixedSectionCollection mixedItems)176 void collectMixedSectionItems(MixedSectionCollection mixedItems) { 177 if (code != null) { 178 code.collectMixedSectionItems(mixedItems); 179 } 180 annotations.collectMixedSectionItems(mixedItems); 181 parameterAnnotations.collectMixedSectionItems(mixedItems); 182 } 183 getCode()184 public Code getCode() { 185 return code; 186 } 187 removeCode()188 public void removeCode() { 189 code = null; 190 } 191 qualifiedName()192 public String qualifiedName() { 193 return method.qualifiedName(); 194 } 195 descriptor()196 public String descriptor() { 197 StringBuilder builder = new StringBuilder(); 198 builder.append("("); 199 for (DexType type : method.proto.parameters.values) { 200 builder.append(type.descriptor.toString()); 201 } 202 builder.append(")"); 203 builder.append(method.proto.returnType.descriptor.toString()); 204 return builder.toString(); 205 } 206 toSmaliString(ClassNameMapper naming)207 public String toSmaliString(ClassNameMapper naming) { 208 StringBuilder builder = new StringBuilder(); 209 builder.append(".method "); 210 builder.append(accessFlags.toSmaliString()); 211 builder.append(" "); 212 builder.append(method.name.toSmaliString()); 213 builder.append(method.proto.toSmaliString()); 214 builder.append("\n"); 215 if (code != null) { 216 DexCode dexCode = code.asDexCode(); 217 builder.append(" .registers "); 218 builder.append(dexCode.registerSize); 219 builder.append("\n\n"); 220 builder.append(dexCode.toSmaliString(naming)); 221 } 222 builder.append(".end method\n"); 223 return builder.toString(); 224 } 225 226 @Override toSourceString()227 public String toSourceString() { 228 return method.toSourceString(); 229 } 230 toAbstractMethod()231 public DexEncodedMethod toAbstractMethod() { 232 accessFlags.setAbstract(); 233 this.code = null; 234 return this; 235 } 236 237 /** 238 * Generates a {@link DexCode} object for the given instructions. 239 * <p> 240 * As the code object is produced outside of the normal compilation cycle, it has to use 241 * {@link ConstStringJumbo} to reference string constants. Hence, code produced form these 242 * templates might incur a size overhead. 243 */ generateCodeFromTemplate( int numberOfRegisters, int outRegisters, Instruction... instructions)244 private DexCode generateCodeFromTemplate( 245 int numberOfRegisters, int outRegisters, Instruction... instructions) { 246 int offset = 0; 247 for (Instruction instruction : instructions) { 248 assert !(instruction instanceof ConstString); 249 instruction.setOffset(offset); 250 offset += instruction.getSize(); 251 } 252 int requiredArgRegisters = accessFlags.isStatic() ? 0 : 1; 253 for (DexType type : method.proto.parameters.values) { 254 requiredArgRegisters += MoveType.fromDexType(type).requiredRegisters(); 255 } 256 // Passing null as highestSortingString is save, as ConstString instructions are not allowed. 257 return new DexCode(Math.max(numberOfRegisters, requiredArgRegisters), requiredArgRegisters, 258 outRegisters, instructions, new DexCode.Try[0], new DexCode.TryHandler[0], null, null); 259 } 260 toEmptyThrowingMethod()261 public DexEncodedMethod toEmptyThrowingMethod() { 262 Instruction insn[] = {new Const(0, 0), new Throw(0)}; 263 DexCode code = generateCodeFromTemplate(1, 0, insn); 264 assert !accessFlags.isAbstract(); 265 Builder builder = builder(this); 266 builder.setCode(code); 267 return builder.build(); 268 } 269 toMethodThatLogsError(DexItemFactory itemFactory)270 public DexEncodedMethod toMethodThatLogsError(DexItemFactory itemFactory) { 271 Signature signature = MethodSignature.fromDexMethod(method); 272 // TODO(herhut): Construct this out of parts to enable reuse, maybe even using descriptors. 273 DexString message = itemFactory.createString( 274 "Shaking error: Missing method in " + method.holder.toSourceString() + ": " 275 + signature); 276 DexString tag = itemFactory.createString("TOIGHTNESS"); 277 DexType[] args = {itemFactory.stringType, itemFactory.stringType}; 278 DexProto proto = itemFactory.createProto(itemFactory.intType, args); 279 DexMethod logMethod = itemFactory 280 .createMethod(itemFactory.createType("Landroid/util/Log;"), proto, 281 itemFactory.createString("e")); 282 DexType exceptionType = itemFactory.createType("Ljava/lang/RuntimeException;"); 283 DexMethod exceptionInitMethod = itemFactory 284 .createMethod(exceptionType, itemFactory.createProto(itemFactory.voidType, 285 itemFactory.stringType), 286 itemFactory.constructorMethodName); 287 DexCode code; 288 if (accessFlags.isConstructor() && !accessFlags.isStatic()) { 289 // The Java VM Spec requires that a constructor calls an initializer from the super class 290 // or another constructor from the current class. For simplicity we do the latter by just 291 // calling outself. This is ok, as the constructor always throws before the recursive call. 292 code = generateCodeFromTemplate(3, 2, new ConstStringJumbo(0, tag), 293 new ConstStringJumbo(1, message), 294 new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0), 295 new NewInstance(0, exceptionType), 296 new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0), 297 new Throw(0), 298 new InvokeDirect(1, method, 2, 0, 0, 0, 0)); 299 300 } else { 301 // These methods might not get registered for jumbo string processing, therefore we always 302 // use the jumbo string encoding for the const string instruction. 303 code = generateCodeFromTemplate(2, 2, new ConstStringJumbo(0, tag), 304 new ConstStringJumbo(1, message), 305 new InvokeStatic(2, logMethod, 0, 1, 0, 0, 0), 306 new NewInstance(0, exceptionType), 307 new InvokeDirect(2, exceptionInitMethod, 0, 1, 0, 0, 0), 308 new Throw(0)); 309 } 310 Builder builder = builder(this); 311 builder.setCode(code); 312 return builder.build(); 313 } 314 toTypeSubstitutedMethod(DexMethod method)315 public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method) { 316 if (this.method == method) { 317 return this; 318 } 319 Builder builder = builder(this); 320 builder.setMethod(method); 321 return builder.build(); 322 } 323 toRenamedMethod(DexString name, DexItemFactory factory)324 public DexEncodedMethod toRenamedMethod(DexString name, DexItemFactory factory) { 325 if (method.name == name) { 326 return this; 327 } 328 DexMethod newMethod = factory.createMethod(method.holder, method.proto, name); 329 Builder builder = builder(this); 330 builder.setMethod(newMethod); 331 return builder.build(); 332 } 333 toForwardingMethod(DexClass holder, DexItemFactory itemFactory)334 public DexEncodedMethod toForwardingMethod(DexClass holder, DexItemFactory itemFactory) { 335 assert accessFlags.isPublic(); 336 DexMethod newMethod = itemFactory.createMethod(holder.type, method.proto, method.name); 337 Invoke.Type type = accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER; 338 Builder builder = builder(this); 339 builder.setMethod(newMethod); 340 if (accessFlags.isAbstract()) { 341 // If the forwarding target is abstract, we can just create an abstract method. While it 342 // will not actually forward, it will create the same exception when hit at runtime. 343 builder.accessFlags.setAbstract(); 344 } else { 345 // Create code that forwards the call to the target. 346 builder.setCode(new SynthesizedCode( 347 new ForwardMethodSourceCode( 348 accessFlags.isStatic() ? null : holder.type, 349 method.proto, 350 accessFlags.isStatic() ? null : method.holder, 351 method, 352 type), 353 registry -> { 354 if (accessFlags.isStatic()) { 355 registry.registerInvokeStatic(method); 356 } else { 357 registry.registerInvokeSuper(method); 358 } 359 })); 360 } 361 builder.accessFlags.setSynthetic(); 362 accessFlags.unsetFinal(); 363 return builder.build(); 364 } 365 codeToString()366 public String codeToString() { 367 return code == null ? "<no code>" : code.toString(this, null); 368 } 369 370 @Override getKey()371 public DexMethod getKey() { 372 return method; 373 } 374 hasAnnotation()375 public boolean hasAnnotation() { 376 return !annotations.isEmpty() || !parameterAnnotations.isEmpty(); 377 } 378 registerReachableDefinitions(UseRegistry registry)379 public void registerReachableDefinitions(UseRegistry registry) { 380 if (code != null) { 381 if (Log.ENABLED) { 382 Log.verbose((Class) getClass(), "Registering definitions reachable from `%s`.", method); 383 } 384 code.registerReachableDefinitions(registry); 385 } 386 } 387 388 public static class OptimizationInfo { 389 390 private int returnedArgument = -1; 391 private boolean neverReturnsNull = false; 392 private boolean returnsConstant = false; 393 private long returnedConstant = 0; 394 private boolean forceInline = false; 395 OptimizationInfo()396 private OptimizationInfo() { 397 // Intentionally left empty. 398 } 399 OptimizationInfo(OptimizationInfo template)400 private OptimizationInfo(OptimizationInfo template) { 401 returnedArgument = template.returnedArgument; 402 neverReturnsNull = template.neverReturnsNull; 403 returnsConstant = template.returnsConstant; 404 returnedConstant = template.returnedConstant; 405 forceInline = template.forceInline; 406 } 407 returnsArgument()408 public boolean returnsArgument() { 409 return returnedArgument != -1; 410 } 411 getReturnedArgument()412 public int getReturnedArgument() { 413 assert returnsArgument(); 414 return returnedArgument; 415 } 416 neverReturnsNull()417 public boolean neverReturnsNull() { 418 return neverReturnsNull; 419 } 420 returnsConstant()421 public boolean returnsConstant() { 422 return returnsConstant; 423 } 424 getReturnedConstant()425 public long getReturnedConstant() { 426 assert returnsConstant(); 427 return returnedConstant; 428 } 429 forceInline()430 public boolean forceInline() { 431 return forceInline; 432 } 433 markReturnsArgument(int argument)434 private void markReturnsArgument(int argument) { 435 assert argument >= 0; 436 assert returnedArgument == -1 || returnedArgument == argument; 437 returnedArgument = argument; 438 } 439 markNeverReturnsNull()440 private void markNeverReturnsNull() { 441 neverReturnsNull = true; 442 } 443 markReturnsConstant(long value)444 private void markReturnsConstant(long value) { 445 assert !returnsConstant || returnedConstant == value; 446 returnsConstant = true; 447 returnedConstant = value; 448 } 449 markForceInline()450 private void markForceInline() { 451 forceInline = true; 452 } 453 copy()454 public OptimizationInfo copy() { 455 return new OptimizationInfo(this); 456 } 457 } 458 459 private static class DefaultOptimizationInfo extends OptimizationInfo { 460 461 static final OptimizationInfo DEFAULT = new DefaultOptimizationInfo(); 462 DefaultOptimizationInfo()463 private DefaultOptimizationInfo() { 464 } 465 466 @Override copy()467 public OptimizationInfo copy() { 468 return this; 469 } 470 } 471 ensureMutableOI()472 synchronized private OptimizationInfo ensureMutableOI() { 473 if (optimizationInfo == DefaultOptimizationInfo.DEFAULT) { 474 optimizationInfo = new OptimizationInfo(); 475 } 476 return optimizationInfo; 477 } 478 markReturnsArgument(int argument)479 synchronized public void markReturnsArgument(int argument) { 480 ensureMutableOI().markReturnsArgument(argument); 481 } 482 markNeverReturnsNull()483 synchronized public void markNeverReturnsNull() { 484 ensureMutableOI().markNeverReturnsNull(); 485 } 486 markReturnsConstant(long value)487 synchronized public void markReturnsConstant(long value) { 488 ensureMutableOI().markReturnsConstant(value); 489 } 490 markForceInline()491 synchronized public void markForceInline() { 492 ensureMutableOI().markForceInline(); 493 } 494 getOptimizationInfo()495 public OptimizationInfo getOptimizationInfo() { 496 return optimizationInfo; 497 } 498 builder(DexEncodedMethod from)499 private static Builder builder(DexEncodedMethod from) { 500 return new Builder(from); 501 } 502 503 private static class Builder { 504 505 private DexMethod method; 506 private DexAccessFlags accessFlags; 507 private DexAnnotationSet annotations; 508 private DexAnnotationSetRefList parameterAnnotations; 509 private Code code; 510 private CompilationState compilationState = CompilationState.NOT_PROCESSED; 511 private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT; 512 Builder(DexEncodedMethod from)513 private Builder(DexEncodedMethod from) { 514 // Copy all the mutable state of a DexEncodedMethod here. 515 method = from.method; 516 accessFlags = new DexAccessFlags(from.accessFlags.get()); 517 annotations = from.annotations; 518 parameterAnnotations = from.parameterAnnotations; 519 code = from.code; 520 compilationState = from.compilationState; 521 optimizationInfo = from.optimizationInfo.copy(); 522 } 523 setMethod(DexMethod method)524 public void setMethod(DexMethod method) { 525 this.method = method; 526 } 527 setAccessFlags(DexAccessFlags accessFlags)528 public void setAccessFlags(DexAccessFlags accessFlags) { 529 this.accessFlags = accessFlags; 530 } 531 setAnnotations(DexAnnotationSet annotations)532 public void setAnnotations(DexAnnotationSet annotations) { 533 this.annotations = annotations; 534 } 535 setParameterAnnotations(DexAnnotationSetRefList parameterAnnotations)536 public void setParameterAnnotations(DexAnnotationSetRefList parameterAnnotations) { 537 this.parameterAnnotations = parameterAnnotations; 538 } 539 setCode(Code code)540 public void setCode(Code code) { 541 this.code = code; 542 } 543 build()544 public DexEncodedMethod build() { 545 assert method != null; 546 assert accessFlags != null; 547 assert annotations != null; 548 assert parameterAnnotations != null; 549 DexEncodedMethod result = 550 new DexEncodedMethod(method, accessFlags, annotations, parameterAnnotations, code); 551 result.compilationState = compilationState; 552 result.optimizationInfo = optimizationInfo; 553 return result; 554 } 555 } 556 } 557