Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions .claude/skills/typed-arrays/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
name: typed-arrays
description: |
Modern TypedArray and ArrayBuffer features including resizable buffers,
transfer operations, Float16Array, and Uint8Array base64/hex encoding.
compatibility: Node.js 20+ and all the modern browsers
---

# Modern Typed Arrays

## ES2023: Change Array by Copy

Immutable operations returning new arrays:

```typescript
const arr = new Uint8Array([3, 1, 2]);

arr.toReversed(); // Uint8Array [2, 1, 3]
arr.toSorted((a, b) => a - b); // Uint8Array [1, 2, 3]
arr.with(0, 99); // Uint8Array [99, 1, 2]
```

## ES2023: findLast / findLastIndex

```typescript
const arr = new Uint8Array([1, 2, 3, 2, 1]);

arr.findLast(x => x === 2); // 2
arr.findLastIndex(x => x === 2); // 3
```

## ES2024: Resizable ArrayBuffer

```typescript
const buffer = new ArrayBuffer(16, { maxByteLength: 1024 });

buffer.resizable; // true
buffer.maxByteLength; // 1024
buffer.resize(64); // grow
buffer.resize(8); // shrink
```

### Growable SharedArrayBuffer

```typescript
const shared = new SharedArrayBuffer(16, { maxByteLength: 1024 });
shared.growable; // true
shared.grow(64); // can only grow, not shrink
```

### TypedArray tracks resizable buffer

```typescript
const buffer = new ArrayBuffer(16, { maxByteLength: 64 });
const view = new Uint8Array(buffer);
view.length; // 16
buffer.resize(32);
view.length; // 32 (auto-tracks)
```

## ES2024: ArrayBuffer Transfer

```typescript
const buffer = new ArrayBuffer(16);
const arr = new Uint8Array(buffer);
arr[0] = 42;

const newBuffer = buffer.transfer(); // zero-copy transfer
buffer.detached; // true
newBuffer.byteLength; // 16

// Transfer with resize
const grown = buffer.transfer(64);

// Convert resizable to fixed
const fixed = resizable.transferToFixedLength();
```

## ES2025: Float16Array

```typescript
const f16 = new Float16Array(4);
const f16arr = Float16Array.of(1.5, 2.5, 3.5);

Float16Array.BYTES_PER_ELEMENT; // 2
// Range: ±65504 (max), ±6.1e-5 (min positive)
```

### DataView Float16

```typescript
const view = new DataView(buffer);
view.setFloat16(0, 3.14, true); // little-endian
view.getFloat16(0, true); // ≈3.140625
```

## ES2026: Uint8Array Base64

Not yet in Node.js v24.

### Base64

```typescript
const bytes = new Uint8Array([72, 101, 108, 108, 111]);

bytes.toBase64(); // "SGVsbG8="
bytes.toBase64({ alphabet: "base64url" }); // URL-safe
bytes.toBase64({ omitPadding: true }); // no trailing =

Uint8Array.fromBase64("SGVsbG8=");
Uint8Array.fromBase64("SGVsbG8", { alphabet: "base64url" });

// Write to existing buffer
const { read, written } = target.setFromBase64("SGVsbG8=");
```
101 changes: 101 additions & 0 deletions .claude/skills/wasm/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
name: wasm
description: |
Modern WebAssembly (Wasm) development expertise covering modern Wasm features
and optimization techniques. Use this skill when working with WebAssembly
modules or optimizing Wasm performance.
compatibility: WebAssembly v3.0 and later
---

# WebAssembly Development Skill

## WAT Syntax

Use **folded (S-expression) syntax** for readability:

```wat
;; Folded syntax (preferred)
(i32.add (local.get $x) (local.get $y))

;; Flat syntax (avoid)
local.get $x
local.get $y
i32.add
```

## WebAssembly Features

### Memory64 (64-bit Address Space)
- Memories and tables use `i64` as address type
- Expands addressable space from 4GB to 16 exabytes
- Syntax: `(memory i64 1)` instead of `(memory 1)`

### Multiple Memories
```wat
(module
(memory $main 1)
(memory $scratch 1))
```

### Tail Call Optimization
- Efficient recursion via `return_call` and `return_call_indirect`
- Prevents stack overflow for tail-recursive functions
```wat
(func $factorial (param $n i64) (param $acc i64) (result i64)
(if (result i64) (i64.eqz (local.get $n))
(then (local.get $acc))
(else (return_call $factorial
(i64.sub (local.get $n) (i64.const 1))
(i64.mul (local.get $n) (local.get $acc))))))
```

### Exception Handling
- Native try/catch/throw semantics
- Interoperates with JavaScript exceptions
```wat
(tag $error (param i32))
(func $may_throw
(throw $error (i32.const 42)))
```

### Relaxed SIMD
- Hardware-dependent SIMD optimizations beyond fixed-width 128-bit
- `i8x16.relaxed_swizzle`, `f32x4.relaxed_madd`, etc.

### WasmGC
- Native garbage-collected types: `struct`, `array`
- Instructions: `array.new`, `array.get`, `array.set`, `struct.new`, `struct.get`
- Reference types: `(ref $type)`, `(ref null $type)`

### externref
- Opaque reference to host (JS) objects
- Cannot be inspected or modified in Wasm, only passed around
- Used with js-string-builtins for efficient string handling

### js-string-builtins
- Import `"wasm:js-string"` for direct JS string operations
- Functions: `length`, `charCodeAt`, `fromCharCodeArray`, `intoCharCodeArray`
- Avoids costly JS↔Wasm boundary crossings for string processing

### SIMD Example
```wat
;; Process 16 bytes at a time
(v128.store (local.get $dst)
(i8x16.add
(v128.load (local.get $src1))
(v128.load (local.get $src2))))
```

## Toolchain (Binaryen)

| Task | Command |
|------|---------|
| Assemble WAT to Wasm | `wasm-as module.wat -o module.wasm` |
| Disassemble Wasm to WAT | `wasm-dis module.wasm -o module.wat` |
| Optimize for size | `wasm-opt -Oz in.wasm -o out.wasm` |
| Optimize for speed | `wasm-opt -O3 in.wasm -o out.wasm` |

## Resources

- [WebAssembly Specification](https://webassembly.github.io/spec/)
- [Binaryen](https://github.com/WebAssembly/binaryen)
57 changes: 57 additions & 0 deletions benchmark/count-utf8.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* eslint-disable no-console */
import { utf8CountJs, WASM_AVAILABLE } from "../src/utils/utf8.ts";
import { getWasmError, utf8CountWasm } from "../src/utils/utf8-wasm.ts";

// @ts-ignore
import Benchmark from "benchmark";

// description
console.log("utf8CountJs - pure JS implementation");
console.log("utf8CountWasm - WebAssembly implementation");

// Show wasm status
console.log("=".repeat(60));
console.log("WebAssembly Status:");
console.log(` WASM_AVAILABLE: ${WASM_AVAILABLE}`);
if (WASM_AVAILABLE) {
console.log(" js-string-builtins: enabled");
} else {
const error = getWasmError();
console.log(` Error: ${error?.message || "unknown"}`);
if (error?.message?.includes("js-string") || error?.message?.includes("builtin")) {
console.log("\n js-string-builtins is enabled by default in Node.js 24+ (V8 13.6+).");
console.log(" For older versions, run with:");
console.log(" node --experimental-wasm-imported-strings node_modules/.bin/ts-node benchmark/count-utf8.ts");
}
}
console.log("=".repeat(60));

for (const baseStr of ["A", "あ", "🌏"]) {
const dataSet = [10, 30, 50, 100, 200, 500, 1000].map((n) => {
return baseStr.repeat(n);
});

for (const str of dataSet) {
const byteLength = utf8CountJs(str);

console.log(`\n## string "${baseStr}" (strLength=${str.length}, byteLength=${byteLength})\n`);

const suite = new Benchmark.Suite();

suite.add("utf8CountJs", () => {
utf8CountJs(str);
});

if (WASM_AVAILABLE) {
suite.add("utf8CountWasm", () => {
utf8CountWasm(str);
});
}

suite.on("cycle", (event: any) => {
console.log(String(event.target));
});

suite.run();
}
}
36 changes: 34 additions & 2 deletions benchmark/decode-string.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
/* eslint-disable no-console */
import { utf8EncodeJs, utf8Count, utf8DecodeJs, utf8DecodeTD } from "../src/utils/utf8";
import { utf8EncodeJs, utf8Count, utf8DecodeJs, utf8DecodeTD, WASM_AVAILABLE } from "../src/utils/utf8.ts";
import { getWasmError, utf8DecodeWasm } from "../src/utils/utf8-wasm.ts";

// @ts-ignore
import Benchmark from "benchmark";

// description
console.log("utf8DecodeJs - pure JS implementation");
console.log("utf8DecodeTD - TextDecoder implementation");
console.log("utf8DecodeWasm - WebAssembly implementation");

// Show wasm status
console.log("=".repeat(60));
console.log("WebAssembly Status:");
console.log(` WASM_AVAILABLE: ${WASM_AVAILABLE}`);
if (WASM_AVAILABLE) {
console.log(" js-string-builtins: enabled");
} else {
const error = getWasmError();
console.log(` Error: ${error?.message || "unknown"}`);
if (error?.message?.includes("js-string") || error?.message?.includes("builtin")) {
console.log("\n js-string-builtins is enabled by default in Node.js 24+ (V8 13.6+).");
console.log(" For older versions, run with:");
console.log(" node --experimental-wasm-imported-strings node_modules/.bin/ts-node benchmark/decode-string.ts");
}
}
console.log("=".repeat(60));

for (const baseStr of ["A", "あ", "🌏"]) {
const dataSet = [10, 100, 500, 1_000].map((n) => {
return baseStr.repeat(n);
Expand All @@ -24,11 +47,20 @@ for (const baseStr of ["A", "あ", "🌏"]) {
}
});

suite.add("TextDecoder", () => {
suite.add("utf8DecodeTD", () => {
if (utf8DecodeTD(bytes, 0, byteLength) !== str) {
throw new Error("wrong result!");
}
});

if (WASM_AVAILABLE) {
suite.add("utf8DecodeWasm", () => {
if (utf8DecodeWasm(bytes, 0, byteLength) !== str) {
throw new Error("wrong result!");
}
});
}

suite.on("cycle", (event: any) => {
console.log(String(event.target));
});
Expand Down
34 changes: 32 additions & 2 deletions benchmark/encode-string.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
/* eslint-disable no-console */
import { utf8EncodeJs, utf8Count, utf8EncodeTE } from "../src/utils/utf8";
import { utf8EncodeJs, utf8Count, utf8EncodeTE, WASM_AVAILABLE } from "../src/utils/utf8.ts";
import { getWasmError, utf8EncodeWasm } from "../src/utils/utf8-wasm.ts";

// @ts-ignore
import Benchmark from "benchmark";

// description
console.log("utf8EncodeJs - pure JS implementation");
console.log("utf8EncodeTE - TextEncoder implementation");
console.log("utf8EncodeWasm - WebAssembly implementation");

// Show wasm status
console.log("=".repeat(60));
console.log("WebAssembly Status:");
console.log(` WASM_AVAILABLE: ${WASM_AVAILABLE}`);
if (WASM_AVAILABLE) {
console.log(" js-string-builtins: enabled");
} else {
const error = getWasmError();
console.log(` Error: ${error?.message || "unknown"}`);
if (error?.message?.includes("js-string") || error?.message?.includes("builtin")) {
console.log("\n js-string-builtins is enabled by default in Node.js 24+ (V8 13.6+).");
console.log(" For older versions, run with:");
console.log(" node --experimental-wasm-imported-strings node_modules/.bin/ts-node benchmark/encode-string.ts");
}
}
console.log("=".repeat(60));

for (const baseStr of ["A", "あ", "🌏"]) {
const dataSet = [10, 30, 50, 100].map((n) => {
return baseStr.repeat(n);
Expand All @@ -21,9 +44,16 @@ for (const baseStr of ["A", "あ", "🌏"]) {
utf8EncodeJs(str, buffer, 0);
});

suite.add("utf8DecodeTE", () => {
suite.add("utf8EncodeTE", () => {
utf8EncodeTE(str, buffer, 0);
});

if (WASM_AVAILABLE) {
suite.add("utf8EncodeWasm", () => {
utf8EncodeWasm(str, buffer, 0);
});
}

suite.on("cycle", (event: any) => {
console.log(String(event.target));
});
Expand Down
2 changes: 1 addition & 1 deletion benchmark/key-decoder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-console */
import { utf8EncodeJs, utf8Count, utf8DecodeJs } from "../src/utils/utf8";
import { utf8EncodeJs, utf8Count, utf8DecodeJs } from "../src/utils/utf8.ts";

// @ts-ignore
import Benchmark from "benchmark";
Expand Down
Loading
Loading