Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -874,12 +874,11 @@ public LuaCompilationUnit transformProgToLua() {

ImAttrType.setWurstClassType(null);
int stage;
if (containsGenericNewCall() || containsTypeClassDispatch()) {
// Both operations need the concrete type argument, which erasure does not keep. Only
// the paths reaching them are specialised: the full elimination used for Jass is
// followed there by class elimination, and leaves state this backend cannot consume.
beginPhase(2, "Specialize generics for generic construction and type class dispatch");
new EliminateGenerics(getImTranslator(), getImProg()).transformGenericNewOnly();
boolean specializeTupleValueTypes = containsTupleTypeArgument();
if (containsGenericNewCall() || containsTypeClassDispatch() || specializeTupleValueTypes) {
beginPhase(2, "Specialize generics for Lua-only concrete operations");
new EliminateGenerics(getImTranslator(), getImProg())
.transformGenericNewOnly(specializeTupleValueTypes);
timeTaker.endPhase();
}
if (runArgs.isNoDebugMessages()) {
Expand Down Expand Up @@ -919,6 +918,12 @@ public LuaCompilationUnit transformProgToLua() {
getImProg().flatten(imTranslator2);
EliminateLocalTypes.eliminateLocalTypesProg(getImProg(), imTranslator2);

timeTaker.beginPhase("eliminate tuples");
getImProg().flatten(imTranslator2);
EliminateTuples.eliminateTuplesProg(getImProg(), imTranslator2);
Comment thread
Frotty marked this conversation as resolved.
imTranslator2.assertProperties(AssertProperty.NOTUPLES);
timeTaker.endPhase();

optimizer.removeGarbage();
imProg.flatten(imTranslator);
timeTaker.endPhase();
Expand Down Expand Up @@ -996,4 +1001,19 @@ public void visit(ImTypeVarDispatch dispatch) {
});
return found[0];
}

/** Tuple type arguments need monomorphisation before tuples can become scalar storage. */
private boolean containsTupleTypeArgument() {
boolean[] found = {false};
getImProg().accept(new de.peeeq.wurstscript.jassIm.Element.DefaultVisitor() {
@Override
public void visit(ImTypeArgument argument) {
if (TypesHelper.typeContainsTuples(argument.getType())) {
found[0] = true;
}
super.visit(argument);
}
});
return found[0];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import de.peeeq.wurstscript.translation.imtojass.ImAttrType;
import de.peeeq.wurstscript.translation.imtojass.TypeRewriteMatcher;
import de.peeeq.wurstscript.translation.lua.translation.RemoveGarbage;
import de.peeeq.wurstscript.types.TypesHelper;
import io.vavr.control.Either;
import org.eclipse.jdt.annotation.Nullable;
import org.jetbrains.annotations.NotNull;
Expand All @@ -29,6 +30,7 @@ public class EliminateGenerics {
private final ImTranslator translator;
private final ImProg prog;
private boolean genericNewOnly;
private boolean specializeTupleValueTypes;
private final Deque<GenericUse> genericsUses = new ArrayDeque<>();
/**
* Call sites already rewritten to a specialisation.
Expand Down Expand Up @@ -112,12 +114,21 @@ public void transform() {
}

/**
* Lua normally erases new generics. Generic construction is the one operation which needs the
* concrete type, so only specialize functions on paths leading to {@code wurstNewInstance}. All other
* generic calls and classes keep the Lua backend's normal erased representation.
* Lua normally erases generics. Generic construction and scalar storage for tuple type arguments
* are the operations which need the concrete type, so only specialize paths leading to those
* operations. All other generic calls and classes keep the Lua backend's erased representation.
*/
public void transformGenericNewOnly() {
transformGenericNewOnly(false);
}

public void transformGenericNewOnly(boolean specializeTupleValueTypes) {
genericNewOnly = true;
this.specializeTupleValueTypes = specializeTupleValueTypes;
if (specializeTupleValueTypes) {
addMemberTypeArguments();
identifyGenericGlobals();
}
collectUnspecializedGenericClassMethods();
// Specialising a constructor makes its result type concrete, which is what lets a method
// call on that result resolve. Repeat until a pass finds nothing new; collection is
Expand All @@ -133,6 +144,14 @@ public void transformGenericNewOnly() {
assertNoReachableGenericNewMarkers();
bindSpecialisedMethodsToTheAllocatedClass();
settleRemainingDispatches();
if (specializeTupleValueTypes) {
for (Map.Entry<ImFunction, GenericTypes> entry :
new ArrayList<>(specializedFunctionGenerics.entrySet())) {
if (genericTypesContainTuple(entry.getValue())) {
rewriteGenericGlobals(entry.getKey(), entry.getValue());
}
}
}
}

/**
Expand Down Expand Up @@ -262,9 +281,43 @@ public void visit(ImMemberAccess memberAccess) {
super.visit(memberAccess);
collectGenericNewUse(memberAccess);
}

@Override
public void visit(ImDealloc dealloc) {
super.visit(dealloc);
collectGenericNewUse(dealloc);
}

@Override
public void visit(ImInstanceof instanceOf) {
super.visit(instanceOf);
collectGenericNewUse(instanceOf);
}

@Override
public void visit(ImTypeIdOfObj typeId) {
super.visit(typeId);
collectGenericNewUse(typeId);
}

@Override
public void visit(ImTypeIdOfClass typeId) {
super.visit(typeId);
collectGenericNewUse(typeId);
}
});
}

private void collectGenericNewUse(ImClassRelatedExprWithClass expression) {
ImClassType clazz = expression.getClazz();
if (clazz.getTypeArguments().isEmpty()
|| typeArgumentsContainTypeVariable(clazz.getTypeArguments())
|| !shouldSpecializeTupleArguments(clazz.getTypeArguments())) {
return;
}
genericsUses.add(new GenericClazzUse(expression));
}

private void collectGenericNewUses(Element element) {
element.accept(new Element.DefaultVisitor() {
@Override
Expand All @@ -290,6 +343,26 @@ public void visit(ImMemberAccess memberAccess) {
super.visit(memberAccess);
collectGenericNewUse(memberAccess);
}

@Override
public void visit(ImVarAccess access) {
super.visit(access);
if (specializedContextContainsTuple(access)
&& globalToClass.containsKey(access.getVar())) {
recordGenericGlobalUse(access, access.getVar());
genericsUses.add(new GenericGlobalAccess(access));
}
}

@Override
public void visit(ImVarArrayAccess access) {
super.visit(access);
if (specializedContextContainsTuple(access)
&& globalToClass.containsKey(access.getVar())) {
recordGenericGlobalUse(access, access.getVar());
genericsUses.add(new GenericGlobalArrayAccess(access));
}
}
});
}

Expand All @@ -304,7 +377,8 @@ private void collectGenericNewUse(ImFunctionCall call) {
return;
}
if (!call.getTypeArguments().isEmpty()
&& functionNeedsSpecialization(call.getFunc(), Collections.newSetFromMap(new IdentityHashMap<>()))) {
&& (shouldSpecializeTupleArguments(call.getTypeArguments())
|| functionNeedsSpecialization(call.getFunc(), Collections.newSetFromMap(new IdentityHashMap<>())))) {
if (!typeArgumentsContainTypeVariable(call.getTypeArguments())) {
genericsUses.add(new GenericImFunctionCall(call));
}
Expand Down Expand Up @@ -347,8 +421,9 @@ private void collectCallThroughGenericReceiver(ImFunctionCall call) {
|| typeArgumentsContainTypeVariable(classType.getTypeArguments())) {
return;
}
if (!functionNeedsSpecialization(call.getFunc(),
Collections.newSetFromMap(new IdentityHashMap<>()))) {
if (!shouldSpecializeTupleArguments(classType.getTypeArguments())
&& !functionNeedsSpecialization(call.getFunc(),
Collections.newSetFromMap(new IdentityHashMap<>()))) {
return;
}
genericsUses.add(new GenericClassFunctionCall(call, owningClass,
Expand Down Expand Up @@ -393,7 +468,8 @@ private void collectGenericNewUse(ImAlloc alloc) {
ImClassType clazz = alloc.getClazz();
if (clazz.getTypeArguments().isEmpty()
|| typeArgumentsContainTypeVariable(clazz.getTypeArguments())
|| !isConstructionOnlyInstantiation(clazz.getClassDef())) {
|| (!shouldSpecializeTupleArguments(clazz.getTypeArguments())
&& !isConstructionOnlyInstantiation(clazz.getClassDef()))) {
Comment thread
Frotty marked this conversation as resolved.
Comment thread
Frotty marked this conversation as resolved.
return;
}
genericsUses.add(new GenericClazzUse(alloc));
Expand Down Expand Up @@ -468,7 +544,7 @@ private void collectGenericNewUse(ImMemberAccess memberAccess) {
// A class that has already been specialised has nothing left to select, and asking the
// receiver to adapt to it fails outright: the receiver is still typed by the generic class
// the specialised one was copied from, which is not a superclass of it.
if (owningClass.getTypeVariables().isEmpty() || !isConstructionOnlyInstantiation(owningClass)) {
if (owningClass.getTypeVariables().isEmpty()) {
return;
}
if (memberAccess.getTypeArguments().isEmpty()) {
Expand All @@ -479,6 +555,10 @@ private void collectGenericNewUse(ImMemberAccess memberAccess) {
|| typeArgumentsContainTypeVariable(memberAccess.getTypeArguments())) {
return;
}
if (!shouldSpecializeTupleArguments(memberAccess.getTypeArguments())
&& !isConstructionOnlyInstantiation(owningClass)) {
return;
}
genericsUses.add(new GenericMemberAccess(memberAccess));
}

Expand All @@ -487,7 +567,8 @@ private void collectGenericNewUse(ImMethodCall call) {
return;
}
ImMethod method = call.getMethod();
if (!methodNeedsSpecialization(method,
if (!shouldSpecializeTupleArguments(call.getTypeArguments())
&& !methodNeedsSpecialization(method,
Collections.newSetFromMap(new IdentityHashMap<>()),
Collections.newSetFromMap(new IdentityHashMap<>()))) {
return;
Expand Down Expand Up @@ -550,6 +631,29 @@ private boolean typeArgumentsContainTypeVariable(ImTypeArguments typeArguments)
return false;
}

private boolean typeArgumentsContainTuple(Iterable<ImTypeArgument> typeArguments) {
for (ImTypeArgument typeArgument : typeArguments) {
if (TypesHelper.typeContainsTuples(typeArgument.getType())) {
return true;
}
}
return false;
}

private boolean shouldSpecializeTupleArguments(ImTypeArguments typeArguments) {
return specializeTupleValueTypes && typeArgumentsContainTuple(typeArguments);
}

private boolean genericTypesContainTuple(GenericTypes generics) {
return typeArgumentsContainTuple(generics.getTypeArguments());
}

private boolean specializedContextContainsTuple(Element element) {
ImFunction function = enclosingFunction(element);
GenericTypes generics = function == null ? null : specializedFunctionGenerics.get(function);
return specializeTupleValueTypes && generics != null && genericTypesContainTuple(generics);
}

private boolean functionNeedsSpecialization(ImFunction function, Set<ImFunction> visited) {
return functionNeedsSpecialization(function, visited,
Collections.newSetFromMap(new IdentityHashMap<>()));
Expand Down Expand Up @@ -1307,6 +1411,10 @@ private ImFunction specializeFunction(ImFunction f, GenericTypes generics) {
rewriteGenerics(newF, generics, typeVars);
}

if (genericNewOnly && specializeTupleValueTypes && genericTypesContainTuple(generics)) {
rewriteGenericGlobals(newF, generics);
}

// Fix calls inside this specialized function so they also point to specialized callees
if (genericNewOnly) {
collectGenericNewUses(newF);
Expand All @@ -1320,6 +1428,45 @@ private ImFunction specializeFunction(ImFunction f, GenericTypes generics) {
return newF;
}

private void rewriteGenericGlobals(ImFunction function, GenericTypes generics) {
function.accept(new Element.DefaultVisitor() {
@Override
public void visit(ImVarAccess access) {
super.visit(access);
access.setVar(specializedGlobal(access.getVar()));
}

@Override
public void visit(ImVarArrayAccess access) {
super.visit(access);
access.setVar(specializedGlobal(access.getVar()));
}

private ImVar specializedGlobal(ImVar original) {
ImTranslator.Specialisation existing = translator.specialisationOf(original);
if (existing != null && translator.genericStaticOwnerOf(original) != null
&& !existing.typeArguments().isEmpty()) {
// This access already names a concrete instantiation of the static field.
// Its own binding wins over the type arguments of the function which happens
// to contain it; in particular, specialising touch<pair> must not turn an
// access produced by Box<int> into one for Box<pair>.
return original;
}
ImClass owner = globalToClass.get(original);
if (owner == null) {
return original;
}
GenericTypes concrete = normalizeToClassArity(generics, owner,
"specialized function " + function.getName());
Comment thread
Frotty marked this conversation as resolved.
if (concrete == null || concrete.containsTypeVariable()) {
return original;
}
ImVar result = ensureSpecializedGlobal(original, owner, concrete);
return result == null ? original : result;
}
});
}

/**
* creates a specialized version of this method
*/
Expand Down Expand Up @@ -1398,6 +1545,9 @@ private ImFunction specializeClassFunction(ImFunction function, ImClass owningCl
newImplementation.getTypeVariables().removeAll();
newImplementation.setName(function.getName() + "_specialized");
rewriteGenerics(newImplementation, generics, typeVariables);
if (specializeTupleValueTypes && genericTypesContainTuple(generics)) {
rewriteGenericGlobals(newImplementation, generics);
}
collectGenericNewUses(newImplementation);
return newImplementation;
}
Expand Down Expand Up @@ -1689,7 +1839,8 @@ private ImClass specializeClass(ImClass c, GenericTypes generics) {
// NEW: Create specialized global variables for this class instantiation
createSpecializedGlobals(c, generics, typeVars);

if (genericNewOnly && isConstructionOnlyInstantiation(c)) {
if (genericNewOnly && (isConstructionOnlyInstantiation(c)
|| (specializeTupleValueTypes && genericTypesContainTuple(generics)))) {
attachSpecializedClassMethods(c, newC, generics);
}

Expand Down
Loading
Loading