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.
4package com.android.tools.r8.graph;
5
6import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_PACKAGE_PRIVATE;
7import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_PRIVATE;
8import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_INLINING_CANDIDATE_PUBLIC;
9import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
10
11import com.android.tools.r8.code.Const;
12import com.android.tools.r8.code.ConstString;
13import com.android.tools.r8.code.ConstStringJumbo;
14import com.android.tools.r8.code.Instruction;
15import com.android.tools.r8.code.InvokeDirect;
16import com.android.tools.r8.code.InvokeStatic;
17import com.android.tools.r8.code.InvokeSuper;
18import com.android.tools.r8.code.NewInstance;
19import com.android.tools.r8.code.Throw;
20import com.android.tools.r8.dex.Constants;
21import com.android.tools.r8.dex.IndexedItemCollection;
22import com.android.tools.r8.dex.MixedSectionCollection;
23import com.android.tools.r8.ir.code.IRCode;
24import com.android.tools.r8.ir.code.Invoke;
25import com.android.tools.r8.ir.code.MoveType;
26import com.android.tools.r8.ir.code.ValueNumberGenerator;
27import com.android.tools.r8.ir.conversion.DexBuilder;
28import com.android.tools.r8.ir.optimize.Inliner.Constraint;
29import com.android.tools.r8.ir.regalloc.RegisterAllocator;
30import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
31import com.android.tools.r8.ir.synthetic.SynthesizedCode;
32import com.android.tools.r8.logging.Log;
33import com.android.tools.r8.naming.ClassNameMapper;
34import com.android.tools.r8.naming.MemberNaming.MethodSignature;
35import com.android.tools.r8.naming.MemberNaming.Signature;
36import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
37import com.android.tools.r8.utils.InternalOptions;
38
39public 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
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
76  public boolean isProcessed() {
77    return compilationState != CompilationState.NOT_PROCESSED;
78  }
79
80  public boolean cannotInline() {
81    return compilationState == CompilationState.NOT_PROCESSED
82        || compilationState == CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
83  }
84
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
110  public boolean isPublicInlining() {
111    return compilationState == PROCESSED_INLINING_CANDIDATE_PUBLIC;
112  }
113
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
133  public void markNotProcessed() {
134    compilationState = CompilationState.NOT_PROCESSED;
135  }
136
137  public IRCode buildIR(InternalOptions options) {
138    return code == null ? null : code.buildIR(this, options);
139  }
140
141  public IRCode buildIR(ValueNumberGenerator valueNumberGenerator, InternalOptions options) {
142    return code == null
143        ? null
144        : code.asDexCode().buildIR(this, valueNumberGenerator, options);
145  }
146
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.
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
161  public String toString() {
162    return "Encoded method " + method;
163  }
164
165  @Override
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
176  void collectMixedSectionItems(MixedSectionCollection mixedItems) {
177    if (code != null) {
178      code.collectMixedSectionItems(mixedItems);
179    }
180    annotations.collectMixedSectionItems(mixedItems);
181    parameterAnnotations.collectMixedSectionItems(mixedItems);
182  }
183
184  public Code getCode() {
185    return code;
186  }
187
188  public void removeCode() {
189    code = null;
190  }
191
192  public String qualifiedName() {
193    return method.qualifiedName();
194  }
195
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
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
227  public String toSourceString() {
228    return method.toSourceString();
229  }
230
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   */
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
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
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
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
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
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
366  public String codeToString() {
367    return code == null ? "<no code>" : code.toString(this, null);
368  }
369
370  @Override
371  public DexMethod getKey() {
372    return method;
373  }
374
375  public boolean hasAnnotation() {
376    return !annotations.isEmpty() || !parameterAnnotations.isEmpty();
377  }
378
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
396    private OptimizationInfo() {
397      // Intentionally left empty.
398    }
399
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
408    public boolean returnsArgument() {
409      return returnedArgument != -1;
410    }
411
412    public int getReturnedArgument() {
413      assert returnsArgument();
414      return returnedArgument;
415    }
416
417    public boolean neverReturnsNull() {
418      return neverReturnsNull;
419    }
420
421    public boolean returnsConstant() {
422      return returnsConstant;
423    }
424
425    public long getReturnedConstant() {
426      assert returnsConstant();
427      return returnedConstant;
428    }
429
430    public boolean forceInline() {
431      return forceInline;
432    }
433
434    private void markReturnsArgument(int argument) {
435      assert argument >= 0;
436      assert returnedArgument == -1 || returnedArgument == argument;
437      returnedArgument = argument;
438    }
439
440    private void markNeverReturnsNull() {
441      neverReturnsNull = true;
442    }
443
444    private void markReturnsConstant(long value) {
445      assert !returnsConstant || returnedConstant == value;
446      returnsConstant = true;
447      returnedConstant = value;
448    }
449
450    private void markForceInline() {
451      forceInline = true;
452    }
453
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
463    private DefaultOptimizationInfo() {
464    }
465
466    @Override
467    public OptimizationInfo copy() {
468      return this;
469    }
470  }
471
472  synchronized private OptimizationInfo ensureMutableOI() {
473    if (optimizationInfo == DefaultOptimizationInfo.DEFAULT) {
474      optimizationInfo = new OptimizationInfo();
475    }
476    return optimizationInfo;
477  }
478
479  synchronized public void markReturnsArgument(int argument) {
480    ensureMutableOI().markReturnsArgument(argument);
481  }
482
483  synchronized public void markNeverReturnsNull() {
484    ensureMutableOI().markNeverReturnsNull();
485  }
486
487  synchronized public void markReturnsConstant(long value) {
488    ensureMutableOI().markReturnsConstant(value);
489  }
490
491  synchronized public void markForceInline() {
492    ensureMutableOI().markForceInline();
493  }
494
495  public OptimizationInfo getOptimizationInfo() {
496    return optimizationInfo;
497  }
498
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
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
524    public void setMethod(DexMethod method) {
525      this.method = method;
526    }
527
528    public void setAccessFlags(DexAccessFlags accessFlags) {
529      this.accessFlags = accessFlags;
530    }
531
532    public void setAnnotations(DexAnnotationSet annotations) {
533      this.annotations = annotations;
534    }
535
536    public void setParameterAnnotations(DexAnnotationSetRefList parameterAnnotations) {
537      this.parameterAnnotations = parameterAnnotations;
538    }
539
540    public void setCode(Code code) {
541      this.code = code;
542    }
543
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