Skip to content

Commit 1d240ee

Browse files
authored
Fixed lua dispatch (#1167)
1 parent ed1f0c8 commit 1d240ee

File tree

2 files changed

+524
-15
lines changed

2 files changed

+524
-15
lines changed

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

Lines changed: 276 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package de.peeeq.wurstscript.translation.lua.translation;
22

33
import de.peeeq.datastructures.UnionFind;
4+
import de.peeeq.wurstscript.ast.ClassDef;
45
import de.peeeq.wurstscript.ast.Element;
56
import de.peeeq.wurstscript.ast.FuncDef;
67
import de.peeeq.wurstscript.ast.NameDef;
8+
import de.peeeq.wurstscript.ast.WParameter;
79
import de.peeeq.wurstscript.ast.WPackage;
810
import de.peeeq.wurstscript.jassIm.*;
911
import de.peeeq.wurstscript.luaAst.*;
@@ -15,6 +17,9 @@
1517
import de.peeeq.wurstscript.utils.Utils;
1618
import org.jetbrains.annotations.NotNull;
1719

20+
import java.nio.file.Files;
21+
import java.nio.file.Path;
22+
import java.nio.file.StandardOpenOption;
1823
import java.util.*;
1924
import java.util.regex.Pattern;
2025
import java.util.stream.Stream;
@@ -88,6 +93,8 @@ public class LuaTranslator {
8893
"leaderboardFromIndex", "multiboardFromIndex", "trackableFromIndex", "lightningFromIndex",
8994
"ubersplatFromIndex", "framehandleFromIndex", "oskeytypeFromIndex"
9095
);
96+
private static final boolean DEBUG_LUA_DISPATCH = "1".equals(System.getenv("WURST_DEBUG_LUA_DISPATCH"))
97+
|| Boolean.getBoolean("wurst.debug.lua.dispatch");
9198

9299
final ImProg prog;
93100
final LuaCompilationUnit luaModel;
@@ -432,10 +439,20 @@ private void normalizeMethodNames() {
432439
}
433440
}
434441

435-
// give all related methods the same name
436-
for (Map.Entry<ImMethod, Set<ImMethod>> entry : methodUnions.groups().entrySet()) {
437-
String name = uniqueName(entry.getKey().getName());
438-
for (ImMethod method : entry.getValue()) {
442+
// give all related methods the same name in deterministic order
443+
List<List<ImMethod>> groups = new ArrayList<>();
444+
for (Set<ImMethod> group : methodUnions.groups().values()) {
445+
List<ImMethod> sortedGroup = new ArrayList<>(group);
446+
sortedGroup.sort(Comparator.comparing(this::methodSortKey));
447+
groups.add(sortedGroup);
448+
}
449+
groups.sort(Comparator.comparing(g -> g.isEmpty() ? "" : methodSortKey(g.get(0))));
450+
for (List<ImMethod> group : groups) {
451+
if (group.isEmpty()) {
452+
continue;
453+
}
454+
String name = uniqueName(group.get(0).getName());
455+
for (ImMethod method : group) {
439456
method.setName(name);
440457
}
441458
}
@@ -907,8 +924,7 @@ private void createClassInitFunction(ImClass c, LuaVariable classVar, LuaMethod
907924
private void initClassTables(ImClass c) {
908925
LuaVariable classVar = luaClassVar.getFor(c);
909926
// create methods:
910-
Set<String> methods = new HashSet<>();
911-
createMethods(c, classVar, methods);
927+
createMethods(c, classVar);
912928

913929
// set supertype metadata:
914930
LuaTableFields superClasses = LuaAst.LuaTableFields();
@@ -929,25 +945,270 @@ private void initClassTables(ImClass c) {
929945

930946
}
931947

932-
private void createMethods(ImClass c, LuaVariable classVar, Set<String> methods) {
933-
for (ImMethod method : c.getMethods()) {
934-
if (methods.contains(method.getName())) {
948+
private void createMethods(ImClass c, LuaVariable classVar) {
949+
List<ImMethod> allMethods = collectMethodsInHierarchy(c);
950+
Set<ImMethod> inHierarchy = new HashSet<>(allMethods);
951+
UnionFind<ImMethod> unions = new UnionFind<>();
952+
for (ImMethod method : allMethods) {
953+
unions.find(method);
954+
for (ImMethod subMethod : method.getSubMethods()) {
955+
if (inHierarchy.contains(subMethod)) {
956+
unions.union(method, subMethod);
957+
}
958+
}
959+
}
960+
961+
Map<ImMethod, List<ImMethod>> groupedMethods = new HashMap<>();
962+
for (ImMethod method : allMethods) {
963+
ImMethod root = unions.find(method);
964+
groupedMethods.computeIfAbsent(root, k -> new ArrayList<>()).add(method);
965+
}
966+
967+
List<List<ImMethod>> groups = new ArrayList<>(groupedMethods.values());
968+
groups.sort(Comparator.comparing(group -> group.isEmpty() ? "" : methodSortKey(group.get(0))));
969+
Map<String, ImMethod> slotToImpl = new TreeMap<>();
970+
for (List<ImMethod> groupMethods : groups) {
971+
if (groupMethods == null || groupMethods.isEmpty()) {
935972
continue;
936973
}
937-
methods.add(method.getName());
938-
if (method.getIsAbstract()) {
974+
groupMethods.sort(Comparator.comparing(this::methodSortKey));
975+
ImMethod chosen = chooseBestImplementationForClass(c, groupMethods);
976+
if (chosen == null || chosen.getIsAbstract() || chosen.getImplementation() == null) {
977+
continue;
978+
}
979+
Set<String> slotNames = collectDispatchSlotNames(c, groupMethods);
980+
for (String slotName : slotNames) {
981+
ImMethod current = slotToImpl.get(slotName);
982+
if (current == null || compareDispatchCandidates(c, chosen, current) < 0) {
983+
slotToImpl.put(slotName, chosen);
984+
}
985+
}
986+
String debugKey = groupMethods.get(0).getName();
987+
debugDispatchGroup(c, debugKey, slotNames, groupMethods, chosen);
988+
}
989+
for (Map.Entry<String, ImMethod> e : slotToImpl.entrySet()) {
990+
ImMethod impl = e.getValue();
991+
if (impl == null || impl.getImplementation() == null) {
939992
continue;
940993
}
941994
luaModel.add(LuaAst.LuaAssignment(LuaAst.LuaExprFieldAccess(
942995
LuaAst.LuaExprVarAccess(classVar),
943-
method.getName()),
944-
LuaAst.LuaExprFuncRef(luaFunc.getFor(method.getImplementation()))
996+
e.getKey()),
997+
LuaAst.LuaExprFuncRef(luaFunc.getFor(impl.getImplementation()))
945998
));
946999
}
947-
// also create links for inherited methods
1000+
}
1001+
1002+
private Set<String> collectDispatchSlotNames(ImClass receiverClass, List<ImMethod> groupMethods) {
1003+
Set<String> slotNames = new TreeSet<>();
1004+
Set<String> semanticNames = new TreeSet<>();
1005+
for (ImMethod m : groupMethods) {
1006+
if (m == null) {
1007+
continue;
1008+
}
1009+
String methodName = m.getName();
1010+
if (!methodName.isEmpty()) {
1011+
slotNames.add(methodName);
1012+
}
1013+
ImClass owner = m.attrClass();
1014+
String semanticName = semanticNameFromMethodName(methodName);
1015+
if (!semanticName.isEmpty()) {
1016+
semanticNames.add(semanticName);
1017+
}
1018+
if (owner != null && !semanticName.isEmpty()) {
1019+
slotNames.add(owner.getName() + "_" + semanticName);
1020+
}
1021+
}
1022+
if (receiverClass != null && !semanticNames.isEmpty()) {
1023+
Set<String> classNames = new TreeSet<>();
1024+
collectClassNamesInHierarchy(receiverClass, classNames, new HashSet<>());
1025+
for (String className : classNames) {
1026+
for (String semanticName : semanticNames) {
1027+
slotNames.add(className + "_" + semanticName);
1028+
}
1029+
}
1030+
}
1031+
return slotNames;
1032+
}
1033+
1034+
private void collectClassNamesInHierarchy(ImClass c, Set<String> out, Set<ImClass> visited) {
1035+
if (c == null || !visited.add(c)) {
1036+
return;
1037+
}
1038+
out.add(c.getName());
9481039
for (ImClassType sc : c.getSuperClasses()) {
949-
createMethods(sc.getClassDef(), classVar, methods);
1040+
collectClassNamesInHierarchy(sc.getClassDef(), out, visited);
1041+
}
1042+
}
1043+
1044+
private List<ImMethod> collectMethodsInHierarchy(ImClass c) {
1045+
List<ImMethod> result = new ArrayList<>();
1046+
collectMethodsInHierarchy(c, result, new HashSet<>());
1047+
result.sort(Comparator.comparing(this::methodSortKey));
1048+
return result;
1049+
}
1050+
1051+
private void collectMethodsInHierarchy(ImClass c, List<ImMethod> out, Set<ImClass> visited) {
1052+
if (c == null || !visited.add(c)) {
1053+
return;
1054+
}
1055+
out.addAll(c.getMethods());
1056+
List<ImClassType> superClasses = new ArrayList<>(c.getSuperClasses());
1057+
superClasses.sort(Comparator.comparing(t -> classSortKey(t.getClassDef())));
1058+
for (ImClassType sc : superClasses) {
1059+
collectMethodsInHierarchy(sc.getClassDef(), out, visited);
1060+
}
1061+
}
1062+
1063+
private ImMethod chooseBestImplementationForClass(ImClass receiverClass, List<ImMethod> candidates) {
1064+
List<ImMethod> concrete = new ArrayList<>();
1065+
for (ImMethod m : candidates) {
1066+
if (!m.getIsAbstract() && m.getImplementation() != null) {
1067+
concrete.add(m);
1068+
}
1069+
}
1070+
if (concrete.isEmpty()) {
1071+
return null;
1072+
}
1073+
concrete.sort((a, b) -> compareDispatchCandidates(receiverClass, a, b));
1074+
return concrete.get(0);
1075+
}
1076+
1077+
private int compareDispatchCandidates(ImClass receiverClass, ImMethod a, ImMethod b) {
1078+
boolean aLocal = isImplementationFromClass(a, receiverClass);
1079+
boolean bLocal = isImplementationFromClass(b, receiverClass);
1080+
if (aLocal != bLocal) {
1081+
return aLocal ? -1 : 1;
1082+
}
1083+
int aDist = classDistance(receiverClass, a.attrClass());
1084+
int bDist = classDistance(receiverClass, b.attrClass());
1085+
if (aDist != bDist) {
1086+
return Integer.compare(aDist, bDist);
1087+
}
1088+
boolean aNoOp = isNoOpImplementation(a);
1089+
boolean bNoOp = isNoOpImplementation(b);
1090+
if (aNoOp != bNoOp) {
1091+
return aNoOp ? 1 : -1;
1092+
}
1093+
return methodSortKey(a).compareTo(methodSortKey(b));
1094+
}
1095+
1096+
private boolean isImplementationFromClass(ImMethod method, ImClass ownerClass) {
1097+
if (method == null || ownerClass == null || method.getImplementation() == null) {
1098+
return false;
1099+
}
1100+
return method.getImplementation().getName().startsWith(ownerClass.getName() + "_");
1101+
}
1102+
1103+
private boolean isNoOpImplementation(ImMethod method) {
1104+
return method != null
1105+
&& method.getImplementation() != null
1106+
&& method.getImplementation().getName().contains("NoOpState_");
1107+
}
1108+
1109+
private int classDistance(ImClass from, ImClass to) {
1110+
if (from == null || to == null) {
1111+
return Integer.MAX_VALUE;
1112+
}
1113+
if (from == to) {
1114+
return 0;
1115+
}
1116+
ArrayDeque<ImClass> queue = new ArrayDeque<>();
1117+
Map<ImClass, Integer> dist = new HashMap<>();
1118+
queue.add(from);
1119+
dist.put(from, 0);
1120+
while (!queue.isEmpty()) {
1121+
ImClass current = queue.removeFirst();
1122+
int currentDist = dist.get(current);
1123+
List<ImClassType> superClasses = new ArrayList<>(current.getSuperClasses());
1124+
superClasses.sort(Comparator.comparing(t -> classSortKey(t.getClassDef())));
1125+
for (ImClassType sc : superClasses) {
1126+
ImClass next = sc.getClassDef();
1127+
if (next == null || dist.containsKey(next)) {
1128+
continue;
1129+
}
1130+
int nextDist = currentDist + 1;
1131+
if (next == to) {
1132+
return nextDist;
1133+
}
1134+
dist.put(next, nextDist);
1135+
queue.add(next);
1136+
}
1137+
}
1138+
return Integer.MAX_VALUE;
1139+
}
1140+
1141+
private void debugDispatchGroup(ImClass receiverClass, String key, Set<String> slotNames, List<ImMethod> groupMethods, ImMethod chosen) {
1142+
if (!DEBUG_LUA_DISPATCH && !isSuspiciousGroup(slotNames, groupMethods, chosen)) {
1143+
return;
1144+
}
1145+
String chosenImpl = chosen != null && chosen.getImplementation() != null ? chosen.getImplementation().getName() : "null";
1146+
StringBuilder candidates = new StringBuilder();
1147+
List<ImMethod> sorted = new ArrayList<>(groupMethods);
1148+
sorted.sort(Comparator.comparing(this::methodSortKey));
1149+
for (ImMethod m : sorted) {
1150+
String impl = m.getImplementation() != null ? m.getImplementation().getName() : "null";
1151+
if (candidates.length() > 0) {
1152+
candidates.append("; ");
1153+
}
1154+
candidates.append(m.getName()).append("->").append(impl).append("@").append(classSortKey(m.attrClass()));
1155+
}
1156+
System.err.println("[LuaDispatch] class=" + classSortKey(receiverClass)
1157+
+ " key=" + key
1158+
+ " slots=" + slotNames
1159+
+ " chosen=" + chosenImpl
1160+
+ " candidates=[" + candidates + "]");
1161+
if (DEBUG_LUA_DISPATCH) {
1162+
String line = "[LuaDispatch] class=" + classSortKey(receiverClass)
1163+
+ " key=" + key
1164+
+ " slots=" + slotNames
1165+
+ " chosen=" + chosenImpl
1166+
+ " candidates=[" + candidates + "]"
1167+
+ System.lineSeparator();
1168+
try {
1169+
Files.writeString(Path.of("C:/Users/Frotty/Documents/GitHub/WurstScript/lua-dispatch-debug.log"),
1170+
line, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
1171+
} catch (Exception ignored) {
1172+
}
1173+
}
1174+
}
1175+
1176+
private boolean isSuspiciousGroup(Set<String> slotNames, List<ImMethod> groupMethods, ImMethod chosen) {
1177+
if (slotNames.size() > 1) {
1178+
return true;
1179+
}
1180+
boolean hasNonNoOp = false;
1181+
for (ImMethod m : groupMethods) {
1182+
if (!isNoOpImplementation(m) && m.getImplementation() != null) {
1183+
hasNonNoOp = true;
1184+
break;
1185+
}
1186+
}
1187+
return hasNonNoOp && isNoOpImplementation(chosen);
1188+
}
1189+
1190+
private String methodSortKey(ImMethod m) {
1191+
String owner = classSortKey(m.attrClass());
1192+
String impl = m.getImplementation() != null ? m.getImplementation().getName() : "";
1193+
return owner + "|" + m.getName() + "|" + impl;
1194+
}
1195+
1196+
private String semanticNameFromMethodName(String methodName) {
1197+
if (methodName == null || methodName.isEmpty()) {
1198+
return "";
1199+
}
1200+
int lastUnderscore = methodName.lastIndexOf('_');
1201+
if (lastUnderscore >= 0 && lastUnderscore + 1 < methodName.length()) {
1202+
return methodName.substring(lastUnderscore + 1);
1203+
}
1204+
return methodName;
1205+
}
1206+
1207+
private String classSortKey(ImClass c) {
1208+
if (c == null) {
1209+
return "";
9501210
}
1211+
return c.getName();
9511212
}
9521213

9531214
@NotNull

0 commit comments

Comments
 (0)