🏡 index : cold-air-networks/mirror-jetstream.git

// 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);
  }
}