Revert "Reland "Fix Kotlin inline retrace support and SMAP parsing."" This reverts commit 48b808dd9ba6940019bb5c50dfdddd9f378d8620. Reason for revert: OOM on CI Failure Link: https://ci.chromium.org/ui/p/r8/builders/ci/linux-android-12/2189 Original change's description: > Reland "Fix Kotlin inline retrace support and SMAP parsing." > > This now only finds references for live classes. > > This is a reland of commit a412c4aba9a651046e5c726b872865f8ed8c737a > > Original change's description: > > Fix Kotlin inline retrace support and SMAP parsing. > > > > Kotlin code can contain inlined methods which is annotated with > > <file>:<line>-<line> to describe where it was inlined from. This means > > that we have to resolve such references to actual methods in the source > > to preserve them in the output mapping. > > > > In R8 partial, an inlined reference might point into D8 code, which > > means that both branches need "full" and upfront info to resolve them. > > > > This CL stores where methods start and end for files referenced by > > inline annotations if output mapping files are enabled. This data does > > not need maintenance since it should always refer to the input program. > > > > I also fixed the handling of overlapping ranges in the SMAP parser to > > comply with specs. > > > > Fixes: b/391785584 > > Change-Id: I77759eeddef82ec1960df11aa75c6bf5c22e67e0 > > Change-Id: I5a623ce9984cd3489aa91b49fd066a28a76aaf80 TBR=christofferqa@google.com,r8-scoped@luci-project-accounts.iam.gserviceaccount.com,android-build-ayeaye@system.gserviceaccount.com,jonathanlist@google.com No-Presubmit: true No-Tree-Checks: true No-Try: true Change-Id: I48f0a476a5a889f1d775ebf2ce17ffd6a20ac847
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java index cf89c64..f169044 100644 --- a/src/main/java/com/android/tools/r8/D8.java +++ b/src/main/java/com/android/tools/r8/D8.java
@@ -35,7 +35,6 @@ import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; import com.android.tools.r8.jar.CfApplicationWriter; import com.android.tools.r8.keepanno.annotations.KeepForApi; -import com.android.tools.r8.kotlin.KotlinInlineMethodMap; import com.android.tools.r8.kotlin.KotlinMetadataRewriter; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.naming.RecordRewritingNamingLens; @@ -256,8 +255,6 @@ if (options.testing.enableD8MetaInfServicesPassThrough) { appView.setAppServices(AppServices.builder(appView).build()); } - appView.setKotlinInlineMethodMap( - KotlinInlineMethodMap.createForD8(appView, executor, timing)); timing.end(); new PrimaryD8L8IRConverter(appView, timing).convert(appView, executor); timing.begin("Post conversion"); @@ -334,7 +331,7 @@ timing.end(); // post-converter reportSyntheticInformation(appView); - writeApplication(appView, marker, executor, timing); + writeApplication(appView, inputApp, marker, executor, timing); options.printWarnings(); } catch (ExecutionException e) { @@ -400,6 +397,7 @@ private static void writeApplication( AppView<AppInfo> appView, + AndroidApp inputApp, Marker marker, ExecutorService executor, Timing timing) @@ -409,9 +407,9 @@ new R8PartialApplicationWriter(appView).write(executor); } else if (options.isGeneratingClassFiles()) { new CfApplicationWriter(appView, marker) - .write(options.getClassFileConsumer(), executor, timing); + .write(options.getClassFileConsumer(), executor, timing, inputApp); } else { - ApplicationWriter.create(appView, marker).write(executor, timing); + ApplicationWriter.create(appView, marker).write(executor, timing, inputApp); } }
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java index ca321a8..0ae18a8 100644 --- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java +++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -144,11 +144,11 @@ assert GlobalSyntheticsGeneratorVerifier.verifyExpectedClassesArePresent(appView); if (options.isGeneratingDex()) { ApplicationWriter.create(appView, options.getMarker()) - .write(executorService, timing); + .write(executorService, timing, app); } else { assert options.isGeneratingClassFiles(); new CfApplicationWriter(appView, options.getMarker()) - .write(options.getClassFileConsumer(), executorService, timing); + .write(options.getClassFileConsumer(), executorService, timing, app); } } catch (ExecutionException e) { throw unwrapExecutionException(e);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index 91cb423..b67a71c 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -64,7 +64,6 @@ import com.android.tools.r8.jar.CfApplicationWriter; import com.android.tools.r8.keepanno.annotations.KeepForApi; import com.android.tools.r8.keepanno.ast.KeepDeclaration; -import com.android.tools.r8.kotlin.KotlinInlineMethodMap; import com.android.tools.r8.kotlin.KotlinMetadataRewriter; import com.android.tools.r8.kotlin.KotlinMetadataUtils; import com.android.tools.r8.naming.IdentifierMinifier; @@ -217,7 +216,8 @@ command.getReporter(), () -> runInternal(app, options, executor)); } - static void writeApplication(AppView<?> appView, ExecutorService executorService, Timing timing) + static void writeApplication( + AppView<?> appView, AndroidApp inputApp, ExecutorService executorService, Timing timing) throws ExecutionException { InternalOptions options = appView.options(); InspectorImpl.runInspections(options.outputInspections, appView.appInfo().classes()); @@ -226,9 +226,9 @@ assert marker != null; if (options.isGeneratingClassFiles()) { new CfApplicationWriter(appView, marker) - .write(options.getClassFileConsumer(), executorService, timing); + .write(options.getClassFileConsumer(), executorService, timing, inputApp); } else { - ApplicationWriter.create(appView, marker).write(executorService, timing); + ApplicationWriter.create(appView, marker).write(executorService, timing, inputApp); } } catch (IOException e) { throw new RuntimeException("Cannot write application", e); @@ -428,9 +428,6 @@ ExceptionUtils.withFinishedResourceHandler(options.reporter, options.proguardSeedsConsumer); } - appView.setKotlinInlineMethodMap( - KotlinInlineMethodMap.createForR8(appViewWithLiveness, executorService, timing)); - if (options.isShrinking()) { // Mark dead proto extensions fields as neither being read nor written. This step must // run prior to the tree pruner. @@ -960,7 +957,7 @@ // Generate the resulting application resources. writeKeepDeclarationsToConfigurationConsumer(keepDeclarations); - writeApplication(appView, executorService, timing); + writeApplication(appView, inputApp, executorService, timing); ResourceWriter.legacyWriteResources(appView, dexFileContent); assert appView.getDontWarnConfiguration().validate(options);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java index 55d55f3..5f386f2 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -50,6 +50,7 @@ import com.android.tools.r8.profile.startup.StartupCompleteness; import com.android.tools.r8.profile.startup.profile.StartupProfile; import com.android.tools.r8.shaking.MainDexInfo; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.DexVersion; import com.android.tools.r8.utils.ExceptionUtils; @@ -260,6 +261,16 @@ } } + private boolean willComputeProguardMap() { + return options.hasMappingFileSupport(); + } + + /** Writer that never needs the input app to deal with mapping info for kotlin. */ + public void write(ExecutorService executorService, Timing timing) + throws IOException, ExecutionException { + assert !willComputeProguardMap(); + write(executorService, timing, null); + } protected void writeVirtualFiles( ExecutorService executorService, @@ -292,7 +303,7 @@ } } - public void write(ExecutorService executorService, Timing timing) + public void write(ExecutorService executorService, Timing timing, AndroidApp inputApp) throws IOException, ExecutionException { timing.begin("DexApplication.write"); @@ -338,12 +349,13 @@ // Now that the instruction offsets in each code object are fixed, compute the mapping file // content. - if (options.shouldOutputMappingFile()) { + if (willComputeProguardMap()) { // TODO(b/220999985): Refactor line number optimization to be per file and thread it above. DebugRepresentationPredicate representation = DebugRepresentation.fromFiles(virtualFiles, options); mapSupplierResult = - runAndWriteMap(appView, executorService, timing, originalSourceFiles, representation); + runAndWriteMap( + inputApp, appView, executorService, timing, originalSourceFiles, representation); } else if (options.convertPcBasedDebugInfoToNative) { convertPcBasedDebugInfoToNative(appView, executorService, timing); } @@ -421,7 +433,7 @@ DexItemFactory factory = appView.dexItemFactory(); currentMarker.ifPresent( marker -> { - if (options.shouldOutputMappingFile()) { + if (willComputeProguardMap()) { lazyDexStrings.add( new LazyDexString() { @@ -443,7 +455,7 @@ if (options.sourceFileProvider == null) { return OriginalSourceFiles.fromClasses(); } - if (!options.shouldOutputMappingFile()) { + if (!willComputeProguardMap()) { rewriteSourceFile(appView.appInfo().classes(), null); return OriginalSourceFiles.unreachable(); }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java index e30476f..7c09caf 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfo.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -212,10 +212,6 @@ .toSingleClassWithProgramOverLibrary(); } - public final DexProgramClass definitionForProgramTypeWithoutExistenceAssert(DexType type) { - return DexProgramClass.asProgramClassOrNull(definitionForWithoutExistenceAssert(type)); - } - public final boolean hasDefinitionForWithoutExistenceAssert(DexType type) { return definitionForWithoutExistenceAssert(type) != null; }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java index be83481..92d95f3 100644 --- a/src/main/java/com/android/tools/r8/graph/AppView.java +++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -39,7 +39,6 @@ import com.android.tools.r8.ir.optimize.library.LibraryMethodSideEffectModelCollection; import com.android.tools.r8.ir.optimize.outliner.bottomup.BottomUpOutliner; import com.android.tools.r8.ir.optimize.unsafe.SyntheticUnsafeClass; -import com.android.tools.r8.kotlin.KotlinInlineMethodMap; import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.naming.SeedMapper; import com.android.tools.r8.optimize.MemberRebindingIdentityLens; @@ -160,7 +159,6 @@ private Set<DexMethod> cfByteCodePassThrough = ImmutableSet.of(); private final Map<DexType, DexValueString> sourceDebugExtensions = new IdentityHashMap<>(); private final Map<DexType, String> sourceFileForPrunedTypes = new IdentityHashMap<>(); - private KotlinInlineMethodMap kotlinInlineMethodMap = null; // Types. private TypeElementFactory typeElementFactory = new TypeElementFactory(); @@ -521,14 +519,6 @@ return sourceDebugExtensions.get(clazz.type); } - public KotlinInlineMethodMap getKotlinInlineMethodMap() { - return kotlinInlineMethodMap; - } - - public void setKotlinInlineMethodMap(KotlinInlineMethodMap kotlinInlineMethodMap) { - this.kotlinInlineMethodMap = kotlinInlineMethodMap; - } - @Override public ClassResolutionResult contextIndependentDefinitionForWithResolutionResult(DexType type) { return appInfo().contextIndependentDefinitionForWithResolutionResult(type);
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java index c3acc1b..513b41b 100644 --- a/src/main/java/com/android/tools/r8/graph/DexApplication.java +++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -1,6 +1,9 @@ // Copyright (c) 2016, 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. +// Copyright (c) 2016, 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.graph; import com.android.tools.r8.DataResourceProvider;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java index 2ed7223..b7c90de 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -2695,7 +2695,7 @@ .setCode( methodSig -> { Code code = generateTemplateMethod(appView.dexItemFactory(), methodSig); - if (appView.options().shouldOutputMappingFile()) { + if (appView.options().hasMappingFileSupport()) { return code.getCodeAsInlining( methodSig, true, member, false, appView.dexItemFactory()); } @@ -2761,7 +2761,7 @@ methodSig -> { Code code = factoryHelper.create(appView.dexItemFactory(), methodSig); - if (appView.options().shouldOutputMappingFile()) { + if (appView.options().hasMappingFileSupport()) { return code.getCodeAsInlining( methodSig, true, member, false, appView.dexItemFactory()); }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java index 4851feb..35152b4 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -264,7 +264,7 @@ IntBox locals = new IntBox(); IntBox stack = new IntBox(); Position preamble = - !lambda.appView.options().shouldOutputMappingFile() + !lambda.appView.options().hasMappingFileSupport() ? null : SyntheticPosition.builder() .setMethod(mainMethod)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java index d75b651..2dad2de 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -299,7 +299,7 @@ CfDesugaringInfoImpl desugaringInfo = new CfDesugaringInfoImpl(cfCode.bytecodeSizeUpperBound()); Box<Position> currentPosition = new Box<>(); - boolean maintainPositionForInlineInfo = appView.options().shouldOutputMappingFile(); + boolean maintainPositionForInlineInfo = appView.options().hasMappingFileSupport(); if (maintainPositionForInlineInfo) { currentPosition.set(cfCode.getPreamblePosition()); if (!currentPosition.isSet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java index 08aa1dd..cb522b7 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
@@ -271,7 +271,7 @@ .setCode( methodSig -> { CfCode code = cfCodeGen.apply(methodSig); - if (appView.options().shouldOutputMappingFile()) { + if (appView.options().hasMappingFileSupport()) { return code.getCodeAsInlining( methodSig, true, context.getReference(), false, factory); }
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java index e6f4af4..b63774d 100644 --- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -18,6 +18,7 @@ import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils; import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapSupplierResult; import com.android.tools.r8.threading.TaskCollection; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.ExceptionUtils; import com.android.tools.r8.utils.InternalGlobalSyntheticsProgramConsumer.InternalGlobalSyntheticsCfConsumer; import com.android.tools.r8.utils.InternalOptions; @@ -51,8 +52,19 @@ public void write(ClassFileConsumer consumer, ExecutorService executorService, Timing timing) throws ExecutionException { + assert !options.hasMappingFileSupport(); + write(consumer, executorService, timing, null); + } + + public void write( + ClassFileConsumer consumer, + ExecutorService executorService, + Timing timing, + AndroidApp inputApp) + throws ExecutionException { timing.time( - "CfApplicationWriter.write", () -> writeApplication(consumer, executorService, timing)); + "CfApplicationWriter.write", + () -> writeApplication(inputApp, consumer, executorService, timing)); } private boolean includeMarker(Marker marker) { @@ -67,6 +79,7 @@ } private void writeApplication( + AndroidApp inputApp, ClassFileConsumer consumer, ExecutorService executorService, Timing timing) @@ -75,10 +88,11 @@ ProguardMapSupplierResult mapSupplierResult = ProguardMapSupplierResult.createEmpty(); try { - if (options.shouldOutputMappingFile()) { + if (options.hasMappingFileSupport()) { assert marker.isPresent(); mapSupplierResult = runAndWriteMap( + inputApp, appView, executorService, timing,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInlineMethodMap.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInlineMethodMap.java deleted file mode 100644 index 8fa6444..0000000 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinInlineMethodMap.java +++ /dev/null
@@ -1,257 +0,0 @@ -// Copyright (c) 2026, 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 com.android.tools.r8.graph.AppInfo; -import com.android.tools.r8.graph.AppView; -import com.android.tools.r8.graph.Code; -import com.android.tools.r8.graph.DexAnnotation; -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.DexValue; -import com.android.tools.r8.shaking.AppInfoWithLiveness; -import com.android.tools.r8.utils.DescriptorUtils; -import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.ThreadUtils; -import com.android.tools.r8.utils.internal.IntBox; -import com.android.tools.r8.utils.internal.collections.ImmutableDisjointIntRangeMap; -import com.android.tools.r8.utils.timing.Timing; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.function.Function; -import java.util.function.Predicate; - -/** - * Supports querying {@code (file, lineStart, lineEnd)} pairs for which method that refers to in the - * input. This is only collected for files which are pointed to by Kotlin inline annotations. It is - * also only collected if a mapping file will be generated. - * - * <p>Context: When Kotlin compiles code that inlines functions, it adds a {@code - * @kotlin.jvm.internal.SourceDebugExtension} (SMAP) attribute to the caller class mapping compiler - * line numbers back to source lines in the inlinee source file. However, SMAP data only contains - * the inlinee file path and line numbers, not the method signatures of the inlined functions. - * During mapping file generation (e.g. in {@link - * com.android.tools.r8.utils.positions.LineNumberOptimizer}), R8 reconstructs inlined frames by - * looking up which source method corresponded to each inlined line. - */ -public class KotlinInlineMethodMap { - - private static final KotlinInlineMethodMap EMPTY = - new KotlinInlineMethodMap(Collections.emptyMap()); - - private final Map<DexType, ImmutableDisjointIntRangeMap<DexMethod>> inlineMethodMap; - - private KotlinInlineMethodMap( - Map<DexType, ImmutableDisjointIntRangeMap<DexMethod>> inlineMethodMap) { - this.inlineMethodMap = inlineMethodMap; - } - - public static KotlinInlineMethodMap empty() { - return EMPTY; - } - - public DexMethod lookup(DexType type, int line) { - ImmutableDisjointIntRangeMap<DexMethod> classLines = inlineMethodMap.get(type); - return classLines != null ? classLines.get(line) : null; - } - - public static KotlinInlineMethodMap createForR8( - AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing) - throws ExecutionException { - InternalOptions options = appView.options(); - if (!options.shouldOutputMappingFile()) { - return null; - } - return timing.time( - "Extract kotlin inline method map", - () -> { - AppInfoWithLiveness appInfo = appView.appInfo(); - Function<DexType, DexProgramClass> classProvider = - type -> { - // Code might validly be inlined from non-present sources. - DexProgramClass clazz = - appInfo.definitionForProgramTypeWithoutExistenceAssert(type); - if (clazz != null) { - return clazz; - } - if (options.getR8PartialR8SubCompilationOptions() != null) { - return options.getR8PartialR8SubCompilationOptions().getDexingOutputClass(type); - } - return null; - }; - return createInternal( - appInfo.classes(), - appInfo::isLiveProgramClass, - classProvider, - options, - appView.dexItemFactory(), - executorService); - }); - } - - public static KotlinInlineMethodMap createForD8( - AppView<AppInfo> appView, ExecutorService executorService, Timing timing) - throws ExecutionException { - InternalOptions options = appView.options(); - if (!options.shouldOutputMappingFile()) { - return null; - } - return timing.time( - "Extract kotlin inline method map", - () -> - createInternal( - appView.appInfo().classes(), - clazz -> true, - appView::definitionForProgramType, - options, - appView.dexItemFactory(), - executorService)); - } - - private static KotlinInlineMethodMap createInternal( - Collection<DexProgramClass> classes, - Predicate<DexProgramClass> isLive, - Function<DexType, DexProgramClass> classProvider, - InternalOptions options, - DexItemFactory factory, - ExecutorService executorService) - throws ExecutionException { - Set<DexType> inlineeClasses = - findInlineReferences(classes, isLive, options, factory, executorService); - if (inlineeClasses.isEmpty()) { - return empty(); - } - var classLines = collectClassLines(inlineeClasses, classProvider, options, executorService); - return new KotlinInlineMethodMap(classLines); - } - - private static Map<DexType, ImmutableDisjointIntRangeMap<DexMethod>> collectClassLines( - Set<DexType> inlineeClasses, - Function<DexType, DexProgramClass> classProvider, - InternalOptions options, - ExecutorService executorService) - throws ExecutionException { - Collection<Entry<DexType, ImmutableDisjointIntRangeMap<DexMethod>>> classLines = - ThreadUtils.processItemsWithResultsThatMatches( - inlineeClasses, - type -> { - DexProgramClass clazz = classProvider.apply(type); - return clazz != null ? computeClassLines(clazz) : null; - }, - Objects::nonNull, - options.getThreadingModule(), - executorService); - return ImmutableMap.copyOf(classLines); - } - - private static Set<DexType> findInlineReferences( - Collection<DexProgramClass> classes, - Predicate<DexProgramClass> isLive, - InternalOptions options, - DexItemFactory factory, - ExecutorService executorService) - throws ExecutionException { - Set<DexType> inlineeClasses = ConcurrentHashMap.newKeySet(); - ThreadUtils.processItemsWithFilterPreprocessing( - classes, - clazz -> { - if (!isLive.test(clazz)) { - return null; - } - DexAnnotation annotation = - clazz.annotations().getFirstMatching(factory.annotationSourceDebugExtension); - if (annotation == null || annotation.annotation.elements.length == 0) { - return null; - } - DexValue value = annotation.annotation.elements[0].value; - if (!value.isDexValueString()) { - return null; - } - return value.asDexValueString(); - }, - (clazz, value) -> { - var smap = KotlinSourceDebugExtensionParser.parse(value); - if (smap == null) { - return; - } - smap.getInlineePositions() - .forEach( - inlineePosition -> { - String internalName = inlineePosition.getSource().getPath(); - String descriptor = - DescriptorUtils.getDescriptorFromClassInternalName(internalName); - DexType inlineeType = factory.createType(descriptor); - inlineeClasses.add(inlineeType); - }); - }, - options, - executorService); - return ImmutableSet.copyOf(inlineeClasses); - } - - private static class MethodInterval { - final int min; - final int max; - final DexMethod method; - - MethodInterval(int min, int max, DexMethod method) { - this.min = min; - this.max = max; - this.method = method; - } - } - - private static Entry<DexType, ImmutableDisjointIntRangeMap<DexMethod>> computeClassLines( - DexProgramClass clazz) { - var builder = ImmutableDisjointIntRangeMap.<DexMethod>builder(); - for (DexEncodedMethod method : clazz.methods()) { - MethodInterval interval = extractMethodInterval(method); - if (interval != null) { - try { - builder.add(interval.min, interval.max, interval.method); - } catch (IllegalArgumentException ignored) { - // Kotlin inline annotations are supported on a best-effort basis, ignore this entry. - } - } - } - ImmutableDisjointIntRangeMap<DexMethod> rangeMap = builder.build(); - return rangeMap.isEmpty() ? null : Map.entry(clazz.getType(), rangeMap); - } - - private static MethodInterval extractMethodInterval(DexEncodedMethod method) { - if (!method.hasCode()) { - return null; - } - Code code = method.getCode(); - if (code.isLazyCfCode()) { - // Unwrap LazyCfCode since it does not override forEachPosition. - code = code.asCfCode(); - } - IntBox min = new IntBox(Integer.MAX_VALUE); - IntBox max = new IntBox(Integer.MIN_VALUE); - code.forEachPosition( - method.getReference(), - method.isD8R8Synthesized(), - position -> { - int line = position.getLine(); - min.setMin(line); - max.setMax(line); - }); - return min.get() != Integer.MAX_VALUE - ? new MethodInterval(min.get(), max.get(), method.getReference()) - : null; - } -}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java index 398cc8b..e2efac1 100644 --- a/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java +++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParser.java
@@ -8,7 +8,7 @@ import com.android.tools.r8.naming.Range; import com.android.tools.r8.utils.internal.StringUtils; import com.android.tools.r8.utils.internal.ThrowingConsumer; -import com.android.tools.r8.utils.internal.collections.ImmutableDisjointIntRangeMap; +import com.android.tools.r8.utils.internal.collections.SegmentTree; import java.io.BufferedReader; import java.io.Closeable; import java.io.IOException; @@ -172,7 +172,7 @@ if (reader.isEOF()) { assert nextLine == null; return new KotlinSourceDebugExtensionParserResult( - inlineePositions.build(), calleePositions.build()); + inlineePositions.segmentTree, calleePositions.segmentTree); } if (!nextLine.equals(SMAP_SECTION_KOTLIN_DEBUG_START)) { return null; @@ -188,7 +188,7 @@ "Unexpected EOF when parsing SMAP debug entries"); } return new KotlinSourceDebugExtensionParserResult( - inlineePositions.build(), calleePositions.build()); + inlineePositions.segmentTree, calleePositions.segmentTree); } catch (IOException | KotlinSourceDebugExtensionParserException e) { return null; } @@ -296,23 +296,9 @@ throw new KotlinSourceDebugExtensionParserException( "Could not find file with index " + fileIndex); } - // https://jakarta.ee/specifications/debugging/2.0/jdsol-spec-2.0.pdf - // > Note that multiple LineInfo may map multiple input source lines to a single output source - // > line, when such a LineSection is being used to map output source lines to input source - // > lines, a first matching LineInfo rule applies. - // - // So entries should only add intervals where previous entries did not. - // `addUncoveredSubRanges` splits the interval into the sub-intervals not already covered. - builder.positions.addUncoveredSubRanges( - target, - target + (size - 1), - (subStart, subEnd) -> { - int delta = subStart - target; - int subSize = subEnd - subStart + 1; - int subOriginalStart = originalStart + delta; - Range subRange = new Range(subOriginalStart, subOriginalStart + (subSize - 1)); - return new Position(thisFileSource, subRange); - }); + Range range = new Range(originalStart, originalStart + (size - 1)); + Position position = new Position(thisFileSource, range); + builder.segmentTree.add(target, target + (size - 1), position); } catch (NumberFormatException e) { throw new KotlinSourceDebugExtensionParserException("Could not convert position to number"); } @@ -320,30 +306,25 @@ public static class KotlinSourceDebugExtensionParserResult { - private final ImmutableDisjointIntRangeMap<Position> inlineePositions; - private final ImmutableDisjointIntRangeMap<Position> calleePositions; + private final SegmentTree<Position> inlineePositions; + private final SegmentTree<Position> calleePositions; public KotlinSourceDebugExtensionParserResult( - ImmutableDisjointIntRangeMap<Position> inlineePositions, - ImmutableDisjointIntRangeMap<Position> calleePositions) { + SegmentTree<Position> inlineePositions, SegmentTree<Position> calleePositions) { this.inlineePositions = inlineePositions; this.calleePositions = calleePositions; } - public ImmutableDisjointIntRangeMap<Position> getInlineePositions() { + public SegmentTree<Position> getInlineePositions() { return inlineePositions; } - public ImmutableDisjointIntRangeMap.Entry<Position> lookupInlinedPositionEntry(int point) { - return inlineePositions.getEntry(point); + public Map.Entry<Integer, Position> lookupInlinedPosition(int point) { + return inlineePositions.findEntry(point); } - public Position lookupInlinedPosition(int point) { - return inlineePositions.get(point); - } - - public Position lookupCalleePosition(int point) { - return calleePositions.get(point); + public Map.Entry<Integer, Position> lookupCalleePosition(int point) { + return calleePositions.findEntry(point); } public int inlinePositionsCount() { @@ -353,13 +334,8 @@ public static class StratumBuilder { - private final ImmutableDisjointIntRangeMap.Builder<Position> positions = - ImmutableDisjointIntRangeMap.builder(); + SegmentTree<Position> segmentTree = new SegmentTree<>(false); Map<Integer, Source> files = new HashMap<>(); - - public ImmutableDisjointIntRangeMap<Position> build() { - return positions.build(); - } } public static class Source {
diff --git a/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java new file mode 100644 index 0000000..7a25330 --- /dev/null +++ b/src/main/java/com/android/tools/r8/utils/CfLineToMethodMapper.java
@@ -0,0 +1,208 @@ +// Copyright (c) 2019, 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.utils; + +import static com.android.tools.r8.utils.internal.FileUtils.CLASS_EXTENSION; + +import com.android.tools.r8.ProgramResource; +import com.android.tools.r8.ProgramResource.Kind; +import com.android.tools.r8.ProgramResourceProvider; +import com.android.tools.r8.ResourceException; +import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.KotlinSourceDebugExtensionParserResult; +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.ArchiveResourceProvider.ArchiveResourceProviderHelper; +import com.android.tools.r8.utils.internal.StreamUtils; +import com.android.tools.r8.utils.timing.Timing; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +public class CfLineToMethodMapper { + + private static final String NAME_DESCRIPTOR_SEPARATOR = ";;"; + + private final Map<String, Int2ObjectMap<String>> sourceMethodMapping; + + private CfLineToMethodMapper(Map<String, Int2ObjectMap<String>> sourceMethodMapping) { + this.sourceMethodMapping = sourceMethodMapping; + } + + public static CfLineToMethodMapper create( + AndroidApp inputApp, + KotlinSourceDebugExtensionCollection kotlinSourceDebugExtensions, + Timing timing) { + Set<ClassReference> kotlinClassesWithInlineFunctions = + getKotlinClassesWithInlineFunctions(kotlinSourceDebugExtensions, timing); + return new CfLineToMethodMapper( + readLineNumbersFromClassFiles(inputApp, kotlinClassesWithInlineFunctions, timing)); + } + + public String lookupNameAndDescriptor(String binaryName, int lineNumber) + throws ResourceException { + return sourceMethodMapping.getOrDefault(binaryName, Int2ObjectMaps.emptyMap()).get(lineNumber); + } + + private static Set<ClassReference> getKotlinClassesWithInlineFunctions( + KotlinSourceDebugExtensionCollection kotlinSourceDebugExtensions, Timing timing) { + try (Timing t0 = timing.begin("Get kotlin classes with inline functions")) { + Set<ClassReference> kotlinClassesWithInlineFunctions = new HashSet<>(); + for (KotlinSourceDebugExtensionParserResult kotlinSourceDebugExtension : + kotlinSourceDebugExtensions.values()) { + boolean first = true; + for (var inlineePosition : kotlinSourceDebugExtension.getInlineePositions().values()) { + if (first) { + // Skip. It is the current holder. + first = false; + } else if (inlineePosition != null) { + String binaryName = inlineePosition.getSource().getPath(); + kotlinClassesWithInlineFunctions.add(Reference.classFromBinaryName(binaryName)); + } + } + } + return kotlinClassesWithInlineFunctions; + } + } + + private static Map<String, Int2ObjectMap<String>> readLineNumbersFromClassFiles( + AndroidApp inputApp, Set<ClassReference> kotlinClassesWithInlineFunctions, Timing timing) { + try (Timing t0 = timing.begin("Read line numbers from class files")) { + Map<String, Int2ObjectMap<String>> sourceMethodMapping = new HashMap<>(); + try { + for (ProgramResourceProvider resourceProvider : inputApp.getProgramResourceProviders()) { + // TODO(b/391785584): Do not use the input providers here. + if (resourceProvider instanceof InternalProgramClassProvider) { + continue; + } + if (resourceProvider instanceof ArchiveResourceProvider) { + // Special case the ArchiveResourceProvider to minimize disk I/O. + ArchiveResourceProvider provider = (ArchiveResourceProvider) resourceProvider; + ArchiveResourceProviderHelper helper = new ArchiveResourceProviderHelper(provider); + timing.begin("Read archive"); + helper.accept( + programResource -> + processProgramResource( + programResource, + kotlinClassesWithInlineFunctions, + sourceMethodMapping, + timing), + entry -> { + String name = entry.getName(); + if (!name.endsWith(CLASS_EXTENSION)) { + return false; + } + String binaryName = name.substring(0, name.length() - CLASS_EXTENSION.length()); + return kotlinClassesWithInlineFunctions.contains( + Reference.classFromBinaryName(binaryName)); + }); + timing.end(); + } else { + ProgramResourceProviderUtils.forEachProgramResourceCompat( + resourceProvider, + programResource -> + processProgramResource( + programResource, + kotlinClassesWithInlineFunctions, + sourceMethodMapping, + timing)); + } + } + } catch (ResourceException e) { + throw new RuntimeException(e); + } + return sourceMethodMapping; + } + } + + private static void processProgramResource( + ProgramResource programResource, + Set<ClassReference> kotlinClassesWithInlineFunctions, + Map<String, Int2ObjectMap<String>> sourceMethodMapping, + Timing timing) { + if (programResource.getKind() != Kind.CF) { + return; + } + Set<String> classDescriptors = programResource.getClassDescriptors(); + if (classDescriptors == null || classDescriptors.size() != 1) { + return; + } + String classDescriptor = classDescriptors.iterator().next(); + ClassReference classReference = Reference.classFromDescriptor(classDescriptor); + if (!kotlinClassesWithInlineFunctions.contains(classReference)) { + return; + } + timing.begin("Parse class"); + Int2ObjectMap<String> currentLineNumberMapping = new Int2ObjectOpenHashMap<>(); + ClassVisitor.visit(programResource, currentLineNumberMapping); + sourceMethodMapping.put(classReference.getBinaryName(), currentLineNumberMapping); + timing.end(); + } + + public static String getName(String nameAndDescriptor) { + int index = nameAndDescriptor.indexOf(NAME_DESCRIPTOR_SEPARATOR); + assert index > 0; + return nameAndDescriptor.substring(0, index); + } + + public static String getDescriptor(String nameAndDescriptor) { + int index = nameAndDescriptor.indexOf(NAME_DESCRIPTOR_SEPARATOR); + assert index > 0; + return nameAndDescriptor.substring(index + NAME_DESCRIPTOR_SEPARATOR.length()); + } + + private static class ClassVisitor extends org.objectweb.asm.ClassVisitor { + + private final Int2ObjectMap<String> lineNumberMapping; + + private ClassVisitor(Int2ObjectMap<String> lineNumberMapping) { + super(InternalOptions.ASM_VERSION); + this.lineNumberMapping = lineNumberMapping; + } + + public static void visit( + ProgramResource programResource, Int2ObjectMap<String> lineNumberMapping) { + byte[] bytes; + try { + bytes = StreamUtils.streamToByteArrayClose(programResource.getByteStream()); + } catch (ResourceException | IOException e) { + // Intentionally left empty because the addition of inline info for kotlin inline + // functions is a best effort. + return; + } + new ClassReader(bytes).accept(new ClassVisitor(lineNumberMapping), ClassReader.SKIP_FRAMES); + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + return new MethodLineVisitor( + name + NAME_DESCRIPTOR_SEPARATOR + descriptor, lineNumberMapping); + } + } + + private static class MethodLineVisitor extends MethodVisitor { + + private final String nameAndDescriptor; + private final Map<Integer, String> lineMethodMapping; + + private MethodLineVisitor(String nameAndDescriptor, Map<Integer, String> lineMethodMapping) { + super(InternalOptions.ASM_VERSION); + this.nameAndDescriptor = nameAndDescriptor; + this.lineMethodMapping = lineMethodMapping; + } + + @Override + public void visitLineNumber(int line, Label start) { + lineMethodMapping.put(line, nameAndDescriptor); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index 870c77e..aace626 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1489,7 +1489,7 @@ // If non null it must be and passed to the consumer. public InternalMapConsumer mapConsumer = null; - public boolean shouldOutputMappingFile() { + public boolean hasMappingFileSupport() { return mapConsumer != null; }
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java index 3250b81..6da517c 100644 --- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -28,7 +28,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Predicate; public class ThreadUtils { @@ -150,28 +149,6 @@ (i, item) -> "Task " + i); } - /** Does not process items where preprocessing returns null. */ - public static <T, I, E extends Exception> void processItemsWithFilterPreprocessing( - Iterable<T> items, - Function<T, I> preprocessing, - ThrowingBiConsumer<T, I, E> consumer, - InternalOptions options, - ExecutorService executorService) - throws ExecutionException { - var tasks = new TaskCollection<>(options.getThreadingModule(), executorService); - for (T item : items) { - var info = preprocessing.apply(item); - if (info != null) { - tasks.submitUnchecked( - () -> { - consumer.accept(item, info); - return null; - }); - } - } - tasks.await(); - } - public static <T, E extends Exception> void processItemsThatMatches( Collection<T> items, Predicate<T> itemPredicate,
diff --git a/src/main/java/com/android/tools/r8/utils/positions/AppPositionRemapper.java b/src/main/java/com/android/tools/r8/utils/positions/AppPositionRemapper.java index cf6aed0..c46e0ac 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/AppPositionRemapper.java +++ b/src/main/java/com/android/tools/r8/utils/positions/AppPositionRemapper.java
@@ -5,6 +5,8 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.CfLineToMethodMapper; import com.android.tools.r8.utils.KotlinSourceDebugExtensionCollection; import com.android.tools.r8.utils.positions.ClassPositionRemapper.IdentityPositionRemapper; import com.android.tools.r8.utils.positions.ClassPositionRemapper.KotlinInlineFunctionAppPositionRemapper; @@ -15,7 +17,7 @@ ClassPositionRemapper createClassPositionRemapper(DexProgramClass clazz); - static AppPositionRemapper create(AppView<?> appView, Timing timing) { + static AppPositionRemapper create(AppView<?> appView, AndroidApp inputApp, Timing timing) { boolean identityMapping = appView.options().lineNumberOptimization.isOff(); AppPositionRemapper positionRemapper = identityMapping @@ -26,7 +28,11 @@ // source debug extension annotation. Instantiate the kotlin remapper on top of the original // remapper to allow for remapping original positions to kotlin inline positions. var kotlinSourceDebugExtensions = KotlinSourceDebugExtensionCollection.create(appView, timing); + // For finding methods in Kotlin files based on SourceDebugExtensions, we use a line method map. + // We create it here to ensure it is only reading class files once. + var cfLineToMethodMapper = + CfLineToMethodMapper.create(inputApp, kotlinSourceDebugExtensions, timing); return new KotlinInlineFunctionAppPositionRemapper( - appView, positionRemapper, kotlinSourceDebugExtensions); + appView, positionRemapper, cfLineToMethodMapper, kotlinSourceDebugExtensions); } }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java b/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java index 5ae3009..b9ca045 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java +++ b/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java
@@ -3,19 +3,23 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.utils.positions; +import com.android.tools.r8.ResourceException; import com.android.tools.r8.graph.AppView; 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.DexString; import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.Position.SourcePosition; -import com.android.tools.r8.kotlin.KotlinInlineMethodMap; +import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser; import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.KotlinSourceDebugExtensionParserResult; +import com.android.tools.r8.utils.CfLineToMethodMapper; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.KotlinSourceDebugExtensionCollection; import com.android.tools.r8.utils.internal.collections.Pair; +import java.util.Map; +import java.util.Map.Entry; // PositionRemapper is a stateful function which takes a position (represented by a // DexDebugPositionState) and returns a remapped Position. @@ -135,17 +139,17 @@ private final AppPositionRemapper baseRemapper; private final DexItemFactory factory; - private final KotlinInlineMethodMap kotlinInlineMethodMap; + private final CfLineToMethodMapper lineToMethodMapper; private final KotlinSourceDebugExtensionCollection kotlinSourceDebugExtensions; KotlinInlineFunctionAppPositionRemapper( AppView<?> appView, AppPositionRemapper baseRemapper, + CfLineToMethodMapper lineToMethodMapper, KotlinSourceDebugExtensionCollection kotlinSourceDebugExtensions) { this.baseRemapper = baseRemapper; this.factory = appView.dexItemFactory(); - this.kotlinInlineMethodMap = appView.getKotlinInlineMethodMap(); - assert this.kotlinInlineMethodMap != null; + this.lineToMethodMapper = lineToMethodMapper; this.kotlinSourceDebugExtensions = kotlinSourceDebugExtensions; } @@ -191,36 +195,58 @@ @Override public Pair<Position, Position> createRemappedPosition(Position position) { int line = position.getLine(); - var inlinedPosition = kotlinSourceDebugExtension.lookupInlinedPositionEntry(line); + Map.Entry<Integer, KotlinSourceDebugExtensionParser.Position> inlinedPosition = + kotlinSourceDebugExtension.lookupInlinedPosition(line); if (inlinedPosition == null) { return baseRemapper.createRemappedPosition(position); } - int inlineeLineDelta = line - inlinedPosition.start; - int originalInlineeLine = inlinedPosition.value.getRange().from + inlineeLineDelta; - String binaryName = inlinedPosition.value.getSource().getPath(); - String clazzDescriptor = DescriptorUtils.getDescriptorFromClassInternalName(binaryName); - DexType inlineeType = factory.createType(clazzDescriptor); - DexMethod inlinee = kotlinInlineMethodMap.lookup(inlineeType, originalInlineeLine); - if (inlinee == null) { - return baseRemapper.createRemappedPosition(position); - } - if (!inlinee.equals(position.getMethod())) { - // We have an inline from a different method than the current position. - var calleePosition = kotlinSourceDebugExtension.lookupCalleePosition(line); - if (calleePosition != null) { - // Take the first line as the callee position - int calleeLine = Math.max(0, calleePosition.getRange().from); - position = position.builderWithCopy().setLine(calleeLine).build(); + int inlineeLineDelta = line - inlinedPosition.getKey(); + int originalInlineeLine = inlinedPosition.getValue().getRange().from + inlineeLineDelta; + try { + String binaryName = inlinedPosition.getValue().getSource().getPath(); + String nameAndDescriptor = + lineToMethodMapper.lookupNameAndDescriptor(binaryName, originalInlineeLine); + if (nameAndDescriptor == null) { + return baseRemapper.createRemappedPosition(position); } - return baseRemapper.createRemappedPosition( - SourcePosition.builder() - .setLine(originalInlineeLine) - .setMethod(inlinee) - .setCallerPosition(position) - .build()); + String clazzDescriptor = DescriptorUtils.getDescriptorFromClassInternalName(binaryName); + String methodName = CfLineToMethodMapper.getName(nameAndDescriptor); + String methodDescriptor = CfLineToMethodMapper.getDescriptor(nameAndDescriptor); + String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(methodDescriptor); + String[] argumentDescriptors = + DescriptorUtils.getArgumentTypeDescriptors(methodDescriptor); + DexString[] argumentDexStringDescriptors = new DexString[argumentDescriptors.length]; + for (int i = 0; i < argumentDescriptors.length; i++) { + argumentDexStringDescriptors[i] = factory.createString(argumentDescriptors[i]); + } + DexMethod inlinee = + factory.createMethod( + factory.createString(clazzDescriptor), + factory.createString(methodName), + factory.createString(returnTypeDescriptor), + argumentDexStringDescriptors); + if (!inlinee.equals(position.getMethod())) { + // We have an inline from a different method than the current position. + Entry<Integer, KotlinSourceDebugExtensionParser.Position> calleePosition = + kotlinSourceDebugExtension.lookupCalleePosition(line); + if (calleePosition != null) { + // Take the first line as the callee position + int calleeLine = Math.max(0, calleePosition.getValue().getRange().from); + position = position.builderWithCopy().setLine(calleeLine).build(); + } + return baseRemapper.createRemappedPosition( + SourcePosition.builder() + .setLine(originalInlineeLine) + .setMethod(inlinee) + .setCallerPosition(position) + .build()); + } + // This is the same position, so we should really not mark this as an inline position. + // Fall through to the default case. + } catch (ResourceException ignored) { + // Intentionally left empty. Remapping of kotlin functions utility is a best effort + // mapping. } - // This is the same position, so we should really not mark this as an inline position. - // Fall through to the default case. return baseRemapper.createRemappedPosition(position); }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java index f0f83e5..eefbd73 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java +++ b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
@@ -24,6 +24,7 @@ import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation; import com.android.tools.r8.naming.mappinginformation.ResidualSignatureMappingInformation; import com.android.tools.r8.shaking.KeepInfoCollection; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.OriginalSourceFiles; import com.android.tools.r8.utils.ThreadUtils; @@ -44,26 +45,31 @@ public class LineNumberOptimizer { public static ProguardMapSupplierResult runAndWriteMap( + AndroidApp inputApp, AppView<?> appView, ExecutorService executorService, Timing timing, OriginalSourceFiles originalSourceFiles, DebugRepresentationPredicate representation) throws ExecutionException { - return new LineNumberOptimizer(appView, executorService, originalSourceFiles, representation) + return new LineNumberOptimizer( + inputApp, appView, executorService, originalSourceFiles, representation) .runAndWriteMap(timing); } + private final AndroidApp inputApp; private final AppView<?> appView; private final ExecutorService executorService; private final OriginalSourceFiles originalSourceFiles; private final DebugRepresentationPredicate representation; private LineNumberOptimizer( + AndroidApp inputApp, AppView<?> appView, ExecutorService executorService, OriginalSourceFiles originalSourceFiles, DebugRepresentationPredicate representation) { + this.inputApp = inputApp; this.appView = appView; this.executorService = executorService; this.originalSourceFiles = originalSourceFiles; @@ -72,8 +78,7 @@ @SuppressWarnings("InconsistentOverloads") private ProguardMapSupplierResult runAndWriteMap(Timing timing) throws ExecutionException { - assert appView.options().shouldOutputMappingFile(); - assert appView.getKotlinInlineMethodMap() != null; + assert appView.options().hasMappingFileSupport(); if (shouldWriteOriginalMappingFile()) { return writeOriginalMappingFile(timing); @@ -160,7 +165,7 @@ PositionToMappedRangeMapper positionToMappedRangeMapper, Timing timing) throws ExecutionException { timing.begin("Process classes"); - AppPositionRemapper positionRemapper = AppPositionRemapper.create(appView, timing); + AppPositionRemapper positionRemapper = AppPositionRemapper.create(appView, inputApp, timing); Deque<ClassPositionMapping> worklist = new ConcurrentLinkedDeque<>(); ThreadUtils.processItemsThatMatches( appView.appInfo().classes(),
diff --git a/src/test/java/com/android/tools/r8/utils/ImmutableDisjointIntRangeMapTest.java b/src/test/java/com/android/tools/r8/utils/ImmutableDisjointIntRangeMapTest.java deleted file mode 100644 index a3a6819..0000000 --- a/src/test/java/com/android/tools/r8/utils/ImmutableDisjointIntRangeMapTest.java +++ /dev/null
@@ -1,89 +0,0 @@ -// Copyright (c) 2026, 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.utils; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import com.android.tools.r8.TestBase; -import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; -import com.android.tools.r8.utils.internal.collections.ImmutableDisjointIntRangeMap; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -@RunWith(Parameterized.class) -public class ImmutableDisjointIntRangeMapTest extends TestBase { - - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return getTestParameters().withNoneRuntime().build(); - } - - public ImmutableDisjointIntRangeMapTest(TestParameters parameters) { - parameters.assertNoneRuntime(); - } - - @Test - public void testSingleRange() { - ImmutableDisjointIntRangeMap<String> map = - ImmutableDisjointIntRangeMap.<String>builder().add(10, 20, "A").build(); - assertEquals(1, map.size()); - - assertNull(map.getEntry(9)); - assertEntry(map.getEntry(10), 10, "A"); - assertEntry(map.getEntry(15), 10, "A"); - assertEntry(map.getEntry(20), 10, "A"); - assertNull(map.getEntry(21)); - } - - @Test - public void testMultipleRangesOutOfOrder() { - ImmutableDisjointIntRangeMap<Character> map = - ImmutableDisjointIntRangeMap.<Character>builder() - .add(31, 100, 'a') - .add(1, 10, 'b') - .add(20, 30, 'c') - .build(); - assertEquals(3, map.size()); - - assertEntry(map.getEntry(5), 1, 'b'); - assertEntry(map.getEntry(25), 20, 'c'); - assertEntry(map.getEntry(50), 31, 'a'); - } - - @Test - public void testAddUncovered() { - ImmutableDisjointIntRangeMap<String> map = - ImmutableDisjointIntRangeMap.<String>builder() - .add(10, 20, "A") - .addUncoveredSubRanges(10, 20, "B") - .addUncoveredSubRanges(12, 18, "C") - .addUncoveredSubRanges(5, 12, "E") - .addUncoveredSubRanges(18, 25, (start, end) -> "D_" + start + "_" + end) - .addUncoveredSubRanges(8, 22, "F") - .addUncoveredSubRanges(30, 40, "G") - .build(); - - assertEquals(4, map.size()); - assertNull(map.getEntry(4)); - assertEntry(map.getEntry(7), 5, "E"); - assertEntry(map.getEntry(15), 10, "A"); - assertEntry(map.getEntry(23), 21, "D_21_25"); - assertNull(map.getEntry(26)); - assertEntry(map.getEntry(35), 30, "G"); - assertNull(map.getEntry(41)); - } - - private <V> void assertEntry( - ImmutableDisjointIntRangeMap.Entry<V> entry, int expectedStart, V expectedValue) { - assertNotNull(entry); - assertEquals(expectedStart, entry.start); - assertEquals(expectedValue, entry.value); - } -}
diff --git a/src/test/java8/kotlin/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java b/src/test/java8/kotlin/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java index 162f31b..4aff93a 100644 --- a/src/test/java8/kotlin/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java +++ b/src/test/java8/kotlin/com/android/tools/r8/kotlin/KotlinSourceDebugExtensionParserTest.java
@@ -55,8 +55,8 @@ KotlinSourceDebugExtensionParser.parse(annotationData); assertNotNull(result); assertEquals(1, result.inlinePositionsCount()); - assertEquals(1, result.lookupInlinedPositionEntry(1).start); - Position position = result.lookupInlinedPosition(1); + assertEquals(1, (int) result.lookupInlinedPosition(1).getKey()); + Position position = result.lookupInlinedPosition(1).getValue(); assertEquals("EnumSwitch.kt", position.getSource().getFileName()); assertEquals("enumswitch/EnumSwitchKt", position.getSource().getPath()); assertEquals(1, position.getRange().from); @@ -97,20 +97,20 @@ KotlinSourceDebugExtensionParser.parse(annotationData); assertNotNull(result); assertEquals(3, result.inlinePositionsCount()); - assertEquals(1, result.lookupInlinedPositionEntry(1).start); - assertEquals(23, result.lookupInlinedPositionEntry(23).start); - assertEquals(24, result.lookupInlinedPositionEntry(24).start); + assertEquals(1, (int) result.lookupInlinedPosition(1).getKey()); + assertEquals(23, (int) result.lookupInlinedPosition(23).getKey()); + assertEquals(24, (int) result.lookupInlinedPosition(24).getKey()); // Check that files are correctly parsed. - Position pos1 = result.lookupInlinedPosition(1); + Position pos1 = result.lookupInlinedPosition(1).getValue(); assertEquals("Main.kt", pos1.getSource().getFileName()); assertEquals("retrace/MainKt", pos1.getSource().getPath()); - Position pos2 = result.lookupInlinedPosition(23); + Position pos2 = result.lookupInlinedPosition(23).getValue(); assertEquals("InlineFunction.kt", pos2.getSource().getFileName()); assertEquals("retrace/InlineFunctionKt", pos2.getSource().getPath()); - Position pos3 = result.lookupInlinedPosition(24); + Position pos3 = result.lookupInlinedPosition(24).getValue(); assertEquals("InlineFunction.kt", pos3.getSource().getFileName()); assertEquals("retrace/InlineFunction", pos3.getSource().getPath()); @@ -318,8 +318,8 @@ KotlinSourceDebugExtensionParserResult parsedResult = KotlinSourceDebugExtensionParser.parse(annotationData); assertNotNull(parsedResult); - assertEquals(24, parsedResult.lookupInlinedPositionEntry(25).start); - Position value = parsedResult.lookupInlinedPosition(25); + assertEquals(24, (int) parsedResult.lookupInlinedPosition(25).getKey()); + Position value = parsedResult.lookupInlinedPosition(25).getValue(); assertEquals(12, value.getRange().from); assertEquals(13, value.getRange().to); }
diff --git a/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/InlineTest.kt b/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/InlineTest.kt deleted file mode 100644 index 34b6e62..0000000 --- a/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/InlineTest.kt +++ /dev/null
@@ -1,20 +0,0 @@ -// Copyright (c) 2026, 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.partial.kotlin - -class Inlinee { - inline fun inlineeMethod(): String { - return "inlined!" - } -} - -class Caller { - fun call(): String { - return Inlinee().inlineeMethod() - } -} - -fun main() { - println(Caller().call()) -}
diff --git a/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/PartialCompilationKotlinInlineFunctionTest.java b/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/PartialCompilationKotlinInlineFunctionTest.java deleted file mode 100644 index 2bf1833..0000000 --- a/src/test/java8/kotlin/com/android/tools/r8/partial/kotlin/PartialCompilationKotlinInlineFunctionTest.java +++ /dev/null
@@ -1,85 +0,0 @@ -// Copyright (c) 2026, 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.partial.kotlin; - -import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static org.hamcrest.MatcherAssert.assertThat; - -import com.android.tools.r8.KotlinCompileMemoizer; -import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler; -import com.android.tools.r8.KotlinTestBase; -import com.android.tools.r8.KotlinTestParameters; -import com.android.tools.r8.TestParameters; -import com.android.tools.r8.ToolHelper; -import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.CodeInspector; -import com.android.tools.r8.utils.codeinspector.MethodSubject; -import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -@RunWith(Parameterized.class) -public class PartialCompilationKotlinInlineFunctionTest extends KotlinTestBase { - - private static final String PKG = - PartialCompilationKotlinInlineFunctionTest.class.getPackage().getName(); - private static final String CALLER_NAME = PKG + ".Caller"; - private static final String MAIN_NAME = PKG + ".InlineTestKt"; - - private final TestParameters parameters; - - @Parameters(name = "{0}, kotlin: {1}") - public static List<Object[]> data() { - return buildParameters( - getTestParameters().withAllRuntimesAndApiLevels().build(), - getKotlinTestParameters().withAllCompilersAndLambdaGenerations().build()); - } - - public PartialCompilationKotlinInlineFunctionTest( - TestParameters parameters, KotlinTestParameters kotlinParameters) { - super(kotlinParameters); - this.parameters = parameters; - } - - @Test - public void test() throws Exception { - parameters.assumeCanUseR8Partial(); - KotlinCompiler kotlinc = kotlinParameters.getCompiler(); - testForR8Partial(parameters.getBackend()) - .addProgramFiles(compiledJars.getForConfiguration(kotlinParameters)) - .addProgramFiles(kotlinc.getKotlinStdlibJar()) - .addProgramFiles(kotlinc.getKotlinAnnotationJar()) - .addLibraryFiles(ToolHelper.getMostRecentAndroidJar()) - .addKeepMainRule(MAIN_NAME) - .addKeepClassAndMembersRules(CALLER_NAME) - .setMinApi(parameters) - .setR8PartialConfiguration( - partialConfigurationBuilder -> { - // Put Caller and Main in R8 partition; Inlinee is in D8 partition. - partialConfigurationBuilder - .addJavaTypeIncludePattern(CALLER_NAME) - .addJavaTypeIncludePattern(MAIN_NAME); - }) - .allowDiagnosticMessages() - .allowUnusedDontWarnPatterns() - .compile() - .inspect(this::inspect) - .run(parameters.getRuntime(), MAIN_NAME) - .assertSuccessWithOutputLines("inlined!"); - } - - private void inspect(CodeInspector inspector) { - ClassSubject callerClass = inspector.clazz(CALLER_NAME); - assertThat(callerClass, isPresent()); - MethodSubject callMethod = callerClass.uniqueMethodWithOriginalName("call"); - assertThat(callMethod, isPresent()); - } - - private static final KotlinCompileMemoizer compiledJars = - getCompileMemoizer( - getKotlinSourceFileFromResources( - PartialCompilationKotlinInlineFunctionTest.class, "InlineTest")); -}
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java index 6c45ff3..78c2262 100644 --- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -2970,7 +2970,7 @@ public static void writeApplication(AppView<?> appView, Timing timing) throws ExecutionException { appView.options().tool = Tool.R8; - R8.writeApplication(appView, Executors.newSingleThreadExecutor(), timing); + R8.writeApplication(appView, null, Executors.newSingleThreadExecutor(), timing); } public static void disassemble(AndroidApp app, PrintStream ps)
diff --git a/src/utils/java/com/android/tools/r8/utils/internal/collections/ImmutableDisjointIntRangeMap.java b/src/utils/java/com/android/tools/r8/utils/internal/collections/ImmutableDisjointIntRangeMap.java deleted file mode 100644 index 64c5d23..0000000 --- a/src/utils/java/com/android/tools/r8/utils/internal/collections/ImmutableDisjointIntRangeMap.java +++ /dev/null
@@ -1,240 +0,0 @@ -// Copyright (c) 2026, 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.utils.internal.collections; - -import java.util.Arrays; -import java.util.Map; -import java.util.TreeMap; -import java.util.function.BiFunction; -import java.util.function.Consumer; - -/** - * A specialized version of {@link SegmentTree} with tiny size. - * - * <p>All ranges are inclusive on both bounds. - * - * <p>Use {@link #builder()} to construct. - */ -public class ImmutableDisjointIntRangeMap<V> { - - private static final ImmutableDisjointIntRangeMap<Object> EMPTY = - new ImmutableDisjointIntRangeMap<>(new int[0], new int[0], new Object[0]); - - private final int[] starts; - private final int[] ends; - private final V[] values; - - private ImmutableDisjointIntRangeMap(int[] starts, int[] ends, V[] values) { - this.starts = starts; - this.ends = ends; - this.values = values; - } - - @SuppressWarnings("unchecked") - public static <V> ImmutableDisjointIntRangeMap<V> empty() { - return (ImmutableDisjointIntRangeMap<V>) EMPTY; - } - - public static <V> Builder<V> builder() { - return new Builder<>(); - } - - public int size() { - return starts.length; - } - - public boolean isEmpty() { - return starts.length == 0; - } - - public static class Entry<V> { - public final int start; - public final V value; - - private Entry(int start, V value) { - this.start = start; - this.value = value; - } - } - - public Entry<V> getEntry(int point) { - int index = findRangeIndex(point); - return index >= 0 ? new Entry<>(starts[index], values[index]) : null; - } - - public V get(int point) { - int index = findRangeIndex(point); - return index >= 0 ? values[index] : null; - } - - /** Returns the entry index of the given point, or -1. */ - private int findRangeIndex(int point) { - int index = Arrays.binarySearch(starts, point); - if (index >= 0) { - return index; - } - // Arrays.binarySearch returns, index = -(insertion point) - 1. - // So, insertion point = -(index + 1), simplified to -index - 1 - // The greatest element less than point is one earlier, so: - int candidate = -index - 2; - if (candidate >= 0 && point <= ends[candidate]) { - return candidate; - } - return -1; - } - - public void forEach(Consumer<? super V> consumer) { - for (V value : values) { - consumer.accept(value); - } - } - - public static class Builder<V> { - - private static class RangeEntry<V> { - final int end; - final V value; - - RangeEntry(int end, V value) { - this.end = end; - this.value = value; - } - } - - private final TreeMap<Integer, RangeEntry<V>> ranges = new TreeMap<>(); - - /** - * @param start start of the range (inclusive) - * @param end end of the range (inclusive) - * @param value value of the range (non-null) - * @throws OverlappingRangeException if this entry overlaps with existing range(s) - * @throws IllegalArgumentException if the range is invalid - */ - public Builder<V> add(int start, int end, V value) { - checkValidRange(start, end); - checkNonOverlapping(start, end); - addWithoutRangeChecks(start, end, value); - return this; - } - - private void addWithoutRangeChecks(int start, int end, V value) { - if (value == null) { - throw new IllegalArgumentException( - "Cannot insert null values [" + start + ", " + end + "]"); - } - ranges.put(start, new RangeEntry<>(end, value)); - } - - /** See {@link #addUncoveredSubRanges(int, int, BiFunction)}. */ - public Builder<V> addUncoveredSubRanges(int start, int end, V value) { - return addUncoveredSubRanges(start, end, (subStart, subEnd) -> value); - } - - /** - * Adds the portions of the given range that are not already covered by existing ranges, - * splitting the range into disjoint sub-intervals if necessary. Portions of the range that - * overlap with already added ranges are skipped. - */ - public Builder<V> addUncoveredSubRanges( - int start, int end, BiFunction<Integer, Integer, V> valueFactory) { - checkValidRange(start, end); - int current = start; - Map.Entry<Integer, RangeEntry<V>> floor = ranges.floorEntry(start); - if (floor != null && floor.getValue().end >= current) { - if (floor.getValue().end == Integer.MAX_VALUE) { - return this; - } - current = floor.getValue().end + 1; - } - while (current <= end) { - Map.Entry<Integer, RangeEntry<V>> next = ranges.ceilingEntry(current); - if (next == null || next.getKey() > end) { - V value = valueFactory.apply(current, end); - addWithoutRangeChecks(current, end, value); - break; - } - if (next.getKey() > current) { - int gapEnd = next.getKey() - 1; - V value = valueFactory.apply(current, gapEnd); - addWithoutRangeChecks(current, gapEnd, value); - } - if (next.getValue().end == Integer.MAX_VALUE) { - break; - } - var nextCurrent = next.getValue().end + 1; - assert nextCurrent > current : "stepped from " + current + " to " + nextCurrent; - current = nextCurrent; - } - return this; - } - - public ImmutableDisjointIntRangeMap<V> build() { - if (ranges.isEmpty()) { - return empty(); - } - int size = ranges.size(); - int[] starts = new int[size]; - int[] ends = new int[size]; - @SuppressWarnings("unchecked") - V[] values = (V[]) new Object[size]; - int i = 0; - for (Map.Entry<Integer, RangeEntry<V>> entry : ranges.entrySet()) { - starts[i] = entry.getKey(); - ends[i] = entry.getValue().end; - values[i] = entry.getValue().value; - i++; - } - return new ImmutableDisjointIntRangeMap<>(starts, ends, values); - } - - public static class OverlappingRangeException extends IllegalArgumentException { - public OverlappingRangeException(String s) { - super(s); - } - } - - /** - * @throws IllegalArgumentException if invalid. - */ - private static void checkValidRange(int start, int end) { - if (start > end) { - throw new IllegalArgumentException("Invalid range: " + start + " > " + end); - } - } - - /** - * @throws OverlappingRangeException if overlapping. - */ - private void checkNonOverlapping(int start, int end) { - Map.Entry<Integer, RangeEntry<V>> floorEntry = ranges.floorEntry(start); - if (floorEntry != null && floorEntry.getValue().end >= start) { - throw new OverlappingRangeException( - "Range [" - + start - + ", " - + end - + "] overlaps with existing range [" - + floorEntry.getKey() - + ", " - + floorEntry.getValue().end - + "]"); - } - - Map.Entry<Integer, RangeEntry<V>> ceilingEntry = ranges.ceilingEntry(start); - if (ceilingEntry != null && end >= ceilingEntry.getKey()) { - throw new OverlappingRangeException( - "Range [" - + start - + ", " - + end - + "] overlaps with existing range [" - + ceilingEntry.getKey() - + ", " - + ceilingEntry.getValue().end - + "]"); - } - } - } -}