sx: step 13 — buffer primitives + buffer-based inspect

Added short aliases make-buffer / buffer? / buffer-append! / buffer->string /
buffer-length on both OCaml and JS hosts, sharing the existing StringBuffer
value type. buffer-append! auto-coerces non-strings via inspect.

Rewrote the OCaml host inspect function to walk a single shared Buffer.t
instead of allocating O(n) intermediate strings via String.concat at every
recursion level. inspect underlies sx-serialize and error-path formatting,
so this benefits the tightest serialization paths.

Median improvements (bin/bench_inspect.exe, best-of-3 of 9-run min):
  tree-d8 (75KB):    5.31ms -> 1.30ms  (-76%)
  tree-d10 (679KB): 81.89ms -> 16.02ms (-80%)
  dict-1000:         0.80ms -> 0.31ms  (-61%)
  list-2000:         0.74ms -> 0.33ms  (-55%)

Tests: OCaml 4545 -> 4550. JS 2591 -> 2596. Zero regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 02:16:59 +00:00
parent c48911e591
commit 0e022ab670
9 changed files with 303 additions and 67 deletions

View File

@@ -214,6 +214,29 @@ Add `make-buffer`, `buffer-append!`, `buffer->string` primitives. Eliminates the
`(str a b c d ...)` quadratic allocation pattern in serializers and renderers.
Wire into `sx_primitives.ml` and the JS platform.
**Outcome:** Short aliases `make-buffer`/`buffer?`/`buffer-append!`/`buffer->string`/
`buffer-length` added on both hosts, sharing the existing `StringBuffer` value type.
`buffer-append!` accepts any value (auto-coerces non-strings via inspect), unlike
`string-buffer-append!` which is strict. The hot path converted was the OCaml
host-internal `inspect` function in `sx_types.ml`: rewrote from `(... ^ String.concat
" " (List.map inspect items) ^ ...)` (which allocates O(n) intermediate strings per
recursion level) to a single shared `Buffer.t` accumulator (`inspect_into buf v`
walks the value tree appending into one buffer). `inspect` is called by
`sx-serialize` on both spec and host paths, plus error-path formatting.
Median improvements (`bin/bench_inspect.exe`, best of 3 runs of 9-run min):
| Benchmark | Baseline (best min) | Buffer (best min) | Change |
|-------------------|--------------------:|------------------:|-------:|
| tree-d8 (75KB) | 5.31ms | 1.30ms | -76% |
| tree-d10 (679KB) | 81.89ms | 16.02ms | -80% |
| dict-1000 | 0.80ms | 0.31ms | -61% |
| list-2000 | 0.74ms | 0.33ms | -55% |
5 new tests in `spec/tests/test-string-buffer.sx` covering the new aliases (incl
non-string coercion and interop with the existing `string-buffer-*` API).
OCaml: 4545 → 4550. JS: 2591 → 2596. Zero regressions.
### Step 14: Inline common primitives in JIT
`hosts/ocaml/lib/sx_vm.ml`: add `OP_ADD`, `OP_SUB`, `OP_EQ`, `OP_APPEND` specialised
@@ -238,7 +261,7 @@ these when operands are known numbers/lists.
| 10 — compiler + as converter registry | [x] | d22361e4 |
| 11 — plugin migration + worker | [x] | 6328b810 |
| 12 — frame records | [x] | a66c0f66 (fib -66%, loop -69%, reduce -86% via prim_call fast path) |
| 13 — buffer primitive | [ ] | |
| 13 — buffer primitive | [x] | (pending) (inspect rewrite: tree-d10 -80%, tree-d8 -76%, dict-1000 -61%, list-2000 -55%) |
| 14 — inline primitives JIT | [ ] | — |
---