blob: e91b7080b6221b4ddab590e452a3952e0c7a6763 [file]
// 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;
}
}