| // 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.ir.desugar.CfClassSynthesizerDesugaring; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.structural.HasherWrapper; |
| 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 Thread mainThread; |
| private final Map<String, String> seenSetForTesting = new ConcurrentHashMap<>(); |
| private int nextProcessorId = 0; |
| |
| private CompilationContext(InternalOptions options) { |
| testingConsumer = options.testing.processingContextsConsumer; |
| mainThread = options.mainThread; |
| } |
| |
| 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); |
| assert mainThread == Thread.currentThread() : "Invoked on another thread than main"; |
| 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; |
| } |
| |
| public MainThreadContext createMainThreadContext() { |
| MainThreadContext singleThreadContext = new MainThreadContext(this, parent.mainThread); |
| assert verifyContext(singleThreadContext); |
| return singleThreadContext; |
| } |
| |
| public ClassSynthesisDesugaringContext createClassSynthesisDesugaringContext( |
| CfClassSynthesizerDesugaring desugaring) { |
| ClassSynthesisDesugaringContext classSynthesisDesugaringContext = |
| new ClassSynthesisDesugaringContext(this, desugaring); |
| assert verifyContext(classSynthesisDesugaringContext); |
| return classSynthesisDesugaringContext; |
| } |
| |
| 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); |
| } |
| } |
| |
| /** The processor context is responsible in creating a task context per thread. */ |
| public abstract static class ThreadTaskContext extends ContextDescriptorProvider { |
| private final ProcessorContext parent; |
| private int nextId = 0; |
| |
| private ThreadTaskContext(ProcessorContext parent) { |
| this.parent = parent; |
| } |
| |
| public ProcessorContext getParent() { |
| return parent; |
| } |
| |
| int incrementAndGetNextId() { |
| return nextId++; |
| } |
| } |
| |
| /** |
| * If no threading is used, the MainThreadContext allows one to generate UniqueContext instances |
| * without a thread specific identifier. |
| */ |
| public static class MainThreadContext extends ThreadTaskContext { |
| |
| private static final String IDENTIFIER = "main"; |
| |
| private final Thread mainThread; |
| |
| private MainThreadContext(ProcessorContext parent, Thread mainThread) { |
| super(parent); |
| this.mainThread = mainThread; |
| } |
| |
| @Override |
| StringBuilder buildContextDescriptorForTesting(StringBuilder builder) { |
| return getParent().buildContextDescriptorForTesting(builder).append(IDENTIFIER); |
| } |
| |
| @Override |
| StringBuilder buildSyntheticSuffix(StringBuilder builder) { |
| return getParent().buildSyntheticSuffix(builder).append(IDENTIFIER); |
| } |
| |
| public UniqueContext createUniqueContext(DexProgramClass context) { |
| assert mainThread == Thread.currentThread() : "Invoked on another thread than main"; |
| UniqueContext uniqueContext = new UniqueContext(this, context, incrementAndGetNextId()); |
| assert getParent().verifyContext(uniqueContext); |
| return uniqueContext; |
| } |
| } |
| |
| public static class ClassSynthesisDesugaringContext extends ThreadTaskContext { |
| private final CfClassSynthesizerDesugaring desugaring; |
| |
| private ClassSynthesisDesugaringContext( |
| ProcessorContext parent, CfClassSynthesizerDesugaring desugaring) { |
| super(parent); |
| this.desugaring = desugaring; |
| } |
| |
| @Override |
| StringBuilder buildContextDescriptorForTesting(StringBuilder builder) { |
| return getParent() |
| .buildContextDescriptorForTesting(builder) |
| .append(desugaring.uniqueIdentifier()); |
| } |
| |
| @Override |
| StringBuilder buildSyntheticSuffix(StringBuilder builder) { |
| return getParent().buildSyntheticSuffix(builder).append(desugaring.uniqueIdentifier()); |
| } |
| |
| public UniqueContext createUniqueContext(DexProgramClass context) { |
| UniqueContext uniqueContext = new UniqueContext(this, context, incrementAndGetNextId()); |
| assert getParent().verifyContext(uniqueContext); |
| return uniqueContext; |
| } |
| } |
| |
| /** Description of the method context from which to synthesize. */ |
| public static class MethodProcessingContext extends ThreadTaskContext { |
| private final ProgramMethod method; |
| |
| private MethodProcessingContext(ProcessorContext parent, ProgramMethod method) { |
| super(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, getClassContext(), incrementAndGetNextId()); |
| assert getParent().verifyContext(uniqueContext); |
| return uniqueContext; |
| } |
| |
| private 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. |
| HasherWrapper hasher = HasherWrapper.sha256Hasher(); |
| method.getReference().hash(hasher); |
| return builder.append('$').append(hasher.hashCodeAsString()); |
| } |
| |
| @Override |
| StringBuilder buildContextDescriptorForTesting(StringBuilder builder) { |
| // Put the type first in the context descriptor. |
| builder.append(getClassContext().getType().toDescriptorString()); |
| return buildSuffix(getParent().buildContextDescriptorForTesting(builder)); |
| } |
| |
| @Override |
| StringBuilder buildSyntheticSuffix(StringBuilder builder) { |
| return buildSuffix(getParent().buildSyntheticSuffix(builder)); |
| } |
| } |
| |
| public static class UniqueContext extends ContextDescriptorProvider { |
| private final ThreadTaskContext parent; |
| // The class context is required to: |
| // - determine in which feature the method should be put (feature split), |
| // - put the synthetic in the correct package. |
| private final DexProgramClass classContext; |
| private final int positionId; |
| |
| private UniqueContext(ThreadTaskContext parent, DexProgramClass classContext, int positionId) { |
| this.parent = parent; |
| this.classContext = classContext; |
| 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 classContext; |
| } |
| |
| public String getSyntheticSuffix() { |
| return buildSyntheticSuffix(new StringBuilder()).toString(); |
| } |
| } |
| } |