profile-wasm-host.mjs raw

   1  // profile-wasm-host.mjs — Worker bootstrap for profile.wasm.
   2  import {
   3    makeCoreHelpers, makeWasi, makeEnv, makeCommonBridge, computeBuildHash,
   4  } from './bridge-common.mjs';
   5  
   6  let mem, xp;
   7  const _sabTable = new Map(), _sabSeqRef = { value: 0 };
   8  const _spawnedWorkerChans = new Map();
   9  const _wasmUrlRef = { value: null }, _buildHashRef = { value: null };
  10  const h = makeCoreHelpers(() => mem, () => xp);
  11  const { readStr, writeStr, writeI32, readBytes, writeBytes, cb0, cbs, cbb, cbdata } = h;
  12  
  13  let _workerMsgCBID = 0;
  14  let _consumerMsgCBID = 0;
  15  const _pending = []; // messages that arrived before WASM was ready
  16  
  17  function jsonEsc(s) {
  18    return String(s).replace(/\\/g,'\\\\').replace(/"/g,'\\"').replace(/\n/g,'\\n').replace(/\r/g,'\\r').replace(/\t/g,'\\t');
  19  }
  20  
  21  const bridge = {
  22    profile_worker_on_message(cbID) { _workerMsgCBID = cbID; },
  23    profile_worker_post(ptr, len) { self.postMessage(readStr(ptr, len)); },
  24    profile_worker_set_timeout(ms, cbID) { return setTimeout(() => cb0(cbID), ms); },
  25    profile_worker_clear_timeout(h) { clearTimeout(h); },
  26    profile_worker_now_seconds() { return BigInt(Math.floor(Date.now() / 1000)); },
  27    profile_send(ptr, len) { self.postMessage({ type: 'profile-send', msg: readStr(ptr, len) }); },
  28    profile_on_message(cbID) { _consumerMsgCBID = cbID; },
  29    ...makeCommonBridge(h, _sabTable, _sabSeqRef, _wasmUrlRef, _buildHashRef, _spawnedWorkerChans),
  30  };
  31  
  32  const wasi = makeWasi(() => mem);
  33  const env = makeEnv(() => mem);
  34  
  35  self.addEventListener('unhandledrejection', function(ev) {
  36    self.postMessage('["__WASM_FATAL","unhandledrejection: ' + jsonEsc(String(ev.reason)) + '"]');
  37  });
  38  
  39  self.onmessage = async function(e) {
  40    const d = e.data;
  41    if (d && d.type === 'init' && d.mode === 'root') {
  42      try {
  43        const wasmBytes = await fetch(d.wasmUrl, { cache: 'no-store' }).then(r => r.arrayBuffer());
  44        _wasmUrlRef.value = d.wasmUrl;
  45        _buildHashRef.value = await computeBuildHash(wasmBytes);
  46        const { instance } = await WebAssembly.instantiate(wasmBytes, { bridge, env, wasi_snapshot_preview1: wasi });
  47        mem = instance.exports.memory; xp = instance.exports;
  48        xp._start();
  49        // Flush messages that arrived before WASM was ready.
  50        while (_pending.length > 0) {
  51          const m = _pending.shift();
  52          if (typeof m === 'string' && _workerMsgCBID) cbs(_workerMsgCBID, m);
  53          else if (m && m.type === '__profile_msg' && _consumerMsgCBID) cbs(_consumerMsgCBID, m.msg || '');
  54        }
  55        self.postMessage('["__WASM_BOOTED"]');
  56      } catch (err) {
  57        self.postMessage('["__WASM_FATAL","boot: ' + jsonEsc(String(err)) + '"]');
  58      }
  59      return;
  60    }
  61    if (_workerMsgCBID || _consumerMsgCBID) {
  62      if (typeof d === 'string' && _workerMsgCBID) cbs(_workerMsgCBID, d);
  63      if (d && d.type === '__profile_msg' && _consumerMsgCBID) cbs(_consumerMsgCBID, d.msg || '');
  64    } else {
  65      _pending.push(d);
  66    }
  67  };
  68