[dart2wasm] Generate the bulk of the JS runtime at compile time.

Change-Id: Ib5f9bdedcda4a4ba305d7eea2e6c127f815582ec
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279640
Commit-Queue: Joshua Litt <joshualitt@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
Joshua Litt 2023-01-25 20:19:08 +00:00 committed by Commit Queue
parent 45817fd9c7
commit 8a7dd221ad
16 changed files with 250 additions and 501 deletions

View file

@ -60,14 +60,6 @@ export const instantiate = async (modulePromise, importObjectPromise) => {
return array;
}
function dataViewFromDartByteData(byteData, byteLength) {
const dataView = new DataView(new ArrayBuffer(byteLength));
for (let i = 0; i < byteLength; i++) {
dataView.setUint8(i, dartInstance.exports.$byteDataGetUint8(byteData, i));
}
return dataView;
}
// A special symbol attached to functions that wrap Dart functions.
const jsWrappedDartFunctionSymbol = Symbol("JSWrappedDartFunction");
@ -77,295 +69,8 @@ export const instantiate = async (modulePromise, importObjectPromise) => {
return wrapped;
}
// Calls a constructor with a variable number of arguments.
function callConstructorVarArgs(constructor, args) {
// Apply bind to the constructor. We pass `null` as the first argument
// to `bind.apply` because this is `bind`'s unused context
// argument(`new` will explicitly create a new context).
const factoryFunction = constructor.bind.apply(constructor, [null, ...args]);
return new factoryFunction();
}
// Imports
const dart2wasm = {
printToConsole: function(string) {
console.log(stringFromDartString(string))
},
scheduleCallback: function(milliseconds, closure) {
setTimeout(function() {
dartInstance.exports.$invokeCallback(closure);
}, milliseconds);
},
futurePromise: new WebAssembly.Function(
{parameters: ['externref', 'externref'], results: ['externref']},
function(future) {
return new Promise(function (resolve, reject) {
dartInstance.exports.$awaitCallback(future, resolve);
});
},
{suspending: 'first'}),
callResolve: function(resolve, result) {
// This trampoline is needed because [resolve] is a JS function that
// can't be called directly from Wasm.
resolve(result);
},
callAsyncBridge: function(args, completer) {
// This trampoline is needed because [asyncBridge] is a function wrapped
// by `returnPromiseOnSuspend`, and the stack-switching functionality of
// that wrapper is implemented as part of the export adapter.
asyncBridge(args, completer);
},
getCurrentStackTrace: function() {
// [Error] should be supported in most browsers.
// A possible future optimization we could do is to just save the
// `Error` object here, and stringify the stack trace when it is
// actually used.
let stackString = new Error().stack.toString();
// We remove the last three lines of the stack trace to prevent including
// `Error`, `getCurrentStackTrace`, and `StackTrace.current` in the
// stack trace.
let userStackString = stackString.split('\n').slice(3).join('\n');
return stringToDartString(userStackString);
},
int8ArrayFromDartInt8List: function(list) {
return arrayFromDartList(Int8Array, list);
},
uint8ArrayFromDartUint8List: function(list) {
return arrayFromDartList(Uint8Array, list);
},
uint8ClampedArrayFromDartUint8ClampedList: function(list) {
return arrayFromDartList(Uint8ClampedArray, list);
},
int16ArrayFromDartInt16List: function(list) {
return arrayFromDartList(Int16Array, list);
},
uint16ArrayFromDartUint16List: function(list) {
return arrayFromDartList(Uint16Array, list);
},
int32ArrayFromDartInt32List: function(list) {
return arrayFromDartList(Int32Array, list);
},
uint32ArrayFromDartUint32List: function(list) {
return arrayFromDartList(Uint32Array, list);
},
float32ArrayFromDartFloat32List: function(list) {
return arrayFromDartList(Float32Array, list);
},
float64ArrayFromDartFloat64List: function(list) {
return arrayFromDartList(Float64Array, list);
},
dataViewFromDartByteData: function(byteData, byteLength) {
return dataViewFromDartByteData(byteData, byteLength);
},
arrayFromDartList: function(list) {
return arrayFromDartList(Array, list);
},
stringFromDartString: stringFromDartString,
stringToDartString: stringToDartString,
objectLength: function(o) {
return o.length;
},
objectReadIndex: function(o, i) {
return o[i];
},
objectKeys: function(o) {
return Object.keys(o);
},
unwrapJSWrappedDartFunction: function(o) {
return o.dartFunction;
},
isJSUndefined: function(o) {
return o === undefined;
},
isJSBoolean: function(o) {
return typeof o === "boolean";
},
isJSNumber: function(o) {
return typeof o === "number";
},
isJSBigInt: function(o) {
return typeof o === "bigint";
},
isJSString: function(o) {
return typeof o === "string";
},
isJSSymbol: function(o) {
return typeof o === "symbol";
},
isJSFunction: function(o) {
return typeof o === "function";
},
isJSInt8Array: function(o) {
return o instanceof Int8Array;
},
isJSUint8Array: function(o) {
return o instanceof Uint8Array;
},
isJSUint8ClampedArray: function(o) {
return o instanceof Uint8ClampedArray;
},
isJSInt16Array: function(o) {
return o instanceof Int16Array;
},
isJSUint16Array: function(o) {
return o instanceof Uint16Array;
},
isJSInt32Array: function(o) {
return o instanceof Int32Array;
},
isJSUint32Array: function(o) {
return o instanceof Uint32Array;
},
isJSFloat32Array: function(o) {
return o instanceof Float32Array;
},
isJSFloat64Array: function(o) {
return o instanceof Float64Array;
},
isJSArrayBuffer: function(o) {
return o instanceof ArrayBuffer;
},
isJSDataView: function(o) {
return o instanceof DataView;
},
isJSArray: function(o) {
return o instanceof Array;
},
isJSWrappedDartFunction: function(o) {
return typeof o === "function" &&
o[jsWrappedDartFunctionSymbol] === true;
},
isJSObject: function(o) {
return o instanceof Object;
},
isJSRegExp: function(o) {
return o instanceof RegExp;
},
isJSSimpleObject: function(o) {
const proto = Object.getPrototypeOf(o);
return proto === Object.prototype || proto === null;
},
roundtrip: function(o) {
// This function exists as a hook for the native JS -> Wasm type
// conversion rules. The Dart runtime will overload variants of this
// function with the necessary return type to trigger the desired
// coercion.
return o;
},
toJSBoolean: function(b) {
return !!b;
},
newObject: function() {
return {};
},
newArray: function() {
return [];
},
globalThis: function() {
return globalThis;
},
getProperty: function(object, name) {
return object[name];
},
hasProperty: function(object, name) {
return name in object;
},
setProperty: function(object, name, value) {
return object[name] = value;
},
callMethodVarArgs: function(object, name, args) {
return object[name].apply(object, args);
},
callConstructorVarArgs: callConstructorVarArgs,
safeCallConstructorVarArgs: function(constructor, args) {
try {
return callConstructorVarArgs(constructor, args);
} catch (e) {
return String(e);
}
},
getTimeZoneNameForSeconds: function(secondsSinceEpoch) {
const date = new Date(secondsSinceEpoch * 1000);
const match = /\((.*)\)/.exec(date.toString());
if (match == null) {
// This should never happen on any recent browser.
return '';
}
return stringToDartString(match[1]);
},
getTimeZoneOffsetInSeconds: function(secondsSinceEpoch) {
return new Date(secondsSinceEpoch * 1000).getTimezoneOffset() * 60;
},
jsonEncode: function(s) {
return stringToDartString(JSON.stringify(stringFromDartString(s)));
},
toUpperCase: function(string) {
return stringToDartString(stringFromDartString(string).toUpperCase());
},
toLowerCase: function(string) {
return stringToDartString(stringFromDartString(string).toLowerCase());
},
isWindows: function() {
return typeof process != undefined &&
Object.prototype.toString.call(process) == "[object process]" &&
process.platform == "win32";
},
getCurrentUri: function() {
// On browsers return `globalThis.location.href`
if (globalThis.location != null) {
return stringToDartString(globalThis.location.href);
}
return null;
},
stringify: function(o) {
return stringToDartString(String(o));
},
doubleToString: function(v) {
return stringToDartString(v.toString());
},
toFixed: function(double, digits) {
return stringToDartString(double.toFixed(digits));
},
toExponential: function(double, fractionDigits) {
return stringToDartString(double.toExponential(fractionDigits));
},
toPrecision: function(double, precision) {
return stringToDartString(double.toPrecision(precision));
},
parseDouble: function(source) {
// Notice that JS parseFloat accepts garbage at the end of the string.
// Accept only:
// - [+/-]NaN
// - [+/-]Infinity
// - a Dart double literal
// We do allow leading or trailing whitespace.
const jsSource = stringFromDartString(source);
if (!/^\s*[+-]?(?:Infinity|NaN|(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(jsSource)) {
return NaN;
}
return parseFloat(jsSource);
},
quoteStringForRegExp: function(string) {
// We specialize this method in the runtime to avoid the overhead of
// jumping back and forth between JS and Dart. This method is optimized
// to test before replacement, which should be much faster. This might
// be worth measuring in real world use cases though.
let jsString = stringFromDartString(string);
if (/[[\]{}()*+?.\\^$|]/.test(jsString)) {
jsString = jsString.replace(/[[\]{}()*+?.\\^$|]/g, '\\$&');
}
return stringToDartString(jsString);
},
areEqualInJS: function(l, r) {
return l === r;
},
promiseThen: function(promise, successFunc, failureFunc) {
promise.then(successFunc, failureFunc);
},
performanceNow: function() {
return performance.now();
},
''';
// We break inside the 'dart2wasm' object to enable injection of methods. We

View file

@ -624,9 +624,12 @@ class _JSLowerer extends Transformer {
}
}
Procedure _getEnclosingProcedure(TreeNode node) {
Procedure? _tryGetEnclosingProcedure(TreeNode? node) {
while (node is! Procedure) {
node = node.parent!;
node = node?.parent;
if (node == null) {
return null;
}
}
return node;
}
@ -638,7 +641,10 @@ class _JSLowerer extends Transformer {
/// 3) All of the arguments to `inlineJS` are [VariableGet]s. (this is
/// checked by [_expandInlineJS]).
bool _shouldReplaceEnclosingProcedure(StaticInvocation node) {
Procedure enclosingProcedure = _getEnclosingProcedure(node);
Procedure? enclosingProcedure = _tryGetEnclosingProcedure(node);
if (enclosingProcedure == null) {
return false;
}
Statement enclosingBody = enclosingProcedure.function.body!;
Expression? expression;
if (enclosingBody is ReturnStatement) {
@ -686,7 +692,7 @@ class _JSLowerer extends Transformer {
Procedure dartProcedure;
Expression result;
if (_replaceProcedureWithInlineJS) {
dartProcedure = _getEnclosingProcedure(node);
dartProcedure = _tryGetEnclosingProcedure(node)!;
result = InvalidExpression("Unreachable");
} else {
dartProcedure = _addInteropProcedure(

View file

@ -49,6 +49,7 @@
// Resolving the `Promise` causes the suspended execution to resume.
import 'dart:_internal' show patch, scheduleCallback, unsafeCastOpaque;
import 'dart:_js_helper' show JS;
import 'dart:wasm';
@ -61,9 +62,12 @@ Future<T> _asyncHelper<T>(WasmStructRef args) {
return completer.future;
}
@pragma("wasm:import", "dart2wasm.callAsyncBridge")
external void _callAsyncBridge(
WasmStructRef args, Completer<Object?> completer);
void _callAsyncBridge(WasmStructRef args, Completer<Object?> completer) =>
// This trampoline is needed because [asyncBridge] is a function wrapped
// by `returnPromiseOnSuspend`, and the stack-switching functionality of
// that wrapper is implemented as part of the export adapter.
JS<void>(
"(args, completer) => asyncBridge(args, completer)", args, completer);
@pragma("wasm:export", "\$asyncBridge")
WasmAnyRef? _asyncBridge(
@ -106,8 +110,15 @@ Object? _awaitHelper(Object? operand, WasmExternRef? stack) {
return result;
}
@pragma("wasm:import", "dart2wasm.futurePromise")
external Object? _futurePromise(WasmExternRef? stack, Future<Object?> future);
Object? _futurePromise(WasmExternRef? stack, Future<Object?> future) =>
JS<Object?>("""new WebAssembly.Function(
{parameters: ['externref', 'externref'], results: ['externref']},
function(future) {
return new Promise(function (resolve, reject) {
dartInstance.exports.\$awaitCallback(future, resolve);
});
},
{suspending: 'first'})""", stack, future);
@pragma("wasm:export", "\$awaitCallback")
void _awaitCallback(Future<Object?> future, WasmExternRef? resolve) {
@ -118,5 +129,7 @@ void _awaitCallback(Future<Object?> future, WasmExternRef? resolve) {
});
}
@pragma("wasm:import", "dart2wasm.callResolve")
external void _callResolve(WasmExternRef? resolve, Object? result);
void _callResolve(WasmExternRef? resolve, Object? result) =>
// This trampoline is needed because [resolve] is a JS function that
// can't be called directly from Wasm.
JS<void>("(resolve, result) => resolve(result)", resolve, result);

View file

@ -4,9 +4,6 @@
import "dart:_internal" show patch;
@pragma("wasm:import", "dart2wasm.parseDouble")
external double _parseDoubleJS(String source);
@patch
double _parseDouble(String source, int start, int end) =>
_parseDoubleJS(source.substring(start, end));
double.parse(source.substring(start, end));

View file

@ -30,7 +30,7 @@ import "dart:_internal"
import "dart:_internal" as _internal show Symbol;
import 'dart:_js_helper' show JSSyntaxRegExp, quoteStringForRegExp;
import 'dart:_js_helper' show JS, JSSyntaxRegExp, quoteStringForRegExp;
import "dart:collection"
show

View file

@ -20,16 +20,24 @@ class DateTime {
static int _getCurrentMicros() =>
(_jsDateNow() * Duration.microsecondsPerMillisecond).toInt();
@pragma("wasm:import", "dart2wasm.getTimeZoneNameForSeconds")
external static String _timeZoneNameForClampedSecondsRaw(
double secondsSinceEpoch);
static String _timeZoneNameForClampedSecondsRaw(double secondsSinceEpoch) =>
JS<String>(r"""secondsSinceEpoch => {
const date = new Date(secondsSinceEpoch * 1000);
const match = /\((.*)\)/.exec(date.toString());
if (match == null) {
// This should never happen on any recent browser.
return '';
}
return stringToDartString(match[1]);
}""", secondsSinceEpoch);
static String _timeZoneNameForClampedSeconds(int secondsSinceEpoch) =>
_timeZoneNameForClampedSecondsRaw(secondsSinceEpoch.toDouble());
@pragma("wasm:import", "dart2wasm.getTimeZoneOffsetInSeconds")
external static double _timeZoneOffsetInSecondsForClampedSeconds(
double secondsSinceEpoch);
static double _timeZoneOffsetInSecondsForClampedSeconds(
double secondsSinceEpoch) =>
JS<double>("s => new Date(s * 1000).getTimezoneOffset() * 60 ",
secondsSinceEpoch);
static const _MICROSECOND_INDEX = 0;
static const _MILLISECOND_INDEX = 1;

View file

@ -4,24 +4,6 @@
part of "core_patch.dart";
@pragma("wasm:import", "dart2wasm.doubleToString")
external String _doubleToString(double v);
@pragma("wasm:import", "dart2wasm.toFixed")
external String _toFixed(double d, double digits);
@pragma("wasm:import", "dart2wasm.toExponential")
external String _toExponential1(double d);
@pragma("wasm:import", "dart2wasm.toExponential")
external String _toExponential2(double d, double fractionDigits);
@pragma("wasm:import", "dart2wasm.toPrecision")
external String _toPrecision(double d, double precision);
@pragma("wasm:import", "dart2wasm.parseDouble")
external double _parseDouble(String doubleString);
@patch
class double {
@patch
@ -40,7 +22,19 @@ class double {
@patch
static double? tryParse(String source) {
double result = _parseDouble(source);
// Notice that JS parseFloat accepts garbage at the end of the string.
// Accept only:
// - [+/-]NaN
// - [+/-]Infinity
// - a Dart double literal
// We do allow leading or trailing whitespace.
double result = JS<double>(r"""s => {
const jsSource = stringFromDartString(s);
if (!/^\s*[+-]?(?:Infinity|NaN|(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(jsSource)) {
return NaN;
}
return parseFloat(jsSource);
}""", source);
if (result.isNaN) {
String trimmed = source.trim();
if (!(trimmed == 'NaN' || trimmed == '+NaN' || trimmed == '-NaN')) {
@ -285,7 +279,7 @@ class _BoxedDouble extends double {
return "0.0";
}
}
String result = _doubleToString(value);
String result = JS<String>("v => stringToDartString(v.toString())", value);
if (this % 1.0 == 0.0 && result.indexOf('e') == -1) {
result = '$result.0';
}
@ -324,8 +318,10 @@ class _BoxedDouble extends double {
return result;
}
String _toStringAsFixed(int fractionDigits) =>
_toFixed(value, fractionDigits.toDouble());
String _toStringAsFixed(int fractionDigits) => JS<String>(
"(d, digits) => stringToDartString(d.toFixed(digits))",
value,
fractionDigits.toDouble());
String toStringAsExponential([int? fractionDigits]) {
// See ECMAScript-262, 15.7.4.6 for details.
@ -352,9 +348,10 @@ class _BoxedDouble extends double {
String _toStringAsExponential(int? fractionDigits) {
if (fractionDigits == null) {
return _toExponential1(value);
return JS<String>("d => stringToDartString(d.toExponential())", value);
} else {
return _toExponential2(value, fractionDigits.toDouble());
return JS<String>("(d, f) => stringToDartString(d.toExponential(f))",
value, fractionDigits.toDouble());
}
}
@ -379,8 +376,10 @@ class _BoxedDouble extends double {
return result;
}
String _toStringAsPrecision(int fractionDigits) =>
_toPrecision(value, fractionDigits.toDouble());
String _toStringAsPrecision(int fractionDigits) => JS<String>(
"(d, precision) => stringToDartString(d.toPrecision(precision))",
value,
fractionDigits.toDouble());
// Order is: NaN > Infinity > ... > 0.0 > -0.0 > ... > -Infinity.
int compareTo(num other) {

View file

@ -2,6 +2,7 @@
// 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 "dart:_js_helper" show JS;
import "dart:typed_data" show Uint8List;
part "class_id.dart";
@ -164,8 +165,12 @@ void _invokeMain(Function main) {
}
// Schedule a callback from JS via setTimeout.
@pragma("wasm:import", "dart2wasm.scheduleCallback")
external void scheduleCallback(double millis, dynamic Function() callback);
void scheduleCallback(double millis, dynamic Function() callback) {
JS<void>(r"""(ms, c) =>
setTimeout(
() => dartInstance.exports.$invokeCallback(c),ms)""", millis,
callback);
}
@pragma("wasm:import", "dart2wasm.jsonEncode")
external String jsonEncode(String object);
String jsonEncode(String object) => JS<String>(
"s => stringToDartString(JSON.stringify(stringFromDartString(s)))", object);

View file

@ -146,186 +146,175 @@ Object jsObjectToDartObject(WasmExternRef? ref) =>
WasmExternRef jsObjectFromDartObject(Object object) =>
unsafeCastOpaque<WasmAnyRef>(object).externalize();
@pragma("wasm:import", "dart2wasm.isJSUndefined")
external bool isJSUndefined(WasmExternRef? o);
bool isJSUndefined(WasmExternRef? o) => JS<bool>('o => o === undefined', o);
@pragma("wasm:import", "dart2wasm.isJSBoolean")
external bool isJSBoolean(WasmExternRef? o);
bool isJSBoolean(WasmExternRef? o) =>
JS<bool>("o => typeof o === 'boolean'", o);
@pragma("wasm:import", "dart2wasm.isJSNumber")
external bool isJSNumber(WasmExternRef? o);
bool isJSNumber(WasmExternRef? o) => JS<bool>("o => typeof o === 'number'", o);
@pragma("wasm:import", "dart2wasm.isJSBigInt")
external bool isJSBigInt(WasmExternRef? o);
bool isJSBigInt(WasmExternRef? o) => JS<bool>("o => typeof o === 'bigint'", o);
@pragma("wasm:import", "dart2wasm.isJSString")
external bool isJSString(WasmExternRef? o);
bool isJSString(WasmExternRef? o) => JS<bool>("o => typeof o === 'string'", o);
@pragma("wasm:import", "dart2wasm.isJSSymbol")
external bool isJSSymbol(WasmExternRef? o);
bool isJSSymbol(WasmExternRef? o) => JS<bool>("o => typeof o === 'symbol'", o);
@pragma("wasm:import", "dart2wasm.isJSFunction")
external bool isJSFunction(WasmExternRef? o);
bool isJSFunction(WasmExternRef? o) =>
JS<bool>("o => typeof o === 'function'", o);
@pragma("wasm:import", "dart2wasm.isJSInt8Array")
external bool isJSInt8Array(WasmExternRef? object);
bool isJSInt8Array(WasmExternRef? o) =>
JS<bool>("o => o instanceof Int8Array", o);
@pragma("wasm:import", "dart2wasm.isJSUint8Array")
external bool isJSUint8Array(WasmExternRef? object);
bool isJSUint8Array(WasmExternRef? o) =>
JS<bool>("o => o instanceof Uint8Array", o);
@pragma("wasm:import", "dart2wasm.isJSUint8ClampedArray")
external bool isJSUint8ClampedArray(WasmExternRef? object);
bool isJSUint8ClampedArray(WasmExternRef? o) =>
JS<bool>("o => o instanceof Uint8ClampedArray", o);
@pragma("wasm:import", "dart2wasm.isJSInt16Array")
external bool isJSInt16Array(WasmExternRef? object);
bool isJSInt16Array(WasmExternRef? o) =>
JS<bool>("o => o instanceof Int16Array", o);
@pragma("wasm:import", "dart2wasm.isJSUint16Array")
external bool isJSUint16Array(WasmExternRef? object);
bool isJSUint16Array(WasmExternRef? o) =>
JS<bool>("o => o instanceof Uint16Array", o);
@pragma("wasm:import", "dart2wasm.isJSInt32Array")
external bool isJSInt32Array(WasmExternRef? object);
bool isJSInt32Array(WasmExternRef? o) =>
JS<bool>("o => o instanceof Int32Array", o);
@pragma("wasm:import", "dart2wasm.isJSUint32Array")
external bool isJSUint32Array(WasmExternRef? object);
bool isJSUint32Array(WasmExternRef? o) =>
JS<bool>("o => o instanceof Uint32Array", o);
@pragma("wasm:import", "dart2wasm.isJSFloat32Array")
external bool isJSFloat32Array(WasmExternRef? object);
bool isJSFloat32Array(WasmExternRef? o) =>
JS<bool>("o => o instanceof Float32Array", o);
@pragma("wasm:import", "dart2wasm.isJSFloat64Array")
external bool isJSFloat64Array(WasmExternRef? object);
bool isJSFloat64Array(WasmExternRef? o) =>
JS<bool>("o => o instanceof Float64Array", o);
@pragma("wasm:import", "dart2wasm.isJSArrayBuffer")
external bool isJSArrayBuffer(WasmExternRef? object);
bool isJSArrayBuffer(WasmExternRef? o) =>
JS<bool>("o => o instanceof ArrayBuffer", o);
@pragma("wasm:import", "dart2wasm.isJSDataView")
external bool isJSDataView(WasmExternRef? object);
bool isJSDataView(WasmExternRef? o) =>
JS<bool>("o => o instanceof DataView", o);
@pragma("wasm:import", "dart2wasm.isJSArray")
external bool isJSArray(WasmExternRef? o);
bool isJSArray(WasmExternRef? o) => JS<bool>("o => o instanceof Array", o);
@pragma("wasm:import", "dart2wasm.isJSWrappedDartFunction")
external bool isJSWrappedDartFunction(WasmExternRef? o);
bool isJSWrappedDartFunction(WasmExternRef? o) => JS<bool>(
"o => typeof o === 'function' && o[jsWrappedDartFunctionSymbol] === true",
o);
@pragma("wasm:import", "dart2wasm.isJSObject")
external bool isJSObject(WasmExternRef? o);
bool isJSObject(WasmExternRef? o) => JS<bool>("o => o instanceof Object", o);
@pragma("wasm:import", "dart2wasm.isJSSimpleObject")
external bool isJSSimpleObject(WasmExternRef? o);
bool isJSSimpleObject(WasmExternRef? o) => JS<bool>("""o => {
const proto = Object.getPrototypeOf(o);
return proto === Object.prototype || proto === null;
}""", o);
@pragma("wasm:import", "dart2wasm.isJSRegExp")
external bool isJSRegExp(WasmExternRef? object);
bool isJSRegExp(WasmExternRef? o) => JS<bool>("o => o instanceof RegExp", o);
@pragma("wasm:import", "dart2wasm.areEqualInJS")
external bool areEqualInJS(WasmExternRef? l, WasmExternRef? r);
bool areEqualInJS(WasmExternRef? l, WasmExternRef? r) =>
JS<bool>("(l, r) => l === r", l, r);
// The JS runtime will run helpful conversion routines between refs and bool /
// double. In the longer term hopefully we can find a way to avoid the round
// trip.
@pragma("wasm:import", "dart2wasm.roundtrip")
external double toDartNumber(WasmExternRef? ref);
double toDartNumber(WasmExternRef? o) => JS<double>("o => o", o);
@pragma("wasm:import", "dart2wasm.roundtrip")
external WasmExternRef? toJSNumber(double d);
WasmExternRef? toJSNumber(double o) => JS<WasmExternRef?>("o => o", o);
@pragma("wasm:import", "dart2wasm.roundtrip")
external bool toDartBool(WasmExternRef? ref);
bool toDartBool(WasmExternRef? o) => JS<bool>("o => o", o);
@pragma("wasm:import", "dart2wasm.toJSBoolean")
external WasmExternRef? toJSBoolean(bool b);
WasmExternRef? toJSBoolean(bool b) => JS<WasmExternRef?>("b => !!b", b);
@pragma("wasm:import", "dart2wasm.objectLength")
external double objectLength(WasmExternRef? ref);
double objectLength(WasmExternRef? o) => JS<double>("o => o.length", o);
@pragma("wasm:import", "dart2wasm.objectReadIndex")
external WasmExternRef? objectReadIndex(WasmExternRef? ref, int index);
WasmExternRef? objectReadIndex(WasmExternRef? o, double index) =>
JS<WasmExternRef?>("(o, i) => o[i]", o, index);
@pragma("wasm:import", "dart2wasm.unwrapJSWrappedDartFunction")
external Object? unwrapJSWrappedDartFunction(WasmExternRef? f);
Object? unwrapJSWrappedDartFunction(WasmExternRef? f) =>
JS<Object?>("f => f.dartFunction", f);
@pragma("wasm:import", "dart2wasm.int8ArrayFromDartInt8List")
external WasmExternRef? jsInt8ArrayFromDartInt8List(Int8List list);
WasmExternRef? jsInt8ArrayFromDartInt8List(Int8List l) =>
JS<WasmExternRef?>('l => arrayFromDartList(Int8Array, l)', l);
@pragma("wasm:import", "dart2wasm.uint8ArrayFromDartUint8List")
external WasmExternRef? jsUint8ArrayFromDartUint8List(Uint8List list);
WasmExternRef? jsUint8ArrayFromDartUint8List(Uint8List l) =>
JS<WasmExternRef?>('l => arrayFromDartList(Uint8Array, l)', l);
@pragma("wasm:import", "dart2wasm.uint8ClampedArrayFromDartUint8ClampedList")
external WasmExternRef? jsUint8ClampedArrayFromDartUint8ClampedList(
Uint8ClampedList list);
WasmExternRef? jsUint8ClampedArrayFromDartUint8ClampedList(
Uint8ClampedList l) =>
JS<WasmExternRef?>('l => arrayFromDartList(Uint8ClampedArray, l)', l);
@pragma("wasm:import", "dart2wasm.int16ArrayFromDartInt16List")
external WasmExternRef? jsInt16ArrayFromDartInt16List(Int16List list);
WasmExternRef? jsInt16ArrayFromDartInt16List(Int16List l) =>
JS<WasmExternRef?>('l => arrayFromDartList(Int16Array, l)', l);
@pragma("wasm:import", "dart2wasm.uint16ArrayFromDartUint16List")
external WasmExternRef? jsUint16ArrayFromDartUint16List(Uint16List list);
WasmExternRef? jsUint16ArrayFromDartUint16List(Uint16List l) =>
JS<WasmExternRef?>('l => arrayFromDartList(Uint16Array, l)', l);
@pragma("wasm:import", "dart2wasm.int32ArrayFromDartInt32List")
external WasmExternRef? jsInt32ArrayFromDartInt32List(Int32List list);
WasmExternRef? jsInt32ArrayFromDartInt32List(Int32List l) =>
JS<WasmExternRef?>('l => arrayFromDartList(Int32Array, l)', l);
@pragma("wasm:import", "dart2wasm.uint32ArrayFromDartUint32List")
external WasmExternRef? jsUint32ArrayFromDartUint32List(Uint32List list);
WasmExternRef? jsUint32ArrayFromDartUint32List(Uint32List l) =>
JS<WasmExternRef?>('l => arrayFromDartList(Uint32Array, l)', l);
@pragma("wasm:import", "dart2wasm.float32ArrayFromDartFloat32List")
external WasmExternRef? jsFloat32ArrayFromDartFloat32List(Float32List list);
WasmExternRef? jsFloat32ArrayFromDartFloat32List(Float32List l) =>
JS<WasmExternRef?>('l => arrayFromDartList(Float32Array, l)', l);
@pragma("wasm:import", "dart2wasm.float64ArrayFromDartFloat64List")
external WasmExternRef? jsFloat64ArrayFromDartFloat64List(Float64List list);
WasmExternRef? jsFloat64ArrayFromDartFloat64List(Float64List l) =>
JS<WasmExternRef?>('l => arrayFromDartList(Float64Array, l)', l);
@pragma("wasm:import", "dart2wasm.dataViewFromDartByteData")
external WasmExternRef? jsDataViewFromDartByteData(
ByteData data, double byteLength);
WasmExternRef? jsDataViewFromDartByteData(ByteData data, double length) =>
JS<WasmExternRef?>("""(data, length) => {
const view = new DataView(new ArrayBuffer(length));
for (let i = 0; i < length; i++) {
view.setUint8(i, dartInstance.exports.\$byteDataGetUint8(data, i));
}
return view;
}""", data, length);
@pragma("wasm:import", "dart2wasm.arrayFromDartList")
external WasmExternRef? jsArrayFromDartList(List<Object?> list);
WasmExternRef? jsArrayFromDartList(List<Object?> l) =>
JS<WasmExternRef?>('l => arrayFromDartList(Array, l)', l);
@pragma("wasm:import", "dart2wasm.stringFromDartString")
external WasmExternRef? jsStringFromDartString(String string);
WasmExternRef? jsStringFromDartString(String s) =>
JS<WasmExternRef?>('stringFromDartString', s);
@pragma("wasm:import", "dart2wasm.stringToDartString")
external String jsStringToDartString(WasmExternRef? string);
String jsStringToDartString(WasmExternRef? s) =>
JS<String>('stringToDartString', s);
@pragma("wasm:import", "dart2wasm.eval")
external void evalRaw(WasmExternRef? code);
WasmExternRef? newObjectRaw() => JS<WasmExternRef?>('() => ({})');
@pragma("wasm:import", "dart2wasm.newObject")
external WasmExternRef? newObjectRaw();
WasmExternRef? newArrayRaw() => JS<WasmExternRef?>('() => []');
@pragma("wasm:import", "dart2wasm.newArray")
external WasmExternRef? newArrayRaw();
WasmExternRef? globalThisRaw() => JS<WasmExternRef?>('() => globalThis');
@pragma("wasm:import", "dart2wasm.globalThis")
external WasmExternRef? globalThisRaw();
WasmExternRef? callConstructorVarArgsRaw(
WasmExternRef? o, WasmExternRef? args) =>
// Apply bind to the constructor. We pass `null` as the first argument
// to `bind.apply` because this is `bind`'s unused context
// argument(`new` will explicitly create a new context).
JS<WasmExternRef?>("""(constructor, args) => {
const factoryFunction = constructor.bind.apply(
constructor, [null, ...args]);
return new factoryFunction();
}""", o, args);
@pragma("wasm:import", "dart2wasm.callConstructorVarArgs")
external WasmExternRef? callConstructorVarArgsRaw(
WasmExternRef? o, WasmExternRef? args);
bool hasPropertyRaw(WasmExternRef? o, WasmExternRef? p) =>
JS<bool>("(o, p) => p in o", o, p);
@pragma("wasm:import", "dart2wasm.safeCallConstructorVarArgs")
external WasmExternRef? safeCallConstructorVarArgsRaw(
WasmExternRef? o, WasmExternRef? args);
WasmExternRef? getPropertyRaw(WasmExternRef? o, WasmExternRef? p) =>
JS<WasmExternRef?>("(o, p) => o[p]", o, p);
@pragma("wasm:import", "dart2wasm.hasProperty")
external bool hasPropertyRaw(WasmExternRef? o, WasmExternRef? name);
WasmExternRef? setPropertyRaw(
WasmExternRef? o, WasmExternRef? p, WasmExternRef? v) =>
JS<WasmExternRef?>("(o, p, v) => o[p] = v", o, p, v);
@pragma("wasm:import", "dart2wasm.getProperty")
external WasmExternRef? getPropertyRaw(WasmExternRef? o, WasmExternRef? name);
WasmExternRef? callMethodVarArgsRaw(
WasmExternRef? o, WasmExternRef? method, WasmExternRef? args) =>
JS<WasmExternRef?>("(o, m, a) => o[m].apply(o, a)", o, method, args);
@pragma("wasm:import", "dart2wasm.setProperty")
external WasmExternRef? setPropertyRaw(
WasmExternRef? o, WasmExternRef? name, WasmExternRef? value);
String stringify(WasmExternRef? object) =>
JS<String>("o => stringToDartString(String(o))", object);
@pragma("wasm:import", "dart2wasm.callMethodVarArgs")
external WasmExternRef? callMethodVarArgsRaw(
WasmExternRef? o, WasmExternRef? method, WasmExternRef? args);
@pragma("wasm:import", "dart2wasm.stringify")
external String stringify(WasmExternRef? object);
@pragma("wasm:import", "dart2wasm.objectKeys")
external WasmExternRef? objectKeysRaw(WasmExternRef? o);
@pragma("wasm:import", "dart2wasm.promiseThen")
external void promiseThen(WasmExternRef? promise, WasmExternRef? successFunc,
WasmExternRef? failureFunc);
void promiseThen(WasmExternRef? promise, WasmExternRef? successFunc,
WasmExternRef? failureFunc) =>
JS<void>("(p, s, f) => p.then(s, f)", promise, successFunc, failureFunc);
// Currently, `allowInterop` returns a Function type. This is unfortunate for
// Dart2wasm because it means arbitrary Dart functions can flow to JS util
@ -493,7 +482,7 @@ List<double> jsFloatTypedArrayToDartFloatTypedData(
int length = objectLength(ref).toInt();
List<double> list = makeTypedData(length);
for (int i = 0; i < length; i++) {
list[i] = toDartNumber(objectReadIndex(ref, i));
list[i] = toDartNumber(objectReadIndex(ref, i.toDouble()));
}
return list;
}
@ -503,13 +492,14 @@ List<int> jsIntTypedArrayToDartIntTypedData(
int length = objectLength(ref).toInt();
List<int> list = makeTypedData(length);
for (int i = 0; i < length; i++) {
list[i] = toDartNumber(objectReadIndex(ref, i)).toInt();
list[i] = toDartNumber(objectReadIndex(ref, i.toDouble())).toInt();
}
return list;
}
List<Object?> toDartList(WasmExternRef? ref) => List<Object?>.generate(
objectLength(ref).round(), (int n) => dartifyRaw(objectReadIndex(ref, n)));
objectLength(ref).round(),
(int n) => dartifyRaw(objectReadIndex(ref, n.toDouble())));
// These two trivial helpers are needed to work around an issue with tearing off
// functions that take / return [WasmExternRef].

View file

@ -176,8 +176,9 @@ Object? objectGetPrototypeOf(Object? object) => throw 'unimplemented';
Object? get objectPrototype => throw 'unimplemented';
@patch
List<Object?> objectKeys(Object? object) =>
dartifyRaw(objectKeysRaw(jsifyRaw(object))) as List<Object?>;
List<Object?> objectKeys(Object? o) =>
dartifyRaw(JS<WasmExternRef?>('o => Object.keys(o)', jsifyRaw(o)))
as List<Object?>;
@patch
Object? dartify(Object? object) {
@ -245,7 +246,7 @@ Object? dartify(Object? object) {
convertedObjects[o] = dartList;
int length = getProperty<double>(o, 'length').toInt();
for (int i = 0; i < length; i++) {
dartList.add(convert(JSValue.box(objectReadIndex(ref, i))));
dartList.add(convert(JSValue.box(objectReadIndex(ref, i.toDouble()))));
}
return dartList;
} else {

View file

@ -5,5 +5,5 @@
part of "internal_patch.dart";
@patch
@pragma("wasm:import", "dart2wasm.printToConsole")
external void printToConsole(String line);
void printToConsole(String line) =>
JS<void>('s => console.log(stringFromDartString(s))');

View file

@ -11,8 +11,17 @@ part of dart._js_helper;
/// Returns a string for a RegExp pattern that matches [string]. This is done by
/// escaping all RegExp metacharacters.
@pragma('wasm:import', 'dart2wasm.quoteStringForRegExp')
external String quoteStringForRegExp(String string);
String quoteStringForRegExp(String string) =>
// This method is optimized to test before replacement, which should be
// much faster. This might be worth measuring in real world use cases
// though.
JS<String>(r"""s => {
let jsString = stringFromDartString(s);
if (/[[\]{}()*+?.\\^$|]/.test(jsString)) {
jsString = jsString.replace(/[[\]{}()*+?.\\^$|]/g, '\\$&');
}
return stringToDartString(jsString);
}""", string);
class JSNativeMatch extends JSArray {
JSNativeMatch(WasmExternRef ref) : super(ref);
@ -99,8 +108,13 @@ class JSSyntaxRegExp implements RegExp {
String modifiers = '$m$i$u$s$g';
// The call to create the regexp is wrapped in a try catch so we can
// reformat the exception if need be.
WasmExternRef? result = safeCallConstructorVarArgsRaw(
getConstructorRaw('RegExp'), [source, modifiers].toExternRef());
WasmExternRef? result = JS<WasmExternRef?>("""(s, m) => {
try {
return new RegExp(s, m);
} catch (e) {
return String(e);
}
}""", source.toExternRef(), modifiers.toExternRef());
if (isJSRegExp(result)) return JSNativeRegExp(result!);
// The returned value is the stringified JavaScript exception. Turn it into
// a Dart exception.

View file

@ -4,14 +4,22 @@
part of "core_patch.dart";
@pragma("wasm:import", "dart2wasm.getCurrentStackTrace")
external String _getCurrentStackTrace();
@patch
class StackTrace {
@patch
@pragma("wasm:entry-point")
static StackTrace get current {
return _StringStackTrace(_getCurrentStackTrace());
// `Error` should be supported in most browsers. A possible future
// optimization we could do is to just save the `Error` object here, and
// stringify the stack trace when it is actually used
//
// Note: We remove the last three lines of the stack trace to prevent
// including `Error`, `getCurrentStackTrace`, and `StackTrace.current` in
// the stack trace.
return _StringStackTrace(JS<String>(r"""() => {
let stackString = new Error().stack.toString();
let userStackString = stackString.split('\n').slice(3).join('\n');
return stringToDartString(userStackString);
}"""));
}
}

View file

@ -4,8 +4,7 @@
part of "core_patch.dart";
@pragma("wasm:import", "dart2wasm.performanceNow")
external double _performanceNow();
double _performanceNow() => JS<double>("performance.now");
@patch
class Stopwatch {

View file

@ -13,11 +13,11 @@ const int _maxLatin1 = 0xff;
const int _maxUtf16 = 0xffff;
const int _maxUnicode = 0x10ffff;
@pragma("wasm:import", "dart2wasm.toUpperCase")
external String _toUpperCase(String string);
String _toUpperCase(String string) => JS<String>(
"s => stringToDartString(stringFromDartString(s).toUpperCase())", string);
@pragma("wasm:import", "dart2wasm.toLowerCase")
external String _toLowerCase(String string);
String _toLowerCase(String string) => JS<String>(
"s => stringToDartString(stringFromDartString(s).toLowerCase())", string);
@patch
class String {

View file

@ -4,17 +4,17 @@
part of "core_patch.dart";
@pragma("wasm:import", "dart2wasm.isWindows")
external bool _isWindowsRaw();
@pragma("wasm:import", "dart2wasm.getCurrentUri")
external String? _currentUriRaw();
@patch
class Uri {
@patch
static Uri get base {
String? currentUri = _currentUriRaw();
String? currentUri = JS<String?>("""() => {
// On browsers return `globalThis.location.href`
if (globalThis.location != null) {
return stringToDartString(globalThis.location.href);
}
return null;
}""");
if (currentUri != null) {
return Uri.parse(currentUri);
}
@ -27,7 +27,11 @@ class _Uri {
@patch
static bool get _isWindows => _isWindowsCached;
static final bool _isWindowsCached = _isWindowsRaw();
static final bool _isWindowsCached = JS<bool>("""() => {
return typeof process != undefined &&
Object.prototype.toString.call(process) == "[object process]" &&
process.platform == "win32"
}""");
// Matches a String that _uriEncodes to itself regardless of the kind of
// component. This corresponds to [_unreservedTable], i.e. characters that