[dart2wasm] Add support for testing dart2wasm with JSC

JSC only supports `print()` but not `console.log()`.

=> The changes to `printToConsole()` are therefore extended
to check for `console.log()` as well as `print()`.
=> This is extending it to a broader subset of dart2js's print

Change-Id: I7efa697477aa60e473d01716b104fc1526035c67
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/347283
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
This commit is contained in:
Martin Kustermann 2024-01-22 12:04:37 +00:00 committed by Commit Queue
parent f706ff4ee2
commit 3a314331a7
8 changed files with 109 additions and 17 deletions

View file

@ -11,6 +11,14 @@
// -- /abs/path/to/<dart_module>.mjs <dart_module>.wasm [<ffi_module>.wasm] \
// [-- Dart commandline arguments...]
//
// Run as follows on JSC:
//
// $> export JSC_useWebAssemblyTypedFunctionReferences=1
// $> export JSC_useWebAssemblyExtendedConstantExpressions=1
// $> export JSC_useWebAssemblyGC=1
// $> jsc run_wasm.js -- <dart_module>.ms <dart_module>.wasm \
// [-- Dart commandline arguments...]
//
// Run as follows on JSShell:
//
// $> js run_wasm.js \
@ -36,6 +44,19 @@ const jsRuntimeArg = 0;
const wasmArg = 1;
const ffiArg = 2;
// This script is intended to be used by D8, JSShell or JSC. We distinguish
// them by the functions they offer to read files:
//
// Engine | Shell | FileRead | Arguments
// --------------------------------------------------------------
// V8 | D8 | readbuffer | arguments (arg0 arg1)
// JavaScriptCore | JSC | readFile | arguments (arg0 arg1)
// SpiderMonkey | JSShell | readRelativeToScript | scriptArgs (-- arg0 arg1)
//
const isD8 = (typeof readbuffer === "function");
const isJSC = (typeof readFile === "function");
const isJSShell = (typeof readRelativeToScript === "function");
// d8's `setTimeout` doesn't work as expected (it doesn't wait before calling
// the callback), and d8 also doesn't have `setInterval` and `queueMicrotask`.
// So we define our own event loop with these functions.
@ -282,10 +303,16 @@ const ffiArg = 2;
}
async function eventLoop(action) {
if (isJSC) asyncTestStart(1);
while (action) {
try {
await action();
} catch (e) {
// JSC doesn't report/print uncaught async exceptions for some reason.
if (isJSC) {
print('Error: ' + e);
print('Stack: ' + e.stack);
}
if (typeof onerror == "function") {
onerror(e, null, -1);
} else {
@ -294,6 +321,7 @@ const ffiArg = 2;
}
action = nextEvent();
}
if (isJSC) asyncTestPassed();
}
// Global properties. "self" refers to the global object, so adding a
@ -315,17 +343,11 @@ const ffiArg = 2;
self.dartUseDateNowForTicks = true;
})(this, []);
// This script is intended to be used by either D8 or JSShell. We distinguish
// the two by seeing whether the global `arguments` exists (D8 uses `arguments`
// and JsShell uses `scriptArgs`).
var isD8 = (typeof arguments != "undefined");
// We would like this itself to be a ES module rather than a script, but
// unfortunately d8 does not return a failed error code if an unhandled
// exception occurs asynchronously in an ES module.
const main = async () => {
var args = isD8 ? arguments : scriptArgs;
var args = (isD8 || isJSC) ? arguments : scriptArgs;
var dartArgs = [];
const argsSplit = args.indexOf("--");
if (argsSplit != -1) {
@ -336,7 +358,14 @@ const main = async () => {
const dart2wasm = await import(args[jsRuntimeArg]);
function compile(filename) {
// Create a Wasm module from the binary wasm file.
var bytes = isD8 ? readbuffer(filename) : readRelativeToScript(filename, "binary") ;
var bytes;
if (isJSC) {
bytes = readFile(filename, "binary");
} else if (isD8) {
bytes = readbuffer(filename);
} else {
bytes = readRelativeToScript(filename, "binary");
}
return new WebAssembly.Module(bytes);
}

View file

@ -67,6 +67,20 @@ const jsRuntimeBlobPart2JSCM = r'''
''';
const jsRuntimeBlobPart3 = r'''
// Prints to the console
function printToConsole(value) {
if (typeof console == "object" && typeof console.log != "undefined") {
console.log(value);
return;
}
if (typeof print == "function") {
print(value);
return;
}
throw "Unable to print message: " + js;
}
// Converts a Dart List to a JS array. Any Dart objects will be converted, but
// this will be cheap for JSValues.
function arrayFromDartList(constructor, list) {
@ -96,7 +110,7 @@ const jsRuntimeBlobPart3 = r'''
}
if (WebAssembly.String === undefined) {
console.log("WebAssembly.String is undefined, adding polyfill");
printToConsole("WebAssembly.String is undefined, adding polyfill");
WebAssembly.String = {
"charCodeAt": (s, i) => s.charCodeAt(i),
"compare": (s1, s2) => {

View file

@ -868,6 +868,7 @@ class Compiler extends NamedEnum {
case Compiler.dart2wasm:
return const [
Runtime.none,
Runtime.jsc,
Runtime.jsshell,
Runtime.d8,
Runtime.chrome,
@ -980,6 +981,7 @@ class Runtime extends NamedEnum {
static const flutter = Runtime._('flutter');
static const dartPrecompiled = Runtime._('dart_precompiled');
static const d8 = Runtime._('d8');
static const jsc = Runtime._('jsc');
static const jsshell = Runtime._('jsshell');
static const firefox = Runtime._('firefox');
static const chrome = Runtime._('chrome');
@ -998,6 +1000,7 @@ class Runtime extends NamedEnum {
flutter,
dartPrecompiled,
d8,
jsc,
jsshell,
firefox,
chrome,
@ -1035,7 +1038,7 @@ class Runtime extends NamedEnum {
bool get isSafari => name.startsWith("safari");
/// Whether this runtime is a command-line JavaScript environment.
bool get isJSCommandLine => const [d8, jsshell].contains(this);
bool get isJSCommandLine => const [d8, jsc, jsshell].contains(this);
/// If the runtime doesn't support `Window.open`, we use iframes instead.
bool get requiresIFrame => !const [ie11, ie10].contains(this);
@ -1063,6 +1066,9 @@ class Runtime extends NamedEnum {
case chromeOnAndroid:
return Compiler.dart2js;
case jsc:
return Compiler.dart2wasm;
case none:
// If we aren't running it, we probably just want to analyze it.
return Compiler.dart2analyzer;

View file

@ -610,11 +610,12 @@ class Dart2WasmCompilerConfiguration extends CompilerConfiguration {
final filename = artifact!.filename;
final args = testFile.dartOptions;
final isD8 = runtimeConfiguration is D8RuntimeConfiguration;
final isJSC = runtimeConfiguration is JSCRuntimeConfiguration;
return [
if (isD8) '--turboshaft-wasm',
if (isD8) '--experimental-wasm-imported-strings',
'pkg/dart2wasm/bin/run_wasm.js',
if (isD8) '--',
if (isD8 || isJSC) '--',
'${filename.substring(0, filename.lastIndexOf('.'))}.mjs',
filename,
...testFile.sharedObjects

View file

@ -87,8 +87,9 @@ fasta: Compile using CFE for errors, but do not run.
help: '''Where the tests should be run.
vm: Run Dart code on the standalone Dart VM.
dart_precompiled: Run a precompiled snapshot on the VM without a JIT.
d8: Run JavaScript from the command line using v8.
jsshell: Run JavaScript from the command line using Firefox js-shell.
d8: Run JavaScript from the command line using Chrome's v8.
jsc: Run JavaScript from the command line using Safari/WebKit's jsc.
jsshell: Run JavaScript from the command line using Firefox's js-shell.
firefox:
chrome:

View file

@ -31,6 +31,9 @@ abstract class RuntimeConfiguration {
// TODO(ahe): Replace this with one or more browser runtimes.
return DummyRuntimeConfiguration();
case Runtime.jsc:
return JSCRuntimeConfiguration(configuration.compiler);
case Runtime.jsshell:
return JsshellRuntimeConfiguration(configuration.compiler);
@ -144,6 +147,15 @@ abstract class RuntimeConfiguration {
return d8;
}
String get jscFileName {
final d8Dir = Repository.dir.append('third_party/jsc');
final d8Path = d8Dir.append(
'${Platform.operatingSystem}/${Architecture.host}/jsc$executableExtension');
final d8 = d8Path.toNativePath();
TestUtils.ensureExists(d8, _configuration);
return d8;
}
String get jsShellFileName {
var executable = 'jsshell$executableExtension';
var jsshellDir = Repository.uri.resolve("tools/testing/bin").path;
@ -218,6 +230,35 @@ class D8RuntimeConfiguration extends CommandLineJavaScriptRuntime {
}
}
/// Safari/WebKit/JavaScriptCore-based development shell (jsc).
class JSCRuntimeConfiguration extends CommandLineJavaScriptRuntime {
final Compiler compiler;
JSCRuntimeConfiguration(this.compiler) : super('jsc');
@override
List<Command> computeRuntimeCommands(
CommandArtifact? artifact,
List<String> arguments,
Map<String, String> environmentOverrides,
List<String> extraLibs,
bool isCrashExpected) {
checkArtifact(artifact!);
if (compiler != Compiler.dart2wasm) {
throw 'No test runner setup for jsc + dart2js yet';
}
final environment = {
...environmentOverrides,
'JSC_useWebAssemblyTypedFunctionReferences': '1',
'JSC_useWebAssemblyExtendedConstantExpression': '1',
'JSC_useWebAssemblyGC': '1',
};
return [
Dart2WasmCommandLineCommand(moniker, jscFileName, arguments, environment)
];
}
}
/// Firefox/SpiderMonkey-based development shell (jsshell).
class JsshellRuntimeConfiguration extends CommandLineJavaScriptRuntime {
final Compiler compiler;

View file

@ -6,4 +6,4 @@ part of "internal_patch.dart";
@patch
void printToConsole(String line) =>
JS<void>('s => console.log(stringFromDartString(s))');
JS<void>('s => printToConsole(stringFromDartString(s))');

View file

@ -451,7 +451,7 @@
"timeout": 240
}
},
"dart2wasm-(linux|mac|win)-(d8|jsshell|chrome|firefox)": {
"dart2wasm-(linux|mac|win)-(d8|jsshell|jsc|chrome|firefox)": {
"options": {
"host-checked": true,
"dart2wasm-options": [
@ -460,7 +460,7 @@
"timeout": 240
}
},
"dart2wasm-(linux|mac|win)-optimized-(d8|jsshell|chrome|firefox)": {
"dart2wasm-(linux|mac|win)-optimized-(d8|jsshell|jsc|chrome|firefox)": {
"options": {
"dart2wasm-options": [
"--optimize"
@ -469,7 +469,7 @@
"timeout": 240
}
},
"dart2wasm-(linux|mac|win)-jscm-(d8|jsshell|chrome|firefox)": {
"dart2wasm-(linux|mac|win)-jscm-(d8|jsshell|jsc|chrome|firefox)": {
"options": {
"dart2wasm-options": [
"--no-optimize",