From c0e12fcb531815b77ae81b075f5941d695ace733 Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 3 Apr 2026 15:29:54 +0200 Subject: [PATCH 1/2] lua again --- .../lua/translation/LuaTranslator.java | 109 ++++++++++++++++++ .../tests/LuaTranslationTests.java | 80 +++++++++++++ 2 files changed, 189 insertions(+) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaTranslator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaTranslator.java index eef1e8778..996c1b2bb 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaTranslator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaTranslator.java @@ -1192,10 +1192,14 @@ private String typeKey(ImType type) { private Set collectDispatchSlotNames(ImClass receiverClass, List groupMethods) { Set slotNames = new TreeSet<>(); Set semanticNames = new TreeSet<>(); + Set dispatchKeys = new TreeSet<>(); + Set closureRuntimeKeys = new TreeSet<>(); for (ImMethod m : groupMethods) { if (m == null) { continue; } + dispatchKeys.add(dispatchSignatureKey(m)); + closureRuntimeKeys.add(closureRuntimeDispatchKey(m)); String methodName = m.getName(); if (!methodName.isEmpty()) { slotNames.add(methodName); @@ -1215,6 +1219,12 @@ private Set collectDispatchSlotNames(ImClass receiverClass, List()); + if (isClosureGeneratedClass(receiverClass) && !closureRuntimeKeys.isEmpty()) { + collectEquivalentClosureFamilyMethodNames(receiverClass, closureRuntimeKeys, semanticNames, slotNames); + } + } if (receiverClass != null && !semanticNames.isEmpty()) { Set classNames = new TreeSet<>(); collectClassNamesInHierarchy(receiverClass, classNames, new HashSet<>()); @@ -1227,6 +1237,105 @@ private Set collectDispatchSlotNames(ImClass receiverClass, List dispatchKeys, Set semanticNames, + Set slotNames, Set visited) { + if (c == null || !visited.add(c)) { + return; + } + for (ImMethod method : c.getMethods()) { + if (method == null) { + continue; + } + if (!dispatchKeys.contains(dispatchSignatureKey(method))) { + continue; + } + String methodName = method.getName(); + String semanticName = semanticNameFromMethodName(methodName); + String sourceSemanticName = sourceSemanticName(method); + if (!semanticNames.contains(semanticName) && !semanticNames.contains(sourceSemanticName)) { + continue; + } + if (!methodName.isEmpty()) { + slotNames.add(methodName); + slotNames.add(c.getName() + "_" + methodName); + } + } + for (ImClassType sc : c.getSuperClasses()) { + collectEquivalentHierarchyMethodNames(sc.getClassDef(), dispatchKeys, semanticNames, slotNames, visited); + } + } + + private void collectEquivalentClosureFamilyMethodNames(ImClass receiverClass, Set closureRuntimeKeys, + Set semanticNames, Set slotNames) { + Set anchors = new HashSet<>(); + collectClosureFamilyAnchors(receiverClass, anchors, new HashSet<>()); + if (anchors.isEmpty()) { + return; + } + List classes = new ArrayList<>(prog.getClasses()); + classes.sort(Comparator.comparing(this::classSortKey)); + for (ImClass candidateClass : classes) { + if (!sharesClosureFamilyAnchor(candidateClass, anchors, new HashSet<>())) { + continue; + } + List methods = new ArrayList<>(candidateClass.getMethods()); + methods.sort(Comparator.comparing(this::methodSortKey)); + for (ImMethod method : methods) { + if (!closureRuntimeKeys.contains(closureRuntimeDispatchKey(method))) { + continue; + } + String methodName = method.getName(); + String semanticName = semanticNameFromMethodName(methodName); + String sourceSemanticName = sourceSemanticName(method); + if (!semanticNames.contains(semanticName) && !semanticNames.contains(sourceSemanticName)) { + continue; + } + if (!methodName.isEmpty()) { + slotNames.add(methodName); + slotNames.add(candidateClass.getName() + "_" + methodName); + } + } + } + } + + private String closureRuntimeDispatchKey(ImMethod method) { + if (method == null) { + return ""; + } + ImFunction implementation = resolveDispatchSignatureImplementation(method, new HashSet<>()); + if (implementation == null) { + return ""; + } + return "" + Math.max(0, implementation.getParameters().size() - 1); + } + + private void collectClosureFamilyAnchors(ImClass c, Set anchors, Set visited) { + if (c == null || !visited.add(c)) { + return; + } + if (!isClosureGeneratedClass(c)) { + anchors.add(c); + } + for (ImClassType sc : c.getSuperClasses()) { + collectClosureFamilyAnchors(sc.getClassDef(), anchors, visited); + } + } + + private boolean sharesClosureFamilyAnchor(ImClass c, Set anchors, Set visited) { + if (c == null || !visited.add(c)) { + return false; + } + if (anchors.contains(c)) { + return true; + } + for (ImClassType sc : c.getSuperClasses()) { + if (sharesClosureFamilyAnchor(sc.getClassDef(), anchors, visited)) { + return true; + } + } + return false; + } + private void collectClassNamesInHierarchy(ImClass c, Set out, Set visited) { if (c == null || !visited.add(c)) { return; 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 9366d263c..86084cdd6 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 @@ -128,6 +128,18 @@ private List nonBaseSubclassBindings(String output, String baseName, Str return result; } + private List subclassCreateClasses(String output, String baseName) { + Matcher matcher = Pattern.compile("function\\s+(" + Pattern.quote(baseName) + "_[A-Za-z0-9_]+):create\\d*\\s*\\(").matcher(output); + List result = new ArrayList<>(); + while (matcher.find()) { + String owner = matcher.group(1); + if (!result.contains(owner)) { + result.add(owner); + } + } + return result; + } + private String compileLuaWithRunArgs(String testName, boolean withStdLib, String... lines) { RunArgs runArgs = new RunArgs().with("-lua", "-inline", "-localOptimizations", "-stacktraces"); WurstGui gui = new WurstGuiCliImpl(); @@ -903,6 +915,74 @@ public void nestedGenericLinkedListClosuresKeepPrefixedLuaSlotNames() throws IOE assertContainsRegex(compiled, "LLItrClosure_[A-Za-z0-9_]+\\.LLItrClosure_run\\s*="); } + @Test + public void erasedLinkedListClosureCallsitesKeepSuffixedRunSlotsAlignedAcrossAllSubclasses() { + String compiled = compileLuaWithRunArgs( + "LuaTranslationTests_erasedLinkedListClosureCallsitesKeepSuffixedRunSlotsAlignedAcrossAllSubclasses", + false, + "package Test", + "interface Alpha0", + " function run()", + "interface Alpha1", + " function run()", + "interface Alpha2", + " function run()", + "interface Alpha3", + " function run()", + "interface Alpha4", + " function run()", + "interface Alpha5", + " function run()", + "public interface MapClosure", + " function run(T t) returns S", + "public interface LLItrClosure", + " function run(T t)", + "class Node", + " T elem", + " Node next = null", + "class LinkedList", + " Node first = null", + " function add(T elem)", + " let n = new Node()", + " n.elem = elem", + " n.next = first", + " first = n", + " function forEach(LLItrClosure itr)", + " var cur = first", + " while cur != null", + " itr.run(cur.elem)", + " cur = cur.next", + " function map(MapClosure itr) returns LinkedList", + " let out = new LinkedList()", + " forEach() t ->", + " out.add(itr.run(t))", + " return out", + "function usePlain(LinkedList xs)", + " xs.forEach() x ->", + " skip", + "function useMapped(LinkedList xs)", + " xs.map((int x) -> x + 1)", + "function useNested(LinkedList xs)", + " xs.forEach() x ->", + " xs.map((int y) -> y + x)", + "init", + " let xs = new LinkedList()", + " xs.add(1)", + " usePlain(xs)", + " useMapped(xs)", + " useNested(xs)" + ); + + List subclasses = subclassCreateClasses(compiled, "LLItrClosure"); + assertTrue("Expected multiple generated LLItrClosure subclasses", subclasses.size() >= 3); + List suffixedSlots = uniqueMatches(compiled, "LLItrClosure_[A-Za-z0-9_]+\\.(run\\d+)\\s*=", 1); + assertTrue("Expected at least one suffixed LLItrClosure run slot", !suffixedSlots.isEmpty()); + String slotName = suffixedSlots.get(0); + for (String subclass : subclasses) { + assertContainsRegex(compiled, Pattern.quote(subclass) + "\\." + Pattern.quote(slotName) + "\\s*="); + } + } + @Test public void mainAndConfigNamesFixed() throws IOException { test().testLua(true).lines( From 3b7e13e244d049fd920db1e76c207e3838e5eb83 Mon Sep 17 00:00:00 2001 From: Frotty Date: Fri, 3 Apr 2026 21:53:30 +0200 Subject: [PATCH 2/2] fix lua performance regression --- .../parserspec/jass_im.parseq | 2 + .../peeeq/wurstio/WurstCompilerJassImpl.java | 4 + .../imtranslation/ClosureTranslator.java | 2 +- .../translation/imtranslation/GetAForB.java | 11 +- .../imtranslation/ImTranslator.java | 4 +- .../imtranslation/LuaDispatchPreparation.java | 411 ++++++++++++++++++ .../imtranslation/OverrideUtils.java | 2 +- .../lua/translation/LuaTranslator.java | 334 +++++--------- 8 files changed, 539 insertions(+), 231 deletions(-) create mode 100644 de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/LuaDispatchPreparation.java diff --git a/de.peeeq.wurstscript/parserspec/jass_im.parseq b/de.peeeq.wurstscript/parserspec/jass_im.parseq index abb3420dc..c8d4eadae 100644 --- a/de.peeeq.wurstscript/parserspec/jass_im.parseq +++ b/de.peeeq.wurstscript/parserspec/jass_im.parseq @@ -68,6 +68,8 @@ ImMethod(@ignoreForEquality de.peeeq.wurstscript.ast.Element trace, String name, ref ImFunction implementation, java.util.List subMethods, + java.util.List luaMethodDispatchAliases, + String luaDispatchGroupKey, boolean isAbstract) diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java index d731a570d..b38336679 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/WurstCompilerJassImpl.java @@ -943,6 +943,10 @@ public LuaCompilationUnit transformProgToLua() { imProg.flatten(imTranslator); timeTaker.endPhase(); + beginPhase(13, "prepare lua dispatch"); + LuaDispatchPreparation.prepare(imProg); + timeTaker.endPhase(); + beginPhase(14, "translate to lua"); LuaTranslator luaTranslator = new LuaTranslator(imProg, imTranslator); LuaCompilationUnit luaCode = luaTranslator.translate(); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ClosureTranslator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ClosureTranslator.java index 07e4a7437..d540afdc7 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ClosureTranslator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ClosureTranslator.java @@ -195,7 +195,7 @@ private ImClass createClass() { tr.getImProg().getFunctions().remove(impl); c.getFunctions().add(impl); ImClassType methodClass = JassIm.ImClassType(c, JassIm.ImTypeArguments()); - ImMethod m = JassIm.ImMethod(e, methodClass, superMethod.getName(), impl, JassIm.ImMethods(), false); + ImMethod m = JassIm.ImMethod(e, methodClass, superMethod.getName(), impl, JassIm.ImMethods(), Lists.newArrayList(), "", false); c.getMethods().add(m); OverrideUtils.addOverrideClosure(tr, superMethod, m, e); diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/GetAForB.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/GetAForB.java index 29deb8d53..c9f5bbf2b 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/GetAForB.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/GetAForB.java @@ -11,12 +11,13 @@ public abstract class GetAForB { public abstract A initFor(B a); public A getFor(B a) { - if (thing.containsKey(a)) { - return thing.get(a); + A existing = thing.get(a); + if (existing != null) { + return existing; } - A b = initFor(a); - thing.put(a, b); - return b; + A created = initFor(a); + thing.put(a, created); + return created; } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java index f9a9ecbfc..d9fa6c7c4 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java @@ -772,7 +772,7 @@ public ImFunction initFor(StructureDef classDef) { public ImMethod initFor(StructureDef classDef) { ImFunction impl = destroyFunc.getFor(classDef); ImMethod m = JassIm.ImMethod(classDef, selfType(classDef), "destroy" + classDef.getName(), - impl, Lists.newArrayList(), false); + impl, Lists.newArrayList(), Lists.newArrayList(), "", false); return m; } }; @@ -2039,7 +2039,7 @@ public ImMethod getMethodFor(FuncDef f) { // otherwise EliminateClasses dispatch lookup can fail. String methodName = imFunc.getName(); WLogger.trace(() -> "[GENCAP] getMethodFor " + elementNameWithPath(f) + " -> methodName=" + methodName); - m = JassIm.ImMethod(f, selfType(f), methodName, imFunc, Lists.newArrayList(), false); + m = JassIm.ImMethod(f, selfType(f), methodName, imFunc, Lists.newArrayList(), Lists.newArrayList(), "", false); methodForFuncDef.put(f, m); } return m; diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/LuaDispatchPreparation.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/LuaDispatchPreparation.java new file mode 100644 index 000000000..d2567e45d --- /dev/null +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/LuaDispatchPreparation.java @@ -0,0 +1,411 @@ +package de.peeeq.wurstscript.translation.imtranslation; + +import de.peeeq.datastructures.UnionFind; +import de.peeeq.wurstscript.ast.AstElementWithFuncName; +import de.peeeq.wurstscript.ast.ExprClosure; +import de.peeeq.wurstscript.ast.FuncDef; +import de.peeeq.wurstscript.jassIm.ImClass; +import de.peeeq.wurstscript.jassIm.ImClassType; +import de.peeeq.wurstscript.jassIm.ImFunction; +import de.peeeq.wurstscript.jassIm.ImMethod; +import de.peeeq.wurstscript.jassIm.ImProg; +import de.peeeq.wurstscript.jassIm.ImType; +import de.peeeq.wurstscript.jassIm.ImVars; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +public final class LuaDispatchPreparation { + private static final Set LUA_RESERVED_NAMES = Set.of( + "print", "tostring", "error", + "main", "config", + "and", "break", "do", "else", "elseif", "end", "false", "for", "function", + "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" + ); + + private LuaDispatchPreparation() { + } + + public static void prepare(ImProg prog) { + List allMethods = collectAllMethods(prog); + assignDispatchGroupKeys(allMethods); + normalizeMethodNames(prog, allMethods); + assignDispatchAliases(prog, allMethods); + } + + private static List collectAllMethods(ImProg prog) { + List methods = new ArrayList<>(); + for (ImClass c : prog.getClasses()) { + methods.addAll(c.getMethods()); + } + methods.sort(Comparator.comparing(LuaDispatchPreparation::methodSortKey)); + return methods; + } + + private static void assignDispatchGroupKeys(List allMethods) { + Set knownMethods = new HashSet<>(allMethods); + UnionFind unions = new UnionFind<>(); + for (ImMethod method : allMethods) { + unions.find(method); + for (ImMethod subMethod : method.getSubMethods()) { + if (knownMethods.contains(subMethod)) { + unions.union(method, subMethod); + } + } + } + + Map> grouped = new LinkedHashMap<>(); + for (ImMethod method : allMethods) { + ImMethod root = unions.find(method); + grouped.computeIfAbsent(root, ignored -> new ArrayList<>()).add(method); + } + + for (List group : grouped.values()) { + Map> partitions = new LinkedHashMap<>(); + group.sort(Comparator.comparing(LuaDispatchPreparation::methodSortKey)); + for (ImMethod method : group) { + partitions.computeIfAbsent(dispatchSignatureKey(method), ignored -> new ArrayList<>()).add(method); + } + for (List partition : partitions.values()) { + partition.sort(Comparator.comparing(LuaDispatchPreparation::methodSortKey)); + if (partition.isEmpty()) { + continue; + } + String key = methodSortKey(partition.get(0)) + "|" + dispatchSignatureKey(partition.get(0)); + for (ImMethod method : partition) { + method.setLuaDispatchGroupKey(key); + } + } + } + } + + private static void normalizeMethodNames(ImProg prog, List allMethods) { + Set usedNames = new HashSet<>(LUA_RESERVED_NAMES); + collectPredefinedNames(prog, usedNames); + + Map> groupedMethods = new TreeMap<>(); + for (ImMethod method : allMethods) { + groupedMethods.computeIfAbsent(method.getLuaDispatchGroupKey(), ignored -> new ArrayList<>()).add(method); + } + List> groups = new ArrayList<>(groupedMethods.values()); + groups.sort(Comparator.comparing(g -> g.isEmpty() ? "" : methodSortKey(g.get(0)))); + for (List group : groups) { + if (group.isEmpty()) { + continue; + } + group.sort(Comparator.comparing(LuaDispatchPreparation::methodSortKey)); + String name = uniqueName(group.get(0).getName(), usedNames); + for (ImMethod method : group) { + method.setName(name); + } + } + } + + private static void assignDispatchAliases(ImProg prog, List allMethods) { + Map> sortedMethodsByClass = new HashMap<>(); + Map> closureFamilyAnchorsCache = new HashMap<>(); + Map> closureFamilyClassesByAnchor = new HashMap<>(); + + for (ImMethod method : allMethods) { + TreeSet aliases = new TreeSet<>(); + addDirectAliases(method, aliases); + addHierarchyAliases(method, aliases, sortedMethodsByClass); + addClosureFamilyAliases(prog, method, aliases, sortedMethodsByClass, closureFamilyAnchorsCache, closureFamilyClassesByAnchor); + method.setLuaMethodDispatchAliases(new ArrayList<>(aliases)); + } + } + + private static void collectPredefinedNames(ImProg prog, Set usedNames) { + prog.getFunctions().forEach(function -> { + if (function.isBj() || function.isExtern() || function.isNative()) { + usedNames.add(function.getName()); + } + }); + prog.getGlobals().forEach(global -> { + if (global.getIsBJ()) { + usedNames.add(global.getName()); + } + }); + } + + private static String uniqueName(String name, Set usedNames) { + int i = 0; + String result = name; + while (usedNames.contains(result)) { + result = name + ++i; + } + usedNames.add(result); + return result; + } + + private static void addDirectAliases(ImMethod method, Set aliases) { + if (method == null) { + return; + } + String methodName = method.getName(); + if (!methodName.isEmpty()) { + aliases.add(methodName); + } + ImClass owner = method.attrClass(); + String semanticName = semanticNameFromMethodName(methodName); + if (owner != null && !semanticName.isEmpty()) { + aliases.add(owner.getName() + "_" + semanticName); + } + String sourceSemanticName = sourceSemanticName(method); + if (owner != null && isClosureGeneratedClass(owner) && !sourceSemanticName.isEmpty()) { + aliases.add(sourceSemanticName); + aliases.add(owner.getName() + "_" + sourceSemanticName); + } + } + + private static void addHierarchyAliases(ImMethod method, Set aliases, Map> sortedMethodsByClass) { + ImClass owner = method.attrClass(); + if (owner == null) { + return; + } + Set semanticNames = semanticNames(method); + if (semanticNames.isEmpty()) { + return; + } + String dispatchKey = dispatchSignatureKey(method); + collectHierarchyAliases(owner, dispatchKey, semanticNames, aliases, sortedMethodsByClass, new HashSet<>()); + } + + private static void collectHierarchyAliases(ImClass c, String dispatchKey, Set semanticNames, Set aliases, + Map> sortedMethodsByClass, Set visited) { + if (c == null || !visited.add(c)) { + return; + } + for (ImMethod candidate : sortedMethodsForClass(c, sortedMethodsByClass)) { + if (!dispatchKey.equals(dispatchSignatureKey(candidate))) { + continue; + } + if (!sharesSemanticName(candidate, semanticNames)) { + continue; + } + String candidateName = candidate.getName(); + if (!candidateName.isEmpty()) { + aliases.add(candidateName); + aliases.add(c.getName() + "_" + candidateName); + } + } + for (ImClassType sc : c.getSuperClasses()) { + collectHierarchyAliases(sc.getClassDef(), dispatchKey, semanticNames, aliases, sortedMethodsByClass, visited); + } + } + + private static void addClosureFamilyAliases(ImProg prog, ImMethod method, Set aliases, + Map> sortedMethodsByClass, + Map> closureFamilyAnchorsCache, + Map> closureFamilyClassesByAnchor) { + ImClass owner = method.attrClass(); + if (owner == null || !isClosureGeneratedClass(owner)) { + return; + } + Set semanticNames = semanticNames(method); + if (semanticNames.isEmpty()) { + return; + } + String runtimeKey = closureRuntimeDispatchKey(method); + for (ImClass anchor : closureFamilyAnchors(owner, closureFamilyAnchorsCache)) { + for (ImClass candidateClass : closureFamilyClassesForAnchor(prog, anchor, closureFamilyClassesByAnchor)) { + for (ImMethod candidate : sortedMethodsForClass(candidateClass, sortedMethodsByClass)) { + if (!runtimeKey.equals(closureRuntimeDispatchKey(candidate))) { + continue; + } + if (!sharesSemanticName(candidate, semanticNames)) { + continue; + } + String candidateName = candidate.getName(); + if (!candidateName.isEmpty()) { + aliases.add(candidateName); + aliases.add(candidateClass.getName() + "_" + candidateName); + } + } + } + } + } + + private static Set semanticNames(ImMethod method) { + Set names = new HashSet<>(); + String semanticName = semanticNameFromMethodName(method.getName()); + if (!semanticName.isEmpty()) { + names.add(semanticName); + } + String sourceSemanticName = sourceSemanticName(method); + if (!sourceSemanticName.isEmpty()) { + names.add(sourceSemanticName); + } + return names; + } + + private static boolean sharesSemanticName(ImMethod method, Set semanticNames) { + if (semanticNames.isEmpty()) { + return false; + } + return semanticNames.contains(semanticNameFromMethodName(method.getName())) + || semanticNames.contains(sourceSemanticName(method)); + } + + private static List sortedMethodsForClass(ImClass c, Map> sortedMethodsByClass) { + return sortedMethodsByClass.computeIfAbsent(c, key -> { + List methods = new ArrayList<>(key.getMethods()); + methods.sort(Comparator.comparing(LuaDispatchPreparation::methodSortKey)); + return methods; + }); + } + + private static Set closureFamilyAnchors(ImClass c, Map> cache) { + return cache.computeIfAbsent(c, key -> { + Set anchors = new TreeSet<>(Comparator.comparing(LuaDispatchPreparation::classSortKey)); + collectClosureFamilyAnchors(key, anchors, new HashSet<>()); + return anchors; + }); + } + + private static void collectClosureFamilyAnchors(ImClass c, Set anchors, Set visited) { + if (c == null || !visited.add(c)) { + return; + } + if (!isClosureGeneratedClass(c)) { + anchors.add(c); + } + for (ImClassType sc : c.getSuperClasses()) { + collectClosureFamilyAnchors(sc.getClassDef(), anchors, visited); + } + } + + private static List closureFamilyClassesForAnchor(ImProg prog, ImClass anchor, Map> cache) { + return cache.computeIfAbsent(anchor, a -> { + List result = new ArrayList<>(); + for (ImClass candidate : prog.getClasses()) { + if (sharesClosureFamilyAnchor(candidate, a, new HashSet<>())) { + result.add(candidate); + } + } + result.sort(Comparator.comparing(LuaDispatchPreparation::classSortKey)); + return result; + }); + } + + private static boolean sharesClosureFamilyAnchor(ImClass c, ImClass anchor, Set visited) { + if (c == null || !visited.add(c)) { + return false; + } + if (c == anchor) { + return true; + } + for (ImClassType sc : c.getSuperClasses()) { + if (sharesClosureFamilyAnchor(sc.getClassDef(), anchor, visited)) { + return true; + } + } + return false; + } + + private static String dispatchSignatureKey(ImMethod method) { + ImFunction implementation = resolveDispatchSignatureImplementation(method, new HashSet<>()); + if (implementation == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + sb.append(typeKey(implementation.getReturnType())).append("|"); + ImVars params = implementation.getParameters(); + for (int i = 1; i < params.size(); i++) { + if (i > 1) { + sb.append(","); + } + sb.append(typeKey(params.get(i).getType())); + } + return sb.toString(); + } + + private static ImFunction resolveDispatchSignatureImplementation(ImMethod method, Set visited) { + if (method == null || !visited.add(method)) { + return null; + } + if (method.getImplementation() != null) { + return method.getImplementation(); + } + List subMethods = new ArrayList<>(method.getSubMethods()); + subMethods.sort(Comparator.comparing(LuaDispatchPreparation::methodSortKey)); + for (ImMethod subMethod : subMethods) { + ImFunction resolved = resolveDispatchSignatureImplementation(subMethod, visited); + if (resolved != null) { + return resolved; + } + } + return null; + } + + private static String closureRuntimeDispatchKey(ImMethod method) { + ImFunction implementation = resolveDispatchSignatureImplementation(method, new HashSet<>()); + if (implementation == null) { + return ""; + } + return "" + Math.max(0, implementation.getParameters().size() - 1); + } + + private static String typeKey(ImType type) { + return type == null ? "" : type.toString(); + } + + private static String methodSortKey(ImMethod method) { + if (method == null) { + return ""; + } + String owner = classSortKey(method.attrClass()); + String impl = method.getImplementation() != null ? method.getImplementation().getName() : ""; + return owner + "|" + method.getName() + "|" + impl; + } + + private static String classSortKey(ImClass c) { + return c == null ? "" : c.getName(); + } + + private static String semanticNameFromMethodName(String methodName) { + if (methodName == null || methodName.isEmpty()) { + return ""; + } + int lastUnderscore = methodName.lastIndexOf('_'); + if (lastUnderscore >= 0 && lastUnderscore + 1 < methodName.length()) { + return methodName.substring(lastUnderscore + 1); + } + return methodName; + } + + private static boolean isClosureGeneratedClass(ImClass c) { + return c != null && c.attrTrace() instanceof ExprClosure; + } + + private static String sourceSemanticName(ImMethod method) { + if (method == null) { + return ""; + } + de.peeeq.wurstscript.ast.Element trace = method.attrTrace(); + if (trace instanceof FuncDef funcDef) { + return funcDef.getName(); + } + if (trace instanceof AstElementWithFuncName withFuncName) { + return withFuncName.getFuncNameId().getName(); + } + if (method.getImplementation() != null) { + String implementationName = method.getImplementation().getName(); + int firstUnderscore = implementationName.indexOf('_'); + if (firstUnderscore > 0) { + return implementationName.substring(0, firstUnderscore); + } + return implementationName; + } + return ""; + } +} diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/OverrideUtils.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/OverrideUtils.java index c356a9184..07aa1a4f4 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/OverrideUtils.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/OverrideUtils.java @@ -119,7 +119,7 @@ public static void addOverride( ImFunction implementation = JassIm.ImFunction(e, subMethod.getName() + "_wrapper", JassIm.ImTypeVars(), parameters, rType, locals, body, flags); tr.getImProg().getFunctions().add(implementation); - ImMethod wrapperMethod = JassIm.ImMethod(e, subMethod.getMethodClass(), subMethod.getName() + "_wrapper", implementation, JassIm.ImMethods(), false); + ImMethod wrapperMethod = JassIm.ImMethod(e, subMethod.getMethodClass(), subMethod.getName() + "_wrapper", implementation, JassIm.ImMethods(), new ArrayList<>(), "", false); subClass.getMethods().add(wrapperMethod); superMethodIm.getSubMethods().add(wrapperMethod); } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaTranslator.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaTranslator.java index 996c1b2bb..5ca6996af 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaTranslator.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaTranslator.java @@ -1,6 +1,5 @@ package de.peeeq.wurstscript.translation.lua.translation; -import de.peeeq.datastructures.UnionFind; import de.peeeq.wurstscript.WLogger; import de.peeeq.wurstscript.ast.*; import de.peeeq.wurstscript.jassIm.*; @@ -17,7 +16,6 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.*; -import java.util.regex.Pattern; import java.util.stream.Stream; import static de.peeeq.wurstscript.translation.lua.translation.ExprTranslation.WURST_SUPERTYPES; @@ -97,6 +95,7 @@ public class LuaTranslator { final ImProg prog; final LuaCompilationUnit luaModel; private final LuaStatements deferredMainInit = LuaAst.LuaStatements(); + private final Map uniqueNameCounters = new HashMap<>(); private final Set usedNames = new HashSet<>(Arrays.asList( // reserved function names "print", "tostring", "error", @@ -270,19 +269,26 @@ private String remapNativeName(String name) { } protected String uniqueName(String name) { - int i = 0; - String rname = name; - while (usedNames.contains(rname)) { - rname = name + ++i; + Integer nextIndex = uniqueNameCounters.get(name); + if (nextIndex == null) { + uniqueNameCounters.put(name, 1); + if (usedNames.add(name)) { + return name; + } + nextIndex = 1; } - usedNames.add(rname); - return rname; + String candidate; + do { + candidate = name + nextIndex; + nextIndex++; + } while (!usedNames.add(candidate)); + uniqueNameCounters.put(name, nextIndex); + return candidate; } public LuaCompilationUnit translate() { collectPredefinedNames(); - normalizeMethodNames(); normalizeFieldNames(); // NormalizeNames.normalizeNames(prog); @@ -372,13 +378,15 @@ private void prependDeferredMainInitToMain() { public static void assertNoLeakedHashtableNativeCalls(String luaCode) { List leaked = new ArrayList<>(); List missingHelpers = new ArrayList<>(); + Set calledFunctionNames = collectCalledFunctionNames(luaCode); + Set definedFunctionNames = collectDefinedFunctionNames(luaCode); for (String nativeName : allHashtableNativeNames()) { - if (containsRegex(luaCode, "\\b" + nativeName + "\\s*\\(")) { + if (calledFunctionNames.contains(nativeName)) { leaked.add(nativeName); } String helperName = "__wurst_" + nativeName; - boolean helperCalled = containsRegex(luaCode, "\\b" + helperName + "\\s*\\("); - boolean helperDefined = containsRegex(luaCode, "\\bfunction\\s+" + helperName + "\\s*\\("); + boolean helperCalled = calledFunctionNames.contains(helperName); + boolean helperDefined = definedFunctionNames.contains(helperName); if (helperCalled && !helperDefined) { missingHelpers.add(helperName); } @@ -393,6 +401,80 @@ public static void assertNoLeakedHashtableNativeCalls(String luaCode) { } } + private static Set collectCalledFunctionNames(String text) { + Set result = new HashSet<>(); + int length = text.length(); + int index = 0; + while (index < length) { + if (!isIdentifierStart(text.charAt(index))) { + index++; + continue; + } + int end = scanIdentifierEnd(text, index + 1); + int next = skipWhitespace(text, end); + if (next < length && text.charAt(next) == '(') { + result.add(text.substring(index, end)); + } + index = end; + } + return result; + } + + private static Set collectDefinedFunctionNames(String text) { + Set result = new HashSet<>(); + int length = text.length(); + int index = 0; + while (index < length) { + if (!matchesWord(text, index, "function")) { + index++; + continue; + } + int nameStart = skipWhitespace(text, index + "function".length()); + if (nameStart >= length || !isIdentifierStart(text.charAt(nameStart))) { + index++; + continue; + } + int nameEnd = scanIdentifierEnd(text, nameStart + 1); + int next = skipWhitespace(text, nameEnd); + if (next < length && text.charAt(next) == '(') { + result.add(text.substring(nameStart, nameEnd)); + } + index = nameEnd; + } + return result; + } + + private static int skipWhitespace(String text, int index) { + while (index < text.length() && Character.isWhitespace(text.charAt(index))) { + index++; + } + return index; + } + + private static int scanIdentifierEnd(String text, int index) { + while (index < text.length() && isIdentifierPart(text.charAt(index))) { + index++; + } + return index; + } + + private static boolean matchesWord(String text, int index, String word) { + int end = index + word.length(); + if (end > text.length() || !text.regionMatches(index, word, 0, word.length())) { + return false; + } + return (index == 0 || !isIdentifierPart(text.charAt(index - 1))) + && (end == text.length() || !isIdentifierPart(text.charAt(end))); + } + + private static boolean isIdentifierStart(char ch) { + return ch == '_' || Character.isLetter(ch); + } + + private static boolean isIdentifierPart(char ch) { + return ch == '_' || Character.isLetterOrDigit(ch); + } + private static List allHashtableNativeNames() { List result = new ArrayList<>(HASHTABLE_NATIVE_NAMES_RAW); result.addAll(HASHTABLE_HANDLE_SAVE_NAMES); @@ -408,10 +490,6 @@ private static List prefixed(List names) { return result; } - private static boolean containsRegex(String text, String regex) { - return Pattern.compile(regex).matcher(text).find(); - } - private boolean isFixedEntryPoint(ImFunction function) { return function == imTr.getMainFunc() || function == imTr.getConfFunc(); } @@ -440,27 +518,19 @@ private void setNameFromTrace(JassImElementWithName named) { } private void normalizeMethodNames() { - // group related methods - UnionFind methodUnions = new UnionFind<>(); + Map> groupedMethods = new TreeMap<>(); for (ImClass c : prog.getClasses()) { for (ImMethod m : c.getMethods()) { - methodUnions.find(m); - for (ImMethod subMethod : m.getSubMethods()) { - methodUnions.union(m, subMethod); - } + groupedMethods.computeIfAbsent(dispatchGroupKey(m), ignored -> new ArrayList<>()).add(m); } } - - // give all related methods the same name in deterministic order - List> groups = new ArrayList<>(); - for (Set group : methodUnions.groups().values()) { - groups.addAll(partitionMethodsByDispatchSignature(group)); - } + List> groups = new ArrayList<>(groupedMethods.values()); groups.sort(Comparator.comparing(g -> g.isEmpty() ? "" : methodSortKey(g.get(0)))); for (List group : groups) { if (group.isEmpty()) { continue; } + group.sort(Comparator.comparing(this::methodSortKey)); String name = uniqueName(group.get(0).getName()); for (ImMethod method : group) { method.setName(name); @@ -1082,27 +1152,12 @@ private void initClassTables(ImClass c) { private void createMethods(ImClass c, LuaVariable classVar) { List allMethods = collectMethodsInHierarchy(c); - Set inHierarchy = new HashSet<>(allMethods); - UnionFind unions = new UnionFind<>(); - for (ImMethod method : allMethods) { - unions.find(method); - for (ImMethod subMethod : method.getSubMethods()) { - if (inHierarchy.contains(subMethod)) { - unions.union(method, subMethod); - } - } - } - - Map> groupedMethods = new HashMap<>(); + Map> groupedMethods = new TreeMap<>(); for (ImMethod method : allMethods) { - ImMethod root = unions.find(method); - groupedMethods.computeIfAbsent(root, k -> new ArrayList<>()).add(method); + groupedMethods.computeIfAbsent(dispatchGroupKey(method), ignored -> new ArrayList<>()).add(method); } - List> groups = new ArrayList<>(); - for (List methods : groupedMethods.values()) { - groups.addAll(partitionMethodsByDispatchSignature(methods)); - } + List> groups = new ArrayList<>(groupedMethods.values()); groups.sort(Comparator.comparing(group -> group.isEmpty() ? "" : methodSortKey(group.get(0)))); Map slotToImpl = new TreeMap<>(); for (List groupMethods : groups) { @@ -1137,92 +1192,25 @@ private void createMethods(ImClass c, LuaVariable classVar) { } } - private List> partitionMethodsByDispatchSignature(Collection methods) { - Map> partitions = new TreeMap<>(); - for (ImMethod method : methods) { - partitions.computeIfAbsent(dispatchSignatureKey(method), k -> new ArrayList<>()).add(method); - } - List> result = new ArrayList<>(partitions.values()); - for (List group : result) { - group.sort(Comparator.comparing(this::methodSortKey)); - } - result.sort(Comparator.comparing(group -> group.isEmpty() ? "" : methodSortKey(group.get(0)))); - return result; - } - - private String dispatchSignatureKey(ImMethod method) { - ImFunction implementation = resolveDispatchSignatureImplementation(method, new HashSet<>()); - if (implementation == null) { - return ""; - } - StringBuilder sb = new StringBuilder(); - sb.append(typeKey(implementation.getReturnType())).append("|"); - ImVars params = implementation.getParameters(); - for (int i = 1; i < params.size(); i++) { - if (i > 1) { - sb.append(","); - } - sb.append(typeKey(params.get(i).getType())); - } - return sb.toString(); - } - - private ImFunction resolveDispatchSignatureImplementation(ImMethod method, Set visited) { - if (method == null || !visited.add(method)) { - return null; - } - if (method.getImplementation() != null) { - return method.getImplementation(); - } - List subMethods = new ArrayList<>(method.getSubMethods()); - subMethods.sort(Comparator.comparing(this::methodSortKey)); - for (ImMethod subMethod : subMethods) { - ImFunction resolved = resolveDispatchSignatureImplementation(subMethod, visited); - if (resolved != null) { - return resolved; - } - } - return null; - } - - private String typeKey(ImType type) { - return type == null ? "" : type.toString(); - } - private Set collectDispatchSlotNames(ImClass receiverClass, List groupMethods) { Set slotNames = new TreeSet<>(); Set semanticNames = new TreeSet<>(); - Set dispatchKeys = new TreeSet<>(); - Set closureRuntimeKeys = new TreeSet<>(); for (ImMethod m : groupMethods) { if (m == null) { continue; } - dispatchKeys.add(dispatchSignatureKey(m)); - closureRuntimeKeys.add(closureRuntimeDispatchKey(m)); - String methodName = m.getName(); - if (!methodName.isEmpty()) { - slotNames.add(methodName); + for (String alias : m.getLuaMethodDispatchAliases()) { + if (alias != null && !alias.isEmpty()) { + slotNames.add(alias); + } } - ImClass owner = m.attrClass(); - String semanticName = semanticNameFromMethodName(methodName); + String semanticName = semanticNameFromMethodName(m.getName()); if (!semanticName.isEmpty()) { semanticNames.add(semanticName); } - if (owner != null && !semanticName.isEmpty()) { - slotNames.add(owner.getName() + "_" + semanticName); - } String sourceSemanticName = sourceSemanticName(m); - if (owner != null && isClosureGeneratedClass(owner) && !sourceSemanticName.isEmpty()) { + if (m.attrClass() != null && m.attrClass().attrTrace() instanceof ExprClosure && !sourceSemanticName.isEmpty()) { semanticNames.add(sourceSemanticName); - slotNames.add(sourceSemanticName); - slotNames.add(owner.getName() + "_" + sourceSemanticName); - } - } - if (receiverClass != null && !dispatchKeys.isEmpty() && !semanticNames.isEmpty()) { - collectEquivalentHierarchyMethodNames(receiverClass, dispatchKeys, semanticNames, slotNames, new HashSet<>()); - if (isClosureGeneratedClass(receiverClass) && !closureRuntimeKeys.isEmpty()) { - collectEquivalentClosureFamilyMethodNames(receiverClass, closureRuntimeKeys, semanticNames, slotNames); } } if (receiverClass != null && !semanticNames.isEmpty()) { @@ -1237,105 +1225,6 @@ private Set collectDispatchSlotNames(ImClass receiverClass, List dispatchKeys, Set semanticNames, - Set slotNames, Set visited) { - if (c == null || !visited.add(c)) { - return; - } - for (ImMethod method : c.getMethods()) { - if (method == null) { - continue; - } - if (!dispatchKeys.contains(dispatchSignatureKey(method))) { - continue; - } - String methodName = method.getName(); - String semanticName = semanticNameFromMethodName(methodName); - String sourceSemanticName = sourceSemanticName(method); - if (!semanticNames.contains(semanticName) && !semanticNames.contains(sourceSemanticName)) { - continue; - } - if (!methodName.isEmpty()) { - slotNames.add(methodName); - slotNames.add(c.getName() + "_" + methodName); - } - } - for (ImClassType sc : c.getSuperClasses()) { - collectEquivalentHierarchyMethodNames(sc.getClassDef(), dispatchKeys, semanticNames, slotNames, visited); - } - } - - private void collectEquivalentClosureFamilyMethodNames(ImClass receiverClass, Set closureRuntimeKeys, - Set semanticNames, Set slotNames) { - Set anchors = new HashSet<>(); - collectClosureFamilyAnchors(receiverClass, anchors, new HashSet<>()); - if (anchors.isEmpty()) { - return; - } - List classes = new ArrayList<>(prog.getClasses()); - classes.sort(Comparator.comparing(this::classSortKey)); - for (ImClass candidateClass : classes) { - if (!sharesClosureFamilyAnchor(candidateClass, anchors, new HashSet<>())) { - continue; - } - List methods = new ArrayList<>(candidateClass.getMethods()); - methods.sort(Comparator.comparing(this::methodSortKey)); - for (ImMethod method : methods) { - if (!closureRuntimeKeys.contains(closureRuntimeDispatchKey(method))) { - continue; - } - String methodName = method.getName(); - String semanticName = semanticNameFromMethodName(methodName); - String sourceSemanticName = sourceSemanticName(method); - if (!semanticNames.contains(semanticName) && !semanticNames.contains(sourceSemanticName)) { - continue; - } - if (!methodName.isEmpty()) { - slotNames.add(methodName); - slotNames.add(candidateClass.getName() + "_" + methodName); - } - } - } - } - - private String closureRuntimeDispatchKey(ImMethod method) { - if (method == null) { - return ""; - } - ImFunction implementation = resolveDispatchSignatureImplementation(method, new HashSet<>()); - if (implementation == null) { - return ""; - } - return "" + Math.max(0, implementation.getParameters().size() - 1); - } - - private void collectClosureFamilyAnchors(ImClass c, Set anchors, Set visited) { - if (c == null || !visited.add(c)) { - return; - } - if (!isClosureGeneratedClass(c)) { - anchors.add(c); - } - for (ImClassType sc : c.getSuperClasses()) { - collectClosureFamilyAnchors(sc.getClassDef(), anchors, visited); - } - } - - private boolean sharesClosureFamilyAnchor(ImClass c, Set anchors, Set visited) { - if (c == null || !visited.add(c)) { - return false; - } - if (anchors.contains(c)) { - return true; - } - for (ImClassType sc : c.getSuperClasses()) { - if (sharesClosureFamilyAnchor(sc.getClassDef(), anchors, visited)) { - return true; - } - } - return false; - } - private void collectClassNamesInHierarchy(ImClass c, Set out, Set visited) { if (c == null || !visited.add(c)) { return; @@ -1498,6 +1387,11 @@ private String methodSortKey(ImMethod m) { return owner + "|" + m.getName() + "|" + impl; } + private String dispatchGroupKey(ImMethod method) { + String key = method.getLuaDispatchGroupKey(); + return key == null || key.isEmpty() ? methodSortKey(method) : key; + } + private String semanticNameFromMethodName(String methodName) { if (methodName == null || methodName.isEmpty()) { return ""; @@ -1509,10 +1403,6 @@ private String semanticNameFromMethodName(String methodName) { return methodName; } - private boolean isClosureGeneratedClass(ImClass c) { - return c != null && c.attrTrace() instanceof ExprClosure; - } - private String sourceSemanticName(ImMethod method) { if (method == null) { return "";