Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -875,10 +875,11 @@ public LuaCompilationUnit transformProgToLua() {
ImAttrType.setWurstClassType(null);
int stage;
boolean specializeTupleValueTypes = containsTupleTypeArgument();
if (containsGenericNewCall() || containsTypeClassDispatch() || specializeTupleValueTypes) {
EliminateGenerics luaGenerics = new EliminateGenerics(getImTranslator(), getImProg());
if (containsGenericNewCall() || containsTypeClassDispatch() || specializeTupleValueTypes
|| luaGenerics.hasGenericStatics()) {
Comment thread
Frotty marked this conversation as resolved.
beginPhase(2, "Specialize generics for Lua-only concrete operations");
new EliminateGenerics(getImTranslator(), getImProg())
.transformGenericNewOnly(specializeTupleValueTypes);
luaGenerics.transformGenericNewOnly(specializeTupleValueTypes);
// Remove phantom erased initialization before optimization can preserve only its side
// effect. A specialized static owns its copied initializer unless the erased static is live.
RemoveGarbage.removePhantomGenericStaticInitializers(getImProg(), getImTranslator());
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package de.peeeq.wurstscript.translation.imtranslation;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import de.peeeq.wurstscript.jassIm.*;

Expand All @@ -16,9 +15,6 @@ class GenericTypes {


public GenericTypes(List<ImTypeArgument> typeArguments) {
for (ImTypeArgument ta : typeArguments) {
Preconditions.checkArgument(!EliminateGenerics.isGenericType(ta.getType()), "Type arguments must not be generic: " + typeArguments);
}
this.typeArguments = ImmutableList.copyOf(typeArguments);
Comment thread
Frotty marked this conversation as resolved.
}

Expand All @@ -36,7 +32,7 @@ public boolean equals(Object o) {
for (int i = 0; i < typeArguments.size(); i++) {
ImTypeArgument t1 = typeArguments.get(i);
ImTypeArgument t2 = ot.typeArguments.get(i);
if (!t1.getType().equalsType(t2.getType())) {
if (!equalTypeIgnoringBindings(t1.getType(), t2.getType())) {
return false;
}
// Deliberately not comparing the type class binding. It is only a fast path for
Expand All @@ -50,6 +46,60 @@ public boolean equals(Object o) {
return false;
}

/**
* Type-class bindings are dispatch metadata, not part of a specialization's structural type.
* Unlike the general IM type equality operation, this comparison therefore ignores bindings
* on every nested class-type argument, not just on the arguments wrapped by this key.
*/
private static boolean equalTypeIgnoringBindings(ImType left, ImType right) {
if (left instanceof ImArrayType) {
return right instanceof ImArrayType
&& equalTypeIgnoringBindings(((ImArrayType) left).getEntryType(),
((ImArrayType) right).getEntryType());
}
if (left instanceof ImArrayTypeMulti) {
return right instanceof ImArrayTypeMulti
&& equalTypeIgnoringBindings(((ImArrayTypeMulti) left).getEntryType(),
((ImArrayTypeMulti) right).getEntryType());
}
if (left instanceof ImTupleType) {
if (!(right instanceof ImTupleType)) {
return false;
}
ImTupleType leftTuple = (ImTupleType) left;
ImTupleType rightTuple = (ImTupleType) right;
if (leftTuple.getTypes().size() != rightTuple.getTypes().size()) {
return false;
}
for (int i = 0; i < leftTuple.getTypes().size(); i++) {
if (!equalTypeIgnoringBindings(leftTuple.getTypes().get(i),
rightTuple.getTypes().get(i))) {
return false;
}
}
return true;
}
if (left instanceof ImClassType) {
if (!(right instanceof ImClassType)) {
return false;
}
ImClassType leftClass = (ImClassType) left;
ImClassType rightClass = (ImClassType) right;
if (leftClass.getClassDef() != rightClass.getClassDef()
|| leftClass.getTypeArguments().size() != rightClass.getTypeArguments().size()) {
return false;
}
for (int i = 0; i < leftClass.getTypeArguments().size(); i++) {
if (!equalTypeIgnoringBindings(leftClass.getTypeArguments().get(i).getType(),
rightClass.getTypeArguments().get(i).getType())) {
return false;
}
}
return true;
}
return left.equalsType(right);
}

@Override
public int hashCode() {
int res = 7;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public record Specialisation(Element original, List<ImTypeArgument> typeArgument
}

private final Map<Element, Specialisation> specialisations = new IdentityHashMap<>();
private final Map<ImClass, Set<GenericTypes>> erasedGenericAllocations = new IdentityHashMap<>();

/**
* @param typeArguments the arguments the copy was made for, empty when a copy carries none of its
Expand Down Expand Up @@ -99,6 +100,31 @@ public void recordGenericStaticOwner(ImVar global, ImClass owner) {
return specialisations.get(copy);
}

public void recordErasedGenericAllocation(ImClass clazz, List<ImTypeArgument> typeArguments) {
erasedGenericAllocations.computeIfAbsent(canonical(clazz), ignored -> new HashSet<>())
.add(new GenericTypes(typeArguments));
}

public boolean hasErasedAllocationWithoutStaticSpecialization(ImClass clazz, ImVar originalStatic) {
Set<GenericTypes> allocations = erasedGenericAllocations.get(canonical(clazz));
if (allocations == null || allocations.isEmpty()) {
return false;
}
Set<GenericTypes> specializedStatics = new HashSet<>();
for (Map.Entry<Element, Specialisation> entry : specialisations.entrySet()) {
Specialisation specialization = entry.getValue();
if (specialization.original() == originalStatic) {
specializedStatics.add(new GenericTypes(specialization.typeArguments()));
}
}
for (GenericTypes allocation : allocations) {
if (!specializedStatics.contains(allocation)) {
return true;
Comment thread
Frotty marked this conversation as resolved.
}
}
return false;
}

/**
* The node {@code copy} was ultimately copied from, or {@code copy} itself.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,9 @@ public static void removePhantomGenericStaticInitializers(ImProg prog, ImTransla
changed = false;
for (ImVar original : candidates.keySet()) {
ImClass owner = translator.genericStaticOwnerOf(original);
if ((used.getVars().contains(original) || used.getInstantiatedClasses().contains(owner))
boolean erasedInstantiationNeedsOriginal = used.getInstantiatedClasses().contains(owner)
&& translator.hasErasedAllocationWithoutStaticSpecialization(owner, original);
if ((used.getVars().contains(original) || erasedInstantiationNeedsOriginal)
&& liveOriginals.add(original)) {
changed = true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package de.peeeq.wurstscript.translation.imtranslation;

import de.peeeq.wurstscript.ast.Ast;
import de.peeeq.wurstscript.jassIm.ImClass;
import de.peeeq.wurstscript.jassIm.ImClassType;
import de.peeeq.wurstscript.jassIm.ImFunction;
import de.peeeq.wurstscript.jassIm.ImMethod;
import de.peeeq.wurstscript.jassIm.ImSimpleType;
import de.peeeq.wurstscript.jassIm.ImTypeArgument;
import de.peeeq.wurstscript.jassIm.ImTypeClassFunc;
import de.peeeq.wurstscript.jassIm.JassIm;
import io.vavr.control.Either;
import org.testng.annotations.Test;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static org.testng.Assert.assertEquals;

public class GenericTypesTests {

@Test
public void nestedTypeClassBindingsDoNotSplitSpecializationKeys() {
ImClass box = genericClass("Box");
ImClass list = genericClass("List");
ImSimpleType integer = JassIm.ImSimpleType("integer");
ImTypeClassFunc requirement = JassIm.ImTypeClassFunc(Ast.NoExpr(), "toIndex",
JassIm.ImTypeVars(), JassIm.ImVars(), integer);
ImFunction instance = JassIm.ImFunction(Ast.NoExpr(), "intToIndex", JassIm.ImTypeVars(),
JassIm.ImVars(), integer, JassIm.ImVars(), JassIm.ImStmts(), List.of());

Map<ImTypeClassFunc, Either<ImMethod, ImFunction>> binding = new LinkedHashMap<>();
binding.put(requirement, Either.right(instance));
ImClassType unboundBox = JassIm.ImClassType(box,
JassIm.ImTypeArguments(argument(integer, Collections.emptyMap())));
ImClassType boundBox = JassIm.ImClassType(box,
JassIm.ImTypeArguments(argument(integer, binding)));

GenericTypes unbound = key(list, unboundBox);
GenericTypes bound = key(list, boundBox);

assertEquals(bound, unbound,
"type-class dispatch metadata must not change a structural specialization key");
assertEquals(bound.hashCode(), unbound.hashCode());
}

private static GenericTypes key(ImClass list, ImClassType nestedType) {
ImClassType listType = JassIm.ImClassType(list,
JassIm.ImTypeArguments(argument(nestedType, Collections.emptyMap())));
return new GenericTypes(List.of(argument(listType, Collections.emptyMap())));
}

private static ImTypeArgument argument(de.peeeq.wurstscript.jassIm.ImType type,
Map<ImTypeClassFunc, Either<ImMethod, ImFunction>> binding) {
return JassIm.ImTypeArgument(type, binding);
}

private static ImClass genericClass(String name) {
return JassIm.ImClass(Ast.NoExpr(), name, JassIm.ImTypeVars(JassIm.ImTypeVar("T")),
JassIm.ImVars(), JassIm.ImMethods(), JassIm.ImFunctions(), List.of());
}
}
Loading
Loading