Skip to content

Commit 1b05c8a

Browse files
committed
- GC proposal: struct/array types, recursive type groups, subtyping, GC opcodes
- Exception handling: tags (import, export, define) - GC opcodes accept builder objects directly instead of requiring .index - Disassembler: converts parsed WASM binary back to WAT - Instruction decoder: decodes raw instruction bytes into structured objects - BinaryReader now handles GC types, tags, data count section, passive segments - WatParser parses rec groups with forward references and sub/sub_final subtyping - WatParser supports named type references in functions - Verifier handles GC opcodes and ref subtype assignments - Replace any with proper types in opcode signatures - Rename defineFuncType→defineFunctionType, defineTableSegment→defineElementSegment - Added mut to playground exports so structDSL example works in browser - Removed disableVerification from structDSL playground example - Removed stale examples/ directory - Cleaned up index exports - Added docs for Disassembler, InstructionDecoder, parseWat rec/sub syntax, GC builder-object support - Added tests for all new features
1 parent e6d4fc9 commit 1b05c8a

55 files changed

Lines changed: 6822 additions & 1323 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ npm run build
186186
npm test
187187

188188
# Run tests with coverage
189-
npm run test:cover
189+
npm run test:coverage
190190

191191
# Regenerate opcode definitions from spec
192192
npm run generate

docs/api.md

Lines changed: 123 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ importFunction(
5151
): ImportBuilder
5252

5353
// Import memory
54-
importMemory(module: string, name: string, initial: number, maximum?: number, shared?: boolean): ImportBuilder
54+
importMemory(module: string, name: string, initial: number, maximum?: number, shared?: boolean, memory64?: boolean): ImportBuilder
5555

5656
// Import a table
5757
importTable(
@@ -396,30 +396,32 @@ delegate(depth: number): void
396396
397397
### GC Operations (requires `gc` feature)
398398
399-
```typescript
400-
// Struct operations
401-
struct_new(typeIndex: number): void
402-
struct_new_default(typeIndex: number): void
403-
struct_get(typeIndex: number, fieldIndex: number): void
404-
struct_get_s(typeIndex: number, fieldIndex: number): void
405-
struct_get_u(typeIndex: number, fieldIndex: number): void
406-
struct_set(typeIndex: number, fieldIndex: number): void
407-
408-
// Array operations
409-
array_new(typeIndex: number): void
410-
array_new_default(typeIndex: number): void
411-
array_new_fixed(typeIndex: number, length: number): void
412-
array_new_data(typeIndex: number, dataIndex: number): void
413-
array_new_elem(typeIndex: number, elemIndex: number): void
414-
array_get(typeIndex: number): void
415-
array_get_s(typeIndex: number): void // Signed packed field access
416-
array_get_u(typeIndex: number): void // Unsigned packed field access
417-
array_set(typeIndex: number): void
399+
All `typeIndex` parameters accept either a numeric index or a builder object (`StructTypeBuilder`, `ArrayTypeBuilder`, etc.) — the `.index` is extracted automatically.
400+
401+
```typescript
402+
// Struct operations — typeIndex accepts number or builder
403+
struct_new(typeIndex: number | { index: number }): void
404+
struct_new_default(typeIndex: number | { index: number }): void
405+
struct_get(typeIndex: number | { index: number }, fieldIndex: number): void
406+
struct_get_s(typeIndex: number | { index: number }, fieldIndex: number): void
407+
struct_get_u(typeIndex: number | { index: number }, fieldIndex: number): void
408+
struct_set(typeIndex: number | { index: number }, fieldIndex: number): void
409+
410+
// Array operations — typeIndex accepts number or builder
411+
array_new(typeIndex: number | { index: number }): void
412+
array_new_default(typeIndex: number | { index: number }): void
413+
array_new_fixed(typeIndex: number | { index: number }, length: number): void
414+
array_new_data(typeIndex: number | { index: number }, dataIndex: number): void
415+
array_new_elem(typeIndex: number | { index: number }, elemIndex: number): void
416+
array_get(typeIndex: number | { index: number }): void
417+
array_get_s(typeIndex: number | { index: number }): void // Signed packed field access
418+
array_get_u(typeIndex: number | { index: number }): void // Unsigned packed field access
419+
array_set(typeIndex: number | { index: number }): void
418420
array_len(): void
419-
array_fill(typeIndex: number): void
420-
array_copy(dstTypeIndex: number, srcTypeIndex: number): void
421-
array_init_data(typeIndex: number, dataIndex: number): void
422-
array_init_elem(typeIndex: number, elemIndex: number): void
421+
array_fill(typeIndex: number | { index: number }): void
422+
array_copy(dstTypeIndex: number | { index: number }, srcTypeIndex: number | { index: number }): void
423+
array_init_data(typeIndex: number | { index: number }, dataIndex: number): void
424+
array_init_elem(typeIndex: number | { index: number }, elemIndex: number): void
423425

424426
// Reference operations
425427
ref_null(heapType: number): void // Push a null reference (HeapType value)
@@ -530,13 +532,19 @@ Returned by `ModuleBuilder.defineTag()`. Describes an exception tag for use with
530532
```typescript
531533
// The function type describing the tag's parameter types
532534
get funcType(): FuncTypeBuilder
535+
536+
// Export this tag
537+
withExport(name?: string): TagBuilder
538+
539+
// Set the debug name for this tag
540+
withName(name: string): TagBuilder
533541
```
534542
535543
---
536544
537545
## ImportBuilder
538546
539-
Returned by `ModuleBuilder.importFunction()`, `importMemory()`, `importTable()`, `importGlobal()`.
547+
Returned by `ModuleBuilder.importFunction()`, `importMemory()`, `importTable()`, `importGlobal()`, `importTag()`.
540548
541549
```typescript
542550
// Properties
@@ -821,6 +829,64 @@ const info = reader.read();
821829

822830
---
823831

832+
## Disassembler
833+
834+
Converts a parsed WASM binary (from `BinaryReader`) back into WAT text format.
835+
836+
```typescript
837+
import { BinaryReader, Disassembler } from 'webasmjs';
838+
839+
const reader = new BinaryReader(wasmBytes);
840+
const moduleInfo = reader.read();
841+
842+
const disassembler = new Disassembler(moduleInfo);
843+
const wat = disassembler.disassemble();
844+
// Returns a full WAT module string:
845+
// (module $name
846+
// (type ...)
847+
// (func ...)
848+
// ...
849+
// )
850+
```
851+
852+
Handles all section types including types (func, struct, array, rec groups), imports, functions with decoded instructions, tables, memories, globals, exports, start, elements, and data segments. Uses the name section when available for readable `$name` identifiers.
853+
854+
---
855+
856+
## InstructionDecoder
857+
858+
Low-level decoder that converts raw WebAssembly instruction bytes into structured objects.
859+
860+
```typescript
861+
import { InstructionDecoder } from 'webasmjs';
862+
import type { DecodedInstruction } from 'webasmjs';
863+
864+
// Decode a buffer of instruction bytes
865+
const decoder = new InstructionDecoder(bodyBytes);
866+
const instructions: DecodedInstruction[] = decoder.decode();
867+
868+
// Or decode an init expression (e.g. from a global or data segment offset)
869+
const initInstructions = InstructionDecoder.decodeInitExpr(initExprBytes);
870+
```
871+
872+
Each `DecodedInstruction` contains:
873+
874+
```typescript
875+
interface DecodedInstruction {
876+
opCode: OpCodeDef; // The opcode definition (mnemonic, value, prefix, etc.)
877+
immediates: {
878+
type: string; // Immediate kind: 'VarUInt32', 'MemoryImmediate', etc.
879+
values: any[]; // Decoded immediate values
880+
};
881+
offset: number; // Byte offset in the original buffer
882+
length: number; // Total byte length of this instruction
883+
}
884+
```
885+
886+
Supports all instruction encodings including single-byte opcodes, prefixed opcodes (0xFC bulk/sat-trunc, 0xFB GC, 0xFD SIMD), LEB128 integers, floats, branch tables, v128 constants, and GC type immediates.
887+
888+
---
889+
824890
## parseWat
825891

826892
Parses WAT text format into a `ModuleBuilder`.
@@ -842,6 +908,38 @@ const mod = parseWat(`
842908
const instance = await mod.instantiate();
843909
```
844910

911+
### Recursive type groups
912+
913+
```typescript
914+
const mod = parseWat(`
915+
(module
916+
(rec
917+
(type $list (sub (struct (field i32) (field (ref null $list)))))
918+
)
919+
(func $len (param (ref null $list)) (result i32)
920+
(local $count i32)
921+
;; ...
922+
)
923+
)
924+
`);
925+
```
926+
927+
### Subtype declarations
928+
929+
```typescript
930+
const mod = parseWat(`
931+
(module
932+
(type $base (sub (struct (field $x (mut i32)))))
933+
(type $child (sub $base (struct (field $x (mut i32)) (field $y f64))))
934+
(type $sealed (sub_final $base (struct (field $x (mut i32)) (field $z i64))))
935+
)
936+
`);
937+
```
938+
939+
- `sub` — declares an open (non-final) type that can be further subtyped
940+
- `sub $parent` — declares an open subtype of `$parent`
941+
- `sub_final $parent` — declares a sealed (final) subtype of `$parent`
942+
845943
---
846944

847945
## Enums

docs/examples.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Examples
22

3-
> For all 103 interactive examples, see the [playground](https://devnamedzed.github.io/webasmjs/).
3+
> For all 112 interactive examples, see the [playground](https://devnamedzed.github.io/webasmjs/).
4+
> The playground includes additional coverage for atomics, memory64, relaxed SIMD, tail calls, imports, and GC struct DSL.
45
> This page covers the most important patterns in detail.
56
67
## Factorial

docs/getting-started.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ The `end` instruction is automatically added when using the callback form.
8080

8181
## Control Flow
8282

83-
Use block, loop, and if/else with callbacks for structured control flow:
83+
Use block, loop, and if/else for structured control flow:
8484

8585
```typescript
8686
mod.defineFunction('abs', [ValueType.Int32], [ValueType.Int32], (f, a) => {
@@ -148,3 +148,19 @@ console.log(wat);
148148
- [API Reference](api.md) — complete documentation of all builders and emitters
149149
- [Examples](examples.md) — annotated examples covering common patterns
150150
- [Playground](https://devnamedzed.github.io/webasmjs/) — try webasmjs in the browser
151+
152+
You can also build modules by parsing WAT text with `parseWat()`:
153+
154+
```typescript
155+
import { parseWat } from 'webasmjs';
156+
157+
const mod = parseWat(`
158+
(module
159+
(func (export "add") (param i32 i32) (result i32)
160+
local.get 0
161+
local.get 1
162+
i32.add))
163+
`);
164+
165+
const instance = await mod.instantiate();
166+
```

examples/factorial.js

Lines changed: 0 additions & 75 deletions
This file was deleted.

examples/index.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

examples/package-lock.json

Lines changed: 0 additions & 27 deletions
This file was deleted.

examples/package.json

Lines changed: 0 additions & 14 deletions
This file was deleted.

generator/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ function getOperandInfo(operand: string | null): OperandInfo | null {
4646
case 'VarUInt1':
4747
return { param: 'varUInt1: number', call: 'varUInt1' };
4848
case 'MemoryImmediate':
49-
return { param: 'alignment: number, offset: number', call: 'alignment, offset' };
49+
return { param: 'alignment: number, offset: number | bigint', call: 'alignment, offset' };
5050
case 'IndirectFunction':
51-
return { param: 'funcTypeBuilder: any', call: 'funcTypeBuilder' };
51+
return { param: 'funcTypeBuilder: any, tableIndex?: number', call: 'funcTypeBuilder, tableIndex' };
5252
case 'BlockSignature':
5353
return { param: 'blockType: any, label?: any', call: 'blockType, label' };
5454
case 'Function':
@@ -70,17 +70,17 @@ function getOperandInfo(operand: string | null): OperandInfo | null {
7070
case 'VarInt64':
7171
return { param: 'varInt64: number | bigint', call: 'varInt64' };
7272
case 'VarUInt32':
73-
return { param: 'varUInt32: number', call: 'varUInt32' };
73+
return { param: 'varUInt32: number | { index: number }', call: 'varUInt32' };
7474
case 'V128Const':
7575
return { param: 'bytes: Uint8Array', call: 'bytes' };
7676
case 'LaneIndex':
7777
return { param: 'laneIndex: number', call: 'laneIndex' };
7878
case 'ShuffleMask':
7979
return { param: 'mask: Uint8Array', call: 'mask' };
8080
case 'TypeIndexField':
81-
return { param: 'typeIndex: number, fieldIndex: number', call: 'typeIndex, fieldIndex' };
81+
return { param: 'typeIndex: number | { index: number }, fieldIndex: number', call: 'typeIndex, fieldIndex' };
8282
case 'TypeIndexIndex':
83-
return { param: 'typeIndex: number, index: number', call: 'typeIndex, index' };
83+
return { param: 'typeIndex: number | { index: number }, index: number', call: 'typeIndex, index' };
8484
case 'HeapType':
8585
return { param: 'heapType: any', call: 'heapType' };
8686
case 'BrOnCast':

0 commit comments

Comments
 (0)