Skip to content

Order a class's functions independently of the compilation unit order - #1425

Merged
Frotty merged 2 commits into
masterfrom
claude/confident-dijkstra-f4ea81
Oct 9, 2026
Merged

Frotty merged 2 commits into
masterfrom
claude/confident-dijkstra-f4ea81

Conversation

@Frotty

@Frotty Frotty commented Oct 9, 2026

Copy link
Copy Markdown
Member

A class's function joined its class's function list the first time anything asked for it (ImTranslator.getFuncFor, the constructor and destroy functions). Packages are translated depth first from each compilation unit, imports first, so the imports of a package which do not import each other, and the packages of an import cycle, ask in the order of the units. The backends emit a class's functions in list order and Lua numbers same-named overloads in it, so the Lua depended on the unit order:

  • Base implements IntM, StrM with m(int) and m(string), the interfaces in two unrelated packages: Base_Base_m and Base_Base_m1 swapped bodies and slot bindings.
  • A superclass and an interface in unrelated packages swapped the order of the subclass's functions.
  • Across an import cycle, a call in one package asked for a method before or after its class was translated.

sortEverything does not sort class function lists, and sorting them would change the output of programs which are already deterministic.

Fix

A class's function now waits until the elements of its class's package are translated. The class then takes its functions in the order they get when that package is translated first:

  1. those asked for by packages it imports (directly or not) which do not import it back, ranked by the depth-first import order from the package;
  2. then those asked for by the package itself, in the order of its definitions (a class after a same-package superclass it pulls in first);
  3. requests from any other package (unrelated, or in a cycle with it) do not count.

Requests are attributed to the top-level definition being translated (TLDTranslation), including a superclass translated first by a subclass in another package. getMethodFor and destroyMethod now ask for their function on every lookup so a cached hit still counts. The class init function goes through the same path. Where the unit order made no difference, this is exactly the old order.

Tests

New DeterministicChecks cases, each compiled for all backends and run in two unit orders, each failing on master:

  • overloadsImplementingInterfacesOfOtherPackagesEmitTheSameLuaInAnyUnitOrder (the overload repro)
  • functionsAskedForByUnrelatedPackagesEmitTheSameLuaInAnyUnitOrder (superclass and interface in unrelated packages)
  • functionsAskedForAcrossAnImportCycleEmitTheSameLuaInAnyUnitOrder (call across a cycle plus a cross-package subclass in it; also fails if only the pull-in attribution is removed)

DeterministicChecks, InterfaceTests, ClassesTests, LuaBackendAuditTests and LuaTranslationTests pass on the merged tree.

Real maps

Castle fight and zombie defense built with -lua -inline -localOptimizations, Lua compared with cmp (zombie defense's build date masked; two baseline builds are byte-identical):

  • The fix changes the function order of 6 of 3512 classes in castle fight and 17 of 2717 in zombie defense. Each is in an import cycle (castle fight: CampaignBots with the campaign chapters, CustomAI/DraftOrchestrator in a 42-package cycle; zombie defense: a 58-package cycle) or extends classes from one (JungleBoss, PlagueBoss, PlainsBoss below Boss/Zombie). So their order depended on where translation entered the cycle.
  • A temporary build which puts those lists back in their old order emits the old Lua byte for byte on both maps, so the reordering is the only change.
  • A first version which deferred functions only until their class was translated changed 1667 castle fight classes, most of them stable (a superclass or interface in an imported package asks for overrides first), so it was not used.

A class's function joined its class's list the first time anything asked
for it. Packages are translated depth first from each compilation unit in
turn, so imports of a package which do not import each other, and the
packages of an import cycle, ask in the order of the units: two interfaces
in unrelated packages asking for the overloads implementing them swapped
Base_Base_m and Base_Base_m1 in the Lua, and a superclass and an interface
in unrelated packages swapped the order in which a class's functions are
emitted.

A class's function now waits until the elements of its class's package are
translated. The class then takes its functions in the order they get when
that package is translated first: those asked for by the packages it imports
and which do not import it back, in the order of their translation from it,
then those asked for by the package itself, in the order of its definitions.
Requests from other packages do not count. A superclass translated first by
a subclass in another package counts for its own package.

Where the unit order made no difference this is the old order. Castle fight
and zombie defense (-lua -inline -localOptimizations) change only in the
function order of 6 and 17 classes, each in or below an import cycle;
putting those lists back in the old order gives the old Lua byte for byte.
@chatgpt-codex-connector

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-10-09T22:13:14.046855Z 61fbf02 PR opened
ℹ️ 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.

@Frotty
Frotty merged commit d4002b8 into master Oct 9, 2026
3 checks passed
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