diff --git a/Dart/benchmark.js b/Dart/benchmark.js index 5c3d73d0..dd4e986e 100644 --- a/Dart/benchmark.js +++ b/Dart/benchmark.js @@ -2,8 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Excerpt from `build/run_wasm.js` to add own task queue implementation, since -// `setTimeout` and `queueMicrotask` are not always available in shells. +// Excerpt from `wasm_gc_benchmarks/tools/run_wasm.js` to add own task queue +// implementation, since `setTimeout` and `queueMicrotask` are not always +// available in shells. +// TODO: Now (2025-08-14) that all shells have `setTimeout` available, can we +// remove this? Talk to Dart2wasm folks. function addTaskQueue(self) { "use strict"; diff --git a/Dart/build.log b/Dart/build.log index c255362e..2904e010 100644 --- a/Dart/build.log +++ b/Dart/build.log @@ -1,5 +1,5 @@ --e Built on Wed Aug 13 13:03:46 CDT 2025 -Cloning into 'wasm_gc_benchmarks'... -13951e1 Roll new version of flute and regenerate flute-based benchmarks -Copying files from wasm_gc_benchmarks/ into build/ -Build success +Built on Thu Aug 14 04:47:37 PM CEST 2025 +Cloning into 'wasm_gc_benchmarks'... +13951e1 Roll new version of flute and regenerate flute-based benchmarks +Copying files from wasm_gc_benchmarks/ into build/ +Build success diff --git a/Dart/build.sh b/Dart/build.sh index 8699b8fa..7d7efd73 100755 --- a/Dart/build.sh +++ b/Dart/build.sh @@ -16,8 +16,6 @@ popd echo "Copying files from wasm_gc_benchmarks/ into build/" | tee -a "$BUILD_LOG" mkdir -p build/ | tee -a "$BUILD_LOG" -# Generic Dart2wasm runner. -cp wasm_gc_benchmarks/tools/run_wasm.js build/ | tee -a "$BUILD_LOG" # Two Flute benchmark applications: complex and todomvc cp wasm_gc_benchmarks/benchmarks-out/flute.complex.dart2wasm.{mjs,wasm} build/ | tee -a "$BUILD_LOG" cp wasm_gc_benchmarks/benchmarks-out/flute.todomvc.dart2wasm.{mjs,wasm} build/ | tee -a "$BUILD_LOG" diff --git a/Dart/build/run_wasm.js b/Dart/build/run_wasm.js deleted file mode 100644 index 8b2eb936..00000000 --- a/Dart/build/run_wasm.js +++ /dev/null @@ -1,424 +0,0 @@ -// Copyright (c) 2024, 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. -// -// Runner V8/JSShell script for testing dart2wasm, takes ".wasm" files as -// arguments. -// -// Run as follows on D8: -// -// $> d8 run_wasm.js \ -// -- /abs/path/to/.mjs .wasm [.wasm] \ -// [-- Dart commandline arguments...] -// -// Run as follows on JSC: -// -// $> jsc run_wasm.js -- .mjs .wasm [.wasm] \ -// [-- Dart commandline arguments...] -// -// Run as follows on JSShell: -// -// $> js run_wasm.js \ -// /abs/path/to/.mjs .wasm [.wasm] \ -// [-- Dart commandline arguments...] -// -// (Notice the missing -- here!) -// -// Please note we require an absolute path for the JS runtime. This is a -// workaround for a discrepancy in D8. Specifically, `import`(used to load .mjs -// files) searches for imports relative to run_wasm.js, but `readbuffer`(used to -// read in .wasm files) searches relative to CWD. A path relative to -// `run_wasm.js` will also work. -// -// Or with the `run_dart2wasm_d8` helper script: -// -// $> sdk/bin/run_dart2wasm_d8 .wasm [.wasm] -// -// If an FFI module is specified, it will be instantiated first, and its -// exports will be supplied as imports to the Dart module under the 'ffi' -// module name. -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"); - -if (isD8) { - // D8's performance.measure is API incompatible with the browser version. - // - // (see also dart2js's `sdk/**/js_runtime/lib/preambles/d8.js`) - delete performance.measure; -} - -function readFileContentsAsBytes(filename) { - var buffer; - if (isJSC) { - buffer = readFile(filename, "binary"); - } else if (isD8) { - buffer = readbuffer(filename); - } else { - buffer = readRelativeToScript(filename, "binary"); - } - return new Uint8Array(buffer, 0, buffer.byteLength); -} - -var args = (isD8 || isJSC) ? arguments : scriptArgs; -var dartArgs = []; -const argsSplit = args.indexOf("--"); -if (argsSplit != -1) { - dartArgs = args.slice(argsSplit + 1); - args = args.slice(0, argsSplit); -} - -// 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. -// -// The code below is copied form dart2js, with some modifications: -// sdk/lib/_internal/js_runtime/lib/preambles/d8.js -(function(self, scriptArguments) { - // Using strict mode to avoid accidentally defining global variables. - "use strict"; // Should be first statement of this function. - - // Task queue as cyclic list queue. - var taskQueue = new Array(8); // Length is power of 2. - var head = 0; - var tail = 0; - var mask = taskQueue.length - 1; - - function addTask(elem) { - taskQueue[head] = elem; - head = (head + 1) & mask; - if (head == tail) _growTaskQueue(); - } - - function removeTask() { - if (head == tail) return; - var result = taskQueue[tail]; - taskQueue[tail] = undefined; - tail = (tail + 1) & mask; - return result; - } - - function _growTaskQueue() { - // head == tail. - var length = taskQueue.length; - var split = head; - taskQueue.length = length * 2; - if (split * 2 < length) { // split < length / 2 - for (var i = 0; i < split; i++) { - taskQueue[length + i] = taskQueue[i]; - taskQueue[i] = undefined; - } - head += length; - } else { - for (var i = split; i < length; i++) { - taskQueue[length + i] = taskQueue[i]; - taskQueue[i] = undefined; - } - tail += length; - } - mask = taskQueue.length - 1; - } - - // Mapping from timer id to timer function. - // The timer id is written on the function as .$timerId. - // That field is cleared when the timer is cancelled, but it is not returned - // from the queue until its time comes. - var timerIds = {}; - var timerIdCounter = 1; // Counter used to assign ids. - - // Zero-timer queue as simple array queue using push/shift. - var zeroTimerQueue = []; - - function addTimer(f, ms) { - ms = Math.max(0, ms); - var id = timerIdCounter++; - // A callback can be scheduled at most once. - // (console.assert is only available on D8) - if (isD8) console.assert(f.$timerId === undefined); - f.$timerId = id; - timerIds[id] = f; - if (ms == 0 && !isNextTimerDue()) { - zeroTimerQueue.push(f); - } else { - addDelayedTimer(f, ms); - } - return id; - } - - function nextZeroTimer() { - while (zeroTimerQueue.length > 0) { - var action = zeroTimerQueue.shift(); - if (action.$timerId !== undefined) return action; - } - } - - function nextEvent() { - var action = removeTask(); - if (action) { - return action; - } - do { - action = nextZeroTimer(); - if (action) break; - var nextList = nextDelayedTimerQueue(); - if (!nextList) { - return; - } - var newTime = nextList.shift(); - advanceTimeTo(newTime); - zeroTimerQueue = nextList; - } while (true) - var id = action.$timerId; - clearTimerId(action, id); - return action; - } - - // Mocking time. - var timeOffset = 0; - var now = function() { - // Install the mock Date object only once. - // Following calls to "now" will just use the new (mocked) Date.now - // method directly. - installMockDate(); - now = Date.now; - return Date.now(); - }; - var originalDate = Date; - var originalNow = originalDate.now; - - function advanceTimeTo(time) { - var now = originalNow(); - if (timeOffset < time - now) { - timeOffset = time - now; - } - } - - function installMockDate() { - var NewDate = function Date(Y, M, D, h, m, s, ms) { - if (this instanceof Date) { - // Assume a construct call. - switch (arguments.length) { - case 0: return new originalDate(originalNow() + timeOffset); - case 1: return new originalDate(Y); - case 2: return new originalDate(Y, M); - case 3: return new originalDate(Y, M, D); - case 4: return new originalDate(Y, M, D, h); - case 5: return new originalDate(Y, M, D, h, m); - case 6: return new originalDate(Y, M, D, h, m, s); - default: return new originalDate(Y, M, D, h, m, s, ms); - } - } - return new originalDate(originalNow() + timeOffset).toString(); - }; - NewDate.UTC = originalDate.UTC; - NewDate.parse = originalDate.parse; - NewDate.now = function now() { return originalNow() + timeOffset; }; - NewDate.prototype = originalDate.prototype; - originalDate.prototype.constructor = NewDate; - Date = NewDate; - } - - // Heap priority queue with key index. - // Each entry is list of [timeout, callback1 ... callbackn]. - var timerHeap = []; - var timerIndex = {}; - - function addDelayedTimer(f, ms) { - var timeout = now() + ms; - var timerList = timerIndex[timeout]; - if (timerList == null) { - timerList = [timeout, f]; - timerIndex[timeout] = timerList; - var index = timerHeap.length; - timerHeap.length += 1; - bubbleUp(index, timeout, timerList); - } else { - timerList.push(f); - } - } - - function isNextTimerDue() { - if (timerHeap.length == 0) return false; - var head = timerHeap[0]; - return head[0] < originalNow() + timeOffset; - } - - function nextDelayedTimerQueue() { - if (timerHeap.length == 0) return null; - var result = timerHeap[0]; - var last = timerHeap.pop(); - if (timerHeap.length > 0) { - bubbleDown(0, last[0], last); - } - return result; - } - - function bubbleUp(index, key, value) { - while (index != 0) { - var parentIndex = (index - 1) >> 1; - var parent = timerHeap[parentIndex]; - var parentKey = parent[0]; - if (key > parentKey) break; - timerHeap[index] = parent; - index = parentIndex; - } - timerHeap[index] = value; - } - - function bubbleDown(index, key, value) { - while (true) { - var leftChildIndex = index * 2 + 1; - if (leftChildIndex >= timerHeap.length) break; - var minChildIndex = leftChildIndex; - var minChild = timerHeap[leftChildIndex]; - var minChildKey = minChild[0]; - var rightChildIndex = leftChildIndex + 1; - if (rightChildIndex < timerHeap.length) { - var rightChild = timerHeap[rightChildIndex]; - var rightKey = rightChild[0]; - if (rightKey < minChildKey) { - minChildIndex = rightChildIndex; - minChild = rightChild; - minChildKey = rightKey; - } - } - if (minChildKey > key) break; - timerHeap[index] = minChild; - index = minChildIndex; - } - timerHeap[index] = value; - } - - function addInterval(f, ms) { - ms = Math.max(0, ms); - var id = timerIdCounter++; - function repeat() { - // Reactivate with the same id. - repeat.$timerId = id; - timerIds[id] = repeat; - addDelayedTimer(repeat, ms); - f(); - } - repeat.$timerId = id; - timerIds[id] = repeat; - addDelayedTimer(repeat, ms); - return id; - } - - function cancelTimer(id) { - var f = timerIds[id]; - if (f == null) return; - clearTimerId(f, id); - } - - function clearTimerId(f, id) { - f.$timerId = undefined; - delete timerIds[id]; - } - - async function eventLoop(action) { - 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 { - throw e; - } - } - action = nextEvent(); - } - } - - // Global properties. "self" refers to the global object, so adding a - // property to "self" defines a global variable. - self.self = self; - self.dartMainRunner = function(main, ignored_args) { - // Initialize. - var action = async function() { await main(scriptArguments, null); } - eventLoop(action); - }; - self.setTimeout = addTimer; - self.clearTimeout = cancelTimer; - self.setInterval = addInterval; - self.clearInterval = cancelTimer; - self.queueMicrotask = addTask; - self.readFileContentsAsBytes = readFileContentsAsBytes; - - self.location = {} - self.location.href = 'file://' + args[wasmArg]; - - // Signals `Stopwatch._initTicker` to use `Date.now` to get ticks instead of - // `performance.now`, as it's not available in d8. - self.dartUseDateNowForTicks = true; -})(this, []); - -// 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 () => { - const dart2wasm = await import(args[jsRuntimeArg]); - - /// Returns whether the `js-string` built-in is supported. - function detectImportedStrings() { - let bytes = [ - 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, - 0, 2, 23, 1, 14, 119, 97, 115, 109, 58, 106, 115, 45, - 115, 116, 114, 105, 110, 103, 4, 99, 97, 115, 116, 0, 0 - ]; - return !WebAssembly.validate( - new Uint8Array(bytes), {builtins: ['js-string']}); - } - - function compile(filename, withJsStringBuiltins) { - // Create a Wasm module from the binary Wasm file. - return WebAssembly.compile( - readFileContentsAsBytes(filename), - withJsStringBuiltins ? {builtins: ['js-string']} : {} - ); - } - - globalThis.window ??= globalThis; - - let importObject = {}; - - // Is an FFI module specified? - if (args.length > 2) { - // Instantiate FFI module. - var ffiInstance = await WebAssembly.instantiate(await compile(args[ffiArg], false), {}); - // Make its exports available as imports under the 'ffi' module name. - importObject.ffi = ffiInstance.exports; - } - - // Instantiate the Dart module, importing from the global scope. - var dartInstance = await dart2wasm.instantiate( - compile(args[wasmArg], detectImportedStrings()), - Promise.resolve(importObject), - ); - - // Call `main`. If tasks are placed into the event loop (by scheduling tasks - // explicitly or awaiting Futures), these will automatically keep the script - // alive even after `main` returns. - await dart2wasm.invoke(dartInstance, ...dartArgs); -}; - -dartMainRunner(main, []); diff --git a/JetStreamDriver.js b/JetStreamDriver.js index 387a4664..e8420a5b 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -2063,6 +2063,11 @@ let BENCHMARKS = [ }, iterations: 15, worstCaseCount: 2, + // Not run by default because the `CupertinoTimePicker` widget is very allocation-heavy, + // leading to an unrealistic GC-dominated workload. See + // https://github.com/WebKit/JetStream/pull/97#issuecomment-3139924169 + // The todomvc workload below is less allocation heavy and a replacement for now. + // TODO: Revisit, once Dart/Flutter worked on this widget or workload. tags: ["Wasm"], }), new WasmEMCCBenchmark({