-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
419 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
...rstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/BranchMerger.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package de.peeeq.wurstscript.intermediatelang.optimizer; | ||
|
||
import com.google.common.collect.LinkedListMultimap; | ||
import com.google.common.collect.Lists; | ||
import com.google.common.collect.Multimap; | ||
import de.peeeq.wurstscript.intermediatelang.optimizer.ControlFlowGraph.Node; | ||
import de.peeeq.wurstscript.jassIm.*; | ||
import de.peeeq.wurstscript.translation.imtranslation.ImTranslator; | ||
import de.peeeq.wurstscript.utils.Pair; | ||
import de.peeeq.wurstscript.utils.Utils; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
|
||
import java.util.List; | ||
|
||
/** | ||
* merges identical nodes in branches if possible without side effects | ||
* <p> | ||
* the input must be a flattened program | ||
*/ | ||
public class BranchMerger { | ||
private final SideEffectAnalyzer sideEffectAnalyzer; | ||
public int totalLocalsMerged = 0; | ||
private final ImProg prog; | ||
private final ImTranslator trans; | ||
|
||
public BranchMerger(ImTranslator trans) { | ||
this.prog = trans.getImProg(); | ||
this.trans = trans; | ||
this.sideEffectAnalyzer = new SideEffectAnalyzer(prog); | ||
} | ||
|
||
public void optimize() { | ||
for (ImFunction func : prog.getFunctions()) { | ||
optimizeFunc(func); | ||
} | ||
} | ||
|
||
private void optimizeFunc(ImFunction func) { | ||
mergeBranches(func); | ||
} | ||
|
||
|
||
private void mergeBranches(ImFunction func) { | ||
ControlFlowGraph cfg = new ControlFlowGraph(func.getBody()); | ||
|
||
// init in and out with empty sets | ||
for (Node n : cfg.getNodes()) { | ||
@Nullable ImStmt stmt = n.getStmt(); | ||
if (stmt != null && stmt.getParent() != null && stmt.getParent() instanceof ImIf) { | ||
ImIf imIf = (ImIf) stmt.getParent(); | ||
if (n.getPredecessors().size() <= 1 && n.getSuccessors().size() == 2) { | ||
Node leftStmt = n.getSuccessors().get(0); | ||
Node rightStmt = n.getSuccessors().get(1); | ||
if (leftStmt.getStmt() != null && rightStmt.getStmt() != null && leftStmt.getStmt().toString().equals(rightStmt.getStmt().toString())) { | ||
if (n.getPredecessors().size() == 1) { | ||
// Possible match. At last check if condition causes sideeffects. | ||
if (sideEffectsCanAffectNode(n, leftStmt)) { | ||
|
||
ImStmt mergedStmt = leftStmt.getStmt(); | ||
mergedStmt.setParent(null); | ||
|
||
ImIf oldIf = imIf.copy(); | ||
oldIf.getThenBlock().get(0).replaceBy(JassIm.ImNull()); | ||
oldIf.getElseBlock().get(0).replaceBy(JassIm.ImNull()); | ||
imIf.replaceBy(JassIm.ImStatementExpr(JassIm.ImStmts(mergedStmt, oldIf), JassIm.ImNull())); | ||
} | ||
} | ||
} | ||
} | ||
// if the first statement of each branch of the if is the same, it can be moved before the branch | ||
} | ||
} | ||
} | ||
|
||
|
||
/** Checking if executing stmtNode could affect the condition of the ifNode. */ | ||
private boolean sideEffectsCanAffectNode(Node ifNode, Node stmtNode) { | ||
return sideEffectAnalyzer.mightAffect(ifNode.getStmt(), stmtNode.getStmt()); | ||
} | ||
|
||
} |
210 changes: 210 additions & 0 deletions
210
...ipt/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/SideEffectAnalyzer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
package de.peeeq.wurstscript.intermediatelang.optimizer; | ||
|
||
import com.google.common.collect.LinkedHashMultimap; | ||
import com.google.common.collect.LinkedListMultimap; | ||
import com.google.common.collect.Multimap; | ||
import de.peeeq.wurstscript.jassIm.*; | ||
import de.peeeq.wurstscript.utils.Utils; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.LinkedHashSet; | ||
import java.util.Set; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
/** | ||
* Analyzes a program for side-effects | ||
*/ | ||
public class SideEffectAnalyzer { | ||
|
||
private final ImProg prog; | ||
// f -> set of functions directly called by f | ||
private Multimap<ImFunction, ImFunction> callRelation; | ||
// f -> set of functions directly and transitively called by f | ||
private Multimap<ImFunction, ImFunction> callRelationTr; | ||
// f -> global variables directly used in f | ||
private Multimap<ImFunction, ImVar> usedGlobals; | ||
|
||
public SideEffectAnalyzer(ImProg prog) { | ||
this.prog = prog; | ||
} | ||
|
||
/** | ||
* @return f -> set of functions directly called by f | ||
*/ | ||
public Multimap<ImFunction, ImFunction> getCallRelation() { | ||
if (callRelation != null) { | ||
return callRelation; | ||
} | ||
callRelation = LinkedListMultimap.create(); | ||
for (ImFunction caller : prog.getFunctions()) { | ||
callRelation.putAll(caller, directlyCalledFunctions(caller)); | ||
} | ||
return callRelation; | ||
} | ||
|
||
/** | ||
* @return f -> set of functions directly and transitively called by f | ||
*/ | ||
public Multimap<ImFunction, ImFunction> getCallRelationTr() { | ||
if (callRelationTr != null) { | ||
return callRelationTr; | ||
} | ||
callRelationTr = Utils.transientClosure(getCallRelation()); | ||
return callRelationTr; | ||
} | ||
|
||
/** | ||
* @return f -> global variables directly used in f | ||
*/ | ||
public Multimap<ImFunction, ImVar> getUsedGlobals() { | ||
if (usedGlobals != null) { | ||
return usedGlobals; | ||
} | ||
usedGlobals = LinkedHashMultimap.create(); | ||
for (ImFunction function : prog.getFunctions()) { | ||
for (ImVar v : directlyUsedVariables(function)) { | ||
if (v.isGlobal()) { | ||
usedGlobals.put(function, v); | ||
} | ||
} | ||
} | ||
return usedGlobals; | ||
} | ||
|
||
|
||
/** | ||
* Functions directly or indirectly called in e | ||
*/ | ||
public Set<ImFunction> calledFunctions(Element e) { | ||
return calledFunctionsStream(e) | ||
.collect(Collectors.toSet()); | ||
} | ||
|
||
/** | ||
* Functions directly or indirectly called in e | ||
*/ | ||
private Stream<ImFunction> calledFunctionsStream(Element e) { | ||
return directlyCalledFunctions(e).stream() | ||
.flatMap(f -> Stream.concat(Stream.of(f), getCallRelationTr().get(f).stream())); | ||
} | ||
|
||
/** | ||
* Natives directly or indirectly called in e | ||
*/ | ||
public Set<ImFunction> calledNatives(Element e) { | ||
return calledFunctionsStream(e) | ||
.filter(ImFunction::isNative) | ||
.collect(Collectors.toSet()); | ||
} | ||
|
||
/** | ||
* Variables directly or indirectly (via called functions) used in e | ||
* Does not consider variables used because of natives doing stuff (e.g. ForGroup callback) | ||
*/ | ||
public Set<ImVar> usedVariables(Element e) { | ||
Stream<ImVar> indirectGlobals = calledFunctionsStream(e) | ||
.flatMap(f -> getUsedGlobals().get(f).stream()); | ||
return Stream.concat(indirectGlobals, directlyUsedVariables(e).stream()) | ||
.collect(Collectors.toSet()); | ||
} | ||
|
||
|
||
/** | ||
* Functions directly called in e | ||
*/ | ||
public Set<ImFunction> directlyCalledFunctions(Element e) { | ||
Set<ImFunction> calledFunctions = new LinkedHashSet<>(); | ||
e.accept(new ImFunction.DefaultVisitor() { | ||
|
||
@Override | ||
public void visit(ImFunctionCall c) { | ||
super.visit(c); | ||
calledFunctions.add(c.getFunc()); | ||
} | ||
}); | ||
return calledFunctions; | ||
|
||
} | ||
|
||
/** | ||
* Variables directly used in e | ||
*/ | ||
public Set<ImVar> directlyUsedVariables(Element e) { | ||
Set<ImVar> imVars = new LinkedHashSet<>(); | ||
e.accept(new ImStmt.DefaultVisitor() { | ||
|
||
@Override | ||
public void visit(ImVarAccess va) { | ||
super.visit(va); | ||
imVars.add(va.getVar()); | ||
} | ||
|
||
@Override | ||
public void visit(ImVarArrayAccess va) { | ||
super.visit(va); | ||
imVars.add(va.getVar()); | ||
} | ||
|
||
@Override | ||
public void visit(ImVarArrayMultiAccess va) { | ||
super.visit(va); | ||
imVars.add(va.getVar()); | ||
} | ||
|
||
@Override | ||
public void visit(ImMemberAccess va) { | ||
super.visit(va); | ||
imVars.add(va.getVar()); | ||
} | ||
|
||
@Override | ||
public void visit(ImSet va) { | ||
super.visit(va); | ||
imVars.add(va.getLeft()); | ||
} | ||
|
||
@Override | ||
public void visit(ImSetTuple va) { | ||
super.visit(va); | ||
imVars.add(va.getLeft()); | ||
} | ||
|
||
@Override | ||
public void visit(ImSetArrayTuple va) { | ||
super.visit(va); | ||
imVars.add(va.getLeft()); | ||
} | ||
|
||
@Override | ||
public void visit(ImVarargLoop va) { | ||
super.visit(va); | ||
imVars.add(va.getLoopVar()); | ||
} | ||
|
||
}); | ||
return imVars; | ||
} | ||
|
||
|
||
/** | ||
* Checks if two statements might affect each other. | ||
* When this returns true, it is certain that it does not matter whether stmt1 or stmt2 are called first | ||
* | ||
* The only difference between executing stmt1; stmt2 vs stmt2; stmt1 would be if one of the statement | ||
* crashes and thus the second statement would not be executed. | ||
* But for optimizations, we assume the program already is correct and thus we can ignore crashes. | ||
*/ | ||
public boolean mightAffect(ImStmt stmt1, ImStmt stmt2) { | ||
if (!calledNatives(stmt1).isEmpty() || !calledNatives(stmt2).isEmpty()) { | ||
// there are natives that can affect other natives | ||
// be safe | ||
return true; | ||
} | ||
Set<ImVar> used1 = usedVariables(stmt1); | ||
Set<ImVar> used2 = usedVariables(stmt2); | ||
|
||
// check that there are no variables, that both use | ||
return used1.stream().noneMatch(used2::contains); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.