Skip to content

Preserve externally visible names during optimization - #1277

Merged
Frotty merged 8 commits into
masterfrom
codex/preserve-name-annotation
Aug 31, 2026
Merged

Preserve externally visible names during optimization#1277
Frotty merged 8 commits into
masterfrom
codex/preserve-name-annotation

Conversation

@Frotty

@Frotty Frotty commented Aug 31, 2026

Copy link
Copy Markdown
Member

Summary

  • Add @preserveName support for functions when -opt compresses identifiers.
  • Carry preservation through IM metadata and reuse it for ExecuteFunc targets.
  • Replace TRVE's global name-based protection set with metadata attached to the resolved AST global.
  • Keep preserved globals alive through garbage collection, inlining, and optimization.

Acceptance criteria

  • An annotated function retains its source name under -opt.
  • ExecuteFunc targets retain their source names and receive the preserved name.
  • Existing direct and hooked TriggerRegisterVariableEvent behavior remains intact.
  • No global name-based protection map remains.

Checks

  • ./gradlew test — passed on this branch (BUILD SUCCESSFUL in 5m 11s).

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T15:28:39.005378Z f578b88 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3e1667ff98

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +43 to +44
if (func.isNative() || func.isBj() || func.isCompiletime() || func.isExtern()
|| NamePreservation.isPreserved(func)) {

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 👍 / 👎.

Comment on lines +3732 to +3735
NameLink variable = de.peeeq.wurstscript.attributes.names.NameResolution
.lookupVarNoConfig(useSite, variableName, false);
if (variable != null && variable.getDef() instanceof GlobalVarDef) {
NamePreservation.preserve((GlobalVarDef) variable.getDef());

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 Resolve TRVE strings independently of lexical shadows

When a TriggerRegisterVariableEvent call is in a scope with a local or parameter matching the target global's runtime name, this ordinary lexical lookup returns that non-global and the preservation marker is never attached; qualified emitted names for static fields similarly need not resolve as source identifiers. The old name-based protection still matched the emitted global, whereas optimization can now remove or rename it. Resolve the runtime spelling to a global AST identity without allowing local shadowing, then carry that node identity into IM metadata.

AGENTS.md reference: AGENTS.md:L217-L220

Useful? React with 👍 / 👎.

@Frotty

Frotty commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ed1157af2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +43 to 46
if (func.isNative() || func.isBj() || func.isCompiletime() || func.isExtern()
|| NamePreservation.isPreserved(func)) {
// do not rename builtin an bj functions
continue;

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 👍 / 👎.

Comment on lines +177 to 178
} else if (isFixedEntryPoint(a) || NamePreservation.isPreserved(a)) {
usedNames.add(name);

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 👍 / 👎.

@Frotty

Frotty commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: de84a794c4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +61 to +63
private static String runtimeName(GlobalVarDef variable) {
if (variable.getParent() != null && variable.getParent().getParent() instanceof NamedScope scope) {
return runtimeName(scope) + "_" + variable.getName();

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 Preserve lowered tuple globals used by TRVE

When TriggerRegisterVariableEvent targets a lowered tuple component—for example, global p of tuple pair(real x, real y) via "pkg_p_x"—this computes only pkg_p. EliminateTuples later emits scalar globals such as pkg_p_x, so no source marker is attached and optimization may remove or rename the actual watched global; the previous emitted-name set handled this spelling. Resolve the component spelling to its structural source path and propagate that identity to the corresponding scalar IM variable.

AGENTS.md reference: AGENTS.md:L217-L220

Useful? React with 👍 / 👎.

Comment on lines +174 to +177
if (!a.isExtern() && !a.isBj() && !a.isNative()
&& !isFixedEntryPoint(a) && !NamePreservation.isPreserved(a)) {
name = uniqueName(name);
} else if (isFixedEntryPoint(a)) {
} else if (isFixedEntryPoint(a) || NamePreservation.isPreserved(a)) {

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 👍 / 👎.

Comment on lines +38 to +40
Annotation marker = Ast.Annotation(variable.getSource(),
Ast.Identifier(variable.getSource(), ANNOTATION.substring(1)), Ast.Arguments());
variable.getModifiers().add(marker);

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 Clear synthetic preservation metadata between checks

When an incremental LSP check marks a global from an unchanged compilation unit and a later edit removes the TRVE registration from another unit, this synthetic annotation remains attached to the reused AST node because nothing distinguishes or clears it. Subsequent builds therefore retain and reserve that global even though a clean build of the same sources would not, making optimized output depend on edit history; keep this metadata transient or remove synthetic markers before each validation.

AGENTS.md reference: AGENTS.md:L291-L294

Useful? React with 👍 / 👎.

Comment on lines +49 to +50
public static void preserveGlobalWithRuntimeName(WurstModel model, String runtimeName) {
model.accept(new Element.DefaultVisitor() {

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 Index runtime globals before processing TRVE calls

For a project containing many TRVE registrations, every constant call invokes this method and traverses the entire WurstModel, making validation cost proportional to registrations times total AST size rather than a single pass as before. Build a runtime-name-to-global index once per validation run, or collect requested names and resolve them in one model traversal.

AGENTS.md reference: AGENTS.md:L88-L91

Useful? React with 👍 / 👎.

@Frotty

Frotty commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1eb32b2d90

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -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 👍 / 👎.

Comment on lines +93 to +95
if (runtimeNameIndex != null) {
runtimeNameIndex.clearSyntheticMarkers();
}

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 Clear TRVE markers across validation instances

During incremental checking, this cleanup never sees markers from the preceding check because WurstChecker.checkProg() constructs a new WurstValidator for every invocation, so runtimeNameIndex is always null here initially. If a TRVE registration is removed while the referenced global's compilation unit is reused, its synthetic @preserveName annotation therefore remains and the optimized output continues retaining it based on edit history. Fresh evidence beyond the prior comment is the current per-check construction at WurstChecker.java:63; track or clear synthetic markers outside the validator instance.

AGENTS.md reference: AGENTS.md:L291-L294

Useful? React with 👍 / 👎.

@Frotty

Frotty commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 781b39da46

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 154 to 158
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);

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 👍 / 👎.

Comment on lines +93 to 96
NamePreservation.clearSyntheticMarkers(prog);
runtimeNameIndex = NamePreservation.indexGlobals(prog);

lightValidation(toCheck);

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 Recompute markers retained by unchanged compilation units

During a partial LSP check where an unrelated compilation unit changes but a TRVE call remains in an unchanged unit, this clears synthetic markers across the entire model while lightValidation(toCheck) only revisits the selected units. The unchanged registration therefore does not recreate its marker, and translating that current model can remove or rename the watched global even though a clean check of the same sources preserves it. Either recompute TRVE preservation from the full model or limit cleanup to registrations that are actually reprocessed.

AGENTS.md reference: AGENTS.md:L291-L294

Useful? React with 👍 / 👎.

@Frotty

Frotty commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

@codex review

@Frotty
Frotty force-pushed the codex/preserve-name-annotation branch from 9a4887f to f578b88 Compare August 31, 2026 15:10
@Frotty

Frotty commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f578b88008

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +175 to 176
&& !isFixedEntryPoint(a) && !NamePreservation.isPreserved(a)) {
name = uniqueName(name);

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 👍 / 👎.

Comment on lines +1070 to +1071
if (NamePreservation.isPreserveAnnotation(annotation.getAnnotationType())) {
flags.add(PRESERVE_NAME);

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 👍 / 👎.

@Frotty
Frotty force-pushed the codex/preserve-name-annotation branch from f578b88 to fd7d4b4 Compare August 31, 2026 15:31
@Frotty
Frotty merged commit ff1979a into master Aug 31, 2026
3 checks passed
@Frotty
Frotty deleted the codex/preserve-name-annotation branch August 31, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant