Skip to content

Commit 45c8ef0

Browse files
committed
Use allocated pressure for Lua helper retries
1 parent 52eff76 commit 45c8ef0

4 files changed

Lines changed: 108 additions & 26 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/LocalMerger.java

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ private void optimizeFunctions(List<ImFunction> functions) {
4242
public String getName() { return "Local variables merged"; }
4343

4444
void optimizeFunc(ImFunction func) {
45-
Map<ImStmt, Set<ImVar>> livenessInfo = calculateLiveness(func);
45+
LivenessAnalysis liveness = analyzeLiveness(func);
46+
Map<ImStmt, Set<ImVar>> livenessInfo = liveness.liveOut;
4647
eliminateDeadCode(livenessInfo);
47-
mergeLocals(livenessInfo, func);
48+
mergeLocals(livenessInfo, liveness.liveAtEntry, func);
4849
}
4950

5051
void optimizeFunc(ImFunction func, LocalPlayerContextAnalyzer analyzer) {
@@ -54,8 +55,10 @@ void optimizeFunc(ImFunction func, LocalPlayerContextAnalyzer analyzer) {
5455

5556
private boolean canMerge(ImType a, ImType b) { return a.equalsType(b); }
5657

57-
private void mergeLocals(Map<ImStmt, Set<ImVar>> livenessInfo, ImFunction func) {
58-
Map<ImVar, java.util.Set<ImVar>> interference = calculateInterferenceGraph(livenessInfo, func);
58+
private void mergeLocals(Map<ImStmt, Set<ImVar>> livenessInfo, Set<ImVar> liveAtEntry,
59+
ImFunction func) {
60+
Map<ImVar, java.util.Set<ImVar>> interference =
61+
calculateInterferenceGraph(livenessInfo, liveAtEntry, func);
5962

6063
Map<ImVar, Integer> declarationOrder = new IdentityHashMap<>();
6164
int nextOrder = 0;
@@ -168,7 +171,7 @@ private static int removeUnusedLocals(ImFunction f) {
168171
}
169172

170173
private Map<ImVar, java.util.Set<ImVar>> calculateInterferenceGraph(
171-
Map<ImStmt, Set<ImVar>> livenessInfo, ImFunction func) {
174+
Map<ImStmt, Set<ImVar>> livenessInfo, Set<ImVar> liveAtEntry, ImFunction func) {
172175
Map<ImVar, java.util.Set<ImVar>> graph = new LinkedHashMap<>();
173176
for (ImVar parameter : func.getParameters()) {
174177
graph.put(parameter, new ObjectOpenHashSet<>());
@@ -177,8 +180,6 @@ private Map<ImVar, java.util.Set<ImVar>> calculateInterferenceGraph(
177180
graph.put(local, new ObjectOpenHashSet<>());
178181
}
179182

180-
java.util.Set<ImVar> explicitlyDefined = Collections.newSetFromMap(new IdentityHashMap<>());
181-
182183
// A definition interferes with every compatible value that remains live after it.
183184
// Building only those edges is equivalent to cliquing every live set, while avoiding
184185
// the old O(statements * liveValues^2) behavior on large inlined functions.
@@ -187,7 +188,6 @@ private Map<ImVar, java.util.Set<ImVar>> calculateInterferenceGraph(
187188
if (defined.isEmpty()) {
188189
continue;
189190
}
190-
explicitlyDefined.addAll(defined);
191191
for (int i = 0; i < defined.size(); i++) {
192192
ImVar definition = defined.get(i);
193193
java.util.Set<ImVar> neighbors = graph.computeIfAbsent(definition, ignored -> new ObjectOpenHashSet<>());
@@ -210,12 +210,12 @@ private Map<ImVar, java.util.Set<ImVar>> calculateInterferenceGraph(
210210
}
211211
}
212212

213-
// Parameters and locals with no explicit assignment receive their values at function
214-
// entry. Model that simultaneous definition so a warning-only read of an uninitialized
215-
// local cannot be colored onto a parameter (or another implicit entry value).
213+
// A local live at entry is read before every control-flow path has assigned it. Its
214+
// target-default value must remain distinct from every incoming parameter and from the
215+
// other entry-live locals, even if a later assignment eventually defines it.
216216
List<ImVar> entryDefinitions = new ArrayList<>(func.getParameters());
217217
for (ImVar local : func.getLocals()) {
218-
if (!explicitlyDefined.contains(local)) {
218+
if (liveAtEntry.contains(local)) {
219219
entryDefinitions.add(local);
220220
}
221221
}
@@ -336,6 +336,10 @@ private static boolean hasSideEffects(Element e) {
336336
* over the strongly connected components of the control flow graph.
337337
*/
338338
public Map<ImStmt, Set<ImVar>> calculateLiveness(ImFunction func) {
339+
return analyzeLiveness(func).liveOut;
340+
}
341+
342+
private LivenessAnalysis analyzeLiveness(ImFunction func) {
339343
// 1. Build Control Flow Graph
340344
ControlFlowGraph cfg = new ControlFlowGraph(func.getBody());
341345
final List<Node> nodes = cfg.getNodes();
@@ -473,6 +477,19 @@ protected Collection<Node> getIncidentNodes(Node t) {
473477
result.put(stmt, io.vavr.collection.HashSet.ofAll(out[i]));
474478
}
475479
}
476-
return result;
480+
Set<ImVar> liveAtEntry = N == 0
481+
? io.vavr.collection.HashSet.empty()
482+
: io.vavr.collection.HashSet.ofAll(in[0]);
483+
return new LivenessAnalysis(result, liveAtEntry);
484+
}
485+
486+
private static final class LivenessAnalysis {
487+
private final Map<ImStmt, Set<ImVar>> liveOut;
488+
private final Set<ImVar> liveAtEntry;
489+
490+
private LivenessAnalysis(Map<ImStmt, Set<ImVar>> liveOut, Set<ImVar> liveAtEntry) {
491+
this.liveOut = liveOut;
492+
this.liveAtEntry = liveAtEntry;
493+
}
477494
}
478495
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imoptimizer/ImInliner.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,31 +75,26 @@ public int inlineLuaDivModHelpersWithinLocalBudget() {
7575
prog.flatten(translator);
7676
int changed = 0;
7777
for (ImFunction function : sortedFunctions(ImHelper.calculateFunctionsOfProg(prog))) {
78-
int[] declarations = {function.getParameters().size() + function.getLocals().size()
79-
+ backendGeneratedLuaLocals(function)};
80-
changed += inlineLuaDivModHelpers(function, function, declarations);
78+
LuaRegisterBudget budget = new LuaRegisterBudget(function);
79+
changed += inlineLuaDivModHelpers(function, function, budget);
8180
}
8281
return changed;
8382
}
8483

85-
private int inlineLuaDivModHelpers(ImFunction function, Element element, int[] declarations) {
84+
private int inlineLuaDivModHelpers(ImFunction function, Element element, LuaRegisterBudget budget) {
8685
int changed = 0;
8786
for (int i = 0; i < element.size(); i++) {
8887
Element child = element.get(i);
8988
if (child instanceof ImFunctionCall call && isLuaDivModHelper(call.getFunc())) {
9089
ImFunction callee = call.getFunc();
91-
int controlLocals = maxOneReturn(callee)
92-
? 0
93-
: 1 + (callee.getReturnType() instanceof ImVoid ? 0 : 1);
94-
int addedDeclarations = callee.getParameters().size() + callee.getLocals().size() + controlLocals;
95-
if (declarations[0] + addedDeclarations <= LUA_INLINE_REGISTER_BUDGET) {
90+
if (budget.fits(call, callee)) {
91+
budget.recordInline(call, callee);
9692
inlineCall(function, element, i, call);
97-
declarations[0] += addedDeclarations;
9893
changed++;
9994
child = element.get(i);
10095
}
10196
}
102-
changed += inlineLuaDivModHelpers(function, child, declarations);
97+
changed += inlineLuaDivModHelpers(function, child, budget);
10398
}
10499
return changed;
105100
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imoptimizer/ImOptimizer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public int inlineLuaDivModHelpersWithinLocalBudget() {
7575

7676
public void localOptimizations() {
7777
totalCount.clear();
78+
optCount = 1;
7879

7980
removeGarbage();
8081

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/OptimizerTests.java

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.google.common.base.Charsets;
44
import com.google.common.io.Files;
5+
import de.peeeq.wurstio.TimeTaker;
56
import de.peeeq.wurstio.UtilsIO;
67
import de.peeeq.wurstscript.RunArgs;
78
import de.peeeq.wurstscript.ast.Ast;
@@ -13,6 +14,7 @@
1314
import de.peeeq.wurstscript.intermediatelang.optimizer.SideEffectAnalyzer;
1415
import de.peeeq.wurstscript.jassIm.*;
1516
import de.peeeq.wurstscript.translation.imoptimizer.ImInliner;
17+
import de.peeeq.wurstscript.translation.imoptimizer.ImOptimizer;
1618
import de.peeeq.wurstscript.translation.imtranslation.ImTranslator;
1719
import de.peeeq.wurstscript.translation.imtranslation.FunctionFlagEnum;
1820
import de.peeeq.wurstscript.types.TypesHelper;
@@ -1557,9 +1559,10 @@ public void localMergerKeepsImplicitEntryLocalSeparateFromParameter() {
15571559
ImFunctionCall call = JassIm.ImFunctionCall(model, sink, JassIm.ImTypeArguments(),
15581560
JassIm.ImExprs(JassIm.ImVarAccess(parameter), JassIm.ImVarAccess(implicit)), false,
15591561
de.peeeq.wurstscript.translation.imtranslation.CallType.NORMAL);
1562+
ImSet laterDefinition = JassIm.ImSet(model, JassIm.ImVarAccess(implicit), JassIm.ImIntVal(1));
15601563
ImFunction caller = JassIm.ImFunction(model, "caller", JassIm.ImTypeVars(),
15611564
JassIm.ImVars(parameter), JassIm.ImVoid(), JassIm.ImVars(implicit),
1562-
JassIm.ImStmts(call), Collections.emptyList());
1565+
JassIm.ImStmts(call, laterDefinition), Collections.emptyList());
15631566
prog.getFunctions().add(sink);
15641567
prog.getFunctions().add(caller);
15651568

@@ -1572,6 +1575,28 @@ public void localMergerKeepsImplicitEntryLocalSeparateFromParameter() {
15721575
"function-entry values must not be assigned the same allocation slot");
15731576
}
15741577

1578+
@Test
1579+
public void repeatedLocalOptimizationStartsANewIteration() {
1580+
WurstModel model = Ast.WurstModel();
1581+
ImTranslator translator = new ImTranslator(model, false, new RunArgs());
1582+
ImFunction main = JassIm.ImFunction(model, "main", JassIm.ImTypeVars(), JassIm.ImVars(),
1583+
JassIm.ImVoid(), JassIm.ImVars(), JassIm.ImStmts(), Collections.emptyList());
1584+
ImFunction config = JassIm.ImFunction(model, "config", JassIm.ImTypeVars(), JassIm.ImVars(),
1585+
JassIm.ImVoid(), JassIm.ImVars(), JassIm.ImStmts(), Collections.emptyList());
1586+
translator.getImProg().getFunctions().add(main);
1587+
translator.getImProg().getFunctions().add(config);
1588+
translator.setMainFunc(main);
1589+
translator.setConfigFunc(config);
1590+
ImOptimizer optimizer = new ImOptimizer(new TimeTaker.Default(), translator);
1591+
1592+
optimizer.localOptimizations();
1593+
main.getLocals().add(JassIm.ImVar(model, TypesHelper.imInt(), "lateUnused", false));
1594+
optimizer.localOptimizations();
1595+
1596+
assertTrue(main.getLocals().isEmpty(),
1597+
"a second local-optimization invocation must execute its passes");
1598+
}
1599+
15751600
@Test
15761601
public void luaArithmeticHelperRetryRespectsFunctionLocalBudget() {
15771602
WurstModel model = Ast.WurstModel();
@@ -1587,7 +1612,7 @@ public void luaArithmeticHelperRetryRespectsFunctionLocalBudget() {
15871612
translator.luaModIntFunc = helper;
15881613

15891614
ImVars callerParameters = JassIm.ImVars();
1590-
for (int i = 0; i < 170; i++) {
1615+
for (int i = 0; i < 177; i++) {
15911616
callerParameters.add(JassIm.ImVar(model, TypesHelper.imInt(), "p" + i, false));
15921617
}
15931618
ImVar result = JassIm.ImVar(model, TypesHelper.imInt(), "result", false);
@@ -1603,10 +1628,21 @@ public void luaArithmeticHelperRetryRespectsFunctionLocalBudget() {
16031628
JassIm.ImExprs(JassIm.ImIntVal(7), JassIm.ImIntVal(3)), false,
16041629
de.peeeq.wurstscript.translation.imtranslation.CallType.NORMAL);
16051630
callerBody.add(JassIm.ImSet(model, JassIm.ImVarAccess(result), call));
1631+
ImVars sinkParameters = JassIm.ImVars();
1632+
ImExprs sinkArguments = JassIm.ImExprs();
1633+
for (int i = 0; i < callerParameters.size(); i++) {
1634+
sinkParameters.add(JassIm.ImVar(model, TypesHelper.imInt(), "value" + i, false));
1635+
sinkArguments.add(JassIm.ImVarAccess(callerParameters.get(i)));
1636+
}
1637+
ImFunction sink = JassIm.ImFunction(model, "sink", JassIm.ImTypeVars(), sinkParameters,
1638+
JassIm.ImVoid(), JassIm.ImVars(), JassIm.ImStmts(), Collections.emptyList());
1639+
callerBody.add(JassIm.ImFunctionCall(model, sink, JassIm.ImTypeArguments(), sinkArguments,
1640+
false, de.peeeq.wurstscript.translation.imtranslation.CallType.NORMAL));
16061641
ImFunction caller = JassIm.ImFunction(model, "caller", JassIm.ImTypeVars(), callerParameters,
16071642
JassIm.ImVoid(), callerLocals, callerBody,
16081643
Collections.emptyList());
16091644
prog.getFunctions().add(helper);
1645+
prog.getFunctions().add(sink);
16101646
prog.getFunctions().add(caller);
16111647

16121648
assertEquals(0, new ImInliner(translator).inlineLuaDivModHelpersWithinLocalBudget());
@@ -1615,6 +1651,39 @@ public void luaArithmeticHelperRetryRespectsFunctionLocalBudget() {
16151651
"the late retry must retain the helper when declarations exceed the safe budget");
16161652
}
16171653

1654+
@Test
1655+
public void luaArithmeticHelperRetryReusesSequentialSlots() {
1656+
WurstModel model = Ast.WurstModel();
1657+
ImTranslator translator = new ImTranslator(model, false, new RunArgs().with("-lua"));
1658+
ImProg prog = translator.getImProg();
1659+
ImVar helperA = JassIm.ImVar(model, TypesHelper.imInt(), "a", false);
1660+
ImVar helperB = JassIm.ImVar(model, TypesHelper.imInt(), "b", false);
1661+
ImFunction helper = JassIm.ImFunction(model, "__wurst_modInt", JassIm.ImTypeVars(),
1662+
JassIm.ImVars(helperA, helperB), TypesHelper.imInt(), JassIm.ImVars(),
1663+
JassIm.ImStmts(JassIm.ImReturn(model, JassIm.ImVarAccess(helperA))),
1664+
Collections.emptyList());
1665+
translator.luaModIntFunc = helper;
1666+
ImVars parameters = JassIm.ImVars();
1667+
for (int i = 0; i < 187; i++) {
1668+
parameters.add(JassIm.ImVar(model, TypesHelper.imInt(), "p" + i, false));
1669+
}
1670+
ImVar result = JassIm.ImVar(model, TypesHelper.imInt(), "result", false);
1671+
ImFunction caller = JassIm.ImFunction(model, "caller", JassIm.ImTypeVars(), parameters,
1672+
JassIm.ImVoid(), JassIm.ImVars(result), JassIm.ImStmts(
1673+
JassIm.ImSet(model, JassIm.ImVarAccess(result), JassIm.ImFunctionCall(model, helper,
1674+
JassIm.ImTypeArguments(), JassIm.ImExprs(JassIm.ImIntVal(7), JassIm.ImIntVal(3)),
1675+
false, de.peeeq.wurstscript.translation.imtranslation.CallType.NORMAL)),
1676+
JassIm.ImSet(model, JassIm.ImVarAccess(result), JassIm.ImFunctionCall(model, helper,
1677+
JassIm.ImTypeArguments(), JassIm.ImExprs(JassIm.ImIntVal(8), JassIm.ImIntVal(3)),
1678+
false, de.peeeq.wurstscript.translation.imtranslation.CallType.NORMAL))),
1679+
Collections.emptyList());
1680+
prog.getFunctions().add(helper);
1681+
prog.getFunctions().add(caller);
1682+
1683+
assertEquals(new ImInliner(translator).inlineLuaDivModHelpersWithinLocalBudget(), 2,
1684+
"sequential helper sites should share the same peak allocation slots");
1685+
}
1686+
16181687
@Test
16191688
public void testFunctionSplitter() {
16201689
WurstModel model = Ast.WurstModel();

0 commit comments

Comments
 (0)