Skip to content

perf(binary): validate packed output with the wire path's validator - #1215

Merged
jlucaso1 merged 1 commit into
mainfrom
perf/binary-packed-revalidation
Aug 6, 2026
Merged

perf(binary): validate packed output with the wire path's validator#1215
jlucaso1 merged 1 commit into
mainfrom
perf/binary-packed-revalidation

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #1214. Review that one first; this diff is one line plus its comment.

Summary

read_packed unpacks hex and nibble into a stack buffer, then runs the result through std::str::from_utf8 before building the CompactString. Those bytes come from the two lookup tables a few lines above, whose entries are all ASCII, so the check can never fail. It is the only from_utf8 in the file validating data the decoder itself produced rather than data that came off the wire, and read_string a hundred lines up shows the contrast: that one validates frame bytes, where validating is mandatory.

This swaps the validator for the one that path already uses, rather than removing the check.

Measurement

Per decode, perf stat -r 5 over 2M iterations of a 30-byte ack, pinned to one core:

instructions wall time
baseline (std::str::from_utf8) 1587 88.1 ns
smoothutf8::from_utf8 1450 87.3 ns
from_utf8_unchecked (ceiling, prototype) 1401 85.1 ns

8.6% fewer instructions, capturing 74% of what removing the check entirely would buy. Native wall time does not move: the difference is inside the noise, and even the unchecked ceiling only shows 2%. The decode is latency-bound rather than throughput-bound, so this lands on instruction count, which is what CI measures and what weighs most on the wasm target where the reporting consumer runs.

The fanout shows -4.7% instructions and no time change, as expected: its packed values are a smaller share of the work.

What this does not cover. Native only. The 8.1% figure that motivated the proposal is wasm32, and I have no wasm measurement to confirm it there.

Design

The check stays. from_utf8_unchecked would take the remaining 3%, and this repo has precedent for exactly that pattern in #1187, which removed the same kind of self-revalidation in AddressBuf::as_str behind a SAFETY: comment. The difference is what it buys: there the invariant was worth the trade, here the remaining slice does not show up in native wall time at all, and decoder.rs currently has zero unsafe. Keeping a real check also means a future edit to either table cannot quietly produce non-ASCII.

One thing worth recording, from confirming the proposal's question about the terminating zero: a nibble of 15 in the middle of a packed value decodes to a NUL that survives into the string. I verified this by hand-building a frame, and the decoded tag comes back as [49, 53, 0, 50]. Neither validator rejects it, because NUL is valid ASCII, so this is pre-existing behavior rather than something this change introduces. Flagging it because a validator sitting right there invites the assumption that it would be caught.

Validation

cargo fmt --all
cargo nextest run --profile ci -p wacore-binary                  # 132 passed
cargo nextest run --profile ci -p wacore-binary --features simd  # 132 passed
cargo clippy -p wacore-binary --all-targets --all-features -- -D warnings
cargo bench -p wacore-binary --bench binary_benchmark

packed_equivalence from #1214 already covers both packed kinds across the SIMD boundary, odd lengths that exercise the half-byte trim, and invalid-nibble rejection, and it passes unedited. Full matrix left to CI.

Follows #1214, which touched the same read_packed for a different reason.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a1923e57-a8dc-4e0f-864b-6f913f17eebf

📥 Commits

Reviewing files that changed from the base of the PR and between 8756097 and 54c6fa5.

📒 Files selected for processing (1)
  • wacore/binary/src/decoder.rs

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved validation of decoded packed data to better handle UTF-8 text.
    • Preserved validation expectations for generated output while improving decoding reliability.

Walkthrough

read_packed now validates decoded packed output with smoothutf8 and retains the expectation that generated output is valid UTF-8.

Changes

Packed decoding

Layer / File(s) Summary
Use smoothutf8 validation
wacore/binary/src/decoder.rs
read_packed replaces standard UTF-8 validation with smoothutf8 and preserves the invalid-output panic expectation.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Possibly related PRs

Suggested labels: performance

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: using the wire path's validator for packed output.
Description check ✅ Passed The description is directly related to the changeset and provides clear rationale, measurements, design details, and validation results.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/binary-packed-revalidation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown

Greptile Summary

The PR replaces the standard UTF-8 validator for decoder-generated packed strings with the validator already used by the wire path.

  • Retains UTF-8 validation and the existing panic message.
  • Expands the comment explaining why packed output is guaranteed to be ASCII and why validation remains.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
wacore/binary/src/decoder.rs Reuses smoothutf8 for packed decoder output without changing reachable decoding semantics.

Reviews (2): Last reviewed commit: "perf(binary): validate packed output wit..." | Re-trigger Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Auto-approved: Localized performance-only swap of the UTF-8 validator on internally generated ASCII bytes, retaining the check and preserving behavior; no public, data, or operational tradeoff remains.

Re-trigger cubic

Base automatically changed from bench/binary-small-stanza-decode to main August 6, 2026 20:30
read_packed unpacks into a stack buffer and then runs the bytes through
std::str::from_utf8 before building the CompactString. Those bytes came
from the two lookup tables a few lines above, whose entries are all
ASCII, so the check can never fail. It is the only from_utf8 in the file
validating data the decoder produced rather than data off the wire.

smoothutf8 is what read_string already uses for wire bytes, for the short
strings that dominate here too. Swapping it in cuts 8.6% of the ack's
instructions (1587 to 1450 per decode, perf stat over 2M). Native wall
time does not move, so this lands on instruction count, which is what CI
measures and what weighs most on wasm.

The check is kept rather than removed. from_utf8_unchecked would take
another 3% and the repo has precedent for it, but that trades a verified
invariant for a documented one to buy a slice of a figure that does not
show up in native time.

Worth recording, since it surprised me: a nibble of 15 in the middle of a
packed value decodes to a NUL that survives into the string. Neither
validator rejects it, because NUL is valid ASCII, so this is unchanged
behavior rather than something the swap introduces.
@jlucaso1
jlucaso1 force-pushed the perf/binary-packed-revalidation branch from e2095b6 to 54c6fa5 Compare August 6, 2026 20:30
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@jlucaso1
jlucaso1 merged commit 3237c72 into main Aug 6, 2026
2 of 3 checks passed
@jlucaso1
jlucaso1 deleted the perf/binary-packed-revalidation branch August 6, 2026 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant