@bytecodealliance/jco 1.9.1 → 1.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytecodealliance/jco",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "description": "JavaScript tooling for working with WebAssembly Components",
5
5
  "author": "Guy Bedford",
6
6
  "bin": {
@@ -24,9 +24,9 @@
24
24
  },
25
25
  "type": "module",
26
26
  "dependencies": {
27
- "@bytecodealliance/componentize-js": "^0.15.0",
27
+ "@bytecodealliance/componentize-js": "^0.17.0",
28
28
  "@bytecodealliance/preview2-shim": "^0.17.1",
29
- "binaryen": "^120.0.0",
29
+ "binaryen": "^122.0.0",
30
30
  "chalk-template": "^1",
31
31
  "commander": "^12",
32
32
  "mkdirp": "^3",
@@ -62,8 +62,8 @@
62
62
  "build:release": "cargo xtask build release",
63
63
  "build:types:preview2-shim": "cargo xtask generate wasi-types",
64
64
  "lint": "eslint -c eslintrc.cjs src/**/*.js packages/*/lib/**/*.js",
65
- "test:lts": "mocha -u tdd test/test.js --timeout 120000",
66
- "test": "node --stack-trace-limit=100 node_modules/mocha/bin/mocha.js -u tdd test/test.js --timeout 120000",
65
+ "test:lts": "mocha -u tdd test/test.js --timeout 240000",
66
+ "test": "node --experimental-wasm-jspi --stack-trace-limit=100 node_modules/mocha/bin/mocha.js -u tdd test/test.js --timeout 240000",
67
67
  "prepublishOnly": "cargo xtask build release && npm run test"
68
68
  },
69
69
  "files": [
@@ -1 +1 @@
1
- {"version":3,"file":"componentize.d.ts","sourceRoot":"","sources":["componentize.js"],"names":[],"mappings":"AAIA,sEAiBC"}
1
+ {"version":3,"file":"componentize.d.ts","sourceRoot":"","sources":["componentize.js"],"names":[],"mappings":"AAIA,sEAkBC"}
package/src/cmd/opt.d.ts CHANGED
@@ -2,10 +2,15 @@ export function opt(componentPath: any, opts: any, program: any): Promise<void>;
2
2
  /**
3
3
  *
4
4
  * @param {Uint8Array} componentBytes
5
- * @param {{ quiet: boolean, optArgs?: string[] }} options?
5
+ * @param {{ quiet: boolean, asyncify?: boolean, optArgs?: string[], noVerify?: boolean }} opts?
6
6
  * @returns {Promise<{ component: Uint8Array, compressionInfo: { beforeBytes: number, afterBytes: number }[] >}
7
7
  */
8
- export function optimizeComponent(componentBytes: Uint8Array, opts: any): Promise<{
8
+ export function optimizeComponent(componentBytes: Uint8Array, opts: {
9
+ quiet: boolean;
10
+ asyncify?: boolean;
11
+ optArgs?: string[];
12
+ noVerify?: boolean;
13
+ }): Promise<{
9
14
  component: Uint8Array;
10
15
  compressionInfo: {
11
16
  beforeBytes: number;
@@ -1 +1 @@
1
- {"version":3,"file":"opt.d.ts","sourceRoot":"","sources":["opt.js"],"names":[],"mappings":"AAQA,gFAoCC;AAED;;;;;GAKG;AACH,kDAJW,UAAU,cAER,OAAO,CAAC;IAAE,SAAS,EAAE,UAAU,CAAC;IAAC,eAAe,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAA,CAAE,CA8E7G"}
1
+ {"version":3,"file":"opt.d.ts","sourceRoot":"","sources":["opt.js"],"names":[],"mappings":"AAgBA,gFA2CC;AAgBD;;;;;GAKG;AACH,kDAJW,UAAU,QACV;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5E,OAAO,CAAC;IAAE,SAAS,EAAE,UAAU,CAAC;IAAC,eAAe,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAAA,CAAE,CAkK7G"}
package/src/cmd/opt.js CHANGED
@@ -1,16 +1,23 @@
1
- import { $init, tools } from '../../obj/wasm-tools.js';
1
+ import { $init, tools } from "../../obj/wasm-tools.js";
2
2
  const { metadataShow, print } = tools;
3
- import { writeFile } from 'fs/promises';
4
- import { fileURLToPath } from 'url';
5
- import c from 'chalk-template';
6
- import { readFile, sizeStr, fixedDigitDisplay, table, spawnIOTmp, setShowSpinner, getShowSpinner } from '../common.js';
7
- import ora from '#ora';
3
+ import { writeFile } from "fs/promises";
4
+ import { fileURLToPath } from "url";
5
+ import c from "chalk-template";
6
+ import {
7
+ readFile,
8
+ sizeStr,
9
+ fixedDigitDisplay,
10
+ table,
11
+ spawnIOTmp,
12
+ setShowSpinner,
13
+ getShowSpinner,
14
+ } from "../common.js";
15
+ import ora from "#ora";
8
16
 
9
- export async function opt (componentPath, opts, program) {
17
+ export async function opt(componentPath, opts, program) {
10
18
  await $init;
11
- const varIdx = program.parent.rawArgs.indexOf('--');
12
- if (varIdx !== -1)
13
- opts.optArgs = program.parent.rawArgs.slice(varIdx + 1);
19
+ const varIdx = program.parent.rawArgs.indexOf("--");
20
+ if (varIdx !== -1) opts.optArgs = program.parent.rawArgs.slice(varIdx + 1);
14
21
  const componentBytes = await readFile(componentPath);
15
22
 
16
23
  if (!opts.quiet) setShowSpinner(true);
@@ -19,128 +26,235 @@ export async function opt (componentPath, opts, program) {
19
26
 
20
27
  await writeFile(opts.output, component);
21
28
 
22
- let totalBeforeBytes = 0, totalAfterBytes = 0;
29
+ let totalBeforeBytes = 0,
30
+ totalAfterBytes = 0;
23
31
 
24
32
  if (!opts.quiet)
25
33
  console.log(c`
26
34
  {bold Optimized WebAssembly Component Internal Core Modules:}
27
35
 
28
- ${table([...compressionInfo.map(({ beforeBytes, afterBytes }, i) => {
29
- totalBeforeBytes += beforeBytes;
30
- totalAfterBytes += afterBytes;
31
- return [
32
- ` - Core Module ${i + 1}: `,
33
- sizeStr(beforeBytes),
34
- ' -> ',
35
- c`{cyan ${sizeStr(afterBytes)}} `,
36
- `(${fixedDigitDisplay(afterBytes / beforeBytes * 100, 2)}%)`
37
- ];
38
- }), ['', '', '', '', ''], [
39
- ` = Total: `,
40
- `${sizeStr(totalBeforeBytes)}`,
41
- ` => `,
42
- c`{cyan ${sizeStr(totalAfterBytes)}} `,
43
- `(${fixedDigitDisplay(totalAfterBytes / totalBeforeBytes * 100, 2)}%)`
44
- ]], [,,,,'right'])}`);
36
+ ${table(
37
+ [
38
+ ...compressionInfo.map(({ beforeBytes, afterBytes }, i) => {
39
+ totalBeforeBytes += beforeBytes;
40
+ totalAfterBytes += afterBytes;
41
+ return [
42
+ ` - Core Module ${i + 1}: `,
43
+ sizeStr(beforeBytes),
44
+ " -> ",
45
+ c`{cyan ${sizeStr(afterBytes)}} `,
46
+ `(${fixedDigitDisplay((afterBytes / beforeBytes) * 100, 2)}%)`,
47
+ ];
48
+ }),
49
+ ["", "", "", "", ""],
50
+ [
51
+ ` = Total: `,
52
+ `${sizeStr(totalBeforeBytes)}`,
53
+ ` => `,
54
+ c`{cyan ${sizeStr(totalAfterBytes)}} `,
55
+ `(${fixedDigitDisplay((totalAfterBytes / totalBeforeBytes) * 100, 2)}%)`,
56
+ ],
57
+ ],
58
+ [, , , , "right"]
59
+ )}`);
45
60
  }
46
61
 
47
62
  /**
48
- *
49
- * @param {Uint8Array} componentBytes
50
- * @param {{ quiet: boolean, optArgs?: string[] }} options?
63
+ * Counts the byte length for the LEB128 encoding of a number.
64
+ * @param {number} val
65
+ * @returns {number}
66
+ */
67
+ function byteLengthLEB128(val) {
68
+ let len = 0;
69
+ do {
70
+ val >>>= 7;
71
+ len++;
72
+ } while (val !== 0);
73
+ return len;
74
+ }
75
+
76
+ /**
77
+ *
78
+ * @param {Uint8Array} componentBytes
79
+ * @param {{ quiet: boolean, asyncify?: boolean, optArgs?: string[], noVerify?: boolean }} opts?
51
80
  * @returns {Promise<{ component: Uint8Array, compressionInfo: { beforeBytes: number, afterBytes: number }[] >}
52
81
  */
53
- export async function optimizeComponent (componentBytes, opts) {
82
+ export async function optimizeComponent(componentBytes, opts) {
54
83
  await $init;
55
84
  const showSpinner = getShowSpinner();
56
85
  let spinner;
57
86
  try {
58
- const coreModules = metadataShow(componentBytes).slice(1, -1).map(({ range }) => range);
87
+ let componentMetadata = metadataShow(componentBytes);
88
+ componentMetadata.forEach((metadata, index) => {
89
+ // add index to the metadata object
90
+ metadata.index = index;
91
+ const size = metadata.range[1] - metadata.range[0];
92
+ // compute previous LEB128 encoding length
93
+ metadata.prevLEBLen = byteLengthLEB128(size);
94
+ });
95
+ const coreModules = componentMetadata
96
+ .filter(({ metaType }) => metaType.tag === "module");
59
97
 
98
+ // log number of core Wasm modules to be run with wasm-opt
60
99
  let completed = 0;
61
- const spinnerText = () => c`{cyan ${completed} / ${coreModules.length}} Running Binaryen on WebAssembly Component Internal Core Modules \n`;
100
+ const spinnerText = () =>
101
+ c`{cyan ${completed} / ${coreModules.length}} Running Binaryen on WebAssembly Component Internal Core Modules \n`;
62
102
  if (showSpinner) {
63
103
  spinner = ora({
64
- color: 'cyan',
65
- spinner: 'bouncingBar'
104
+ color: "cyan",
105
+ spinner: "bouncingBar",
66
106
  }).start();
67
107
  spinner.text = spinnerText();
68
108
  }
69
109
 
70
- const optimizedCoreModules = await Promise.all(coreModules.map(async ([coreModuleStart, coreModuleEnd]) => {
71
- const optimized = wasmOpt(componentBytes.subarray(coreModuleStart, coreModuleEnd), opts?.optArgs);
72
- if (spinner) {
73
- completed++;
74
- spinner.text = spinnerText();
110
+ // gather the options for wasm-opt. optionally, adding the asyncify flag
111
+ const args = opts?.optArgs ?
112
+ [...opts.optArgs] :
113
+ ['-Oz', '--low-memory-unused', '--enable-bulk-memory', '--strip-debug'];
114
+ if (opts?.asyncify) args.push('--asyncify');
115
+
116
+
117
+ // process core Wasm modules with wasm-opt
118
+ await Promise.all(
119
+ coreModules.map(async (metadata) => {
120
+ if (metadata.metaType.tag === "module") {
121
+ // store the wasm-opt processed module in the metadata
122
+ metadata.optimized = await wasmOpt(
123
+ componentBytes.subarray(metadata.range[0], metadata.range[1]),
124
+ args);
125
+
126
+ // compute the size change, including the change to
127
+ // the LEB128 encoding of the size change
128
+ const prevModuleSize = metadata.range[1] - metadata.range[0];
129
+ const newModuleSize = metadata.optimized.byteLength;
130
+ metadata.newLEBLen = byteLengthLEB128(newModuleSize);
131
+ metadata.sizeChange = newModuleSize - prevModuleSize;
132
+
133
+ if (spinner) {
134
+ completed++;
135
+ spinner.text = spinnerText();
136
+ }
137
+ }
138
+ }));
139
+
140
+ // organize components in modules into tree parent and children
141
+ const nodes = componentMetadata.slice(1);
142
+ const getChildren = (parentIndex) => {
143
+ const children = [];
144
+ for (let i = 0; i < nodes.length; i++) {
145
+ const metadata = nodes[i];
146
+ if (metadata.parentIndex === parentIndex) {
147
+ nodes.splice(i, 1); // remove from nodes
148
+ i--;
149
+ metadata.children = getChildren(metadata.index);
150
+ metadata.sizeChange = metadata.children
151
+ .reduce((total, {prevLEBLen, newLEBLen, sizeChange}) => {
152
+ return sizeChange ?
153
+ total + sizeChange + newLEBLen - prevLEBLen :
154
+ total;
155
+ },
156
+ metadata.sizeChange || 0);
157
+ const prevSize = metadata.range[1] - metadata.range[0];
158
+ metadata.newLEBLen = byteLengthLEB128(prevSize + metadata.sizeChange);
159
+ children.push(metadata);
160
+ }
75
161
  }
76
- return optimized;
77
- }));
162
+ return children;
163
+ };
164
+ const componentTree = getChildren(0);
78
165
 
79
- let outComponentBytes = new Uint8Array(componentBytes.byteLength);
80
- let nextReadPos = 0, nextWritePos = 0;
81
- for (let i = 0; i < coreModules.length; i++) {
82
- const [coreModuleStart, coreModuleEnd] = coreModules[i];
83
- const optimizedCoreModule = optimizedCoreModules[i];
166
+ // compute the total size change in the component binary
167
+ const sizeChange = componentTree
168
+ .reduce((total, {prevLEBLen, newLEBLen, sizeChange}) => {
169
+ return total + (sizeChange || 0) + newLEBLen - prevLEBLen;
170
+ }, 0);
84
171
 
85
- let lebByteLen = 1;
86
- while (componentBytes[coreModuleStart - 1 - lebByteLen] & 0x80) lebByteLen++;
172
+ let outComponentBytes = new Uint8Array(
173
+ componentBytes.byteLength + sizeChange);
174
+ let nextReadPos = 0, nextWritePos = 0;
87
175
 
88
- // Write from the last read to the LEB byte start of the core module
89
- outComponentBytes.set(componentBytes.subarray(nextReadPos, coreModuleStart - lebByteLen), nextWritePos);
90
- nextWritePos += coreModuleStart - lebByteLen - nextReadPos;
176
+ const write = ({prevLEBLen, range, optimized, children, sizeChange}) => {
177
+ // write from the last read to the LEB byte start
178
+ outComponentBytes.set(
179
+ componentBytes.subarray(nextReadPos, range[0] - prevLEBLen),
180
+ nextWritePos
181
+ );
182
+ nextWritePos += range[0] - prevLEBLen - nextReadPos;
91
183
 
92
- // Write the new LEB bytes
93
- let val = optimizedCoreModule.byteLength;
184
+ // write the new LEB bytes
185
+ let val = range[1] - range[0] + sizeChange;
94
186
  do {
95
- const byte = val & 0x7F;
187
+ const byte = val & 0x7f;
96
188
  val >>>= 7;
97
189
  outComponentBytes[nextWritePos++] = val === 0 ? byte : byte | 0x80;
98
190
  } while (val !== 0);
99
191
 
100
- // Write the core module
101
- outComponentBytes.set(optimizedCoreModule, nextWritePos);
102
- nextWritePos += optimizedCoreModule.byteLength;
103
-
104
- nextReadPos = coreModuleEnd;
105
- }
192
+ if (optimized) {
193
+ // write the core module
194
+ outComponentBytes.set(optimized, nextWritePos);
195
+ nextReadPos = range[1];
196
+ nextWritePos += optimized.byteLength;
197
+ } else if (children.length > 0) {
198
+ // write child components / modules
199
+ nextReadPos = range[0];
200
+ children.forEach(write);
201
+ } else {
202
+ // write component
203
+ outComponentBytes.set(
204
+ componentBytes.subarray(range[0], range[1]),
205
+ nextWritePos
206
+ );
207
+ nextReadPos = range[1];
208
+ nextWritePos += range[1] - range[0];
209
+ }
210
+ };
106
211
 
107
- outComponentBytes.set(componentBytes.subarray(nextReadPos, componentBytes.byteLength), nextWritePos);
108
- nextWritePos += componentBytes.byteLength - nextReadPos;
109
- nextReadPos += componentBytes.byteLength - nextReadPos;
212
+ // write each top-level component / module
213
+ componentTree.forEach(write);
110
214
 
111
- outComponentBytes = outComponentBytes.subarray(0, outComponentBytes.length + nextWritePos - nextReadPos);
215
+ // write remaining
216
+ outComponentBytes.set(
217
+ componentBytes.subarray(nextReadPos),
218
+ nextWritePos
219
+ );
112
220
 
113
221
  // verify it still parses ok
114
- try {
115
- await print(outComponentBytes);
116
- } catch (e) {
117
- throw new Error(`Internal error performing optimization.\n${e.message}`)
222
+ if (!opts?.noVerify) {
223
+ try {
224
+ await print(outComponentBytes);
225
+ } catch (e) {
226
+ throw new Error(
227
+ `Internal error performing optimization.\n${e.message}`
228
+ );
229
+ }
118
230
  }
119
231
 
120
232
  return {
121
233
  component: outComponentBytes,
122
- compressionInfo: coreModules.map(([s, e], i) => ({ beforeBytes: e - s, afterBytes: optimizedCoreModules[i].byteLength }))
234
+ compressionInfo: coreModules.map(({range, optimized}) => ({
235
+ beforeBytes: range[1] - range[0],
236
+ afterBytes: optimized?.byteLength,
237
+ })),
123
238
  };
124
- }
125
- finally {
126
- if (spinner)
127
- spinner.stop();
239
+ } finally {
240
+ if (spinner) spinner.stop();
128
241
  }
129
242
  }
130
243
 
131
244
  /**
132
- * @param {Uint8Array} source
245
+ * @param {Uint8Array} source
246
+ * @param {Array<string>} args
133
247
  * @returns {Promise<Uint8Array>}
134
248
  */
135
- async function wasmOpt(source, args = ['-O1', '--low-memory-unused', '--enable-bulk-memory']) {
136
- const wasmOptPath = fileURLToPath(import.meta.resolve('binaryen/bin/wasm-opt'));
249
+ async function wasmOpt(source, args) {
250
+ const wasmOptPath = fileURLToPath(
251
+ import.meta.resolve("binaryen/bin/wasm-opt")
252
+ );
137
253
 
138
254
  try {
139
- return await spawnIOTmp(wasmOptPath, source, [
140
- ...args, '-o'
141
- ]);
255
+ return await spawnIOTmp(wasmOptPath, source, [...args, "-o"]);
142
256
  } catch (e) {
143
- if (e.toString().includes('BasicBlock requested'))
257
+ if (e.toString().includes("BasicBlock requested"))
144
258
  return wasmOpt(source, args);
145
259
  throw e;
146
260
  }
package/src/cmd/run.js CHANGED
@@ -40,8 +40,8 @@ export async function serve (componentPath, args, opts) {
40
40
  return runComponent(componentPath, args, opts, `
41
41
  import { HTTPServer } from '@bytecodealliance/preview2-shim/http';
42
42
  const server = new HTTPServer(mod.incomingHandler);
43
- ${tryFindPort ? `
44
43
  let port = ${port};
44
+ ${tryFindPort ? `
45
45
  while (true) {
46
46
  try {
47
47
  server.listen(port, ${JSON.stringify(host)});
@@ -52,8 +52,8 @@ export async function serve (componentPath, args, opts) {
52
52
  }
53
53
  port++;
54
54
  }
55
- ` : `server.listen(${port}, ${JSON.stringify(host)})`}
56
- console.error(\`Server listening on \${port}...\`);
55
+ ` : `server.listen(port, ${JSON.stringify(host)})`}
56
+ console.error(\`Server listening on port...\`);
57
57
  `);
58
58
  }
59
59
 
@@ -1,4 +1,5 @@
1
1
  export function types(witPath: any, opts: any): Promise<void>;
2
+ export function guestTypes(witPath: any, opts: any): Promise<void>;
2
3
  /**
3
4
  * @param {string} witPath
4
5
  * @param {{
@@ -6,8 +7,12 @@ export function types(witPath: any, opts: any): Promise<void>;
6
7
  * worldName?: string,
7
8
  * instantiation?: 'async' | 'sync',
8
9
  * tlaCompat?: bool,
10
+ * asyncMode?: string,
11
+ * asyncImports?: string[],
12
+ * asyncExports?: string[],
9
13
  * outDir?: string,
10
14
  * features?: string[] | 'all',
15
+ * guest?: bool,
11
16
  * }} opts
12
17
  * @returns {Promise<{ [filename: string]: Uint8Array }>}
13
18
  */
@@ -16,8 +21,12 @@ export function typesComponent(witPath: string, opts: {
16
21
  worldName?: string;
17
22
  instantiation?: "async" | "sync";
18
23
  tlaCompat?: bool;
24
+ asyncMode?: string;
25
+ asyncImports?: string[];
26
+ asyncExports?: string[];
19
27
  outDir?: string;
20
28
  features?: string[] | "all";
29
+ guest?: bool;
21
30
  }): Promise<{
22
31
  [filename: string]: Uint8Array;
23
32
  }>;
@@ -30,6 +39,9 @@ export function transpile(componentPath: any, opts: any, program: any): Promise<
30
39
  * instantiation?: 'async' | 'sync',
31
40
  * importBindings?: 'js' | 'optimized' | 'hybrid' | 'direct-optimized',
32
41
  * map?: Record<string, string>,
42
+ * asyncMode?: string,
43
+ * asyncImports?: string[],
44
+ * asyncExports?: string[],
33
45
  * validLiftingOptimization?: bool,
34
46
  * tracing?: bool,
35
47
  * nodejsCompat?: bool,
@@ -51,6 +63,9 @@ export function transpileComponent(component: Uint8Array, opts?: {
51
63
  instantiation?: "async" | "sync";
52
64
  importBindings?: "js" | "optimized" | "hybrid" | "direct-optimized";
53
65
  map?: Record<string, string>;
66
+ asyncMode?: string;
67
+ asyncImports?: string[];
68
+ asyncExports?: string[];
54
69
  validLiftingOptimization?: bool;
55
70
  tracing?: bool;
56
71
  nodejsCompat?: bool;
@@ -1 +1 @@
1
- {"version":3,"file":"transpile.d.ts","sourceRoot":"","sources":["transpile.js"],"names":[],"mappings":"AAgBA,8DAGC;AAED;;;;;;;;;;;GAWG;AACH,wCAXW,MAAM,QACN;IACV,IAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAiB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACrC,SAAa,CAAC,EAAE,IAAI,CAAC;IACrB,MAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAY,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;CAC7B,GACS,OAAO,CAAC;IAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;CAAE,CAAC,CA6BvD;AAkBD,sFAwBC;AAkBD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,8CAtBW,UAAU,SACV;IACV,IAAQ,EAAE,MAAM,CAAC;IACjB,aAAiB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACrC,cAAkB,CAAC,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,GAAG,kBAAkB,CAAC;IACxE,GAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,wBAA4B,CAAC,EAAE,IAAI,CAAC;IACpC,OAAW,CAAC,EAAE,IAAI,CAAC;IACnB,YAAgB,CAAC,EAAE,IAAI,CAAC;IACxB,SAAa,CAAC,EAAE,IAAI,CAAC;IACrB,YAAgB,CAAC,EAAE,IAAI,CAAC;IACxB,EAAM,CAAC,EAAE,IAAI,CAAC;IACd,MAAU,CAAC,EAAE,IAAI,CAAC;IAClB,QAAY,CAAC,EAAE,IAAI,CAAC;IACpB,iBAAqB,CAAC,EAAE,IAAI,CAAC;IAC7B,MAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAe,CAAC,EAAE,IAAI,CAAC;IACvB,sBAA0B,CAAC,EAAE,IAAI,CAAC;IAClC,OAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,GACS,OAAO,CAAC;IAAE,KAAK,EAAE;QAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAAC,EAAE,CAAA;CAAE,CAAC,CAoSnI"}
1
+ {"version":3,"file":"transpile.d.ts","sourceRoot":"","sources":["transpile.js"],"names":[],"mappings":"AAgCA,8DAGC;AAED,mEAGC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wCAfW,MAAM,QACN;IACN,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAC5B,KAAK,CAAC,EAAE,IAAI,CAAC;CACd,GACS,OAAO,CAAC;IAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;CAAE,CAAC,CA8CvD;AAkBD,sFA8BC;AAkBD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,8CAzBW,UAAU,SACV;IACN,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACjC,cAAc,CAAC,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,GAAG,kBAAkB,CAAC;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,wBAAwB,CAAC,EAAE,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,EAAE,CAAC,EAAE,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,iBAAiB,CAAC,EAAE,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,sBAAsB,CAAC,EAAE,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB,GACS,OAAO,CAAC;IAAE,KAAK,EAAE;QAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAAC,EAAE,CAAA;CAAE,CAAC,CAgTnI"}
@@ -14,6 +14,22 @@ import { platform } from 'node:process';
14
14
 
15
15
  const isWindows = platform === 'win32';
16
16
 
17
+ const ASYNC_WASI_IMPORTS = [
18
+ "wasi:io/poll#poll",
19
+ "wasi:io/poll#[method]pollable.block",
20
+ "wasi:io/streams#[method]input-stream.blocking-read",
21
+ "wasi:io/streams#[method]input-stream.blocking-skip",
22
+ "wasi:io/streams#[method]output-stream.blocking-flush",
23
+ "wasi:io/streams#[method]output-stream.blocking-write-and-flush",
24
+ "wasi:io/streams#[method]output-stream.blocking-write-zeroes-and-flush",
25
+ "wasi:io/streams#[method]output-stream.blocking-splice",
26
+ ];
27
+
28
+ const ASYNC_WASI_EXPORTS = [
29
+ "wasi:cli/run#run",
30
+ "wasi:http/incoming-handler#handle",
31
+ ];
32
+
17
33
  export async function types (witPath, opts) {
18
34
  const files = await typesComponent(witPath, opts);
19
35
  await writeFiles(files, opts.quiet ? false : 'Generated Type Files');
@@ -31,6 +47,9 @@ export async function guestTypes (witPath, opts) {
31
47
  * worldName?: string,
32
48
  * instantiation?: 'async' | 'sync',
33
49
  * tlaCompat?: bool,
50
+ * asyncMode?: string,
51
+ * asyncImports?: string[],
52
+ * asyncExports?: string[],
34
53
  * outDir?: string,
35
54
  * features?: string[] | 'all',
36
55
  * guest?: bool,
@@ -57,6 +76,21 @@ export async function typesComponent (witPath, opts) {
57
76
  features = { tag: 'list', val: opts.feature };
58
77
  }
59
78
 
79
+ if (opts.asyncWasiImports)
80
+ opts.asyncImports = ASYNC_WASI_IMPORTS.concat(opts.asyncImports || []);
81
+ if (opts.asyncWasiExports)
82
+ opts.asyncExports = ASYNC_WASI_EXPORTS.concat(opts.asyncExports || []);
83
+
84
+ const asyncMode = !opts.asyncMode || opts.asyncMode === 'sync' ?
85
+ null :
86
+ {
87
+ tag: opts.asyncMode,
88
+ val: {
89
+ imports: opts.asyncImports || [],
90
+ exports: opts.asyncExports || [],
91
+ },
92
+ };
93
+
60
94
  return Object.fromEntries(generateTypes(name, {
61
95
  wit: { tag: 'path', val: (isWindows ? '//?/' : '') + resolve(witPath) },
62
96
  instantiation,
@@ -64,6 +98,7 @@ export async function typesComponent (witPath, opts) {
64
98
  world: opts.worldName,
65
99
  features,
66
100
  guest: opts.guest ?? false,
101
+ asyncMode,
67
102
  }).map(([name, file]) => [`${outDir}${name}`, file]));
68
103
  }
69
104
 
@@ -105,6 +140,12 @@ export async function transpile (componentPath, opts, program) {
105
140
  opts.name = basename(componentPath.slice(0, -extname(componentPath).length || Infinity));
106
141
  if (opts.map)
107
142
  opts.map = Object.fromEntries(opts.map.map(mapping => mapping.split('=')));
143
+
144
+ if (opts.asyncWasiImports)
145
+ opts.asyncImports = ASYNC_WASI_IMPORTS.concat(opts.asyncImports || []);
146
+ if (opts.asyncWasiExports)
147
+ opts.asyncExports = ASYNC_WASI_EXPORTS.concat(opts.asyncExports || []);
148
+
108
149
  const { files } = await transpileComponent(component, opts);
109
150
  await writeFiles(files, opts.quiet ? false : 'Transpiled JS Component Files');
110
151
  }
@@ -133,6 +174,9 @@ async function wasm2Js (source) {
133
174
  * instantiation?: 'async' | 'sync',
134
175
  * importBindings?: 'js' | 'optimized' | 'hybrid' | 'direct-optimized',
135
176
  * map?: Record<string, string>,
177
+ * asyncMode?: string,
178
+ * asyncImports?: string[],
179
+ * asyncExports?: string[],
136
180
  * validLiftingOptimization?: bool,
137
181
  * tracing?: bool,
138
182
  * nodejsCompat?: bool,
@@ -155,6 +199,7 @@ export async function transpileComponent (component, opts = {}) {
155
199
 
156
200
  let spinner;
157
201
  const showSpinner = getShowSpinner();
202
+
158
203
  if (opts.optimize) {
159
204
  if (showSpinner) setShowSpinner(true);
160
205
  ({ component } = await optimizeComponent(component, opts));
@@ -183,10 +228,21 @@ export async function transpileComponent (component, opts = {}) {
183
228
  instantiation = { tag: 'async' };
184
229
  }
185
230
 
231
+ const asyncMode = !opts.asyncMode || opts.asyncMode === 'sync' ?
232
+ null :
233
+ {
234
+ tag: opts.asyncMode,
235
+ val: {
236
+ imports: opts.asyncImports || [],
237
+ exports: opts.asyncExports || [],
238
+ },
239
+ };
240
+
186
241
  let { files, imports, exports } = generate(component, {
187
242
  name: opts.name ?? 'component',
188
243
  map: Object.entries(opts.map ?? {}),
189
244
  instantiation,
245
+ asyncMode,
190
246
  importBindings: opts.importBindings ? { tag: opts.importBindings } : null,
191
247
  validLiftingOptimization: opts.validLiftingOptimization ?? false,
192
248
  tracing: opts.tracing ?? false,
package/src/jco.js CHANGED
@@ -52,6 +52,11 @@ program.command('transpile')
52
52
  .option('--no-typescript', 'do not output TypeScript .d.ts types')
53
53
  .option('--valid-lifting-optimization', 'optimize component binary validations assuming all lifted values are valid')
54
54
  .addOption(new Option('--import-bindings [mode]', 'bindings mode for imports').choices(['js', 'optimized', 'hybrid', 'direct-optimized']).preset('js'))
55
+ .addOption(new Option('--async-mode [mode]', 'EXPERIMENTAL: use async imports and exports').choices(['sync', 'jspi']).preset('sync'))
56
+ .option('--async-wasi-imports', 'EXPERIMENTAL: async component imports from WASI interfaces')
57
+ .option('--async-wasi-exports', 'EXPERIMENTAL: async component exports from WASI interfaces')
58
+ .option('--async-imports <imports...>', 'EXPERIMENTAL: async component imports (examples: "wasi:io/poll@0.2.0#poll", "wasi:io/poll#[method]pollable.block")')
59
+ .option('--async-exports <exports...>', 'EXPERIMENTAL: async component exports (examples: "wasi:cli/run@#run", "handle")')
55
60
  .option('--tracing', 'emit `tracing` calls on function entry/exit')
56
61
  .option('-b, --base64-cutoff <bytes>', 'set the byte size under which core Wasm binaries will be inlined as base64', myParseInt)
57
62
  .option('--tla-compat', 'enables compatibility for JS environments without top-level await support via an async $init promise export')
@@ -76,6 +81,11 @@ program.command('types')
76
81
  .requiredOption('-o, --out-dir <out-dir>', 'output directory')
77
82
  .option('--tla-compat', 'generates types for the TLA compat output with an async $init promise export')
78
83
  .addOption(new Option('-I, --instantiation [mode]', 'type output for custom module instantiation').choices(['async', 'sync']).preset('async'))
84
+ .addOption(new Option('--async-mode [mode]', 'EXPERIMENTAL: use async imports and exports').choices(['sync', 'jspi']).preset('sync'))
85
+ .option('--async-wasi-imports', 'EXPERIMENTAL: async component imports from WASI interfaces')
86
+ .option('--async-wasi-exports', 'EXPERIMENTAL: async component exports from WASI interfaces')
87
+ .option('--async-imports <imports...>', 'EXPERIMENTAL: async component imports (examples: "wasi:io/poll@0.2.0#poll", "wasi:io/poll#[method]pollable.block")')
88
+ .option('--async-exports <exports...>', 'EXPERIMENTAL: async component exports (examples: "wasi:cli/run@#run", "handle")')
79
89
  .option('-q, --quiet', 'disable output summary')
80
90
  .option('--feature <feature>', 'enable one specific WIT feature (repeatable)', collectOptions, [])
81
91
  .option('--all-features', 'enable all features')
@@ -144,6 +154,7 @@ program.command('opt')
144
154
  .usage('<component-file> -o <output-file>')
145
155
  .argument('<component-file>', 'Wasm component binary filepath')
146
156
  .requiredOption('-o, --output <output-file>', 'optimized component output filepath')
157
+ .option('--asyncify', 'runs Asyncify pass in wasm-opt')
147
158
  .option('-q, --quiet')
148
159
  .option('--', 'custom wasm-opt arguments (defaults to best size optimization)')
149
160
  .action(asyncAction(opt));