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,77 @@ 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)) {
if (var.isGlobal()) {
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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 +98,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 +139,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 +172,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 +196,15 @@ public void visit(ImSet e) {
}
}
}

private List<ImExpr> collectSideEffects(ImExpr expr, SideEffectAnalyzer analyzer) {
if (expr == null) {
return Collections.emptyList();
}
if (analyzer.hasObservableSideEffects(expr, func -> func.isNative()
&& UselessFunctionCallsRemover.isFunctionWithoutSideEffect(func.getName()))) {
return Collections.singletonList(expr);
}
return Collections.emptyList();
}
}
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
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,154 @@ public ImProg translateProg() {
}
}

public void removeEmptyPackageInits() {
Set<ImFunction> emptyInitFunctions = new HashSet<>();
for (ImFunction initFunc : imProg.getFunctions()) {
if (initFunc.getName().startsWith("init_") && isTrivialInitFunction(initFunc)) {
emptyInitFunctions.add(initFunc);
}
}
if (emptyInitFunctions.isEmpty()) {
return;
}

Map<ImVar, ImFunction> initFuncRefs = collectInitFuncRefs();
removeInitCallsFromMain(emptyInitFunctions, initFuncRefs);
removeInitFuncRefsFromGlobals(emptyInitFunctions);
imProg.getFunctions().removeIf(emptyInitFunctions::contains);
initFuncMap.values().removeIf(emptyInitFunctions::contains);
}

private boolean isTrivialInitFunction(ImFunction initFunc) {
if (initFunc.getBody().isEmpty()) {
return true;
}
if (initFunc.getBody().size() != 1) {
return false;
}
ImStmt stmt = initFunc.getBody().get(0);
if (!(stmt instanceof ImReturn)) {
return false;
}
ImExprOpt returnValue = ((ImReturn) stmt).getReturnValue();
if (returnValue instanceof ImNoExpr) {
return true;
}
return returnValue instanceof ImBoolVal && ((ImBoolVal) returnValue).getValB();
}

private void removeInitCallsFromMain(Set<ImFunction> emptyInitFunctions, Map<ImVar, ImFunction> initFuncRefs) {
ImFunction main = getMainFunc();
if (main == null) {
return;
}

ImFunction native_TriggerAddCondition = getNativeFunc("TriggerAddCondition");
ImFunction native_Condition = getNativeFunc("Condition");
ImFunction native_ClearTrigger = getNativeFunc("TriggerClearConditions");

ImStmts mainBody = main.getBody();
for (int i = 0; i < mainBody.size(); i++) {
ImStmt stmt = mainBody.get(i);
if (stmt instanceof ImFunctionCall) {
ImFunctionCall call = (ImFunctionCall) stmt;
if (emptyInitFunctions.contains(call.getFunc())) {
mainBody.remove(i--);
continue;
}
if (native_TriggerAddCondition != null && native_Condition != null
&& call.getFunc() == native_TriggerAddCondition
&& hasInitCondition(call, native_Condition, emptyInitFunctions, initFuncRefs)) {
if (i + 2 < mainBody.size()
&& mainBody.get(i + 1) instanceof ImIf
&& isTriggerClear(mainBody.get(i + 2), native_ClearTrigger)) {
mainBody.remove(i + 2);
mainBody.remove(i + 1);
mainBody.remove(i--);
}
}
}
}
}

private void removeInitFuncRefsFromGlobals(Set<ImFunction> emptyInitFunctions) {
ImFunction globalInit = getGlobalInitFunc();
if (globalInit == null) {
return;
}
ImStmts body = globalInit.getBody();
for (int i = 0; i < body.size(); i++) {
ImStmt stmt = body.get(i);
if (!(stmt instanceof ImSet)) {
continue;
}
ImExpr right = ((ImSet) stmt).getRight();
if (right instanceof ImFuncRef && emptyInitFunctions.contains(((ImFuncRef) right).getFunc())) {
body.remove(i--);
}
}
}

private Map<ImVar, ImFunction> collectInitFuncRefs() {
ImFunction globalInit = getGlobalInitFunc();
if (globalInit == null) {
return Collections.emptyMap();
}
Map<ImVar, ImFunction> refs = new HashMap<>();
ImStmts body = globalInit.getBody();
for (int i = 0; i < body.size(); i++) {
ImStmt stmt = body.get(i);
if (!(stmt instanceof ImSet)) {
continue;
}
ImSet set = (ImSet) stmt;
if (!(set.getLeft() instanceof ImVarAccess)) {
continue;
}
if (!(set.getRight() instanceof ImFuncRef)) {
continue;
}
refs.put(((ImVarAccess) set.getLeft()).getVar(), ((ImFuncRef) set.getRight()).getFunc());
}
return refs;
}

private boolean hasInitCondition(ImFunctionCall call, ImFunction nativeCondition, Set<ImFunction> emptyInitFunctions,
Map<ImVar, ImFunction> initFuncRefs) {
if (call.getArguments().size() < 2) {
return false;
}
ImExpr conditionExpr = call.getArguments().get(1);
if (!(conditionExpr instanceof ImFunctionCall)) {
return false;
}
ImFunctionCall conditionCall = (ImFunctionCall) conditionExpr;
if (conditionCall.getFunc() != nativeCondition) {
return false;
}
if (conditionCall.getArguments().size() != 1) {
return false;
}
ImExpr argument = conditionCall.getArguments().get(0);
if (argument instanceof ImFuncRef) {
ImFuncRef funcRef = (ImFuncRef) argument;
return emptyInitFunctions.contains(funcRef.getFunc());
}
if (argument instanceof ImVarAccess) {
ImVar var = ((ImVarAccess) argument).getVar();
ImFunction target = initFuncRefs.get(var);
return target != null && emptyInitFunctions.contains(target);
}
return false;
}

private boolean isTriggerClear(ImStmt stmt, ImFunction nativeClearTrigger) {
if (nativeClearTrigger == null) {
return false;
}
return stmt instanceof ImFunctionCall && ((ImFunctionCall) stmt).getFunc() == nativeClearTrigger;
}

/**
* Number all the compiletime functions and expressions,
* so that the one with the lowest number can be executed first.
Expand Down
Loading
Loading