From 6bef3f5d7ca4c8502950bcffc628daa55bca1a24 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 23 May 2026 13:53:50 -0400 Subject: [PATCH 1/6] ref(wasm): split layout and input entrypoints --- .gitignore | 6 ++++-- Makefile | 51 +++++++++++++++++++++++++++----------------- deno.json | 4 +++- input-native.ts | 10 +-------- layout.ts | 4 ++++ src/module-input.c | 6 ++++++ src/module-layout.c | 10 +++++++++ tasks/build-npm.ts | 7 +++++- tasks/bundle-wasm.ts | 8 ++++--- term-native.ts | 2 +- 10 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 layout.ts create mode 100644 src/module-input.c create mode 100644 src/module-layout.c diff --git a/.gitignore b/.gitignore index 99b6cdf..30f5beb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /.agent-shell/ -/clayterm.wasm -/wasm.ts +/clayterm-layout.wasm +/clayterm-input.wasm +/wasm-layout.ts +/wasm-input.ts /build/ /node_modules/ diff --git a/Makefile b/Makefile index 1103b8e..90fb527 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,4 @@ CC = clang -TARGET = clayterm.wasm -SRC = src/module.c CFLAGS = --target=wasm32 -nostdlib -O2 \ -ffunction-sections -fdata-sections \ @@ -8,7 +6,13 @@ CFLAGS = --target=wasm32 -nostdlib -O2 \ -DCLAY_IMPLEMENTATION -DCLAY_WASM \ -Isrc -I. -EXPORTS = \ +LDFLAGS_COMMON = -Wl,--no-entry \ + -Wl,--import-memory \ + -Wl,--stack-first \ + -Wl,--strip-all \ + -Wl,--gc-sections + +LAYOUT_EXPORTS = \ -Wl,--export=__heap_base \ -Wl,--export=clayterm_size \ -Wl,--export=init \ @@ -24,7 +28,10 @@ EXPORTS = \ -Wl,--export=error_count \ -Wl,--export=error_type \ -Wl,--export=error_message_length \ - -Wl,--export=error_message_ptr \ + -Wl,--export=error_message_ptr + +INPUT_EXPORTS = \ + -Wl,--export=__heap_base \ -Wl,--export=input_size \ -Wl,--export=input_init \ -Wl,--export=input_scan \ @@ -32,27 +39,33 @@ EXPORTS = \ -Wl,--export=input_event \ -Wl,--export=input_delay -LDFLAGS = -Wl,--no-entry \ - -Wl,--import-memory \ - -Wl,--stack-first \ - -Wl,--strip-all \ - -Wl,--gc-sections \ - -Wl,--undefined=Clay__MeasureText \ - -Wl,--undefined=Clay__QueryScrollOffset \ - $(EXPORTS) +LAYOUT_LDFLAGS = $(LDFLAGS_COMMON) \ + -Wl,--undefined=Clay__MeasureText \ + -Wl,--undefined=Clay__QueryScrollOffset \ + $(LAYOUT_EXPORTS) -all: $(TARGET) wasm.ts - @echo "Built $(TARGET) ($$(wc -c < $(TARGET)) bytes raw, $$(gzip -c $(TARGET) | wc -c) bytes gzip)" +INPUT_LDFLAGS = $(LDFLAGS_COMMON) \ + $(INPUT_EXPORTS) DEPS = $(wildcard src/*.c src/*.h) -$(TARGET): $(DEPS) - $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(SRC) +all: clayterm-layout.wasm clayterm-input.wasm wasm-layout.ts wasm-input.ts + @echo "Built clayterm-layout.wasm ($$(wc -c < clayterm-layout.wasm) bytes raw, $$(gzip -c clayterm-layout.wasm | wc -c) bytes gzip)" + @echo "Built clayterm-input.wasm ($$(wc -c < clayterm-input.wasm) bytes raw, $$(gzip -c clayterm-input.wasm | wc -c) bytes gzip)" + +clayterm-layout.wasm: $(DEPS) + $(CC) $(CFLAGS) $(LAYOUT_LDFLAGS) -o $@ src/module-layout.c + +clayterm-input.wasm: $(DEPS) + $(CC) $(filter-out -DCLAY_IMPLEMENTATION -DCLAY_WASM, $(CFLAGS)) $(INPUT_LDFLAGS) -o $@ src/module-input.c + +wasm-layout.ts: clayterm-layout.wasm + deno run --allow-read --allow-write tasks/bundle-wasm.ts clayterm-layout.wasm wasm-layout.ts -wasm.ts: $(TARGET) - deno run --allow-read --allow-write tasks/bundle-wasm.ts +wasm-input.ts: clayterm-input.wasm + deno run --allow-read --allow-write tasks/bundle-wasm.ts clayterm-input.wasm wasm-input.ts clean: - rm -f $(TARGET) wasm.ts + rm -f clayterm.wasm clayterm-layout.wasm clayterm-input.wasm wasm.ts wasm-layout.ts wasm-input.ts .PHONY: all clean diff --git a/deno.json b/deno.json index 901feb9..71af68c 100644 --- a/deno.json +++ b/deno.json @@ -21,11 +21,13 @@ }, "exports": { ".": "./mod.ts", + "./layout": "./layout.ts", + "./input": "./input.ts", "./validate": "./validate.ts" }, "publish": { "include": ["*.ts"], - "exclude": ["!wasm.ts"] + "exclude": ["!wasm-layout.ts", "!wasm-input.ts"] }, "nodeModulesDir": "auto", "fmt": { diff --git a/input-native.ts b/input-native.ts index 7ed6d1b..34272b0 100644 --- a/input-native.ts +++ b/input-native.ts @@ -169,7 +169,7 @@ export interface InputNative { delay(st: number): number; } -import { compiled } from "./wasm.ts"; +import { compiled } from "./wasm-input.ts"; export async function createInputNative( escLatency: number, @@ -178,14 +178,6 @@ export async function createInputNative( let instance = await WebAssembly.instantiate(compiled, { env: { memory }, - clay: { - measureTextFunction() {}, - queryScrollOffsetFunction(ret: number) { - let v = new DataView(memory.buffer); - v.setFloat32(ret, 0, true); - v.setFloat32(ret + 4, 0, true); - }, - }, }); let exports = instance.exports as unknown as { diff --git a/layout.ts b/layout.ts new file mode 100644 index 0000000..1fe605e --- /dev/null +++ b/layout.ts @@ -0,0 +1,4 @@ +export * from "./ops.ts"; +export * from "./term.ts"; +export * from "./settings.ts"; +export * from "./termcodes.ts"; diff --git a/src/module-input.c b/src/module-input.c new file mode 100644 index 0000000..550d698 --- /dev/null +++ b/src/module-input.c @@ -0,0 +1,6 @@ +/* module-input.c — input-only compilation unit, no Clay layout engine */ + +#include "mem.c" +#include "utf8.c" +#include "trie.c" +#include "input.c" diff --git a/src/module-layout.c b/src/module-layout.c new file mode 100644 index 0000000..f865f5f --- /dev/null +++ b/src/module-layout.c @@ -0,0 +1,10 @@ +/* module-layout.c — layout-only compilation unit, no input parser */ + +#include "../clay/clay.h" + +#include "mem.c" +#include "buffer.c" +#include "cell.c" +#include "utf8.c" +#include "wcwidth.c" +#include "clayterm.c" diff --git a/tasks/build-npm.ts b/tasks/build-npm.ts index da64792..174b949 100644 --- a/tasks/build-npm.ts +++ b/tasks/build-npm.ts @@ -10,7 +10,12 @@ if (!version) { } await build({ - entryPoints: ["./mod.ts"], + entryPoints: [ + "./mod.ts", + { name: "./layout", path: "./layout.ts" }, + { name: "./input", path: "./input.ts" }, + { name: "./validate", path: "./validate.ts" }, + ], outDir, shims: { deno: false, diff --git a/tasks/bundle-wasm.ts b/tasks/bundle-wasm.ts index c517974..2cad7e8 100644 --- a/tasks/bundle-wasm.ts +++ b/tasks/bundle-wasm.ts @@ -27,7 +27,9 @@ function encodeZ85(data: Uint8Array): string { return out.join(""); } -const wasm = await Deno.readFile("clayterm.wasm"); +const [input = "clayterm.wasm", output = "wasm.ts"] = Deno.args; + +const wasm = await Deno.readFile(input); const compressed = new Uint8Array( brotliCompressSync(wasm, { @@ -50,9 +52,9 @@ const compressed=d(${JSON.stringify(z85)},${compressed.byteLength}); export const compiled=await WebAssembly.compile(new Uint8Array(brotliDecompressSync(compressed))); `; -await Deno.writeTextFile("wasm.ts", source); +await Deno.writeTextFile(output, source); console.log( - `wrote wasm.ts (${wasm.length} → ${compressed.byteLength} bytes compressed, ${z85.length} bytes z85, ${ + `wrote ${output} (${wasm.length} → ${compressed.byteLength} bytes compressed, ${z85.length} bytes z85, ${ Math.round(z85.length / wasm.length * 100) }%)`, ); diff --git a/term-native.ts b/term-native.ts index 40e646d..548344f 100644 --- a/term-native.ts +++ b/term-native.ts @@ -31,7 +31,7 @@ export interface Native { errorMessage(ct: number, index: number): string; } -import { compiled } from "./wasm.ts"; +import { compiled } from "./wasm-layout.ts"; export async function createTermNative( w: number, From 27af483962fdea6b4f41dd84e67e435165d66b0f Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sat, 23 May 2026 21:21:46 -0400 Subject: [PATCH 2/6] ref: rename to layout.wasm and input.wasm --- .github/workflows/verify.yaml | 6 ++++-- .gitignore | 8 ++++---- BUILD.md | 22 +++++++++++++--------- Makefile | 20 ++++++++++---------- deno.json | 2 +- input-native.ts | 2 +- tasks/bundle-wasm.ts | 6 +++++- term-native.ts | 2 +- 8 files changed, 39 insertions(+), 29 deletions(-) diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index 09bce38..b8b54b9 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -45,8 +45,10 @@ jobs: with: name: clayterm-wasm path: | - clayterm.wasm - wasm.ts + layout.wasm + input.wasm + layout.wasm.ts + input.wasm.ts test-alt-os: needs: test diff --git a/.gitignore b/.gitignore index 30f5beb..efab0a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ /.agent-shell/ -/clayterm-layout.wasm -/clayterm-input.wasm -/wasm-layout.ts -/wasm-input.ts +/layout.wasm +/input.wasm +/layout.wasm.ts +/input.wasm.ts /build/ /node_modules/ diff --git a/BUILD.md b/BUILD.md index 4946ba1..abdc902 100644 --- a/BUILD.md +++ b/BUILD.md @@ -18,10 +18,12 @@ The local source build is driven by `make`. It generates: -- `clayterm.wasm` — the compiled WebAssembly module built from the C sources -- `wasm.ts` — a generated TypeScript file derived from `clayterm.wasm` +- `layout.wasm` — the layout WebAssembly module built from the C sources +- `input.wasm` — the input WebAssembly module built from the C sources +- `layout.wasm.ts` — generated TypeScript derived from `layout.wasm` +- `input.wasm.ts` — generated TypeScript derived from `input.wasm` -`wasm.ts` is generated output, not hand-maintained source. +These `.ts` files are generated output, not hand-maintained source. ## Clone the repo with submodules @@ -182,8 +184,10 @@ make This should produce: -- `clayterm.wasm` -- `wasm.ts` +- `layout.wasm` +- `input.wasm` +- `layout.wasm.ts` +- `input.wasm.ts` For a clean rebuild: @@ -197,7 +201,7 @@ Re-run `make` when: - you change files under `src/` - you update the `clay` submodule -- `clayterm.wasm` or `wasm.ts` is missing +- `layout.wasm`, `input.wasm`, `layout.wasm.ts`, or `input.wasm.ts` is missing - generated outputs look stale after switching branches or pulling changes When in doubt, use a clean rebuild: @@ -247,7 +251,7 @@ make clean && make Symptoms may include: - target-related `clang` errors mentioning `wasm32` -- linker failures while producing `clayterm.wasm` +- linker failures while producing the `.wasm` outputs Recovery: @@ -267,8 +271,8 @@ If the smoke test fails, fix the toolchain first and only then rerun `make`. Symptoms may include: -- `clayterm.wasm` is missing -- `wasm.ts` is missing +- `layout.wasm` or `input.wasm` is missing +- `layout.wasm.ts` or `input.wasm.ts` is missing - you changed `src/` or updated `clay/`, but the generated outputs do not match Recovery: diff --git a/Makefile b/Makefile index 90fb527..9feb82b 100644 --- a/Makefile +++ b/Makefile @@ -49,23 +49,23 @@ INPUT_LDFLAGS = $(LDFLAGS_COMMON) \ DEPS = $(wildcard src/*.c src/*.h) -all: clayterm-layout.wasm clayterm-input.wasm wasm-layout.ts wasm-input.ts - @echo "Built clayterm-layout.wasm ($$(wc -c < clayterm-layout.wasm) bytes raw, $$(gzip -c clayterm-layout.wasm | wc -c) bytes gzip)" - @echo "Built clayterm-input.wasm ($$(wc -c < clayterm-input.wasm) bytes raw, $$(gzip -c clayterm-input.wasm | wc -c) bytes gzip)" +all: layout.wasm input.wasm layout.wasm.ts input.wasm.ts + @echo "Built layout.wasm ($$(wc -c < layout.wasm) bytes raw, $$(gzip -c layout.wasm | wc -c) bytes gzip)" + @echo "Built input.wasm ($$(wc -c < input.wasm) bytes raw, $$(gzip -c input.wasm | wc -c) bytes gzip)" -clayterm-layout.wasm: $(DEPS) +layout.wasm: $(DEPS) $(CC) $(CFLAGS) $(LAYOUT_LDFLAGS) -o $@ src/module-layout.c -clayterm-input.wasm: $(DEPS) +input.wasm: $(DEPS) $(CC) $(filter-out -DCLAY_IMPLEMENTATION -DCLAY_WASM, $(CFLAGS)) $(INPUT_LDFLAGS) -o $@ src/module-input.c -wasm-layout.ts: clayterm-layout.wasm - deno run --allow-read --allow-write tasks/bundle-wasm.ts clayterm-layout.wasm wasm-layout.ts +layout.wasm.ts: layout.wasm + deno run --allow-read --allow-write tasks/bundle-wasm.ts layout.wasm layout.wasm.ts -wasm-input.ts: clayterm-input.wasm - deno run --allow-read --allow-write tasks/bundle-wasm.ts clayterm-input.wasm wasm-input.ts +input.wasm.ts: input.wasm + deno run --allow-read --allow-write tasks/bundle-wasm.ts input.wasm input.wasm.ts clean: - rm -f clayterm.wasm clayterm-layout.wasm clayterm-input.wasm wasm.ts wasm-layout.ts wasm-input.ts + rm -f layout.wasm input.wasm layout.wasm.ts input.wasm.ts .PHONY: all clean diff --git a/deno.json b/deno.json index 71af68c..afda61f 100644 --- a/deno.json +++ b/deno.json @@ -27,7 +27,7 @@ }, "publish": { "include": ["*.ts"], - "exclude": ["!wasm-layout.ts", "!wasm-input.ts"] + "exclude": ["!layout.wasm.ts", "!input.wasm.ts"] }, "nodeModulesDir": "auto", "fmt": { diff --git a/input-native.ts b/input-native.ts index 34272b0..11576c9 100644 --- a/input-native.ts +++ b/input-native.ts @@ -169,7 +169,7 @@ export interface InputNative { delay(st: number): number; } -import { compiled } from "./wasm-input.ts"; +import { compiled } from "./input.wasm.ts"; export async function createInputNative( escLatency: number, diff --git a/tasks/bundle-wasm.ts b/tasks/bundle-wasm.ts index 2cad7e8..bd32feb 100644 --- a/tasks/bundle-wasm.ts +++ b/tasks/bundle-wasm.ts @@ -27,7 +27,11 @@ function encodeZ85(data: Uint8Array): string { return out.join(""); } -const [input = "clayterm.wasm", output = "wasm.ts"] = Deno.args; +const [input, output] = Deno.args; +if (!input || !output) { + console.error("Usage: bundle-wasm.ts "); + Deno.exit(1); +} const wasm = await Deno.readFile(input); diff --git a/term-native.ts b/term-native.ts index 548344f..3ad544b 100644 --- a/term-native.ts +++ b/term-native.ts @@ -31,7 +31,7 @@ export interface Native { errorMessage(ct: number, index: number): string; } -import { compiled } from "./wasm-layout.ts"; +import { compiled } from "./layout.wasm.ts"; export async function createTermNative( w: number, From 9b7e4ac710950fcb3b9e1ff16b4a1d33233357ef Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 1 Jun 2026 09:13:09 -0500 Subject: [PATCH 3/6] fix(ci): point build at split files --- .github/workflows/benchmark.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index cca1753..e78b617 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -38,8 +38,10 @@ jobs: id: wasm-cache with: path: | - clayterm.wasm - wasm.ts + layout.wasm + input.wasm + layout.wasm.ts + input.wasm.ts key: wasm-${{ hashFiles('Makefile', 'src/**', 'tasks/bundle-wasm.ts') }} - name: Build WASM @@ -62,7 +64,9 @@ jobs: with: name: bench-build retention-days: 1 - path: wasm.ts + path: | + layout.wasm.ts + input.wasm.ts simulation: name: Run benchmarks (simulation) From 8aad22250012a5a5075c5a19dd99f6925165b8ca Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 1 Jun 2026 15:16:20 -0500 Subject: [PATCH 4/6] ref(wasm): shared decode util --- tasks/bundle-wasm.ts | 11 ++++------- wasm-decode.ts | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 wasm-decode.ts diff --git a/tasks/bundle-wasm.ts b/tasks/bundle-wasm.ts index bd32feb..e71a15e 100644 --- a/tasks/bundle-wasm.ts +++ b/tasks/bundle-wasm.ts @@ -47,13 +47,10 @@ const compressed = new Uint8Array( const z85 = encodeZ85(compressed); -// Decoder uses division instead of >>> to avoid 32-bit truncation on values near 0xFFFFFFFF. -const source = `import{brotliDecompressSync}from"node:zlib"; -const Z="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#"; -const T=new Uint8Array(128);for(let i=0;i<85;i++)T[Z.charCodeAt(i)]=i; -function d(s:string,n:number){const b=new Uint8Array(n);let o=0;for(let i=0;i()[]{}@%$#"; + +const DECODE = new Uint8Array(128); +for (let i = 0; i < 85; i++) DECODE[Z85.charCodeAt(i)] = i; + +// Division instead of >>> avoids 32-bit truncation on values near 0xFFFFFFFF. +function decodeZ85(s: string, n: number): Uint8Array { + let bytes = new Uint8Array(n); + let o = 0; + for (let i = 0; i < s.length && o < n; i += 5) { + let v = DECODE[s.charCodeAt(i)] * 52200625 + + DECODE[s.charCodeAt(i + 1)] * 614125 + + DECODE[s.charCodeAt(i + 2)] * 7225 + + DECODE[s.charCodeAt(i + 3)] * 85 + + DECODE[s.charCodeAt(i + 4)]; + if (o < n) bytes[o++] = Math.floor(v / 16777216); + if (o < n) bytes[o++] = Math.floor(v / 65536) % 256; + if (o < n) bytes[o++] = Math.floor(v / 256) % 256; + if (o < n) bytes[o++] = v % 256; + } + return bytes; +} + +/** + * Decode an inlined WASM blob: z85 → brotli-decompress → compile. + * + * `byteLength` is the brotli-compressed length, used to size the z85 + * decode buffer before decompression restores the original module. + */ +export function decode( + z85: string, + byteLength: number, +): Promise { + let compressed = decodeZ85(z85, byteLength); + return WebAssembly.compile(new Uint8Array(brotliDecompressSync(compressed))); +} From 51f4d26074c0b5cdf2f8b2d8273084e233829ff7 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 1 Jun 2026 16:02:12 -0500 Subject: [PATCH 5/6] ref(make): explicit opt flags --- Makefile | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 9feb82b..8dadf17 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,15 @@ CC = clang -CFLAGS = --target=wasm32 -nostdlib -O2 \ - -ffunction-sections -fdata-sections \ - -mbulk-memory \ - -DCLAY_IMPLEMENTATION -DCLAY_WASM \ - -Isrc -I. +CFLAGS_BASE = --target=wasm32 -nostdlib \ + -ffunction-sections -fdata-sections \ + -mbulk-memory \ + -Isrc -I. + +INPUT_OPT ?= -Oz +LAYOUT_OPT ?= -O2 + +LAYOUT_CFLAGS = $(CFLAGS_BASE) $(LAYOUT_OPT) -DCLAY_IMPLEMENTATION -DCLAY_WASM +INPUT_CFLAGS = $(CFLAGS_BASE) $(INPUT_OPT) LDFLAGS_COMMON = -Wl,--no-entry \ -Wl,--import-memory \ @@ -54,10 +59,10 @@ all: layout.wasm input.wasm layout.wasm.ts input.wasm.ts @echo "Built input.wasm ($$(wc -c < input.wasm) bytes raw, $$(gzip -c input.wasm | wc -c) bytes gzip)" layout.wasm: $(DEPS) - $(CC) $(CFLAGS) $(LAYOUT_LDFLAGS) -o $@ src/module-layout.c + $(CC) $(LAYOUT_CFLAGS) $(LAYOUT_LDFLAGS) -o $@ src/module-layout.c input.wasm: $(DEPS) - $(CC) $(filter-out -DCLAY_IMPLEMENTATION -DCLAY_WASM, $(CFLAGS)) $(INPUT_LDFLAGS) -o $@ src/module-input.c + $(CC) $(INPUT_CFLAGS) $(INPUT_LDFLAGS) -o $@ src/module-input.c layout.wasm.ts: layout.wasm deno run --allow-read --allow-write tasks/bundle-wasm.ts layout.wasm layout.wasm.ts From 854558c16e97457eb92608886c8f357e1f850a65 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 1 Jun 2026 21:03:58 -0500 Subject: [PATCH 6/6] fix(perf): build input.wasm with -O2 not -Oz --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8dadf17..8f7f1ae 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ CFLAGS_BASE = --target=wasm32 -nostdlib \ -mbulk-memory \ -Isrc -I. -INPUT_OPT ?= -Oz +INPUT_OPT ?= -O2 LAYOUT_OPT ?= -O2 LAYOUT_CFLAGS = $(CFLAGS_BASE) $(LAYOUT_OPT) -DCLAY_IMPLEMENTATION -DCLAY_WASM