// Copyright 2025 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Excerpt from `polyfills.mjs` from the upstream Kotlin compose-multiplatform // benchmark directory, with minor changes for JetStream. globalThis.window ??= globalThis; globalThis.navigator ??= {}; if (!globalThis.navigator.languages) { globalThis.navigator.languages = ['en-US', 'en']; globalThis.navigator.userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'; globalThis.navigator.platform = "MacIntel"; } // Compose reads `window.isSecureContext` in its Clipboard feature: globalThis.isSecureContext = false; // Disable explicit GC (it wouldn't work in browsers anyway). globalThis.gc = () => { // DEBUG // console.log("gc()"); } class URL { href; constructor(url, base) { // DEBUG // console.log('URL', url, base); this.href = url; } } globalThis.URL = URL; // We always polyfill `fetch` and `instantiateStreaming` for consistency between // engine shells and browsers and to avoid introducing network latency into the // first iteration / instantiation measurement. // The downside is that this doesn't test streaming Wasm instantiation, which we // are willing to accept. let preload = { /* Initialized in init() below due to async. */ }; const originalFetch = globalThis.fetch ?? function(url) { throw new Error("no fetch available"); } globalThis.fetch = async function(url) { // DEBUG // console.log('fetch', url); // Redirect some paths to cached/preloaded resources. if (preload[url]) { return { ok: true, status: 200, arrayBuffer() { return preload[url]; }, async blob() { return { size: preload[url].byteLength, async arrayBuffer() { return preload[url]; } } }, }; } // This should only be called in the browser, where fetch() is available. return originalFetch(url); }; globalThis.WebAssembly.instantiateStreaming = async function(m,i) { // DEBUG // console.log('instantiateStreaming',m,i); return WebAssembly.instantiate((await m).arrayBuffer(),i); }; // Provide `setTimeout` for Kotlin coroutines. const originalSetTimeout = setTimeout; globalThis.setTimeout = function(f, delayMs) { // DEBUG // console.log('setTimeout', f, t); // Deep in the Compose UI framework, one task is scheduled every 16ms, see // https://github.com/JetBrains/compose-multiplatform-core/blob/a52f2981b9bc7cdba1d1fbe71654c4be448ebea7/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/spatial/RectManager.kt#L138 // and // https://github.com/JetBrains/compose-multiplatform-core/blob/a52f2981b9bc7cdba1d1fbe71654c4be448ebea7/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/OnLayoutRectChangedModifier.kt#L56 // We don't want to delay work in the Wall-time based measurement in JetStream, // but executing this immediately (without delay) produces redundant work that // is not realistic for a full-browser Kotlin/multiplatform application either, // according to Kotlin/JetBrains folks. // Hence the early return for 16ms delays. if (delayMs === 16) return; if (delayMs !== 0) { throw new Error('Unexpected delay for setTimeout polyfill: ' + delayMs); } originalSetTimeout(f); } // Don't automatically run the main function on instantiation. globalThis.skipFunMain = true; // Prevent this from being detected as a shell environment, so that we use the // same code paths as in the browser. // See `compose-benchmarks-benchmarks.uninstantiated.mjs`. delete globalThis.d8; delete globalThis.inIon; delete globalThis.jscOptions; class Benchmark { skikoInstantiate; mainInstantiate; wasmInstanceExports; async init() { // DEBUG // console.log("init"); preload = { 'skiko.wasm': await JetStream.getBinary(JetStream.preload.skikoWasmBinary), './compose-benchmarks-benchmarks.wasm': await JetStream.getBinary(JetStream.preload.composeWasmBinary), './composeResources/compose_benchmarks.benchmarks.generated.resources/drawable/compose-multiplatform.png': await JetStream.getBinary(JetStream.preload.inputImageCompose), './composeResources/compose_benchmarks.benchmarks.generated.resources/drawable/example1_cat.jpg': await JetStream.getBinary(JetStream.preload.inputImageCat), './composeResources/compose_benchmarks.benchmarks.generated.resources/files/example1_compose-community-primary.png': await JetStream.getBinary(JetStream.preload.inputImageComposeCommunity), './composeResources/compose_benchmarks.benchmarks.generated.resources/font/jetbrainsmono_italic.ttf': await JetStream.getBinary(JetStream.preload.inputFontItalic), './composeResources/compose_benchmarks.benchmarks.generated.resources/font/jetbrainsmono_regular.ttf': await JetStream.getBinary(JetStream.preload.inputFontRegular), }; // We patched `skiko.mjs` to not immediately instantiate the `skiko.wasm` // module, so that we can move the dynamic JS import here but measure // WebAssembly compilation and instantiation as part of the first iteration. this.skikoInstantiate = (await JetStream.dynamicImport(JetStream.preload.skikoJsModule)).default; this.mainInstantiate = (await JetStream.dynamicImport(JetStream.preload.composeJsModule)).instantiate; } async runIteration() { // DEBUG // console.log("runIteration"); // Compile once in the first iteration. if (!this.wasmInstanceExports) { const skikoExports = (await this.skikoInstantiate()).wasmExports; this.wasmInstanceExports = (await this.mainInstantiate({ './skiko.mjs': skikoExports })).exports; } // We render/animate/process fewer frames than in the upstream benchmark, // since we run multiple iterations in JetStream (to measure first, worst, // and average runtime) and don't want the overall workload to take too long. const frameCountFactor = 5; // The factors for the subitems are chosen to make them take the same order // of magnitude in terms of Wall time. await this.wasmInstanceExports.customLaunch("AnimatedVisibility", 100 * frameCountFactor); await this.wasmInstanceExports.customLaunch("LazyGrid", 1 * frameCountFactor); await this.wasmInstanceExports.customLaunch("LazyGrid-ItemLaunchedEffect", 1 * frameCountFactor); // The `SmoothScroll` variants of the LazyGrid workload are much faster. await this.wasmInstanceExports.customLaunch("LazyGrid-SmoothScroll", 5 * frameCountFactor); await this.wasmInstanceExports.customLaunch("LazyGrid-SmoothScroll-ItemLaunchedEffect", 5 * frameCountFactor); // This is quite GC-heavy, is this realistic for Kotlin/compose applications? await this.wasmInstanceExports.customLaunch("VisualEffects", 1 * frameCountFactor); await this.wasmInstanceExports.customLaunch("MultipleComponents-NoVectorGraphics", 10 * frameCountFactor); } }