@@ -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 )) {
0 commit comments