space-data-module-sdk 0.5.5 → 0.5.9
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/README.md +94 -33
- package/package.json +1 -1
- package/src/compiler/compileModule.js +247 -4
- package/src/compiler/index.d.ts +3 -0
- package/src/compiler/index.js +1 -0
- package/src/index.d.ts +25 -0
- package/src/invoke/codec.js +9 -7
- package/src/testing/buildWasmEdgeRunner.js +214 -0
- package/src/testing/index.d.ts +109 -0
- package/src/testing/index.js +16 -0
- package/src/testing/moduleHarness.js +62 -0
- package/src/testing/native/wasmedge_emscripten_pthread_runner.c +1001 -0
- package/src/testing/processInvoke.js +226 -0
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Space Data Module SDK
|
|
2
2
|
|
|
3
|
-
`space-data-module-sdk` defines the canonical module
|
|
4
|
-
|
|
5
|
-
vocabulary,
|
|
6
|
-
used to move modules between hosts.
|
|
3
|
+
`space-data-module-sdk` defines the canonical Space Data module architecture for
|
|
4
|
+
WebAssembly artifacts: an embedded FlatBuffer manifest, canonical ABI exports,
|
|
5
|
+
a stable capability vocabulary, single-file packaging, and the signing and
|
|
6
|
+
transport records used to move modules between hosts.
|
|
7
7
|
|
|
8
8
|
This repository is the source of truth for module-level concerns:
|
|
9
9
|
|
|
@@ -11,17 +11,34 @@ This repository is the source of truth for module-level concerns:
|
|
|
11
11
|
- embedded manifest exports inside `.wasm` modules
|
|
12
12
|
- standards-aware compliance and capability validation
|
|
13
13
|
- module compilation and protection
|
|
14
|
-
- the `sds.bundle` single-file
|
|
14
|
+
- the `sds.bundle` single-file container
|
|
15
15
|
- deployment authorization plus SDS publication records (`REC`, `PNM`, `ENC`)
|
|
16
16
|
- the first canonical module hostcall/import ABI surface
|
|
17
|
+
- shared module-level testing/runtime harnesses, including the WasmEdge
|
|
18
|
+
process runner used by package-level validation suites
|
|
17
19
|
|
|
18
20
|
<p align="center">
|
|
19
21
|
<img src="docs/architecture.svg" alt="Module architecture overview" width="820" />
|
|
20
22
|
</p>
|
|
21
23
|
|
|
22
|
-
##
|
|
24
|
+
## Shared Harness Ownership
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
This repo owns the generic module-side runtime harnesses used across the stack:
|
|
27
|
+
|
|
28
|
+
- `createModuleHarness(...)` for process and WasmEdge-backed module invocation
|
|
29
|
+
- `resolveModuleHarnessLaunchPlan(...)` for portable launch planning
|
|
30
|
+
- `buildWasmEdgeEmscriptenPthreadRunner(...)` plus the shared native runner
|
|
31
|
+
source and runner-level pthread smoke test
|
|
32
|
+
|
|
33
|
+
Flow-specific standalone harnessing lives in `sdn-flow`, which layers flow
|
|
34
|
+
enqueue/drain behavior on top of its compiled standalone runtime host. Package-
|
|
35
|
+
specific validation suites, including conjunction replay/V&V, are expected to
|
|
36
|
+
sit on top of these shared harnesses instead of defining their own runtime
|
|
37
|
+
process model.
|
|
38
|
+
|
|
39
|
+
## Module Artifact Model
|
|
40
|
+
|
|
41
|
+
A compliant module built with this SDK is always a valid `.wasm` artifact with:
|
|
25
42
|
|
|
26
43
|
- an embedded `PluginManifest.fbs`
|
|
27
44
|
- exported manifest accessors:
|
|
@@ -37,10 +54,15 @@ A module built with this SDK is a `.wasm` artifact with:
|
|
|
37
54
|
- resolved deployment plans and input bindings
|
|
38
55
|
- deployment authorization
|
|
39
56
|
- auxiliary FlatBuffer or raw payloads
|
|
40
|
-
- optional appended SDS
|
|
57
|
+
- optional appended SDS FlatBuffer publication records in a trailing `REC`
|
|
58
|
+
container carrying:
|
|
41
59
|
- `PNM` digital-signature/publication metadata
|
|
42
60
|
- `ENC` encrypted-delivery metadata
|
|
43
|
-
|
|
61
|
+
|
|
62
|
+
Single-file bundling does not append arbitrary bytes to the end of the wasm
|
|
63
|
+
module. The one-file module container is `sds.bundle`, stored as a standard
|
|
64
|
+
wasm custom section. Appended bytes are reserved for SDS publication records
|
|
65
|
+
such as `PNM` and `ENC`, wrapped in a trailing `REC` FlatBuffer collection.
|
|
44
66
|
|
|
45
67
|
The module contract stays the same whether the artifact is loaded directly,
|
|
46
68
|
wrapped in a deployment envelope, or shipped as one bundled `.wasm` file.
|
|
@@ -65,7 +87,7 @@ same request bytes from guest memory and returns response bytes in guest
|
|
|
65
87
|
memory.
|
|
66
88
|
|
|
67
89
|
For simple single-input / single-output methods, command mode also supports a
|
|
68
|
-
|
|
90
|
+
raw shortcut:
|
|
69
91
|
|
|
70
92
|
```bash
|
|
71
93
|
wasmedge module.wasm --method echo < input.fb > output.fb
|
|
@@ -101,8 +123,8 @@ anywhere it can:
|
|
|
101
123
|
- read FlatBuffers
|
|
102
124
|
- honor the module capability and host ABI contract
|
|
103
125
|
|
|
104
|
-
That
|
|
105
|
-
|
|
126
|
+
That architecture is intended to stay portable across the common WebAssembly
|
|
127
|
+
and FlatBuffer host environments:
|
|
106
128
|
|
|
107
129
|
- browser
|
|
108
130
|
- Node.js
|
|
@@ -126,11 +148,58 @@ This repo currently includes:
|
|
|
126
148
|
- a reference Node host and sync `sdn_host` bridge for the first hostcall
|
|
127
149
|
surface
|
|
128
150
|
|
|
151
|
+
## Runtime Targets
|
|
152
|
+
|
|
153
|
+
Manifests can declare coarse runtime targets in `manifest.runtimeTargets`.
|
|
154
|
+
|
|
155
|
+
If a manifest declares `runtimeTargets: ["wasi"]`, this SDK treats that as
|
|
156
|
+
"standalone WASI, no host wrapper required." In practice that currently means:
|
|
157
|
+
|
|
158
|
+
- the artifact must declare the `command` invoke surface
|
|
159
|
+
- declared capabilities must stay within the pure WASI subset:
|
|
160
|
+
`logging`, `clock`, `random`, `filesystem`, `pipe`
|
|
161
|
+
- hosted protocols may only use `wasi-pipe` transport
|
|
162
|
+
|
|
163
|
+
If a manifest declares `runtimeTargets: ["wasmedge"]`, this SDK treats that as
|
|
164
|
+
the preferred server-side target when the guest needs network-oriented runtime
|
|
165
|
+
features such as sockets or TLS. Plain `wasi` remains the strict portability
|
|
166
|
+
baseline; `wasmedge` is the practical higher-capability target.
|
|
167
|
+
|
|
168
|
+
## WasmEdge Pthreads
|
|
169
|
+
|
|
170
|
+
`space-data-module-sdk` is also the source of truth for module thread-model
|
|
171
|
+
selection.
|
|
172
|
+
|
|
173
|
+
- `compileModuleFromSource({ threadModel })` accepts an explicit thread model.
|
|
174
|
+
- If `threadModel` is omitted, the SDK resolves it from `manifest.runtimeTargets`.
|
|
175
|
+
- `runtimeTargets: ["wasmedge"]` defaults to `emscripten-pthreads`.
|
|
176
|
+
- Other targets currently default to `single-thread`.
|
|
177
|
+
|
|
178
|
+
WasmEdge-targeted pthread builds do not use the embedded `sdn-emception`
|
|
179
|
+
toolchain. They require a real system Emscripten installation on `PATH`, and
|
|
180
|
+
the compiler result plus guest-link bundle metadata preserve the selected
|
|
181
|
+
`threadModel`.
|
|
182
|
+
|
|
183
|
+
As emitted by current Emscripten, these pthread artifacts still import
|
|
184
|
+
Emscripten `env.*` host functions plus imported shared memory. That means a
|
|
185
|
+
bare `wasmedge` CLI invocation is not yet the direct execution path for them;
|
|
186
|
+
they currently require a WasmEdge-side host shim that satisfies the Emscripten
|
|
187
|
+
pthread contract.
|
|
188
|
+
|
|
189
|
+
If a runtime cannot interoperate with the guest pthread contract directly,
|
|
190
|
+
document that as a wrapper requirement instead of changing the guest artifact
|
|
191
|
+
semantics.
|
|
192
|
+
|
|
129
193
|
## Testing
|
|
130
194
|
|
|
131
195
|
This repo now exposes a manifest-driven harness generator from
|
|
132
196
|
`space-data-module-sdk/testing` and two complementary integration suites:
|
|
133
197
|
|
|
198
|
+
- shared process-level helpers for command-surface runtimes:
|
|
199
|
+
- `createPluginInvokeProcessClient(...)`
|
|
200
|
+
- `resolveWasmEdgePluginLaunchPlan(...)`
|
|
201
|
+
- `buildWasmEdgeEmscriptenPthreadRunner(...)`
|
|
202
|
+
|
|
134
203
|
- `npm run test:runtime-matrix`
|
|
135
204
|
- cross-language runtime smoke across the same WASM in Node.js, Go, Python,
|
|
136
205
|
Rust, Java, C#, and Swift
|
|
@@ -145,20 +214,6 @@ The detailed edge cases and the current WASI-vs-host portability boundary are
|
|
|
145
214
|
documented in
|
|
146
215
|
[`docs/testing-harness.md`](./docs/testing-harness.md).
|
|
147
216
|
|
|
148
|
-
If a manifest declares `runtimeTargets: ["wasi"]`, this SDK now treats that as
|
|
149
|
-
"standalone WASI, no host wrapper required." In practice that currently means:
|
|
150
|
-
|
|
151
|
-
- the artifact must declare the `command` invoke surface
|
|
152
|
-
- declared capabilities must stay within the pure WASI subset:
|
|
153
|
-
`logging`, `clock`, `random`, `filesystem`, `pipe`
|
|
154
|
-
- hosted protocols may only use `wasi-pipe` transport
|
|
155
|
-
|
|
156
|
-
For maximum server-side portability with guest-owned network services, use
|
|
157
|
-
`runtimeTargets: ["wasmedge"]`. That target is intended for WasmEdge
|
|
158
|
-
environments with socket/TLS extensions, while plain `wasi` remains the strict
|
|
159
|
-
no-wrapper baseline. The Node-RED-oriented parity map lives in
|
|
160
|
-
[`docs/node-red-default-node-parity.md`](./docs/node-red-default-node-parity.md).
|
|
161
|
-
|
|
162
217
|
## Install
|
|
163
218
|
|
|
164
219
|
```bash
|
|
@@ -176,6 +231,8 @@ import {
|
|
|
176
231
|
validateManifestWithStandards,
|
|
177
232
|
} from "space-data-module-sdk";
|
|
178
233
|
|
|
234
|
+
manifest.runtimeTargets = ["wasmedge"];
|
|
235
|
+
|
|
179
236
|
const manifestBytes = encodePluginManifest(manifest);
|
|
180
237
|
const validation = await validateManifestWithStandards(manifest);
|
|
181
238
|
if (!validation.ok) {
|
|
@@ -186,6 +243,8 @@ const compilation = await compileModuleFromSource({
|
|
|
186
243
|
manifest,
|
|
187
244
|
sourceCode,
|
|
188
245
|
language: "c",
|
|
246
|
+
// Optional. Defaults from manifest.runtimeTargets.
|
|
247
|
+
// threadModel: "emscripten-pthreads",
|
|
189
248
|
});
|
|
190
249
|
|
|
191
250
|
const bundle = await createSingleFileBundle({
|
|
@@ -243,10 +302,11 @@ The full contract split is documented in
|
|
|
243
302
|
|
|
244
303
|
`sds.bundle` keeps module delivery to one file without changing WebAssembly
|
|
245
304
|
loadability for the runtime payload itself. The SDK writes the bundle as a
|
|
246
|
-
standard custom section inside the wasm module
|
|
247
|
-
|
|
248
|
-
wasm bytes
|
|
249
|
-
|
|
305
|
+
standard custom section inside the wasm module. When the artifact is signed or
|
|
306
|
+
encrypted for publication, SDS appends FlatBuffer publication records after the
|
|
307
|
+
wasm bytes in a trailing `REC` container. Loaders must scan from the end,
|
|
308
|
+
resolve `PNM` / `ENC`, strip or decrypt as needed, and only then hand the
|
|
309
|
+
remaining raw wasm bytes to a runtime such as WasmEdge.
|
|
250
310
|
|
|
251
311
|
The reference path lives in
|
|
252
312
|
[`examples/single-file-bundle`](./examples/single-file-bundle):
|
|
@@ -263,7 +323,7 @@ for resolved protocol installations and producer input bindings.
|
|
|
263
323
|
|
|
264
324
|
## Module Publication
|
|
265
325
|
|
|
266
|
-
Packages that publish
|
|
326
|
+
Packages that publish Space Data modules use the canonical `sdn-module`
|
|
267
327
|
publication descriptor. That descriptor covers:
|
|
268
328
|
|
|
269
329
|
- standalone module packages
|
|
@@ -323,7 +383,7 @@ includes:
|
|
|
323
383
|
|
|
324
384
|
Manifests can also declare coarse runtime targets for planning and compliance:
|
|
325
385
|
|
|
326
|
-
`node` `browser` `wasi` `server` `desktop` `edge`
|
|
386
|
+
`node` `browser` `wasi` `wasmedge` `server` `desktop` `edge`
|
|
327
387
|
|
|
328
388
|
## Environment Notes
|
|
329
389
|
|
|
@@ -379,7 +439,8 @@ npm run check:compliance
|
|
|
379
439
|
```
|
|
380
440
|
|
|
381
441
|
Node.js `>=20` is required. The compiler uses `sdn-emception` and `flatc-wasm`
|
|
382
|
-
by default.
|
|
442
|
+
by default for the embedded toolchain path. WasmEdge pthread builds require a
|
|
443
|
+
system Emscripten toolchain on `PATH`.
|
|
383
444
|
|
|
384
445
|
If another repo needs the same compiler runtime, the package also exposes a
|
|
385
446
|
shared emception session at `space-data-module-sdk/compiler/emception` with
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
3
5
|
import {
|
|
4
6
|
mkdtemp,
|
|
7
|
+
mkdir,
|
|
8
|
+
readFile,
|
|
5
9
|
rm,
|
|
6
10
|
writeFile,
|
|
7
11
|
} from "node:fs/promises";
|
|
@@ -24,7 +28,11 @@ import {
|
|
|
24
28
|
} from "./flatcSupport.js";
|
|
25
29
|
import { runWithEmceptionLock } from "./emceptionNode.js";
|
|
26
30
|
import { encodePluginManifest, toEmbeddedPluginManifest } from "../manifest/index.js";
|
|
27
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
DefaultInvokeExports,
|
|
33
|
+
InvokeSurface,
|
|
34
|
+
RuntimeTarget,
|
|
35
|
+
} from "../runtime/constants.js";
|
|
28
36
|
import {
|
|
29
37
|
appendPublicationRecordCollection,
|
|
30
38
|
createEncryptedEnvelopePayload,
|
|
@@ -55,6 +63,12 @@ import { sha256Bytes } from "../utils/crypto.js";
|
|
|
55
63
|
import { getWasmWallet } from "../utils/wasmCrypto.js";
|
|
56
64
|
|
|
57
65
|
const C_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
66
|
+
const execFileAsync = promisify(execFile);
|
|
67
|
+
|
|
68
|
+
export const ModuleThreadModel = Object.freeze({
|
|
69
|
+
SINGLE_THREAD: "single-thread",
|
|
70
|
+
EMSCRIPTEN_PTHREADS: "emscripten-pthreads",
|
|
71
|
+
});
|
|
58
72
|
|
|
59
73
|
function selectCompiler(language) {
|
|
60
74
|
const normalized = String(language ?? "c").trim().toLowerCase();
|
|
@@ -81,16 +95,63 @@ function buildCompilerArgs(exportedSymbols, options = {}) {
|
|
|
81
95
|
(symbol) => "-Wl,--export=" + symbol,
|
|
82
96
|
);
|
|
83
97
|
const extraArgs = [];
|
|
98
|
+
const threadArgs = [];
|
|
99
|
+
if (options.threadModel === ModuleThreadModel.EMSCRIPTEN_PTHREADS) {
|
|
100
|
+
threadArgs.push("-pthread");
|
|
101
|
+
}
|
|
84
102
|
if (options.allowUndefinedImports === true) {
|
|
85
103
|
extraArgs.push("-s", "ERROR_ON_UNDEFINED_SYMBOLS=0", "-Wl,--allow-undefined");
|
|
86
104
|
}
|
|
87
|
-
const args = [
|
|
105
|
+
const args = [
|
|
106
|
+
"-O2",
|
|
107
|
+
...threadArgs,
|
|
108
|
+
"-s",
|
|
109
|
+
"STANDALONE_WASM=1",
|
|
110
|
+
...extraArgs,
|
|
111
|
+
...linkerExports,
|
|
112
|
+
];
|
|
88
113
|
if (options.noEntry === true) {
|
|
89
114
|
args.splice(1, 0, "--no-entry");
|
|
90
115
|
}
|
|
91
116
|
return args;
|
|
92
117
|
}
|
|
93
118
|
|
|
119
|
+
function normalizeThreadModel(value) {
|
|
120
|
+
if (value === undefined || value === null || value === "") {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const normalized = String(value).trim().toLowerCase();
|
|
124
|
+
if (normalized === ModuleThreadModel.SINGLE_THREAD) {
|
|
125
|
+
return ModuleThreadModel.SINGLE_THREAD;
|
|
126
|
+
}
|
|
127
|
+
if (normalized === ModuleThreadModel.EMSCRIPTEN_PTHREADS) {
|
|
128
|
+
return ModuleThreadModel.EMSCRIPTEN_PTHREADS;
|
|
129
|
+
}
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Unsupported threadModel "${value}". Expected "${ModuleThreadModel.SINGLE_THREAD}" or "${ModuleThreadModel.EMSCRIPTEN_PTHREADS}".`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveThreadModel({ manifest, threadModel } = {}) {
|
|
136
|
+
const explicit = normalizeThreadModel(threadModel);
|
|
137
|
+
if (explicit) {
|
|
138
|
+
return explicit;
|
|
139
|
+
}
|
|
140
|
+
const runtimeTargets = Array.isArray(manifest?.runtimeTargets)
|
|
141
|
+
? manifest.runtimeTargets
|
|
142
|
+
.map((target) => String(target ?? "").trim().toLowerCase())
|
|
143
|
+
.filter(Boolean)
|
|
144
|
+
: [];
|
|
145
|
+
if (runtimeTargets.includes(RuntimeTarget.WASMEDGE)) {
|
|
146
|
+
return ModuleThreadModel.EMSCRIPTEN_PTHREADS;
|
|
147
|
+
}
|
|
148
|
+
return ModuleThreadModel.SINGLE_THREAD;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function requiresSystemEmscripten(threadModel) {
|
|
152
|
+
return threadModel === ModuleThreadModel.EMSCRIPTEN_PTHREADS;
|
|
153
|
+
}
|
|
154
|
+
|
|
94
155
|
function guestLinkSymbolPrefix(pluginId) {
|
|
95
156
|
const normalized = String(pluginId ?? "module");
|
|
96
157
|
const ascii = Array.from(new TextEncoder().encode(normalized))
|
|
@@ -280,6 +341,7 @@ function createGuestLinkBundleEntries(guestLink) {
|
|
|
280
341
|
methodSymbols: guestLink.methodSymbols,
|
|
281
342
|
methodIds: Object.keys(guestLink.methodSymbols ?? {}),
|
|
282
343
|
language: guestLink.language,
|
|
344
|
+
threadModel: guestLink.threadModel ?? ModuleThreadModel.SINGLE_THREAD,
|
|
283
345
|
},
|
|
284
346
|
description: "Guest-link metadata for monolithic flow linking.",
|
|
285
347
|
},
|
|
@@ -302,6 +364,14 @@ async function writeFilesToEmception(emception, rootDir, files) {
|
|
|
302
364
|
}
|
|
303
365
|
}
|
|
304
366
|
|
|
367
|
+
async function writeFilesToDirectory(rootDir, files) {
|
|
368
|
+
for (const [relativePath, content] of Object.entries(files)) {
|
|
369
|
+
const filePath = path.join(rootDir, relativePath);
|
|
370
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
371
|
+
await writeFile(filePath, content);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
305
375
|
function removeEmceptionDirectory(emception, directoryPath) {
|
|
306
376
|
if (!emception.FS.analyzePath(directoryPath).exists) {
|
|
307
377
|
return;
|
|
@@ -466,6 +536,8 @@ async function compileWithEmception(options = {}) {
|
|
|
466
536
|
language,
|
|
467
537
|
symbolPrefix: guestLink.prefix,
|
|
468
538
|
methodSymbols: guestLink.methodSymbols,
|
|
539
|
+
threadModel:
|
|
540
|
+
compileOptions.threadModel ?? ModuleThreadModel.SINGLE_THREAD,
|
|
469
541
|
objectBytes: linkObjectBytes,
|
|
470
542
|
},
|
|
471
543
|
};
|
|
@@ -483,6 +555,166 @@ async function compileWithEmception(options = {}) {
|
|
|
483
555
|
}
|
|
484
556
|
}
|
|
485
557
|
|
|
558
|
+
async function ensureSystemCompilerAvailable(command) {
|
|
559
|
+
try {
|
|
560
|
+
await execFileAsync(command, ["--version"]);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (error?.code === "ENOENT") {
|
|
563
|
+
throw new Error(
|
|
564
|
+
`System Emscripten toolchain is required for "${ModuleThreadModel.EMSCRIPTEN_PTHREADS}" builds, but "${command}" was not found on PATH.`,
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
throw error;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async function runSystemCompiler(command, args, options = {}) {
|
|
572
|
+
try {
|
|
573
|
+
await execFileAsync(command, args, {
|
|
574
|
+
cwd: options.cwd,
|
|
575
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
576
|
+
});
|
|
577
|
+
} catch (error) {
|
|
578
|
+
const stderr = typeof error?.stderr === "string" ? error.stderr.trim() : "";
|
|
579
|
+
const stdout = typeof error?.stdout === "string" ? error.stdout.trim() : "";
|
|
580
|
+
const detail = stderr || stdout || error?.message || "unknown error";
|
|
581
|
+
throw new Error(
|
|
582
|
+
`Compilation failed with ${command} (system emscripten): ${detail}`,
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async function compileWithSystemEmscripten(options = {}) {
|
|
588
|
+
const {
|
|
589
|
+
manifest,
|
|
590
|
+
language,
|
|
591
|
+
sourceCompilerCommand,
|
|
592
|
+
sourceExtension,
|
|
593
|
+
sourceCode,
|
|
594
|
+
manifestSource,
|
|
595
|
+
invokeHeaderSource,
|
|
596
|
+
invokeSource,
|
|
597
|
+
exportedSymbols,
|
|
598
|
+
outputPath,
|
|
599
|
+
compileOptions,
|
|
600
|
+
} = options;
|
|
601
|
+
const tempDir = await mkdtemp(
|
|
602
|
+
path.join(os.tmpdir(), "space-data-module-sdk-compile-"),
|
|
603
|
+
);
|
|
604
|
+
const resolvedOutputPath = path.resolve(
|
|
605
|
+
outputPath ?? path.join(tempDir, "module.wasm"),
|
|
606
|
+
);
|
|
607
|
+
const runtimeIncludeDir = path.join(tempDir, "flatbuffers-runtime");
|
|
608
|
+
const sourcePath = path.join(tempDir, `module.${sourceExtension}`);
|
|
609
|
+
const manifestSourcePath = path.join(tempDir, "plugin-manifest-exports.cpp");
|
|
610
|
+
const invokeHeaderPath = path.join(tempDir, "space_data_module_invoke.h");
|
|
611
|
+
const invokeSourcePath = path.join(tempDir, "plugin-invoke-bridge.cpp");
|
|
612
|
+
const sourceObjectPath = path.join(tempDir, "module.o");
|
|
613
|
+
const linkObjectPath = path.join(tempDir, "module-link.o");
|
|
614
|
+
const manifestObjectPath = path.join(tempDir, "plugin-manifest-exports.o");
|
|
615
|
+
const invokeObjectPath = path.join(tempDir, "plugin-invoke-bridge.o");
|
|
616
|
+
const wasmOutputPath = path.join(tempDir, "module.wasm");
|
|
617
|
+
|
|
618
|
+
try {
|
|
619
|
+
await ensureSystemCompilerAvailable(sourceCompilerCommand);
|
|
620
|
+
await ensureSystemCompilerAvailable("em++");
|
|
621
|
+
const { runtimeHeaders, schemaHeaders } = await getInvokeCppSupportFiles();
|
|
622
|
+
const args = buildCompilerArgs(exportedSymbols, compileOptions);
|
|
623
|
+
await writeFilesToDirectory(runtimeIncludeDir, runtimeHeaders);
|
|
624
|
+
await writeFilesToDirectory(tempDir, schemaHeaders);
|
|
625
|
+
await writeFile(sourcePath, sourceCode);
|
|
626
|
+
await writeFile(manifestSourcePath, manifestSource);
|
|
627
|
+
await writeFile(invokeHeaderPath, invokeHeaderSource);
|
|
628
|
+
await writeFile(invokeSourcePath, invokeSource);
|
|
629
|
+
|
|
630
|
+
const sourceCompileArgs = [
|
|
631
|
+
"-c",
|
|
632
|
+
sourcePath,
|
|
633
|
+
`-I${tempDir}`,
|
|
634
|
+
...(compileOptions.threadModel === ModuleThreadModel.EMSCRIPTEN_PTHREADS
|
|
635
|
+
? ["-pthread"]
|
|
636
|
+
: []),
|
|
637
|
+
"-o",
|
|
638
|
+
sourceObjectPath,
|
|
639
|
+
];
|
|
640
|
+
await runSystemCompiler(sourceCompilerCommand, sourceCompileArgs);
|
|
641
|
+
|
|
642
|
+
const sourceObjectBytes = new Uint8Array(await readFile(sourceObjectPath));
|
|
643
|
+
const guestLink = deriveGuestLinkRenameArgs({
|
|
644
|
+
objectBytes: sourceObjectBytes,
|
|
645
|
+
pluginId: manifest?.pluginId,
|
|
646
|
+
methodIds: Array.isArray(manifest?.methods)
|
|
647
|
+
? manifest.methods.map((method) => String(method?.methodId ?? ""))
|
|
648
|
+
: [],
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
const linkCompileArgs = [
|
|
652
|
+
"-c",
|
|
653
|
+
sourcePath,
|
|
654
|
+
`-I${tempDir}`,
|
|
655
|
+
...(compileOptions.threadModel === ModuleThreadModel.EMSCRIPTEN_PTHREADS
|
|
656
|
+
? ["-pthread"]
|
|
657
|
+
: []),
|
|
658
|
+
...guestLink.renameArgs,
|
|
659
|
+
"-o",
|
|
660
|
+
linkObjectPath,
|
|
661
|
+
];
|
|
662
|
+
await runSystemCompiler(sourceCompilerCommand, linkCompileArgs);
|
|
663
|
+
await runSystemCompiler("em++", [
|
|
664
|
+
"-c",
|
|
665
|
+
manifestSourcePath,
|
|
666
|
+
"-std=c++17",
|
|
667
|
+
`-I${tempDir}`,
|
|
668
|
+
`-I${runtimeIncludeDir}`,
|
|
669
|
+
...(compileOptions.threadModel === ModuleThreadModel.EMSCRIPTEN_PTHREADS
|
|
670
|
+
? ["-pthread"]
|
|
671
|
+
: []),
|
|
672
|
+
"-o",
|
|
673
|
+
manifestObjectPath,
|
|
674
|
+
]);
|
|
675
|
+
await runSystemCompiler("em++", [
|
|
676
|
+
"-c",
|
|
677
|
+
invokeSourcePath,
|
|
678
|
+
"-std=c++17",
|
|
679
|
+
`-I${tempDir}`,
|
|
680
|
+
`-I${runtimeIncludeDir}`,
|
|
681
|
+
...(compileOptions.threadModel === ModuleThreadModel.EMSCRIPTEN_PTHREADS
|
|
682
|
+
? ["-pthread"]
|
|
683
|
+
: []),
|
|
684
|
+
"-o",
|
|
685
|
+
invokeObjectPath,
|
|
686
|
+
]);
|
|
687
|
+
await runSystemCompiler("em++", [
|
|
688
|
+
sourceObjectPath,
|
|
689
|
+
manifestObjectPath,
|
|
690
|
+
invokeObjectPath,
|
|
691
|
+
...args,
|
|
692
|
+
"-o",
|
|
693
|
+
wasmOutputPath,
|
|
694
|
+
]);
|
|
695
|
+
|
|
696
|
+
const wasmBytes = new Uint8Array(await readFile(wasmOutputPath));
|
|
697
|
+
const linkObjectBytes = new Uint8Array(await readFile(linkObjectPath));
|
|
698
|
+
await writeFile(resolvedOutputPath, wasmBytes);
|
|
699
|
+
return {
|
|
700
|
+
wasmBytes,
|
|
701
|
+
outputPath: resolvedOutputPath,
|
|
702
|
+
tempDir,
|
|
703
|
+
guestLink: {
|
|
704
|
+
format: "wasm-object",
|
|
705
|
+
language,
|
|
706
|
+
symbolPrefix: guestLink.prefix,
|
|
707
|
+
methodSymbols: guestLink.methodSymbols,
|
|
708
|
+
threadModel: compileOptions.threadModel,
|
|
709
|
+
objectBytes: linkObjectBytes,
|
|
710
|
+
},
|
|
711
|
+
};
|
|
712
|
+
} catch (error) {
|
|
713
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
714
|
+
throw error;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
486
718
|
export async function compileModuleFromSource(options = {}) {
|
|
487
719
|
const manifest = options.manifest ?? {};
|
|
488
720
|
const sourceCode = String(options.sourceCode ?? "");
|
|
@@ -531,11 +763,19 @@ export async function compileModuleFromSource(options = {}) {
|
|
|
531
763
|
let wasmBytes;
|
|
532
764
|
let resolvedOutputPath = null;
|
|
533
765
|
let tempDir = null;
|
|
766
|
+
const threadModel = resolveThreadModel({
|
|
767
|
+
manifest,
|
|
768
|
+
threadModel: options.threadModel,
|
|
769
|
+
});
|
|
534
770
|
const compileOptions = {
|
|
535
771
|
...options,
|
|
536
772
|
noEntry: includeCommandMain !== true,
|
|
773
|
+
threadModel,
|
|
537
774
|
};
|
|
538
|
-
const
|
|
775
|
+
const compileFunction = requiresSystemEmscripten(threadModel)
|
|
776
|
+
? compileWithSystemEmscripten
|
|
777
|
+
: compileWithEmception;
|
|
778
|
+
const result = await compileFunction({
|
|
539
779
|
manifest,
|
|
540
780
|
language: compiler.language,
|
|
541
781
|
sourceCompilerCommand: compiler.command,
|
|
@@ -559,8 +799,11 @@ export async function compileModuleFromSource(options = {}) {
|
|
|
559
799
|
});
|
|
560
800
|
|
|
561
801
|
return {
|
|
562
|
-
compiler:
|
|
802
|
+
compiler: requiresSystemEmscripten(threadModel)
|
|
803
|
+
? "em++ (system emscripten pthreads)"
|
|
804
|
+
: "em++ (emception)",
|
|
563
805
|
language: compiler.language,
|
|
806
|
+
threadModel,
|
|
564
807
|
outputPath: resolvedOutputPath,
|
|
565
808
|
tempDir,
|
|
566
809
|
wasmBytes,
|
package/src/compiler/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export type {
|
|
2
2
|
CompilationResult,
|
|
3
|
+
GuestLinkArtifact,
|
|
4
|
+
ModuleThreadModelName,
|
|
3
5
|
ProtectedArtifact,
|
|
4
6
|
} from "../index.js";
|
|
5
7
|
|
|
@@ -7,6 +9,7 @@ export {
|
|
|
7
9
|
cleanupCompilation,
|
|
8
10
|
compileModuleFromSource,
|
|
9
11
|
createRecipientKeypairHex,
|
|
12
|
+
ModuleThreadModel,
|
|
10
13
|
protectModuleArtifact,
|
|
11
14
|
} from "../index.js";
|
|
12
15
|
|
package/src/compiler/index.js
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -556,6 +556,7 @@ export function decodeProtectedBlobBase64(
|
|
|
556
556
|
export interface CompilationResult {
|
|
557
557
|
compiler: string;
|
|
558
558
|
language: string;
|
|
559
|
+
threadModel: ModuleThreadModelName;
|
|
559
560
|
outputPath: string | null;
|
|
560
561
|
tempDir: string | null;
|
|
561
562
|
wasmBytes: Uint8Array;
|
|
@@ -569,9 +570,19 @@ export interface GuestLinkArtifact {
|
|
|
569
570
|
language: string;
|
|
570
571
|
symbolPrefix: string;
|
|
571
572
|
methodSymbols: Record<string, string>;
|
|
573
|
+
threadModel: ModuleThreadModelName;
|
|
572
574
|
objectBytes: Uint8Array;
|
|
573
575
|
}
|
|
574
576
|
|
|
577
|
+
export type ModuleThreadModelName =
|
|
578
|
+
| "single-thread"
|
|
579
|
+
| "emscripten-pthreads";
|
|
580
|
+
|
|
581
|
+
export const ModuleThreadModel: {
|
|
582
|
+
readonly SINGLE_THREAD: "single-thread";
|
|
583
|
+
readonly EMSCRIPTEN_PTHREADS: "emscripten-pthreads";
|
|
584
|
+
};
|
|
585
|
+
|
|
575
586
|
export interface ProtectedArtifact {
|
|
576
587
|
mnemonic: string;
|
|
577
588
|
signingPublicKeyHex: string;
|
|
@@ -602,6 +613,7 @@ export function compileModuleFromSource(options: {
|
|
|
602
613
|
manifest: PluginManifest;
|
|
603
614
|
sourceCode: string;
|
|
604
615
|
language?: string;
|
|
616
|
+
threadModel?: ModuleThreadModelName;
|
|
605
617
|
outputPath?: string;
|
|
606
618
|
allowUndefinedImports?: boolean;
|
|
607
619
|
}): Promise<CompilationResult>;
|
|
@@ -650,12 +662,25 @@ export type {
|
|
|
650
662
|
HarnessInvokeScenario,
|
|
651
663
|
HarnessRawScenario,
|
|
652
664
|
ManifestHarnessPlan,
|
|
665
|
+
ModuleHarness,
|
|
666
|
+
ModuleHarnessRuntimeDescriptor,
|
|
667
|
+
PluginInvokeProcessClient,
|
|
668
|
+
PluginInvokeProcessLaunchPlan,
|
|
669
|
+
WasmEdgeRunnerBuildPlan,
|
|
653
670
|
} from "./testing/index.js";
|
|
654
671
|
|
|
655
672
|
export {
|
|
673
|
+
buildWasmEdgeEmscriptenPthreadRunner,
|
|
674
|
+
buildWasmEdgeSpawnEnv,
|
|
675
|
+
createModuleHarness,
|
|
676
|
+
createPluginInvokeProcessClient,
|
|
656
677
|
describeCapabilityRuntimeSurface,
|
|
657
678
|
generateManifestHarnessPlan,
|
|
658
679
|
materializeHarnessScenario,
|
|
680
|
+
resolveModuleHarnessLaunchPlan,
|
|
681
|
+
resolveWasmEdgeRunnerBuildPlan,
|
|
682
|
+
resolveWasmEdgeRunnerSourcePath,
|
|
683
|
+
resolveWasmEdgePluginLaunchPlan,
|
|
659
684
|
serializeHarnessPlan,
|
|
660
685
|
} from "./testing/index.js";
|
|
661
686
|
|
package/src/invoke/codec.js
CHANGED
|
@@ -12,7 +12,7 @@ import { InvokeSurface } from "../generated/orbpro/manifest/invoke-surface.js";
|
|
|
12
12
|
import { BufferMutability } from "../generated/orbpro/stream/buffer-mutability.js";
|
|
13
13
|
import { BufferOwnership } from "../generated/orbpro/stream/buffer-ownership.js";
|
|
14
14
|
import { FlatBufferTypeRefT } from "../generated/orbpro/stream/flat-buffer-type-ref.js";
|
|
15
|
-
import {
|
|
15
|
+
import { TypedArenaBufferT } from "../generated/orbpro/stream/typed-arena-buffer.js";
|
|
16
16
|
import { toUint8Array } from "../runtime/bufferLike.js";
|
|
17
17
|
|
|
18
18
|
function toByteBuffer(data) {
|
|
@@ -149,20 +149,22 @@ function normalizeArenaFrame(frame = {}, offset) {
|
|
|
149
149
|
|
|
150
150
|
function packArenaFrames(frames = []) {
|
|
151
151
|
const packedFrames = [];
|
|
152
|
-
const
|
|
152
|
+
const normalizedFrames = [];
|
|
153
153
|
let offset = 0;
|
|
154
154
|
for (const frame of frames) {
|
|
155
155
|
const normalized = normalizeArenaFrame(frame, offset);
|
|
156
|
-
for (let index = 0; index < normalized.padding; index += 1) {
|
|
157
|
-
arena.push(0);
|
|
158
|
-
}
|
|
159
|
-
arena.push(...normalized.payload);
|
|
160
156
|
offset = normalized.buffer.offset + normalized.buffer.size;
|
|
161
157
|
packedFrames.push(normalized.buffer);
|
|
158
|
+
normalizedFrames.push(normalized);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const arena = new Uint8Array(offset);
|
|
162
|
+
for (const normalized of normalizedFrames) {
|
|
163
|
+
arena.set(normalized.payload, normalized.buffer.offset);
|
|
162
164
|
}
|
|
163
165
|
return {
|
|
164
166
|
frames: packedFrames,
|
|
165
|
-
arena
|
|
167
|
+
arena,
|
|
166
168
|
};
|
|
167
169
|
}
|
|
168
170
|
|