• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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