blob: 53df1804cd07240b9ad8a7ecdf3b19bd7ba13b69 [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:dart2wasm/class_info.dart';
import 'package:dart2wasm/code_generator.dart';
import 'package:dart2wasm/translator.dart';
import 'package:kernel/ast.dart';
import 'package:wasm_builder/wasm_builder.dart' as w;
/// Helper class for building runtime types.
class Types {
final Translator translator;
late final typeClassInfo = translator.classInfo[translator.typeClass]!;
late final w.ValueType typeListExpectedType = classAndFieldToType(
translator.interfaceTypeClass, FieldIndex.interfaceTypeTypeArguments);
late final w.ValueType namedParametersExpectedType = classAndFieldToType(
translator.functionTypeClass, FieldIndex.functionTypeNamedParameters);
Types(this.translator);
w.ValueType classAndFieldToType(Class cls, int fieldIndex) =>
translator.classInfo[cls]!.struct.fields[fieldIndex].type.unpacked;
Iterable<Class> _getConcreteSubtypes(Class cls) =>
translator.subtypes.getSubtypesOf(cls).where((c) => !c.isAbstract);
w.ValueType get nullableTypeType => typeClassInfo.nullableType;
w.ValueType get nonNullableTypeType => typeClassInfo.nonNullableType;
InterfaceType get namedParameterType =>
InterfaceType(translator.namedParameterClass, Nullability.nonNullable);
/// Build a [Map<int, List<int>>] to store subtype information.
Map<int, List<int>> _buildSubtypeMap() {
List<ClassInfo> classes = translator.classes;
Map<int, List<int>> subtypeMap = {};
for (ClassInfo classInfo in classes) {
if (classInfo.cls == null) continue;
List<int> classIds = _getConcreteSubtypes(classInfo.cls!)
.map((cls) => translator.classInfo[cls]!.classId)
.where((classId) => classId != classInfo.classId)
.toList();
if (classIds.isEmpty) continue;
subtypeMap[classInfo.classId] = classIds;
}
return subtypeMap;
}
/// Builds the subtype map and pushes it onto the stack.
w.ValueType makeSubtypeMap(w.Instructions b) {
// Instantiate subtype map constant.
Map<int, List<int>> subtypeMap = _buildSubtypeMap();
ClassInfo immutableMapInfo =
translator.classInfo[translator.immutableMapClass]!;
w.ValueType expectedType = immutableMapInfo.nonNullableType;
DartType mapAndSetKeyType = translator.coreTypes.intNonNullableRawType;
DartType mapValueType = InterfaceType(translator.immutableListClass,
Nullability.nonNullable, [mapAndSetKeyType]);
List<ConstantMapEntry> entries = subtypeMap.entries.map((mapEntry) {
return ConstantMapEntry(
IntConstant(mapEntry.key),
ListConstant(mapAndSetKeyType,
mapEntry.value.map((i) => IntConstant(i)).toList()));
}).toList();
translator.constants.instantiateConstant(null, b,
MapConstant(mapAndSetKeyType, mapValueType, entries), expectedType);
return expectedType;
}
bool isGenericFunction(FunctionType type) => type.typeParameters.isNotEmpty;
bool _isTypeConstant(DartType type) {
return type is DynamicType ||
type is VoidType ||
type is NeverType ||
type is NullType ||
type is FutureOrType && _isTypeConstant(type.typeArgument) ||
(type is FunctionType &&
type.typeParameters.isEmpty && // TODO(joshualitt) generic functions
_isTypeConstant(type.returnType) &&
type.positionalParameters.every(_isTypeConstant) &&
type.namedParameters.every((n) => _isTypeConstant(n.type))) ||
type is InterfaceType && type.typeArguments.every(_isTypeConstant);
}
Class classForType(DartType type) {
if (type is DynamicType) {
return translator.dynamicTypeClass;
} else if (type is VoidType) {
return translator.voidTypeClass;
} else if (type is NeverType) {
// For runtime types with sound null safety, `Never?` is the same as
// `Null`.
if (type.nullability == Nullability.nullable) {
return translator.nullTypeClass;
} else {
return translator.neverTypeClass;
}
} else if (type is NullType) {
return translator.nullTypeClass;
} else if (type is FutureOrType) {
return translator.futureOrTypeClass;
} else if (type is InterfaceType) {
return translator.interfaceTypeClass;
} else if (type is FunctionType) {
if (isGenericFunction(type)) {
return translator.genericFunctionTypeClass;
} else {
return translator.functionTypeClass;
}
}
throw "Unexpected DartType: $type";
}
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));
translator.convertType(codeGen.function, listType, typeListExpectedType);
}
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);
}
void _makeFutureOrType(CodeGenerator codeGen, FutureOrType type) {
w.Instructions b = codeGen.b;
w.DefinedFunction function = codeGen.function;
// We canonicalize `FutureOr<T?>` to `FutureOr<T?>?`. However, we have to
// take special care to handle the case where we have
// 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);
w.Local typeArgumentTemporary = codeGen.addLocal(typeArgumentType);
b.local_tee(typeArgumentTemporary);
b.struct_get(typeClassInfo.struct, FieldIndex.typeIsNullable);
b.local_get(typeArgumentTemporary);
translator.convertType(function, typeArgumentType, nonNullableTypeType);
} else {
encodeNullability(b, type);
makeType(codeGen, type.typeArgument);
}
}
void _makeFunctionType(
CodeGenerator codeGen, ClassInfo info, FunctionType type) {
w.Instructions b = codeGen.b;
encodeNullability(b, type);
makeType(codeGen, type.returnType);
if (type.positionalParameters.every(_isTypeConstant)) {
translator.constants.instantiateConstant(
codeGen.function,
b,
translator.constants.makeTypeList(type.positionalParameters),
typeListExpectedType);
} else {
_makeTypeList(codeGen, type.positionalParameters);
}
b.i64_const(type.requiredParameterCount);
if (type.namedParameters.every((n) => _isTypeConstant(n.type))) {
translator.constants.instantiateConstant(
codeGen.function,
b,
translator.constants.makeNamedParametersList(type),
namedParametersExpectedType);
} else {
Class namedParameterClass = translator.namedParameterClass;
Constructor namedParameterConstructor =
namedParameterClass.constructors.single;
List<Expression> expressions = [];
for (NamedType n in type.namedParameters) {
expressions.add(_isTypeConstant(n.type)
? ConstantExpression(
translator.constants.makeNamedParameterConstant(n),
namedParameterType)
: ConstructorInvocation(
namedParameterConstructor,
Arguments([
StringLiteral(n.name),
TypeLiteral(n.type),
BoolLiteral(n.isRequired)
])));
}
w.ValueType namedParametersListType = codeGen.makeList(
expressions, translator.fixedLengthListClass, namedParameterType);
translator.convertType(codeGen.function, namedParametersListType,
namedParametersExpectedType);
}
}
/// 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) {
w.Instructions b = codeGen.b;
if (_isTypeConstant(type)) {
translator.constants.instantiateConstant(
codeGen.function, b, TypeLiteralConstant(type), nonNullableTypeType);
return nonNullableTypeType;
}
// All of the singleton types represented by canonical objects should be
// created const.
assert(type is TypeParameterType ||
type is InterfaceType ||
type is FutureOrType ||
type is FunctionType);
if (type is TypeParameterType) {
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);
} else if (type is FutureOrType) {
_makeFutureOrType(codeGen, type);
} else if (type is FunctionType) {
if (isGenericFunction(type)) {
// TODO(joshualitt): Implement generic function types and share most of
// the logic with _makeFunctionType.
print("Not implemented: RTI ${type}");
encodeNullability(b, type);
} else {
_makeFunctionType(codeGen, info, type);
}
} else {
throw '`$type` should have already been handled.';
}
translator.struct_new(b, info);
return info.nonNullableType;
}
/// Test value against a Dart type. Expects the value on the stack as a
/// (ref null #Top) and leaves the result on the stack as an i32.
/// TODO(joshualitt): Remove dependency on [CodeGenerator]
void emitTypeTest(CodeGenerator codeGen, DartType type, DartType operandType,
TreeNode node) {
w.Instructions b = codeGen.b;
if (type is! InterfaceType) {
// TODO(askesc): Implement type test for remaining types
print("Not implemented: Type test with non-interface type $type"
" at ${node.location}");
b.drop();
b.i32_const(1);
return;
}
bool isPotentiallyNullable = operandType.isPotentiallyNullable;
w.Label? resultLabel;
if (isPotentiallyNullable) {
// Store operand in a temporary variable, since Binaryen does not support
// block inputs.
w.Local operand = codeGen.addLocal(translator.topInfo.nullableType);
b.local_set(operand);
resultLabel = b.block(const [], const [w.NumType.i32]);
w.Label nullLabel = b.block(const [], const []);
b.local_get(operand);
b.br_on_null(nullLabel);
}
if (type.typeArguments.any((t) => t is! DynamicType)) {
// If the tested-against type as an instance of the static operand type
// has the same type arguments as the static operand type, it is not
// necessary to test the type arguments.
Class cls = translator.classForType(operandType);
InterfaceType? base = translator.hierarchy
.getTypeAsInstanceOf(type, cls, codeGen.member.enclosingLibrary)
?.withDeclaredNullability(operandType.declaredNullability);
if (base != operandType) {
print("Not implemented: Type test with type arguments"
" at ${node.location}");
}
}
List<Class> concrete = _getConcreteSubtypes(type.classNode).toList();
if (type.classNode == translator.coreTypes.functionClass) {
ClassInfo functionInfo = translator.classInfo[translator.functionClass]!;
translator.ref_test(b, functionInfo);
} else if (concrete.isEmpty) {
b.drop();
b.i32_const(0);
} else if (concrete.length == 1) {
ClassInfo info = translator.classInfo[concrete.single]!;
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.i32_const(info.classId);
b.i32_eq();
} else {
w.Local idLocal = codeGen.addLocal(w.NumType.i32);
b.struct_get(translator.topInfo.struct, FieldIndex.classId);
b.local_set(idLocal);
w.Label done = b.block(const [], const [w.NumType.i32]);
b.i32_const(1);
for (Class cls in concrete) {
ClassInfo info = translator.classInfo[cls]!;
b.i32_const(info.classId);
b.local_get(idLocal);
b.i32_eq();
b.br_if(done);
}
b.drop();
b.i32_const(0);
b.end(); // done
}
if (isPotentiallyNullable) {
b.br(resultLabel!);
b.end(); // nullLabel
encodeNullability(b, type);
b.end(); // resultLabel
}
}
/// Returns true if a given type is nullable, and false otherwise. This
/// function should not be used on [DartType]s with undetermined nullability.
bool isNullable(DartType type) {
Nullability nullability = type.nullability;
assert(nullability == Nullability.nullable ||
nullability == Nullability.nonNullable);
return nullability == Nullability.nullable ? true : false;
}
void encodeNullability(w.Instructions b, DartType type) =>
b.i32_const(isNullable(type) ? 1 : 0);
}