| // 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); |
| } |