Skip to content

refactor: concentrate bulk-decode unsafe in a Utf8Writer - #37

Merged
bonega merged 1 commit into
masterfrom
refactor/utf8writer
Jul 4, 2026
Merged

refactor: concentrate bulk-decode unsafe in a Utf8Writer#37
bonega merged 1 commit into
masterfrom
refactor/utf8writer

Conversation

@bonega

@bonega bonega commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Wraps the bulk decoder's hand-written unsafe in a Utf8Writer so the decode helpers become safe code and the overshoot invariant lives in one place.

Changes

  • Utf8Writer owns the write cursor behind three primitives — push_entry (the branchless 4-byte overshoot store), push_ascii_word (word-at-a-time ASCII copy), finish. Each documents the slack the caller must guarantee.
  • for_input_len computes the len * 3 + 1 output capacity, so a wrong size is unrepresentable.
  • Debug asserts on every write turn the far-away capacity argument into a per-write check that Miri and the fuzzers exercise.
  • Drop the transmute in the aligned-ASCII path for to_ne_bytes (memory-order passthrough; to_le/to_be would break big-endian).

Not changed

Behavior and codegen. The hot-loop assembly is instruction-identical to master.

Verification

  • Differential fuzzers (decode_byte, invariant, validate) — no findings.
  • Miri under Stacked and Tree Borrows, strict provenance, on little-endian and big-endian s390x.
  • New tests/writer_stress.rs: a bounded, deterministic differential sweep of the writer (prefix/aligned/suffix + exact-capacity tail), cheap enough for Miri — libFuzzer targets can't run under Miri, so this is what exercises the unsafe there.
  • New miri CI job (.#miri nightly dev shell in flake.nix) gates the above on every PR.

Replace the raw `&mut *mut u8` cursor threaded through the decode
helpers with a `Utf8Writer` that owns the overshoot invariant behind
`push_entry`/`push_ascii_word`/`finish`, leaving the decode functions as
safe code. `Utf8Writer::for_input_len` computes the `len * 3 + 1` output
capacity so callers can't pass a wrong size, and each write `debug_assert`s
its slack, so Miri and the fuzzers trip on an overrun at the write site.

Drop the `transmute::<&usize, &[u8; 8]>` in the word-at-a-time path for a
`to_ne_bytes` memory-order passthrough (no unsafe, `to_le`/`to_be` would
break big-endian).

No behavior or codegen change: the hot-loop assembly is instruction-
identical to master, confirmed by the differential fuzzers and Miri under
Stacked + Tree Borrows on little- and big-endian (s390x).

Add `tests/writer_stress.rs`, a bounded Miri-runnable differential sweep of
the writer, and a `miri` CI job (via a `.#miri` nightly dev shell) gating it.
@bonega
bonega enabled auto-merge (squash) July 4, 2026 10:36
@bonega
bonega merged commit 7081230 into master Jul 4, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant