Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,11 @@ public JassProg transformProgToJass() {
// translate flattened intermediate lang to jass:

beginPhase(14, "translate to jass");
optimizer.removeGarbage();
imProg.flatten(imTranslator);
imTranslator.removeEmptyPackageInits();
optimizer.removeGarbage();
imProg.flatten(imTranslator);
getImTranslator().calculateCallRelationsAndUsedVariables();
ImToJassTranslator translator =
new ImToJassTranslator(getImProg(), getImTranslator().getCalledFunctions(), getImTranslator().getMainFunc(), getImTranslator().getConfFunc());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -455,4 +458,83 @@ public boolean hasSideEffects(Element elem) {
Set<ImVar> imVars = directlySetVariables(elem);
return natives.size() + directFuncs.size() + imVars.size() > 0;
}

/**
* Checks if the given element has observable side effects.
* Pure natives can be configured via the predicate.
*/
public boolean hasObservableSideEffects(Element elem, Predicate<ImFunction> isNativeWithoutSideEffects) {
return new ObservableSideEffectChecker(isNativeWithoutSideEffects).hasSideEffects(elem);
}

private final class ObservableSideEffectChecker {
private final Predicate<ImFunction> isNativeWithoutSideEffects;
private final Map<ImFunction, Boolean> cache = new HashMap<>();
private final Set<ImFunction> inProgress = new LinkedHashSet<>();

private ObservableSideEffectChecker(Predicate<ImFunction> isNativeWithoutSideEffects) {
this.isNativeWithoutSideEffects = isNativeWithoutSideEffects;
}

private boolean hasSideEffects(Element elem) {
if (!directlySetVariables(elem).isEmpty()) {
return true;
}
for (ImFunction nativeFunc : calledNatives(elem)) {
if (!isNativeWithoutSideEffects.test(nativeFunc)) {
return true;
}
}
for (ImFunction called : calledFunctions(elem)) {
if (functionHasSideEffects(called)) {
return true;
}
}
return false;
}

private boolean functionHasSideEffects(ImFunction func) {
Boolean cached = cache.get(func);
if (cached != null) {
return cached;
}
if (func.isNative()) {
boolean sideEffect = !isNativeWithoutSideEffects.test(func);
cache.put(func, sideEffect);
return sideEffect;
}
if (!inProgress.add(func)) {
return true;
}
boolean sideEffect = hasGlobalSideEffects(func.getBody());
inProgress.remove(func);
cache.put(func, sideEffect);
return sideEffect;
}

private boolean hasGlobalSideEffects(Element elem) {
for (ImVar var : directlySetVariables(elem)) {
// Some optimization passes temporarily detach vars; in that state isGlobal() throws.
if (isAttachedGlobal(var)) {
return true;
}
}
for (ImFunction nativeFunc : calledNatives(elem)) {
if (!isNativeWithoutSideEffects.test(nativeFunc)) {
return true;
}
}
for (ImFunction called : calledFunctions(elem)) {
if (functionHasSideEffects(called)) {
return true;
}
}
return false;
}

private boolean isAttachedGlobal(ImVar var) {
Element parent = var.getParent();
return parent != null && parent.getParent() instanceof ImProg;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import com.google.common.collect.Lists;
import de.peeeq.wurstio.TimeTaker;
import de.peeeq.wurstscript.WurstOperator;
import de.peeeq.wurstscript.WLogger;
import de.peeeq.wurstscript.intermediatelang.optimizer.BranchMerger;
import de.peeeq.wurstscript.intermediatelang.optimizer.ConstantAndCopyPropagation;
import de.peeeq.wurstscript.intermediatelang.optimizer.LocalMerger;
import de.peeeq.wurstscript.intermediatelang.optimizer.SideEffectAnalyzer;
import de.peeeq.wurstscript.intermediatelang.optimizer.SimpleRewrites;
import de.peeeq.wurstscript.jassIm.*;
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;
Expand Down Expand Up @@ -97,6 +99,7 @@ public void removeGarbage() {
while (changes && iterations++ < 10) {
ImProg prog = trans.imProg();
trans.calculateCallRelationsAndUsedVariables();
SideEffectAnalyzer sideEffectAnalyzer = new SideEffectAnalyzer(prog);

// keep only used variables
int globalsBefore = prog.getGlobals().size();
Expand Down Expand Up @@ -137,25 +140,30 @@ public void visit(ImSet e) {
if (e.getLeft() instanceof ImVarAccess) {
ImVarAccess va = (ImVarAccess) e.getLeft();
if (!trans.getReadVariables().contains(va.getVar()) && !TRVEHelper.protectedVariables.contains(va.getVar().getName())) {
replacements.add(Pair.create(e, Collections.singletonList(e.getRight())));
List<ImExpr> sideEffects = collectSideEffects(e.getRight(), sideEffectAnalyzer);
replacements.add(Pair.create(e, sideEffects));
}
} else if (e.getLeft() instanceof ImVarArrayAccess) {
ImVarArrayAccess va = (ImVarArrayAccess) e.getLeft();
if (!trans.getReadVariables().contains(va.getVar()) && !TRVEHelper.protectedVariables.contains(va.getVar().getName())) {
// IMPORTANT: removeAll() clears parent references
List<ImExpr> exprs = va.getIndexes().removeAll();
exprs.add(e.getRight());
List<ImExpr> exprs = new ArrayList<>();
for (ImExpr index : va.getIndexes()) {
exprs.addAll(collectSideEffects(index, sideEffectAnalyzer));
}
exprs.addAll(collectSideEffects(e.getRight(), sideEffectAnalyzer));
replacements.add(Pair.create(e, exprs));
}
} else if (e.getLeft() instanceof ImTupleSelection) {
ImVar var = TypesHelper.getTupleVar((ImTupleSelection) e.getLeft());
if(var != null && !trans.getReadVariables().contains(var) && !TRVEHelper.protectedVariables.contains(var.getName())) {
replacements.add(Pair.create(e, Collections.singletonList(e.getRight())));
List<ImExpr> sideEffects = collectSideEffects(e.getRight(), sideEffectAnalyzer);
replacements.add(Pair.create(e, sideEffects));
}
} else if(e.getLeft() instanceof ImMemberAccess) {
ImMemberAccess va = ((ImMemberAccess) e.getLeft());
if (!trans.getReadVariables().contains(va.getVar()) && !TRVEHelper.protectedVariables.contains(va.getVar().getName())) {
replacements.add(Pair.create(e, Collections.singletonList(e.getRight())));
List<ImExpr> sideEffects = collectSideEffects(e.getRight(), sideEffectAnalyzer);
replacements.add(Pair.create(e, sideEffects));
}
}
}
Expand All @@ -165,7 +173,9 @@ public void visit(ImSet e) {
for (Pair<ImStmt, List<ImExpr>> pair : replacements) {
changes = true;
ImExpr r;
if (pair.getB().size() == 1) {
if (pair.getB().isEmpty()) {
r = ImHelper.statementExprVoid(JassIm.ImStmts());
} else if (pair.getB().size() == 1) {
r = pair.getB().get(0);
// CRITICAL: Clear parent before reusing the node
r.setParent(null);
Expand All @@ -187,4 +197,76 @@ public void visit(ImSet e) {
}
}
}

private List<ImExpr> collectSideEffects(ImExpr expr, SideEffectAnalyzer analyzer) {
if (expr == null) {
return Collections.emptyList();
}
if (mayTrapAtRuntime(expr)) {
return Collections.singletonList(expr);
}
if (analyzer.hasObservableSideEffects(expr, func -> func.isNative()
&& UselessFunctionCallsRemover.isFunctionWithoutSideEffect(func.getName()))) {
return Collections.singletonList(expr);
}
return Collections.emptyList();
}

private boolean mayTrapAtRuntime(Element elem) {
return mayTrapAtRuntime(elem, new HashMap<>(), new LinkedHashSet<>());
}

private boolean mayTrapAtRuntime(Element elem, Map<ImFunction, Boolean> functionCache, Set<ImFunction> inProgress) {
if (elem instanceof ImFunctionCall) {
ImFunction calledFunc = ((ImFunctionCall) elem).getFunc();
if (functionMayTrapAtRuntime(calledFunc, functionCache, inProgress)) {
return true;
}
} else if (elem instanceof ImMethodCall) {
ImFunction calledFunc = ((ImMethodCall) elem).getMethod().getImplementation();
if (calledFunc == null || functionMayTrapAtRuntime(calledFunc, functionCache, inProgress)) {
return true;
}
}

if (elem instanceof ImOperatorCall) {
ImOperatorCall opCall = (ImOperatorCall) elem;
WurstOperator op = opCall.getOp();
if ((op == WurstOperator.DIV_INT || op == WurstOperator.MOD_INT) && opCall.getArguments().size() >= 2) {
ImExpr denominator = opCall.getArguments().get(1);
// Preserve integer div/mod unless denominator is provably non-zero.
if (!(denominator instanceof ImIntVal) || ((ImIntVal) denominator).getValI() == 0) {
return true;
}
}
}
for (int i = 0; i < elem.size(); i++) {
Element child = elem.get(i);
if (mayTrapAtRuntime(child, functionCache, inProgress)) {
return true;
}
}
return false;
}

private boolean functionMayTrapAtRuntime(ImFunction function, Map<ImFunction, Boolean> functionCache, Set<ImFunction> inProgress) {
if (function.isNative()) {
return false;
}

Boolean cachedResult = functionCache.get(function);
if (cachedResult != null) {
return cachedResult;
}

if (!inProgress.add(function)) {
// Recursive cycles are conservatively treated as potentially trapping.
return true;
}

boolean mayTrap = mayTrapAtRuntime(function.getBody(), functionCache, inProgress);
inProgress.remove(function);
functionCache.put(function, mayTrap);
return mayTrap;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,29 @@ private static ImStatementExpr inSet(ImSet imSet, ImFunction f) {
+ "\nLHS=" + left + "\nRHS=" + right);
}

boolean allLiteral = true;
for (ImExpr r : rhsLeaves) {
if (!isSimpleLiteral(r)) {
allLiteral = false;
break;
}
}

if (allLiteral) {
for (int i = 0; i < lhsLeaves.size(); i++) {
ImLExpr l = lhsLeaves.get(i);
ImType targetT = l.attrTyp();
ImExpr r = rhsLeaves.get(i);
if (r instanceof ImNull) {
r = ImHelper.defaultValueForComplexType(targetT);
}
l.setParent(null);
r.setParent(null);
stmts.add(JassIm.ImSet(imSet.getTrace(), l, r));
}
return ImHelper.statementExprVoid(stmts);
}

// 4) Evaluate RHS leaves first into temps (preserve side-effect order & alias safety)
List<ImVar> temps = new ArrayList<>(rhsLeaves.size());
for (int i = 0; i < rhsLeaves.size(); i++) {
Expand Down Expand Up @@ -485,6 +508,14 @@ private static ImStatementExpr inSet(ImSet imSet, ImFunction f) {
return ImHelper.statementExprVoid(stmts);
}

private static boolean isSimpleLiteral(ImExpr expr) {
return expr instanceof ImBoolVal
|| expr instanceof ImIntVal
|| expr instanceof ImRealVal
|| expr instanceof ImStringVal
|| expr instanceof ImNull;
}

/** Flatten LHS recursively into addressable leaves (ImLExpr), hoisting side-effects */
private static void flattenLhsTuple(ImExpr e, List<ImLExpr> out, ImStmts sideStmts) {
ImExpr x = extractSideEffect(e, sideStmts);
Expand Down
Loading
Loading