[dart2wasm] Put function types of imports in singleton rec groups

Starting from ba6be821c99db09b0ef2689dea8f0cb863c47ff5 (11.0.194), V8
enforces type equivalence according to the canonical iso-recursive
types for functions imported from other modules or constructed using
`WebAssembly.Function`, which means that to match such a type, a
function type declared in a Wasm module must be declared in a
singleton rec group.

To be compatible with this new canonicalization scheme, we define the
function types for all imported functions each in their own rec group
separately from other types.

Change-Id: I166b4b7d1c8fa48638f816f874f737536a61f15d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/274388
Commit-Queue: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Joshua Litt <joshualitt@google.com>
This commit is contained in:
Aske Simon Christensen 2022-12-13 20:43:22 +00:00 committed by Commit Queue
parent 905b913a77
commit f053d2170d
3 changed files with 53 additions and 21 deletions

View file

@ -66,9 +66,14 @@ class FunctionCollector {
String module = importName.substring(0, dot);
String name = importName.substring(dot + 1);
if (member is Procedure) {
// Define the function type in a singular recursion group to enable it
// to be unified with function types defined in FFI modules or using
// `WebAssembly.Function`.
m.splitRecursionGroup();
w.FunctionType ftype = _makeFunctionType(
translator, member.reference, member.function.returnType, null,
isImportOrExport: true);
m.splitRecursionGroup();
_functions[member.reference] =
m.importFunction(module, name, ftype, "$importName (import)");
}
@ -242,34 +247,29 @@ w.FunctionType _makeFunctionType(Translator translator, Reference target,
function.positionalParameters.map((p) => p.type);
}
List<w.ValueType> typeParameters = List.filled(typeParamCount,
translator.classInfo[translator.typeClass]!.nonNullableType);
// Translate types differently for imports and exports.
w.ValueType translateType(DartType type) => isImportOrExport
? translator.translateExternalType(type)
: translator.translateType(type);
// The only reference types allowed as parameters and returns on imported or
// exported functions for JS interop are `externref` and `funcref`.
w.ValueType adjustExternalType(w.ValueType type) {
if (isImportOrExport && type is w.RefType) {
if (type.heapType.isSubtypeOf(w.HeapType.func)) {
return w.RefType.func(nullable: true);
}
return w.RefType.extern(nullable: true);
}
return type;
}
List<w.ValueType> typeParameters = List.filled(
typeParamCount,
translateType(
InterfaceType(translator.typeClass, Nullability.nonNullable)));
List<w.ValueType> inputs = [];
if (receiverType != null) {
inputs.add(adjustExternalType(receiverType));
assert(!isImportOrExport);
inputs.add(receiverType);
}
inputs.addAll(typeParameters.map(adjustExternalType));
inputs.addAll(
params.map((t) => adjustExternalType(translator.translateType(t))));
inputs.addAll(typeParameters);
inputs.addAll(params.map(translateType));
List<w.ValueType> outputs = returnType is VoidType
? member.function?.asyncMarker == AsyncMarker.Async
? [adjustExternalType(translator.topInfo.nullableType)]
? [translateType(translator.coreTypes.objectNullableRawType)]
: const []
: [adjustExternalType(translator.translateType(returnType))];
: [translateType(returnType)];
return translator.m.addFunctionType(inputs, outputs);
}

View file

@ -196,9 +196,12 @@ class Translator with KernelNodes {
voidMarker = w.RefType.def(w.StructType("void"), nullable: true);
mainFunction = _findMainMethod(libraries.first);
// Collect imports and exports as the very first thing so the function types
// for the imports can be places in singleton recursion groups.
functions.collectImportsAndExports();
closureLayouter.collect([mainFunction.function]);
classInfoCollector.collect();
functions.collectImportsAndExports();
initFunction =
m.addFunction(m.addFunctionType(const [], const []), "#init");
@ -495,6 +498,30 @@ class Translator with KernelNodes {
() => m.addArrayType("Array<$name>", elementType: w.FieldType(type)));
}
/// Translate a Dart type as it should appear on parameters and returns of
/// imported and exported functions. The only reference types allowed here
/// for JS interop are `externref` and `funcref`.
///
/// This function can be called before the class info is built.
w.ValueType translateExternalType(DartType type) {
if (type is InterfaceType) {
Class cls = type.classNode;
if (cls == wasmFuncRefClass || cls == wasmFunctionClass) {
return w.RefType.func(nullable: true);
}
if (!type.isPotentiallyNullable) {
w.StorageType? builtin = builtinTypes[cls];
if (builtin != null && builtin.isPrimitive) {
return builtin as w.ValueType;
}
if (isFfiCompound(cls)) {
return w.NumType.i32;
}
}
}
return w.RefType.extern(nullable: true);
}
w.DefinedGlobal makeFunctionRef(w.BaseFunction f) {
return functionRefCache.putIfAbsent(f, () {
w.DefinedGlobal global = m.addGlobal(

View file

@ -121,7 +121,12 @@ class Module with SerializerMixin {
/// Insert a recursion group split in the list of type definitions. Types can
/// only reference other types in the same or earlier recursion groups.
void splitRecursionGroup() {
recursionGroupSplits.add(defTypes.length);
int typeCount = defTypes.length;
if (typeCount > 0 &&
(recursionGroupSplits.isEmpty ||
recursionGroupSplits.last != typeCount)) {
recursionGroupSplits.add(typeCount);
}
}
/// Add a new function to the module with the given function type.