blob: f9ee6a401417832607cbec69b7a140557c4c0311 [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;
Types(this.translator);
List<Class> _getConcreteSubtypes(Class cls) => translator.subtypes
.getSubtypesOf(cls)
.where((c) => !c.isAbstract)
.toList();
bool _isTypeConstant(DartType type) {
return type is DynamicType ||
type is VoidType ||
type is NeverType ||
type is NullType ||
type is FunctionType ||
type is InterfaceType && type.typeArguments.every(_isTypeConstant);
}
/// Makes a `_Type` object on the stack.
w.ValueType makeType(CodeGenerator codeGen, DartType type, TreeNode node) {
w.ValueType typeType =
translator.classInfo[translator.typeClass]!.nullableType;
w.Instructions b = codeGen.b;
if (_isTypeConstant(type)) {
translator.constants.instantiateConstant(
codeGen.function, b, TypeLiteralConstant(type), typeType);
return typeType;
}
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);
return local.type;
} else {
codeGen.unimplemented(
node, "Type parameter access inside lambda", [typeType]);
return typeType;
}
}
// 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.nullableType);
translator.convertType(codeGen.function, thisType, info.nullableType);
b.struct_get(info.struct, fieldIndex);
return typeType;
}
ClassInfo info = translator.classInfo[translator.typeClass]!;
translator.functions.allocateClass(info.classId);
if (type is FutureOrType) {
// TODO(askesc): Have an actual representation of FutureOr types
b.ref_null(info.nullableType.heapType);
return info.nullableType;
}
if (type is! InterfaceType) {
codeGen.unimplemented(node, type, [info.nullableType]);
return info.nullableType;
}
ClassInfo typeInfo = translator.classInfo[type.classNode]!;
w.ValueType typeListExpectedType =
info.struct.fields[FieldIndex.typeTypeArguments].type.unpacked;
b.i32_const(info.classId);
b.i32_const(initialIdentityHash);
b.i64_const(typeInfo.classId);
w.DefinedFunction function = codeGen.function;
if (type.typeArguments.isEmpty) {
b.global_get(translator.constants.emptyTypeList);
translator.convertType(function,
translator.constants.emptyTypeList.type.type, typeListExpectedType);
} else if (type.typeArguments.every(_isTypeConstant)) {
ListConstant typeArgs = ListConstant(
InterfaceType(translator.typeClass, Nullability.nonNullable),
type.typeArguments.map((t) => TypeLiteralConstant(t)).toList());
translator.constants
.instantiateConstant(function, b, typeArgs, typeListExpectedType);
} else {
w.ValueType listType = codeGen.makeList(
type.typeArguments.map((t) => TypeLiteral(t)).toList(),
translator.fixedLengthListClass,
InterfaceType(translator.typeClass, Nullability.nonNullable),
node);
translator.convertType(function, listType, typeListExpectedType);
}
translator.struct_new(b, info);
return info.nullableType;
}
/// 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.
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 isNullable = operandType.isPotentiallyNullable;
w.Label? resultLabel;
if (isNullable) {
// 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);
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 (isNullable) {
b.br(resultLabel!);
b.end(); // nullLabel
b.i32_const(type.declaredNullability == Nullability.nullable ? 1 : 0);
b.end(); // resultLabel
}
}
}