2d78883f27
Right now a JS interop callback works like this: * Each wasm module that gets instantiated will be given it's module instance (JS calls Dart to set it) via `setThisModule` * When Dart code calls JS and gives it a callback to invoke, it gave it this module instance. It will also make the callback wasm function weakly exported. * The JS trampoline code, when invoked, would then call the weakly exported wasm function from the module instance. We simplify this now by making the Dart code simply give the wasm function reference to JS, then JS can later on invoke it. No need to weakly export a function and call back via `module.exports.<weaklyExportedCallback>` To ensure binaryen is aware that the wasm function may be called from JS, we annotate it via the `(@binaryen.js.called)` annotation. Change-Id: I828dd0cf8d3b36db338792c4e277a4bb94c76faf Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/511080 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Srujan Gaddam <srujzs@google.com>
1021 lines
35 KiB
Dart
1021 lines
35 KiB
Dart
// 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 = Variable(
|
|
'#jsInvocation',
|
|
initializer: invocation,
|
|
type: nullableWasmExternRefType,
|
|
isSynthesized: true,
|
|
);
|
|
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;
|
|
// }
|
|
Variable v = Variable(
|
|
'#vardouble',
|
|
initializer: AsExpression(expression, coreTypes.doubleNullableRawType),
|
|
type: coreTypes.doubleNullableRawType,
|
|
isSynthesized: true,
|
|
);
|
|
Variable v2 = Variable(
|
|
'#varint',
|
|
initializer: invokeMethod(v, numToIntTarget),
|
|
type: coreTypes.intNonNullableRawType,
|
|
isSynthesized: true,
|
|
);
|
|
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;
|
|
}
|
|
}
|