| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.jack.optimizations; |
| |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| |
| import com.android.jack.Options; |
| import com.android.jack.analysis.DefinitionMarker; |
| import com.android.jack.analysis.UseDefsMarker; |
| import com.android.jack.analysis.UsedVariableMarker; |
| import com.android.jack.analysis.dfa.reachingdefs.ReachingDefsMarker; |
| import com.android.jack.cfg.BasicBlock; |
| import com.android.jack.cfg.ControlFlowGraph; |
| import com.android.jack.ir.ast.JAsgOperation; |
| import com.android.jack.ir.ast.JExpression; |
| import com.android.jack.ir.ast.JExpressionStatement; |
| import com.android.jack.ir.ast.JMethod; |
| import com.android.jack.ir.ast.JStatement; |
| import com.android.jack.ir.ast.JVariable; |
| import com.android.jack.ir.ast.JVariableRef; |
| import com.android.jack.scheduling.filter.TypeWithoutPrebuiltFilter; |
| import com.android.jack.transformations.request.Remove; |
| import com.android.jack.transformations.request.Replace; |
| import com.android.jack.transformations.request.TransformationRequest; |
| import com.android.jack.transformations.threeaddresscode.ThreeAddressCodeForm; |
| import com.android.jack.util.ControlFlowHelper; |
| import com.android.jack.util.DefsAndUsesChainOptimizationTools; |
| import com.android.jack.util.ThreeAddressCodeFormUtils; |
| import com.android.sched.item.Description; |
| import com.android.sched.schedulable.Constraint; |
| import com.android.sched.schedulable.Filter; |
| import com.android.sched.schedulable.RunnableSchedulable; |
| import com.android.sched.schedulable.Support; |
| import com.android.sched.schedulable.Transform; |
| import com.android.sched.schedulable.Use; |
| import com.android.sched.util.config.ThreadConfig; |
| import com.android.sched.util.log.Tracer; |
| import com.android.sched.util.log.TracerFactory; |
| import com.android.sched.util.log.stats.Counter; |
| import com.android.sched.util.log.stats.CounterImpl; |
| import com.android.sched.util.log.stats.StatisticId; |
| |
| import java.util.ArrayList; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.annotation.CheckForNull; |
| import javax.annotation.Nonnull; |
| |
| /** |
| * Optimization will transform |
| * Path 1 |
| * a = true // s1 |
| * ... |
| * Path 2 |
| * a = false // s2 |
| * ... |
| * Path 3 target of path 1 & 2 |
| * b = a // s0 |
| * |
| * To: |
| * |
| * Path 1 |
| * b = true |
| * ... |
| * Path2 |
| * b = false |
| * ... |
| * Path 3 target of path 1 & 2 |
| * |
| * Optimization can be apply if the following conditions are respected: |
| * (i) all the possible paths from entry point to s0 are going through {si} |
| * (ii) all possible paths from {si} to s0 do not have b assigned or referenced in between |
| * (iii) all a defined in {si} is only referenced in s0 |
| * (iv) all possible paths between {bi} and their uses don’t go through {si} |
| * (v) there are no more definitions of a except those defined in {si} |
| */ |
| @Description("Simplify definition uses chains.") |
| @Constraint(need = { DefinitionMarker.class, UseDefsMarker.class, ThreeAddressCodeForm.class, |
| ControlFlowGraph.class, UsedVariableMarker.class }) |
| // ReachingDefsMarker is no longer valid after this optimization, thus remove it directly |
| @Transform(remove = { ReachingDefsMarker.class }) |
| @Use(DefsAndUsesChainOptimizationTools.class) |
| @Support(Optimizations.DefUseSimplifier.class) |
| @Filter(TypeWithoutPrebuiltFilter.class) |
| public class DefUsesChainsSimplifier extends DefUsesAndUseDefsChainsSimplifier |
| implements RunnableSchedulable<JMethod> { |
| |
| @Nonnull |
| public static final StatisticId<Counter> SIMPLIFIED_DEF_USE = new StatisticId<Counter>( |
| "jack.optimization.defuse", "Def use chain simplified", |
| CounterImpl.class, Counter.class); |
| |
| @Nonnull |
| private final com.android.jack.util.filter.Filter<JMethod> filter = |
| ThreadConfig.get(Options.METHOD_FILTER); |
| |
| @Nonnull |
| private final Tracer tracer = TracerFactory.getTracer(); |
| |
| /** Represents information for single optimization application */ |
| private static class OptInfo { |
| @Nonnull final VarInfo aVarInfo; |
| @Nonnull final DefinitionMarker bDefinition; |
| |
| private OptInfo( |
| @Nonnull VarInfo aVarInfo, |
| @Nonnull DefinitionMarker bDefinition) { |
| this.aVarInfo = aVarInfo; |
| this.bDefinition = bDefinition; |
| } |
| } |
| |
| /** Collected information of variable definitions and usages */ |
| private static class VarInfo { |
| @Nonnull final JVariable var; |
| /** Definitions of the variable */ |
| @Nonnull final Set<DefinitionMarker> defs = Sets.newIdentityHashSet(); |
| /** Statements referencing the variable */ |
| @Nonnull final List<JStatement> refStmts = new ArrayList<>(); |
| |
| VarInfo(@Nonnull JVariable var) { |
| this.var = var; |
| } |
| |
| void mergeWith(@Nonnull VarInfo other) { |
| // All defs of variable 'a' are now |
| // patched to be new defs of variable 'b' |
| this.defs.addAll(other.defs); |
| // 'a' variable was supposed to only have one |
| // referencing statement 's0' which we have removed |
| assert other.refStmts.size() == 1; |
| } |
| } |
| |
| /** Collects all variables used within method with their definitions */ |
| @Nonnull |
| private static LinkedHashMap<JVariable, VarInfo> collectDefinitions( |
| @Nonnull ControlFlowGraph cfg) { |
| LinkedHashMap<JVariable, VarInfo> defs = Maps.newLinkedHashMap(); // Keep the insertion order |
| |
| for (BasicBlock bb : cfg.getNodes()) { |
| for (JStatement stmt : bb.getStatements()) { |
| |
| // store variable -> definition info |
| DefinitionMarker dm = ThreeAddressCodeFormUtils.getDefinitionMarker(stmt); |
| if (dm != null) { |
| JVariable variable = dm.getDefinedVariable(); |
| VarInfo info = defs.get(variable); |
| if (info == null) { |
| info = new VarInfo(variable); |
| defs.put(variable, info); |
| } |
| info.defs.add(dm); |
| } |
| |
| // store variable -> referencing statement info |
| for (JVariableRef ref : DefsAndUsesChainOptimizationTools.getUsedVariables(stmt)) { |
| JVariable variable = ref.getTarget(); |
| VarInfo info = defs.get(variable); |
| if (info == null) { |
| info = new VarInfo(variable); |
| defs.put(variable, info); |
| } |
| info.refStmts.add(stmt); |
| } |
| } |
| } |
| |
| return defs; |
| } |
| |
| /** Returns candidates for optimization satisfying conditions (iii) and (v). */ |
| @Nonnull |
| private static LinkedHashMap<JVariable, OptInfo> collectCandidates( |
| @Nonnull LinkedHashMap<JVariable, VarInfo> definitions) { |
| |
| LinkedHashMap<JVariable, OptInfo> result = Maps.newLinkedHashMap(); // Keep the insertion order |
| for (Map.Entry<JVariable, VarInfo> info : definitions.entrySet()) { |
| OptInfo opt = considerCandidate(info.getValue()); |
| if (opt != null) { |
| result.put(opt.aVarInfo.var, opt); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Checks if the variable 'var' with the definitions and referencing statements |
| * (precalculated in 'info') satisfy conditions (iii) and (v), and if it does, |
| * returns a candidate pair {a(=var), b-definition(calculated)} info. |
| * |
| * Note that this pair still need to be checked against other conditions. |
| */ |
| @CheckForNull |
| private static OptInfo considerCandidate(@Nonnull VarInfo info) { |
| |
| // 'a' must be synthetic variable |
| if (!info.var.isSynthetic()) { |
| return null; |
| } |
| |
| // There must be only one single assignment statement s0 : b = a. |
| if (info.refStmts.size() != 1) { |
| return null; |
| } |
| JStatement stmt = info.refStmts.get(0); |
| if (!(stmt instanceof JExpressionStatement)) { |
| return null; |
| } |
| JExpression assignment = ((JExpressionStatement) stmt).getExpr(); |
| if (!(assignment instanceof JAsgOperation)) { |
| return null; |
| } |
| |
| // All defs of a-variable should only be used once in the same assignment |
| for (DefinitionMarker aDef : info.defs) { |
| List<JVariableRef> aRefs = aDef.getUses(); |
| if (aRefs.size() == 1) { |
| if (assignment != aRefs.get(0).getParent()) { |
| return null; |
| } |
| } |
| } |
| |
| // Just confirm that 'assignment' is a reference to the original variable |
| DefinitionMarker bDef = assignment.getMarker(DefinitionMarker.class); |
| if (bDef == null || !bDef.hasValue()) { |
| return null; |
| } |
| JExpression valueExpr = bDef.getValue(); |
| if (!(valueExpr instanceof JVariableRef) || |
| ((JVariableRef) valueExpr).getTarget() != info.var) { |
| return null; |
| } |
| |
| // OK, we guarantee that conditions (iii) and (v) are satisfied \ |
| return new OptInfo(info, bDef); |
| } |
| |
| /* |
| * Helper class implementing traversal CFG |
| * |
| * Lazily calculates and stores flags on each basic block indicating |
| * the latest (in bb) reference to 'a' or 'b' or being entry point. |
| * |
| * Implements cfg traversal to detect conditions (i), (ii) and (iv). |
| */ |
| private static final class CfgHelper { |
| private static final byte BB_NOT_CHECKED_YET = 0; |
| private static final byte BB_ACCESSES_NONE = 1; |
| private static final byte BB_ASSIGNS_OR_READS_B = 2; |
| private static final byte BB_ASSIGNS_A = 4; |
| private static final byte BB_ENTRY_POINT = 8; |
| |
| private final ControlFlowGraph cfg; |
| private final byte[] flags; // Lazily calculated |
| private final JVariable aVar; |
| private final JVariable bVar; |
| |
| CfgHelper(@Nonnull ControlFlowGraph cfg, @Nonnull JVariable aVar, @Nonnull JVariable bVar) { |
| this.cfg = cfg; |
| this.aVar = aVar; |
| this.bVar = bVar; |
| this.flags = new byte[cfg.getBasicBlockMaxId()]; |
| } |
| |
| boolean isCondition1or2Violated(@Nonnull JStatement startStmt) { |
| return traverse( |
| Lists.newArrayList(startStmt), |
| (byte) (BB_ENTRY_POINT | BB_ASSIGNS_OR_READS_B), BB_ASSIGNS_A); |
| } |
| |
| boolean isCondition4Violated(@Nonnull List<JStatement> startStmts) { |
| return traverse( |
| startStmts, BB_ASSIGNS_A, BB_ASSIGNS_OR_READS_B); |
| } |
| |
| private boolean traverse( |
| @Nonnull List<JStatement> startStmts, byte violatingFlags, byte ignorePredecessorFlags) { |
| |
| boolean[] queued = new boolean[flags.length]; |
| LinkedList<BasicBlock> queue = new LinkedList<>(); |
| |
| // Fill in the queue with predecessors of basic blocks including start statements |
| for (JStatement stmt : startStmts) { |
| // NOTE: we don't mark this basic block here as queued since we only analyzed |
| // part of it [0...stmt), we still want to look at this block later if/when |
| // we reach it in traversal to analyze the remaining [stmt...N] statements. |
| // NOTE: If there are no statements accessing 'a' or 'b' in the remaining |
| // section ([stmt...N]), the block will be marked as |
| // BB_ASSIGNS_OR_READS_B since 'b' is assigned or accessed in stmt |
| BasicBlock bb = ControlFlowHelper.getBasicBlock(stmt); |
| if (process(bb, stmt, violatingFlags, ignorePredecessorFlags, queue, queued)) { |
| return true; // Violated in [0...stmt) section! |
| } |
| } |
| |
| // Traverse basic blocks backwards starting at one or more basic blocks queued |
| while (!queue.isEmpty()) { |
| BasicBlock bb = queue.removeFirst(); |
| if (process(bb, null, violatingFlags, ignorePredecessorFlags, queue, queued)) { |
| return true; // Violated in the whole block |
| } |
| } |
| |
| // traversal ended, not failed once |
| return false; |
| } |
| |
| private boolean process( |
| @Nonnull BasicBlock bb, |
| @CheckForNull JStatement stmt, |
| byte violatingFlags, |
| byte ignorePredecessorFlags, |
| @Nonnull LinkedList<BasicBlock> queue, |
| @Nonnull boolean[] queued) { |
| |
| int bbFlag = stmt != null ? computeBasicBlockFlag(bb, stmt) : getBasicBlockFlag(bb); |
| assert bbFlag != BB_NOT_CHECKED_YET; |
| if ((violatingFlags & bbFlag) != 0) { |
| return true; // Condition violated |
| } |
| |
| if ((ignorePredecessorFlags & bbFlag) == 0) { |
| for (BasicBlock bbPredecessor : bb.getPredecessors()) { |
| int id = bbPredecessor.getId(); |
| if (!queued[id]) { |
| queue.addLast(bbPredecessor); |
| queued[id] = true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private byte getBasicBlockFlag(@Nonnull BasicBlock basicBlock) { |
| int id = basicBlock.getId(); |
| byte flag = flags[id]; |
| if (flag == BB_NOT_CHECKED_YET) { |
| flag = computeBasicBlockFlag(basicBlock, null); |
| flags[id] = flag; |
| } |
| return flag; |
| } |
| |
| private byte computeBasicBlockFlag( |
| @Nonnull BasicBlock basicBlock, @CheckForNull JStatement upperLimit) { |
| |
| List<JStatement> statements = basicBlock.getStatements(); |
| for (int i = statements.size() - 1; i >= 0; i--) { |
| JStatement stmt = statements.get(i); |
| |
| // If upper limit statement is specified, ignore everything until we see it |
| if (upperLimit != null) { |
| if (upperLimit == stmt) { |
| upperLimit = null; |
| } |
| continue; // to the next (actually previous) statement |
| } |
| |
| // Assigns A or B? |
| DefinitionMarker dm = ThreeAddressCodeFormUtils.getDefinitionMarker(stmt); |
| if (dm != null) { |
| JVariable variable = dm.getDefinedVariable(); |
| if (variable == aVar) { |
| return BB_ASSIGNS_A; |
| } else if (variable == bVar) { |
| return BB_ASSIGNS_OR_READS_B; |
| } |
| } |
| |
| // Reads B? |
| List<JVariableRef> refs = DefsAndUsesChainOptimizationTools.getUsedVariables(stmt); |
| for (JVariableRef ref : refs) { |
| if (ref.getTarget() == bVar) { |
| return BB_ASSIGNS_OR_READS_B; |
| } |
| } |
| } |
| |
| return cfg.getEntryNode() == basicBlock ? BB_ENTRY_POINT : BB_ACCESSES_NONE; |
| } |
| } |
| |
| /** Check if preconditions stand for this candidate and perform optimization if they do */ |
| private void processCandidate( |
| @Nonnull JMethod method, |
| @Nonnull ControlFlowGraph cfg, |
| @Nonnull LinkedHashMap<JVariable, VarInfo> definitions, |
| @Nonnull OptInfo info) { |
| |
| // Checking preconditions |
| DefinitionMarker bDef = info.bDefinition; |
| JVariable bVariable = bDef.getDefinedVariable(); |
| |
| // Build CFG helper to use for more complex analysis |
| CfgHelper helper = new CfgHelper(cfg, info.aVarInfo.var, bVariable); |
| |
| JStatement s0 = bDef.getStatement(); |
| assert s0 != null; |
| assert definitions.containsKey(info.bDefinition.getDefinedVariable()); |
| |
| // Check for conditions (i) and (ii) |
| if (helper.isCondition1or2Violated(s0)) { |
| return; |
| } |
| |
| // Check for condition (iv) for all references of variable 'b' |
| VarInfo bVarInfo = definitions.get(bVariable); |
| if (helper.isCondition4Violated(bVarInfo.refStmts)) { |
| return; |
| } |
| |
| // Apply optimization for the pair and patch precalculated info |
| tracer.getStatistic(SIMPLIFIED_DEF_USE).incValue(); |
| TransformationRequest tr = new TransformationRequest(method); |
| |
| for (DefinitionMarker aDef : info.aVarInfo.defs) { |
| JVariableRef bRefNew = |
| getNewVarRef(bDef.getDefinedExpr(), aDef.getDefinedExpr().getSourceInfo()); |
| tr.append(new Replace(aDef.getDefinedExpr(), bRefNew)); |
| |
| // Definition of 'a' is becoming a definition of 'b' |
| aDef.resetDefinedVariable(bVariable); |
| // Also all uses of the original b definition are now uses of a new one |
| // Update definitions as well if needed |
| aDef.removeAllUses(); // should only be one |
| for (JVariableRef bDefUse : bDef.getUses()) { |
| aDef.addUse(bDefUse); |
| } |
| } |
| |
| // Update use/def information to reflect the change |
| bDef.removeAllUses(); |
| |
| // Update `definitions` |
| bVarInfo.mergeWith(definitions.get(info.aVarInfo.var)); |
| bVarInfo.defs.remove(bDef); |
| definitions.remove(info.aVarInfo.var); |
| |
| tr.append(new Remove(s0)); |
| tr.commit(); |
| } |
| |
| /** |
| * Process the candidates in order taking into account possible dependencies, |
| * break cycles if any. |
| * |
| * There may be a sequences of the variables that can be optimized, |
| * like in the following case: |
| * |
| * $tmp1 = 1 $tmp1 = 2 |
| * \ / |
| * S0.a: $tmp2 = $tmp1 $tmp2 = 3 |
| * \ / |
| * S0.b: x = $tmp2 |
| * |
| * In this case we want optimizations to be processed is certain order: first take care |
| * of the case when S0 is S0.b, then when S0 is S0.a. This makes it easier to correctly update |
| * pre-collected data as we do optimizations. |
| * |
| * We also want to deterministically break cycles like { $t1 = $t0; $t2 = $t1; $t0 = $t2 } |
| * in case they happen. |
| */ |
| private void processCandidatesWithDependencies( |
| @Nonnull JMethod method, |
| @Nonnull ControlFlowGraph cfg, |
| @Nonnull LinkedHashMap<JVariable, OptInfo> candidates, |
| @Nonnull LinkedHashMap<JVariable, VarInfo> definitions) { |
| |
| Set<OptInfo> queued = Sets.newIdentityHashSet(); |
| for (Map.Entry<JVariable, OptInfo> entry : candidates.entrySet()) { |
| processCandidateWithDependencies( |
| method, cfg, entry.getValue(), candidates, definitions, queued); |
| } |
| } |
| |
| private void processCandidateWithDependencies( |
| @Nonnull JMethod method, |
| @Nonnull ControlFlowGraph cfg, |
| @Nonnull OptInfo candidate, |
| @Nonnull LinkedHashMap<JVariable, OptInfo> candidates, |
| @Nonnull LinkedHashMap<JVariable, VarInfo> definitions, |
| @Nonnull Set<OptInfo> queued) { |
| |
| if (queued.contains(candidate)) { |
| // This candidate is either already processed or there is a dependency cycle |
| // and this one is scheduled to be processed but waits for it's dependencies, |
| // in both cases don't want to process it now. |
| return; |
| } |
| |
| queued.add(candidate); |
| |
| // Check if this candidate has dependency on other, i.e. current candidate's 'b' |
| // variable is an 'a' variable in some other possible optimization. Process that |
| // optimization first |
| JVariable bVar = candidate.bDefinition.getDefinedVariable(); |
| if (candidates.containsKey(bVar)) { |
| // We do have 'b' in our candidate's list, process it first |
| processCandidateWithDependencies( |
| method, cfg, candidates.get(bVar), candidates, definitions, queued); |
| } |
| |
| // Process this candidate |
| processCandidate(method, cfg, definitions, candidate); |
| } |
| |
| @Override |
| public void run(@Nonnull JMethod method) { |
| if (method.isNative() || method.isAbstract() || !filter.accept(this.getClass(), method)) { |
| return; |
| } |
| |
| ControlFlowGraph cfg = method.getMarker(ControlFlowGraph.class); |
| assert cfg != null; |
| |
| // Collect all variables defined in the method with info we are going to use to |
| // analyze them and make optimizations in one pass. |
| // NOTE: we preserve the insertion order to make it deterministic |
| LinkedHashMap<JVariable, VarInfo> definitions = collectDefinitions(cfg); |
| |
| // Define a list of candidates to be optimized which satisfy |
| // conditions (iii) and (v), other conditions will be checked later. |
| // NOTE: we preserve the insertion order to make it deterministic |
| LinkedHashMap<JVariable, OptInfo> candidates = collectCandidates(definitions); |
| |
| // Now handle each candidate pair separately to check the remaining |
| // conditions and apply optimization if they are satisfied. |
| processCandidatesWithDependencies(method, cfg, candidates, definitions); |
| |
| // Remove ReachingDefsMarker on every basic block. |
| for (BasicBlock bb : cfg.getNodes()) { |
| bb.removeMarker(ReachingDefsMarker.class); |
| } |
| } |
| } |