Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import de.peeeq.wurstscript.attributes.names.DesugarArrayLength;
import de.peeeq.wurstscript.gui.WurstGui;
import de.peeeq.wurstscript.validation.GlobalCaches;
import de.peeeq.wurstscript.validation.TRVEHelper;
import de.peeeq.wurstscript.validation.WurstValidator;

import java.util.ArrayList;
Expand Down Expand Up @@ -36,7 +35,6 @@ public void checkProg(WurstModel root, Collection<CompilationUnit> toCheck) {
if (root.isEmpty()) {
return;
}
TRVEHelper.protectedVariables.clear();
new DesugarArrayLength().run(root);
gui.sendProgress("Checking Files");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;
import de.peeeq.wurstscript.translation.imtranslation.ImTranslator;
import de.peeeq.wurstscript.utils.Utils;
import de.peeeq.wurstscript.validation.TRVEHelper;
import de.peeeq.wurstscript.validation.NamePreservation;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
Expand Down Expand Up @@ -47,8 +47,8 @@ public int optimize(ImTranslator trans) {
// cannot optimize arrays yet
continue;
}
if (TRVEHelper.protectedVariables.contains(v.getName())) {
// keep TRVE vars
if (NamePreservation.isPreserved(v)) {
// keep names which are part of the external Warcraft III API
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,32 @@
import de.peeeq.wurstscript.jassIm.ImVar;
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;
import de.peeeq.wurstscript.translation.imtranslation.ImTranslator;
import de.peeeq.wurstscript.validation.TRVEHelper;
import de.peeeq.wurstscript.validation.NamePreservation;

import java.util.HashSet;
import java.util.Set;

public class ImCompressor {

private final ImTranslator trans;
private final ImProg prog;
private final NameGenerator ng;
private final Set<String> preservedNames = new HashSet<>();

public ImCompressor(ImTranslator translator) {
this.trans = translator;
this.prog = translator.getImProg();
ng = new NameGenerator();
for (ImVar global : prog.getGlobals()) {
if (NamePreservation.isPreserved(global)) {
preservedNames.add(global.getName());
}
}
for (ImFunction function : ImHelper.calculateFunctionsOfProg(prog)) {
if (NamePreservation.isPreserved(function)) {
preservedNames.add(function.getName());
}
}
}

public void compressNames() {
Expand All @@ -27,21 +41,21 @@ public void compressNames() {

public void compressGlobals() {
for (final ImVar global : prog.getGlobals()) {
if (global.getIsBJ() || TRVEHelper.protectedVariables.contains(global.getName())) {
// do not rename bj constants
// do not rename TRVE vars
if (global.getIsBJ() || NamePreservation.isPreserved(global)) {
// do not rename bj constants or names exposed to Warcraft III
continue;
}

String replacement = ng.getUniqueToken();
String replacement = nextCompressedName();

global.setName(replacement);
}
}

public void compressFunctions() {
for (ImFunction func : ImHelper.calculateFunctionsOfProg(prog)) {
if (func.isNative() || func.isBj() || func.isCompiletime() || func.isExtern()) {
if (func.isNative() || func.isBj() || func.isCompiletime() || func.isExtern()
|| NamePreservation.isPreserved(func)) {
Comment on lines +57 to +58

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retain externally invoked preserved functions

When an @preserveName function has no IM caller—which is the expected case for a callback invoked solely by Warcraft or other external code—ImOptimizer.removeGarbage() runs before this compressor and retains only functions reachable from main or config; Lua's RemoveGarbage likewise ignores the preservation flag. The function is therefore deleted before this check can preserve its name. Add preserved functions as reachability roots in both backend paths so the annotation can expose an external-only entry point.

AGENTS.md reference: AGENTS.md:L229-L235

Useful? React with 👍 / 👎.

// do not rename builtin an bj functions
continue;
Comment on lines +57 to 60

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reserve preserved names before generating compressed tokens

When a preserved identifier matches a token produced by NameGenerator (for example, w, the first token), skipping it here does not prevent a later ordinary symbol from being renamed to that same identifier. With an earlier ordinary function and a later @preserveName function w, Jass name deduplication can consequently rename the preserved function to w_1, while Lua can emit colliding globals; either outcome defeats the externally visible name contract. Seed the compressor's reserved-name set with all names that must remain unchanged before assigning any tokens.

Useful? React with 👍 / 👎.

}
Expand All @@ -50,12 +64,20 @@ public void compressFunctions() {
// do not rename main and config functions
continue;
}
String rname = ng.getUniqueToken();
String rname = nextCompressedName();
func.setName(rname);
}

}

private String nextCompressedName() {
String replacement;
do {
replacement = ng.getUniqueToken();
} while (preservedNames.contains(replacement));
return replacement;
}

private void compressLocals(ImFunction func) {
// TODO compressing locals should not use the global name pool but use a own pool
for (ImVar local : func.getParameters()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import de.peeeq.wurstscript.translation.imtranslation.ImTranslator;
import de.peeeq.wurstscript.types.TypesHelper;
import de.peeeq.wurstscript.utils.Pair;
import de.peeeq.wurstscript.validation.TRVEHelper;
import de.peeeq.wurstscript.validation.NamePreservation;

import java.util.stream.Collectors;

Expand Down Expand Up @@ -174,13 +174,13 @@ public void visit(ImSet e) {
super.visit(e);
if (e.getLeft() instanceof ImVarAccess) {
ImVarAccess va = (ImVarAccess) e.getLeft();
if (!readVars.contains(va.getVar()) && !TRVEHelper.protectedVariables.contains(va.getVar().getName())) {
if (!readVars.contains(va.getVar()) && !NamePreservation.isPreserved(va.getVar())) {
List<ImExpr> sideEffects = collectSideEffects(e.getRight(), sideEffectAnalyzer);
replacements.add(Pair.create(e, sideEffects));
}
} else if (e.getLeft() instanceof ImVarArrayAccess) {
ImVarArrayAccess va = (ImVarArrayAccess) e.getLeft();
if (!readVars.contains(va.getVar()) && !TRVEHelper.protectedVariables.contains(va.getVar().getName())) {
if (!readVars.contains(va.getVar()) && !NamePreservation.isPreserved(va.getVar())) {
List<ImExpr> exprs = new ArrayList<>();
for (ImExpr index : va.getIndexes()) {
exprs.addAll(collectSideEffects(index, sideEffectAnalyzer));
Expand All @@ -190,13 +190,13 @@ public void visit(ImSet e) {
}
} else if (e.getLeft() instanceof ImTupleSelection) {
ImVar var = TypesHelper.getTupleVar((ImTupleSelection) e.getLeft());
if(var != null && !readVars.contains(var) && !TRVEHelper.protectedVariables.contains(var.getName())) {
if(var != null && !readVars.contains(var) && !NamePreservation.isPreserved(var)) {
List<ImExpr> sideEffects = collectSideEffects(e.getRight(), sideEffectAnalyzer);
replacements.add(Pair.create(e, sideEffects));
}
} else if(e.getLeft() instanceof ImMemberAccess) {
ImMemberAccess va = ((ImMemberAccess) e.getLeft());
if (!readVars.contains(va.getVar()) && !TRVEHelper.protectedVariables.contains(va.getVar().getName())) {
if (!readVars.contains(va.getVar()) && !NamePreservation.isPreserved(va.getVar())) {
List<ImExpr> sideEffects = collectSideEffects(e.getRight(), sideEffectAnalyzer);
replacements.add(Pair.create(e, sideEffects));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import de.peeeq.wurstscript.translation.imoptimizer.RestrictedCompressedNames;
import de.peeeq.wurstscript.translation.imtranslation.FunctionFlagEnum;
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;
import de.peeeq.wurstscript.validation.NamePreservation;
import de.peeeq.wurstscript.utils.Utils;
import org.eclipse.jdt.annotation.Nullable;

Expand Down Expand Up @@ -50,6 +51,11 @@ public JassProg translate() {

translateFunctionTransitive(mainFunc);
translateFunctionTransitive(confFunction);
for (ImFunction function : ImHelper.calculateFunctionsOfProg(imProg)) {
if (NamePreservation.isPreserved(function)) {
translateFunctionTransitive(function);
}
}

return prog;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import de.peeeq.wurstscript.attributes.names.OtherLink;
import de.peeeq.wurstscript.jassIm.*;
import de.peeeq.wurstscript.types.*;
import de.peeeq.wurstscript.validation.NamePreservation;
import de.peeeq.wurstscript.utils.Utils;
import io.vavr.control.Either;
import io.vavr.control.Option;
Expand Down Expand Up @@ -588,6 +589,7 @@ private static ImExpr translateFunctionCall(FunctionCall e, ImTranslator t, ImFu
String exFunc = s.getValS();
NameLink func = Utils.getFirst(e.lookupFuncs(exFunc));
ImFunction executedFunc = t.getFuncFor((TranslatedToImFunction) func.getDef());
NamePreservation.preserve(executedFunc);
return ImFunctionCall(e, executedFunc, ImTypeArguments(), JassIm.ImExprs(), true, CallType.EXECUTE);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public enum FunctionFlagEnum implements FunctionFlag {
IS_TEST,
IS_COMPILETIME_NATIVE,
IS_EXTERN,
IS_VARARG
IS_VARARG,
PRESERVE_NAME

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import de.peeeq.wurstscript.types.*;
import de.peeeq.wurstscript.utils.Pair;
import de.peeeq.wurstscript.utils.Utils;
import de.peeeq.wurstscript.validation.TRVEHelper;
import de.peeeq.wurstscript.validation.NamePreservation;
import de.peeeq.wurstscript.validation.WurstValidator;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
Expand Down Expand Up @@ -1067,6 +1067,9 @@ public ImFunction getFuncFor(TranslatedToImFunction funcDef) {
if (m instanceof Annotation) {
Annotation annotation = (Annotation) m;
flags.add(new FunctionFlagAnnotation(annotation.getAnnotationType()));
if (NamePreservation.isPreserveAnnotation(annotation.getAnnotationType())) {
flags.add(PRESERVE_NAME);
Comment on lines +1070 to +1071

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject duplicate preserved runtime names

When two legal top-level overloads with the same source name both carry @preserveName, this assigns PRESERVE_NAME to both IM functions. Lua then emits both definitions under the same global name, so the latter overwrites the former and calls to one overload execute the wrong body; Jass instead suffixes one in makeNamesUnique, violating its preservation contract. Diagnose multiple preserved functions sharing a runtime name rather than marking all of them preservable.

AGENTS.md reference: AGENTS.md:L229-L235

Useful? React with 👍 / 👎.

}
}
}
}
Expand Down Expand Up @@ -1462,13 +1465,18 @@ private void calculateCallRelationsAndVariables(boolean includeUsedVariables) {
final ImFunction conf = getConfFunc();
if (conf != null && conf != main) calculateCallRelations(conf, includeUsedVariables);

// mark protected globals as read
// TRVEHelper.protectedVariables is presumably a HashSet<String> (O(1) contains)
for (ImVar global : imProg.getGlobals()) {
if (TRVEHelper.protectedVariables.contains(global.getName())) {
readVariables.add(global);
// Preserved functions are externally visible entry points even when no Wurst code calls
// them. Keep their bodies and everything they call reachable for both backends.
for (ImFunction function : ImHelper.calculateFunctionsOfProg(imProg)) {
if (NamePreservation.isPreserved(function)) {
calculateCallRelations(function, includeUsedVariables);
}
}

// Mark externally visible globals as read so they survive garbage collection.
for (ImVar global : imProg.getGlobals()) {
if (NamePreservation.isPreserved(global)) readVariables.add(global);
}
}

private void calculateCallRelations(ImFunction rootFunction, boolean includeUsedVariables) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import de.peeeq.wurstscript.types.TypesHelper;
import de.peeeq.wurstscript.utils.Lazy;
import de.peeeq.wurstscript.utils.Utils;
import de.peeeq.wurstscript.validation.NamePreservation;

import java.util.*;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -156,8 +157,10 @@ private static final class LazyArrayDefault {
@Override
public LuaVariable initFor(ImVar a) {
String name = a.getName();
if (!a.getIsBJ()) {
if (!a.getIsBJ() && !NamePreservation.isPreserved(a)) {
name = uniqueName(name);
} else {
usedNames.add(name);
}
return LuaAst.LuaVariable(name, LuaAst.LuaNoExpr());
}
Expand All @@ -168,9 +171,10 @@ public LuaVariable initFor(ImVar a) {
@Override
public LuaFunction initFor(ImFunction a) {
String name = a.getName();
if (!a.isExtern() && !a.isBj() && !a.isNative() && !isFixedEntryPoint(a)) {
if (!a.isExtern() && !a.isBj() && !a.isNative()
&& !isFixedEntryPoint(a) && !NamePreservation.isPreserved(a)) {
name = uniqueName(name);
Comment on lines +175 to 176

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject Lua-invalid preserved names

When a valid Wurst function uses a Lua-only keyword such as goto, adding @preserveName takes this branch and skips uniqueName(), which is the only path through LuaIdentifiers.toIdentifier; the Lua AST consequently emits function goto(...), which Lua 5.3 rejects. Jass accepts this identifier, so either emit such preserved globals through syntax that supports arbitrary keys or diagnose names that cannot be represented in Lua.

AGENTS.md reference: AGENTS.md:L229-L235

Useful? React with 👍 / 👎.

} else if (isFixedEntryPoint(a)) {
} else if (isFixedEntryPoint(a) || NamePreservation.isPreserved(a)) {
Comment on lines +174 to +177

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep preserved class functions named in Lua

When @preserveName is applied to a class/static function, this branch initially retains its IM name, but translateClass later unconditionally executes luaFunc.getFor(f).setName(uniqueName(c.getName() + "_" + f.getName())), so Lua still renames the externally invoked function while Jass skips compression. Fresh evidence beyond the earlier Lua collision comment is this later overwrite after lazy initialization; exempt preserved class functions from that final renaming and reserve their names.

AGENTS.md reference: AGENTS.md:L229-L235

Useful? React with 👍 / 👎.

usedNames.add(name);
Comment on lines +177 to 178

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reserve preserved Lua names before lazy translation

In Lua builds without compressor-driven renaming, if a reachable ordinary function or global has the same IM spelling as a preserved function, the ordinary symbol can claim the name through uniqueName() before this lazy initializer runs. This usedNames.add(name) then silently fails, so both definitions are emitted under the same Lua global and one overwrites the other. Include preserved functions and globals in collectPredefinedNames() so ordinary symbols are uniqued around them before translation begins.

AGENTS.md reference: AGENTS.md:L229-L235

Useful? React with 👍 / 👎.

}

Expand Down Expand Up @@ -327,8 +331,8 @@ protected String uniqueName(String rawName) {
}

public LuaCompilationUnit translate() {
assertNoDanglingFunctionReferences(prog);
collectPredefinedNames();
assertNoDanglingFunctionReferences(prog);

normalizeFieldNames();

Expand Down Expand Up @@ -488,7 +492,8 @@ private boolean isFixedEntryPoint(ImFunction function) {

private void collectPredefinedNames() {
for (ImFunction function : prog.getFunctions()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reserve preserved class functions before allocating Lua names

When a class Foo has @preserveName function bar, its preserved Lua name is Foo_bar; if another class is named Foo_bar, the class-variable pass claims that name before translateClass() runs, and the preserved function is then emitted with the same global name, overwriting the sibling class table. Fresh evidence after the earlier class-function comment is that collectPredefinedNames() still scans only prog.getFunctions(), while these functions live in ImClass.getFunctions(). Include preserved class functions in this reservation pass before any Lua names are allocated.

AGENTS.md reference: AGENTS.md:L229-L235

Useful? React with 👍 / 👎.

if (function.isBj() || function.isExtern() || function.isNative()) {
if (function.isBj() || function.isExtern() || function.isNative()
|| NamePreservation.isPreserved(function)) {
// Don't rename Wurst-internal stubs (names starting with __wurst_)
// since their names are intentionally different from their trace's source name.
if (!function.getName().startsWith("__wurst_")) {
Expand All @@ -502,6 +507,8 @@ private void collectPredefinedNames() {
if (global.getIsBJ()) {
setNameFromTrace(global);
usedNames.add(global.getName());
} else if (NamePreservation.isPreserved(global)) {
usedNames.add(global.getName());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import de.peeeq.wurstscript.jassIm.*;
import de.peeeq.wurstscript.translation.imtranslation.ImHelper;
import de.peeeq.wurstscript.translation.imtranslation.ImTranslator;
import de.peeeq.wurstscript.validation.TRVEHelper;
import de.peeeq.wurstscript.validation.NamePreservation;

import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -122,7 +122,7 @@ public static void removeGarbage(ImProg prog, ImTranslator translator) {
Used used = collectUsed(prog, translator);

prog.getClasses().removeIf(c -> !used.getClasses().contains(c));
prog.getGlobals().removeIf(g -> !used.getVars().contains(g) && !TRVEHelper.protectedVariables.contains(g.getName()));
prog.getGlobals().removeIf(g -> !used.getVars().contains(g) && !NamePreservation.isPreserved(g));
prog.getFunctions().removeIf(f -> !used.getFunctions().contains(f));
prog.getMethods().removeIf(m -> !used.getMethods().contains(m));
for (ImMethod m : prog.getMethods()) {
Expand Down Expand Up @@ -153,7 +153,8 @@ private static Used collectUsed(ImProg prog, ImTranslator translator,
Used used = new Used(translator, ignoredInitializers);
for (ImFunction f : ImHelper.calculateFunctionsOfProg(prog)) {
if (f.getName().equals("main")
|| f.getName().equals("config")) {
|| f.getName().equals("config")
|| NamePreservation.isPreserved(f)) {
visitFunction(f, used);
Comment on lines 154 to 158

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain owners of preserved static class functions

When @preserveName is applied to a static class function whose body does not otherwise reference its declaring class, rooting the function here only adds it to used.functions. Unlike an instance function, it has no this parameter whose type would retain the owning ImClass, so the class is removed at line 124 and the preserved function disappears with it from Lua output. Retain the function's owning class as part of this root traversal.

AGENTS.md reference: AGENTS.md:L229-L235

Useful? React with 👍 / 👎.

}
}
Expand Down Expand Up @@ -221,7 +222,6 @@ private static void visitFunction(ImFunction f, Used used) {
return;
}
used.addFunction(f);

visitType(f.getReturnType(), used);
f.accept(new Element.DefaultVisitor() {
@Override
Expand Down
Loading
Loading