Remove old StringBuilderOptimizer
Change-Id: Ief90f5511d3e05835ecbb1e62503c7ed1f4eb484
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 05824a0..b0694ab 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -81,7 +81,6 @@
import com.android.tools.r8.ir.optimize.membervaluepropagation.R8MemberValuePropagation;
import com.android.tools.r8.ir.optimize.outliner.Outliner;
import com.android.tools.r8.ir.optimize.string.StringBuilderAppendOptimizer;
-import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
import com.android.tools.r8.ir.optimize.string.StringOptimizer;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.IdentifierNameStringMarker;
@@ -123,7 +122,6 @@
private final FieldAccessAnalysis fieldAccessAnalysis;
private final LibraryMethodOverrideAnalysis libraryMethodOverrideAnalysis;
private final StringOptimizer stringOptimizer;
- private final StringBuilderOptimizer stringBuilderOptimizer;
private final IdempotentFunctionCallCanonicalizer idempotentFunctionCallCanonicalizer;
private final ClassInliner classInliner;
private final InternalOptions options;
@@ -182,7 +180,6 @@
this.classInitializerDefaultsOptimization =
new ClassInitializerDefaultsOptimization(appView, this);
this.stringOptimizer = new StringOptimizer(appView);
- this.stringBuilderOptimizer = new StringBuilderOptimizer(appView);
this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter);
this.assertionsRewriter = new AssertionsRewriter(appView);
this.idempotentFunctionCallCanonicalizer = new IdempotentFunctionCallCanonicalizer(appView);
@@ -807,9 +804,6 @@
if (stringOptimizer != null) {
stringOptimizer.logResult();
}
- if (stringBuilderOptimizer != null) {
- stringBuilderOptimizer.logResults();
- }
}
// Assure that no more optimization feedback left after post processing.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
deleted file mode 100644
index bb75dc2..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
+++ /dev/null
@@ -1,180 +0,0 @@
-// 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.ir.optimize.string;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractState;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.AbstractTransferFunction;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.FailedTransferFunctionResult;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.SetUtils;
-import com.google.common.collect.Sets;
-import java.util.Set;
-
-/**
- * This defines a simple program analysis that determines if there is a path from a call to append()
- * on a StringBuilder back to itself in the control flow graph.
- *
- * <p>The analysis explicitly allows paths from a call to append() back to itself that go through a
- * call to toString() on the builder. This ensures that we can still optimize builders that are
- * fully enclosed in a loop: <code>
- * while (true) {
- * System.out.println(new StringBuilder().append("42").toString());
- * }
- * </code>
- */
-class StringBuilderAppendFlowAnalysis {
-
- /**
- * Returns true if there is a call to {@code append()} on {@param builder}, which is inside a
- * loop.
- */
- static boolean hasAppendInstructionInLoop(
- AppView<?> appView,
- IRCode code,
- Value builder,
- StringBuilderOptimizationConfiguration configuration) {
- IntraproceduralDataflowAnalysis<AbstractStateImpl> analysis =
- new IntraproceduralDataflowAnalysis<>(
- appView,
- AbstractStateImpl.bottom(),
- code,
- new TransferFunction(builder, configuration));
- DataflowAnalysisResult result = analysis.run(builder.definition.getBlock());
- return result.isFailedAnalysisResult();
- }
-
- /** This defines the state that we keep track of for each {@link BasicBlock}. */
- private static class AbstractStateImpl extends AbstractState<AbstractStateImpl> {
-
- private static final AbstractStateImpl BOTTOM = new AbstractStateImpl();
-
- // The set of invoke instructions that call append(), which is on a path to the current program
- // point.
- private final Set<InvokeVirtual> liveAppendInstructions;
-
- private AbstractStateImpl() {
- this(Sets.newIdentityHashSet());
- }
-
- private AbstractStateImpl(Set<InvokeVirtual> liveAppendInstructions) {
- this.liveAppendInstructions = liveAppendInstructions;
- }
-
- public static AbstractStateImpl bottom() {
- return BOTTOM;
- }
-
- private AbstractStateImpl addLiveAppendInstruction(InvokeVirtual invoke) {
- Set<InvokeVirtual> newLiveAppendInstructions =
- SetUtils.newIdentityHashSet(liveAppendInstructions);
- newLiveAppendInstructions.add(invoke);
- return new AbstractStateImpl(newLiveAppendInstructions);
- }
-
- private boolean isAppendInstructionLive(InvokeVirtual invoke) {
- return liveAppendInstructions.contains(invoke);
- }
-
- @Override
- public AbstractStateImpl asAbstractState() {
- return this;
- }
-
- @Override
- public AbstractStateImpl join(AppView<?> appView, AbstractStateImpl state) {
- if (liveAppendInstructions.isEmpty()) {
- return state;
- }
- if (state.liveAppendInstructions.isEmpty()) {
- return this;
- }
- Set<InvokeVirtual> newLiveAppendInstructions =
- SetUtils.newIdentityHashSet(liveAppendInstructions, state.liveAppendInstructions);
- return new AbstractStateImpl(newLiveAppendInstructions);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == null || getClass() != other.getClass()) {
- return false;
- }
- AbstractStateImpl state = (AbstractStateImpl) other;
- return liveAppendInstructions.equals(state.liveAppendInstructions);
- }
-
- @Override
- public int hashCode() {
- return liveAppendInstructions.hashCode();
- }
- }
-
- /**
- * This defines the transfer function for the analysis.
- *
- * <p>If a call to {@code append()} on the builder is seen, then that invoke instruction is added
- * to the abstract state.
- *
- * <p>If a call to {@code toString()} on the builder i seen, then the abstract state is reset to
- * bottom.
- */
- private static class TransferFunction
- implements AbstractTransferFunction<BasicBlock, Instruction, AbstractStateImpl> {
-
- private final Value builder;
- private final StringBuilderOptimizationConfiguration configuration;
-
- private TransferFunction(Value builder, StringBuilderOptimizationConfiguration configuration) {
- this.builder = builder;
- this.configuration = configuration;
- }
-
- @Override
- public TransferFunctionResult<AbstractStateImpl> apply(
- Instruction instruction, AbstractStateImpl state) {
- if (instruction.isInvokeMethod()) {
- return apply(state, instruction.asInvokeMethod());
- }
- return state;
- }
-
- private TransferFunctionResult<AbstractStateImpl> apply(
- AbstractStateImpl state, InvokeMethod invoke) {
- if (isAppendOnBuilder(invoke)) {
- assert invoke.isInvokeVirtual();
- InvokeVirtual appendInvoke = invoke.asInvokeVirtual();
- if (state.isAppendInstructionLive(appendInvoke)) {
- return new FailedTransferFunctionResult<>();
- }
- return state.addLiveAppendInstruction(appendInvoke);
- }
- if (isToStringOnBuilder(invoke)) {
- return AbstractStateImpl.bottom();
- }
- return state;
- }
-
- private boolean isAppendOnBuilder(InvokeMethod invoke) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- return configuration.isAppendMethod(invokedMethod)
- && invoke.getArgument(0).getAliasedValue() == builder;
- }
-
- private boolean isToStringOnBuilder(InvokeMethod invoke) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- return configuration.isToStringMethod(invokedMethod)
- && invoke.getArgument(0).getAliasedValue() == builder;
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizationConfiguration.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizationConfiguration.java
deleted file mode 100644
index d1612c3..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizationConfiguration.java
+++ /dev/null
@@ -1,24 +0,0 @@
-// 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.ir.optimize.string;
-
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.InvokeMethod;
-
-interface StringBuilderOptimizationConfiguration {
- boolean isBuilderType(DexType type);
-
- boolean isBuilderInit(DexMethod method, DexType builderType);
-
- boolean isBuilderInit(DexMethod method);
-
- boolean isBuilderInitWithInitialValue(InvokeMethod invoke);
-
- boolean isAppendMethod(DexMethod method);
-
- boolean isSupportedAppendMethod(InvokeMethod invoke);
-
- boolean isToStringMethod(DexMethod method);
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
deleted file mode 100644
index 957ccfe..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ /dev/null
@@ -1,1024 +0,0 @@
-// 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.ir.optimize.string;
-
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.escape.EscapeAnalysis;
-import com.android.tools.r8.ir.analysis.escape.EscapeAnalysisConfiguration;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.Assume;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstNumber;
-import com.android.tools.r8.ir.code.ConstString;
-import com.android.tools.r8.ir.code.DominatorTree;
-import com.android.tools.r8.ir.code.DominatorTree.Assumption;
-import com.android.tools.r8.ir.code.Goto;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.If;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
-import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.NumberConversion;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.StringUtils;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
-import it.unimi.dsi.fastutil.objects.Object2IntMap;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-// This optimization attempts to replace all builder.toString() calls with a constant string.
-// TODO(b/114002137): for now, the analysis depends on rewriteMoveResult.
-// Consider the following example:
-//
-// StringBuilder builder;
-// if (...) {
-// builder.append("X");
-// } else {
-// builder.append("Y");
-// }
-// builder.toString();
-//
-// Its corresponding IR looks like:
-// block0:
-// b <- new-instance StringBuilder
-// if ... block2 // Otherwise, fallthrough
-// block1:
-// c1 <- "X"
-// b1 <- invoke-virtual b, c1, ...append
-// goto block3
-// block2:
-// c2 <- "Y"
-// b2 <- invoke-virtual b, c2, ...append
-// goto block3
-// block3:
-// invoke-virtual b, ...toString
-//
-// After rewriteMoveResult, aliased out values, b1 and b2, are gone. So the analysis can focus on
-// single SSA values, assuming it's flow-sensitive (which is not true in general).
-public class StringBuilderOptimizer {
-
- private final AppView<?> appView;
- private final DexItemFactory factory;
- @VisibleForTesting
- StringConcatenationAnalysis analysis;
- final StringBuilderOptimizationConfiguration optimizationConfiguration;
-
- private int numberOfBuildersWithMultipleToString = 0;
- private int numberOfBuildersWithoutToString = 0;
- private int numberOfBuildersThatEscape = 0;
- private int numberOfBuildersWhoseResultIsInterned = 0;
- private int numberOfBuildersWithNonTrivialStateChange = 0;
- private int numberOfBuildersWithUnsupportedArg = 0;
- private int numberOfBuildersWithMergingPoints = 0;
- private int numberOfBuildersWithNonDeterministicArg = 0;
- private int numberOfDeadBuilders = 0;
- private int numberOfBuildersSimplified = 0;
- private final Object2IntMap<Integer> histogramOfLengthOfAppendChains;
- private final Object2IntMap<Integer> histogramOfLengthOfEndResult;
- private final Object2IntMap<Integer> histogramOfLengthOfPartialAppendChains;
- private final Object2IntMap<Integer> histogramOfLengthOfPartialResult;
-
- public StringBuilderOptimizer(AppView<? extends AppInfo> appView) {
- this.appView = appView;
- this.factory = appView.dexItemFactory();
- this.optimizationConfiguration = new DefaultStringBuilderOptimizationConfiguration();
- if (Log.ENABLED && Log.isLoggingEnabledFor(StringBuilderOptimizer.class)) {
- histogramOfLengthOfAppendChains = new Object2IntArrayMap<>();
- histogramOfLengthOfEndResult = new Object2IntArrayMap<>();
- histogramOfLengthOfPartialAppendChains = new Object2IntArrayMap<>();
- histogramOfLengthOfPartialResult = new Object2IntArrayMap<>();
- } else {
- histogramOfLengthOfAppendChains = null;
- histogramOfLengthOfEndResult = null;
- histogramOfLengthOfPartialAppendChains = null;
- histogramOfLengthOfPartialResult = null;
- }
- }
-
- public void logResults() {
- assert Log.ENABLED;
- Log.info(getClass(),
- "# builders w/ multiple toString(): %s", numberOfBuildersWithMultipleToString);
- Log.info(getClass(),
- "# builders w/o toString(): %s", numberOfBuildersWithoutToString);
- Log.info(getClass(),
- "# builders that escape: %s", numberOfBuildersThatEscape);
- Log.info(getClass(),
- "# builders whose result is interned: %s", numberOfBuildersWhoseResultIsInterned);
- Log.info(getClass(),
- "# builders w/ non-trivial state change: %s", numberOfBuildersWithNonTrivialStateChange);
- Log.info(getClass(),
- "# builders w/ unsupported arg: %s", numberOfBuildersWithUnsupportedArg);
- Log.info(getClass(),
- "# builders w/ merging points: %s", numberOfBuildersWithMergingPoints);
- Log.info(getClass(),
- "# builders w/ non-deterministic arg: %s", numberOfBuildersWithNonDeterministicArg);
- Log.info(getClass(), "# dead builders : %s", numberOfDeadBuilders);
- Log.info(getClass(), "# builders simplified: %s", numberOfBuildersSimplified);
- if (histogramOfLengthOfAppendChains != null) {
- Log.info(getClass(), "------ histogram of StringBuilder append chain lengths ------");
- histogramOfLengthOfAppendChains.forEach((chainSize, count) -> {
- Log.info(getClass(),
- "%s: %s (%s)", chainSize, StringUtils.times("*", Math.min(count, 53)), count);
- });
- }
- if (histogramOfLengthOfEndResult != null) {
- Log.info(getClass(), "------ histogram of StringBuilder result lengths ------");
- histogramOfLengthOfEndResult.forEach((length, count) -> {
- Log.info(getClass(),
- "%s: %s (%s)", length, StringUtils.times("*", Math.min(count, 53)), count);
- });
- }
- if (histogramOfLengthOfPartialAppendChains != null) {
- Log.info(getClass(),
- "------ histogram of StringBuilder append chain lengths (partial) ------");
- histogramOfLengthOfPartialAppendChains.forEach((chainSize, count) -> {
- Log.info(getClass(),
- "%s: %s (%s)", chainSize, StringUtils.times("*", Math.min(count, 53)), count);
- });
- }
- if (histogramOfLengthOfPartialResult != null) {
- Log.info(getClass(), "------ histogram of StringBuilder partial result lengths ------");
- histogramOfLengthOfPartialResult.forEach(
- (length, count) ->
- Log.info(
- getClass(),
- "%s: %s (%s)",
- length,
- StringUtils.times("*", Math.min(count, 53)),
- count));
- }
- }
-
- public void computeTrivialStringConcatenation(IRCode code) {
- StringConcatenationAnalysis analysis = new StringConcatenationAnalysis(code);
- // Only for testing purpose, where we ran the analysis for only one method.
- // Using `this.analysis` is not thread-safe, of course.
- this.analysis = analysis;
- Set<Value> candidateBuilders =
- analysis.findAllLocalBuilders()
- .stream()
- .filter(analysis::canBeOptimized)
- .collect(Collectors.toSet());
- if (candidateBuilders.isEmpty()) {
- return;
- }
- analysis
- .buildBuilderStateGraph(candidateBuilders)
- .applyConcatenationResults(candidateBuilders)
- .removeTrivialBuilders();
- }
-
- class StringConcatenationAnalysis {
-
- // Inspired by {@link JumboStringTest}. Some code intentionally may have too many append(...).
- private static final int CONCATENATION_THRESHOLD = 200;
- private static final String ANY_STRING = "*";
- private static final String DUMMY = "$dummy$";
-
- private final IRCode code;
-
- // A map from SSA Value of StringBuilder type to its toString() counts.
- // Reused (e.g., concatenated, toString, concatenated more, toString) builders are out of scope.
- // TODO(b/114002137): some of those toString could have constant string states.
- final Object2IntMap<Value> builderToStringCounts = new Object2IntArrayMap<>();
-
- StringConcatenationAnalysis(IRCode code) {
- this.code = code;
- }
-
- // This optimization focuses on builders that are created and used locally.
- // In the first step, we collect builders that are created in the current method.
- // In the next step, we will filter out builders that cannot be optimized. To avoid multiple
- // iterations per builder, we're collecting # of uses of those builders by iterating the code
- // twice in this step.
- private Set<Value> findAllLocalBuilders() {
- // During the first iteration, collect builders that are locally created.
- // TODO(b/114002137): Make sure new-instance is followed by <init> before any other calls.
- for (NewInstance newInstance : code.<NewInstance>instructions(Instruction::isNewInstance)) {
- if (optimizationConfiguration.isBuilderType(newInstance.clazz)) {
- Value builder = newInstance.asNewInstance().dest();
- assert !builderToStringCounts.containsKey(builder);
- builderToStringCounts.put(builder, 0);
- }
- }
- if (builderToStringCounts.isEmpty()) {
- return ImmutableSet.of();
- }
- int concatenationCount = 0;
- // During the second iteration, count builders' usage.
- for (InvokeMethod invoke : code.<InvokeMethod>instructions(Instruction::isInvokeMethod)) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (optimizationConfiguration.isAppendMethod(invokedMethod)) {
- concatenationCount++;
- // The analysis might be overwhelmed.
- if (concatenationCount > CONCATENATION_THRESHOLD) {
- return ImmutableSet.of();
- }
- } else if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
- assert invoke.arguments().size() == 1;
- Value receiver = invoke.getArgument(0).getAliasedValue();
- for (Value builder : collectAllLinkedBuilders(receiver)) {
- if (builderToStringCounts.containsKey(builder)) {
- int count = builderToStringCounts.getInt(builder);
- builderToStringCounts.put(builder, count + 1);
- }
- }
- }
- }
- return builderToStringCounts.keySet();
- }
-
- private Set<Value> collectAllLinkedBuilders(Value builder) {
- Set<Value> builders = Sets.newIdentityHashSet();
- Set<Value> visited = Sets.newIdentityHashSet();
- collectAllLinkedBuilders(builder, builders, visited);
- return builders;
- }
-
- private void collectAllLinkedBuilders(Value builder, Set<Value> builders, Set<Value> visited) {
- if (!visited.add(builder)) {
- return;
- }
- if (builder.isPhi()) {
- for (Value operand : builder.asPhi().getOperands()) {
- collectAllLinkedBuilders(operand, builders, visited);
- }
- } else {
- builders.add(builder);
- }
- }
-
- private boolean canBeOptimized(Value builder) {
- // If the builder is definitely null, it may be handled by other optimizations.
- // E.g., any further operations, such as append, will raise NPE.
- // But, as we collect local builders, it should never be null.
- assert !builder.isAlwaysNull(appView);
- // Before checking the builder usage, make sure we have its usage count.
- assert builderToStringCounts.containsKey(builder);
- // If a builder is reused, chances are the code is not trivial, e.g., building a prefix
- // at some point; appending different suffices in different conditions; and building again.
- if (builderToStringCounts.getInt(builder) > 1) {
- numberOfBuildersWithMultipleToString++;
- return false;
- }
- // If a builder is not used, i.e., never converted to string, it doesn't make sense to
- // attempt to compute its compile-time constant string.
- if (builderToStringCounts.getInt(builder) < 1) {
- numberOfBuildersWithoutToString++;
- return false;
- }
- // Make sure builder is neither phi nor coming from outside of the method.
- assert !builder.isPhi() && builder.definition.isNewInstance();
- assert builder.getType().isClassType();
- DexType builderType = builder.getType().asClassType().getClassType();
- assert optimizationConfiguration.isBuilderType(builderType);
- EscapeAnalysis escapeAnalysis =
- new EscapeAnalysis(
- appView, new StringBuilderOptimizerEscapeAnalysisConfiguration(builder));
- return !escapeAnalysis.isEscaping(code, builder);
- }
-
- // A map from SSA Value of StringBuilder type to its internal state per instruction.
- final Map<Value, Map<Instruction, BuilderState>> builderStates = new HashMap<>();
-
- // Create a builder state, only used when visiting new-instance instructions.
- private Map<Instruction, BuilderState> createBuilderState(Value builder) {
- // By using LinkedHashMap, we want to visit instructions in the order of their insertions.
- return builderStates.computeIfAbsent(builder, ignore -> new LinkedHashMap<>());
- }
-
- // Get a builder state, used for all other cases except for new-instance instructions.
- private Map<Instruction, BuilderState> getBuilderState(Value builder) {
- return builderStates.get(builder);
- }
-
- // Suppose a simple, trivial chain:
- // new StringBuilder().append("a").append("b").toString();
- //
- // the resulting graph would be:
- // [s1, root, "", {s2}],
- // [s2, s1, "a", {s3}],
- // [s3, s2, "b", {}].
- //
- // For each meaningful IR (init, append, toString), the corresponding state will be bound:
- // <init> -> s1, 1st append -> s2, 2nd append -> s3, toString -> s3.
- //
- // Suppose an example with a phi:
- // StringBuilder b = flag ? new StringBuilder("x") : new StringBuilder("y");
- // b.append("z").toString();
- //
- // the resulting graph would be:
- // [s1, root, "", {s2, s3, s4}],
- // [s2, s1, "x", {}],
- // [s3, s1, "y", {}],
- // [s4, s1, "z", {}].
- //
- // Note that neither s2 nor s3 can dominate s4, and thus all of s{2..4} are linked to s1.
- // An alternative graph shape would be: [s4, {s2, s3}, "z", {}] (and proper successor update in
- // s2 and s3, of course). But, from the point of the view of finding the trivial chain, there is
- // no difference. The current graph construction relies on and resembles dominator tree.
- private StringConcatenationAnalysis buildBuilderStateGraph(Set<Value> candidateBuilders) {
- DominatorTree dominatorTree = new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS);
- for (BasicBlock block : code.topologicallySortedBlocks()) {
- for (Instruction instr : block.getInstructions()) {
- if (instr.isNewInstance()
- && optimizationConfiguration.isBuilderType(instr.asNewInstance().clazz)) {
- Value builder = instr.asNewInstance().dest();
- if (!candidateBuilders.contains(builder)) {
- continue;
- }
- if (builder.hasPhiUsers()) {
- candidateBuilders.remove(builder);
- continue;
- }
- Map<Instruction, BuilderState> perInstrState = createBuilderState(builder);
- perInstrState.put(instr, BuilderState.createRoot());
- continue;
- }
-
- if (instr.isInvokeDirect()
- && optimizationConfiguration.isBuilderInitWithInitialValue(instr.asInvokeDirect())) {
- InvokeDirect invoke = instr.asInvokeDirect();
- Value builder = invoke.getReceiver().getAliasedValue();
- if (!candidateBuilders.contains(builder)) {
- continue;
- }
- assert invoke.inValues().size() == 2;
- Value arg = invoke.getFirstNonReceiverArgument().getAliasedValue();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- DexType parameterType = invokedMethod.getParameter(0);
- String addition = extractConstantArgument(arg, invokedMethod, parameterType);
- Map<Instruction, BuilderState> perInstrState = getBuilderState(builder);
- BuilderState dominantState = findDominantState(dominatorTree, perInstrState, instr);
- if (dominantState != null) {
- BuilderState currentState = dominantState.createChild(addition);
- perInstrState.put(instr, currentState);
- } else {
- // TODO(b/114002137): if we want to utilize partial results, don't remove it here.
- candidateBuilders.remove(builder);
- }
- continue;
- }
-
- if (!instr.isInvokeMethodWithReceiver()) {
- continue;
- }
-
- InvokeMethodWithReceiver invoke = instr.asInvokeMethodWithReceiver();
- if (optimizationConfiguration.isAppendMethod(invoke.getInvokedMethod())) {
- Value builder = invoke.getReceiver().getAliasedValue();
- // If `builder` is phi, itself and predecessors won't be tracked.
- // Not being tracked means that they won't be optimized, which is intentional.
- if (!candidateBuilders.contains(builder)) {
- continue;
- }
- if (invoke.hasUsedOutValue()) {
- candidateBuilders.remove(builder);
- continue;
- }
- Value arg = invoke.getFirstNonReceiverArgument().getAliasedValue();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- DexType parameterType = invokedMethod.getParameter(0);
- String addition = extractConstantArgument(arg, invokedMethod, parameterType);
- Map<Instruction, BuilderState> perInstrState = getBuilderState(builder);
- BuilderState dominantState = findDominantState(dominatorTree, perInstrState, instr);
- if (dominantState != null) {
- BuilderState currentState = dominantState.createChild(addition);
- perInstrState.put(instr, currentState);
- } else {
- // TODO(b/114002137): if we want to utilize partial results, don't remove it here.
- candidateBuilders.remove(builder);
- }
- }
- if (optimizationConfiguration.isToStringMethod(invoke.getInvokedMethod())) {
- Value builder = invoke.getReceiver().getAliasedValue();
- // If `builder` is phi, itself and predecessors won't be tracked.
- // Not being tracked means that they won't be optimized, which is intentional.
- if (!candidateBuilders.contains(builder)) {
- continue;
- }
- Map<Instruction, BuilderState> perInstrState = getBuilderState(builder);
- BuilderState dominantState = findDominantState(dominatorTree, perInstrState, instr);
- if (dominantState != null) {
- // Instead of using the dominant state directly, treat this retrieval point as a new
- // state without an addition so that dominant state can account for dependent states.
- BuilderState currentState = dominantState.createChild("");
- perInstrState.put(instr, currentState);
- } else {
- // TODO(b/114002137): if we want to utilize partial results, don't remove it here.
- candidateBuilders.remove(builder);
- }
- }
- }
- }
-
- return this;
- }
-
- private String extractConstantArgument(Value arg, DexMethod method, DexType argType) {
- String addition = ANY_STRING;
- if (arg.isPhi()) {
- return addition;
- }
- if (arg.definition.isConstString()) {
- addition = arg.definition.asConstString().getValue().toString();
- } else if (arg.definition.isConstNumber() || arg.definition.isNumberConversion()) {
- Number number = extractConstantNumber(arg);
- if (number == null) {
- return addition;
- }
- if (arg.getType().isPrimitiveType()) {
- if (argType == factory.booleanType) {
- addition = String.valueOf(number.intValue() != 0);
- } else if (argType == factory.byteType) {
- addition = String.valueOf(number.byteValue());
- } else if (argType == factory.shortType) {
- addition = String.valueOf(number.shortValue());
- } else if (argType == factory.charType) {
- addition = String.valueOf((char) number.intValue());
- } else if (argType == factory.intType) {
- addition = String.valueOf(number.intValue());
- } else if (argType == factory.longType) {
- addition = String.valueOf(number.longValue());
- } else if (argType == factory.floatType) {
- addition = String.valueOf(number.floatValue());
- } else if (argType == factory.doubleType) {
- addition = String.valueOf(number.doubleValue());
- }
- } else if (arg.getType().isNullType()
- && !method.isInstanceInitializer(factory)
- && argType != factory.charArrayType) {
- assert number.intValue() == 0;
- addition = "null";
- }
- }
- return addition;
- }
-
- private Number extractConstantNumber(Value arg) {
- if (arg.isPhi()) {
- return null;
- }
- if (arg.definition.isConstNumber()) {
- ConstNumber cst = arg.definition.asConstNumber();
- if (cst.outType() == ValueType.LONG) {
- return cst.getLongValue();
- } else if (cst.outType() == ValueType.FLOAT) {
- return cst.getFloatValue();
- } else if (cst.outType() == ValueType.DOUBLE) {
- return cst.getDoubleValue();
- } else {
- assert cst.outType() == ValueType.INT || cst.outType() == ValueType.OBJECT;
- return cst.getIntValue();
- }
- } else if (arg.definition.isNumberConversion()) {
- NumberConversion conversion = arg.definition.asNumberConversion();
- assert conversion.inValues().size() == 1;
- Number temp = extractConstantNumber(conversion.inValues().get(0));
- if (temp == null) {
- return null;
- }
- DexType conversionType = conversion.to.toDexType(factory);
- if (conversionType == factory.booleanType) {
- return temp.intValue() != 0 ? 1 : 0;
- } else if (conversionType == factory.byteType) {
- return temp.byteValue();
- } else if (conversionType == factory.shortType) {
- return temp.shortValue();
- } else if (conversionType == factory.charType) {
- return temp.intValue();
- } else if (conversionType == factory.intType) {
- return temp.intValue();
- } else if (conversionType == factory.longType) {
- return temp.longValue();
- } else if (conversionType == factory.floatType) {
- return temp.floatValue();
- } else if (conversionType == factory.doubleType) {
- return temp.doubleValue();
- }
- }
- return null;
- }
-
- private BuilderState findDominantState(
- DominatorTree dominatorTree,
- Map<Instruction, BuilderState> perInstrState,
- Instruction current) {
- BuilderState result = null;
- BasicBlock lastDominantBlock = null;
- for (Instruction instr : perInstrState.keySet()) {
- BasicBlock block = instr.getBlock();
- if (!dominatorTree.dominatedBy(current.getBlock(), block)) {
- continue;
- }
- if (lastDominantBlock == null
- || dominatorTree.dominatedBy(block, lastDominantBlock)) {
- result = perInstrState.get(instr);
- lastDominantBlock = block;
- }
- }
- return result;
- }
-
- private void logHistogramOfChains(List<String> contents, boolean isPartial) {
- if (!Log.ENABLED || !Log.isLoggingEnabledFor(StringOptimizer.class)) {
- return;
- }
- if (contents == null || contents.isEmpty()) {
- return;
- }
- String result = StringUtils.join("", contents);
- Integer size = Integer.valueOf(contents.size());
- Integer length = Integer.valueOf(result.length());
- if (isPartial) {
- assert histogramOfLengthOfPartialAppendChains != null;
- synchronized (histogramOfLengthOfPartialAppendChains) {
- int count = histogramOfLengthOfPartialAppendChains.getOrDefault(size, 0);
- histogramOfLengthOfPartialAppendChains.put(size, count + 1);
- }
- assert histogramOfLengthOfPartialResult != null;
- synchronized (histogramOfLengthOfPartialResult) {
- int count = histogramOfLengthOfPartialResult.getOrDefault(length, 0);
- histogramOfLengthOfPartialResult.put(length, count + 1);
- }
- } else {
- assert histogramOfLengthOfAppendChains != null;
- synchronized (histogramOfLengthOfAppendChains) {
- int count = histogramOfLengthOfAppendChains.getOrDefault(size, 0);
- histogramOfLengthOfAppendChains.put(size, count + 1);
- }
- assert histogramOfLengthOfEndResult != null;
- synchronized (histogramOfLengthOfEndResult) {
- int count = histogramOfLengthOfEndResult.getOrDefault(length, 0);
- histogramOfLengthOfEndResult.put(length, count + 1);
- }
- }
- }
-
- // StringBuilders that are simplified by this analysis or simply dead (e.g., after applying
- // -assumenosideeffects to logging calls, then logging messages built by builders are dead).
- // Will be used to clean up uses of the builders, such as creation, <init>, and append calls.
- final Set<Value> deadBuilders = Sets.newIdentityHashSet();
- final Set<Value> simplifiedBuilders = Sets.newIdentityHashSet();
-
- private StringConcatenationAnalysis applyConcatenationResults(Set<Value> candidateBuilders) {
- Set<Value> affectedValues = Sets.newIdentityHashSet();
- InstructionListIterator it = code.instructionListIterator();
- while (it.hasNext()) {
- Instruction instr = it.nextUntil(i -> isToStringOfInterest(candidateBuilders, i));
- if (instr == null) {
- break;
- }
- InvokeMethod invoke = instr.asInvokeMethod();
- assert invoke.arguments().size() == 1;
- Value builder = invoke.getArgument(0).getAliasedValue();
- Value outValue = invoke.outValue();
- if (outValue == null || outValue.isDead(appView, code)) {
- // If the out value is still used but potentially dead, replace it with a dummy string.
- if (outValue != null && outValue.isUsed()) {
- Value dummy =
- code.createValue(
- TypeElement.stringClassType(appView, definitelyNotNull()),
- invoke.getLocalInfo());
- it.replaceCurrentInstruction(new ConstString(dummy, factory.createString(DUMMY)));
- } else {
- it.removeOrReplaceByDebugLocalRead();
- }
- // Although this builder is not simplified by this analysis, add that to the set so that
- // it can be removed at the final clean-up phase.
- deadBuilders.add(builder);
- numberOfDeadBuilders++;
- continue;
- }
- Map<Instruction, BuilderState> perInstrState = builderStates.get(builder);
- assert perInstrState != null;
- BuilderState builderState = perInstrState.get(instr);
- assert builderState != null;
- String element = toCompileTimeString(builder, builderState);
- assert element != null;
- Value stringValue =
- code.createValue(
- TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
- affectedValues.addAll(outValue.affectedValues());
- it.replaceCurrentInstruction(new ConstString(stringValue, factory.createString(element)));
- simplifiedBuilders.add(builder);
- numberOfBuildersSimplified++;
- }
- // Concatenation results are not null, and thus propagate that information.
- if (!affectedValues.isEmpty()) {
- new TypeAnalysis(appView).narrowing(affectedValues);
- }
- return this;
- }
-
- private boolean isToStringOfInterest(Set<Value> candidateBuilders, Instruction instr) {
- if (!instr.isInvokeMethod()) {
- return false;
- }
- InvokeMethod invoke = instr.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (!optimizationConfiguration.isToStringMethod(invokedMethod)) {
- return false;
- }
- assert invoke.arguments().size() == 1;
- Value builder = invoke.getArgument(0).getAliasedValue();
- if (!candidateBuilders.contains(builder)) {
- return false;
- }
- // If the result of toString() is no longer used, computing the compile-time constant is
- // even not necessary.
- if (!invoke.hasOutValue() || invoke.outValue().isDead(appView, code)) {
- return true;
- }
- Map<Instruction, BuilderState> perInstrState = builderStates.get(builder);
- if (perInstrState == null) {
- return false;
- }
- BuilderState builderState = perInstrState.get(instr);
- if (builderState == null) {
- return false;
- }
- String element = toCompileTimeString(builder, builderState);
- if (element == null) {
- return false;
- }
- return true;
- }
-
- // Find the trivial chain of builder-append*-toString.
- // Note that we can't determine a compile-time constant string if there are any ambiguity.
- @VisibleForTesting
- String toCompileTimeString(Value builder, BuilderState state) {
- boolean continuedForLogging = false;
- LinkedList<String> contents = new LinkedList<>();
- while (state != null) {
- // Not a single chain if there are multiple successors.
- if (state.nexts != null && state.nexts.size() > 1) {
- numberOfBuildersWithMergingPoints++;
- if (Log.ENABLED && Log.isLoggingEnabledFor(StringBuilderOptimizer.class)) {
- logHistogramOfChains(contents, true);
- continuedForLogging = true;
- contents.clear();
- state = state.previous;
- continue;
- }
- return null;
- }
- // Reaching the root.
- if (state.addition == null) {
- break;
- }
- // A non-deterministic argument is appended.
- // TODO(b/129200243): Even though it's ambiguous, if the chain length is 2, and the 1st one
- // is definitely non-null, we can convert it to String#concat.
- if (state.addition.equals(ANY_STRING)) {
- numberOfBuildersWithNonDeterministicArg++;
- if (Log.ENABLED && Log.isLoggingEnabledFor(StringBuilderOptimizer.class)) {
- logHistogramOfChains(contents, true);
- continuedForLogging = true;
- contents.clear();
- state = state.previous;
- continue;
- }
- return null;
- }
- contents.push(state.addition);
- state = state.previous;
- }
- if (continuedForLogging) {
- logHistogramOfChains(contents, true);
- return null;
- }
- if (Log.ENABLED && Log.isLoggingEnabledFor(StringBuilderOptimizer.class)) {
- logHistogramOfChains(contents, false);
- }
- if (contents.isEmpty()) {
- return null;
- }
- if (StringBuilderAppendFlowAnalysis.hasAppendInstructionInLoop(
- appView, code, builder, optimizationConfiguration)) {
- return null;
- }
- return StringUtils.join("", contents);
- }
-
- void removeTrivialBuilders() {
- if (deadBuilders.isEmpty() && simplifiedBuilders.isEmpty()) {
- return;
- }
- Set<Value> affectedValues = Sets.newIdentityHashSet();
- Set<Value> buildersToRemove = SetUtils.newIdentityHashSet(deadBuilders, simplifiedBuilders);
- Set<Value> buildersUsedInIf = Sets.newIdentityHashSet();
- // All instructions that refer to dead/simplified builders are dead.
- // Here, we remove toString() calls, append(...) calls, <init>, and new-instance in order.
- InstructionListIterator it = code.instructionListIterator();
- boolean shouldRemoveUnreachableBlocks = false;
- while (it.hasNext()) {
- Instruction instr = it.next();
- if (instr.isIf()) {
- If theIf = instr.asIf();
- Value lhs = theIf.lhs().getAliasedValue();
- if (theIf.isZeroTest()) {
- if (buildersToRemove.contains(lhs)) {
- theIf.targetFromNullObject().unlinkSinglePredecessorSiblingsAllowed();
- it.replaceCurrentInstruction(new Goto());
- shouldRemoveUnreachableBlocks = true;
- }
- } else {
- Value rhs = theIf.rhs().getAliasedValue();
- if (buildersToRemove.contains(lhs)) {
- buildersUsedInIf.add(lhs);
- }
- if (buildersToRemove.contains(rhs)) {
- buildersUsedInIf.add(rhs);
- }
- }
- } else if (instr.isInvokeMethod()) {
- InvokeMethod invoke = instr.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (optimizationConfiguration.isToStringMethod(invokedMethod)
- && buildersToRemove.contains(invoke.getArgument(0).getAliasedValue())) {
- it.removeOrReplaceByDebugLocalRead();
- }
- }
- }
- if (shouldRemoveUnreachableBlocks) {
- affectedValues.addAll(code.removeUnreachableBlocks());
- }
- buildersToRemove.removeAll(buildersUsedInIf);
- // append(...) and <init> don't have out values, so removing them won't bother each other.
- it = code.instructionListIterator();
- while (it.hasNext()) {
- Instruction instr = it.next();
- if (instr.isInvokeVirtual()) {
- InvokeVirtual invoke = instr.asInvokeVirtual();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (optimizationConfiguration.isAppendMethod(invokedMethod)
- && buildersToRemove.contains(invoke.getReceiver().getAliasedValue())) {
- it.removeOrReplaceByDebugLocalRead();
- }
- }
- if (instr.isInvokeDirect()) {
- InvokeDirect invoke = instr.asInvokeDirect();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (optimizationConfiguration.isBuilderInit(invokedMethod)
- && buildersToRemove.contains(invoke.getReceiver().getAliasedValue())) {
- it.removeOrReplaceByDebugLocalRead();
- }
- }
- // If there are aliasing instructions, they should be removed before new-instance.
- if (instr.isAssume() && buildersToRemove.contains(instr.outValue().getAliasedValue())) {
- Assume assumeInstruction = instr.asAssume();
- Value src = assumeInstruction.src();
- Value dest = assumeInstruction.outValue();
- dest.replaceUsers(src);
- it.remove();
- }
- }
- // new-instance should be removed at last, since it will check the out value, builder, is not
- // used anywhere, which we've removed so far.
- it = code.instructionListIterator();
- while (it.hasNext()) {
- Instruction instr = it.next();
- if (instr.isNewInstance()
- && optimizationConfiguration.isBuilderType(instr.asNewInstance().clazz)
- && instr.hasOutValue()
- && buildersToRemove.contains(instr.outValue())) {
- it.removeOrReplaceByDebugLocalRead();
- }
- }
- if (!affectedValues.isEmpty()) {
- new TypeAnalysis(appView).narrowing(affectedValues);
- }
- assert code.isConsistentSSA(appView);
- }
- }
-
- class DefaultStringBuilderOptimizationConfiguration
- implements StringBuilderOptimizationConfiguration {
- @Override
- public boolean isBuilderType(DexType type) {
- return type == factory.stringBuilderType
- || type == factory.stringBufferType;
- }
-
- @Override
- public boolean isBuilderInit(DexMethod method, DexType builderType) {
- return builderType == method.holder
- && factory.isConstructor(method);
- }
-
- @Override
- public boolean isBuilderInit(DexMethod method) {
- return isBuilderType(method.holder)
- && factory.isConstructor(method);
- }
-
- @Override
- public boolean isBuilderInitWithInitialValue(InvokeMethod invoke) {
- return isBuilderInit(invoke.getInvokedMethod())
- && invoke.inValues().size() == 2
- && !invoke.inValues().get(1).getType().isPrimitiveType();
- }
-
- @Override
- public boolean isAppendMethod(DexMethod method) {
- return factory.stringBuilderMethods.isAppendMethod(method)
- || factory.stringBufferMethods.isAppendMethod(method);
- }
-
- @Override
- public boolean isSupportedAppendMethod(InvokeMethod invoke) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- assert isAppendMethod(invokedMethod);
- if (invoke.hasOutValue()) {
- return false;
- }
- // Any methods other than append(arg) are not trivial since they may change the builder
- // state not monotonically.
- if (invoke.inValues().size() > 2) {
- numberOfBuildersWithNonTrivialStateChange++;
- return false;
- }
- assert invoke.inValues().size() == 2;
- TypeElement argType = invoke.inValues().get(1).getType();
- if (!argType.isPrimitiveType() && !argType.isClassType() && !argType.isNullType()) {
- numberOfBuildersWithUnsupportedArg++;
- return false;
- }
- if (argType.isClassType()) {
- DexType argClassType = argType.asClassType().getClassType();
- return canHandleArgumentType(argClassType);
- }
- return true;
- }
-
- @Override
- public boolean isToStringMethod(DexMethod method) {
- return method == factory.objectMembers.toString
- || method == factory.stringBuilderMethods.toString
- || method == factory.stringBufferMethods.toString
- || method == factory.stringMembers.valueOf;
- }
-
- private boolean canHandleArgumentType(DexType argType) {
- // TODO(b/113859361): passed to another builder should be an eligible case.
- return argType == factory.stringType || argType == factory.charSequenceType;
- }
- }
-
- class StringBuilderOptimizerEscapeAnalysisConfiguration implements EscapeAnalysisConfiguration {
- final Value builder;
- final DexType builderType;
-
- private StringBuilderOptimizerEscapeAnalysisConfiguration(Value builder) {
- this.builder = builder;
- assert builder.getType().isClassType();
- builderType = builder.getType().asClassType().getClassType();
- }
-
- private void logEscapingRoute(boolean legitimate) {
- if (!legitimate) {
- numberOfBuildersThatEscape++;
- }
- }
-
- @Override
- public boolean isLegitimateEscapeRoute(
- AppView<?> appView,
- EscapeAnalysis escapeAnalysis,
- Instruction escapeRoute,
- ProgramMethod context) {
- if (escapeRoute.isReturn() || escapeRoute.isThrow() || escapeRoute.isStaticPut()) {
- logEscapingRoute(false);
- return false;
- }
- if (escapeRoute.isInvokeMethod()) {
- // Program class may call String#intern(). Only allow library calls.
- // TODO(b/114002137): For now, we allow only library calls to avoid a case like
- // identity(Builder.toString()).intern(); but it's too restrictive.
- DexClass holderClass =
- appView.definitionFor(escapeRoute.asInvokeMethod().getInvokedMethod().holder);
- if (holderClass != null && !holderClass.isLibraryClass()) {
- logEscapingRoute(false);
- return false;
- }
-
- InvokeMethod invoke = escapeRoute.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
-
- if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
- Value out = escapeRoute.outValue();
- if (out != null) {
- // If Builder#toString or String#valueOf is interned, it could be used for equality
- // check. Replacing builder-based runtime result with a compile time constant may change
- // the program's runtime behavior.
- for (Instruction outUser : out.uniqueUsers()) {
- if (outUser.isInvokeMethodWithReceiver()
- && outUser.asInvokeMethodWithReceiver().getInvokedMethod()
- == factory.stringMembers.intern) {
- numberOfBuildersWhoseResultIsInterned++;
- return false;
- }
- }
- }
- // Otherwise, use of Builder#toString and String#valueOf is legitimate.
- return true;
- }
-
- // Make sure builder's uses are local, i.e., not escaping from the current method.
- if (invokedMethod.holder != builderType) {
- logEscapingRoute(false);
- return false;
- }
-
- // <init> is legitimate.
- if (optimizationConfiguration.isBuilderInit(invokedMethod, builderType)) {
- return true;
- }
- // Even though all invocations belong to the builder type, there are some methods other
- // than append/toString, e.g., setCharAt, setLength, subSequence, etc.
- // Seeing any of them indicates that this code is not trivial.
- if (!optimizationConfiguration.isAppendMethod(invokedMethod)) {
- numberOfBuildersWithNonTrivialStateChange++;
- return false;
- }
- if (!optimizationConfiguration.isSupportedAppendMethod(invoke)) {
- return false;
- }
-
- // Reaching here means that this invocation is part of trivial patterns we're looking for.
- return true;
- }
- if (escapeRoute.isArrayPut()) {
- Value array = escapeRoute.asArrayPut().array().getAliasedValue();
- boolean legitimate = !array.isPhi() && array.definition.isCreatingArray();
- logEscapingRoute(legitimate);
- return legitimate;
- }
- if (escapeRoute.isInstancePut()) {
- Value instance = escapeRoute.asInstancePut().object().getAliasedValue();
- boolean legitimate = !instance.isPhi() && instance.definition.isNewInstance();
- logEscapingRoute(legitimate);
- return legitimate;
- }
- // All other cases are not legitimate.
- logEscapingRoute(false);
- return false;
- }
- }
-
- // A chain of builder's internal state changes.
- static class BuilderState {
- BuilderState previous;
- String addition;
- Set<BuilderState> nexts;
-
- private BuilderState() {
- previous = null;
- addition = null;
- nexts = null;
- }
-
- static BuilderState createRoot() {
- return new BuilderState();
- }
-
- BuilderState createChild(String addition) {
- BuilderState newState = new BuilderState();
- newState.previous = this;
- newState.addition = addition;
- if (this.nexts == null) {
- this.nexts = Sets.newIdentityHashSet();
- }
- this.nexts.add(newState);
- return newState;
- }
- }
-}