| // 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:kernel/ast.dart'; |
| import 'package:wasm_builder/wasm_builder.dart' as w; |
| |
| import 'class_info.dart'; |
| import 'closures.dart'; |
| import 'code_generator.dart' show CallTarget, CodeGenerator, MacroAssembler; |
| import 'dispatch_table.dart'; |
| import 'reference_extensions.dart'; |
| import 'translator.dart'; |
| |
| /// Stores forwarders for dynamic gets, sets, and invocations. See [Forwarder] |
| /// for details. Each module will contain its own forwarders for the names |
| /// invoked from it. |
| class DynamicForwarders { |
| final Translator translator; |
| final w.ModuleBuilder callingModule; |
| |
| final Map<Name, CallTarget> _getterForwarderOfName = {}; |
| final Map<Name, CallTarget> _setterForwarderOfName = {}; |
| final Map<CallShape, CallTarget> _methodForwarderOfName = {}; |
| |
| DynamicForwarders(this.translator, this.callingModule); |
| |
| CallTarget getDynamicGetForwarder(Name name) => |
| _getterForwarderOfName[name] ??= _DynamicForwarderCallTarget(translator, |
| _ForwarderKind.Getter, CallShape(name, 0, 0, []), callingModule); |
| |
| CallTarget getDynamicSetForwarder(Name name) => |
| _setterForwarderOfName[name] ??= _DynamicForwarderCallTarget(translator, |
| _ForwarderKind.Setter, CallShape(name, 0, 1, []), callingModule); |
| |
| CallTarget getDynamicInvocationForwarder(CallShape shape) { |
| // Add Wasm function to the map before generating the forwarder code, to |
| // allow recursive calls in the "call" forwarder. |
| var forwarder = _methodForwarderOfName[shape]; |
| if (forwarder == null) { |
| forwarder = _DynamicForwarderCallTarget( |
| translator, _ForwarderKind.Method, shape, callingModule); |
| _methodForwarderOfName[shape] = forwarder; |
| } |
| return forwarder; |
| } |
| } |
| |
| class CallShape { |
| final Name name; |
| final int typeCount; |
| final int positionalCount; |
| final List<String> 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; |
| final CallShape callShape; |
| final w.ModuleBuilder callingModule; |
| |
| _DynamicForwarderCallTarget( |
| this.translator, this._kind, this.callShape, this.callingModule) |
| : assert(!translator.isDynamicSubmodule || |
| (callShape.name.text == 'call' && _kind == _ForwarderKind.Method)), |
| super(_functionType(translator, _kind, callShape)); |
| |
| static w.FunctionType _functionType( |
| Translator translator, _ForwarderKind kind, CallShape shape) { |
| return switch (kind) { |
| _ForwarderKind.Getter => translator.typesBuilder.defineFunction([ |
| translator.topType, |
| ], [ |
| translator.topType |
| ]), |
| _ForwarderKind.Setter => translator.typesBuilder.defineFunction([ |
| translator.topType, |
| translator.topType, |
| ], [ |
| translator.topType |
| ]), |
| _ForwarderKind.Method => translator.typesBuilder.defineFunction([ |
| translator.topType, |
| for (int i = 0; i < shape.typeCount; ++i) |
| translator.translateType(translator.typeType), |
| for (int i = 0; i < shape.positionalCount; ++i) translator.topType, |
| for (int i = 0; i < shape.named.length; ++i) translator.topType, |
| ], [ |
| translator.topType |
| ]), |
| }; |
| } |
| |
| @override |
| String get name => 'Dynamic $_kind forwarder for "$callShape"'; |
| |
| @override |
| bool get supportsInlining => false; |
| |
| @override |
| late final w.BaseFunction function = (() { |
| final function = callingModule.functions.define(signature, name); |
| final forwarder = |
| _DynamicForwarderCodeGenerator(translator, _kind, callShape, function); |
| translator.compilationQueue.add(CompilationTask(function, forwarder)); |
| return function; |
| })(); |
| } |
| |
| /// A function that "forwards" a dynamic get, set, or invocation to the right |
| /// type checking member. |
| /// |
| /// A forwarder function takes 4 arguments: |
| /// |
| /// - The receiver of the dynamic get, set, or invocation |
| /// - A Dart list for type arguments (empty in gets and sets) |
| /// - A Dart list of positional arguments (empty in gets) |
| /// - A Dart list of named arguments (empty in gets and sets) |
| /// |
| /// It compares the receiver class ID with the IDs of classes with a matching |
| /// member name ([memberName]). When it finds a match, it compares the passed |
| /// arguments with expected parameters, adjusts parameter lists with default |
| /// values, and calls the matching member's type checker method, which type |
| /// checks the passed arguments before calling the actual member. |
| /// |
| /// A forwarder calls `noSuchMethod` on the receiver when a matching member is |
| /// not found, or the passed arguments do not match the expected parameters of |
| /// the member. |
| class _DynamicForwarderCodeGenerator extends CodeGenerator { |
| final Translator translator; |
| final _ForwarderKind _kind; |
| final CallShape callerShape; |
| final w.FunctionBuilder function; |
| |
| _DynamicForwarderCodeGenerator( |
| this.translator, this._kind, this.callerShape, this.function); |
| |
| @override |
| void generate(w.InstructionsBuilder b, List<w.Local> paramLocals, |
| w.Label? returnLabel) { |
| assert(returnLabel == null); // no inlining support atm. |
| switch (_kind) { |
| case _ForwarderKind.Getter: |
| _generateGetterCode(translator); |
| break; |
| |
| case _ForwarderKind.Setter: |
| _generateSetterCode(translator); |
| break; |
| |
| case _ForwarderKind.Method: |
| _generateMethodCode(translator); |
| break; |
| } |
| } |
| |
| void _generateGetterCode(Translator translator) { |
| final selectors = |
| translator.dispatchTable.dynamicGetterSelectors(callerShape.name); |
| final ranges = selectors |
| .expand((selector) => selector |
| .targets(unchecked: false) |
| .targetRanges |
| .map((r) => (range: r.range, value: r.target))) |
| .toList(); |
| ranges.sort((a, b) => a.range.start.compareTo(b.range.start)); |
| |
| final nullableReceiverLocal = function.locals[0]; |
| final outputs = function.type.outputs; |
| final b = function.body; |
| |
| // 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); |
| createGetterInvocationObject(translator, b, callerShape.name); |
| |
| translator.callReference( |
| translator.noSuchMethodErrorThrowWithInvocation.reference, b); |
| b.unreachable(); |
| b.end(); // nullBlock |
| b.local_set(receiverLocal); |
| } |
| |
| b.local_get(receiverLocal); |
| b.loadClassId(translator, receiverLocal.type); |
| b.classIdSearch(ranges, outputs, (Reference target) { |
| final targetMember = target.asMember; |
| final Reference targetReference; |
| if (targetMember is Procedure) { |
| targetReference = targetMember.isGetter |
| ? targetMember.reference |
| : targetMember.tearOffReference; |
| } else if (targetMember is Field) { |
| targetReference = targetMember.getterReference; |
| } else { |
| throw '_generateGetterCode: member is not a procedure or field: $targetMember'; |
| } |
| |
| final w.BaseFunction targetFunction = |
| translator.functions.getFunction(targetReference); |
| b.local_get(receiverLocal); |
| translator.convertType( |
| b, receiverLocal.type, targetFunction.type.inputs.first); |
| translator.callFunction(targetFunction, b); |
| // Box return value if needed |
| translator.convertType( |
| b, targetFunction.type.outputs.single, outputs.single); |
| }, () { |
| generateNoSuchMethodCall(translator, b, () => b.local_get(receiverLocal), |
| () => createGetterInvocationObject(translator, b, callerShape.name)); |
| }); |
| |
| b.return_(); |
| b.end(); |
| } |
| |
| void _generateSetterCode(Translator translator) { |
| final selectors = |
| translator.dispatchTable.dynamicSetterSelectors(callerShape.name); |
| final ranges = selectors |
| .expand((selector) => selector |
| .targets(unchecked: false) |
| .targetRanges |
| .map((r) => (range: r.range, value: r.target))) |
| .toList(); |
| ranges.sort((a, b) => a.range.start.compareTo(b.range.start)); |
| |
| final nullableReceiverLocal = function.locals[0]; |
| final positionalArgLocal = function.locals[1]; |
| |
| final b = function.body; |
| |
| // 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); |
| createSetterInvocationObject( |
| translator, b, callerShape.name, positionalArgLocal); |
| |
| translator.callReference( |
| translator.noSuchMethodErrorThrowWithInvocation.reference, b); |
| b.unreachable(); |
| b.end(); // nullBlock |
| b.local_set(receiverLocal); |
| } |
| |
| 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); |
| }, () { |
| generateNoSuchMethodCall( |
| translator, |
| b, |
| () => b.local_get(receiverLocal), |
| () => createSetterInvocationObject( |
| translator, b, callerShape.name, positionalArgLocal)); |
| |
| b.drop(); // drop noSuchMethod return value |
| b.local_get(positionalArgLocal); |
| }); |
| |
| b.return_(); |
| b.end(); |
| } |
| |
| 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<Object?> |
| 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<Object?> |
| 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); |
| |
| translator.callReference( |
| translator.noSuchMethodErrorThrowWithInvocation.reference, b); |
| b.unreachable(); |
| |
| b.end(); // nullBlock |
| b.local_set(receiverLocal); |
| } |
| |
| final classIdLocal = b.addLocal(w.NumType.i32); |
| |
| // Continuation of this block calls `noSuchMethod` on the receiver. |
| final noSuchMethodBlock = b.block(); |
| |
| final methodSelectors = |
| translator.dispatchTable.dynamicMethodSelectors(callerShape.name); |
| for (final selector in methodSelectors) { |
| // Accumulates all class ID ranges that have the same target. |
| final Map<Reference, List<Range>> targets = {}; |
| for (final (:range, :target) |
| in selector.targets(unchecked: false).targetRanges) { |
| targets.putIfAbsent(target, () => []).add(range); |
| } |
| |
| 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. |
| if (!callerShape.matchesTarget(targetFunction)) { |
| continue; |
| } |
| |
| b.local_get(receiverLocal); |
| b.loadClassId(translator, receiverLocal.type); |
| b.local_set(classIdLocal); |
| |
| final classIdNoMatch = b.block(); |
| final classIdMatch = b.block(); |
| |
| for (Range classIdRange in classIdRanges) { |
| if (classIdRange.length == 1) { |
| b.local_get(classIdLocal); |
| b.i32_const(classIdRange.start); |
| b.i32_eq(); |
| b.br_if(classIdMatch); |
| } else { |
| b.local_get(classIdLocal); |
| b.i32_const(classIdRange.start); |
| b.i32_sub(); |
| b.i32_const(classIdRange.length); |
| b.i32_lt_u(); |
| b.br_if(classIdMatch); |
| } |
| } |
| |
| b.br(classIdNoMatch); |
| 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); |
| } |
| |
| 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); |
| b.return_(); |
| b.end(); // classIdNoMatch |
| } |
| } |
| |
| final getterValueLocal = b.addLocal(translator.topType); |
| void handleGetterSelector(SelectorInfo selector) { |
| for (final (:range, :target) |
| in selector.targets(unchecked: false).targetRanges) { |
| 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; |
| } |
| |
| 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 '_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, |
| b, |
| closureLocal, |
| typeArgsLocal, |
| positionalArgsLocal, |
| namedArgsLocal, |
| noSuchMethodBlock); |
| if (translator.dynamicModuleSupportEnabled) { |
| generateDynamicClosureCallViaDynamicEntry( |
| 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(); |
| }); |
| } 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) { |
| 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)); |
| |
| b.end(); |
| } |
| } |
| |
| enum _ForwarderKind { |
| Getter, |
| Setter, |
| Method; |
| |
| @override |
| String toString() { |
| return switch (this) { |
| _ForwarderKind.Getter => "get", |
| _ForwarderKind.Setter => "set", |
| _ForwarderKind.Method => "method" |
| }; |
| } |
| } |
| |
| /// Generate code that checks shape and type of the closure. |
| /// |
| /// [closureLocal] should be a local of type `ref #ClosureBase` containing a |
| /// closure value. |
| /// |
| /// * [typeArgsLocal] is a `WasmArray<_Type>` |
| /// * [posArgsLocal] is a `WasmArray<Object?>` |
| /// * [namedArgsLocal] is a `WasmArray<Object?>` - (symbol, value) pairs |
| /// |
| /// Will update `typeArgsLocal` with default type arguments (if needed). |
| /// |
| /// [noSuchMethodBlock] is used as the `br` target when the shape check fails. |
| void generateDynamicClosureCallShapeAndTypeCheck( |
| Translator translator, |
| w.InstructionsBuilder b, |
| w.Local closureLocal, |
| w.Local typeArgsLocal, |
| w.Local posArgsLocal, |
| w.Local namedArgsLocal, |
| w.Label noSuchMethodBlock) { |
| assert(typeArgsLocal.type == translator.typeArrayTypeRef); |
| assert(posArgsLocal.type == translator.nullableObjectArrayTypeRef); |
| assert(namedArgsLocal.type == translator.nullableObjectArrayTypeRef); |
| |
| // Read the `_FunctionType` field |
| final functionTypeLocal = |
| b.addLocal(translator.closureLayouter.functionTypeType); |
| b.local_get(closureLocal); |
| b.struct_get(translator.closureLayouter.closureBaseStruct, |
| FieldIndex.closureRuntimeType); |
| b.local_tee(functionTypeLocal); |
| |
| // If no type arguments were supplied but the closure has type parameters, use |
| // the default values. |
| b.local_get(typeArgsLocal); |
| b.array_len(); |
| b.i32_eqz(); |
| b.if_(); |
| b.local_get(functionTypeLocal); |
| b.struct_get( |
| translator.classInfo[translator.functionTypeClass]!.struct, |
| translator |
| .fieldIndex[translator.functionTypeTypeParameterDefaultsField]!); |
| b.local_set(typeArgsLocal); |
| b.end(); |
| |
| // Check closure shape |
| // [functionTypeLocal] already on the stack. |
| b.local_get(typeArgsLocal); |
| b.local_get(posArgsLocal); |
| b.local_get(namedArgsLocal); |
| translator.callReference(translator.checkClosureShape.reference, b); |
| b.i32_eqz(); |
| b.br_if(noSuchMethodBlock); |
| |
| // Shape check passed, check types |
| if (!translator.options.omitImplicitTypeChecks) { |
| b.local_get(functionTypeLocal); |
| b.local_get(typeArgsLocal); |
| b.local_get(posArgsLocal); |
| b.local_get(namedArgsLocal); |
| translator.callReference(translator.checkClosureType.reference, b); |
| b.drop(); |
| } |
| |
| // Type check passed \o/ |
| } |
| |
| void generateDynamicClosureCallViaDynamicEntry( |
| Translator translator, |
| w.InstructionsBuilder b, |
| w.Local closureLocal, |
| w.Local typeArgsLocal, |
| w.Local posArgsLocal, |
| w.Local namedArgsLocal) { |
| assert(translator.dynamicModuleSupportEnabled || |
| translator.closureLayouter.usesFunctionApplyWithNamedArguments); |
| |
| // Type check passed, call vtable entry |
| b.local_get(closureLocal); |
| b.local_get(typeArgsLocal); |
| b.local_get(posArgsLocal); |
| b.local_get(namedArgsLocal); |
| |
| // Get vtable |
| b.local_get(closureLocal); |
| b.struct_get( |
| translator.closureLayouter.closureBaseStruct, FieldIndex.closureVtable); |
| |
| // Get entry function |
| b.struct_get(translator.closureLayouter.vtableBaseStruct, |
| translator.closureLayouter.vtableDynamicClosureCallEntryIndex!); |
| b.call_ref(translator.dynamicCallVtableEntryFunctionType); |
| } |
| |
| void generateDynamicClosureCallViaPositionalArgs( |
| Translator translator, |
| w.InstructionsBuilder b, |
| w.Local closureLocal, |
| w.Local typeArgsLocal, |
| w.Local posArgsLocal) { |
| assert(!translator.dynamicModuleSupportEnabled && |
| !translator.closureLayouter.usesFunctionApplyWithNamedArguments); |
| |
| final maxTypeCount = translator.closureLayouter.maxTypeArgumentCount(); |
| b.emitDenseTableBranch([translator.topType], maxTypeCount, () { |
| b.local_get(typeArgsLocal); |
| b.array_len(); |
| }, (typeCount) { |
| final maxPositionalCount = |
| translator.closureLayouter.maxPositionalCountFor(typeCount); |
| b.emitDenseTableBranch([translator.topType], maxPositionalCount, () { |
| b.local_get(posArgsLocal); |
| b.array_len(); |
| }, (posCount) { |
| final representation = translator.closureLayouter |
| .getClosureRepresentation(typeCount, posCount, []); |
| if (representation == null) { |
| // This is a call combination that the closure layouter determined |
| // cannot occur in the program. |
| 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 < posCount; ++i) { |
| b.local_get(posArgsLocal); |
| b.i32_const(i); |
| b.array_get(translator.nullableObjectArrayType); |
| } |
| |
| final vtable = representation.vtableStruct; |
| final vtableIndex = representation.fieldIndexForSignature(posCount, []); |
| |
| 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)); |
| }, () { |
| b.unreachable(); |
| }); |
| }, () { |
| b.unreachable(); |
| }); |
| } |
| |
| 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, |
| Name memberName, |
| ) { |
| translator.constants.instantiateConstant( |
| b, |
| translator.symbols.getterSymbolFromName(memberName), |
| translator.classInfo[translator.symbolClass]!.nonNullableType); |
| |
| translator.callReference(translator.invocationGetterFactory.reference, b); |
| } |
| |
| void createSetterInvocationObject( |
| Translator translator, |
| w.InstructionsBuilder b, |
| Name memberName, |
| w.Local positionalArgLocal, |
| ) { |
| translator.constants.instantiateConstant( |
| b, |
| translator.symbols.setterSymbolFromName(memberName), |
| translator.classInfo[translator.symbolClass]!.nonNullableType); |
| |
| b.local_get(positionalArgLocal); |
| translator.callReference(translator.invocationSetterFactory.reference, b); |
| } |
| |
| void generateNoSuchMethodCall( |
| Translator translator, |
| w.InstructionsBuilder b, |
| void Function() pushReceiver, |
| void Function() pushInvocationObject, |
| ) { |
| final SelectorInfo noSuchMethodSelector = translator.dispatchTable |
| .selectorForTarget(translator.objectNoSuchMethod.reference); |
| translator.functions.recordSelectorUse(noSuchMethodSelector, false); |
| final signature = noSuchMethodSelector.signature; |
| |
| final targetRanges = |
| noSuchMethodSelector.targets(unchecked: false).targetRanges; |
| final staticDispatchRanges = |
| noSuchMethodSelector.targets(unchecked: false).staticDispatchRanges; |
| |
| // NOTE: Keep this in sync with |
| // `code_generator.dart:AstCodeGenerator._virtualCall`. |
| final bool directCall = |
| targetRanges.length == 1 && staticDispatchRanges.length == 1; |
| final callPolymorphicDispatcher = |
| !directCall && staticDispatchRanges.isNotEmpty; |
| |
| final noSuchMethodParamInfo = noSuchMethodSelector.paramInfo; |
| final noSuchMethodWasmFunctionType = signature; |
| |
| pushReceiver(); |
| if (callPolymorphicDispatcher) { |
| b.loadClassId(translator, translator.topTypeNonNullable); |
| pushReceiver(); |
| } |
| pushInvocationObject(); |
| |
| final invocationFactory = translator.functions |
| .getFunction(translator.invocationGenericMethodFactory.reference); |
| translator.convertType( |
| b, invocationFactory.type.outputs[0], signature.inputs[1]); |
| |
| // `noSuchMethod` can have extra parameters as long as they are optional. |
| // Push any optional positional parameters. |
| int wasmArgIdx = 2; |
| for (int positionalArgIdx = 1; |
| positionalArgIdx < noSuchMethodParamInfo.positional.length; |
| positionalArgIdx += 1) { |
| final positionalParameterValue = |
| noSuchMethodParamInfo.positional[positionalArgIdx]!; |
| translator.constants.instantiateConstant(b, positionalParameterValue, |
| noSuchMethodWasmFunctionType.inputs[wasmArgIdx]); |
| wasmArgIdx += 1; |
| } |
| |
| // Push any optional named parameters |
| for (String namedParameterName in noSuchMethodParamInfo.names) { |
| final namedParameterValue = |
| noSuchMethodParamInfo.named[namedParameterName]!; |
| translator.constants.instantiateConstant(b, namedParameterValue, |
| noSuchMethodWasmFunctionType.inputs[wasmArgIdx]); |
| wasmArgIdx += 1; |
| } |
| |
| assert(wasmArgIdx == noSuchMethodWasmFunctionType.inputs.length); |
| |
| if (directCall) { |
| translator.callReference(targetRanges[0].target, b); |
| } else if (callPolymorphicDispatcher) { |
| b.invoke(translator |
| .getPolymorphicDispatchersForModule(b.moduleBuilder) |
| .getPolymorphicDispatcher(noSuchMethodSelector, |
| useUncheckedEntry: false)); |
| } else { |
| pushReceiver(); |
| translator.callDispatchTable(b, noSuchMethodSelector, |
| interfaceTarget: translator.objectNoSuchMethod.reference, |
| useUncheckedEntry: false); |
| } |
| } |
| |
| class ClassIdRange { |
| final int start; |
| final int end; // inclusive |
| |
| ClassIdRange(this.start, this.end); |
| } |