[dart2wasm] Support accessing function type parameter from within lambda

This completes the implementation of runtime access to type arguments.

Generic function expressions and local functions are now explicitly
rejected, which causes a number of new failures. These tests were
previously spurious passes, since generic functions are not supported.

Change-Id: Ibbf8bde94cddf2c0f86e3740a798c1602897f631
Cq-Include-Trybots: luci.dart.try:dart2wasm-linux-x64-d8-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/244402
Reviewed-by: Joshua Litt <joshualitt@google.com>
Commit-Queue: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
Aske Simon Christensen
2022-05-20 12:34:12 +00:00
committed by Commit Bot
parent 7b1675f1a5
commit 1fbbbf3c97
3 changed files with 118 additions and 67 deletions
+46 -6
View File
@@ -47,6 +47,9 @@ class Context {
/// The variables captured by this context.
final List<VariableDeclaration> variables = [];
/// The type parameters captured by this context.
final List<TypeParameter> typeParameters = [];
/// Whether this context contains a captured `this`. Only member contexts can.
bool containsThis = false;
@@ -57,7 +60,8 @@ class Context {
/// generation.
late w.Local currentLocal;
bool get isEmpty => variables.isEmpty && !containsThis;
bool get isEmpty =>
variables.isEmpty && typeParameters.isEmpty && !containsThis;
int get parentFieldIndex {
assert(parent != null);
@@ -74,7 +78,7 @@ class Context {
/// A captured variable.
class Capture {
final VariableDeclaration variable;
final TreeNode variable;
late final Context context;
late final int fieldIndex;
bool written = false;
@@ -88,7 +92,7 @@ class Capture {
/// tree for a member.
class Closures {
final CodeGenerator codeGen;
final Map<VariableDeclaration, Capture> captures = {};
final Map<TreeNode, Capture> captures = {};
bool isThisCaptured = false;
final Map<FunctionNode, Lambda> lambdas = {};
final Map<TreeNode, Context> contexts = {};
@@ -98,6 +102,9 @@ class Closures {
Translator get translator => codeGen.translator;
late final w.ValueType typeType =
translator.classInfo[translator.typeClass]!.nullableType;
void findCaptures(Member member) {
var find = CaptureFinder(this, member);
if (member is Constructor) {
@@ -144,6 +151,11 @@ class Closures {
translator.translateType(variable.type).withNullability(true)));
captures[variable]!.fieldIndex = index;
}
for (TypeParameter parameter in context.typeParameters) {
int index = struct.fields.length;
struct.fields.add(w.FieldType(typeType));
captures[parameter]!.fieldIndex = index;
}
}
}
}
@@ -152,7 +164,7 @@ class Closures {
class CaptureFinder extends RecursiveVisitor {
final Closures closures;
final Member member;
final Map<VariableDeclaration, int> variableDepth = {};
final Map<TreeNode, int> variableDepth = {};
int depth = 0;
CaptureFinder(this.closures, this.member);
@@ -170,12 +182,23 @@ class CaptureFinder extends RecursiveVisitor {
super.visitVariableDeclaration(node);
}
void _visitVariableUse(VariableDeclaration variable) {
@override
void visitTypeParameter(TypeParameter node) {
if (node.parent is FunctionNode) {
if (depth > 0) {
variableDepth[node] = depth;
}
}
super.visitTypeParameter(node);
}
void _visitVariableUse(TreeNode variable) {
int declDepth = variableDepth[variable] ?? 0;
assert(declDepth <= depth);
if (declDepth < depth) {
closures.captures[variable] = Capture(variable);
} else if (variable.parent is FunctionDeclaration) {
} else if (variable is VariableDeclaration &&
variable.parent is FunctionDeclaration) {
closures.closurizedFunctions.add(variable.parent as FunctionDeclaration);
}
}
@@ -213,7 +236,10 @@ class CaptureFinder extends RecursiveVisitor {
void visitTypeParameterType(TypeParameterType node) {
if (node.parameter.parent == member.enclosingClass) {
_visitThis();
} else if (node.parameter.parent is FunctionNode) {
_visitVariableUse(node.parameter);
}
super.visitTypeParameterType(node);
}
void _visitLambda(FunctionNode node) {
@@ -222,6 +248,10 @@ class CaptureFinder extends RecursiveVisitor {
throw "Not supported: Optional parameters for "
"function expression or local function at ${node.location}";
}
if (node.typeParameters.isNotEmpty) {
throw "Not supported: Type parameters for "
"function expression or local function at ${node.location}";
}
int parameterCount = node.requiredParameterCount;
w.FunctionType type = translator.closureFunctionType(parameterCount);
w.DefinedFunction function =
@@ -310,6 +340,16 @@ class ContextCollector extends RecursiveVisitor {
super.visitVariableDeclaration(node);
}
@override
void visitTypeParameter(TypeParameter node) {
Capture? capture = closures.captures[node];
if (capture != null) {
currentContext!.typeParameters.add(node);
capture.context = currentContext!;
}
super.visitTypeParameter(node);
}
@override
void visitVariableSet(VariableSet node) {
closures.captures[node.variable]?.written = true;
+54 -18
View File
@@ -400,6 +400,15 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.struct_set(capture.context.struct, capture.fieldIndex);
}
});
typeLocals.forEach((parameter, local) {
Capture? capture = closures.captures[parameter];
if (capture != null) {
b.local_get(capture.context.currentLocal);
b.local_get(local);
translator.convertType(function, local.type, capture.type);
b.struct_set(capture.context.struct, capture.fieldIndex);
}
});
}
/// Helper function to throw a Wasm ref downcast error.
@@ -475,7 +484,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
}
for (TypeParameter typeParam in cls.typeParameters) {
types.makeType(
this, TypeParameterType(typeParam, Nullability.nonNullable), node);
this, TypeParameterType(typeParam, Nullability.nonNullable));
}
_visitArguments(node.arguments, node.targetReference, 1);
_call(node.targetReference);
@@ -493,7 +502,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.ref_as_non_null();
}
for (DartType typeArg in supertype!.typeArguments) {
types.makeType(this, typeArg, node);
types.makeType(this, typeArg);
}
_visitArguments(node.arguments, node.targetReference,
1 + supertype.typeArguments.length);
@@ -1788,7 +1797,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
final w.FunctionType signature = translator.signatureFor(target);
final ParameterInfo paramInfo = translator.paramInfoFor(target);
for (int i = 0; i < node.types.length; i++) {
types.makeType(this, node.types[i], node);
types.makeType(this, node.types[i]);
}
signatureOffset += node.types.length;
for (int i = 0; i < node.positional.length; i++) {
@@ -1827,11 +1836,8 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
@override
w.ValueType visitStringConcatenation(
StringConcatenation node, w.ValueType expectedType) {
makeList(
node.expressions,
translator.fixedLengthListClass,
InterfaceType(translator.stringBaseClass, Nullability.nonNullable),
node);
makeList(node.expressions, translator.fixedLengthListClass,
InterfaceType(translator.stringBaseClass, Nullability.nonNullable));
return _call(translator.stringInterpolate.reference);
}
@@ -1899,12 +1905,12 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
@override
w.ValueType visitListLiteral(ListLiteral node, w.ValueType expectedType) {
return makeList(node.expressions, translator.growableListClass,
node.typeArgument, node);
return makeList(
node.expressions, translator.growableListClass, node.typeArgument);
}
w.ValueType makeList(List<Expression> expressions, Class cls,
DartType typeArg, TreeNode node) {
w.ValueType makeList(
List<Expression> expressions, Class cls, DartType typeArg) {
ClassInfo info = translator.classInfo[cls]!;
translator.functions.allocateClass(info.classId);
w.RefType refType = info.struct.fields.last.type.unpacked as w.RefType;
@@ -1914,7 +1920,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
types.makeType(this, typeArg, node);
types.makeType(this, typeArg);
b.i64_const(length);
if (options.lazyConstants) {
// Avoid array.init instruction in lazy constants mode
@@ -1950,8 +1956,8 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
w.BaseFunction mapFactory =
translator.functions.getFunction(translator.mapFactory.reference);
w.ValueType factoryReturnType = mapFactory.type.outputs.single;
types.makeType(this, node.keyType, node);
types.makeType(this, node.valueType, node);
types.makeType(this, node.keyType);
types.makeType(this, node.valueType);
b.call(mapFactory);
if (node.entries.isEmpty) {
return factoryReturnType;
@@ -1980,7 +1986,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
w.BaseFunction setFactory =
translator.functions.getFunction(translator.setFactory.reference);
w.ValueType factoryReturnType = setFactory.type.outputs.single;
types.makeType(this, node.typeArgument, node);
types.makeType(this, node.typeArgument);
b.call(setFactory);
if (node.expressions.isEmpty) {
return factoryReturnType;
@@ -2005,7 +2011,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
@override
w.ValueType visitTypeLiteral(TypeLiteral node, w.ValueType expectedType) {
return types.makeType(this, node.type, node);
return types.makeType(this, node.type);
}
@override
@@ -2027,7 +2033,7 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
types.emitTypeTest(this, node.type, dartTypeOf(node.operand), node);
b.br_if(asCheckBlock);
b.local_get(operand);
types.makeType(this, node.type, node);
types.makeType(this, node.type);
_call(translator.stackTraceCurrent.reference);
_call(translator.throwAsCheckError.reference);
b.unreachable();
@@ -2035,6 +2041,36 @@ class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
b.local_get(operand);
return operand.type;
}
w.ValueType instantiateTypeParameter(TypeParameter parameter) {
w.ValueType resultType;
if (parameter.parent is FunctionNode) {
// Type argument to function
w.Local? local = typeLocals[parameter];
if (local != null) {
b.local_get(local);
resultType = local.type;
} else {
Capture capture = closures.captures[parameter]!;
b.local_get(capture.context.currentLocal);
b.struct_get(capture.context.struct, capture.fieldIndex);
resultType = capture.type;
}
} else {
// Type argument of class
Class cls = parameter.parent as Class;
ClassInfo info = translator.classInfo[cls]!;
int fieldIndex = translator.typeParameterIndex[parameter]!;
w.ValueType thisType = visitThis(info.nonNullableType);
translator.convertType(function, thisType, info.nonNullableType);
b.struct_get(info.struct, fieldIndex);
resultType = info.struct.fields[fieldIndex].type.unpacked;
}
final w.ValueType nonNullableTypeType =
translator.classInfo[translator.typeClass]!.nonNullableType;
translator.convertType(function, resultType, nonNullableTypeType);
return nonNullableTypeType;
}
}
class TryBlockFinalizer {
+18 -43
View File
@@ -117,27 +117,24 @@ class Types {
throw "Unexpected DartType: $type";
}
void _makeTypeList(
CodeGenerator codeGen, List<DartType> types, TreeNode node) {
void _makeTypeList(CodeGenerator codeGen, List<DartType> types) {
w.ValueType listType = codeGen.makeList(
types.map((t) => TypeLiteral(t)).toList(),
translator.fixedLengthListClass,
InterfaceType(translator.typeClass, Nullability.nonNullable),
node);
InterfaceType(translator.typeClass, Nullability.nonNullable));
translator.convertType(codeGen.function, listType, typeListExpectedType);
}
void _makeInterfaceType(CodeGenerator codeGen, ClassInfo info,
InterfaceType type, TreeNode node) {
void _makeInterfaceType(
CodeGenerator codeGen, ClassInfo info, InterfaceType type) {
w.Instructions b = codeGen.b;
ClassInfo typeInfo = translator.classInfo[type.classNode]!;
encodeNullability(b, type);
b.i64_const(typeInfo.classId);
_makeTypeList(codeGen, type.typeArguments, node);
_makeTypeList(codeGen, type.typeArguments);
}
void _makeFutureOrType(
CodeGenerator codeGen, FutureOrType type, TreeNode node) {
void _makeFutureOrType(CodeGenerator codeGen, FutureOrType type) {
w.Instructions b = codeGen.b;
w.DefinedFunction function = codeGen.function;
@@ -146,7 +143,7 @@ class Types {
// undetermined nullability. To handle this, we emit the type argument, and
// read back its nullability at runtime.
if (type.nullability == Nullability.undetermined) {
w.ValueType typeArgumentType = makeType(codeGen, type.typeArgument, node);
w.ValueType typeArgumentType = makeType(codeGen, type.typeArgument);
w.Local typeArgumentTemporary = codeGen.addLocal(typeArgumentType);
b.local_tee(typeArgumentTemporary);
b.struct_get(typeClassInfo.struct, FieldIndex.typeIsNullable);
@@ -154,15 +151,15 @@ class Types {
translator.convertType(function, typeArgumentType, nonNullableTypeType);
} else {
encodeNullability(b, type);
makeType(codeGen, type.typeArgument, node);
makeType(codeGen, type.typeArgument);
}
}
void _makeFunctionType(
CodeGenerator codeGen, ClassInfo info, FunctionType type, TreeNode node) {
CodeGenerator codeGen, ClassInfo info, FunctionType type) {
w.Instructions b = codeGen.b;
encodeNullability(b, type);
makeType(codeGen, type.returnType, node);
makeType(codeGen, type.returnType);
if (type.positionalParameters.every(_isTypeConstant)) {
translator.constants.instantiateConstant(
codeGen.function,
@@ -170,7 +167,7 @@ class Types {
translator.constants.makeTypeList(type.positionalParameters),
typeListExpectedType);
} else {
_makeTypeList(codeGen, type.positionalParameters, node);
_makeTypeList(codeGen, type.positionalParameters);
}
b.i64_const(type.requiredParameterCount);
if (type.namedParameters.every((n) => _isTypeConstant(n.type))) {
@@ -197,8 +194,8 @@ class Types {
BoolLiteral(n.isRequired)
])));
}
w.ValueType namedParametersListType = codeGen.makeList(expressions,
translator.fixedLengthListClass, namedParameterType, node);
w.ValueType namedParametersListType = codeGen.makeList(
expressions, translator.fixedLengthListClass, namedParameterType);
translator.convertType(codeGen.function, namedParametersListType,
namedParametersExpectedType);
}
@@ -207,7 +204,7 @@ class Types {
/// Makes a `_Type` object on the stack.
/// TODO(joshualitt): Refactor this logic to remove the dependency on
/// CodeGenerator.
w.ValueType makeType(CodeGenerator codeGen, DartType type, TreeNode node) {
w.ValueType makeType(CodeGenerator codeGen, DartType type) {
w.Instructions b = codeGen.b;
if (_isTypeConstant(type)) {
translator.constants.instantiateConstant(
@@ -221,38 +218,16 @@ class Types {
type is FutureOrType ||
type is FunctionType);
if (type is TypeParameterType) {
if (type.parameter.parent is FunctionNode) {
// Type argument to function
w.Local? local = codeGen.typeLocals[type.parameter];
if (local != null) {
b.local_get(local);
translator.convertType(
codeGen.function, local.type, nonNullableTypeType);
return nonNullableTypeType;
} else {
codeGen.unimplemented(node, "Type parameter access inside lambda",
[nonNullableTypeType]);
return nonNullableTypeType;
}
}
// Type argument of class
Class cls = type.parameter.parent as Class;
ClassInfo info = translator.classInfo[cls]!;
int fieldIndex = translator.typeParameterIndex[type.parameter]!;
w.ValueType thisType = codeGen.visitThis(info.nonNullableType);
translator.convertType(codeGen.function, thisType, info.nonNullableType);
b.struct_get(info.struct, fieldIndex);
b.ref_as_non_null();
return nonNullableTypeType;
return codeGen.instantiateTypeParameter(type.parameter);
}
ClassInfo info = translator.classInfo[classForType(type)]!;
translator.functions.allocateClass(info.classId);
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
if (type is InterfaceType) {
_makeInterfaceType(codeGen, info, type, node);
_makeInterfaceType(codeGen, info, type);
} else if (type is FutureOrType) {
_makeFutureOrType(codeGen, type, node);
_makeFutureOrType(codeGen, type);
} else if (type is FunctionType) {
if (isGenericFunction(type)) {
// TODO(joshualitt): Implement generic function types and share most of
@@ -260,7 +235,7 @@ class Types {
print("Not implemented: RTI ${type}");
encodeNullability(b, type);
} else {
_makeFunctionType(codeGen, info, type, node);
_makeFunctionType(codeGen, info, type);
}
} else {
throw '`$type` should have already been handled.';