space-data-module-sdk 0.8.19 → 0.8.20
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/LICENSE +1 -1
- package/README.md +2 -2
- package/docs/assembly.js +190 -0
- package/docs/browser-wasmedge-isomorphic.html +6 -1
- package/docs/byo-wasm-quickstart.html +6 -1
- package/docs/conformance.html +6 -1
- package/docs/events-abi.html +6 -1
- package/docs/families/analytics.html +7 -2
- package/docs/families/attitude.html +7 -2
- package/docs/families/behavior.html +7 -2
- package/docs/families/breakup.html +7 -2
- package/docs/families/conjunction.html +7 -2
- package/docs/families/data-source.html +7 -2
- package/docs/families/effects.html +7 -2
- package/docs/families/environment.html +7 -2
- package/docs/families/estimation.html +7 -2
- package/docs/families/gnc.html +7 -2
- package/docs/families/maneuver.html +7 -2
- package/docs/families/obstruction.html +7 -2
- package/docs/families/propagator.html +7 -2
- package/docs/families/propulsion.html +7 -2
- package/docs/families/reentry.html +7 -2
- package/docs/families/rf.html +7 -2
- package/docs/families/scheduler.html +7 -2
- package/docs/families/sensor.html +7 -2
- package/docs/families/signature.html +7 -2
- package/docs/flatsql-host-contract.html +7 -2
- package/docs/flatsql-host-contract.md +2 -2
- package/docs/flatsql-streaming-standard.html +6 -1
- package/docs/gpu-module-abi.html +6 -1
- package/docs/index.html +21 -14
- package/docs/isomorphic-pthreads.html +19 -2
- package/docs/isomorphic-pthreads.md +59 -1
- package/docs/isomorphic-sdn-runtime-plan.html +6 -1
- package/docs/language-runtime-matrix.html +6 -1
- package/docs/llms.txt +1 -1
- package/docs/module-bundle-runtime-plan.html +6 -1
- package/docs/module-publication-standard.html +6 -1
- package/docs/node-red-default-node-parity.html +6 -1
- package/docs/protect-and-sign.html +6 -1
- package/docs/protocol-installation.html +6 -1
- package/docs/publication-submission.html +6 -1
- package/docs/secrets-capability.html +6 -1
- package/docs/styles.css +32 -0
- package/docs/testing-harness.html +6 -1
- package/docs/tri-runtime-parity-gate.html +6 -1
- package/docs/tri-runtime-parity.html +6 -1
- package/package.json +1 -1
- package/src/host/browserModuleHarness.js +39 -35
- package/src/host/isomorphicLoader.js +13 -6
- package/src/host/wasiShim.js +57 -13
- package/src/host/wasiThreadBrowserWorker.mjs +1 -0
- package/src/host/wasiThreadHost.js +16 -1
- package/src/host/wasiThreadWorker.mjs +2 -1
- package/src/host/wasiThreadWorkerRuntime.js +2 -1
- package/src/testing/browserModuleHarness.js +2 -0
- package/src/testing/buildWasmEdgeRunner.js +147 -4
- package/src/testing/docker/wasmedge-parity.Dockerfile +23 -1
- package/src/testing/isomorphicHarness.js +5 -0
- package/src/testing/native/wasmedge-0.16.4-atomic-wait.patch +111 -0
- package/src/testing/native/wasmedge_wasi_threads_runner.c +345 -0
- package/src/testing/parityBrowserRunner.js +12 -4
- package/src/testing/parityHarness.js +11 -3
- package/src/testing/parityLanes.js +29 -5
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
+
<script>try{var t=localStorage.getItem("sdn-theme");if(t==="light"||t==="dark")document.documentElement.setAttribute("data-theme",t)}catch(e){}</script>
|
|
5
6
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
7
|
<meta name="description" content="docs/tri-runtime-parity.md describes the parity harness: one" />
|
|
7
8
|
<title>Tri-runtime parity gate — Space Data Module SDK</title>
|
|
8
9
|
<link rel="stylesheet" href="./styles.css" />
|
|
10
|
+
<script src="./assembly.js" defer></script>
|
|
9
11
|
<!-- SDN_CONSUMER_ASSETS_START -->
|
|
10
12
|
<link rel="stylesheet" href="https://static.spacedatanetwork.org/assets/hd-wallet-ui/2.0.28/sdn-wallet-public-client.c3f68d1cfd88478f10d836a5e829d1dfc6a10157972cf0f7d4d319d0636f2cc4.css" integrity="sha384-fICuhN4I9xqOK1F5vGGzl26opuO+xQIvTZXMSV76lWTBiQ6AfzztMGLLDw8yRT8i" crossorigin="anonymous">
|
|
11
13
|
<script defer src="https://static.spacedatanetwork.org/assets/hd-wallet-ui/2.0.28/sdn-wallet-public-client.f611e7e151a3b5c38384272f8894a4c6634f0a29bf925b0e02e0e27f8db0bfff.js" integrity="sha384-R+QqsKoWJIS7iWrtPmgJ49DVnb1hSGjZokupbLInJd1PLp8RwQhtF/hnaerc33ci" crossorigin="anonymous" data-sdn-wallet-public-client="v1"></script>
|
|
@@ -13,6 +15,7 @@
|
|
|
13
15
|
<!-- SDN_CONSUMER_ASSETS_END -->
|
|
14
16
|
</head>
|
|
15
17
|
<body>
|
|
18
|
+
<canvas id="assembly" aria-hidden="true"></canvas>
|
|
16
19
|
<a class="skip-link" href="#main">Skip to content</a>
|
|
17
20
|
<sdn-stack-nav active="module-sdk"></sdn-stack-nav>
|
|
18
21
|
<header class="site-header">
|
|
@@ -22,12 +25,13 @@
|
|
|
22
25
|
<a href="./conformance.html">Conformance</a>
|
|
23
26
|
<a href="https://github.com/DigitalArsenal/space-data-module-sdk">GitHub</a>
|
|
24
27
|
<a class="stack-button" href="#stack">Stack</a>
|
|
28
|
+
<button class="theme-toggle" type="button" aria-label="Switch between light and dark theme" title="Light or dark theme"><svg class="sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg><svg class="moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z"/></svg></button>
|
|
25
29
|
</nav>
|
|
26
30
|
</header>
|
|
27
31
|
<div class="shell">
|
|
28
32
|
<nav class="sidebar" id="sidebar" aria-label="Documentation">
|
|
29
33
|
<h2>Start here</h2><ul>
|
|
30
|
-
<li><a href="./index.html"><span>
|
|
34
|
+
<li><a href="./index.html"><span>Module types</span></a></li>
|
|
31
35
|
<li><a href="./byo-wasm-quickstart.html"><span>BYO-wasm quickstart</span></a></li>
|
|
32
36
|
<li><a href="./conformance.html"><span>Conformance kit</span></a></li>
|
|
33
37
|
<li><a href="./protect-and-sign.html"><span>Protect and sign</span></a></li>
|
|
@@ -177,6 +181,7 @@ space-data-module parity-gate --gate-manifest ./parity/negative-control.json --e
|
|
|
177
181
|
<footer class="site-footer">
|
|
178
182
|
Space Data Module SDK — Apache-2.0. Every ABI page renders a Markdown
|
|
179
183
|
source in <code>docs/</code>; edit the Markdown, never the HTML.
|
|
184
|
+
Contact <a href="mailto:tj@edgesource.com">tj@edgesource.com</a>.
|
|
180
185
|
</footer>
|
|
181
186
|
</body>
|
|
182
187
|
</html>
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
+
<script>try{var t=localStorage.getItem("sdn-theme");if(t==="light"||t==="dark")document.documentElement.setAttribute("data-theme",t)}catch(e){}</script>
|
|
5
6
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
7
|
<meta name="description" content="ONE dist/isomorphic/module.wasm, THREE runtimes, IDENTICAL behavior:" />
|
|
7
8
|
<title>Tri-runtime parity — Space Data Module SDK</title>
|
|
8
9
|
<link rel="stylesheet" href="./styles.css" />
|
|
10
|
+
<script src="./assembly.js" defer></script>
|
|
9
11
|
<!-- SDN_CONSUMER_ASSETS_START -->
|
|
10
12
|
<link rel="stylesheet" href="https://static.spacedatanetwork.org/assets/hd-wallet-ui/2.0.28/sdn-wallet-public-client.c3f68d1cfd88478f10d836a5e829d1dfc6a10157972cf0f7d4d319d0636f2cc4.css" integrity="sha384-fICuhN4I9xqOK1F5vGGzl26opuO+xQIvTZXMSV76lWTBiQ6AfzztMGLLDw8yRT8i" crossorigin="anonymous">
|
|
11
13
|
<script defer src="https://static.spacedatanetwork.org/assets/hd-wallet-ui/2.0.28/sdn-wallet-public-client.f611e7e151a3b5c38384272f8894a4c6634f0a29bf925b0e02e0e27f8db0bfff.js" integrity="sha384-R+QqsKoWJIS7iWrtPmgJ49DVnb1hSGjZokupbLInJd1PLp8RwQhtF/hnaerc33ci" crossorigin="anonymous" data-sdn-wallet-public-client="v1"></script>
|
|
@@ -13,6 +15,7 @@
|
|
|
13
15
|
<!-- SDN_CONSUMER_ASSETS_END -->
|
|
14
16
|
</head>
|
|
15
17
|
<body>
|
|
18
|
+
<canvas id="assembly" aria-hidden="true"></canvas>
|
|
16
19
|
<a class="skip-link" href="#main">Skip to content</a>
|
|
17
20
|
<sdn-stack-nav active="module-sdk"></sdn-stack-nav>
|
|
18
21
|
<header class="site-header">
|
|
@@ -22,12 +25,13 @@
|
|
|
22
25
|
<a href="./conformance.html">Conformance</a>
|
|
23
26
|
<a href="https://github.com/DigitalArsenal/space-data-module-sdk">GitHub</a>
|
|
24
27
|
<a class="stack-button" href="#stack">Stack</a>
|
|
28
|
+
<button class="theme-toggle" type="button" aria-label="Switch between light and dark theme" title="Light or dark theme"><svg class="sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg><svg class="moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z"/></svg></button>
|
|
25
29
|
</nav>
|
|
26
30
|
</header>
|
|
27
31
|
<div class="shell">
|
|
28
32
|
<nav class="sidebar" id="sidebar" aria-label="Documentation">
|
|
29
33
|
<h2>Start here</h2><ul>
|
|
30
|
-
<li><a href="./index.html"><span>
|
|
34
|
+
<li><a href="./index.html"><span>Module types</span></a></li>
|
|
31
35
|
<li><a href="./byo-wasm-quickstart.html"><span>BYO-wasm quickstart</span></a></li>
|
|
32
36
|
<li><a href="./conformance.html"><span>Conformance kit</span></a></li>
|
|
33
37
|
<li><a href="./protect-and-sign.html"><span>Protect and sign</span></a></li>
|
|
@@ -198,6 +202,7 @@
|
|
|
198
202
|
<footer class="site-footer">
|
|
199
203
|
Space Data Module SDK — Apache-2.0. Every ABI page renders a Markdown
|
|
200
204
|
source in <code>docs/</code>; edit the Markdown, never the HTML.
|
|
205
|
+
Contact <a href="mailto:tj@edgesource.com">tj@edgesource.com</a>.
|
|
201
206
|
</footer>
|
|
202
207
|
</body>
|
|
203
208
|
</html>
|
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* stdout for the response bytes.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { createBrowserWasiShim, WasiExitError } from "./wasiShim.js";
|
|
23
|
+
import { createBrowserWasiShim, createSharedWasiProcess, WasiExitError } from "./wasiShim.js";
|
|
24
24
|
import {
|
|
25
25
|
createWasiThreadSpawn,
|
|
26
26
|
isWasiThreadsModule,
|
|
@@ -307,7 +307,10 @@ async function instantiateBrowserModule(options = {}) {
|
|
|
307
307
|
"Browser module harness memory must be a WebAssembly.Memory.",
|
|
308
308
|
);
|
|
309
309
|
}
|
|
310
|
+
const isThreaded = isWasiThreadsModule(options.wasmModule);
|
|
311
|
+
const processState = isThreaded ? createSharedWasiProcess(options) : null;
|
|
310
312
|
const wasi = createBrowserWasiShim({
|
|
313
|
+
processState,
|
|
311
314
|
args: options.args ?? [],
|
|
312
315
|
env: options.env ?? {},
|
|
313
316
|
stdinBytes: options.stdinBytes ?? new Uint8Array(),
|
|
@@ -323,7 +326,6 @@ async function instantiateBrowserModule(options = {}) {
|
|
|
323
326
|
// `wasi.thread-spawn` over a SHARED imported memory. Such a module can only be
|
|
324
327
|
// instantiated with shared memory, so force it here regardless of the caller's
|
|
325
328
|
// sharedMemory flag; single-thread artifacts are unaffected.
|
|
326
|
-
const isThreaded = isWasiThreadsModule(options.wasmModule);
|
|
327
329
|
const memoryImports = moduleImports.filter((entry) => entry.kind === "memory");
|
|
328
330
|
if (!providedMemory && memoryImports.length > 0) {
|
|
329
331
|
providedMemory = createImportedMemory(
|
|
@@ -356,6 +358,7 @@ async function instantiateBrowserModule(options = {}) {
|
|
|
356
358
|
threadHost = await createWasiThreadSpawn({
|
|
357
359
|
wasmModule: options.wasmModule,
|
|
358
360
|
memory: providedMemory,
|
|
361
|
+
processState,
|
|
359
362
|
// Browser warm-pool sizing: cap pooled workers at how many guest threads
|
|
360
363
|
// the module will actually ask for. Ignored by the Node lazy path.
|
|
361
364
|
requestedThreads: options.maxThreads,
|
|
@@ -395,35 +398,28 @@ async function instantiateBrowserModule(options = {}) {
|
|
|
395
398
|
Object.assign(importObject, bridge.imports);
|
|
396
399
|
}
|
|
397
400
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
wasi.setMemory(memory);
|
|
410
|
-
}
|
|
411
|
-
if (instance.exports._initialize) {
|
|
412
|
-
instance.exports._initialize();
|
|
413
|
-
} else if (isThreaded && typeof instance.exports._start === "function") {
|
|
414
|
-
// wasi-threads artifacts link the wasi command crt (exports `_start`), not a
|
|
415
|
-
// reactor (`_initialize`). Run `_start` ONCE to execute global constructors
|
|
416
|
-
// + WASI init before any direct invoke. The module declares no command
|
|
417
|
-
// surface, so its main is an empty stub — `_start` runs ctors and proc_exit's
|
|
418
|
-
// out; swallow that exit. Direct-invoke exports remain callable afterward
|
|
419
|
-
// (the linear memory + globals persist).
|
|
420
|
-
try {
|
|
421
|
-
instance.exports._start();
|
|
422
|
-
} catch (error) {
|
|
423
|
-
if (!(error instanceof WasiExitError)) {
|
|
424
|
-
throw error;
|
|
401
|
+
try {
|
|
402
|
+
instance = await WebAssembly.instantiate(options.wasmModule, importObject);
|
|
403
|
+
memory = instance.exports.memory ?? providedMemory;
|
|
404
|
+
if (memory) {
|
|
405
|
+
if (
|
|
406
|
+
options.sharedMemory === true &&
|
|
407
|
+
!isSharedArrayBufferLike(memory.buffer)
|
|
408
|
+
) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
"Browser module harness sharedMemory requires shared WebAssembly.Memory backing.",
|
|
411
|
+
);
|
|
425
412
|
}
|
|
413
|
+
wasi.setMemory(memory);
|
|
414
|
+
}
|
|
415
|
+
if (instance.exports._initialize) {
|
|
416
|
+
instance.exports._initialize();
|
|
426
417
|
}
|
|
418
|
+
// Command exports initialize their CRT on entry. Calling _start here would
|
|
419
|
+
// consume stdin before invocation; direct-only modules use _initialize.
|
|
420
|
+
} catch (error) {
|
|
421
|
+
await threadHost?.terminateAll();
|
|
422
|
+
throw error;
|
|
427
423
|
}
|
|
428
424
|
|
|
429
425
|
return {
|
|
@@ -561,8 +557,11 @@ export async function createBrowserModuleHarness(options = {}) {
|
|
|
561
557
|
maxThreads: options.maxThreads,
|
|
562
558
|
threadHostcallChannel: options.threadHostcallChannel,
|
|
563
559
|
enableBrowserWasiThreads: options.enableBrowserWasiThreads,
|
|
560
|
+
wasiThreadWorkerBaseUrl: options.wasiThreadWorkerBaseUrl,
|
|
561
|
+
wasiThreadWorkerUrl: options.wasiThreadWorkerUrl,
|
|
564
562
|
});
|
|
565
563
|
const { instance, bridge, wasi, memory, threadHost } = activeContext;
|
|
564
|
+
let lastCommandContext = null;
|
|
566
565
|
const allowRawInvoke = options.allowRawInvoke !== false;
|
|
567
566
|
|
|
568
567
|
// --- Invoke helpers ---
|
|
@@ -805,6 +804,8 @@ export async function createBrowserModuleHarness(options = {}) {
|
|
|
805
804
|
async function invokeCommandRaw(stdinBytes) {
|
|
806
805
|
const commandContext = await instantiateBrowserModule({
|
|
807
806
|
wasmModule,
|
|
807
|
+
// Command CRT startup consumes stdin and is not reentrant. Run it only
|
|
808
|
+
// below, after this fresh instance has received the actual request.
|
|
808
809
|
host,
|
|
809
810
|
hostcallDispatch: options.hostcallDispatch,
|
|
810
811
|
args: options.args,
|
|
@@ -820,7 +821,10 @@ export async function createBrowserModuleHarness(options = {}) {
|
|
|
820
821
|
maxThreads: options.maxThreads,
|
|
821
822
|
threadHostcallChannel: options.threadHostcallChannel,
|
|
822
823
|
enableBrowserWasiThreads: options.enableBrowserWasiThreads,
|
|
824
|
+
wasiThreadWorkerBaseUrl: options.wasiThreadWorkerBaseUrl,
|
|
825
|
+
wasiThreadWorkerUrl: options.wasiThreadWorkerUrl,
|
|
823
826
|
});
|
|
827
|
+
lastCommandContext = commandContext;
|
|
824
828
|
try {
|
|
825
829
|
const commandExport = commandContext.instance.exports[DefaultInvokeExports.commandSymbol];
|
|
826
830
|
if (typeof commandExport !== "function") {
|
|
@@ -833,6 +837,8 @@ export async function createBrowserModuleHarness(options = {}) {
|
|
|
833
837
|
if (!(error instanceof WasiExitError) || error.code !== 0) {
|
|
834
838
|
throw error;
|
|
835
839
|
}
|
|
840
|
+
} finally {
|
|
841
|
+
await commandContext.threadHost?.terminateAll();
|
|
836
842
|
}
|
|
837
843
|
return commandContext.wasi.stdout;
|
|
838
844
|
}
|
|
@@ -903,11 +909,9 @@ export async function createBrowserModuleHarness(options = {}) {
|
|
|
903
909
|
return new Uint8Array(memory.buffer, ptr, size).slice();
|
|
904
910
|
}
|
|
905
911
|
|
|
906
|
-
function destroy() {
|
|
912
|
+
async function destroy() {
|
|
907
913
|
wasi.flushOutput();
|
|
908
|
-
|
|
909
|
-
void threadHost.terminateAll();
|
|
910
|
-
}
|
|
914
|
+
await threadHost?.terminateAll();
|
|
911
915
|
}
|
|
912
916
|
|
|
913
917
|
return {
|
|
@@ -920,12 +924,12 @@ export async function createBrowserModuleHarness(options = {}) {
|
|
|
920
924
|
module: wasmModule,
|
|
921
925
|
host,
|
|
922
926
|
bridge,
|
|
923
|
-
wasi,
|
|
927
|
+
get wasi() { return lastCommandContext?.wasi ?? wasi; },
|
|
924
928
|
memory,
|
|
925
929
|
// Present only for isomorphic-pthreads (wasi-threads) artifacts: exposes the
|
|
926
930
|
// spawn host so callers can observe real thread activity
|
|
927
931
|
// (spawnCount/distinctOsThreadCount).
|
|
928
|
-
threadHost,
|
|
932
|
+
get threadHost() { return lastCommandContext?.threadHost ?? threadHost; },
|
|
929
933
|
callHost,
|
|
930
934
|
invokeDirect,
|
|
931
935
|
invoke,
|
|
@@ -65,12 +65,18 @@ async function createWasmEdgeCommandHarness(options = {}) {
|
|
|
65
65
|
const loadableBytes = toLoadableWasmBytes(wasmBytes);
|
|
66
66
|
const inspection = await inspectModule(loadableBytes);
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
const threaded = inspection.imports.some((entry) =>
|
|
69
|
+
entry.module === "wasi" && entry.name === "thread-spawn");
|
|
70
|
+
const surface = inspection.exports.includes(DefaultInvokeExports.commandSymbol) ? "command" : "direct";
|
|
71
|
+
if (surface === "direct" && (!threaded || !inspection.exports.includes(DefaultInvokeExports.invokeSymbol))) {
|
|
69
72
|
throw new Error(
|
|
70
73
|
"Standalone WasmEdge loading requires a command-surface artifact with the _start export.",
|
|
71
74
|
);
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
const command = threaded
|
|
78
|
+
? await (await import("../testing/buildWasmEdgeRunner.js")).resolveWasmEdgeWasiThreadsRunner(options)
|
|
79
|
+
: options.wasmEdgeRunnerBinary ?? options.wasmEdgeBinary ?? "wasmedge";
|
|
74
80
|
let launchWasmPath = wasmPath;
|
|
75
81
|
let tempArtifactDir = null;
|
|
76
82
|
if (loadableBytes.byteLength !== wasmBytes.byteLength) {
|
|
@@ -84,9 +90,9 @@ async function createWasmEdgeCommandHarness(options = {}) {
|
|
|
84
90
|
await writeFile(launchWasmPath, loadableBytes);
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
const command = options.wasmEdgeBinary ?? "wasmedge";
|
|
88
93
|
const args = [
|
|
89
94
|
...(options.enableThreads === false ? [] : ["--enable-threads"]),
|
|
95
|
+
...Object.entries(options.env ?? {}).flatMap(([key, value]) => ["--env", `${key}=${value}`]),
|
|
90
96
|
launchWasmPath,
|
|
91
97
|
...(Array.isArray(options.args) ? options.args : []),
|
|
92
98
|
];
|
|
@@ -156,7 +162,7 @@ async function createWasmEdgeCommandHarness(options = {}) {
|
|
|
156
162
|
runtime: {
|
|
157
163
|
kind: "wasmedge",
|
|
158
164
|
profile: inspection.profile,
|
|
159
|
-
surface
|
|
165
|
+
surface,
|
|
160
166
|
},
|
|
161
167
|
launchPlan,
|
|
162
168
|
invokeRaw,
|
|
@@ -225,8 +231,7 @@ export async function loadModule(options = {}) {
|
|
|
225
231
|
// byte-level question.
|
|
226
232
|
const needsCompiledModule =
|
|
227
233
|
runtimeKind === "wasmedge" &&
|
|
228
|
-
!runtimeHostRequested
|
|
229
|
-
!options.wasmEdgeRunnerBinary;
|
|
234
|
+
!runtimeHostRequested;
|
|
230
235
|
const needsBytes = Boolean(signaturePolicy) || runtimeKind === "wasmedge";
|
|
231
236
|
let artifactBytes = null;
|
|
232
237
|
let wasmModule = null;
|
|
@@ -264,9 +269,11 @@ export async function loadModule(options = {}) {
|
|
|
264
269
|
});
|
|
265
270
|
if (needsCompiledModule) {
|
|
266
271
|
const inspection = await inspectModule(wasmModule);
|
|
272
|
+
const threaded = inspection.imports.some((entry) => entry.module === "wasi" && entry.name === "thread-spawn");
|
|
267
273
|
if (
|
|
268
274
|
(inspection.profile === "standalone" || inspection.profile === "module-host-abi") &&
|
|
269
|
-
inspection.exports.includes("_start")
|
|
275
|
+
(inspection.exports.includes("_start") || (threaded && inspection.exports.includes("plugin_invoke_stream"))) &&
|
|
276
|
+
(!options.wasmEdgeRunnerBinary || options.surface === "command" || threaded)
|
|
270
277
|
) {
|
|
271
278
|
return attachHostDispatch(
|
|
272
279
|
await createWasmEdgeCommandHarness(options),
|
package/src/host/wasiShim.js
CHANGED
|
@@ -34,10 +34,34 @@ export class WasiExitError extends Error {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// One process owns its descriptors, even when libc executes main on a pthread.
|
|
38
|
+
// Offsets are atomically reserved so concurrent readers/writers never duplicate
|
|
39
|
+
// input or overwrite another thread's output. Overflow is an explicit WASI error.
|
|
40
|
+
export function createSharedWasiProcess({ args = [], env = {}, stdinBytes = [], maxOutputBytes = 16 * 1024 * 1024 } = {}) {
|
|
41
|
+
const bytes = new Uint8Array(stdinBytes);
|
|
42
|
+
const input = new Uint8Array(new SharedArrayBuffer(bytes.length));
|
|
43
|
+
input.set(bytes);
|
|
44
|
+
return {
|
|
45
|
+
args, env, input,
|
|
46
|
+
control: new Int32Array(new SharedArrayBuffer(5 * 4)),
|
|
47
|
+
stdout: new Uint8Array(new SharedArrayBuffer(maxOutputBytes)),
|
|
48
|
+
stderr: new Uint8Array(new SharedArrayBuffer(maxOutputBytes)),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function reserve(control, index, length, capacity) {
|
|
53
|
+
for (;;) {
|
|
54
|
+
const offset = Atomics.load(control, index);
|
|
55
|
+
if (length > capacity - offset) return -1;
|
|
56
|
+
if (Atomics.compareExchange(control, index, offset, offset + length) === offset) return offset;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
37
60
|
export function createBrowserWasiShim(options = {}) {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
61
|
+
const shared = options.processState;
|
|
62
|
+
const args = shared?.args ?? options.args ?? [];
|
|
63
|
+
const env = shared?.env ?? options.env ?? {};
|
|
64
|
+
const stdinBytes = shared?.input ?? new Uint8Array(options.stdinBytes ?? []);
|
|
41
65
|
const logOutput = options.logOutput === true;
|
|
42
66
|
const performanceApi = options.performance ?? globalThis.performance ?? {
|
|
43
67
|
now: () => Date.now(),
|
|
@@ -100,7 +124,14 @@ export function createBrowserWasiShim(options = {}) {
|
|
|
100
124
|
const base = iovsPtr + i * 8;
|
|
101
125
|
const ptr = dv.getUint32(base, true);
|
|
102
126
|
const len = dv.getUint32(base + 4, true);
|
|
103
|
-
|
|
127
|
+
if (shared) {
|
|
128
|
+
const output = fd === 1 ? shared.stdout : shared.stderr;
|
|
129
|
+
const offset = reserve(shared.control, fd, len, output.length);
|
|
130
|
+
if (offset < 0) return 51; // NOSPC: never silently truncate command output.
|
|
131
|
+
output.set(bytes.subarray(ptr, ptr + len), offset);
|
|
132
|
+
} else {
|
|
133
|
+
target.push(bytes.slice(ptr, ptr + len));
|
|
134
|
+
}
|
|
104
135
|
totalWritten += len;
|
|
105
136
|
}
|
|
106
137
|
|
|
@@ -119,16 +150,25 @@ export function createBrowserWasiShim(options = {}) {
|
|
|
119
150
|
let totalRead = 0;
|
|
120
151
|
|
|
121
152
|
for (let i = 0; i < iovsLen; i += 1) {
|
|
122
|
-
if (stdinOffset >= stdinBytes.length) {
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
153
|
const base = iovsPtr + i * 8;
|
|
126
154
|
const ptr = dv.getUint32(base, true);
|
|
127
155
|
const len = dv.getUint32(base + 4, true);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
156
|
+
let offset, count;
|
|
157
|
+
if (shared) {
|
|
158
|
+
do {
|
|
159
|
+
offset = Atomics.load(shared.control, 0);
|
|
160
|
+
count = Math.min(len, stdinBytes.length - offset);
|
|
161
|
+
} while (Atomics.compareExchange(shared.control, 0, offset, offset + count) !== offset);
|
|
162
|
+
} else {
|
|
163
|
+
offset = stdinOffset;
|
|
164
|
+
count = Math.min(len, stdinBytes.length - offset);
|
|
165
|
+
stdinOffset += count;
|
|
166
|
+
}
|
|
167
|
+
if (count === 0) {
|
|
168
|
+
if (offset >= stdinBytes.length) break;
|
|
169
|
+
continue; // An empty iovec does not mean end of file.
|
|
170
|
+
}
|
|
171
|
+
bytes.set(stdinBytes.subarray(offset, offset + count), ptr);
|
|
132
172
|
totalRead += count;
|
|
133
173
|
}
|
|
134
174
|
|
|
@@ -243,6 +283,10 @@ export function createBrowserWasiShim(options = {}) {
|
|
|
243
283
|
}
|
|
244
284
|
|
|
245
285
|
function proc_exit(code) {
|
|
286
|
+
if (shared) {
|
|
287
|
+
Atomics.store(shared.control, 4, code);
|
|
288
|
+
Atomics.store(shared.control, 3, 1);
|
|
289
|
+
}
|
|
246
290
|
if (logOutput) {
|
|
247
291
|
flushOutput();
|
|
248
292
|
}
|
|
@@ -317,10 +361,10 @@ export function createBrowserWasiShim(options = {}) {
|
|
|
317
361
|
getMemory,
|
|
318
362
|
flushOutput,
|
|
319
363
|
get stdout() {
|
|
320
|
-
return concatChunks(stdoutChunks);
|
|
364
|
+
return shared ? shared.stdout.slice(0, Atomics.load(shared.control, 1)) : concatChunks(stdoutChunks);
|
|
321
365
|
},
|
|
322
366
|
get stderr() {
|
|
323
|
-
return concatChunks(stderrChunks);
|
|
367
|
+
return shared ? shared.stderr.slice(0, Atomics.load(shared.control, 2)) : concatChunks(stderrChunks);
|
|
324
368
|
},
|
|
325
369
|
};
|
|
326
370
|
}
|
|
@@ -202,7 +202,7 @@ const BROWSER_POOL_PROBE_TIMEOUT_MS = 1500;
|
|
|
202
202
|
// surfacing) for the life of the pool.
|
|
203
203
|
function armBrowserPool(
|
|
204
204
|
created,
|
|
205
|
-
{ wasmModule, memory, hostcallChannel, timeoutMs, onExit },
|
|
205
|
+
{ wasmModule, memory, hostcallChannel, processState, timeoutMs, onExit },
|
|
206
206
|
) {
|
|
207
207
|
return new Promise((resolve) => {
|
|
208
208
|
let remaining = created.length;
|
|
@@ -266,6 +266,7 @@ function armBrowserPool(
|
|
|
266
266
|
wasmModule,
|
|
267
267
|
memory,
|
|
268
268
|
hostcallChannel: hostcallChannel ?? null,
|
|
269
|
+
processState,
|
|
269
270
|
});
|
|
270
271
|
}
|
|
271
272
|
});
|
|
@@ -333,6 +334,7 @@ export async function createWasiThreadSpawn({
|
|
|
333
334
|
memory,
|
|
334
335
|
requestedThreads,
|
|
335
336
|
hostcallChannel,
|
|
337
|
+
processState,
|
|
336
338
|
requiresHostcalls = false,
|
|
337
339
|
enableBrowserThreads,
|
|
338
340
|
browserWorkerBaseUrl,
|
|
@@ -367,17 +369,29 @@ export async function createWasiThreadSpawn({
|
|
|
367
369
|
);
|
|
368
370
|
const NodeWorker = workerThreads.Worker;
|
|
369
371
|
const nodeWorkerUrl = new URL("./wasiThreadWorker.mjs", import.meta.url);
|
|
372
|
+
// An --input-type parent runs eval/stdin, but this worker loads a file.
|
|
373
|
+
// Inheriting that flag makes the worker fail while the guest is blocked
|
|
374
|
+
// synchronously in pthread_join and cannot receive the error event.
|
|
375
|
+
const parentArgs = globalThis.process.execArgv;
|
|
376
|
+
const sourceFlags = ["--input-type", "--eval", "-e", "--print", "-p"];
|
|
377
|
+
const execArgv = parentArgs.some((arg) => arg.startsWith("--input-type"))
|
|
378
|
+
? parentArgs.filter((arg, index, args) =>
|
|
379
|
+
!sourceFlags.some((flag) => arg === flag || arg.startsWith(`${flag}=`)) &&
|
|
380
|
+
!(index > 0 && sourceFlags.includes(args[index - 1])))
|
|
381
|
+
: undefined;
|
|
370
382
|
|
|
371
383
|
const threadSpawn = (startArg) => {
|
|
372
384
|
const tid = (nextTid += 1);
|
|
373
385
|
try {
|
|
374
386
|
const worker = new NodeWorker(nodeWorkerUrl, {
|
|
387
|
+
execArgv,
|
|
375
388
|
workerData: {
|
|
376
389
|
wasmModule,
|
|
377
390
|
memory,
|
|
378
391
|
tid,
|
|
379
392
|
startArg,
|
|
380
393
|
hostcallChannel: hostcallChannel ?? null,
|
|
394
|
+
processState,
|
|
381
395
|
},
|
|
382
396
|
});
|
|
383
397
|
// Node exposes the OS-thread id per Worker — distinct ids are direct
|
|
@@ -491,6 +505,7 @@ export async function createWasiThreadSpawn({
|
|
|
491
505
|
wasmModule,
|
|
492
506
|
memory,
|
|
493
507
|
hostcallChannel,
|
|
508
|
+
processState,
|
|
494
509
|
timeoutMs: BROWSER_POOL_PROBE_TIMEOUT_MS,
|
|
495
510
|
onExit: returnWorkerToIdle,
|
|
496
511
|
});
|
|
@@ -12,11 +12,12 @@ import { workerData } from "node:worker_threads";
|
|
|
12
12
|
|
|
13
13
|
import { createWasiThreadWorkerRuntime } from "./wasiThreadWorkerRuntime.js";
|
|
14
14
|
|
|
15
|
-
const { wasmModule, memory, tid, startArg, hostcallChannel } = workerData;
|
|
15
|
+
const { wasmModule, memory, tid, startArg, hostcallChannel, processState } = workerData;
|
|
16
16
|
const runtime = createWasiThreadWorkerRuntime({
|
|
17
17
|
wasmModule,
|
|
18
18
|
memory,
|
|
19
19
|
hostcallChannel,
|
|
20
|
+
processState,
|
|
20
21
|
});
|
|
21
22
|
const instance = runtime.instantiate();
|
|
22
23
|
|
|
@@ -58,8 +58,9 @@ export function createWasiThreadWorkerRuntime({
|
|
|
58
58
|
wasmModule,
|
|
59
59
|
memory,
|
|
60
60
|
hostcallChannel,
|
|
61
|
+
processState,
|
|
61
62
|
} = {}) {
|
|
62
|
-
const wasi = createBrowserWasiShim({});
|
|
63
|
+
const wasi = createBrowserWasiShim({ processState });
|
|
63
64
|
wasi.setMemory(memory);
|
|
64
65
|
let instance = null;
|
|
65
66
|
let hostcalls = null;
|