@nimbus-sh/worker 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/dist/esbuild-cli-artifact.generated.d.ts +1 -1
  2. package/dist/esbuild-cli-artifact.generated.js +4 -4
  3. package/dist/esbuild-wasm-bundle.generated.d.ts +11 -10
  4. package/dist/esbuild-wasm-bundle.generated.d.ts.map +1 -1
  5. package/dist/esbuild-wasm-bundle.generated.js +11 -10
  6. package/dist/facets/build-facet.d.ts +53 -0
  7. package/dist/facets/build-facet.d.ts.map +1 -0
  8. package/dist/facets/build-facet.js +346 -0
  9. package/dist/facets/cirrus-real.d.ts +3 -0
  10. package/dist/facets/cirrus-real.d.ts.map +1 -1
  11. package/dist/facets/cirrus-real.js +1 -2
  12. package/dist/facets/compose.d.ts +1 -0
  13. package/dist/facets/compose.d.ts.map +1 -1
  14. package/dist/facets/compose.js +3 -0
  15. package/dist/facets/data-plan.d.ts +14 -30
  16. package/dist/facets/data-plan.d.ts.map +1 -1
  17. package/dist/facets/data-plan.js +153 -13
  18. package/dist/facets/dev-stylesheet.d.ts +27 -0
  19. package/dist/facets/dev-stylesheet.d.ts.map +1 -0
  20. package/dist/facets/dev-stylesheet.js +65 -0
  21. package/dist/facets/esbuild-transform.d.ts +23 -18
  22. package/dist/facets/esbuild-transform.d.ts.map +1 -1
  23. package/dist/facets/esbuild-transform.js +63 -109
  24. package/dist/facets/manager.d.ts +49 -9
  25. package/dist/facets/manager.d.ts.map +1 -1
  26. package/dist/facets/manager.js +238 -69
  27. package/dist/facets/oxc-transform.d.ts +29 -0
  28. package/dist/facets/oxc-transform.d.ts.map +1 -0
  29. package/dist/facets/oxc-transform.js +198 -0
  30. package/dist/facets/prebundle-pool.d.ts +23 -0
  31. package/dist/facets/prebundle-pool.d.ts.map +1 -0
  32. package/dist/facets/prebundle-pool.js +55 -0
  33. package/dist/facets/process.d.ts +13 -6
  34. package/dist/facets/process.d.ts.map +1 -1
  35. package/dist/facets/process.js +37 -31
  36. package/dist/facets/transform-store.d.ts +4 -4
  37. package/dist/facets/transform-store.js +4 -4
  38. package/dist/facets/vite-dev-server.d.ts +89 -12
  39. package/dist/facets/vite-dev-server.d.ts.map +1 -1
  40. package/dist/facets/vite-dev-server.js +66 -43
  41. package/dist/hosted/commands.d.ts.map +1 -1
  42. package/dist/hosted/commands.js +20 -4
  43. package/dist/hosted/runtime.d.ts.map +1 -1
  44. package/dist/hosted/runtime.js +7 -2
  45. package/dist/hosted/services.d.ts +3 -3
  46. package/dist/hosted/services.d.ts.map +1 -1
  47. package/dist/hosted/services.js +102 -95
  48. package/dist/hosted/session.d.ts +1 -0
  49. package/dist/hosted/session.d.ts.map +1 -1
  50. package/dist/loaders/generated-workers.d.ts +3 -0
  51. package/dist/loaders/generated-workers.d.ts.map +1 -1
  52. package/dist/loaders/generated-workers.js +3 -0
  53. package/dist/napi-wasm-artifacts.generated.js +2 -2
  54. package/dist/node-shims-artifact.generated.d.ts +14 -1
  55. package/dist/node-shims-artifact.generated.d.ts.map +1 -1
  56. package/dist/node-shims-artifact.generated.js +23 -10
  57. package/dist/npm/bin-links.d.ts +7 -4
  58. package/dist/npm/bin-links.d.ts.map +1 -1
  59. package/dist/npm/bin-links.js +14 -55
  60. package/dist/npm/cache.d.ts +6 -0
  61. package/dist/npm/cache.d.ts.map +1 -1
  62. package/dist/npm/cache.js +26 -4
  63. package/dist/npm/installer.d.ts +1 -1
  64. package/dist/npm/installer.d.ts.map +1 -1
  65. package/dist/npm/installer.js +26 -52
  66. package/dist/npm/pre-bundle-facet.d.ts +15 -117
  67. package/dist/npm/pre-bundle-facet.d.ts.map +1 -1
  68. package/dist/npm/pre-bundle-facet.js +13 -511
  69. package/dist/oxc-facet-artifact.generated.d.ts +18 -0
  70. package/dist/oxc-facet-artifact.generated.d.ts.map +1 -0
  71. package/dist/oxc-facet-artifact.generated.js +17 -0
  72. package/dist/oxc-wasm-artifact.generated.d.ts +20 -0
  73. package/dist/oxc-wasm-artifact.generated.d.ts.map +1 -0
  74. package/dist/oxc-wasm-artifact.generated.js +19 -0
  75. package/dist/rolldown-facet-artifact.generated.d.ts +18 -0
  76. package/dist/rolldown-facet-artifact.generated.d.ts.map +1 -0
  77. package/dist/rolldown-facet-artifact.generated.js +17 -0
  78. package/dist/router/remote-api.d.ts.map +1 -1
  79. package/dist/router/remote-api.js +9 -0
  80. package/dist/runtime/bash-repl.d.ts.map +1 -1
  81. package/dist/runtime/bash-repl.js +1 -4
  82. package/dist/runtime/cpython-resident.d.ts.map +1 -1
  83. package/dist/runtime/cpython-resident.js +1 -0
  84. package/dist/runtime/esbuild-wasm-bytes.d.ts +25 -23
  85. package/dist/runtime/esbuild-wasm-bytes.d.ts.map +1 -1
  86. package/dist/runtime/esbuild-wasm-bytes.js +30 -25
  87. package/dist/runtime/facet-loader-host.d.ts +5 -4
  88. package/dist/runtime/facet-loader-host.d.ts.map +1 -1
  89. package/dist/runtime/facet-loader-host.js +5 -4
  90. package/dist/runtime/node-runner.d.ts +2 -0
  91. package/dist/runtime/node-runner.d.ts.map +1 -1
  92. package/dist/runtime/node-runner.js +1 -0
  93. package/dist/runtime/node-shims-artifact.d.ts +8 -1
  94. package/dist/runtime/node-shims-artifact.d.ts.map +1 -1
  95. package/dist/runtime/node-shims-artifact.js +32 -4
  96. package/dist/runtime/node-shims.d.ts.map +1 -1
  97. package/dist/runtime/node-shims.js +145 -109
  98. package/dist/runtime/opencode-facet-runner.d.ts.map +1 -1
  99. package/dist/runtime/opencode-facet-runner.js +6 -2
  100. package/dist/runtime/oxc-wasm-bytes.d.ts +19 -0
  101. package/dist/runtime/oxc-wasm-bytes.d.ts.map +1 -0
  102. package/dist/runtime/oxc-wasm-bytes.js +39 -0
  103. package/dist/runtime/package-manager.js +1 -1
  104. package/dist/runtime/process-logs-api.d.ts.map +1 -1
  105. package/dist/runtime/process-logs-api.js +2 -0
  106. package/dist/runtime/project-fs.d.ts +8 -1
  107. package/dist/runtime/project-fs.d.ts.map +1 -1
  108. package/dist/runtime/project-fs.js +15 -1
  109. package/dist/runtime/python-repl.d.ts +40 -1
  110. package/dist/runtime/python-repl.d.ts.map +1 -1
  111. package/dist/runtime/python-repl.js +19 -11
  112. package/dist/runtime/ruby-repl.d.ts +38 -0
  113. package/dist/runtime/ruby-repl.d.ts.map +1 -1
  114. package/dist/runtime/ruby-repl.js +24 -5
  115. package/dist/runtime/ruby-resident.d.ts.map +1 -1
  116. package/dist/runtime/ruby-resident.js +2 -0
  117. package/dist/session/nimbus-session.d.ts +13 -8
  118. package/dist/session/nimbus-session.d.ts.map +1 -1
  119. package/dist/session/nimbus-session.js +11 -5
  120. package/dist/session/programmatic.d.ts +25 -30
  121. package/dist/session/programmatic.d.ts.map +1 -1
  122. package/dist/session/programmatic.js +65 -93
  123. package/dist/session/routes.d.ts.map +1 -1
  124. package/dist/session/routes.js +23 -7
  125. package/dist/session/rpc.d.ts +16 -6
  126. package/dist/session/rpc.d.ts.map +1 -1
  127. package/dist/session/rpc.js +53 -13
  128. package/dist/session/start-real-vite.d.ts +24 -9
  129. package/dist/session/start-real-vite.d.ts.map +1 -1
  130. package/dist/session/start-real-vite.js +40 -4
  131. package/dist/session/supervisor-op.d.ts +14 -0
  132. package/dist/session/supervisor-op.d.ts.map +1 -1
  133. package/dist/session/supervisor-op.js +49 -0
  134. package/dist/session/supervisor-rpc.d.ts +11 -0
  135. package/dist/session/supervisor-rpc.d.ts.map +1 -1
  136. package/dist/session/supervisor-rpc.js +18 -0
  137. package/dist/session/vite-command.d.ts.map +1 -1
  138. package/dist/session/vite-command.js +23 -19
  139. package/dist/session/ws-relay.d.ts.map +1 -1
  140. package/dist/session/ws-relay.js +13 -1
  141. package/dist/shell/npm-bin-entrypoints.d.ts +3 -1
  142. package/dist/shell/npm-bin-entrypoints.d.ts.map +1 -1
  143. package/dist/shell/npm-bin-entrypoints.js +53 -25
  144. package/dist/vfs/facet-resident-store.d.ts.map +1 -1
  145. package/dist/vfs/facet-resident-store.js +11 -5
  146. package/dist/wrangler/nimbus-wrangler.d.ts +8 -0
  147. package/dist/wrangler/nimbus-wrangler.d.ts.map +1 -1
  148. package/dist/wrangler/nimbus-wrangler.js +54 -57
  149. package/package.json +7 -5
  150. package/public/_assets/esbuild-0.24.2.wasm +0 -0
  151. package/public/_assets/napi-wasm/loader/{99efae93dd1dcc2b → ab7e57fe9e8750c2}/napi-wasm-loader.mjs +4 -4
  152. package/public/_assets/napi-wasm/loader/{99efae93dd1dcc2b → ab7e57fe9e8750c2}/provenance.json +2 -2
  153. package/public/_assets/oxc/nimbus-oxc-1aa8c4c7057c6c28.provenance.json +72 -0
  154. package/public/_assets/oxc/nimbus-oxc-1aa8c4c7057c6c28.wasm +0 -0
  155. package/public/_assets/runtime/esbuild-cli-f13e6a112b03ac07.js +1143 -0
  156. package/public/_assets/runtime/js-interpreter-3c37c48024f0f651.js +23 -0
  157. package/public/_assets/runtime/js-interpreter-ops-2f4dfec6798e70fc.js +290 -0
  158. package/public/_assets/runtime/js-interpreter-primordials-e620a61b7e9800fa.js +1 -0
  159. package/public/_assets/runtime/{node-shims-f92f16c7d7c532e9.js → node-shims-e3adc4576da31e6e.js} +651 -103
  160. package/public/_assets/runtime/{esbuild-cli-b7aecffd4c6a36fe.js → oxc-facet-fd86fefcdf16bf9c.js} +217 -1047
  161. package/public/_assets/runtime/{resident-store-92386265e8b306dc.js → resident-store-009f39539ac989fc.js} +11 -5
  162. package/public/_assets/runtime/rolldown-facet-09983434e2dbe93e.js +15165 -0
  163. package/public/_assets/runtime/{vfs-write-ledger-d33ee0034446c3e0.js → vfs-write-ledger-9e9594591425a9b8.js} +4 -1
  164. package/scripts/acorn-primordials.mjs +637 -0
  165. package/scripts/bundle-esbuild-wasm.mjs +42 -50
  166. package/scripts/bundle-facet-workers.mjs +247 -18
  167. package/scripts/bundle-node-shims.mjs +29 -2
  168. package/scripts/bundle-oxc-wasm.mjs +105 -0
  169. package/scripts/interpreter-bundle.mjs +105 -0
  170. package/scripts/napi-wasm/build.mjs +4 -1
  171. package/scripts/napi-wasm/loader/napi-wasm-loader.ts +38 -3
  172. package/scripts/oxc-wasm/Cargo.lock +1246 -0
  173. package/scripts/oxc-wasm/Cargo.toml +30 -0
  174. package/scripts/oxc-wasm/build.mjs +249 -0
  175. package/scripts/oxc-wasm/examples/memory.rs +51 -0
  176. package/scripts/oxc-wasm/examples/transform.rs +20 -0
  177. package/scripts/oxc-wasm/inputs.mjs +32 -0
  178. package/scripts/oxc-wasm/rust-toolchain.toml +5 -0
  179. package/scripts/oxc-wasm/src/abi.rs +136 -0
  180. package/scripts/oxc-wasm/src/diagnostics.rs +49 -0
  181. package/scripts/oxc-wasm/src/helpers.rs +139 -0
  182. package/scripts/oxc-wasm/src/lib.rs +247 -0
  183. package/scripts/oxc-wasm/src/module.rs +978 -0
  184. package/scripts/oxc-wasm/src/names.rs +175 -0
  185. package/scripts/oxc-wasm/src/options.rs +154 -0
  186. package/scripts/parser-audit.mjs +492 -0
  187. package/scripts/rolldown-facet/entry.mjs +21 -0
  188. package/scripts/rolldown-facet/shims.mjs +64 -0
  189. package/dist/facets/esbuild-bundle-pool.d.ts +0 -43
  190. package/dist/facets/esbuild-bundle-pool.d.ts.map +0 -1
  191. package/dist/facets/esbuild-bundle-pool.js +0 -80
  192. package/dist/loaders/pre-bundle-preamble.d.ts +0 -44
  193. package/dist/loaders/pre-bundle-preamble.d.ts.map +0 -1
  194. package/dist/loaders/pre-bundle-preamble.js +0 -116
  195. package/dist/runtime/host-wasm.d.ts +0 -8
  196. package/dist/runtime/host-wasm.d.ts.map +0 -1
  197. package/dist/runtime/host-wasm.js +0 -33
  198. /package/public/_assets/napi-wasm/loader/{99efae93dd1dcc2b → ab7e57fe9e8750c2}/wasi-trampoline.wasm +0 -0
@@ -0,0 +1,30 @@
1
+ # Nimbus's Oxc transform, built as wasm by build.mjs (see src/lib.rs).
2
+ [package]
3
+ name = "nimbus_oxc"
4
+ version = "0.1.0"
5
+ edition = "2024"
6
+ publish = false
7
+ license = "MIT"
8
+
9
+ # Its own workspace: build.mjs copies the crate into a work directory.
10
+ [workspace]
11
+
12
+ [lib]
13
+ # cdylib for the wasm; rlib for the examples, which run the same transform natively.
14
+ crate-type = ["cdylib", "rlib"]
15
+ doctest = false
16
+
17
+ [dependencies]
18
+ oxc = { version = "=0.152.0", default-features = false, features = ["transformer", "semantic", "codegen", "ast_visit"] }
19
+ oxc_ecmascript = "=0.152.0"
20
+ oxc_traverse = "=0.152.0"
21
+ memchr = "2"
22
+ rustc-hash = "2"
23
+
24
+ [profile.release]
25
+ codegen-units = 1
26
+ lto = "fat"
27
+ opt-level = 3
28
+ panic = "abort"
29
+ debug = false
30
+ strip = "symbols"
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * build.mjs — build Nimbus's Oxc transform (this crate) as wasm, reproducibly.
4
+ *
5
+ * bun packages/worker/scripts/oxc-wasm/build.mjs --work <dir> --out <dir>
6
+ *
7
+ * Cargo may run in its own bounded cgroup (keep this Bun orchestrator small):
8
+ * NIMBUS_CARGO_RUNNER=/mnt/scratch/nimbus/run-bounded NIMBUS_CARGO_JOBS=4 \
9
+ * NIMBUS_TEST_MEMORY_MAX=2G NIMBUS_TEST_TIMEOUT=3600 run-bounded bun .../build.mjs ...
10
+ *
11
+ * Every input is pinned and every check fails loudly:
12
+ * 1. The crate (Cargo.toml, Cargo.lock, rust-toolchain.toml, src/) is copied
13
+ * into <work>/nimbus-oxc, so the build path is the work directory's, and
14
+ * built with `cargo --locked` (crates.io checksums in Cargo.lock) by the
15
+ * exact rustc rust-toolchain.toml names, for wasm32-unknown-unknown.
16
+ * 2. binaryen's release archive is checked against its pinned SHA-256, and
17
+ * its wasm-opt drops the wasm-bindgen exports oxc-browserslist's js-sys
18
+ * dependency leaves (no code path here calls into JavaScript), replaces
19
+ * the one import they keep, a diverging `throw`, with a trap, and
20
+ * optimizes (-O3).
21
+ * 3. The result must import nothing, export exactly the ABI (src/abi.rs),
22
+ * and define an unshared memory of at most OXC_MAX_INITIAL_PAGES.
23
+ * Output (--out): nimbus-oxc.wasm and provenance.json (inputs, toolchain,
24
+ * flags, digests). Stage it with
25
+ * NIMBUS_OXC_WASM_ARTIFACTS=<out> node packages/worker/scripts/bundle-oxc-wasm.mjs
26
+ */
27
+
28
+ import { createHash } from 'node:crypto';
29
+ import { spawnSync } from 'node:child_process';
30
+ import { promises as fs, existsSync } from 'node:fs';
31
+ import path from 'node:path';
32
+ import { fileURLToPath } from 'node:url';
33
+ import { changedInputs, oxcWasmInputs } from './inputs.mjs';
34
+
35
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
36
+ const TARGET = 'wasm32-unknown-unknown';
37
+ /**
38
+ * Bytes of linear memory the wasm shadow stack takes. Call frames live on the
39
+ * host's native stack; this one holds what they spill, and at 4 MiB it is not
40
+ * what limits nesting depth under workerd's or Node's default stack.
41
+ */
42
+ const WASM_STACK_BYTES = 4 * 1024 * 1024;
43
+ /**
44
+ * The ceiling on the module's floor, the stack plus static data, in 64 KiB
45
+ * pages: 5 MiB. Napi-rs's wasm builds start at 981 pages (a 64 MB stack).
46
+ */
47
+ const OXC_MAX_INITIAL_PAGES = 80;
48
+ const RUSTFLAGS = ['-Ctarget-feature=+simd128', `-Clink-arg=-zstack-size=${WASM_STACK_BYTES}`];
49
+ const BINARYEN = {
50
+ version: 'version_133',
51
+ url: 'https://github.com/WebAssembly/binaryen/releases/download/version_133/binaryen-version_133-x86_64-linux.tar.gz',
52
+ sha256: '2dc9c7813f5375db93d96ead4b78222fcc3e2677bbb832297af4797782a37489',
53
+ };
54
+ const WASM_OPT_ARGS = [
55
+ // The features rustc 1.98 enables for wasm32-unknown-unknown, plus simd128.
56
+ '--enable-simd', '--enable-bulk-memory', '--enable-nontrapping-float-to-int', '--enable-sign-ext',
57
+ '--enable-mutable-globals', '--enable-reference-types', '--enable-multivalue',
58
+ '--remove-exports', '--pass-arg=remove-exports@__*',
59
+ '--remove-imports',
60
+ '-O3',
61
+ ];
62
+ const EXPORTS = ['memory', 'nimbus_oxc_alloc', 'nimbus_oxc_realloc', 'nimbus_oxc_release', 'nimbus_oxc_transform'];
63
+ const CRATE_FILES = ['Cargo.toml', 'Cargo.lock', 'rust-toolchain.toml', 'src'];
64
+
65
+ function arg(name) {
66
+ const i = process.argv.indexOf(name);
67
+ if (i < 0 || !process.argv[i + 1]) throw new Error(`oxc-wasm: ${name} <value> is required`);
68
+ return path.resolve(process.argv[i + 1]);
69
+ }
70
+ const WORK = arg('--work');
71
+ const OUT = arg('--out');
72
+
73
+ const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex');
74
+
75
+ function run(cmd, args, opts = {}) {
76
+ const r = spawnSync(cmd, args, { stdio: opts.capture ? 'pipe' : 'inherit', encoding: 'utf8', ...opts });
77
+ if (r.status !== 0) throw new Error(`oxc-wasm: ${cmd} ${args.join(' ')} exited ${r.status}${r.stderr ? `\n${r.stderr}` : ''}`);
78
+ return r.stdout ?? '';
79
+ }
80
+
81
+ async function copyCrate(into) {
82
+ await fs.rm(into, { recursive: true, force: true });
83
+ await fs.mkdir(into, { recursive: true });
84
+ for (const entry of CRATE_FILES) await fs.cp(path.join(HERE, entry), path.join(into, entry), { recursive: true });
85
+ }
86
+
87
+ function toolchain(crateDir, text) {
88
+ const pinned = text.match(/^channel = "(.+)"$/m)?.[1];
89
+ if (!pinned) throw new Error('oxc-wasm: rust-toolchain.toml names no channel');
90
+ // rustup, when present, owns the pinned toolchain; otherwise the rustc on PATH must be it.
91
+ const which = spawnSync('rustup', ['which', '--toolchain', pinned, 'rustc'], { encoding: 'utf8', cwd: crateDir });
92
+ const binDir = which.status === 0 ? path.dirname(which.stdout.trim()) : null;
93
+ const env = { ...process.env, PATH: binDir ? `${binDir}:${process.env.PATH}` : process.env.PATH, RUSTUP_TOOLCHAIN: pinned };
94
+ const rustc = run('rustc', ['-vV'], { capture: true, env });
95
+ const release = rustc.match(/^release: (.+)$/m)?.[1];
96
+ if (release !== pinned) {
97
+ throw new Error(`oxc-wasm: needs rustc ${pinned} with ${TARGET} (rustup toolchain install ${pinned} --target ${TARGET}); found ${release}`);
98
+ }
99
+ const sysroot = run('rustc', ['--print', 'sysroot'], { capture: true, env }).trim();
100
+ if (!existsSync(path.join(sysroot, 'lib', 'rustlib', TARGET))) {
101
+ throw new Error(`oxc-wasm: rustc ${pinned} has no ${TARGET} target (rustup target add --toolchain ${pinned} ${TARGET})`);
102
+ }
103
+ // The compiler itself, not a rustup proxy: cargo is told to use exactly this.
104
+ const compiler = path.join(sysroot, 'bin', 'rustc');
105
+ if (run(compiler, ['-vV'], { capture: true }) !== rustc) throw new Error(`oxc-wasm: ${compiler} is not the rustc ${pinned} checked`);
106
+ return { rustc: rustc.trim(), compiler, env };
107
+ }
108
+
109
+ async function wasmOpt() {
110
+ const archives = path.join(WORK, '_archives');
111
+ await fs.mkdir(archives, { recursive: true });
112
+ const archive = path.join(archives, path.basename(BINARYEN.url));
113
+ if (!existsSync(archive)) {
114
+ const res = await fetch(BINARYEN.url, { redirect: 'follow' });
115
+ if (!res.ok) throw new Error(`oxc-wasm: GET ${BINARYEN.url} -> ${res.status}`);
116
+ await fs.writeFile(archive, new Uint8Array(await res.arrayBuffer()));
117
+ }
118
+ const digest = sha256(await fs.readFile(archive));
119
+ if (digest !== BINARYEN.sha256) throw new Error(`oxc-wasm: ${BINARYEN.url} sha256 ${digest}, pinned ${BINARYEN.sha256}`);
120
+ const dir = path.join(WORK, 'binaryen');
121
+ await fs.rm(dir, { recursive: true, force: true });
122
+ await fs.mkdir(dir, { recursive: true });
123
+ run('tar', ['xzf', archive, '-C', dir]);
124
+ const bin = path.join(dir, `binaryen-${BINARYEN.version}`, 'bin', 'wasm-opt');
125
+ const version = run(bin, ['--version'], { capture: true }).trim();
126
+ if (!version.includes(BINARYEN.version)) throw new Error(`oxc-wasm: ${bin} reports ${version}`);
127
+ return { bin, version };
128
+ }
129
+
130
+ /** The import, export and memory sections of a wasm binary. */
131
+ function wasmFacts(bytes) {
132
+ let at = 8;
133
+ const u = () => {
134
+ let result = 0, shift = 0, byte;
135
+ do { byte = bytes[at++]; result |= (byte & 0x7f) << shift; shift += 7; } while (byte & 0x80);
136
+ return result >>> 0;
137
+ };
138
+ const str = () => { const n = u(); const s = new TextDecoder().decode(bytes.subarray(at, at + n)); at += n; return s; };
139
+ const facts = { imports: [], exports: [], memories: [] };
140
+ while (at < bytes.length) {
141
+ const id = bytes[at++];
142
+ const size = u();
143
+ const end = at + size;
144
+ if (id === 2) {
145
+ for (let i = u(); i > 0; i--) {
146
+ facts.imports.push(`${str()}.${str()}`);
147
+ const kind = bytes[at++];
148
+ if (kind === 0) u();
149
+ else if (kind === 1) { at++; const f = bytes[at++]; u(); if (f & 1) u(); }
150
+ else if (kind === 2) { const f = bytes[at++]; u(); if (f & 1) u(); }
151
+ else if (kind === 3) at += 2;
152
+ }
153
+ } else if (id === 5) {
154
+ for (let i = u(); i > 0; i--) {
155
+ const flags = bytes[at++];
156
+ const memory = { shared: (flags & 2) !== 0, initial: u() };
157
+ if (flags & 1) memory.maximum = u();
158
+ facts.memories.push(memory);
159
+ }
160
+ } else if (id === 7) {
161
+ for (let i = u(); i > 0; i--) { facts.exports.push(str()); at++; u(); }
162
+ }
163
+ at = end;
164
+ }
165
+ return facts;
166
+ }
167
+
168
+ // ── Main ────────────────────────────────────────────────────────────────
169
+ await fs.mkdir(WORK, { recursive: true });
170
+ await fs.mkdir(OUT, { recursive: true });
171
+ const crateDir = path.join(WORK, 'nimbus-oxc');
172
+ const inputs = await oxcWasmInputs();
173
+ await copyCrate(crateDir);
174
+ const toolchainText = await fs.readFile(path.join(crateDir, 'rust-toolchain.toml'), 'utf8');
175
+ const rust = toolchain(crateDir, toolchainText);
176
+ const optimizer = await wasmOpt();
177
+
178
+ const cargoHome = process.env.CARGO_HOME ?? path.join(process.env.HOME ?? '/', '.cargo');
179
+ // Cargo and rustc read settings from the environment that Cargo.toml and the
180
+ // flags below pin (RUSTC, RUSTDOC, CARGO_PROFILE_RELEASE_*, CARGO_BUILD_*,
181
+ // RUSTFLAGS, RUSTC_WRAPPER, …): the build sees none of them but where its
182
+ // caches live, and the compiler is the one toolchain() checked.
183
+ const KEPT = new Set(['CARGO_HOME', 'RUSTUP_HOME', 'RUSTUP_TOOLCHAIN']);
184
+ const OVERRIDES = /^(CARGO|RUST|RUSTC|RUSTDOC)(_|$)|^RUSTFLAGS$|^RUSTDOCFLAGS$/;
185
+ const ambient = Object.fromEntries(Object.entries(rust.env).filter(([name]) => KEPT.has(name) || !OVERRIDES.test(name)));
186
+ for (const name of ['RUSTC', 'RUSTDOC', 'RUSTFLAGS', 'RUSTC_WRAPPER', 'CARGO_PROFILE_RELEASE_OPT_LEVEL', 'CARGO_BUILD_TARGET']) {
187
+ if (name in ambient) throw new Error(`oxc-wasm: ${name} survived the environment filter`);
188
+ }
189
+ const env = {
190
+ ...ambient,
191
+ RUSTC: rust.compiler,
192
+ CARGO_TARGET_DIR: path.join(WORK, 'target'),
193
+ CARGO_ENCODED_RUSTFLAGS: RUSTFLAGS.join('\x1f'),
194
+ // Machine paths out of panic messages without entering cargo's metadata
195
+ // hash (napi-wasm/rustc-remap.sh explains why not RUSTFLAGS).
196
+ RUSTC_WRAPPER: path.join(HERE, '..', 'napi-wasm', 'rustc-remap.sh'),
197
+ NIMBUS_REMAP_WORK: WORK,
198
+ NIMBUS_REMAP_CARGO_HOME: cargoHome,
199
+ SOURCE_DATE_EPOCH: '0',
200
+ };
201
+ const jobs = Number(process.env.NIMBUS_CARGO_JOBS ?? '4');
202
+ if (!Number.isInteger(jobs) || jobs < 1 || jobs > 12) throw new Error('oxc-wasm: NIMBUS_CARGO_JOBS must be an integer from 1 to 12');
203
+ const cargoArgs = ['build', '--release', '--locked', '--lib', '--target', TARGET, '-j', String(jobs)];
204
+ const runner = process.env.NIMBUS_CARGO_RUNNER;
205
+ if (runner) run(runner, ['cargo', ...cargoArgs], { cwd: crateDir, env });
206
+ else run('cargo', cargoArgs, { cwd: crateDir, env });
207
+
208
+ const linked = path.join(WORK, 'target', TARGET, 'release', 'nimbus_oxc.wasm');
209
+ const output = path.join(OUT, 'nimbus-oxc.wasm');
210
+ run(optimizer.bin, [linked, ...WASM_OPT_ARGS, '-o', output]);
211
+ const wasm = new Uint8Array(await fs.readFile(output));
212
+ const facts = wasmFacts(wasm);
213
+ if (facts.imports.length !== 0) throw new Error(`oxc-wasm: the module imports ${facts.imports.join(', ')}; it must import nothing`);
214
+ if (JSON.stringify([...facts.exports].sort()) !== JSON.stringify(EXPORTS)) {
215
+ throw new Error(`oxc-wasm: the module exports ${facts.exports.join(', ')}; the ABI is ${EXPORTS.join(', ')}`);
216
+ }
217
+ const [memory] = facts.memories;
218
+ if (facts.memories.length !== 1 || memory.shared) throw new Error('oxc-wasm: the module must define one unshared memory');
219
+ if (memory.initial > OXC_MAX_INITIAL_PAGES) {
220
+ throw new Error(`oxc-wasm: the module's memory starts at ${memory.initial} pages, over OXC_MAX_INITIAL_PAGES (${OXC_MAX_INITIAL_PAGES})`);
221
+ }
222
+
223
+ const drifted = changedInputs(inputs, await oxcWasmInputs());
224
+ if (drifted.length) throw new Error(`oxc-wasm: inputs changed during the build: ${drifted.join(', ')}`);
225
+ const cargoToml = await fs.readFile(path.join(crateDir, 'Cargo.toml'), 'utf8');
226
+ const lock = await fs.readFile(path.join(crateDir, 'Cargo.lock'), 'utf8');
227
+ const provenance = {
228
+ artifact: 'nimbus-oxc',
229
+ oxc: lock.match(/\nname = "oxc"\nversion = "(.+)"/)?.[1],
230
+ target: TARGET,
231
+ source: {
232
+ crate: 'packages/worker/scripts/oxc-wasm',
233
+ manifestVersion: cargoToml.match(/^version = "(.+)"$/m)?.[1],
234
+ // Every input's sha256 (inputs.mjs); bundle-oxc-wasm.mjs stages this wasm
235
+ // only while they are the tree's.
236
+ inputs,
237
+ },
238
+ toolchain: rust.rustc,
239
+ cargo: {
240
+ command: `cargo ${cargoArgs.slice(0, -2).join(' ')}`,
241
+ rustflags: RUSTFLAGS,
242
+ rustcWrapper: 'napi-wasm/rustc-remap.sh (--remap-path-prefix <work>=/build <CARGO_HOME>=/cargo)',
243
+ },
244
+ wasmOpt: { binaryen: BINARYEN, version: optimizer.version, args: WASM_OPT_ARGS },
245
+ wasm: { imports: [], exports: EXPORTS, memoryInitialPages: memory.initial, stackBytes: WASM_STACK_BYTES },
246
+ outputs: { 'nimbus-oxc.wasm': { bytes: wasm.length, sha256: sha256(wasm) } },
247
+ };
248
+ await fs.writeFile(path.join(OUT, 'provenance.json'), JSON.stringify(provenance, null, 2) + '\n');
249
+ console.log(JSON.stringify(provenance.outputs, null, 2));
@@ -0,0 +1,51 @@
1
+ //! `cargo run --release --example memory -- <file> [key=value ...]`: peak heap of one transform.
2
+ use std::alloc::{GlobalAlloc, Layout, System};
3
+ use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
4
+
5
+ use oxc::allocator::Allocator;
6
+
7
+ struct Counting;
8
+ static LIVE: AtomicUsize = AtomicUsize::new(0);
9
+ static PEAK: AtomicUsize = AtomicUsize::new(0);
10
+
11
+ unsafe impl GlobalAlloc for Counting {
12
+ unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
13
+ let live = LIVE.fetch_add(layout.size(), Relaxed) + layout.size();
14
+ PEAK.fetch_max(live, Relaxed);
15
+ unsafe { System.alloc(layout) }
16
+ }
17
+ unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
18
+ LIVE.fetch_sub(layout.size(), Relaxed);
19
+ unsafe { System.dealloc(ptr, layout) }
20
+ }
21
+ unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
22
+ let live = LIVE.fetch_add(new_size, Relaxed) + new_size;
23
+ PEAK.fetch_max(live, Relaxed);
24
+ LIVE.fetch_sub(layout.size(), Relaxed);
25
+ unsafe { System.realloc(ptr, layout, new_size) }
26
+ }
27
+ }
28
+
29
+ #[global_allocator]
30
+ static GLOBAL: Counting = Counting;
31
+
32
+ fn main() {
33
+ let mut args = std::env::args().skip(1);
34
+ let path = args.next().expect("a file");
35
+ let source = std::fs::read_to_string(&path).expect("readable file");
36
+ let wire: Vec<String> = args.flat_map(|a| a.splitn(2, '=').map(str::to_string).collect::<Vec<_>>()).collect();
37
+ let options = nimbus_oxc::options::Options::decode(&wire.join("\0")).expect("options");
38
+ let base = LIVE.load(Relaxed);
39
+ PEAK.store(base, Relaxed);
40
+ let allocator = Allocator::default();
41
+ let started = std::time::Instant::now();
42
+ let output = nimbus_oxc::transform(&allocator, &source, &options);
43
+ let ms = started.elapsed().as_secs_f64() * 1000.0;
44
+ let arena = allocator.capacity();
45
+ let mib = |b: usize| b as f64 / 1048576.0;
46
+ eprintln!(
47
+ "source {:.2} MiB, output {:.2} MiB, arena {:.2} MiB (used {:.2}), peak heap over base {:.2} MiB, {:.1} ms, errors {}",
48
+ mib(source.len()), mib(output.code.len()), mib(arena), mib(allocator.used_bytes()), mib(PEAK.load(Relaxed) - base), ms,
49
+ output.diagnostics.iter().filter(|d| d.error).count()
50
+ );
51
+ }
@@ -0,0 +1,20 @@
1
+ //! `cargo run --example transform -- <file> [key=value ...]`: print one transform.
2
+ use oxc::allocator::Allocator;
3
+
4
+ fn main() {
5
+ let mut args = std::env::args().skip(1);
6
+ let path = args.next().expect("a file");
7
+ let source = std::fs::read_to_string(&path).expect("readable file");
8
+ // key=value; define=name=replacement
9
+ let wire: Vec<String> = args.flat_map(|a| a.splitn(if a.starts_with("define=") { 3 } else { 2 }, '=').map(str::to_string).collect::<Vec<_>>()).collect();
10
+ let options = nimbus_oxc::options::Options::decode(&wire.join("\0")).expect("options");
11
+ let allocator = Allocator::default();
12
+ let output = nimbus_oxc::transform(&allocator, &source, &options);
13
+ for d in &output.diagnostics {
14
+ eprintln!("{} {}:{}:{}: {}", if d.error { "ERROR" } else { "WARN" }, d.file, d.line, d.column, d.text);
15
+ }
16
+ print!("{}", output.code);
17
+ if let Some(map) = output.map {
18
+ eprintln!("MAP {map}");
19
+ }
20
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * inputs.mjs — what the Oxc wasm is built from, as digests: build.mjs writes
3
+ * them into the staged provenance.json, and bundle-oxc-wasm.mjs refuses to
4
+ * stage a wasm whose recorded inputs are not the tree's (a source edited
5
+ * without a rebuild).
6
+ */
7
+ import { createHash } from 'node:crypto';
8
+ import { promises as fs } from 'node:fs';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const CRATE = path.dirname(fileURLToPath(import.meta.url));
13
+ const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex');
14
+
15
+ /** Files outside src/ that decide the output, relative to the crate. */
16
+ const FILES = ['Cargo.toml', 'Cargo.lock', 'rust-toolchain.toml', 'build.mjs', 'inputs.mjs', '../napi-wasm/rustc-remap.sh'];
17
+
18
+ /** `{ <path>: <sha256> }` for every input, src/ file by file, sorted by path. */
19
+ export async function oxcWasmInputs(crate = CRATE) {
20
+ const src = (await fs.readdir(path.join(crate, 'src'), { recursive: true, withFileTypes: true }))
21
+ .filter((entry) => entry.isFile())
22
+ .map((entry) => path.relative(crate, path.join(entry.parentPath, entry.name)).split(path.sep).join('/'));
23
+ const paths = [...FILES, ...src].sort();
24
+ const digests = await Promise.all(paths.map(async (file) => [file, sha256(await fs.readFile(path.join(crate, file)))]));
25
+ return Object.fromEntries(digests);
26
+ }
27
+
28
+ /** The inputs that differ between two records, as `<path>` lines. */
29
+ export function changedInputs(recorded, current) {
30
+ const paths = new Set([...Object.keys(recorded ?? {}), ...Object.keys(current)]);
31
+ return [...paths].sort().filter((file) => recorded?.[file] !== current[file]);
32
+ }
@@ -0,0 +1,5 @@
1
+ # The exact rustc build.mjs builds with (it refuses any other).
2
+ [toolchain]
3
+ channel = "1.98.1"
4
+ targets = ["wasm32-unknown-unknown"]
5
+ profile = "minimal"
@@ -0,0 +1,136 @@
1
+ //! The wasm32 interface. The module imports nothing; JavaScript drives it
2
+ //! through four exports:
3
+ //!
4
+ //! 1. `nimbus_oxc_alloc(capacity)` returns a buffer, into which the caller
5
+ //! writes the UTF-8 source followed by the options' wire form (options.rs).
6
+ //! `nimbus_oxc_realloc(ptr, capacity, new_capacity)` grows it, for a source
7
+ //! whose UTF-8 is longer than the caller first guessed.
8
+ //! 2. `nimbus_oxc_transform(ptr, capacity, source_len, options_len)` consumes
9
+ //! that buffer and returns the address of nine `u32`s: status (0 ok, 1 the
10
+ //! module was refused, 2 the options were), then the address and length of
11
+ //! the code, of the source map JSON, and of the diagnostics, then the bytes
12
+ //! of AST arena the transform used and reserved.
13
+ //! 3. The caller copies what it needs, then calls `nimbus_oxc_release()`,
14
+ //! which frees the outputs. Nothing is kept between transforms, so the
15
+ //! arena a large module needed is reused for the next module's arena and
16
+ //! everything else.
17
+ //!
18
+ //! Diagnostics are seven fields per diagnostic: `E` or `W`, line, column,
19
+ //! length, file, line text, message. Each field is its UTF-8 byte length in
20
+ //! decimal, `:`, then its bytes, so a field may hold any character (a source
21
+ //! line can hold a NUL).
22
+
23
+ use std::alloc::{Layout, alloc, dealloc, realloc};
24
+ use std::fmt::Write;
25
+
26
+ use oxc::allocator::Allocator;
27
+
28
+ use crate::options::Options;
29
+
30
+ struct Result {
31
+ words: [u32; 9],
32
+ code: String,
33
+ map: String,
34
+ diagnostics: String,
35
+ }
36
+
37
+ static mut RESULT: Option<Result> = None;
38
+
39
+ #[unsafe(no_mangle)]
40
+ pub extern "C" fn nimbus_oxc_alloc(capacity: usize) -> *mut u8 {
41
+ // SAFETY: the size is nonzero and the layout's alignment is 1.
42
+ unsafe { alloc(Layout::from_size_align_unchecked(capacity.max(1), 1)) }
43
+ }
44
+
45
+ /// # Safety
46
+ /// `ptr` came from `nimbus_oxc_alloc(capacity)` (or this function with that capacity).
47
+ #[unsafe(no_mangle)]
48
+ pub unsafe extern "C" fn nimbus_oxc_realloc(ptr: *mut u8, capacity: usize, new_capacity: usize) -> *mut u8 {
49
+ // SAFETY: as documented; the layout matches the allocation's.
50
+ unsafe { realloc(ptr, Layout::from_size_align_unchecked(capacity.max(1), 1), new_capacity.max(1)) }
51
+ }
52
+
53
+ /// # Safety
54
+ /// `ptr` came from `nimbus_oxc_alloc(capacity)` and its first
55
+ /// `source_len + options_len` bytes are written.
56
+ #[unsafe(no_mangle)]
57
+ pub unsafe extern "C" fn nimbus_oxc_transform(
58
+ ptr: *mut u8,
59
+ capacity: usize,
60
+ source_len: usize,
61
+ options_len: usize,
62
+ ) -> *const u32 {
63
+ // SAFETY: the caller wrote these bytes at `ptr`.
64
+ let bytes = unsafe { std::slice::from_raw_parts(ptr, source_len + options_len) };
65
+ let result = run(&bytes[..source_len], &bytes[source_len..]);
66
+ // SAFETY: allocated by `nimbus_oxc_alloc(capacity)`.
67
+ unsafe { dealloc(ptr, Layout::from_size_align_unchecked(capacity.max(1), 1)) };
68
+ // SAFETY: wasm32 here is single-threaded; nothing else holds a reference to RESULT.
69
+ unsafe {
70
+ let slot = &mut *std::ptr::addr_of_mut!(RESULT);
71
+ let result = slot.insert(result);
72
+ result.words[1] = result.code.as_ptr() as u32;
73
+ result.words[2] = result.code.len() as u32;
74
+ result.words[3] = result.map.as_ptr() as u32;
75
+ result.words[4] = result.map.len() as u32;
76
+ result.words[5] = result.diagnostics.as_ptr() as u32;
77
+ result.words[6] = result.diagnostics.len() as u32;
78
+ result.words.as_ptr()
79
+ }
80
+ }
81
+
82
+ #[unsafe(no_mangle)]
83
+ pub extern "C" fn nimbus_oxc_release() {
84
+ // SAFETY: as in `nimbus_oxc_transform`.
85
+ unsafe { *std::ptr::addr_of_mut!(RESULT) = None };
86
+ }
87
+
88
+ fn run(source: &[u8], options: &[u8]) -> Result {
89
+ let refused = |status: u32, message: String| Result {
90
+ words: [status, 0, 0, 0, 0, 0, 0, 0, 0],
91
+ code: String::new(),
92
+ map: String::new(),
93
+ diagnostics: diagnostic_fields(['E'.to_string(), "0".into(), "0".into(), "0".into(), "<stdin>".into(), String::new(), message]),
94
+ };
95
+ let Ok(source) = std::str::from_utf8(source) else {
96
+ return refused(1, "the source is not UTF-8".into());
97
+ };
98
+ let options = match std::str::from_utf8(options).map_err(|e| e.to_string()).and_then(Options::decode) {
99
+ Ok(options) => options,
100
+ Err(message) => return refused(2, message),
101
+ };
102
+ let allocator = Allocator::default();
103
+ let output = crate::transform(&allocator, source, &options);
104
+ let arena = [allocator.used_bytes() as u32, allocator.capacity() as u32];
105
+ drop(allocator);
106
+ let diagnostics = output
107
+ .diagnostics
108
+ .iter()
109
+ .map(|d| {
110
+ diagnostic_fields([
111
+ if d.error { "E" } else { "W" }.to_string(),
112
+ d.line.to_string(),
113
+ d.column.to_string(),
114
+ d.length.to_string(),
115
+ d.file.clone(),
116
+ d.line_text.clone(),
117
+ d.text.clone(),
118
+ ])
119
+ })
120
+ .collect();
121
+ Result {
122
+ words: [u32::from(output.has_errors()), 0, 0, 0, 0, 0, 0, arena[0], arena[1]],
123
+ code: output.code,
124
+ map: output.map.unwrap_or_default(),
125
+ diagnostics,
126
+ }
127
+ }
128
+
129
+ /// One diagnostic's fields, each as `<byte length>:<bytes>`.
130
+ fn diagnostic_fields(fields: [String; 7]) -> String {
131
+ let mut out = String::new();
132
+ for field in fields {
133
+ let _ = write!(out, "{}:{field}", field.len());
134
+ }
135
+ out
136
+ }
@@ -0,0 +1,49 @@
1
+ //! Diagnostics in the shape esbuild reports them: text plus a location whose
2
+ //! line is 1-based and whose column is a 0-based byte offset into the line.
3
+
4
+ use oxc::diagnostics::{OxcDiagnostic, Severity};
5
+
6
+ pub struct Diagnostic {
7
+ pub error: bool,
8
+ pub text: String,
9
+ pub file: String,
10
+ /// 0 when the diagnostic has no location.
11
+ pub line: u32,
12
+ pub column: u32,
13
+ pub length: u32,
14
+ pub line_text: String,
15
+ }
16
+
17
+ pub fn convert(source: &str, file: &str, diagnostics: Vec<OxcDiagnostic>) -> Vec<Diagnostic> {
18
+ diagnostics
19
+ .into_iter()
20
+ .filter(|d| matches!(d.severity, Severity::Error | Severity::Warning))
21
+ .map(|d| {
22
+ let mut text = d.message.to_string();
23
+ if let Some(help) = &d.help {
24
+ text.push_str(" (");
25
+ text.push_str(help);
26
+ text.push(')');
27
+ }
28
+ let mut diagnostic = Diagnostic {
29
+ error: d.severity == Severity::Error,
30
+ text,
31
+ file: file.to_string(),
32
+ line: 0,
33
+ column: 0,
34
+ length: 0,
35
+ line_text: String::new(),
36
+ };
37
+ if let Some(label) = d.labels.first() {
38
+ let offset = (label.offset() as usize).min(source.len());
39
+ let line_start = source[..offset].rfind(['\n', '\r']).map_or(0, |i| i + 1);
40
+ let line_end = source[offset..].find(['\n', '\r']).map_or(source.len(), |i| offset + i);
41
+ diagnostic.line = 1 + source[..line_start].bytes().filter(|&b| b == b'\n').count() as u32;
42
+ diagnostic.column = (offset - line_start) as u32;
43
+ diagnostic.length = label.span().size().min((line_end - offset) as u32);
44
+ diagnostic.line_text = source[line_start..line_end].to_string();
45
+ }
46
+ diagnostic
47
+ })
48
+ .collect()
49
+ }