diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imoptimizer/ImOptimizer.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imoptimizer/ImOptimizer.java index 01d4fc2ba..62882abd1 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imoptimizer/ImOptimizer.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imoptimizer/ImOptimizer.java @@ -70,53 +70,54 @@ public void doInlining() { public int inlineLuaDivModHelpersWithinLocalBudget() { return new ImInliner(trans).inlineLuaDivModHelpersWithinLocalBudget(); } - - private int optCount = 1; - public void localOptimizations() { totalCount.clear(); - optCount = 1; removeGarbage(); - int finalItr = 0; - for (int i = 1; i <= 10 && optCount > 0; i++) { - optCount = 0; - LocalPlayerContextAnalyzer localPlayerContextAnalyzer = null; - for (OptimizerPass pass : localPasses) { - int count; - if (pass instanceof LocalPlayerAwareOptimizerPass) { - if (localPlayerContextAnalyzer == null) { - localPlayerContextAnalyzer = - new LocalPlayerContextAnalyzer(trans.getImProg()); - } - LocalPlayerContextAnalyzer analyzer = localPlayerContextAnalyzer; - LocalPlayerAwareOptimizerPass localPlayerAwarePass = - (LocalPlayerAwareOptimizerPass) pass; - count = timeTaker.measure( - pass.getName(), - () -> localPlayerAwarePass.optimize(trans, analyzer)); - } else { - count = timeTaker.measure(pass.getName(), () -> pass.optimize(trans)); - // A general mutating pass may invalidate dependency edges. - localPlayerContextAnalyzer = null; - } - optCount += count; - totalCount.put(pass.getName(), totalCount.getOrDefault(pass.getName(), 0) + count); - } - - if (optCount > 0) { - removeGarbage(); - trans.getImProg().flatten(trans); - } + int optCount = runLocalOptimizationSweep(); + if (optCount > 0) { + removeGarbage(); + trans.getImProg().flatten(trans); + } - finalItr = i; - WLogger.info("=== Optimization pass: " + i + " opts: " + optCount + " ==="); + int cleanupCount = runLocalOptimizationSweep(); + if (cleanupCount > 0) { + removeGarbage(); + trans.getImProg().flatten(trans); } - WLogger.info("=== Local optimizations done! Ran " + finalItr + " passes. ==="); + + WLogger.info("=== Local optimization passes done! Opts: " + (optCount + cleanupCount) + " ==="); totalCount.forEach((k, v) -> WLogger.info("== " + k + ": " + v)); } + private int runLocalOptimizationSweep() { + int optCount = 0; + LocalPlayerContextAnalyzer localPlayerContextAnalyzer = null; + for (OptimizerPass pass : localPasses) { + int count; + if (pass instanceof LocalPlayerAwareOptimizerPass) { + if (localPlayerContextAnalyzer == null) { + localPlayerContextAnalyzer = + new LocalPlayerContextAnalyzer(trans.getImProg()); + } + LocalPlayerContextAnalyzer analyzer = localPlayerContextAnalyzer; + LocalPlayerAwareOptimizerPass localPlayerAwarePass = + (LocalPlayerAwareOptimizerPass) pass; + count = timeTaker.measure( + pass.getName(), + () -> localPlayerAwarePass.optimize(trans, analyzer)); + } else { + count = timeTaker.measure(pass.getName(), () -> pass.optimize(trans)); + // A general mutating pass may invalidate dependency edges. + localPlayerContextAnalyzer = null; + } + optCount += count; + totalCount.put(pass.getName(), totalCount.getOrDefault(pass.getName(), 0) + count); + } + return optCount; + } + public void doNullsetting() { NullSetter ns = new NullSetter(trans); ns.optimize(); diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTranslationTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTranslationTests.java index 3aeec8bb4..7a7070de9 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTranslationTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaTranslationTests.java @@ -2293,7 +2293,12 @@ public void luaInlinerReusesRegistersAcrossSequentialInlineSites() { false, Collections.emptyList(), new RunArgs().with("-lua", "-inline", "-localOptimizations"), lines.toArray(new String[0])); - String callerBody = getFunctionBody(compiled, "caller"); + int callerStart = compiled.indexOf("function caller("); + assertTrue("caller function not found", callerStart >= 0); + int callerBodyStart = compiled.indexOf('\n', callerStart); + int callerEnd = compiled.indexOf("\nend", callerBodyStart); + assertTrue("caller function end not found", callerEnd > callerBodyStart); + String callerBody = compiled.substring(callerBodyStart + 1, callerEnd); assertFalse("low-pressure sequential helper calls should still inline:\n" + callerBody, callerBody.contains("helper(")); assertFalse("sequential inline temporaries should reuse registers:\n" + callerBody, diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/OptimizerTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/OptimizerTests.java index e0bd59db7..2875b129b 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/OptimizerTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/OptimizerTests.java @@ -1597,6 +1597,45 @@ public void repeatedLocalOptimizationStartsANewIteration() { "a second local-optimization invocation must execute its passes"); } + @Test + public void localOptimizationRunsTwoBoundedSweepsPerInvocation() { + class CountingTimeTaker extends TimeTaker.Default { + int measurements; + + @Override + public T measure(String name, java.util.function.Supplier f) { + measurements++; + return f.get(); + } + } + + WurstModel model = Ast.WurstModel(); + ImTranslator translator = new ImTranslator(model, false, new RunArgs()); + ImVar value = JassIm.ImVar(model, TypesHelper.imInt(), "value", false); + ImFunction sink = JassIm.ImFunction(model, "sink", JassIm.ImTypeVars(), + JassIm.ImVars(value), JassIm.ImVoid(), JassIm.ImVars(), JassIm.ImStmts(), + Collections.singletonList(FunctionFlagEnum.IS_NATIVE)); + ImFunctionCall call = JassIm.ImFunctionCall(model, sink, JassIm.ImTypeArguments(), + JassIm.ImExprs(JassIm.ImOperatorCall(de.peeeq.wurstscript.WurstOperator.PLUS, + JassIm.ImExprs(JassIm.ImIntVal(1), JassIm.ImIntVal(2)))), false, + de.peeeq.wurstscript.translation.imtranslation.CallType.NORMAL); + ImFunction main = JassIm.ImFunction(model, "main", JassIm.ImTypeVars(), JassIm.ImVars(), + JassIm.ImVoid(), JassIm.ImVars(), JassIm.ImStmts(call), Collections.emptyList()); + ImFunction config = JassIm.ImFunction(model, "config", JassIm.ImTypeVars(), JassIm.ImVars(), + JassIm.ImVoid(), JassIm.ImVars(), JassIm.ImStmts(), Collections.emptyList()); + translator.getImProg().getFunctions().add(sink); + translator.getImProg().getFunctions().add(main); + translator.getImProg().getFunctions().add(config); + translator.setMainFunc(main); + translator.setConfigFunc(config); + CountingTimeTaker timeTaker = new CountingTimeTaker(); + + new ImOptimizer(timeTaker, translator).localOptimizations(); + + assertEquals(timeTaker.measurements, 16, + "the optimizer should run two fixed sweeps rather than iterating to convergence"); + } + @Test public void luaArithmeticHelperRetryRespectsFunctionLocalBudget() { WurstModel model = Ast.WurstModel();