Skip to content

Commit cdc2a90

Browse files
committed
Collect fixed generic method operations
1 parent 408e1c9 commit cdc2a90

2 files changed

Lines changed: 87 additions & 23 deletions

File tree

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

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public class EliminateGenerics {
4242
private final Set<Element> specializedCallSites = Collections.newSetFromMap(new IdentityHashMap<>());
4343
private final Set<Element> recordedErasedStaticAllocations =
4444
Collections.newSetFromMap(new IdentityHashMap<>());
45+
private final Set<ImFunction> scannedFixedStaticCallees =
46+
Collections.newSetFromMap(new IdentityHashMap<>());
4547
private final Table<ImFunction, GenericTypes, ImFunction> specializedFunctions = HashBasedTable.create();
4648
/** The class each function was moved out of, for calls which name their target without a receiver. */
4749
private final Map<ImFunction, ImClass> functionOwners = new IdentityHashMap<>();
@@ -486,26 +488,25 @@ private void collectGenericNewUse(ImFunctionCall call) {
486488
// The generic callee remains erased, so its body is skipped by collectGenericNewRoots.
487489
// Fixed concrete allocations inside it still name real per-instantiation statics and
488490
// must be registered without cloning the caller for unrelated type arguments.
489-
recordFixedErasedStaticAllocations(call.getFunc(),
490-
Collections.newSetFromMap(new IdentityHashMap<>()));
491+
recordFixedErasedStaticAllocations(call.getFunc());
491492
}
492493
}
493494

494-
private void recordFixedErasedStaticAllocations(ImFunction function,
495-
Set<ImFunction> visited) {
496-
if (!visited.add(function)) {
495+
private void recordFixedErasedStaticAllocations(ImFunction function) {
496+
if (!scannedFixedStaticCallees.add(function)) {
497497
return;
498498
}
499499
function.accept(new Element.DefaultVisitor() {
500500
@Override
501501
public void visit(ImFunctionCall nestedCall) {
502502
super.visit(nestedCall);
503-
recordErasedConstructorAllocation(nestedCall);
504-
if (!nestedCall.getTypeArguments().isEmpty()
505-
&& !typeArgumentsContainTypeVariable(nestedCall.getTypeArguments())
506-
&& !(nestedCall.getFunc().getTrace() instanceof ConstructorDef)) {
507-
recordFixedErasedStaticAllocations(nestedCall.getFunc(), visited);
508-
}
503+
collectGenericNewUse(nestedCall);
504+
}
505+
506+
@Override
507+
public void visit(ImMethodCall nestedCall) {
508+
super.visit(nestedCall);
509+
collectGenericNewUse(nestedCall);
509510
}
510511
});
511512
}
@@ -744,21 +745,25 @@ private void collectGenericNewUse(ImMethodCall call) {
744745
specializedCallSites.add(call);
745746
return;
746747
}
747-
if (!shouldSpecializeTupleArguments(call.getTypeArguments())
748-
&& !methodNeedsSpecialization(method,
749-
Collections.newSetFromMap(new IdentityHashMap<>()),
750-
Collections.newSetFromMap(new IdentityHashMap<>()))) {
751-
return;
752-
}
753748
if (isMissingClassTypeArguments(call, method)) {
754749
addMemberTypeArguments(call, method.attrClass());
755750
}
756751
if (typeArgumentsContainTypeVariable(call.getTypeArguments())) {
757-
// The receiver's declared type is still generic, which happens when the method is
758-
// called straight on a freshly constructed value. The construction states the
759-
// instantiation, so take the arguments from it.
752+
// A call directly on a fresh generic construction gets its concrete class arguments
753+
// from that construction before deciding between specialization and fixed-body scan.
760754
useConstructionTypeArguments(call);
761755
}
756+
boolean needsSpecialization = methodNeedsSpecialization(method,
757+
Collections.newSetFromMap(new IdentityHashMap<>()),
758+
Collections.newSetFromMap(new IdentityHashMap<>()));
759+
if (!shouldSpecializeTupleArguments(call.getTypeArguments()) && !needsSpecialization) {
760+
if (!call.getTypeArguments().isEmpty()
761+
&& !typeArgumentsContainTypeVariable(call.getTypeArguments())
762+
&& method.getImplementation() != null) {
763+
recordFixedErasedStaticAllocations(method.getImplementation());
764+
}
765+
return;
766+
}
762767
if (!call.getTypeArguments().isEmpty()
763768
&& !typeArgumentsContainTypeVariable(call.getTypeArguments())) {
764769
genericsUses.add(new GenericMethodCall(call));
@@ -876,9 +881,10 @@ public void visit(ImAlloc alloc) {
876881

877882
@Override
878883
public void visit(ImFunctionCall call) {
884+
boolean dependsOnTypeVariable = typeArgumentsContainTypeVariable(call.getTypeArguments());
879885
if (constructsClassOwningGenericGlobals(function, call)
880-
|| translator.isGenericNewMarker(call.getFunc())
881-
|| functionNeedsSpecialization(call.getFunc(), visitedFunctions, visitedMethods)) {
886+
|| (dependsOnTypeVariable && (translator.isGenericNewMarker(call.getFunc())
887+
|| functionNeedsSpecialization(call.getFunc(), visitedFunctions, visitedMethods)))) {
882888
found[0] = true;
883889
return;
884890
}
@@ -887,7 +893,8 @@ public void visit(ImFunctionCall call) {
887893

888894
@Override
889895
public void visit(ImMethodCall call) {
890-
if (methodNeedsSpecialization(call.getMethod(), visitedFunctions, visitedMethods)) {
896+
if (typeArgumentsContainTypeVariable(call.getTypeArguments())
897+
&& methodNeedsSpecialization(call.getMethod(), visitedFunctions, visitedMethods)) {
891898
found[0] = true;
892899
return;
893900
}

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,63 @@ public void inheritedGenericStaticInitializerUsesDeclaringOwnerMapping() {
11961196
);
11971197
}
11981198

1199+
@Test
1200+
public void fixedAllocationInsideErasedGenericMethodIsRegistered() throws IOException {
1201+
test().testLua(true).executeProg().lines(
1202+
"package Test",
1203+
"native testSuccess()",
1204+
"int bumps",
1205+
"function bump() returns int",
1206+
" bumps++",
1207+
" return bumps",
1208+
"class Box<T:>",
1209+
" static int value = bump()",
1210+
" construct()",
1211+
" static function get() returns int",
1212+
" return value",
1213+
"class Factory<T:>",
1214+
" construct()",
1215+
" function make() returns Box<int>",
1216+
" return new Box<int>()",
1217+
"init",
1218+
" let factory = new Factory<real>()",
1219+
" let made = factory.make()",
1220+
" let fresh = new Factory<string>().make()",
1221+
" if made != null and fresh != null and Box<string>.get() == 2 and bumps == 2",
1222+
" testSuccess()"
1223+
);
1224+
1225+
String compiled = compiledLua("fixedAllocationInsideErasedGenericMethodIsRegistered");
1226+
assertFalse("fixed generic method body must not be cloned for its class argument",
1227+
compiled.contains("Factory_make_specialized"));
1228+
}
1229+
1230+
@Test
1231+
public void fixedStaticCalleeDoesNotSpecializeUnrelatedGenericCaller() throws IOException {
1232+
test().testLua(true).executeProg().lines(
1233+
"package Test",
1234+
"native testSuccess()",
1235+
"int bumps",
1236+
"function bump() returns int",
1237+
" bumps++",
1238+
" return bumps",
1239+
"class Box<T:>",
1240+
" static int value = bump()",
1241+
" static function get() returns int",
1242+
" return value",
1243+
"function helper<T:>() returns int",
1244+
" return Box<int>.get()",
1245+
"init",
1246+
" if helper<string>() == 1 and helper<real>() == 1",
1247+
" and Box<string>.get() == 2 and bumps == 2",
1248+
" testSuccess()"
1249+
);
1250+
1251+
String compiled = compiledLua("fixedStaticCalleeDoesNotSpecializeUnrelatedGenericCaller");
1252+
assertFalse("fixed Box<int> static call must not clone helper<T>",
1253+
compiled.contains("helper_specialized"));
1254+
}
1255+
11991256
@Test
12001257
public void randomizedNestedGenericTupleClassShapesMatchAllBackends() {
12011258
record Shape(String type, String value, int constructions) {}

0 commit comments

Comments
 (0)