@twinkle-lang/twinkle 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/runtime.mjs +60 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinkle-lang/twinkle",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Twinkle — a statically typed language targeting WebAssembly GC. CLI (twk) plus an embeddable compile/run library.",
5
5
  "type": "module",
6
6
  "bin": { "twk": "node.mjs" },
package/runtime.mjs CHANGED
@@ -208,15 +208,17 @@ function readExternMeta(wasmModule) {
208
208
  }
209
209
  }
210
210
 
211
- function autoBridgeExternImports(wasmModule, hostImports, b, jspi = false, imports = {}, externMeta = {}) {
212
- let importList;
213
- try {
214
- importList = WebAssembly.Module.imports(wasmModule);
215
- } catch {
216
- // Module.imports may fail on GC modules in some runtimes; nothing to bridge.
217
- return;
211
+ function importListFromExternMeta(externMeta) {
212
+ const list = [];
213
+ for (const [module, entries] of Object.entries(externMeta)) {
214
+ for (const name of Object.keys(entries)) {
215
+ list.push({ module, name, kind: "function" });
216
+ }
218
217
  }
218
+ return list;
219
+ }
219
220
 
221
+ function bridgeExternImports(importList, hostImports, b, jspi = false, imports = {}, externMeta = {}) {
220
222
  const { found, missing } = resolveExternImports(importList, hostImports, imports);
221
223
 
222
224
  if (missing.length > 0) {
@@ -292,6 +294,52 @@ function autoBridgeExternImports(wasmModule, hostImports, b, jspi = false, impor
292
294
  }
293
295
  }
294
296
 
297
+ function autoBridgeExternImports(wasmModule, hostImports, b, jspi = false, imports = {}, externMeta = {}) {
298
+ let importList;
299
+ try {
300
+ importList = WebAssembly.Module.imports(wasmModule);
301
+ } catch {
302
+ // Some browsers (notably Safari on Wasm GC modules) can instantiate a module
303
+ // but reject import introspection. Twinkle modules carry their extern ABI in
304
+ // a custom section, so use that as the browser fallback instead of leaving
305
+ // extern modules absent from the import object.
306
+ importList = importListFromExternMeta(externMeta);
307
+ }
308
+ bridgeExternImports(importList, hostImports, b, jspi, imports, externMeta);
309
+ }
310
+
311
+ function missingImportFromError(e) {
312
+ const msg = e?.message ?? "";
313
+ const match = msg.match(/import\s+([^\s:]+):([^\s]+)\s+must be an object/)
314
+ ?? msg.match(/Import #[0-9]+ module="([^"]+)" function="([^"]+)"/)
315
+ ?? msg.match(/Import #[0-9]+ "([^"]+)" "([^"]+)"/);
316
+ if (match) return { module: match[1], name: match[2], kind: "function" };
317
+ const moduleOnly = msg.match(/Import #[0-9]+ "([^"]+)": module is not an object or function/);
318
+ if (moduleOnly) return { module: moduleOnly[1], name: null, kind: "function" };
319
+ return null;
320
+ }
321
+
322
+ function instantiateWithExternRetry(mainModule, hostImports, b, jspi, imports, externMeta) {
323
+ // Last-ditch Safari fallback: if both Module.imports() and customSections()
324
+ // are unavailable for a GC module, instantiate once, read the missing import
325
+ // from the LinkError text, bridge it, and retry. This preserves globalThis
326
+ // fallback for common browser globals such as performance/Math.
327
+ for (let i = 0; i < 64; i++) {
328
+ try {
329
+ return new WebAssembly.Instance(mainModule, hostImports);
330
+ } catch (e) {
331
+ const imp = missingImportFromError(e);
332
+ if (!imp) throw e;
333
+ if (imp.name === null) {
334
+ hostImports[imp.module] = {};
335
+ } else {
336
+ bridgeExternImports([imp], hostImports, b, jspi, imports, externMeta);
337
+ }
338
+ }
339
+ }
340
+ throw new Error("too many missing WebAssembly imports while instantiating");
341
+ }
342
+
295
343
  // ---------------------------------------------------------------------------
296
344
  // Host imports
297
345
  // ---------------------------------------------------------------------------
@@ -521,7 +569,7 @@ function prepareWasm(wasmBytes, opts, { jspi = false } = {}) {
521
569
  const externMeta = readExternMeta(mainModule);
522
570
  autoBridgeExternImports(mainModule, hostImports, b, jspi, imports, externMeta);
523
571
 
524
- return { mainModule, hostImports, b, runtime };
572
+ return { mainModule, hostImports, b, runtime, imports, externMeta, jspi };
525
573
  }
526
574
 
527
575
  // ---------------------------------------------------------------------------
@@ -529,9 +577,9 @@ function prepareWasm(wasmBytes, opts, { jspi = false } = {}) {
529
577
  // ---------------------------------------------------------------------------
530
578
 
531
579
  export function runWasmBytes(wasmBytes, opts = {}) {
532
- const { mainModule, hostImports } = prepareWasm(wasmBytes, opts);
580
+ const { mainModule, hostImports, b, imports, externMeta, jspi } = prepareWasm(wasmBytes, opts);
533
581
  try {
534
- const instance = new WebAssembly.Instance(mainModule, hostImports);
582
+ const instance = instantiateWithExternRetry(mainModule, hostImports, b, jspi, imports, externMeta);
535
583
  // Boot-compiled modules export __twinkle_start instead of using a Wasm
536
584
  // start section. Stage0-compiled modules still use the start section and
537
585
  // run during instantiation above.
@@ -552,7 +600,7 @@ export function runWasmBytes(wasmBytes, opts = {}) {
552
600
  // ---------------------------------------------------------------------------
553
601
 
554
602
  export async function runWasmBytesAsync(wasmBytes, opts = {}) {
555
- const { mainModule, hostImports, b, runtime } = prepareWasm(wasmBytes, opts, { jspi: hasJspi });
603
+ const { mainModule, hostImports, b, runtime, imports, externMeta, jspi } = prepareWasm(wasmBytes, opts, { jspi: hasJspi });
556
604
 
557
605
  if (hasJspi) {
558
606
  // Wrap stdin reads as suspending imports so the event loop stays free while
@@ -593,7 +641,7 @@ export async function runWasmBytesAsync(wasmBytes, opts = {}) {
593
641
  }
594
642
 
595
643
  try {
596
- const instance = new WebAssembly.Instance(mainModule, hostImports);
644
+ const instance = instantiateWithExternRetry(mainModule, hostImports, b, jspi, imports, externMeta);
597
645
  if (instance.exports.__twinkle_start) {
598
646
  if (hasJspi) {
599
647
  const start = WebAssembly.promising(instance.exports.__twinkle_start);