watr 4.6.9 → 4.7.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/dist/watr.wasm CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "watr",
3
- "version": "4.6.9",
3
+ "version": "4.7.0",
4
4
  "description": "Light & fast WAT compiler – WebAssembly Text to binary, parse, print, transform",
5
5
  "main": "watr.js",
6
6
  "bin": {
@@ -26,6 +26,10 @@
26
26
  "./compile": {
27
27
  "types": "./types/src/compile.d.ts",
28
28
  "default": "./src/compile.js"
29
+ },
30
+ "./optimize": {
31
+ "types": "./types/src/optimize.d.ts",
32
+ "default": "./src/optimize.js"
29
33
  }
30
34
  },
31
35
  "typesVersions": {
@@ -45,12 +49,12 @@
45
49
  "scripts": {
46
50
  "build": "npm run build:js && npm run build:wasm",
47
51
  "build:js": "esbuild watr.js --bundle --format=esm --outfile=dist/watr.js && esbuild watr.js --bundle --format=esm --minify --outfile=dist/watr.min.js",
48
- "build:wasm": "npx jz watr.js -O2 -o dist/watr.wasm",
52
+ "build:wasm": "npx jz watr.js -O3 -o dist/watr.wasm",
49
53
  "test": "node test",
50
54
  "test:wasm": "WATR_WASM=1 node test",
51
55
  "test:repl": "npx playwright test",
52
56
  "types": "npx tsc src/*.js watr.js --allowJs --declaration --emitDeclarationOnly --declarationMap --outDir types",
53
- "prepublishOnly": "npm test && npm run test:wasm && npm run build && npm run types",
57
+ "prepublishOnly": "npm run build && npm run types && npm test && npm run test:wasm",
54
58
  "prepare": "git submodule update --init --recursive 2>/dev/null || true"
55
59
  },
56
60
  "repository": {
@@ -93,7 +97,7 @@
93
97
  "devDependencies": {
94
98
  "@playwright/test": "^1.57.0",
95
99
  "esbuild": "^0.24.2",
96
- "jz": "^0.3.1",
100
+ "jz": "^0.7.0",
97
101
  "tst": "^9.0.0"
98
102
  }
99
103
  }
package/src/compile.js CHANGED
@@ -41,7 +41,11 @@ const cleanup = (node, result) => {
41
41
  * Converts a WebAssembly Text Format (WAT) tree to a WebAssembly binary format (WASM).
42
42
  *
43
43
  * @param {string|Array} nodes - The WAT tree or string to be compiled to WASM binary.
44
- * @returns {Uint8Array} The compiled WASM binary data.
44
+ * @returns {Uint8Array} The compiled WASM binary. When the WAT carries
45
+ * `@metadata.code.<type>` annotations, the result also exposes a `.metadata`
46
+ * property — `{ [type]: [[absoluteByteOffset, data], ...] }`, each offset the
47
+ * instruction's position in the final binary. That's the hook a source-map
48
+ * emitter needs to correlate generated wasm back to its source.
45
49
  */
46
50
  export default function compile(nodes) {
47
51
  // normalize to (module ...) form
@@ -237,32 +241,80 @@ export default function compile(nodes) {
237
241
  // pre-compute sections that may collect string constants (for strings section ordering)
238
242
  const globalSection = bin(SECTION.global)
239
243
  const elemSection = bin(SECTION.elem)
240
- const codeSection = bin(SECTION.code)
244
+ // inline bin(code) so the per-function item bytes survive — with ctx.codeSizePrefix
245
+ // they let us lift code-metadata positions to absolute binary offsets below
246
+ const codeItems = ctx.code.filter(Boolean).map(item => build[SECTION.code](item, ctx)).filter(Boolean)
247
+ const codeSection = codeItems.length ? [SECTION.code, ...vec(vec(codeItems))] : []
241
248
  const metaSection = binMeta()
242
249
  const dataSection = bin(SECTION.data)
243
250
  const stringsSection = ctx.strings.length ? [SECTION.strings, ...vec([0x00, ...vec(ctx.strings.map(s => vec(s)))])] : []
244
251
 
252
+ // sections in binary order (each a byte array — lengths feed offset math)
253
+ const sections = [
254
+ bin(SECTION.custom),
255
+ bin(SECTION.type),
256
+ bin(SECTION.import),
257
+ bin(SECTION.func),
258
+ bin(SECTION.table),
259
+ bin(SECTION.memory),
260
+ bin(SECTION.tag),
261
+ stringsSection,
262
+ globalSection,
263
+ bin(SECTION.export),
264
+ bin(SECTION.start, false),
265
+ elemSection,
266
+ bin(SECTION.datacount, false),
267
+ codeSection,
268
+ metaSection,
269
+ dataSection
270
+ ]
271
+
245
272
  // build final binary
246
- return Uint8Array.from([
273
+ const wasm = Uint8Array.from([
247
274
  0x00, 0x61, 0x73, 0x6d, // magic
248
275
  0x01, 0x00, 0x00, 0x00, // version
249
- ...bin(SECTION.custom),
250
- ...bin(SECTION.type),
251
- ...bin(SECTION.import),
252
- ...bin(SECTION.func),
253
- ...bin(SECTION.table),
254
- ...bin(SECTION.memory),
255
- ...bin(SECTION.tag),
256
- ...stringsSection,
257
- ...globalSection,
258
- ...bin(SECTION.export),
259
- ...bin(SECTION.start, false),
260
- ...elemSection,
261
- ...bin(SECTION.datacount, false),
262
- ...codeSection,
263
- ...metaSection,
264
- ...dataSection
276
+ ...sections.flat()
265
277
  ])
278
+
279
+ // Lift any (@metadata.code.*) annotations to absolute binary offsets and hang
280
+ // them off the result, so a source-map emitter can correlate wasm back to
281
+ // source. The result stays a Uint8Array; .metadata appears only when the WAT
282
+ // actually carried annotations — the common (annotation-free) path stays free.
283
+ const md = metadataOffsets()
284
+ if (md) wasm.metadata = md
285
+
286
+ return wasm
287
+
288
+ // Map each recorded code-metadata position (function-body-relative, as stored
289
+ // by build.code) to its absolute byte offset in the final binary.
290
+ // → { [type]: [[absoluteByteOffset, data], ...] } sorted by offset, or
291
+ // undefined when no annotations were present.
292
+ function metadataOffsets() {
293
+ let has = false
294
+ for (const _ in ctx.metadata) has = true // any annotations? (no break — jz self-host lowers for-in without one)
295
+ if (!has) return
296
+ const out = {}
297
+ // 8 = magic + version; sections before code give the code section's base
298
+ const codeBase = 8 + sections.slice(0, sections.indexOf(codeSection)).reduce((n, s) => n + s.length, 0)
299
+ // function items are the tail of the code section (after id + size + count)
300
+ const itemsBase = codeBase + codeSection.length - codeItems.reduce((n, it) => n + it.length, 0)
301
+ // per code index → absolute offset of its function body (start of locals vec)
302
+ const bodyBase = []
303
+ for (let i = 0, off = itemsBase; i < codeItems.length; i++) {
304
+ bodyBase[i] = off + (ctx.codeSizePrefix?.[i] ?? 0)
305
+ off += codeItems[i].length
306
+ }
307
+ const importedFuncs = ctx.import.filter(imp => imp[2][0] === 'func').length
308
+ for (const type in ctx.metadata) {
309
+ const entries = []
310
+ for (const [funcIdx, instances] of ctx.metadata[type]) {
311
+ const base = bodyBase[funcIdx - importedFuncs]
312
+ for (const [pos, data] of instances) entries.push([base + pos, data])
313
+ }
314
+ out[type] = entries.sort((a, b) => a[0] - b[0])
315
+ }
316
+ return out
317
+ }
266
318
  }
267
319
 
268
320
 
@@ -722,19 +774,30 @@ const build = [
722
774
  ctx.meta = {}
723
775
  const bytes = instr(body, ctx)
724
776
 
725
- // Store collected metadata for this function
726
- const funcIdx = ctx.import.filter(imp => imp[2][0] === 'func').length + codeIdx
727
- for (const type in ctx.meta) ((ctx.metadata ??= {})[type] ??= []).push([funcIdx, ctx.meta[type]])
728
-
729
777
  // squash locals into (n:u32 t:valtype)*, n is number and t is type
730
778
  // we skip locals provided by params
731
779
  let loctypes = ctx.local.slice(param.length).reduce((a, type) => (type == a[a.length - 1]?.[1] ? a[a.length - 1][0]++ : a.push([1, type]), a), [])
780
+ const locals = vec(loctypes.map(([n, t]) => [...uleb(n), ...reftype(t, ctx)]))
781
+
782
+ // Store collected metadata. instr() records positions relative to the
783
+ // instruction stream; shift them past the locals vec so they are
784
+ // function-body-relative — the reference point the code-metadata spec (and
785
+ // wabt/binaryen) use for the metadata.code.* sections.
786
+ const funcIdx = ctx.import.filter(imp => imp[2][0] === 'func').length + codeIdx
787
+ for (const type in ctx.meta) {
788
+ for (const inst of ctx.meta[type]) inst[0] += locals.length
789
+ ;((ctx.metadata ??= {})[type] ??= []).push([funcIdx, ctx.meta[type]])
790
+ }
732
791
 
733
792
  // cleanup tmp state
734
793
  ctx.local = ctx.block = ctx.meta = null
735
794
 
736
795
  // https://webassembly.github.io/spec/core/binary/modules.html#code-section
737
- return vec([...vec(loctypes.map(([n, t]) => [...uleb(n), ...reftype(t, ctx)])), ...bytes])
796
+ const item = vec([...locals, ...bytes])
797
+ // size-prefix length (item minus its body) lets the now function-body-relative
798
+ // metadata positions be lifted to absolute binary offsets
799
+ ;(ctx.codeSizePrefix ??= [])[codeIdx] = item.length - locals.length - bytes.length
800
+ return item
738
801
  },
739
802
 
740
803
  // (data (i32.const 0) "\aa" "\bb"?)
@@ -986,8 +1049,12 @@ const instr = (nodes, ctx) => {
986
1049
  bytes.push(...HANDLER[op](nodes, ctx, op))
987
1050
  }
988
1051
 
989
- // Record metadata at current byte position
990
- for (const [type, data] of meta) ((ctx.meta[type] ??= []).push([out.length, data]))
1052
+ // Attach any pending annotations to this instruction's byte position, then
1053
+ // clear — a (@metadata.code.*) annotation applies to the next instruction only
1054
+ if (meta.length) {
1055
+ for (const [type, data] of meta) ((ctx.meta[type] ??= []).push([out.length, data]))
1056
+ meta = []
1057
+ }
991
1058
 
992
1059
  out.push(...bytes)
993
1060
  }