@twinkle-lang/twinkle 0.4.0 → 0.5.0
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.
- package/boot.wasm +0 -0
- package/package.json +1 -1
- package/runtime.mjs +57 -13
package/boot.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/runtime.mjs
CHANGED
|
@@ -181,7 +181,34 @@ export function resolveExternImports(importList, hostImports, imports = {}, glob
|
|
|
181
181
|
* - Int (bigint), Float (number), Bool (i32) pass through
|
|
182
182
|
* Throws a single aggregated error if any extern import is unsatisfied.
|
|
183
183
|
*/
|
|
184
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Read the compiler-emitted `twinkle.externs` custom section into a
|
|
186
|
+
* `module → name → { args, ret }` map. Best-effort: `customSections` may throw
|
|
187
|
+
* on GC modules in some engines (same risk class as `Module.imports`), and the
|
|
188
|
+
* section is absent for non-Twinkle wasm — either way we return `{}` and the
|
|
189
|
+
* manual override / string-default path takes over.
|
|
190
|
+
*/
|
|
191
|
+
function readExternMeta(wasmModule) {
|
|
192
|
+
let sections;
|
|
193
|
+
try {
|
|
194
|
+
sections = WebAssembly.Module.customSections(wasmModule, "twinkle.externs");
|
|
195
|
+
} catch {
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
if (!sections || sections.length === 0) return {};
|
|
199
|
+
try {
|
|
200
|
+
const list = JSON.parse(new TextDecoder().decode(new Uint8Array(sections[0])));
|
|
201
|
+
const map = {};
|
|
202
|
+
for (const e of list) {
|
|
203
|
+
(map[e.module] ??= {})[e.name] = { args: e.args, ret: e.ret };
|
|
204
|
+
}
|
|
205
|
+
return map;
|
|
206
|
+
} catch {
|
|
207
|
+
return {};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function autoBridgeExternImports(wasmModule, hostImports, b, jspi = false, imports = {}, externMeta = {}) {
|
|
185
212
|
let importList;
|
|
186
213
|
try {
|
|
187
214
|
importList = WebAssembly.Module.imports(wasmModule);
|
|
@@ -201,21 +228,34 @@ function autoBridgeExternImports(wasmModule, hostImports, b, jspi = false, impor
|
|
|
201
228
|
);
|
|
202
229
|
}
|
|
203
230
|
|
|
204
|
-
// Per-import arg marshaling honors
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
231
|
+
// Per-import arg marshaling honors a per-position kind spec. Two vocabularies
|
|
232
|
+
// are accepted and treated identically: the compiler-emitted twinkle.externs
|
|
233
|
+
// kinds ("str" | "ref" | "i64" | "f64" | "i32") and the manual override's
|
|
234
|
+
// ("raw" | "string"). Numbers pass through (handled before the spec). "ref" /
|
|
235
|
+
// "raw" pass the value untouched — essential for externref args (e.g. a canvas
|
|
236
|
+
// 2D context), since decodeString on an opaque host object recurses until a
|
|
237
|
+
// stack overflow in some engines (notably Safari). Anything else (incl. no
|
|
238
|
+
// entry) is assumed to be a Wasm GC string and decoded.
|
|
210
239
|
const makeMarshalArgs = (spec) => (args) => args.map((arg, i) => {
|
|
211
240
|
if (typeof arg === "bigint") return Number(arg);
|
|
212
241
|
if (typeof arg === "number") return arg;
|
|
213
|
-
|
|
242
|
+
const k = spec?.[i];
|
|
243
|
+
if (k === "ref" || k === "raw") return arg;
|
|
214
244
|
return decodeString(b, arg);
|
|
215
245
|
});
|
|
216
246
|
|
|
217
|
-
|
|
247
|
+
// Return marshaling uses the compiler-emitted `ret` kind when available, so
|
|
248
|
+
// we never guess from the JS value's type. Falls back to a generic guess when
|
|
249
|
+
// there is no section (manual-only callers).
|
|
250
|
+
const marshalReturn = (result, ret) => {
|
|
218
251
|
if (result === undefined || result === null) return;
|
|
252
|
+
switch (ret) {
|
|
253
|
+
case "ref": return result;
|
|
254
|
+
case "str": return typeof result === "string" ? encodeString(b, result) : result;
|
|
255
|
+
case "i64": return typeof result === "number" ? BigInt(result) : result;
|
|
256
|
+
case "f64": case "i32": return result;
|
|
257
|
+
case "void": return undefined;
|
|
258
|
+
}
|
|
219
259
|
if (typeof result === "string") return encodeString(b, result);
|
|
220
260
|
if (typeof result === "number") return result;
|
|
221
261
|
if (typeof result === "bigint") return result;
|
|
@@ -223,13 +263,16 @@ function autoBridgeExternImports(wasmModule, hostImports, b, jspi = false, impor
|
|
|
223
263
|
};
|
|
224
264
|
|
|
225
265
|
for (const { module, name, fn, recv, args } of found) {
|
|
226
|
-
|
|
266
|
+
// Precedence: a manual `args` override wins over the section's kinds.
|
|
267
|
+
const meta = externMeta[module]?.[name];
|
|
268
|
+
const marshalArgs = makeMarshalArgs(args ?? meta?.args);
|
|
269
|
+
const ret = meta?.ret;
|
|
227
270
|
let bridgedFn;
|
|
228
271
|
if (jspi) {
|
|
229
272
|
// JSPI mode: async wrapper so Promise-returning JS functions suspend
|
|
230
273
|
// Wasm. Non-Promise returns pass through without suspension.
|
|
231
274
|
const asyncWrapper = async (...args) =>
|
|
232
|
-
marshalReturn(await fn.apply(recv, marshalArgs(args)));
|
|
275
|
+
marshalReturn(await fn.apply(recv, marshalArgs(args)), ret);
|
|
233
276
|
bridgedFn = new WebAssembly.Suspending(asyncWrapper);
|
|
234
277
|
} else {
|
|
235
278
|
// Sync mode: detect and reject Promise returns
|
|
@@ -241,7 +284,7 @@ function autoBridgeExternImports(wasmModule, hostImports, b, jspi = false, impor
|
|
|
241
284
|
`Promise-returning externs require a runtime with WebAssembly.Suspending/promising support.`,
|
|
242
285
|
);
|
|
243
286
|
}
|
|
244
|
-
return marshalReturn(result);
|
|
287
|
+
return marshalReturn(result, ret);
|
|
245
288
|
};
|
|
246
289
|
}
|
|
247
290
|
if (!hostImports[module]) hostImports[module] = {};
|
|
@@ -475,7 +518,8 @@ function prepareWasm(wasmBytes, opts, { jspi = false } = {}) {
|
|
|
475
518
|
|
|
476
519
|
const hostImports = makeHostImports(b, runtime, bridgeBytes);
|
|
477
520
|
const mainModule = new WebAssembly.Module(wasmBytes);
|
|
478
|
-
|
|
521
|
+
const externMeta = readExternMeta(mainModule);
|
|
522
|
+
autoBridgeExternImports(mainModule, hostImports, b, jspi, imports, externMeta);
|
|
479
523
|
|
|
480
524
|
return { mainModule, hostImports, b, runtime };
|
|
481
525
|
}
|