| // Copyright (c) 2023, 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:_js_interop_checks/src/transformations/js_util_optimizer.dart' |
| show ExtensionIndex; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/type_environment.dart'; |
| |
| import '../util.dart'; |
| |
| const String referencedProcedurePlaceholder = "\$REF_PLACEHOLDER"; |
| |
| /// Contains context for a helper member generated for JS interop. |
| /// |
| /// This is used to store information about helper members that are generated |
| /// for JS interop, such as the JS code to be executed or the trampoline |
| /// reference. |
| /// |
| /// This data is attached to the corresponding [Member] node as a pragma. This |
| /// can then be decoded and handled during compilation. Storing this info as a |
| /// pragma means that the information is automatically encoded in the dill file |
| /// if dart2wasm is running as separate phases. |
| sealed class JsInteropMemberData { |
| String get pragmaName; |
| Constant? get toPragmaValue; |
| bool get isImport; |
| void applyToMember(Member member, CoreTypes coreTypes) { |
| addPragma(member, pragmaName, coreTypes, value: toPragmaValue); |
| } |
| |
| static JsInteropMemberData? fromMember(Member member, CoreTypes coreTypes) { |
| final jsCodePragma = getPragma<String>( |
| coreTypes, |
| member, |
| JsCodeData._pragmaName, |
| ); |
| if (jsCodePragma != null) { |
| return JsCodeData(jsCodePragma); |
| } |
| final hasJsTrampolinePragma = hasPragma( |
| coreTypes, |
| member, |
| JsTrampolineData._pragmaName, |
| ); |
| if (hasJsTrampolinePragma) { |
| return JsTrampolineData(); |
| } |
| final jsTrampolineWrapperPragma = getPragma<ListConstant>( |
| coreTypes, |
| member, |
| JsTrampolineWrapperData._pragmaName, |
| ); |
| if (jsTrampolineWrapperPragma != null) { |
| return JsTrampolineWrapperData.fromPragmaValue(jsTrampolineWrapperPragma); |
| } |
| return null; |
| } |
| } |
| |
| /// Data for a JS interop helper member that contains JS code. |
| /// |
| /// This is used to represent inline JS code written using |
| /// dart:_js_helper::JS. The filled in template is populated into the |
| /// finalized mjs file. |
| /// |
| /// We also use this to represent the JS code generated by procedure |
| /// specializers. See `interop_specializer.dart` for more information. |
| class JsCodeData extends JsInteropMemberData { |
| static const String _pragmaName = 'wasm:js-code'; |
| final String jsCode; |
| |
| @override |
| String get pragmaName => _pragmaName; |
| |
| @override |
| bool get isImport => true; |
| |
| JsCodeData(this.jsCode); |
| |
| @override |
| Constant get toPragmaValue => StringConstant(jsCode); |
| } |
| |
| /// Data for a JS interop helper member that is a trampoline. |
| /// |
| /// This is used to represent the exported trampoline function that is called |
| /// from JS. These are generated by .toJS calls on Function types. See |
| /// `callback_specializer.dart` for more information. |
| class JsTrampolineData extends JsInteropMemberData { |
| static const String _pragmaName = 'wasm:js-trampoline'; |
| |
| @override |
| String get pragmaName => _pragmaName; |
| |
| @override |
| bool get isImport => false; |
| |
| JsTrampolineData(); |
| |
| @override |
| Constant? get toPragmaValue => null; |
| } |
| |
| /// Data for a JS interop helper member that is a trampoline wrapper. |
| /// |
| /// This is used to represent the imported trampoline wrapper function that is |
| /// generated in the mjs file and imported into the wasm module. See |
| /// `callback_specializer.dart` for more information. |
| class JsTrampolineWrapperData extends JsInteropMemberData { |
| static const String _pragmaName = 'wasm:js-trampoline-wrapper'; |
| final int numJsParameters; |
| final bool captureThis; |
| final bool needsCastClosure; |
| |
| @override |
| String get pragmaName => _pragmaName; |
| |
| @override |
| bool get isImport => true; |
| |
| JsTrampolineWrapperData({ |
| required this.numJsParameters, |
| required this.captureThis, |
| required this.needsCastClosure, |
| }); |
| |
| factory JsTrampolineWrapperData.fromPragmaValue(ListConstant constant) { |
| final numJsParameters = (constant.entries[0] as IntConstant).value; |
| final captureThis = (constant.entries[1] as BoolConstant).value; |
| final needsCastClosure = (constant.entries[2] as BoolConstant).value; |
| return JsTrampolineWrapperData( |
| numJsParameters: numJsParameters, |
| captureThis: captureThis, |
| needsCastClosure: needsCastClosure, |
| ); |
| } |
| |
| @override |
| ListConstant get toPragmaValue => ListConstant(DynamicType(), [ |
| IntConstant(numJsParameters), |
| BoolConstant(captureThis), |
| BoolConstant(needsCastClosure), |
| ]); |
| |
| String jsCode() { |
| final jsParameters = <String>[]; |
| for (int i = 0; i < numJsParameters; i++) { |
| jsParameters.add('x$i'); |
| } |
| String jsWrapperParams = jsParameters.join(','); |
| // We could avoid incrementing the arguments length in the case of |
| // `captureThis` and have the function trampoline account for the extra |
| // argument, but there's no benefit in doing that. |
| String argumentsLength = captureThis |
| ? 'arguments.length + 1' |
| : 'arguments.length'; |
| String dartArguments = 'f,$argumentsLength'; |
| String jsMethodParams = '(wasmFunction,f)'; |
| if (needsCastClosure) { |
| dartArguments = '$dartArguments,castClosure'; |
| jsMethodParams = '(wasmFunction,f,castClosure)'; |
| } |
| if (captureThis) dartArguments = '$dartArguments,this'; |
| if (jsParameters.isNotEmpty) { |
| dartArguments = '$dartArguments,$jsWrapperParams'; |
| } |
| // Create JS method. |
| // Note: We have to use a regular function for the inner closure in some |
| // cases because we need access to `arguments`. |
| return "$jsMethodParams => finalizeWrapper(f, function($jsWrapperParams) {" |
| " return wasmFunction($dartArguments) })"; |
| } |
| } |
| |
| Procedure makeInteropProcedure( |
| Library library, |
| String name, |
| Uri fileUri, |
| FunctionNode functionNode, { |
| required bool isExternal, |
| }) { |
| final procedure = Procedure( |
| Name(name, library), |
| ProcedureKind.Method, |
| functionNode, |
| fileUri: fileUri, |
| isStatic: true, |
| isExternal: isExternal, |
| ); |
| library.addProcedure(procedure); |
| return procedure; |
| } |
| |
| /// A utility wrapper for [CoreTypes]. |
| class CoreTypesUtil { |
| final ExtensionIndex extensionIndex; |
| final CoreTypes coreTypes; |
| final Procedure dartifyRawTarget; |
| final Procedure functionToJSTarget; |
| final Procedure functionToJSCaptureThisTarget; |
| final Procedure greaterThanOrEqualToTarget; |
| final Procedure inlineJSTarget; |
| final Procedure isDartFunctionWrappedTarget; |
| final Procedure jsifyRawTarget; |
| final Procedure jsObjectFromDartObjectTarget; |
| final Class jsValueClass; |
| final Procedure jsValueBoxTarget; |
| final Procedure jsValueUnboxTarget; |
| final Procedure numToIntTarget; |
| final Class wasmExternRefClass; |
| final Class wasmVoidClass; |
| final Class wasmArrayClass; |
| final Class wasmArrayRefClass; |
| final Procedure wrapDartFunctionTarget; |
| final Procedure exportWasmFunctionTarget; |
| final Procedure wasmInternalizeNonNullable; |
| final Procedure unsafeCastOpaqueTarget; |
| final Member wasmExternRefNullRef; |
| final Class wasmI32Class; |
| final Procedure wasmI32ToIntSigned; |
| final Procedure isDartNullTarget; |
| final Procedure throwArgumentNullErrorTarget; |
| |
| // Dart value to JS converters. |
| final Procedure toJSBoolean; |
| final Procedure jsifyInt; |
| final Procedure toJSNumber; |
| final Procedure jsifyNum; |
| final Procedure jsifyJSValue; |
| final Procedure jsifyString; |
| final Procedure jsifyJSInt8ArrayImpl; |
| final Procedure jsifyJSUint8ArrayImpl; |
| final Procedure jsifyJSUint8ClampedArrayImpl; |
| final Procedure jsifyJSInt16ArrayImpl; |
| final Procedure jsifyJSUint16ArrayImpl; |
| final Procedure jsifyJSInt32ArrayImpl; |
| final Procedure jsifyJSUint32ArrayImpl; |
| final Procedure jsifyJSFloat32ArrayImpl; |
| final Procedure jsifyJSFloat64ArrayImpl; |
| final Procedure jsInt8ArrayFromDartInt8List; |
| final Procedure jsUint8ArrayFromDartUint8List; |
| final Procedure jsUint8ClampedArrayFromDartUint8ClampedList; |
| final Procedure jsInt16ArrayFromDartInt16List; |
| final Procedure jsUint16ArrayFromDartUint16List; |
| final Procedure jsInt32ArrayFromDartInt32List; |
| final Procedure jsUint32ArrayFromDartUint32List; |
| final Procedure jsFloat32ArrayFromDartFloat32List; |
| final Procedure jsFloat64ArrayFromDartFloat64List; |
| final Procedure jsifyJSDataViewImpl; // JS ByteData |
| final Procedure jsifyByteData; // Wasm ByteData |
| final Procedure jsifyRawList; |
| final Procedure jsifyJSArrayBufferImpl; // JS ByteBuffer |
| final Procedure jsArrayBufferFromDartByteBuffer; // Wasm ByteBuffer |
| final Procedure jsifyFunction; |
| final Class wasmFuncRefClass; |
| final Procedure wasmFunctionFromFunction; |
| |
| // Classes used in type tests for the converters. |
| final Class jsInt8ArrayImplClass; |
| final Class jsUint8ArrayImplClass; |
| final Class jsUint8ClampedArrayImplClass; |
| final Class jsInt16ArrayImplClass; |
| final Class jsUint16ArrayImplClass; |
| final Class jsInt32ArrayImplClass; |
| final Class jsUint32ArrayImplClass; |
| final Class jsFloat32ArrayImplClass; |
| final Class jsFloat64ArrayImplClass; |
| final Class int8ListClass; |
| final Class uint8ListClass; |
| final Class uint8ClampedListClass; |
| final Class int16ListClass; |
| final Class uint16ListClass; |
| final Class int32ListClass; |
| final Class uint32ListClass; |
| final Class float32ListClass; |
| final Class float64ListClass; |
| final Class jsDataViewImplClass; |
| final Class byteDataClass; |
| final Class jsArrayBufferImplClass; |
| final Class byteBufferClass; |
| |
| // NB. We rely on iteration ordering being insertion order to handle subtypes |
| // before supertypes to convert as `int` and `double` before `num`. |
| late final Map<Class, Procedure> _jsifyMap = { |
| coreTypes.boolClass: toJSBoolean, |
| coreTypes.intClass: jsifyInt, |
| coreTypes.doubleClass: toJSNumber, |
| coreTypes.numClass: jsifyNum, |
| jsValueClass: jsifyJSValue, |
| coreTypes.stringClass: jsifyString, |
| jsInt8ArrayImplClass: jsifyJSInt8ArrayImpl, |
| jsUint8ArrayImplClass: jsifyJSUint8ArrayImpl, |
| jsUint8ClampedArrayImplClass: jsifyJSUint8ClampedArrayImpl, |
| jsInt16ArrayImplClass: jsifyJSInt16ArrayImpl, |
| jsUint16ArrayImplClass: jsifyJSUint16ArrayImpl, |
| jsInt32ArrayImplClass: jsifyJSInt32ArrayImpl, |
| jsUint32ArrayImplClass: jsifyJSUint32ArrayImpl, |
| jsFloat32ArrayImplClass: jsifyJSFloat32ArrayImpl, |
| jsFloat64ArrayImplClass: jsifyJSFloat64ArrayImpl, |
| int8ListClass: jsInt8ArrayFromDartInt8List, |
| uint8ListClass: jsUint8ArrayFromDartUint8List, |
| uint8ClampedListClass: jsUint8ClampedArrayFromDartUint8ClampedList, |
| int16ListClass: jsInt16ArrayFromDartInt16List, |
| uint16ListClass: jsUint16ArrayFromDartUint16List, |
| int32ListClass: jsInt32ArrayFromDartInt32List, |
| uint32ListClass: jsUint32ArrayFromDartUint32List, |
| float32ListClass: jsFloat32ArrayFromDartFloat32List, |
| float64ListClass: jsFloat64ArrayFromDartFloat64List, |
| jsDataViewImplClass: jsifyJSDataViewImpl, |
| byteDataClass: jsifyByteData, |
| coreTypes.listClass: jsifyRawList, |
| jsArrayBufferImplClass: jsifyJSArrayBufferImpl, |
| byteBufferClass: jsArrayBufferFromDartByteBuffer, |
| coreTypes.functionClass: jsifyFunction, |
| }; |
| |
| /// Conversion functions from `WasmExternRef?`. These functions should check |
| /// for `null` and `undefined` values, and types to prevent nulls from flowing |
| /// into non-nullable Dart values or wrapping references with incorrect types. |
| /// (e.g. a `String` as `Uint8list`) |
| /// |
| /// Return values should be non-nullable. |
| late final Map<Class, Procedure> _dartifyNonNullableMap = { |
| coreTypes.boolClass: coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toDartBool', |
| ), |
| coreTypes.intClass: coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toDartInt', |
| ), |
| coreTypes.doubleClass: coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toDartDouble', |
| ), |
| coreTypes.numClass: coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toDartDouble', |
| ), |
| coreTypes.stringClass: coreTypes.index.getProcedure( |
| 'dart:_string', |
| 'JSStringImpl', |
| 'fromRef', |
| ), |
| coreTypes.listClass: coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toDartList', |
| ), |
| coreTypes.index.getClass('dart:typed_data', 'Int8List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSInt8ArrayImpl', 'fromRef'), |
| coreTypes.index.getClass('dart:typed_data', 'Uint8List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSUint8ArrayImpl', 'fromRef'), |
| coreTypes.index.getClass('dart:typed_data', 'Uint8ClampedList'): coreTypes |
| .index |
| .getProcedure('dart:_js_types', 'JSUint8ClampedArrayImpl', 'fromRef'), |
| coreTypes.index.getClass('dart:typed_data', 'Int16List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSInt16ArrayImpl', 'fromRef'), |
| coreTypes.index.getClass('dart:typed_data', 'Uint16List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSUint16ArrayImpl', 'fromRef'), |
| coreTypes.index.getClass('dart:typed_data', 'Int32List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSInt32ArrayImpl', 'fromRef'), |
| coreTypes.index.getClass('dart:typed_data', 'Uint32List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSUint32ArrayImpl', 'fromRef'), |
| coreTypes.index.getClass('dart:typed_data', 'Float32List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSFloat32ArrayImpl', 'fromRef'), |
| coreTypes.index.getClass('dart:typed_data', 'Float64List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSFloat64ArrayImpl', 'fromRef'), |
| coreTypes.index.getClass('dart:typed_data', 'ByteBuffer'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSArrayBufferImpl', 'fromRef'), |
| coreTypes.index.getClass('dart:typed_data', 'ByteData'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSDataViewImpl', 'fromRef'), |
| }; |
| |
| /// Similar to [_dartifyNonNullableMap], but return values should be nullable. |
| /// |
| /// These shouldn't throw on `null` or `undefined` arguments and instead |
| /// return Dart `null`. |
| late final Map<Class, Procedure> _dartifyNullableMap = { |
| coreTypes.boolClass: coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toDartNullableBool', |
| ), |
| coreTypes.intClass: coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toDartNullableInt', |
| ), |
| coreTypes.doubleClass: coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toDartNullableDouble', |
| ), |
| coreTypes.numClass: coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toDartNullableDouble', |
| ), |
| coreTypes.stringClass: coreTypes.index.getProcedure( |
| 'dart:_string', |
| 'JSStringImpl', |
| 'fromRefNullable', |
| ), |
| coreTypes.listClass: coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toDartNullableList', |
| ), |
| coreTypes.index.getClass('dart:typed_data', 'Int8List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSInt8ArrayImpl', 'fromRefNullable'), |
| coreTypes.index.getClass('dart:typed_data', 'Uint8List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSUint8ArrayImpl', 'fromRefNullable'), |
| coreTypes.index.getClass('dart:typed_data', 'Uint8ClampedList'): coreTypes |
| .index |
| .getProcedure( |
| 'dart:_js_types', |
| 'JSUint8ClampedArrayImpl', |
| 'fromRefNullable', |
| ), |
| coreTypes.index.getClass('dart:typed_data', 'Int16List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSInt16ArrayImpl', 'fromRefNullable'), |
| coreTypes.index.getClass('dart:typed_data', 'Uint16List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSUint16ArrayImpl', 'fromRefNullable'), |
| coreTypes.index.getClass('dart:typed_data', 'Int32List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSInt32ArrayImpl', 'fromRefNullable'), |
| coreTypes.index.getClass('dart:typed_data', 'Uint32List'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSUint32ArrayImpl', 'fromRefNullable'), |
| coreTypes.index.getClass('dart:typed_data', 'Float32List'): coreTypes.index |
| .getProcedure( |
| 'dart:_js_types', |
| 'JSFloat32ArrayImpl', |
| 'fromRefNullable', |
| ), |
| coreTypes.index.getClass('dart:typed_data', 'Float64List'): coreTypes.index |
| .getProcedure( |
| 'dart:_js_types', |
| 'JSFloat64ArrayImpl', |
| 'fromRefNullable', |
| ), |
| coreTypes.index.getClass('dart:typed_data', 'ByteBuffer'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSArrayBufferImpl', 'fromRefNullable'), |
| coreTypes.index.getClass('dart:typed_data', 'ByteData'): coreTypes.index |
| .getProcedure('dart:_js_types', 'JSDataViewImpl', 'fromRefNullable'), |
| }; |
| |
| CoreTypesUtil(this.coreTypes, this.extensionIndex) |
| : dartifyRawTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'dartifyRaw', |
| ), |
| functionToJSTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:js_interop', |
| 'FunctionToJSExportedDartFunction|get#toJS', |
| ), |
| functionToJSCaptureThisTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:js_interop', |
| 'FunctionToJSExportedDartFunction|get#toJSCaptureThis', |
| ), |
| greaterThanOrEqualToTarget = coreTypes.index.getProcedure( |
| 'dart:core', |
| 'num', |
| '>=', |
| ), |
| inlineJSTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'JS', |
| ), |
| isDartFunctionWrappedTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| '_isDartFunctionWrapped', |
| ), |
| numToIntTarget = coreTypes.index.getProcedure( |
| 'dart:core', |
| 'num', |
| 'toInt', |
| ), |
| jsifyRawTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyRaw', |
| ), |
| jsObjectFromDartObjectTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsObjectFromDartObject', |
| ), |
| jsValueBoxTarget = coreTypes.index.getProcedure( |
| 'dart:_js_helper', |
| 'JSValue', |
| 'box', |
| ), |
| jsValueClass = coreTypes.index.getClass('dart:_js_helper', 'JSValue'), |
| jsValueUnboxTarget = coreTypes.index.getProcedure( |
| 'dart:_js_helper', |
| 'JSValue', |
| 'unbox', |
| ), |
| wasmExternRefClass = coreTypes.index.getClass( |
| 'dart:_wasm', |
| 'WasmExternRef', |
| ), |
| wasmExternRefNullRef = coreTypes.index.getMember( |
| 'dart:_wasm', |
| 'WasmExternRef', |
| 'get:nullRef', |
| ), |
| wasmVoidClass = coreTypes.index.getClass('dart:_wasm', 'WasmVoid'), |
| wasmInternalizeNonNullable = coreTypes.index.getTopLevelProcedure( |
| 'dart:_wasm', |
| '_internalizeNonNullable', |
| ), |
| unsafeCastOpaqueTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:_internal', |
| 'unsafeCastOpaque', |
| ), |
| wasmArrayClass = coreTypes.index.getClass('dart:_wasm', 'WasmArray'), |
| wasmArrayRefClass = coreTypes.index.getClass( |
| 'dart:_wasm', |
| 'WasmArrayRef', |
| ), |
| wasmI32Class = coreTypes.index.getClass('dart:_wasm', 'WasmI32'), |
| wasmI32ToIntSigned = coreTypes.index.getProcedure( |
| 'dart:_wasm', |
| 'WasmI32', |
| 'toIntSigned', |
| ), |
| wrapDartFunctionTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| '_wrapDartFunction', |
| ), |
| exportWasmFunctionTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:_internal', |
| 'exportWasmFunction', |
| ), |
| toJSBoolean = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toJSBoolean', |
| ), |
| jsifyInt = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyInt', |
| ), |
| toJSNumber = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'toJSNumber', |
| ), |
| jsifyNum = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyNum', |
| ), |
| jsifyJSValue = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSValue', |
| ), |
| jsifyString = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyString', |
| ), |
| jsifyJSInt8ArrayImpl = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSInt8ArrayImpl', |
| ), |
| jsifyJSUint8ArrayImpl = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSUint8ArrayImpl', |
| ), |
| jsifyJSUint8ClampedArrayImpl = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSUint8ClampedArrayImpl', |
| ), |
| jsifyJSInt16ArrayImpl = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSInt16ArrayImpl', |
| ), |
| jsifyJSUint16ArrayImpl = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSUint16ArrayImpl', |
| ), |
| jsifyJSInt32ArrayImpl = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSInt32ArrayImpl', |
| ), |
| jsifyJSUint32ArrayImpl = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSUint32ArrayImpl', |
| ), |
| jsifyJSFloat32ArrayImpl = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSFloat32ArrayImpl', |
| ), |
| jsifyJSFloat64ArrayImpl = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSFloat64ArrayImpl', |
| ), |
| jsInt8ArrayFromDartInt8List = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsInt8ArrayFromDartInt8List', |
| ), |
| jsUint8ArrayFromDartUint8List = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsUint8ArrayFromDartUint8List', |
| ), |
| jsUint8ClampedArrayFromDartUint8ClampedList = coreTypes.index |
| .getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsUint8ClampedArrayFromDartUint8ClampedList', |
| ), |
| jsInt16ArrayFromDartInt16List = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsInt16ArrayFromDartInt16List', |
| ), |
| jsUint16ArrayFromDartUint16List = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsUint16ArrayFromDartUint16List', |
| ), |
| jsInt32ArrayFromDartInt32List = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsInt32ArrayFromDartInt32List', |
| ), |
| jsUint32ArrayFromDartUint32List = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsUint32ArrayFromDartUint32List', |
| ), |
| jsFloat32ArrayFromDartFloat32List = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsFloat32ArrayFromDartFloat32List', |
| ), |
| jsFloat64ArrayFromDartFloat64List = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsFloat64ArrayFromDartFloat64List', |
| ), |
| jsifyJSDataViewImpl = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSDataViewImpl', |
| ), |
| jsifyByteData = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyByteData', |
| ), |
| jsifyRawList = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| '_jsifyRawList', |
| ), |
| jsifyJSArrayBufferImpl = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyJSArrayBufferImpl', |
| ), |
| jsArrayBufferFromDartByteBuffer = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsArrayBufferFromDartByteBuffer', |
| ), |
| jsifyFunction = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'jsifyFunction', |
| ), |
| wasmFuncRefClass = coreTypes.index.getClass('dart:_wasm', 'WasmFuncRef'), |
| wasmFunctionFromFunction = coreTypes.index.getProcedure( |
| 'dart:_wasm', |
| 'WasmFunction', |
| 'fromFunction', |
| ), |
| jsInt8ArrayImplClass = coreTypes.index.getClass( |
| 'dart:_js_types', |
| 'JSInt8ArrayImpl', |
| ), |
| jsUint8ArrayImplClass = coreTypes.index.getClass( |
| 'dart:_js_types', |
| 'JSUint8ArrayImpl', |
| ), |
| jsUint8ClampedArrayImplClass = coreTypes.index.getClass( |
| 'dart:_js_types', |
| 'JSUint8ClampedArrayImpl', |
| ), |
| jsInt16ArrayImplClass = coreTypes.index.getClass( |
| 'dart:_js_types', |
| 'JSInt16ArrayImpl', |
| ), |
| jsUint16ArrayImplClass = coreTypes.index.getClass( |
| 'dart:_js_types', |
| 'JSUint16ArrayImpl', |
| ), |
| jsInt32ArrayImplClass = coreTypes.index.getClass( |
| 'dart:_js_types', |
| 'JSInt32ArrayImpl', |
| ), |
| jsUint32ArrayImplClass = coreTypes.index.getClass( |
| 'dart:_js_types', |
| 'JSUint32ArrayImpl', |
| ), |
| jsFloat32ArrayImplClass = coreTypes.index.getClass( |
| 'dart:_js_types', |
| 'JSFloat32ArrayImpl', |
| ), |
| jsFloat64ArrayImplClass = coreTypes.index.getClass( |
| 'dart:_js_types', |
| 'JSFloat64ArrayImpl', |
| ), |
| int8ListClass = coreTypes.index.getClass('dart:typed_data', 'Int8List'), |
| uint8ListClass = coreTypes.index.getClass('dart:typed_data', 'Uint8List'), |
| uint8ClampedListClass = coreTypes.index.getClass( |
| 'dart:typed_data', |
| 'Uint8ClampedList', |
| ), |
| int16ListClass = coreTypes.index.getClass('dart:typed_data', 'Int16List'), |
| uint16ListClass = coreTypes.index.getClass( |
| 'dart:typed_data', |
| 'Uint16List', |
| ), |
| int32ListClass = coreTypes.index.getClass('dart:typed_data', 'Int32List'), |
| uint32ListClass = coreTypes.index.getClass( |
| 'dart:typed_data', |
| 'Uint32List', |
| ), |
| float32ListClass = coreTypes.index.getClass( |
| 'dart:typed_data', |
| 'Float32List', |
| ), |
| float64ListClass = coreTypes.index.getClass( |
| 'dart:typed_data', |
| 'Float64List', |
| ), |
| jsDataViewImplClass = coreTypes.index.getClass( |
| 'dart:_js_types', |
| 'JSDataViewImpl', |
| ), |
| byteDataClass = coreTypes.index.getClass('dart:typed_data', 'ByteData'), |
| byteBufferClass = coreTypes.index.getClass( |
| 'dart:typed_data', |
| 'ByteBuffer', |
| ), |
| jsArrayBufferImplClass = coreTypes.index.getClass( |
| 'dart:_js_types', |
| 'JSArrayBufferImpl', |
| ), |
| isDartNullTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:_js_helper', |
| 'isDartNull', |
| ), |
| throwArgumentNullErrorTarget = coreTypes.index.getTopLevelProcedure( |
| 'dart:_error_utils', |
| '_throwArgumentNullError', |
| ); |
| |
| DartType get nonNullableObjectType => |
| coreTypes.objectRawType(Nullability.nonNullable); |
| |
| DartType get nonNullableWasmExternRefType => |
| wasmExternRefClass.getThisType(coreTypes, Nullability.nonNullable); |
| |
| DartType get nonNullableWasmFuncRefType => |
| wasmFuncRefClass.getThisType(coreTypes, Nullability.nonNullable); |
| |
| DartType get nullableJSValueType => |
| InterfaceType(jsValueClass, Nullability.nullable); |
| |
| DartType get nullableWasmExternRefType => |
| wasmExternRefClass.getThisType(coreTypes, Nullability.nullable); |
| |
| /// Whether [type] erases to a `JSValue` or `JSValue?`. |
| bool isJSValueType(DartType type) => |
| extensionIndex.isStaticInteropType(type) || |
| extensionIndex.isExternalDartReferenceType(type); |
| |
| Expression variableCheckConstant(Variable variable, Constant constant) => |
| StaticInvocation( |
| coreTypes.identicalProcedure, |
| Arguments([VariableGet(variable), ConstantExpression(constant)]), |
| ); |
| |
| Expression variableGreaterThanOrEqualToConstant( |
| Variable variable, |
| Constant constant, |
| ) => InstanceInvocation( |
| InstanceAccessKind.Instance, |
| VariableGet(variable), |
| greaterThanOrEqualToTarget.name, |
| Arguments([ConstantExpression(constant)]), |
| interfaceTarget: greaterThanOrEqualToTarget, |
| functionType: greaterThanOrEqualToTarget.getterType as FunctionType, |
| ); |
| |
| /// Cast the [invocation] if needed to conform to the expected [expectedType]. |
| /// |
| /// [expectedType] is the return type of the interop function, as written by |
| /// the user. |
| Expression castInvocationForReturn( |
| Expression invocation, |
| DartType expectedType, { |
| bool onlyHandleNull = false, |
| }) { |
| Expression expression; |
| if (expectedType is VoidType || |
| expectedType is InterfaceType && |
| expectedType.classNode == wasmVoidClass) { |
| // Technically a `void` return value can still be used, by casting the |
| // return type to `dynamic` or `Object?`. However this case should be |
| // extremely rare, and `dartifyRaw` overhead for return values that should |
| // never be used in practice is too much, so we avoid `dartifyRaw` on |
| // `void` returns and always return `null`. |
| return BlockExpression( |
| Block([ExpressionStatement(invocation)]), |
| NullLiteral(), |
| ); |
| } |
| if (onlyHandleNull) return invocation; |
| |
| if (isJSValueType(expectedType)) { |
| // Casts are expensive, so we stick to a null-assertion if needed. If |
| // the nullability can't be determined, cast. |
| expression = invokeOneArg(jsValueBoxTarget, invocation); |
| final nullability = expectedType.extensionTypeErasure.nullability; |
| if (nullability == Nullability.nonNullable) { |
| expression = NullCheck(expression); |
| } else if (nullability == Nullability.undetermined) { |
| expression = AsExpression(expression, expectedType); |
| } |
| } else { |
| final expectedTypeExtensionTypeErasure = |
| expectedType.extensionTypeErasure; |
| final conversionProcedure = _dartConversionProcedure( |
| expectedTypeExtensionTypeErasure, |
| ); |
| final invocationValueVar = SyntheticVariable( |
| cosmeticName: '#jsInvocation', |
| initializer: invocation, |
| type: nullableWasmExternRefType, |
| ); |
| expression = Let( |
| invocationValueVar, |
| invokeOneArg(conversionProcedure, VariableGet(invocationValueVar)), |
| ); |
| } |
| |
| return expression; |
| } |
| |
| // Handles any necessary type conversions. Today this is just for handling the |
| // case where a user wants us to coerce a JS number to an int instead of a |
| // double. This is okay as long as the value is an integer value. |
| Expression convertAndCast(DartType staticType, Expression expression) { |
| final erasedType = staticType.extensionTypeErasure; |
| if (erasedType == coreTypes.intNullableRawType || |
| erasedType == coreTypes.intNonNullableRawType) { |
| // let v = [expression] as double? in |
| // if (v == null) { |
| // return null; |
| // } else { |
| // let v2 = v.toInt() in |
| // if (v == v2) { |
| // return v2; |
| // } else { |
| // throw; |
| // } |
| SyntheticVariable v = SyntheticVariable( |
| cosmeticName: '#vardouble', |
| initializer: AsExpression(expression, coreTypes.doubleNullableRawType), |
| type: coreTypes.doubleNullableRawType, |
| ); |
| SyntheticVariable v2 = SyntheticVariable( |
| cosmeticName: '#varint', |
| initializer: invokeMethod(v, numToIntTarget), |
| type: coreTypes.intNonNullableRawType, |
| ); |
| expression = Let( |
| v, |
| ConditionalExpression( |
| variableCheckConstant(v, NullConstant()), |
| ConstantExpression(NullConstant()), |
| Let( |
| v2, |
| ConditionalExpression( |
| invokeMethod( |
| v, |
| coreTypes.objectEquals, |
| Arguments([VariableGet(v2)]), |
| ), |
| VariableGet(v2), |
| Throw( |
| StringLiteral('Expected integer value, but was not integer.'), |
| ), |
| coreTypes.intNonNullableRawType, |
| ), |
| ), |
| coreTypes.intNullableRawType, |
| ), |
| ); |
| } |
| return AsExpression(expression, staticType); |
| } |
| |
| /// Return the function to convert a value with type [valueType] to Dart |
| /// interop type [expectedType]. |
| /// |
| /// [expectedType] can be any interop type, but for now this only handles the |
| /// interop types generated by [_Specializer._getRawInteropProcedure]. |
| /// |
| /// `null` return value means no conversion is needed, the value can be passed |
| /// to the interop function directly. |
| /// |
| /// The argument passed to the returned conversion function needs to be |
| /// non-nullable. This function asserts that [valueType] is non-nullable |
| /// so that the argument passed to the conversion function won't be `null`. |
| Procedure? _jsConversionProcedure( |
| DartType valueType, |
| DartType expectedType, |
| TypeEnvironment typeEnv, |
| ) { |
| assert( |
| valueType is DynamicType || |
| valueType.nullability == Nullability.nonNullable, |
| 'valueType: $valueType', |
| ); |
| if (expectedType == coreTypes.doubleNonNullableRawType) { |
| assert( |
| valueType is InterfaceType && |
| valueType.classNode == coreTypes.doubleClass, |
| ); |
| return null; |
| } |
| |
| if (coreTypes.isNull(valueType)) { |
| return null; |
| } |
| |
| assert( |
| expectedType == nullableWasmExternRefType, |
| 'Unexpected expected type: $expectedType', |
| ); |
| |
| for (final entry in _jsifyMap.entries) { |
| if (typeEnv.isSubtypeOf( |
| valueType, |
| InterfaceType(entry.key, Nullability.nonNullable), |
| )) { |
| return entry.value; |
| } |
| } |
| |
| // `dynamic` or `Object?`, convert based on runtime type. |
| return jsifyRawTarget; |
| } |
| |
| /// Return the function to convert a value returned by an interop procedure |
| /// generated by [_Specializer.getRawInteropProcedure] to the expected Dart |
| /// type. |
| /// |
| /// The value passed to the returned conversion function should be an |
| /// `externref`. |
| Procedure _dartConversionProcedure(DartType expectedType) { |
| Procedure? conversionProcedure; |
| if (expectedType is InterfaceType) { |
| conversionProcedure = expectedType.isPotentiallyNullable |
| ? _dartifyNullableMap[expectedType.classNode] |
| : _dartifyNonNullableMap[expectedType.classNode]; |
| } |
| return conversionProcedure ?? dartifyRawTarget; |
| } |
| } |
| |
| StaticInvocation invokeOneArg(Procedure target, Expression arg) => |
| StaticInvocation(target, Arguments([arg])); |
| |
| InstanceInvocation invokeMethod( |
| Variable receiver, |
| Procedure target, [ |
| Arguments? arguments, |
| ]) => InstanceInvocation( |
| InstanceAccessKind.Instance, |
| VariableGet(receiver), |
| target.name, |
| arguments ?? Arguments([]), |
| interfaceTarget: target, |
| functionType: target.function.computeFunctionType(Nullability.nonNullable), |
| ); |
| |
| bool parametersNeedParens(List<String> parameters) => |
| parameters.isEmpty || parameters.length > 1; |
| |
| Expression jsifyValue( |
| Variable variable, |
| DartType expectedType, |
| CoreTypesUtil coreTypes, |
| TypeEnvironment typeEnv, |
| ) { |
| Expression? conversion; |
| |
| if (coreTypes.extensionIndex.isStaticInteropType(variable.type) || |
| coreTypes.extensionIndex.isExternalDartReferenceType(variable.type)) { |
| conversion = invokeOneArg( |
| coreTypes.jsValueUnboxTarget, |
| VariableGet(variable), |
| ); |
| } else if (variable.type is VoidType || |
| coreTypes.coreTypes.isNull(variable.type)) { |
| conversion = StaticGet(coreTypes.wasmExternRefNullRef); |
| } else { |
| final conversionProcedure = coreTypes._jsConversionProcedure( |
| // We check for null before invoking the conversion procedure. |
| variable.type.extensionTypeErasure.withDeclaredNullability( |
| Nullability.nonNullable, |
| ), |
| expectedType, |
| typeEnv, |
| ); |
| if (conversionProcedure != null) { |
| conversion = invokeOneArg(conversionProcedure, VariableGet(variable)); |
| } |
| } |
| |
| conversion ??= VariableGet(variable); |
| |
| if (variable.type.isPotentiallyNullable) { |
| return ConditionalExpression( |
| EqualsNull(VariableGet(variable)), |
| StaticGet(coreTypes.wasmExternRefNullRef), |
| conversion, |
| InterfaceType(coreTypes.wasmExternRefClass, Nullability.nullable), |
| ); |
| } else { |
| return conversion; |
| } |
| } |