diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart index bdfdb7d098e..94a81049c2a 100644 --- a/pkg/dart2wasm/lib/code_generator.dart +++ b/pkg/dart2wasm/lib/code_generator.dart @@ -4,6 +4,7 @@ import 'dart:collection' show LinkedHashMap; +import 'package:collection/collection.dart'; import 'package:kernel/ast.dart'; import 'package:kernel/type_environment.dart'; import 'package:wasm_builder/wasm_builder.dart' as w; @@ -12,7 +13,7 @@ import 'async.dart'; import 'class_info.dart'; import 'closures.dart'; import 'dispatch_table.dart'; -import 'dynamic_forwarders.dart'; +import 'functions.dart' show CallShape, makeDynamicForwarderSignature; import 'globals.dart'; import 'intrinsics.dart'; import 'param_info.dart'; @@ -3219,10 +3220,6 @@ CodeGenerator? getInlinableMemberCodeGenerator(Translator translator, return TearOffCodeGenerator(translator, functionType, member); } - if (reference.isTypeCheckerReference) { - return TypeCheckerCodeGenerator(translator, functionType, member); - } - if (member is Constructor) { if (reference.isConstructorBodyReference) { return ConstructorCodeGenerator(translator, functionType, member); @@ -3433,199 +3430,228 @@ class TearOffCodeGenerator extends AstCodeGenerator { } } -class TypeCheckerCodeGenerator extends AstCodeGenerator { - final Member member; +/// Generates code for a dynamic forwarder function. +/// +/// Dynamic forwarders are functions that +/// * may have to populate default type arguments +/// * may have to check argument types +/// * may have to unbox arguments +/// * call the normal getter/setter/method target +/// * may have to box the result value +/// +/// We generate them for each [CallShape] and the caller guarantees that the +/// [CallShape] is valid for the given target [reference]. The signature for +/// such a forwarder function is determined by [makeDynamicForwarderSignature]. +class DynamicForwarderCodeGenerator extends AstCodeGenerator { + final Reference reference; + final CallShape callShape; - TypeCheckerCodeGenerator( - Translator translator, w.FunctionType functionType, this.member) - : super(translator, functionType, member); + DynamicForwarderCodeGenerator(Translator translator, + w.FunctionType functionType, this.reference, this.callShape) + : super(translator, functionType, reference.asMember); @override void generateInternal() { + final member = reference.asMember; + // Initialize [Closures] without [Closures.captures]: Similar to // [TearOffCodeGenerator], type parameters will be loaded from the `this` // struct. closures = translator.getClosures(member, findCaptures: false); - if (member is Field || - (member is Procedure && (member as Procedure).isSetter)) { - _generateFieldSetterTypeCheckerMethod(); + if (member is Field) { + if (reference.isImplicitGetter) { + _generateDynamicGetterForwarder(); + } else { + assert(reference.isImplicitSetter); + _generateDynamicSetterForwarder(); + } } else { - _generateProcedureTypeCheckerMethod(); + member as Procedure; + if (member.isSetter) { + _generateDynamicSetterForwarder(); + } else if (member.isGetter) { + _generateDynamicGetterForwarder(); + } else { + _generateDynamicMethodForwarder(); + } } } - /// Generate type checker method for a method. - /// - /// This function will be called by an invocation forwarder in a dynamic - /// invocation to type check parameters before calling the actual method. - void _generateProcedureTypeCheckerMethod() { - final receiverLocal = paramLocals[0]; - final typeArgsLocal = paramLocals[1]; - final positionalArgsLocal = paramLocals[2]; - final namedArgsLocal = paramLocals[3]; + void _generateDynamicMethodForwarder() { + // The offsets of arguments passed by the caller. + const int argReceiverOffset = 0; + const int argTypesOffset = argReceiverOffset + 1; + final int argPositionalsOffset = argTypesOffset + callShape.typeCount; + final int argNamedOffset = argPositionalsOffset + callShape.positionalCount; - _initializeThis(member.reference); + final targetProcedure = reference.asMember as Procedure; + final targetFunction = targetProcedure.function; + assert(callShape.matchesTarget(targetFunction)); + + final target = translator.getFunctionEntry(targetProcedure.reference, + uncheckedEntry: false); + final targetSignature = translator.signatureForDirectCall(target); + + _initializeThis(reference); final typeType = translator.classInfo[translator.typeClass]!.nonNullableType; - - final target = - translator.getFunctionEntry(member.reference, uncheckedEntry: false); final targetParamInfo = translator.paramInfoForDirectCall(target); - final procedure = member as Procedure; - - // Bind type parameters - final memberTypeParams = procedure.function.typeParameters; - assert(memberTypeParams.length == targetParamInfo.typeParamCount); - - if (memberTypeParams.isNotEmpty) { - // Type argument list is either empty or have the right number of types - // (checked by the forwarder). - b.local_get(typeArgsLocal); - b.array_len(); - b.i32_eqz(); - b.if_([], List.generate(memberTypeParams.length, (_) => typeType)); - // No type arguments passed, initialize with defaults - for (final typeParam in memberTypeParams) { - types.makeType(this, typeParam.defaultType); - } - b.else_(); - for (int typeParamIdx = 0; - typeParamIdx < memberTypeParams.length; - typeParamIdx += 1) { - b.local_get(typeArgsLocal); - b.i32_const(typeParamIdx); - b.array_get(translator.typeArrayType); - } - b.end(); - - // Create locals for type parameters. These will be used by `makeType` - // below when generating types of parameters, for type checks, and when - // pushing the type parameters when calling the actual member. - for (int typeParamIdx = memberTypeParams.length - 1; - typeParamIdx >= 0; - typeParamIdx -= 1) { - final local = addLocal(typeType); - b.local_set(local); - typeLocals[memberTypeParams[typeParamIdx]] = local; - } - } - - if (!translator.options.omitImplicitTypeChecks) { - // Check type parameter bounds - for (TypeParameter typeParameter in memberTypeParams) { - if (typeParameter.bound != translator.coreTypes.objectNullableRawType) { - _generateTypeArgumentBoundCheck(typeParameter.name!, - typeLocals[typeParameter]!, typeParameter.bound); - } - } - - // Check positional argument types - final List memberPositionalParams = - procedure.function.positionalParameters; - - for (int positionalParamIdx = 0; - positionalParamIdx < memberPositionalParams.length; - positionalParamIdx += 1) { - final param = memberPositionalParams[positionalParamIdx]; - b.local_get(positionalArgsLocal); - b.i32_const(positionalParamIdx); - b.array_get(translator.nullableObjectArrayType); - _generateArgumentTypeCheck(param.name!, translator.topType, param.type); - } - - // Check named argument types - final memberNamedParams = procedure.function.namedParameters; - - /// Maps a named parameter in the member's signature to the parameter's - /// index in the array [namedArgsLocal]. - int mapNamedParameterToArrayIndex(String name) { - int? idx; - for (int i = 0; i < targetParamInfo.names.length; i += 1) { - if (targetParamInfo.names[i] == name) { - idx = i; - break; - } - } - return idx!; - } - - for (int namedParamIdx = 0; - namedParamIdx < memberNamedParams.length; - namedParamIdx += 1) { - final param = memberNamedParams[namedParamIdx]; - b.local_get(namedArgsLocal); - b.i32_const(mapNamedParameterToArrayIndex(param.name!)); - b.array_get(translator.nullableObjectArrayType); - _generateArgumentTypeCheck(param.name!, translator.topType, param.type); - } - } - - // Argument types are as expected, call the member function - final w.FunctionType memberWasmFunctionType = - translator.signatureForDirectCall(target); - final List memberWasmInputs = memberWasmFunctionType.inputs; - + // Load the receiver + final receiverLocal = paramLocals[argReceiverOffset]; b.local_get(receiverLocal); - translator.convertType(b, receiverLocal.type, memberWasmInputs[0]); + translator.convertType(b, receiverLocal.type, targetSignature.inputs[0]); - for (final typeParam in memberTypeParams) { - b.local_get(typeLocals[typeParam]!); + // Load type parameters for target. + final targetTypeParams = targetFunction.typeParameters; + assert(targetTypeParams.length == targetParamInfo.typeParamCount); + if (targetTypeParams.isNotEmpty) { + if (callShape.typeCount != 0) { + // Provided by caller. + for (int i = 0; i < targetTypeParams.length; ++i) { + final param = targetTypeParams[i]; + final paramValue = paramLocals[argTypesOffset + i]; + b.local_get(paramValue); + typeLocals[param] = paramValue; + } + } else { + // Use default-to-bounds. + for (int i = 0; i < targetTypeParams.length; ++i) { + final param = targetTypeParams[i]; + types.makeType(this, param.defaultType); + final paramValue = b.addLocal(typeType); + b.local_tee(paramValue); + typeLocals[param] = paramValue; + } + } } - int memberParamIdx = - 1 + targetParamInfo.typeParamCount; // skip receiver and type args - - void pushArgument(w.Local listLocal, int listIdx, int wasmInputIdx) { - b.local_get(listLocal); - b.i32_const(listIdx); - b.array_get(translator.nullableObjectArrayType); - translator.convertType( - b, translator.topType, memberWasmInputs[wasmInputIdx]); + // Check type parameter bounds. + if (!translator.options.omitImplicitTypeChecks) { + for (int i = 0; i < targetTypeParams.length; ++i) { + final param = targetTypeParams[i]; + if (param.bound != translator.coreTypes.objectNullableRawType) { + final paramValue = typeLocals[param]!; + _generateTypeArgumentBoundCheck(param.name!, paramValue, param.bound); + } + } } - for (int positionalParamIdx = 0; - positionalParamIdx < targetParamInfo.positional.length; - positionalParamIdx += 1) { - pushArgument(positionalArgsLocal, positionalParamIdx, memberParamIdx); - memberParamIdx += 1; + // Load positional parameters for the target (and check types if needed). + final targetPositionalParams = targetFunction.positionalParameters; + for (int i = 0; i < targetParamInfo.positional.length; i++) { + final targetParamType = + targetSignature.inputs[1 + targetParamInfo.typeParamCount + i]; + if (i < callShape.positionalCount) { + // Provided by the caller. + final paramValue = paramLocals[argPositionalsOffset + i]; + b.local_get(paramValue); + if (!translator.options.omitImplicitTypeChecks) { + final param = targetPositionalParams[i]; + b.local_get(paramValue); + _generateArgumentTypeCheck( + param.name!, translator.topType, param.type); + } + translator.convertType(b, paramValue.type, targetParamType); + } else { + // Default to use if the callee has the `i` parameter. + final defaultFunctionValue = i < targetPositionalParams.length + ? (targetPositionalParams[i].initializer as ConstantExpression?) + ?.constant + : null; + // Default to use if callee doesn't have the `i` parameter. + final defaultValue = targetParamInfo.positional[i]; + // The target wasm function corresponding to an instance method may have + // a selector signature (which is based on all implementations of a + // selector) and therefore may have more parameters than the actual + // target needs (the others are ignored in the callee). + final value = defaultFunctionValue ?? defaultValue!; + translator.constants.instantiateConstant(b, value, targetParamType); + } } - for (int namedParamIdx = 0; - namedParamIdx < targetParamInfo.names.length; - namedParamIdx += 1) { - pushArgument(namedArgsLocal, namedParamIdx, memberParamIdx); - memberParamIdx += 1; + // Load named arguments (and check types if needed). + final targetNamedParams = targetFunction.namedParameters; + for (int i = 0; i < targetParamInfo.names.length; ++i) { + final targetParamType = targetSignature.inputs[1 + + targetParamInfo.typeParamCount + + targetParamInfo.positional.length + + i]; + final name = targetParamInfo.names[i]; + final namedParam = + targetNamedParams.firstWhereOrNull((n) => n.name == name); + final callerIndex = callShape.named.indexOf(name); + if (0 <= callerIndex) { + // Provided by the caller. + final paramValue = paramLocals[argNamedOffset + callerIndex]; + b.local_get(paramValue); + if (!translator.options.omitImplicitTypeChecks) { + b.local_get(paramValue); + _generateArgumentTypeCheck( + name, translator.topType, namedParam!.type); + } + translator.convertType(b, paramValue.type, targetParamType); + } else { + // Default to use if callee has the `name` parameter. + final defaultFunctionValue = + (namedParam?.initializer as ConstantExpression?)?.constant; + // Default to use if callee doesn't have `name` parameter. + final defaultValue = targetParamInfo.named[name]; + // The target wasm function corresponding to an instance method may have + // a selector signature (which is based on all implementations of a + // selector) and therefore may have more parameters than the actual + // target needs (the others are ignored in the callee). + final value = (defaultFunctionValue ?? defaultValue)!; + translator.constants.instantiateConstant(b, value, targetParamType); + } } call(target); - - translator.convertType( - b, - translator.outputOrVoid(memberWasmFunctionType.outputs), + translator.convertType(b, translator.outputOrVoid(targetSignature.outputs), translator.topType); - b.return_(); b.end(); } - /// Generate type checker method for a setter. - /// - /// This function will be called by a setter forwarder in a dynamic set to - /// type check the setter argument before calling the actual setter. - void _generateFieldSetterTypeCheckerMethod() { + void _generateDynamicGetterForwarder() { + final receiverLocal = paramLocals[0]; + _initializeThis(reference); + + final member = reference.asMember; + final info = translator.classInfo[member.enclosingClass]!; + if (member is Field) { + int fieldIndex = translator.fieldIndex[member]!; + b.local_get(receiverLocal); + b.struct_get(info.struct, fieldIndex); + translator.convertType( + b, info.struct.fields[fieldIndex].type.unpacked, translator.topType); + } else { + final target = + translator.getFunctionEntry(reference, uncheckedEntry: false); + final getterProcedureWasmType = translator.signatureForDirectCall(target); + final getterWasmOutputs = getterProcedureWasmType.outputs; + assert(getterWasmOutputs.length == 1); + b.local_get(receiverLocal); + call(target); + translator.convertType(b, outputs.single, translator.topType); + } + + b.end(); // end function + } + + void _generateDynamicSetterForwarder() { final receiverLocal = paramLocals[0]; final positionalArgLocal = paramLocals[1]; - _initializeThis(member.reference); + _initializeThis(reference); - final member_ = member; + final member = reference.asMember; DartType paramType; - if (member_ is Field) { - paramType = member_.type; + if (member is Field) { + paramType = member.type; } else { - paramType = (member_ as Procedure).setterType; + paramType = (member as Procedure).setterType; } if (!translator.options.omitImplicitTypeChecks) { @@ -3637,9 +3663,9 @@ class TypeCheckerCodeGenerator extends AstCodeGenerator { ); } - ClassInfo info = translator.classInfo[member_.enclosingClass]!; - if (member_ is Field) { - int fieldIndex = translator.fieldIndex[member_]!; + ClassInfo info = translator.classInfo[member.enclosingClass]!; + if (member is Field) { + int fieldIndex = translator.fieldIndex[member]!; b.local_get(receiverLocal); translator.convertType(b, receiverLocal.type, info.nonNullableType); b.local_get(positionalArgLocal); @@ -3647,9 +3673,8 @@ class TypeCheckerCodeGenerator extends AstCodeGenerator { info.struct.fields[fieldIndex].type.unpacked); b.struct_set(info.struct, fieldIndex); } else { - final setterProcedure = member_ as Procedure; - final target = translator.getFunctionEntry(setterProcedure.reference, - uncheckedEntry: false); + final target = + translator.getFunctionEntry(reference, uncheckedEntry: false); final setterProcedureWasmType = translator.signatureForDirectCall(target); final setterWasmInputs = setterProcedureWasmType.inputs; assert(setterWasmInputs.length == 2); @@ -3665,6 +3690,78 @@ class TypeCheckerCodeGenerator extends AstCodeGenerator { } } +/// Creates [Invocation] object based on a [CallShape]. +class InvocationCreationStubGenerator implements CodeGenerator { + final Translator translator; + final CallShape callShape; + + InvocationCreationStubGenerator(this.translator, this.callShape); + + @override + void generate(w.InstructionsBuilder b, List paramLocals, + w.Label? returnLabel) { + assert(returnLabel == null); + + int argumentIterator = 0; + + final typeArgs = + b.addLocal(w.RefType(translator.typeArrayType, nullable: false)); + for (int i = 0; i < callShape.typeCount; ++i) { + b.local_get(paramLocals[argumentIterator++]); + } + b.array_new_fixed(translator.typeArrayType, callShape.typeCount); + b.local_set(typeArgs); + + final posArgs = b.addLocal( + w.RefType(translator.nullableObjectArrayType, nullable: false)); + for (int i = 0; i < callShape.positionalCount; ++i) { + b.local_get(paramLocals[argumentIterator++]); + } + b.array_new_fixed( + translator.nullableObjectArrayType, callShape.positionalCount); + b.local_set(posArgs); + + final namedArgs = b.addLocal( + w.RefType(translator.nullableObjectArrayType, nullable: false)); + for (int i = 0; i < callShape.named.length; ++i) { + final name = callShape.named[i]; + translator.constants.instantiateConstant(b, + translator.symbols.symbolForNamedParameter(name), translator.topType); + b.local_get(paramLocals[argumentIterator++]); + } + b.array_new_fixed( + translator.nullableObjectArrayType, callShape.named.length * 2); + b.local_set(namedArgs); + + createInvocationObject( + translator, b, callShape.name, typeArgs, posArgs, namedArgs); + b.return_(); + b.end(); + } +} + +void createInvocationObject( + Translator translator, + w.InstructionsBuilder b, + Name memberName, + w.Local typeArgsLocal, + w.Local positionalArgsLocal, + w.Local namedArgsLocal) { + translator.constants.instantiateConstant( + b, + translator.symbols.methodSymbolFromName(memberName), + translator.classInfo[translator.symbolClass]!.nonNullableType); + + b.local_get(typeArgsLocal); + translator.callReference(translator.typeArgumentsToList.reference, b); + b.local_get(positionalArgsLocal); + translator.callReference(translator.positionalParametersToList.reference, b); + b.local_get(namedArgsLocal); + translator.callReference(translator.namedParametersToMap.reference, b); + translator.callReference( + translator.invocationGenericMethodFactory.reference, b); +} + class InitializerListCodeGenerator extends AstCodeGenerator { final Constructor member; diff --git a/pkg/dart2wasm/lib/dynamic_forwarders.dart b/pkg/dart2wasm/lib/dynamic_forwarders.dart index fca617bcbc9..fcb9cfaf554 100644 --- a/pkg/dart2wasm/lib/dynamic_forwarders.dart +++ b/pkg/dart2wasm/lib/dynamic_forwarders.dart @@ -9,6 +9,7 @@ import 'class_info.dart'; import 'closures.dart'; import 'code_generator.dart' show CallTarget, CodeGenerator, MacroAssembler; import 'dispatch_table.dart'; +import 'functions.dart' show CallShape; import 'reference_extensions.dart'; import 'translator.dart'; @@ -46,61 +47,6 @@ class DynamicForwarders { } } -class CallShape { - final Name name; - final int typeCount; - final int positionalCount; - final List named; - - CallShape(this.name, this.typeCount, this.positionalCount, this.named); - - int get totalArgumentCount => typeCount + positionalCount + named.length; - - bool matchesTarget(FunctionNode target) { - if (typeCount != target.typeParameters.length && typeCount != 0) { - return false; - } - if (positionalCount < target.requiredParameterCount || - positionalCount > target.positionalParameters.length) { - return false; - } - final namedParams = target.namedParameters; - for (final name in namedParams) { - if (name.isRequired && !named.contains(name.name)) { - return false; - } - } - for (final name in named) { - if (!namedParams.any((n) => n.name == name)) { - return false; - } - } - return true; - } - - @override - int get hashCode => - Object.hash(name, typeCount, positionalCount, Object.hashAll(named)); - - @override - bool operator ==(other) { - if (other is! CallShape) return false; - if (name != other.name) return false; - if (typeCount != other.typeCount) return false; - if (named.length != other.named.length) return false; - for (int i = 0; i < named.length; ++i) { - if (named[i] != other.named[i]) { - return false; - } - } - return true; - } - - @override - String toString() => - 'CallShape($name, $typeCount, $positionalCount, ${named.join('-')})'; -} - class _DynamicForwarderCallTarget extends CallTarget { final Translator translator; final _ForwarderKind _kind; @@ -308,10 +254,10 @@ class _DynamicForwarderCodeGenerator extends CodeGenerator { b.local_get(receiverLocal); b.loadClassId(translator, receiverLocal.type); b.classIdSearch(ranges, [positionalArgLocal.type], (Reference target) { - final Member targetMember = target.asMember; b.local_get(receiverLocal); b.local_get(positionalArgLocal); - translator.callReference(targetMember.typeCheckerReference, b); + translator.callFunction( + translator.functions.getDynamicForwarder(target, callerShape), b); }, () { generateNoSuchMethodCall( translator, @@ -330,75 +276,20 @@ class _DynamicForwarderCodeGenerator extends CodeGenerator { void _generateMethodCode(Translator translator) { final b = function.body; - final nullableReceiverLocal = function.locals[0]; // ref #Top - // Load type parameter as WasmArray<_Type> - final typeArgsLocal = b.addLocal(translator.typeArrayTypeRef); - if (callerShape.typeCount == 0) { - final emptyArray = translator.constants - .makeArrayOf(translator.coreTypes.typeNonNullableRawType, []); - translator.constants - .instantiateConstant(b, emptyArray, translator.typeArrayTypeRef); - } else { - for (int i = 0; i < callerShape.typeCount; ++i) { - b.local_get(function.locals[1 + i]); - } - b.array_new_fixed(translator.typeArrayType, callerShape.typeCount); - } - b.local_set(typeArgsLocal); - - // Load positional parameters as WasmArray - final positionalArgsLocal = - b.addLocal(translator.nullableObjectArrayTypeRef); - if (callerShape.positionalCount == 0) { - final emptyArray = translator.constants - .makeArrayOf(translator.coreTypes.objectNullableRawType, []); - translator.constants.instantiateConstant( - b, emptyArray, translator.nullableObjectArrayTypeRef); - } else { - for (int i = 0; i < callerShape.positionalCount; ++i) { - b.local_get(function.locals[1 + callerShape.typeCount + i]); - } - b.array_new_fixed( - translator.nullableObjectArrayType, callerShape.positionalCount); - } - b.local_set(positionalArgsLocal); - - // Load named parameters as WasmArray - final namedArgsLocal = b.addLocal(translator.nullableObjectArrayTypeRef); - if (callerShape.named.isEmpty) { - final emptyArray = translator.constants - .makeArrayOf(translator.coreTypes.objectNullableRawType, []); - translator.constants.instantiateConstant( - b, emptyArray, translator.nullableObjectArrayTypeRef); - } else { - for (int i = 0; i < callerShape.named.length; ++i) { - translator.constants.instantiateConstant( - b, - translator.symbols.symbolForNamedParameter(callerShape.named[i]), - translator.topType); - b.local_get(function.locals[ - 1 + callerShape.typeCount + callerShape.positionalCount + i]); - } - b.array_new_fixed( - translator.nullableObjectArrayType, callerShape.named.length * 2); - } - b.local_set(namedArgsLocal); - // Check for `null`. final receiverLocal = b.addLocal(translator.topTypeNonNullable); { final nullBlock = b.block([], [translator.topTypeNonNullable]); b.local_get(nullableReceiverLocal); b.br_on_non_null(nullBlock); - // Throw `NoSuchMethodError`. Normally this needs to happen via instance - // invocation of `noSuchMethod` (done in [_callNoSuchMethod]), but we don't - // have a `Null` class in dart2wasm so we throw directly. b.local_get(nullableReceiverLocal); - createInvocationObject(translator, b, callerShape.name, typeArgsLocal, - positionalArgsLocal, namedArgsLocal); - + for (int i = 0; i < callerShape.totalArgumentCount; ++i) { + b.local_get(function.locals[1 + i]); + } + translator.callFunction( + translator.functions.getInvocationCreatorStub(callerShape), b); translator.callReference( translator.noSuchMethodErrorThrowWithInvocation.reference, b); b.unreachable(); @@ -416,6 +307,8 @@ class _DynamicForwarderCodeGenerator extends CodeGenerator { // Continuation of this block calls `noSuchMethod` on the receiver. final noSuchMethodBlock = b.block(); + // Step 1) Look through all possible targets that have the dynamic selector + // as a method. final methodSelectors = translator.dispatchTable.dynamicMethodSelectors(callerShape.name); for (final selector in methodSelectors) { @@ -429,7 +322,6 @@ class _DynamicForwarderCodeGenerator extends CodeGenerator { for (final MapEntry(key: target, value: classIdRanges) in targets.entries) { final Procedure targetMember = target.asMember as Procedure; - final targetMemberParamInfo = translator.paramInfoForDirectCall(target); final targetFunction = targetMember.function; // Filter out targets that cannot match based on mismatched arguments. @@ -460,281 +352,279 @@ class _DynamicForwarderCodeGenerator extends CodeGenerator { b.end(); // classIdMatch b.local_get(receiverLocal); - b.local_get(typeArgsLocal); - - if (callerShape.positionalCount == - targetMemberParamInfo.positional.length) { - b.local_get(positionalArgsLocal); - } else { - final targetPositionals = targetFunction.positionalParameters; - for (int i = 0; i < targetMemberParamInfo.positional.length; ++i) { - if (i < callerShape.positionalCount) { - b.local_get(function.locals[1 + callerShape.typeCount + i]); - continue; - } - final defaultValue = targetMemberParamInfo.positional[i]; - // The target (a type checker function) has a signature that is - // created based on the union/merged of all members of the selector. - // - // Some implementations of the selector may have more positionals - // than others, hence the `i < targetPositionals.length`. - final defaultFunctionValue = i < targetPositionals.length - ? (targetPositionals[i].initializer as ConstantExpression?) - ?.constant - : null; - translator.constants.instantiateConstant( - b, defaultFunctionValue ?? defaultValue!, translator.topType); - } - b.array_new_fixed(translator.nullableObjectArrayType, - targetMemberParamInfo.positional.length); + for (int i = 0; i < callerShape.totalArgumentCount; ++i) { + b.local_get(function.locals[1 + i]); } - - Expression? initializerForNamedParamInMember(String paramName) { - for (int i = 0; i < targetFunction.namedParameters.length; i++) { - if (targetFunction.namedParameters[i].name == paramName) { - return targetFunction.namedParameters[i].initializer; - } - } - return null; - } - - if (targetMemberParamInfo.names.isEmpty) { - final emptyArray = translator.constants - .makeArrayOf(translator.coreTypes.objectNullableRawType, []); - translator.constants.instantiateConstant( - b, emptyArray, translator.nullableObjectArrayTypeRef); - } else { - // The type checker forwarder expects all named arguments as an array of - // values (i.e. not array of (symbol, value) pairs). - for (int i = 0; i < targetMemberParamInfo.names.length; ++i) { - final name = targetMemberParamInfo.names[i]; - final index = callerShape.named.indexOf(name); - if (index != -1) { - b.local_get(function.locals[1 + - callerShape.typeCount + - callerShape.positionalCount + - index]); - continue; - } - final defaultValue = targetMemberParamInfo.named[name]; - final defaultFunctionValue = - (initializerForNamedParamInMember(name) as ConstantExpression?) - ?.constant; - assert(defaultValue != null || defaultFunctionValue != null); - translator.constants.instantiateConstant( - b, defaultFunctionValue ?? defaultValue!, translator.topType); - } - b.array_new_fixed(translator.nullableObjectArrayType, - targetMemberParamInfo.named.length); - } - - translator.callReference(targetMember.typeCheckerReference, b); + translator.callFunction( + translator.functions.getDynamicForwarder(target, callerShape), b); b.return_(); b.end(); // classIdNoMatch } } - final getterValueLocal = b.addLocal(translator.topType); - void handleGetterSelector(SelectorInfo selector) { - for (final (:range, :target) - in selector.targets(unchecked: false).allTargetRanges) { - final targetMember = target.asMember; - // This loop checks getters and fields. Methods are considered in the - // previous loop, skip them here. - if (targetMember is Procedure && !targetMember.isGetter) { - continue; + // Step 2) The receiver does not have the dynamic selector as a method. Now + // we look through all possible getters with the dynamic selector name, + // invoke the getter and then try to call it (via closure call or `.call()`). + final getterSelectors = + translator.dispatchTable.dynamicGetterSelectors(callerShape.name); + final dynamicMainModuleGetterSelectors = translator + .dynamicMainModuleDispatchTable + ?.dynamicGetterSelectors(callerShape.name); + if (getterSelectors.isNotEmpty || + dynamicMainModuleGetterSelectors != null) { + // Load type parameter as WasmArray<_Type> + final typeArgsLocal = b.addLocal(translator.typeArrayTypeRef); + if (callerShape.typeCount == 0) { + final emptyArray = translator.constants + .makeArrayOf(translator.coreTypes.typeNonNullableRawType, []); + translator.constants + .instantiateConstant(b, emptyArray, translator.typeArrayTypeRef); + } else { + for (int i = 0; i < callerShape.typeCount; ++i) { + b.local_get(function.locals[1 + i]); } + b.array_new_fixed(translator.typeArrayType, callerShape.typeCount); + } + b.local_set(typeArgsLocal); - for (int classId = range.start; classId <= range.end; ++classId) { - b.local_get(receiverLocal); - b.loadClassId(translator, receiverLocal.type); - b.i32_const(classId); - b.i32_eq(); - b.if_(); + // Load positional parameters as WasmArray + final positionalArgsLocal = + b.addLocal(translator.nullableObjectArrayTypeRef); + if (callerShape.positionalCount == 0) { + final emptyArray = translator.constants + .makeArrayOf(translator.coreTypes.objectNullableRawType, []); + translator.constants.instantiateConstant( + b, emptyArray, translator.nullableObjectArrayTypeRef); + } else { + for (int i = 0; i < callerShape.positionalCount; ++i) { + b.local_get(function.locals[1 + callerShape.typeCount + i]); + } + b.array_new_fixed( + translator.nullableObjectArrayType, callerShape.positionalCount); + } + b.local_set(positionalArgsLocal); - final Reference targetReference; - if (targetMember is Procedure) { - assert(targetMember.isGetter); // methods are skipped above - targetReference = targetMember.reference; - } else if (targetMember is Field) { - targetReference = targetMember.getterReference; - } else { - throw '_generateMethodCode: member is not a procedure or field: $targetMember'; - } - - final w.BaseFunction targetFunction = - translator.functions.getFunction(targetReference); - - // Get field value - b.local_get(receiverLocal); - translator.convertType( - b, receiverLocal.type, targetFunction.type.inputs.first); - translator.callFunction(targetFunction, b); - translator.convertType( - b, targetFunction.type.outputs.single, translator.topType); - b.local_tee(getterValueLocal); - - // Throw `NoSuchMethodError` if the value is null - b.br_on_null(noSuchMethodBlock); - // Reuse `receiverLocal`. This also updates the `noSuchMethod` receiver - // below. - b.local_tee(receiverLocal); - - // Invoke "call" if the value is not a closure - b.loadClassId(translator, receiverLocal.type); - b.i32_const( - (translator.closureInfo.classId as AbsoluteClassId).value); - b.i32_ne(); - b.if_(); - // Value is not a closure - final callForwarder = translator - .getDynamicForwardersForModule(b.moduleBuilder) - .getDynamicInvocationForwarder(CallShape( - Name('call'), - callerShape.typeCount, - callerShape.positionalCount, - callerShape.named)) - .function; - - b.local_get(receiverLocal); - for (int i = 0; i < callerShape.typeCount; ++i) { - b.local_get(function.locals[1 + i]); - } - for (int i = 0; i < callerShape.positionalCount; ++i) { - b.local_get(function.locals[1 + callerShape.typeCount + i]); - } - for (int i = 0; i < callerShape.named.length; ++i) { - b.local_get(function.locals[ - 1 + callerShape.typeCount + callerShape.positionalCount + i]); - } - translator.callFunction(callForwarder, b); - b.return_(); - b.end(); - - // Cast the closure to `#ClosureBase` - final closureBaseType = w.RefType.def( - translator.closureLayouter.closureBaseStruct, - nullable: false); - final closureLocal = b.addLocal(closureBaseType); - b.local_get(receiverLocal); - b.ref_cast(closureBaseType); - b.local_set(closureLocal); - - generateDynamicClosureCallShapeAndTypeCheck( - translator, + // Load named parameters as WasmArray + final namedArgsLocal = b.addLocal(translator.nullableObjectArrayTypeRef); + if (callerShape.named.isEmpty) { + final emptyArray = translator.constants + .makeArrayOf(translator.coreTypes.objectNullableRawType, []); + translator.constants.instantiateConstant( + b, emptyArray, translator.nullableObjectArrayTypeRef); + } else { + for (int i = 0; i < callerShape.named.length; ++i) { + translator.constants.instantiateConstant( b, - closureLocal, - typeArgsLocal, - positionalArgsLocal, - namedArgsLocal, - noSuchMethodBlock); - if (translator.dynamicModuleSupportEnabled) { - generateDynamicClosureCallViaDynamicEntry( + translator.symbols.symbolForNamedParameter(callerShape.named[i]), + translator.topType); + b.local_get(function.locals[ + 1 + callerShape.typeCount + callerShape.positionalCount + i]); + } + b.array_new_fixed( + translator.nullableObjectArrayType, callerShape.named.length * 2); + } + b.local_set(namedArgsLocal); + + final getterValueLocal = b.addLocal(translator.topType); + void handleGetterSelector(SelectorInfo selector) { + for (final (:range, :target) + in selector.targets(unchecked: false).allTargetRanges) { + final targetMember = target.asMember; + // We only care about getters here as methods were already handled in + // the loop in `Step 1` above. + if (targetMember is Procedure && !targetMember.isGetter) { + continue; + } + + for (int classId = range.start; classId <= range.end; ++classId) { + b.local_get(receiverLocal); + b.loadClassId(translator, receiverLocal.type); + b.i32_const(classId); + b.i32_eq(); + b.if_(); + + final Reference targetReference; + if (targetMember is Procedure) { + assert(targetMember.isGetter); // methods are skipped above + targetReference = targetMember.reference; + } else if (targetMember is Field) { + targetReference = targetMember.getterReference; + } else { + throw StateError('Expected field getter or procedure getter.'); + } + + final w.BaseFunction targetFunction = + translator.functions.getFunction(targetReference); + + // Get field value + b.local_get(receiverLocal); + translator.convertType( + b, receiverLocal.type, targetFunction.type.inputs.first); + translator.callFunction(targetFunction, b); + translator.convertType( + b, targetFunction.type.outputs.single, translator.topType); + b.local_tee(getterValueLocal); + + // Throw `NoSuchMethodError` if the value is null + b.br_on_null(noSuchMethodBlock); + // Reuse `receiverLocal`. This also updates the `noSuchMethod` + // receiver below. + b.local_tee(receiverLocal); + + // Invoke "call" if the value is not a closure + b.loadClassId(translator, receiverLocal.type); + b.i32_const( + (translator.closureInfo.classId as AbsoluteClassId).value); + b.i32_ne(); + b.if_(); + // Value is not a closure + final callForwarder = translator + .getDynamicForwardersForModule(b.moduleBuilder) + .getDynamicInvocationForwarder(CallShape( + Name('call'), + callerShape.typeCount, + callerShape.positionalCount, + callerShape.named)) + .function; + + b.local_get(receiverLocal); + for (int i = 0; i < callerShape.typeCount; ++i) { + b.local_get(function.locals[1 + i]); + } + for (int i = 0; i < callerShape.positionalCount; ++i) { + b.local_get(function.locals[1 + callerShape.typeCount + i]); + } + for (int i = 0; i < callerShape.named.length; ++i) { + b.local_get(function.locals[ + 1 + callerShape.typeCount + callerShape.positionalCount + i]); + } + translator.callFunction(callForwarder, b); + b.return_(); + b.end(); + + // Cast the closure to `#ClosureBase` + final closureBaseType = w.RefType.def( + translator.closureLayouter.closureBaseStruct, + nullable: false); + final closureLocal = b.addLocal(closureBaseType); + b.local_get(receiverLocal); + b.ref_cast(closureBaseType); + b.local_set(closureLocal); + + generateDynamicClosureCallShapeAndTypeCheck( translator, b, closureLocal, typeArgsLocal, positionalArgsLocal, - namedArgsLocal); - } else { - void emitCallForTypeCount(int typeCount) { - final representation = translator.closureLayouter - .getClosureRepresentation(typeCount, - callerShape.positionalCount, callerShape.named); - if (representation == null) { - // This is a call combination that the closure layouter determined - // cannot occur in the program (it means the shape&type checks - // we already performed earlier must have thrown an NSM error - // and we cannot get here). - b.unreachable(); - return; - } - - b.local_get(closureLocal); - b.struct_get(translator.closureLayouter.closureBaseStruct, - FieldIndex.closureContext); - for (int i = 0; i < typeCount; ++i) { - b.local_get(typeArgsLocal); - b.i32_const(i); - b.array_get(translator.typeArrayType); - } - for (int i = 0; i < callerShape.positionalCount; ++i) { - b.local_get(function.locals[1 + callerShape.typeCount + i]); - } - for (int i = 0; i < callerShape.named.length; ++i) { - b.local_get(function.locals[1 + - callerShape.typeCount + - callerShape.positionalCount + - i]); - } - - final vtable = representation.vtableStruct; - final vtableIndex = representation.fieldIndexForSignature( - callerShape.positionalCount, callerShape.named); - - b.local_get(closureLocal); - b.struct_get(translator.closureLayouter.closureBaseStruct, - FieldIndex.closureVtable); - b.ref_cast(w.RefType(vtable, nullable: false)); - b.struct_get(vtable, vtableIndex); - b.call_ref(vtable.getVtableEntryAt(vtableIndex)); - } - - // The closure representation algorithm has considered dynamic - // callsites and will have therefore specialized vtable entries - // for valid call shape of dynamic closure calls. - if (callerShape.typeCount == 0) { - // The dynamic callsite has not provided type arguments but the - // target closure may be generic. The shape&type checking we - // already performed may have populated default type arguments (of - // unknown length) for the closure. - // - // So we - final maxTypeCount = - translator.closureLayouter.maxTypeArgumentCount(); - b.emitDenseTableBranch([translator.topType], maxTypeCount, () { - b.local_get(typeArgsLocal); - b.array_len(); - }, (int typeCount) { - emitCallForTypeCount(typeCount); - }, () { - b.unreachable(); - }); + namedArgsLocal, + noSuchMethodBlock); + if (translator.dynamicModuleSupportEnabled) { + generateDynamicClosureCallViaDynamicEntry( + translator, + b, + closureLocal, + typeArgsLocal, + positionalArgsLocal, + namedArgsLocal); } else { - emitCallForTypeCount(callerShape.typeCount); - } - } - b.return_(); + void emitCallForTypeCount(int typeCount) { + final representation = translator.closureLayouter + .getClosureRepresentation(typeCount, + callerShape.positionalCount, callerShape.named); + if (representation == null) { + // This is a call combination that the closure layouter + // determined cannot occur in the program (it means the + // shape&type checks we already performed earlier must + // have thrown an NSM error and we cannot get here). + b.unreachable(); + return; + } - b.end(); // class ID + b.local_get(closureLocal); + b.struct_get(translator.closureLayouter.closureBaseStruct, + FieldIndex.closureContext); + for (int i = 0; i < typeCount; ++i) { + b.local_get(typeArgsLocal); + b.i32_const(i); + b.array_get(translator.typeArrayType); + } + for (int i = 0; i < callerShape.positionalCount; ++i) { + b.local_get(function.locals[1 + callerShape.typeCount + i]); + } + for (int i = 0; i < callerShape.named.length; ++i) { + b.local_get(function.locals[1 + + callerShape.typeCount + + callerShape.positionalCount + + i]); + } + + final vtable = representation.vtableStruct; + final vtableIndex = representation.fieldIndexForSignature( + callerShape.positionalCount, callerShape.named); + + b.local_get(closureLocal); + b.struct_get(translator.closureLayouter.closureBaseStruct, + FieldIndex.closureVtable); + b.ref_cast(w.RefType(vtable, nullable: false)); + b.struct_get(vtable, vtableIndex); + b.call_ref(vtable.getVtableEntryAt(vtableIndex)); + } + + // The closure representation algorithm has considered dynamic + // callsites and will have therefore specialized vtable entries + // for valid call shape of dynamic closure calls. + if (callerShape.typeCount == 0) { + // The dynamic callsite has not provided type arguments but the + // target closure may be generic. The shape&type checking we + // already performed may have populated default type arguments + // (of unknown length) for the closure. + // + // So we branch on the number of type parameters to invoke the + // right closure entrypoint. + final maxTypeCount = + translator.closureLayouter.maxTypeArgumentCount(); + b.emitDenseTableBranch([translator.topType], maxTypeCount, () { + b.local_get(typeArgsLocal); + b.array_len(); + }, (int typeCount) { + emitCallForTypeCount(typeCount); + }, () { + b.unreachable(); + }); + } else { + emitCallForTypeCount(callerShape.typeCount); + } + } + b.return_(); + + b.end(); // class ID + } } } - } - final getterSelectors = - translator.dispatchTable.dynamicGetterSelectors(callerShape.name); - for (final selector in getterSelectors) { - handleGetterSelector(selector); - } - - final dynamicMainModuleGetterSelectors = translator - .dynamicMainModuleDispatchTable - ?.dynamicGetterSelectors(callerShape.name); - if (dynamicMainModuleGetterSelectors != null) { - for (final selector in dynamicMainModuleGetterSelectors) { + for (final selector in getterSelectors) { handleGetterSelector(selector); } + + if (dynamicMainModuleGetterSelectors != null) { + for (final selector in dynamicMainModuleGetterSelectors) { + handleGetterSelector(selector); + } + } } b.end(); // noSuchMethodBlock // Unable to find a matching member, call `noSuchMethod` - generateNoSuchMethodCall( - translator, - b, - () => b.local_get(receiverLocal), - () => createInvocationObject(translator, b, callerShape.name, - typeArgsLocal, positionalArgsLocal, namedArgsLocal)); + generateNoSuchMethodCall(translator, b, () => b.local_get(receiverLocal), + () { + for (int i = 0; i < callerShape.totalArgumentCount; ++i) { + b.local_get(function.locals[1 + i]); + } + translator.callFunction( + translator.functions.getInvocationCreatorStub(callerShape), b); + }); b.end(); } @@ -910,28 +800,6 @@ void generateDynamicClosureCallViaPositionalArgs( }); } -void createInvocationObject( - Translator translator, - w.InstructionsBuilder b, - Name memberName, - w.Local typeArgsLocal, - w.Local positionalArgsLocal, - w.Local namedArgsLocal) { - translator.constants.instantiateConstant( - b, - translator.symbols.methodSymbolFromName(memberName), - translator.classInfo[translator.symbolClass]!.nonNullableType); - - b.local_get(typeArgsLocal); - translator.callReference(translator.typeArgumentsToList.reference, b); - b.local_get(positionalArgsLocal); - translator.callReference(translator.positionalParametersToList.reference, b); - b.local_get(namedArgsLocal); - translator.callReference(translator.namedParametersToMap.reference, b); - translator.callReference( - translator.invocationGenericMethodFactory.reference, b); -} - void createGetterInvocationObject( Translator translator, w.InstructionsBuilder b, @@ -1036,10 +904,3 @@ void generateNoSuchMethodCall( useUncheckedEntry: false); } } - -class ClassIdRange { - final int start; - final int end; // inclusive - - ClassIdRange(this.start, this.end); -} diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart index 1bc7c668dfb..a269f23d5df 100644 --- a/pkg/dart2wasm/lib/functions.dart +++ b/pkg/dart2wasm/lib/functions.dart @@ -22,6 +22,11 @@ class FunctionCollector { // Wasm function for each Dart function final Map _functions = {}; + // Wasm function for each Dart function + caller shape combination. + final Map> + _dynamicForwarderFunctions = {}; + // Wasm function to create [Invocation] objects based on [CallShape]. + final Map _invocationCreatorStubs = {}; // Wasm function for each function expression and local function. final Map _lambdas = {}; // Selector IDs that are invoked via GDT. @@ -143,9 +148,7 @@ class FunctionCollector { : translator.signatureForDirectCall(target); final function = module.functions.define(ftype, getFunctionName(target)) - ..isPure = hasPureAnnotation && - !target.isTypeCheckerReference && - !target.isCheckedEntryReference; + ..isPure = hasPureAnnotation && !target.isCheckedEntryReference; if (exportName != null) module.exports.export(exportName, function); // Export the function from the main module if it is callable from @@ -164,6 +167,34 @@ class FunctionCollector { }); } + w.BaseFunction getDynamicForwarder(Reference target, CallShape shape) { + return (_dynamicForwarderFunctions[target] ??= {}).putIfAbsent(shape, () { + final module = translator.moduleForReference(target); + final ftype = makeDynamicForwarderSignature(translator, shape); + final name = getDynamicForwarderName(target, shape); + final function = module.functions.define(ftype, name); + final codegen = + DynamicForwarderCodeGenerator(translator, ftype, target, shape); + translator.compilationQueue + .add(AstCompilationTask(function, codegen, target)); + return function; + }); + } + + w.BaseFunction getInvocationCreatorStub(CallShape shape) { + return _invocationCreatorStubs.putIfAbsent(shape, () { + final module = translator.isDynamicSubmodule + ? translator.dynamicSubmodule + : translator.mainModule; + final ftype = makeInvocationCreatorSignature(translator, shape); + final name = getInvocationCreatorStubName(shape); + final function = module.functions.define(ftype, name); + final codegen = InvocationCreationStubGenerator(translator, shape); + translator.compilationQueue.add(CompilationTask(function, codegen)); + return function; + }); + } + w.BaseFunction _importFunctionToDynamicSubmodule(Reference target) { assert(translator.isDynamicSubmodule); @@ -217,14 +248,6 @@ class FunctionCollector { return makeFunctionTypeForBody(translator, member); } - if (target.isTypeCheckerReference) { - if (member is Field || (member is Procedure && member.isSetter)) { - return translator.dynamicSetForwarderFunctionType; - } else { - return translator.dynamicInvocationForwarderFunctionType; - } - } - if (target.isTearOffReference) { assert(!translator.dispatchTable .selectorForTarget(target) @@ -270,14 +293,6 @@ class FunctionCollector { memberName = memberName.substring(0, memberName.length - 1); } - if (target.isTypeCheckerReference) { - if (member is Field || (member is Procedure && member.isSetter)) { - return '$memberName setter type checker'; - } else { - return '$memberName invocation type checker'; - } - } - if (member is Field) { if (target.isImplicitSetter) { return '$memberName= implicit setter'; @@ -299,6 +314,16 @@ class FunctionCollector { } } + String getDynamicForwarderName(Reference target, CallShape shape) { + final member = target.asMember; + final memberName = member.toString(); + return '$memberName ($shape)'; + } + + String getInvocationCreatorStubName(CallShape shape) { + return 'Invocation creator ($shape)'; + } + void recordSelectorUse(SelectorInfo selector, bool useUncheckedEntry) { final set = useUncheckedEntry ? _calledUncheckedSelectors : _calledSelectors; @@ -609,6 +634,31 @@ w.FunctionType makeFunctionTypeForBody(Translator translator, Member member) { return translator.typesBuilder.defineFunction(inputs, outputs); } +w.FunctionType makeDynamicForwarderSignature( + Translator translator, CallShape shape) { + return translator.typesBuilder.defineFunction([ + translator.topTypeNonNullable, + for (int i = 0; i < shape.typeCount; ++i) + translator.translateType(translator.types.typeType), + for (int i = 0; i < shape.positionalCount; ++i) translator.topType, + for (int i = 0; i < shape.named.length; ++i) translator.topType, + ], [ + translator.topType + ]); +} + +w.FunctionType makeInvocationCreatorSignature( + Translator translator, CallShape shape) { + return translator.typesBuilder.defineFunction([ + for (int i = 0; i < shape.typeCount; ++i) + translator.translateType(translator.types.typeType), + for (int i = 0; i < shape.positionalCount; ++i) translator.topType, + for (int i = 0; i < shape.named.length; ++i) translator.topType, + ], [ + translator.invocationType, + ]); +} + w.FunctionType _makeFunctionType( Translator translator, Reference target, w.ValueType? receiverType, {bool isImportOrExport = false}) { @@ -652,3 +702,70 @@ w.FunctionType _makeFunctionType( return translator.typesBuilder.defineFunction(inputs, outputs); } + +class CallShape { + final Name name; + final int typeCount; + final int positionalCount; + final List named; + + CallShape(this.name, this.typeCount, this.positionalCount, this.named); + + int get totalArgumentCount => typeCount + positionalCount + named.length; + + bool matchesTarget(FunctionNode target) { + if (typeCount != target.typeParameters.length && typeCount != 0) { + return false; + } + if (positionalCount < target.requiredParameterCount || + positionalCount > target.positionalParameters.length) { + return false; + } + final namedParams = target.namedParameters; + for (final name in namedParams) { + if (name.isRequired && !named.contains(name.name)) { + return false; + } + } + for (final name in named) { + if (!namedParams.any((n) => n.name == name)) { + return false; + } + } + return true; + } + + @override + int get hashCode => + Object.hash(name, typeCount, positionalCount, Object.hashAll(named)); + + @override + bool operator ==(other) { + if (other is! CallShape) return false; + if (name != other.name) return false; + if (typeCount != other.typeCount) return false; + if (named.length != other.named.length) return false; + for (int i = 0; i < named.length; ++i) { + if (named[i] != other.named[i]) { + return false; + } + } + return true; + } + + @override + String toString() { + final sb = StringBuffer(); + sb.write('$name'); + if (typeCount != 0) { + sb.write(' types:$typeCount'); + } + if (positionalCount != 0) { + sb.write(' pos:$positionalCount'); + } + if (named.isNotEmpty) { + sb.write(' names:${named.join('-')}'); + } + return 'CallShape($sb)'; + } +} diff --git a/pkg/dart2wasm/lib/modules.dart b/pkg/dart2wasm/lib/modules.dart index a30604f36ee..4782d10bbbc 100644 --- a/pkg/dart2wasm/lib/modules.dart +++ b/pkg/dart2wasm/lib/modules.dart @@ -135,8 +135,7 @@ class ModuleOutputData { } } else { node as Procedure; - if (reference.isTypeCheckerReference || - reference.isCheckedEntryReference || + if (reference.isCheckedEntryReference || reference.isUncheckedEntryReference || reference.isBodyReference || reference.isTearOffReference) { diff --git a/pkg/dart2wasm/lib/reference_extensions.dart b/pkg/dart2wasm/lib/reference_extensions.dart index eb79d7a3f52..433760a2eb9 100644 --- a/pkg/dart2wasm/lib/reference_extensions.dart +++ b/pkg/dart2wasm/lib/reference_extensions.dart @@ -18,8 +18,7 @@ extension GetterSetterReference on Reference { if (member.setterReference == this) return true; if (member.isInstanceMember) { return _isUncheckedEntrySetterReference || - _isCheckedEntrySetterReference || - isTypeCheckerReference; + _isCheckedEntrySetterReference; } } return false; @@ -44,7 +43,6 @@ extension GetterSetterReference on Reference { // Use Expandos to avoid keeping the procedure alive. final Expando _staticFieldInitializerReference = Expando(); final Expando _tearOffReference = Expando(); -final Expando _typeCheckerReference = Expando(); final Expando _checkedEntryReferences = Expando(); final Expando _uncheckedEntryReferences = Expando(); final Expando _bodyReferences = Expando(); @@ -58,9 +56,6 @@ extension CustomReference on Member { Reference get tearOffReference => _tearOffReference[this] ??= Reference()..node = this; - Reference get typeCheckerReference => - _typeCheckerReference[this] ??= Reference()..node = this; - Reference get checkedEntryReference { assert(_memberCanHaveMultipleEntryPoints(this)); return _checkedEntryReferences[this] ??= Reference()..node = this; @@ -89,8 +84,6 @@ extension IsCustomReference on Reference { bool get isTearOffReference => _tearOffReference[asMember] == this; - bool get isTypeCheckerReference => _typeCheckerReference[asMember] == this; - bool get isCheckedEntryReference => _checkedEntryReferences[asMember] == this; bool get _isCheckedEntrySetterReference => diff --git a/pkg/dart2wasm/lib/serialization.dart b/pkg/dart2wasm/lib/serialization.dart index a58c60c9888..810681dd407 100644 --- a/pkg/dart2wasm/lib/serialization.dart +++ b/pkg/dart2wasm/lib/serialization.dart @@ -38,13 +38,12 @@ class _EntityToIdMapper { if (reference.isTearOffReference) return 2; if (reference.isConstructorBodyReference) return 3; if (reference.isInitializerReference) return 4; - if (reference.isTypeCheckerReference) return 5; - if (reference.isCheckedEntryReference) return 6; - if (reference.isUncheckedEntryReference) return 7; - if (reference.isBodyReference) return 8; - if (reference.isStaticFieldInitializer) return 9; + if (reference.isCheckedEntryReference) return 5; + if (reference.isUncheckedEntryReference) return 6; + if (reference.isBodyReference) return 7; + if (reference.isStaticFieldInitializer) return 8; assert(reference == reference.asMember.reference); - return 10; + return 9; } } @@ -85,12 +84,11 @@ class _IdToEntityMapper { if (flag == 2) return (member as Procedure).tearOffReference; if (flag == 3) return (member as Constructor).constructorBodyReference; if (flag == 4) return (member as Constructor).initializerReference; - if (flag == 5) return member.typeCheckerReference; - if (flag == 6) return member.checkedEntryReference; - if (flag == 7) return member.uncheckedEntryReference; - if (flag == 8) return member.bodyReference; - if (flag == 9) return (member as Field).staticFieldInitializer; - assert(flag == 10); + if (flag == 5) return member.checkedEntryReference; + if (flag == 6) return member.uncheckedEntryReference; + if (flag == 7) return member.bodyReference; + if (flag == 8) return (member as Field).staticFieldInitializer; + assert(flag == 9); return member.reference; } } diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 9f196f0c99a..689d869056b 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart @@ -325,6 +325,11 @@ class Translator with KernelNodes { // The wasm type used to hold values of `String?` late final w.RefType stringTypeNullable = stringType.withNullability(true); + // The wasm type used to hold values of `Invocation` + late final w.RefType invocationType = translateType( + InterfaceType(coreTypes.invocationClass, Nullability.nonNullable)) + as w.RefType; + final Map _partialInstantiators = {}; PartialInstantiator getPartialInstantiatorForModule(w.ModuleBuilder module) { return _partialInstantiators[module] ??= PartialInstantiator(this, module); @@ -415,45 +420,6 @@ class Translator with KernelNodes { topType, ]); - /// Type of a dynamic invocation forwarder function. - late final w.FunctionType dynamicInvocationForwarderFunctionType = - typesBuilder.defineFunction([ - // Receiver - topTypeNonNullable, - - // Type arguments - typeArrayTypeRef, - - // Positional arguments - nullableObjectArrayTypeRef, - - // Named arguments, represented as array of symbol and object pairs - nullableObjectArrayTypeRef, - ], [ - topType, - ]); - - /// Type of a dynamic get forwarder function. - late final w.FunctionType dynamicGetForwarderFunctionType = - typesBuilder.defineFunction([ - // Receiver - topTypeNonNullable, - ], [ - topType, - ]); - - /// Type of a dynamic set forwarder function. - late final w.FunctionType dynamicSetForwarderFunctionType = - typesBuilder.defineFunction([ - // Receiver - topTypeNonNullable, - - // Positional argument - topType, - ], [ - topType, - ]); - // Module predicates and helpers final ModuleOutputData _moduleOutputData; Iterable get modules => _builderToOutput.keys; @@ -1702,9 +1668,7 @@ class Translator with KernelNodes { } w.FunctionType _signatureForModule(Reference target, DispatchTable? table) { - if (table != null && - !target.isBodyReference && - !target.isTypeCheckerReference) { + if (table != null && !target.isBodyReference) { final selector = table.selectorForTarget(target); if (selector.containsTarget(target) || selector.isDynamicSubmoduleOverridable) { @@ -2327,16 +2291,13 @@ class AstCompilationTask extends CompilationTask { if (exportName != null) { header = "$header (exported as $exportName)"; } - if (reference.isTypeCheckerReference) { - header = "$header (type checker)"; - } print(header); print(function.type); print(member.function ?.computeFunctionType(Nullability.nonNullable) .toStringInternal()); } - if (printKernel && !reference.isTypeCheckerReference) { + if (printKernel) { if (member is Constructor) { Class cls = member.enclosingClass; for (Field field in cls.fields) { @@ -2355,9 +2316,7 @@ class AstCompilationTask extends CompilationTask { if (!printWasm) print(""); } - final codeGen = getMemberCodeGenerator(translator, function, reference); - codeGen.generate(function.body, function.locals.toList(), null); - + _codeGenerator.generate(function.body, function.locals.toList(), null); if (printWasm) { print(function.body.trace); } diff --git a/pkg/dart2wasm/test/ir_tests/dynamic_call.wat b/pkg/dart2wasm/test/ir_tests/dynamic_call.wat index 56eb8dec151..2ae9a20a3dc 100644 --- a/pkg/dart2wasm/test/ir_tests/dynamic_call.wat +++ b/pkg/dart2wasm/test/ir_tests/dynamic_call.wat @@ -2,8 +2,6 @@ (type $#Top (struct (field $field0 i32))) (type $Array (array (field (mut (ref null $#Top))))) - (type $Array (array (field (mut i32)))) - (type $Array<_Type> (array (field (mut (ref $_Type))))) (type $BoxedBool (sub final $#Top (struct (field $field0 i32) (field $value (mut i32))))) @@ -31,24 +29,6 @@ (field $field2 (ref $_Type)) (field $_length (mut i64)) (field $_data (mut (ref $Array)))))) - (type $_DefaultMap&_HashFieldBase&MapMixin (sub final $_HashFieldBase (struct - (field $field0 i32) - (field $field1 (mut i32)) - (field $_index (mut (ref $Array))) - (field $_hashMask (mut i64)) - (field $_data (mut (ref $Array))) - (field $_usedData (mut i64)) - (field $_deletedKeys (mut i64)) - (field $field7 (ref $_Type)) - (field $field8 (ref $_Type))))) - (type $_HashFieldBase (sub $Object (struct - (field $field0 i32) - (field $field1 (mut i32)) - (field $_index (mut (ref $Array))) - (field $_hashMask (mut i64)) - (field $_data (mut (ref $Array))) - (field $_usedData (mut i64)) - (field $_deletedKeys (mut i64))))) (type $_Invocation (sub final $Object (struct (field $field0 i32) (field $field1 (mut i32)) @@ -59,32 +39,6 @@ (field $field0 i32) (field $field1 (mut i32)) (field $isDeclaredNullable i32)))) - (global $.a (import "" "a") (ref extern)) - (global $.toString (import "" "toString") (ref extern)) - (global $"SymbolConstant(#a)" (ref $Symbol) - (i32.const 69) - (i32.const 0) - (global.get $"\"a\"") - (struct.new $Symbol)) - (global $"SymbolConstant(#toString)" (ref $Symbol) - (i32.const 69) - (i32.const 0) - (global.get $"\"toString\"") - (struct.new $Symbol)) - (global $"WasmArray[0]" (ref $Array) - (array.new_fixed $Array 0)) - (global $"WasmArray[0]" (ref $Array<_Type>) - (array.new_fixed $Array<_Type> 0)) - (global $"\"a\"" (ref $JSExternWrapper) - (i32.const 106) - (i32.const 0) - (global.get $.a) - (struct.new $JSExternWrapper)) - (global $"\"toString\"" (ref $JSExternWrapper) - (i32.const 106) - (i32.const 0) - (global.get $.toString) - (struct.new $JSExternWrapper)) (global $1 (ref $BoxedInt) (i32.const 65) (i64.const 1) @@ -93,95 +47,62 @@ (i32.const 3) (i32.const 1) (struct.new $BoxedBool)) - (func $Bar.toString invocation type checker (param $this (ref $#Top)) (param $var0 (ref $Array<_Type>)) (param $var1 (ref $Array)) (param $var2 (ref $Array)) (result (ref null $#Top)) <...>) - (func $"Dynamic method forwarder for \"CallShape(toString, 0, 0, a)\"" (param $var0 (ref null $#Top)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) - (local $var2 (ref $Array<_Type>)) - (local $var3 (ref $Array)) - (local $var4 (ref $Array)) - (local $var5 (ref $#Top)) - (local $var6 i32) - (local $var7 (ref null $#Top)) - global.get $"WasmArray[0]" - local.set $var2 - global.get $"WasmArray[0]" - local.set $var3 - global.get $"SymbolConstant(#a)" - local.get $var1 - array.new_fixed $Array 2 - local.set $var4 + (func $Bar.toString (CallShape(toString names:a)) (param $this (ref $#Top)) (param $var0 (ref null $#Top)) (result (ref null $#Top)) <...>) + (func $"Dynamic method forwarder for \"CallShape(toString names:a)\"" (param $var0 (ref null $#Top)) (param $var1 (ref null $#Top)) (result (ref null $#Top)) + (local $var2 (ref $#Top)) + (local $var3 i32) block $label0 (result (ref $#Top)) local.get $var0 br_on_non_null $label0 local.get $var0 - global.get $"SymbolConstant(#toString)" - local.get $var2 - call $_typeArgumentsToList - local.get $var3 - call $_positionalParametersToList - local.get $var4 - call $_namedParametersToMap - call $"new Invocation.genericMethod" + local.get $var1 + call $"Invocation creator (CallShape(toString names:a))" call $NoSuchMethodError._throwWithInvocation unreachable end $label0 - local.set $var5 - local.get $var5 + local.set $var2 + local.get $var2 struct.get $#Top $field0 - local.set $var6 + local.set $var3 block $label1 block $label2 block $label3 - local.get $var6 + local.get $var3 i32.const 108 i32.eq br_if $label3 br $label2 end $label3 - local.get $var5 local.get $var2 - local.get $var3 local.get $var1 - array.new_fixed $Array 1 - call $"Bar.toString invocation type checker" + call $"Bar.toString (CallShape(toString names:a))" return end $label2 block $label4 block $label5 - local.get $var6 + local.get $var3 i32.const 109 i32.eq br_if $label5 br $label4 end $label5 - local.get $var5 local.get $var2 - local.get $var3 local.get $var1 - array.new_fixed $Array 1 - call $"Foo.toString invocation type checker" + call $"Foo.toString (CallShape(toString names:a))" return end $label4 end - local.get $var5 - global.get $"SymbolConstant(#toString)" local.get $var2 - call $_typeArgumentsToList - local.get $var3 - call $_positionalParametersToList - local.get $var4 - call $_namedParametersToMap - call $"new Invocation.genericMethod" + local.get $var1 + call $"Invocation creator (CallShape(toString names:a))" call $Object.noSuchMethod ) - (func $Foo.toString invocation type checker (param $this (ref $#Top)) (param $var0 (ref $Array<_Type>)) (param $var1 (ref $Array)) (param $var2 (ref $Array)) (result (ref null $#Top)) <...>) - (func $new Invocation.genericMethod (param $memberName (ref $Symbol)) (param $typeArguments (ref null $Object)) (param $positionalArguments (ref null $Object)) (param $namedArguments (ref null $Object)) (result (ref $_Invocation)) <...>) + (func $Foo.toString (CallShape(toString names:a)) (param $this (ref $#Top)) (param $var0 (ref null $#Top)) (result (ref null $#Top)) <...>) + (func $Invocation creator (CallShape(toString names:a)) (param $var0 (ref null $#Top)) (result (ref $_Invocation)) <...>) (func $Bar (result (ref $Object)) <...>) (func $Foo (result (ref $Object)) <...>) (func $NoSuchMethodError._throwWithInvocation (param $receiver (ref null $#Top)) (param $invocation (ref $_Invocation)) (result (ref none)) <...>) (func $Object.noSuchMethod (param $this (ref $#Top)) (param $invocation (ref $_Invocation)) (result (ref null $#Top)) <...>) - (func $_namedParametersToMap (param $namedArguments (ref $Array)) (result (ref $_DefaultMap&_HashFieldBase&MapMixin)) <...>) - (func $_positionalParametersToList (param $positional (ref $Array)) (result (ref $WasmListBase)) <...>) - (func $_typeArgumentsToList (param $typeArgs (ref $Array<_Type>)) (result (ref $WasmListBase)) <...>) (func $confuse (param $a (ref null $#Top)) (result (ref null $#Top)) <...>) (func $main (result (ref null $#Top)) (local $var0 (ref null $#Top)) @@ -191,7 +112,7 @@ global.get $true local.set $var0 local.get $var0 - call $"Dynamic method forwarder for \"CallShape(toString, 0, 0, a)\"" + call $"Dynamic method forwarder for \"CallShape(toString names:a)\"" call $print drop call $Bar @@ -199,7 +120,7 @@ global.get $1 local.set $var1 local.get $var1 - call $"Dynamic method forwarder for \"CallShape(toString, 0, 0, a)\"" + call $"Dynamic method forwarder for \"CallShape(toString names:a)\"" call $print drop ref.null none diff --git a/tests/web/wasm/dynamic_calls_test.dart b/tests/web/wasm/dynamic_calls_test.dart new file mode 100644 index 00000000000..77e8265149d --- /dev/null +++ b/tests/web/wasm/dynamic_calls_test.dart @@ -0,0 +1,102 @@ +// Copyright (c) 2026, 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:expect/expect.dart'; + +import '' deferred as D; + +void main() async { + await D.loadLibrary(); + + final list = D.getObjects(); + final a = list[int.parse('0')]; + final b = list[int.parse('1')]; + final c = list[int.parse('2')]; + final d = list[int.parse('3')]; + + // Successfull dynamic method/getter/setter call + Expect.equals(52, a.foo(10)); + Expect.equals(42, a.fooGetter); + a.fooSetter = 100; + Expect.equals(100, a.fooGetter); + + // Successfull dynamic tearoff + dynamic closure call. + final fooTearOff = a.foo; + Expect.equals(110, fooTearOff(10)); + + // Successful call via field. + Expect.equals(20, b.foo(10)); + + // Argument type check errors + Expect.throws(() => a.foo('')); + Expect.throws(() => a.fooSetter = ''); + + // User-defined noSuchMethod handler + Expect.equals(1, c.foo(1)); + Expect.equals(2, c.bar(1, 2)); + Expect.equals(3, c.baz); + c.buz = 2; + Expect.equals(4, D.cCounter); + + // Default noSuchMethod handler + Expect.throwsNoSuchMethodError(() => d.foo(10)); + Expect.throwsNoSuchMethodError(() => d.fooGetter); + Expect.throwsNoSuchMethodError(() => d.fooSetter = 10); + + final e = list[int.parse('4')]; + + // Optional positional and type args + Expect.equals("$Object 10 1 2", e.foo(10)); + Expect.equals("$Object 10 20 2", e.foo(10, 20)); + Expect.equals("$Object 10 20 30", e.foo(10, 20, 30)); + Expect.equals("$String hi 1 2", e.foo("hi")); + Expect.equals("$String hi 20 30", e.foo("hi", 20, 30)); + + // Optional named and type args + Expect.equals("$Object $dynamic 10 null 3", e.bar(10)); + Expect.equals("$int $String 10 hi 3", e.bar(10, y: "hi")); + Expect.equals( + "$int $String 10 hi 40", + e.bar(10, y: "hi", z: 40), + ); + Expect.equals("$Object $dynamic 10 null 40", e.bar(10, z: 40)); + + // NoSuchMethod for wrong shape + Expect.throwsNoSuchMethodError(() => e.foo()); + Expect.throwsNoSuchMethodError(() => e.foo(1, 2, 3, 4)); + Expect.throwsNoSuchMethodError(() => e.bar(10, unknown: 1)); +} + +List getObjects() => [ + A(), + B((int x) => x * 2), + C(), + Object(), + OptionalArgs(), +]; + +class A { + int _value = 42; + + int foo(int x) => x + _value; + int get fooGetter => _value; + set fooSetter(int x) => _value = x; +} + +class B { + final Function foo; + B(this.foo); +} + +int cCounter = 0; + +class C { + @override + noSuchMethod(Invocation i) => ++cCounter; +} + +class OptionalArgs { + String foo(T x, [int y = 1, int z = 2]) => "$T $x $y $z"; + String bar(T x, {U? y, int z = 3}) => "$T $U $x $y $z"; +}