| // Copyright (c) 2021, 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.contexts; |
| |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.google.common.hash.Hasher; |
| import com.google.common.hash.Hashing; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.function.Consumer; |
| |
| public class CompilationContext { |
| |
| // Internal contract to compute a unique suffix for synthetics. |
| private abstract static class ContextDescriptorProvider { |
| |
| // Method to construct a fully qualified description of the context. |
| // This is used to ensure that all contexts are unique during compilation. |
| abstract StringBuilder buildContextDescriptorForTesting(StringBuilder builder); |
| |
| // Build a suffix to append to synthetic definitions. |
| abstract StringBuilder buildSyntheticSuffix(StringBuilder builder); |
| } |
| |
| /** |
| * Create the initial compilation context. |
| * |
| * <p>This context should be a singleton for a given compilation allocated by AppView. |
| */ |
| public static CompilationContext createInitialContext(InternalOptions options) { |
| return new CompilationContext(options); |
| } |
| |
| private final Consumer<String> testingConsumer; |
| private final Map<String, String> seenSetForTesting = new ConcurrentHashMap<>(); |
| private int nextProcessorId = 0; |
| |
| private CompilationContext(InternalOptions options) { |
| testingConsumer = options.testing.processingContextsConsumer; |
| } |
| |
| private boolean verifyContext(ContextDescriptorProvider context) { |
| String descriptor = context.buildContextDescriptorForTesting(new StringBuilder()).toString(); |
| String suffix = context.buildSyntheticSuffix(new StringBuilder()).toString(); |
| assert descriptor.endsWith(suffix); |
| if (testingConsumer != null) { |
| testingConsumer.accept(descriptor); |
| } |
| assert seenSetForTesting.put(descriptor, descriptor) == null |
| : "Duplicated use of context descriptor: " + descriptor; |
| return true; |
| } |
| |
| /** |
| * Creates the context for a "processor". |
| * |
| * <p>A "processor" is just a compilation task but which is deterministically ordered as part of |
| * the full compilation pipeline. Thus, this method should only be called on the main-thread |
| * ensuring that the assigned ids are deterministic. The id itself has not particular meaning. |
| */ |
| public ProcessorContext createProcessorContext() { |
| ProcessorContext processorContext = new ProcessorContext(this, nextProcessorId++); |
| assert verifyContext(processorContext); |
| return processorContext; |
| } |
| |
| public static class ProcessorContext extends ContextDescriptorProvider { |
| private final CompilationContext parent; |
| private final int processorId; |
| |
| private ProcessorContext(CompilationContext parent, int processorId) { |
| this.parent = parent; |
| this.processorId = processorId; |
| } |
| |
| private boolean verifyContext(ContextDescriptorProvider context) { |
| assert parent.verifyContext(context); |
| return true; |
| } |
| |
| /** |
| * Create processing context for a single method. |
| * |
| * <p>There should only ever be a single allocation of the particular method-processing context. |
| * This is generally ensured by, eg, a MethodProcessor, having private access to the processing |
| * context and ensuring a safe single allocation of the individual method-processing contexts. |
| */ |
| public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) { |
| MethodProcessingContext methodProcessingContext = new MethodProcessingContext(this, method); |
| assert verifyContext(methodProcessingContext); |
| return methodProcessingContext; |
| } |
| |
| private StringBuilder buildSuffix(StringBuilder builder) { |
| return builder.append('$').append(processorId); |
| } |
| |
| @Override |
| StringBuilder buildContextDescriptorForTesting(StringBuilder builder) { |
| return buildSuffix(builder); |
| } |
| |
| @Override |
| StringBuilder buildSyntheticSuffix(StringBuilder builder) { |
| return buildSuffix(builder); |
| } |
| } |
| |
| /** Description of the method context from which to synthesize. */ |
| public static class MethodProcessingContext extends ContextDescriptorProvider { |
| private final ProcessorContext parent; |
| private final ProgramMethod method; |
| private int nextId = 0; |
| |
| private MethodProcessingContext(ProcessorContext parent, ProgramMethod method) { |
| this.parent = parent; |
| this.method = method; |
| } |
| |
| /** |
| * Create a unique processing context. |
| * |
| * <p>The uniqueness of the context requires that the parent, eg, method-context, is unique and |
| * that the processing of that entity is such that the calls to this method happen in a |
| * deterministic order, eg, by the processing of method instructions being single threaded. |
| */ |
| public UniqueContext createUniqueContext() { |
| UniqueContext uniqueContext = new UniqueContext(this, nextId++); |
| assert parent.verifyContext(uniqueContext); |
| return uniqueContext; |
| } |
| |
| DexProgramClass getClassContext() { |
| return method.getHolder(); |
| } |
| |
| public ProgramMethod getMethodContext() { |
| return method; |
| } |
| |
| private StringBuilder buildSuffix(StringBuilder builder) { |
| // TODO(b/172194101): Sanitize the method descriptor instead of hashing. |
| Hasher hasher = Hashing.sha256().newHasher(); |
| method.getReference().hash(hasher); |
| return builder.append('$').append(hasher.hash().toString()); |
| } |
| |
| @Override |
| StringBuilder buildContextDescriptorForTesting(StringBuilder builder) { |
| // Put the type first in the context descriptor. |
| builder.append(getClassContext().getType().toDescriptorString()); |
| return buildSuffix(parent.buildContextDescriptorForTesting(builder)); |
| } |
| |
| @Override |
| StringBuilder buildSyntheticSuffix(StringBuilder builder) { |
| return buildSuffix(parent.buildSyntheticSuffix(builder)); |
| } |
| } |
| |
| public static class UniqueContext extends ContextDescriptorProvider { |
| private final MethodProcessingContext parent; |
| private final int positionId; |
| |
| private UniqueContext(MethodProcessingContext parent, int positionId) { |
| this.parent = parent; |
| this.positionId = positionId; |
| } |
| |
| private StringBuilder buildSuffix(StringBuilder builder) { |
| return builder.append('$').append(positionId); |
| } |
| |
| @Override |
| StringBuilder buildContextDescriptorForTesting(StringBuilder builder) { |
| return buildSuffix(parent.buildContextDescriptorForTesting(builder)); |
| } |
| |
| @Override |
| StringBuilder buildSyntheticSuffix(StringBuilder builder) { |
| return buildSuffix(parent.buildSyntheticSuffix(builder)); |
| } |
| |
| public DexProgramClass getClassContext() { |
| return parent.getClassContext(); |
| } |
| |
| public String getSyntheticSuffix() { |
| return buildSyntheticSuffix(new StringBuilder()).toString(); |
| } |
| } |
| } |