From 847ea5bbdf896d2c88840df38d8fd2716143ee60 Mon Sep 17 00:00:00 2001 From: Jerry Belich Date: Wed, 9 Sep 2026 22:47:41 -0700 Subject: [PATCH 1/2] make the container hash table a total order container_hash_t compared on the hash alone, and binary_emitter sorts that table with std::sort, which is not stable. Two entries sharing a hash could come out in either order depending on the standard library, so the same story compiled to different bytes under libstdc++ than under libc++ - which makes comparing output between builds useless as a correctness check. It is not only cosmetic. story_impl.cpp's upper_bound() returns the last entry whose key is <= the target, so when entries share a hash the runtime resolves the path to whichever one the sort happened to leave last. The ties are common and are not hash collisions between different paths: build_container_hash_map() recurses into indexed children carrying the parent's name unchanged, so one path string is emitted once per indexed child at a different offset. One story measured here has 164 entries under 128 distinct hashes, and all 26 collisions are a repeated path. Tie-breaking on the offset makes the order total, so every toolchain agrees. key() still returns the hash alone, because the runtime's binary search looks up by hash and must keep matching every entry in a run. Whether the highest-offset entry is the RIGHT container for a repeated path is a separate question this does not answer; it only makes the answer the same everywhere. Co-Authored-By: Claude Opus 5 --- shared/private/header.h | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/shared/private/header.h b/shared/private/header.h index 198b989c..fd7c25ef 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -71,7 +71,33 @@ struct container_hash_t { uint32_t key() const { return _hash; } - bool operator<(const container_hash_t& other) const { return _hash < other._hash; } + /* Ordered by hash, then by offset. + * + * The offset is not decoration: without it this is a partial order, and + * binary_emitter sorts the table with std::sort, which is not stable. Any two + * entries sharing a hash could then come out in either order, and which one you + * got depended on the standard library - the same story compiled to different + * bytes under libstdc++ than under libc++, which makes byte comparison useless + * as a check between builds. + * + * It is not only cosmetic. story_impl.cpp's upper_bound() returns the LAST entry + * whose key is <= the target, so when entries share a hash the runtime resolves + * the path to whichever one the sort happened to leave last. Ties are common: + * paths are not unique here, because build_container_hash_map() recurses into + * indexed children carrying the parent's name unchanged, so one path string is + * emitted once per indexed child at a different offset. One test story has 164 + * entries under 128 distinct hashes, and every one of those 26 collisions is a + * repeated path rather than two different paths colliding. + * + * Tie-breaking on the offset makes the order total, so every toolchain agrees + * and the resolved container is a property of the story rather than of the + * compiler that built it. key() deliberately still returns the hash alone - the + * runtime's binary search looks up by hash and must keep matching every entry in + * a run. */ + bool operator<(const container_hash_t& other) const + { + return _hash != other._hash ? _hash < other._hash : _offset < other._offset; + } }; // One entry in the container map. Used to work out which container a story location is in. From 33d0e572bedbfcd6d6c359d3c0fef6d46aaca9e3 Mon Sep 17 00:00:00 2001 From: Jerry Belich Date: Thu, 10 Sep 2026 09:42:29 -0700 Subject: [PATCH 2/2] style: satisfy the 100 column limit for operator< The body fits on one line under the project's .clang-format, so the format job wants it there. Produced with clang-format rather than by hand; no behaviour change. Co-Authored-By: Claude Opus 5 --- shared/private/header.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shared/private/header.h b/shared/private/header.h index fd7c25ef..e2cf4524 100644 --- a/shared/private/header.h +++ b/shared/private/header.h @@ -95,9 +95,7 @@ struct container_hash_t { * runtime's binary search looks up by hash and must keep matching every entry in * a run. */ bool operator<(const container_hash_t& other) const - { - return _hash != other._hash ? _hash < other._hash : _offset < other._offset; - } + { return _hash != other._hash ? _hash < other._hash : _offset < other._offset; } }; // One entry in the container map. Used to work out which container a story location is in.