Skip to content

Commit

Permalink
Merge c70709f into 060d5bd
Browse files Browse the repository at this point in the history
  • Loading branch information
Frotty committed May 24, 2018
2 parents 060d5bd + c70709f commit 8dedfa1
Show file tree
Hide file tree
Showing 6 changed files with 419 additions and 7 deletions.
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ jdk:
env:
- TERM=dumb

before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/

before_install:
- sudo apt-get -qq update
- sudo apt-get install -y wine
Expand Down
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());
}

}
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,20 @@ private void optimizeElement(Element elem) {
// optimize children:
for (int i = 0; i < elem.size(); i++) {
optimizeElement(elem.get(i));
if (i > 0) {
Element lookback = elem.get(i - 1);
if (elem.get(i) instanceof ImExitwhen && lookback instanceof ImExitwhen) {
optimizeConsecutiveExitWhen((ImExitwhen) lookback, (ImExitwhen) elem.get(i));
}

if (elem.get(i) instanceof ImSet && lookback instanceof ImSet) {
optimizeConsecutiveSet((ImSet) lookback, (ImSet) elem.get(i));
}
}
}
if (elem instanceof ImOperatorCall) {
ImOperatorCall opc = (ImOperatorCall) elem;
optimizeElement(opc.getArguments());
optimizeOpCall(opc);
} else if (elem instanceof ImIf) {
ImIf imIf = (ImIf) elem;
Expand All @@ -88,6 +99,13 @@ private void optimizeElement(Element elem) {

}

private void optimizeConsecutiveExitWhen(ImExitwhen lookback, ImExitwhen element) {
lookback.getCondition().setParent(null);
lookback.getCondition().replaceBy(JassIm.ImOperatorCall(WurstOperator.OR, JassIm.ImExprs(lookback.getCondition(), element.getCondition())));
element.replaceBy(JassIm.ImNull());
totalRewrites++;
}

private void optimizeExitwhen(ImExitwhen imExitwhen) {
ImExpr expr = imExitwhen.getCondition();
if (expr instanceof ImBoolVal) {
Expand Down Expand Up @@ -470,4 +488,40 @@ private void optimizeIf(ImIf imIf) {
}
}

/**
* Optimizes
* set We=We-1
* set We=We-1
* like code that is created by the branch merger
*/
private void optimizeConsecutiveSet(ImSet imSet, ImSet imSet2) {
ImVar leftVar1 = imSet.getLeft();
ImVar leftVar2 = imSet2.getLeft();

ImExpr rightExpr1 = imSet.getRight();
ImExpr rightExpr2 = imSet2.getRight();

if (leftVar1.structuralEquals(leftVar2)) {
if (rightExpr1 instanceof ImOperatorCall && rightExpr2 instanceof ImOperatorCall) {
ImOperatorCall rightOpCall1 = (ImOperatorCall) rightExpr1;
ImOperatorCall rightOpCall2 = (ImOperatorCall) rightExpr2;
if (rightOpCall1.getArguments().size() == 2 && rightOpCall2.getArguments().size() == 2) {
if (rightOpCall1.getArguments().get(0) instanceof ImVarAccess && rightOpCall2.getArguments().get(0) instanceof ImVarAccess) {
ImVarAccess imVarAccess1 = (ImVarAccess) rightOpCall1.getArguments().get(0);
ImVarAccess imVarAccess2 = (ImVarAccess) rightOpCall2.getArguments().get(0);
if (imVarAccess1.getVar().structuralEquals(leftVar1) && imVarAccess2.getVar().structuralEquals(leftVar2)) {
if (rightOpCall1.getArguments().get(1) instanceof ImConst && rightOpCall2.getArguments().get(1) instanceof ImConst) {
rightOpCall1.setParent(null);
imVarAccess2.replaceBy(rightOpCall1);
imSet.replaceBy(JassIm.ImNull());
totalRewrites++;
}
}

}
}
}
}
}

}

0 comments on commit 8dedfa1

Please sign in to comment.