Skip to content

Commit 9861d7a

Browse files
committed
Specialize static-owning generic factories
1 parent 48c2f77 commit 9861d7a

2 files changed

Lines changed: 67 additions & 4 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/EliminateGenerics.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -807,9 +807,10 @@ private boolean functionNeedsSpecialization(ImFunction function, Set<ImFunction>
807807
/**
808808
* Whether a function must be specialised even on Lua, which otherwise keeps generics erased.
809809
* <p>
810-
* Two operations need the concrete type argument: constructing a value of it, and dispatching
811-
* on a type class bound. Specialising these paths keeps a bounded generic as cheap on Lua as it
812-
* is on Jass, at the cost of one copy per instantiation actually used.
810+
* Concrete type arguments are needed when constructing a value of them, dispatching on a type
811+
* class bound, or constructing a generic class whose static storage is per instantiation.
812+
* Specialising these paths keeps a bounded generic as cheap on Lua as it is on Jass, at the cost
813+
* of one copy per instantiation actually used.
813814
*/
814815
private boolean functionNeedsSpecialization(ImFunction function, Set<ImFunction> visitedFunctions,
815816
Set<ImMethod> visitedMethods) {
@@ -849,7 +850,8 @@ public void visit(ImAlloc alloc) {
849850

850851
@Override
851852
public void visit(ImFunctionCall call) {
852-
if (translator.isGenericNewMarker(call.getFunc())
853+
if (constructsClassOwningGenericGlobals(function, call)
854+
|| translator.isGenericNewMarker(call.getFunc())
853855
|| functionNeedsSpecialization(call.getFunc(), visitedFunctions, visitedMethods)) {
854856
found[0] = true;
855857
return;
@@ -869,6 +871,27 @@ public void visit(ImMethodCall call) {
869871
return found[0];
870872
}
871873

874+
/**
875+
* A generic caller containing {@code new Box<T>()} must be revisited after {@code T} becomes
876+
* concrete so each constructed instantiation can register its own static storage. Detect the
877+
* constructor call at the caller boundary; marking the constructor implementation itself would
878+
* unnecessarily redirect ordinary objects away from Lua's erased representation.
879+
*/
880+
private boolean constructsClassOwningGenericGlobals(ImFunction enclosingFunction,
881+
ImFunctionCall call) {
882+
if (!(call.getFunc().getTrace() instanceof ConstructorDef)) {
883+
return false;
884+
}
885+
// A lowered constructor wrapper calls the class initializer carrying the same source
886+
// ConstructorDef. That call implements the current allocation; it is not another generic
887+
// allocation hidden inside this function and direct callers register it themselves.
888+
if (enclosingFunction.getTrace() == call.getFunc().getTrace()) {
889+
return false;
890+
}
891+
ImClass owner = classOwning(call.getFunc());
892+
return owner != null && classOwnsGenericGlobals(owner);
893+
}
894+
872895
private boolean methodNeedsSpecialization(ImMethod method, Set<ImFunction> visitedFunctions,
873896
Set<ImMethod> visitedMethods) {
874897
if (!visitedMethods.add(method)) {

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/LuaBackendAuditTests.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,46 @@ public void eachConstructedErasedInstantiationGetsItsOwnStaticInitializer() {
10531053
);
10541054
}
10551055

1056+
@Test
1057+
public void genericFactoryAllocationSpecializesStaticOwningClass() throws IOException {
1058+
test().testLua(true).executeProg().lines(
1059+
"package Test",
1060+
"native testSuccess()",
1061+
"int bumps",
1062+
"function bump() returns int",
1063+
" bumps++",
1064+
" return bumps",
1065+
"class Box<T:>",
1066+
" static int value = bump()",
1067+
" construct()",
1068+
" static function get() returns int",
1069+
" return value",
1070+
"function make<T:>() returns Box<T>",
1071+
" return new Box<T>()",
1072+
"function forward<T:>() returns Box<T>",
1073+
" return make<T>()",
1074+
"class Maker<T:>",
1075+
" construct()",
1076+
" function makeBox() returns Box<T>",
1077+
" return new Box<T>()",
1078+
"init",
1079+
" let first = forward<int>()",
1080+
" let second = forward<string>()",
1081+
" let third = new Maker<real>().makeBox()",
1082+
" if first != null and second != null and third != null",
1083+
" and Box<int>.get() == 1 and Box<string>.get() == 2",
1084+
" and Box<real>.get() == 3 and bumps == 3",
1085+
" testSuccess()"
1086+
);
1087+
1088+
String compiled = compiledLua("genericFactoryAllocationSpecializesStaticOwningClass");
1089+
assertEquals("the shared constructor must allocate ordinary objects on the erased Lua class",
1090+
1, countOccurrences(compiled, "= Box:create()"));
1091+
assertFalse("static specialization must not create specialized object classes",
1092+
java.util.regex.Pattern.compile("(?m)^Box_specialized\\S* = \\(\\{\\}\\)$")
1093+
.matcher(compiled).find());
1094+
}
1095+
10561096
@Test
10571097
public void randomizedNestedGenericTupleClassShapesMatchAllBackends() {
10581098
record Shape(String type, String value, int constructions) {}

0 commit comments

Comments
 (0)