Skip to content

Commit 83172cb

Browse files
committed
Merge branch 'perf/compiler-hot-paths-and-test-isolation' into test/fuzz-invariants-not-volume
2 parents 4bcd026 + 78c9650 commit 83172cb

10 files changed

Lines changed: 312 additions & 26 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,13 @@ public LuaCompilationUnit transformProgToLua() {
917917
// inliner
918918
stage = 5;
919919
if (runArgs.isInline()) {
920+
// Expose hot loop calls which cannot dispatch anywhere else to the ordinary inliner.
921+
// Calls outside loops keep their established method/slot representation.
922+
beginPhase(5, "lower monomorphic Lua method calls");
923+
LuaMethodCallLowering.transform(imProg);
924+
imTranslator.assertProperties();
925+
timeTaker.endPhase();
926+
920927
beginPhase(5, "inlining");
921928
optimizer.doInlining();
922929
imTranslator2.assertProperties();

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/prettyPrint/PrettyUtils.java

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,48 @@ public class PrettyUtils {
2222
/**
2323
* @param args
2424
*/
25-
public static void pretty(List<String> args) throws IOException {
26-
if (args.size() == 0) {
27-
return;
25+
/**
26+
* What {@link #pretty(List)} does with a given argument list.
27+
*
28+
* <p>Split out from the dispatch so it can be asserted directly: the alternative is running the
29+
* real thing, and both the directory walk and the single-file branch print to stdout while
30+
* readFile swallows its own exceptions, so neither outcome is distinguishable from the other.
31+
*/
32+
public enum PrettyAction {
33+
/** No arguments; nothing to do. */
34+
NONE,
35+
/** "..." - format every .wurst file below the root. */
36+
ALL,
37+
/** "tree <file>" - dump the parse tree. */
38+
TREE,
39+
/** Anything else is taken as a file name. */
40+
SINGLE_FILE
41+
}
42+
43+
public static PrettyAction selectAction(List<String> args) {
44+
if (args.isEmpty()) {
45+
return PrettyAction.NONE;
2846
}
2947
String arg = args.get(0);
30-
// Was args.equals("...") - comparing the List to a String, which is never true, so
31-
// the "..." argument silently fell through to being treated as a file name below.
48+
// This used to read args.equals("..."), comparing the List itself to a String, which is
49+
// never true - so "..." fell through and was treated as a file name.
3250
if (arg.equals("...")) {
33-
prettyAll(".");
34-
return;
51+
return PrettyAction.ALL;
3552
}
3653
if (arg.equals("tree") && args.size() >= 2) {
37-
debug(args.get(1));
38-
return;
54+
return PrettyAction.TREE;
3955
}
56+
return PrettyAction.SINGLE_FILE;
57+
}
4058

41-
String clean = pretty(new File(arg));
42-
System.out.println(clean);
59+
public static void pretty(List<String> args) throws IOException {
60+
switch (selectAction(args)) {
61+
case NONE -> {
62+
}
63+
case ALL -> prettyAll(".");
64+
case TREE -> debug(args.get(1));
65+
case SINGLE_FILE -> System.out.println(pretty(new File(args.get(0))));
66+
}
4367
}
4468

4569
public static String pretty(String source, String ending) {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package de.peeeq.wurstscript.translation.imtranslation;
2+
3+
import de.peeeq.wurstscript.jassIm.*;
4+
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
8+
/**
9+
* Lowers loop-local method calls with exactly one possible implementation to ordinary function
10+
* calls on Lua.
11+
*
12+
* <p>The Lua emitter has always used the same direct-call fast path. Performing the lowering before
13+
* optimization exposes hot calls to the ordinary inliner without guessing about receiver types or
14+
* generated method names. Calls outside loops, and calls which can participate in virtual dispatch,
15+
* remain untouched to avoid broad code-shape churn for a speculative gain.
16+
*/
17+
public final class LuaMethodCallLowering {
18+
19+
private LuaMethodCallLowering() {
20+
}
21+
22+
public static int transform(ImProg prog) {
23+
List<ImMethodCall> calls = new ArrayList<>();
24+
prog.accept(new ImProg.DefaultVisitor() {
25+
@Override
26+
public void visit(ImMethodCall call) {
27+
super.visit(call);
28+
if (isInsideLoop(call) && canLowerDirectly(call.getMethod())) {
29+
calls.add(call);
30+
}
31+
}
32+
});
33+
34+
for (ImMethodCall call : calls) {
35+
lower(call);
36+
}
37+
return calls.size();
38+
}
39+
40+
private static boolean isInsideLoop(ImMethodCall call) {
41+
Element owner = call.getParent();
42+
while (owner != null && !(owner instanceof ImFunction)) {
43+
if (owner instanceof ImLoop || owner instanceof ImVarargLoop) {
44+
return true;
45+
}
46+
owner = owner.getParent();
47+
}
48+
return false;
49+
}
50+
51+
public static boolean canLowerDirectly(ImMethod method) {
52+
return method != null
53+
&& !method.getIsAbstract()
54+
&& method.getImplementation() != null
55+
&& method.getSubMethods().isEmpty();
56+
}
57+
58+
private static void lower(ImMethodCall call) {
59+
ImExpr receiver = call.getReceiver();
60+
receiver.setParent(null);
61+
ImExprs arguments = JassIm.ImExprs(receiver);
62+
arguments.addAll(call.getArguments().removeAll());
63+
call.replaceBy(JassIm.ImFunctionCall(call.getTrace(), call.getMethod().getImplementation(),
64+
JassIm.ImTypeArguments(call.getTypeArguments().removeAll()), arguments,
65+
call.getTuplesEliminated(), CallType.NORMAL));
66+
}
67+
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/VarargEliminator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ private Collection<ImMethodCall> collectMonomorphicVarargMethodCalls() {
148148
public void visit(ImMethodCall c) {
149149
super.visit(c);
150150
ImMethod method = c.getMethod();
151-
if (method != null && !method.getIsAbstract() && method.getImplementation() != null
152-
&& method.getSubMethods().isEmpty() && method.getImplementation().hasFlag(IS_VARARG)) {
151+
if (LuaMethodCallLowering.canLowerDirectly(method)
152+
&& method.getImplementation().hasFlag(IS_VARARG)) {
153153
calls.add(c);
154154
}
155155
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/ExprTranslation.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import de.peeeq.wurstscript.jassIm.*;
66
import de.peeeq.wurstscript.luaAst.*;
77
import de.peeeq.wurstscript.translation.imtranslation.ImTranslator;
8+
import de.peeeq.wurstscript.translation.imtranslation.LuaMethodCallLowering;
89
import de.peeeq.wurstscript.types.TypesHelper;
910

1011
import java.util.Optional;
@@ -187,9 +188,7 @@ public static LuaExpr translate(ImMemberAccess e, LuaTranslator tr) {
187188

188189
public static LuaExpr translate(ImMethodCall e, LuaTranslator tr) {
189190
ImMethod method = e.getMethod();
190-
if (!method.getIsAbstract()
191-
&& method.getImplementation() != null
192-
&& method.getSubMethods().isEmpty()) {
191+
if (LuaMethodCallLowering.canLowerDirectly(method)) {
193192
LuaExprlist args = LuaAst.LuaExprlist();
194193
args.add(e.getReceiver().translateToLua(tr));
195194
for (ImExpr arg : e.getArguments()) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaNatives.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public class LuaNatives {
179179
f.getBody().add(LuaAst.LuaLiteral("local prev = __wurst_enumPlayer_override"));
180180
f.getBody().add(LuaAst.LuaLiteral("ForForce(whichForce, function()"));
181181
f.getBody().add(LuaAst.LuaLiteral(" count = count + 1"));
182-
f.getBody().add(LuaAst.LuaLiteral(" players[count] = __wurst_GetEnumPlayer()"));
182+
f.getBody().add(LuaAst.LuaLiteral(" players[count] = GetEnumPlayer()"));
183183
f.getBody().add(LuaAst.LuaLiteral("end)"));
184184
f.getBody().add(LuaAst.LuaLiteral("for i = 1, count do"));
185185
f.getBody().add(LuaAst.LuaLiteral(" __wurst_enumPlayer_override = players[i]"));
@@ -206,7 +206,7 @@ public class LuaNatives {
206206
f.getBody().add(LuaAst.LuaLiteral("local prev = __wurst_enumUnit_override"));
207207
f.getBody().add(LuaAst.LuaLiteral("ForGroup(whichGroup, function()"));
208208
f.getBody().add(LuaAst.LuaLiteral(" count = count + 1"));
209-
f.getBody().add(LuaAst.LuaLiteral(" units[count] = __wurst_GetEnumUnit()"));
209+
f.getBody().add(LuaAst.LuaLiteral(" units[count] = GetEnumUnit()"));
210210
f.getBody().add(LuaAst.LuaLiteral("end)"));
211211
f.getBody().add(LuaAst.LuaLiteral("for i = 1, count do"));
212212
f.getBody().add(LuaAst.LuaLiteral(" __wurst_enumUnit_override = units[i]"));
@@ -234,7 +234,7 @@ public class LuaNatives {
234234
f.getBody().add(LuaAst.LuaLiteral("local prev = __wurst_enumItem_override"));
235235
f.getBody().add(LuaAst.LuaLiteral("EnumItemsInRect(r, filter, function()"));
236236
f.getBody().add(LuaAst.LuaLiteral(" count = count + 1"));
237-
f.getBody().add(LuaAst.LuaLiteral(" items[count] = __wurst_GetEnumItem()"));
237+
f.getBody().add(LuaAst.LuaLiteral(" items[count] = GetEnumItem()"));
238238
f.getBody().add(LuaAst.LuaLiteral("end)"));
239239
f.getBody().add(LuaAst.LuaLiteral("for i = 1, count do"));
240240
f.getBody().add(LuaAst.LuaLiteral(" __wurst_enumItem_override = items[i]"));
@@ -262,7 +262,7 @@ public class LuaNatives {
262262
f.getBody().add(LuaAst.LuaLiteral("local prev = __wurst_enumDestructable_override"));
263263
f.getBody().add(LuaAst.LuaLiteral("EnumDestructablesInRect(r, filter, function()"));
264264
f.getBody().add(LuaAst.LuaLiteral(" count = count + 1"));
265-
f.getBody().add(LuaAst.LuaLiteral(" dests[count] = __wurst_GetEnumDestructable()"));
265+
f.getBody().add(LuaAst.LuaLiteral(" dests[count] = GetEnumDestructable()"));
266266
f.getBody().add(LuaAst.LuaLiteral("end)"));
267267
f.getBody().add(LuaAst.LuaLiteral("for i = 1, count do"));
268268
f.getBody().add(LuaAst.LuaLiteral(" __wurst_enumDestructable_override = dests[i]"));
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package tests.prettyprint;
2+
3+
import de.peeeq.wurstscript.attributes.prettyPrint.PrettyUtils;
4+
import org.testng.annotations.Test;
5+
6+
import java.util.Arrays;
7+
import java.util.Collections;
8+
9+
import static org.testng.AssertJUnit.assertEquals;
10+
11+
/**
12+
* Argument dispatch for the -prettyPrint CLI entry point.
13+
*
14+
* <p>The "..." branch used to compare the argument List itself to a String, so it was never taken
15+
* and "..." was treated as a file name instead; readFile then swallowed the resulting
16+
* FileNotFoundException, so the mistake produced no visible failure.
17+
*/
18+
public class PrettyUtilsArgsTest {
19+
20+
@Test
21+
public void tripleDotSelectsDirectoryFormatting() {
22+
assertEquals(PrettyUtils.PrettyAction.ALL,
23+
PrettyUtils.selectAction(Collections.singletonList("...")));
24+
}
25+
26+
@Test
27+
public void aFileNameIsNotTreatedAsDirectoryFormatting() {
28+
assertEquals(PrettyUtils.PrettyAction.SINGLE_FILE,
29+
PrettyUtils.selectAction(Collections.singletonList("some/file.wurst")));
30+
}
31+
32+
@Test
33+
public void treeWithAFileSelectsTreeDump() {
34+
assertEquals(PrettyUtils.PrettyAction.TREE,
35+
PrettyUtils.selectAction(Arrays.asList("tree", "some/file.wurst")));
36+
}
37+
38+
@Test
39+
public void treeWithoutAFileIsTreatedAsAFileName() {
40+
assertEquals(PrettyUtils.PrettyAction.SINGLE_FILE,
41+
PrettyUtils.selectAction(Collections.singletonList("tree")));
42+
}
43+
44+
@Test
45+
public void noArgumentsDoesNothing() {
46+
assertEquals(PrettyUtils.PrettyAction.NONE,
47+
PrettyUtils.selectAction(Collections.emptyList()));
48+
}
49+
}

0 commit comments

Comments
 (0)