| // Copyright (c) 2020, 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.kotlin; |
| |
| import static com.android.tools.r8.kotlin.KotlinClassMetadataReader.hasKotlinClassMetadataAnnotation; |
| import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo; |
| |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexDefinitionSupplier; |
| import com.android.tools.r8.graph.DexEncodedMember; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexMethod; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.EnclosingMethodAttribute; |
| import com.android.tools.r8.graph.ProgramDefinition; |
| import com.android.tools.r8.graph.analysis.EnqueuerAnalysis; |
| import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; |
| import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; |
| import com.android.tools.r8.shaking.Enqueuer; |
| import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier; |
| import com.android.tools.r8.shaking.KeepClassInfo; |
| import com.google.common.collect.Sets; |
| import java.util.Set; |
| |
| public class KotlinMetadataEnqueuerExtension extends EnqueuerAnalysis { |
| |
| private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance(); |
| |
| private final AppView<?> appView; |
| private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier; |
| private final Set<DexType> prunedTypes; |
| |
| public KotlinMetadataEnqueuerExtension( |
| AppView<?> appView, |
| EnqueuerDefinitionSupplier enqueuerDefinitionSupplier, |
| Set<DexType> prunedTypes) { |
| this.appView = appView; |
| this.enqueuerDefinitionSupplier = enqueuerDefinitionSupplier; |
| this.prunedTypes = prunedTypes; |
| } |
| |
| private KotlinMetadataDefinitionSupplier definitionsForContext(ProgramDefinition context) { |
| return new KotlinMetadataDefinitionSupplier(context, enqueuerDefinitionSupplier, prunedTypes); |
| } |
| |
| @Override |
| public void done(Enqueuer enqueuer) { |
| // In the first round of tree shaking build up all metadata such that it can be traced later. |
| boolean keepKotlinMetadata = |
| KeepClassInfo.isKotlinMetadataClassKept( |
| appView.dexItemFactory(), |
| appView.options(), |
| appView.appInfo()::definitionForWithoutExistenceAssert, |
| enqueuer::getKeepInfo); |
| // In the first round of tree shaking build up all metadata such that it can be traced later. |
| if (enqueuer.getMode().isInitialTreeShaking()) { |
| Set<DexMethod> keepByteCodeFunctions = Sets.newIdentityHashSet(); |
| Set<DexProgramClass> localOrAnonymousClasses = Sets.newIdentityHashSet(); |
| enqueuer.forAllLiveClasses( |
| clazz -> { |
| assert clazz.getKotlinInfo().isNoKotlinInformation(); |
| if (enqueuer |
| .getKeepInfo(clazz) |
| .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) { |
| if (KotlinClassMetadataReader.isLambda(appView, clazz) |
| && clazz.hasClassInitializer()) { |
| feedback.classInitializerMayBePostponed(clazz.getClassInitializer()); |
| } |
| clazz.clearKotlinInfo(); |
| clazz.removeAnnotations( |
| annotation -> |
| annotation.getAnnotationType() |
| == appView.dexItemFactory().kotlinMetadataType); |
| } else { |
| clazz.setKotlinInfo( |
| KotlinClassMetadataReader.getKotlinInfo( |
| clazz, appView, method -> keepByteCodeFunctions.add(method.getReference()))); |
| if (clazz.getEnclosingMethodAttribute() != null |
| && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) { |
| localOrAnonymousClasses.add(clazz); |
| } |
| } |
| }); |
| for (DexProgramClass localOrAnonymousClass : localOrAnonymousClasses) { |
| EnclosingMethodAttribute enclosingAttribute = |
| localOrAnonymousClass.getEnclosingMethodAttribute(); |
| DexClass holder = |
| definitionsForContext(localOrAnonymousClass) |
| .definitionForHolder(enclosingAttribute.getEnclosingMethod()); |
| if (holder == null) { |
| continue; |
| } |
| DexEncodedMethod method = holder.lookupMethod(enclosingAttribute.getEnclosingMethod()); |
| // If we cannot lookup the method, the conservative choice is keep the byte code. |
| if (method == null |
| || (method.getKotlinInfo().isFunction() |
| && method.getKotlinInfo().asFunction().hasCrossInlineParameter())) { |
| localOrAnonymousClass.forEachProgramMethod( |
| m -> keepByteCodeFunctions.add(m.getReference())); |
| } |
| } |
| appView.setCfByteCodePassThrough(keepByteCodeFunctions); |
| } else { |
| assert enqueuer.getMode().isFinalTreeShaking(); |
| enqueuer.forAllLiveClasses( |
| clazz -> { |
| if (enqueuer |
| .getKeepInfo(clazz) |
| .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) { |
| clazz.clearKotlinInfo(); |
| clazz.members().forEach(DexEncodedMember::clearKotlinInfo); |
| clazz.removeAnnotations( |
| annotation -> |
| annotation.getAnnotationType() |
| == appView.dexItemFactory().kotlinMetadataType); |
| } else { |
| // Use the concrete getNoKotlinInfo() instead of isNoKotlinInformation() to handle |
| // invalid kotlin info as well. |
| assert hasKotlinClassMetadataAnnotation(clazz, definitionsForContext(clazz)) |
| == (clazz.getKotlinInfo() != getNoKotlinInfo()); |
| } |
| }); |
| } |
| // Trace through the modeled kotlin metadata. |
| enqueuer.forAllLiveClasses( |
| clazz -> { |
| clazz.getKotlinInfo().trace(definitionsForContext(clazz)); |
| clazz.forEachProgramMember( |
| member -> |
| member.getDefinition().getKotlinInfo().trace(definitionsForContext(member))); |
| }); |
| } |
| |
| public class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier { |
| |
| private final ProgramDefinition context; |
| private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier; |
| private final Set<DexType> prunedTypes; |
| |
| private KotlinMetadataDefinitionSupplier( |
| ProgramDefinition context, |
| EnqueuerDefinitionSupplier enqueuerDefinitionSupplier, |
| Set<DexType> prunedTypes) { |
| this.context = context; |
| this.enqueuerDefinitionSupplier = enqueuerDefinitionSupplier; |
| this.prunedTypes = prunedTypes; |
| } |
| |
| @Override |
| public DexClass definitionFor(DexType type) { |
| // TODO(b/157700128) Metadata cannot at this point keep anything alive. Therefore, if a type |
| // has been pruned it may still be referenced, so we do an early check here to ensure it will |
| // not end up as. Ideally, those types should be removed by a pass on the modeled data. |
| if (prunedTypes != null && prunedTypes.contains(type)) { |
| return null; |
| } |
| return enqueuerDefinitionSupplier.definitionFor(type, context); |
| } |
| |
| @Override |
| public DexItemFactory dexItemFactory() { |
| return appView.dexItemFactory(); |
| } |
| } |
| } |