WriteBytes fuses: one store flushes the scratch, the payload goes in whole, one load reloads the tail - #77
Merged
Merged
Conversation
…whole, one load reloads the tail WriteBytes pushed the block's head and tail through the packer a byte at a time -- WriteBits per byte, spill test and all -- and that loop priced the function out of generated callers: refused at cost 345 against threshold 225 for a chat-shaped string body, leaving every string field an out-of-line call into the 213-instruction generic. serialize.c fused its body first (serialize.c #26, under the Implementation Law's both-sides-were-wrong clause); this is the reference adopting the same shape in its own idiom. The fused body: the partial scratch word goes to the buffer as one 8-byte store (low bytes are the bytes already written, high bytes zero, overwritten by the payload), the whole payload lands as one SERIALIZE_BULK_COPY at the byte cursor, and the trailing partial word is loaded back into the scratch -- masked to its tail bits -- so later writes pack into it exactly as before. The tail load touches only the word the final flush is already obliged to store. No head loop, no tail loop, no per-byte spill tests; the byte-swapped scratch makes the edge words single moves on either endianness, and the s390x golden leg pins that. SERIALIZE_BULK_COPY is new: __builtin_memcpy on clang/GCC, plain memcpy elsewhere, the variable-length twin of serialize.c's macro pair. It keeps the body's pricing independent of any platform's fortify capture (glibc honors _FORTIFY_SOURCE in C++; Darwin's capture is C-only, so the C++ refusal measured here was entirely the byte loops). The always-inlined packer paths keep their plain memcpy spelling. Wire bytes are identical: an exhaustive differential against main -- every scratch occupancy (0..64 pre-bits) x every payload length (0..40), chained second block, mid-stream bit fields, poisoned buffers -- is byte-for-byte the same over all 2665 cases, and every case round-trips, also clean under asan+ubsan and with NDEBUG. Check model unchanged: release write stays checkless; the two new asserts (scratch tracks the cursor, byte alignment) are debug-only invariants. Gates: ctest Debug + Release + asan/ubsan legs, the c++03 consumer legs, tools/conformance (89 checks), and serialize.c's make diff against this checkout (core / wide / compressed float all IDENTICAL). Evidence: the chat-shaped repro at -O2 -DNDEBUG drops from 314 instructions across three functions (WriteBytes out of line) to 40 in one fully inlined writer; the inliner accepts at cost 90 where it refused at 345. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The C++ half of the string-body gap:
BitWriter::WriteBytespushed the block's head and tail through the packer a byte at a time, and that loop priced the function out of generated callers. serialize.c fused its body first (mas-bandwidth/serialize.c#26, merged); this is the reference adopting the same shape in its own idiom, per the Implementation Law's both-sides-were-wrong clause. The wire format is unchanged — proven below, not asserted.The mechanism
The old body wrote head and tail bytes with
WriteBits( data[i], 8 )— spill test and all, per byte. For a chat-shaped string body the inliner refused the result at cost 345 against threshold 225, so every generated string and byte-array field paid an out-of-line call into the 213-instruction generic.The fused body:
SERIALIZE_BULK_COPYlands the whole payload at the byte cursor,The byte-swapped scratch is what makes the edge words single moves on either endianness; the s390x golden leg pins that.
SERIALIZE_BULK_COPYis the variable-length twin of serialize.c's macro pair:__builtin_memcpyon clang/GCC, plainmemcpyelsewhere. It keeps the body's pricing independent of any platform's fortify capture (glibc honors_FORTIFY_SOURCEin C++ too; Darwin's capture turns out to be C-only, so the C++ refusal measured here was entirely the byte loops — stated honestly in the macro comment). The always-inlined packer paths keep their plainmemcpyspelling.Wire unchanged — proof
-DNDEBUG.serialize.c'smake diffagainst this checkout: core / wide / compressed float all IDENTICAL.test_golden_wire_format, golden zero-length/unaligned vectors,test_unaligned_writer): green in Debug, Release, and asan+ubsan builds.tools/conformance: 89 checks against STANDARD.md, 0 failures.Check model unchanged
Release write stays checkless. The two new asserts (byte alignment — already asserted — plus scratch-tracks-the-cursor) are debug-only invariants; writing after a mid-stream
FlushBitswas never supported by the old shape either.Evidence
Chat-shaped repro (two bit fields + string field, 11-byte payload, Apple clang,
-O2 -DNDEBUG):WriteBytesinserialize_string_internal<WriteStream>WriteBytesout of line)Per the repo's own caution (#27's isolated win did not survive composition): the composed claim belongs to schema's four-language bench, where this write body is the runtime dependency of the C++ side of the string rows.
🤖 Generated with Claude Code