bench: record u39 (table.new pre-sized result) post-mortem Attempted emitting table_new(0, N) for decode result tables. Tests passed, JIT passed, but small-payload decode (10B, 100B) regressed 15-18% because the table.new upvalue-call cost exceeds any rehash savings at that scale, and the small case never reaches the first rehash anyway. Large-payload decode flat. Confirms drm's claim that alloc shape is not the bottleneck on the bench corpus. Code change reverted; only the writeup lands. beads-tarantool-protobuf-u39
codegen: local counter per repeated field on decode Replaces `list[#list + 1] = val` with `_n_<fname> = _n_<fname> + 1; list[_n_<fname>] = val` in every generated M.X_decode repeated-field append site. One counter local per repeated non-map field, declared at function entry. Counters survive across loop iterations so out-of-order wire entries for the same field continue past the existing length without re-scanning. Profile attributed 6.2% of hello.Person 1KB decode to the `#list + 1` re-traversal (26-email Person paid 26 list scans per decode). Bench (Person full decode, msgs/s, median-of-3 vs post-4kj): 1KB +5.0%, 10KB +5.0%, 100KB +7.1%. Encode flat. Tests 745/745. JIT 37/37, 0 bridges. beads-tarantool-protobuf-cch
codegen: inline 1-byte tag fast path at decode call sites Hoists wire.decode_tag's 1-byte fast path into every generated M.X_decode while-loop, falling back to the helper for multi-byte tags (field ids > 15). The 1-byte case covers every protobuf field with id 1..15 and is the dominant decode dispatch in real payloads. Header now localizes string.byte, bit.band, and bit.rshift so the inlined ops compile to straight local calls. Bench (Person full decode, msgs/s, median-of-3 vs proper post-h8v 3-run baseline): 10B +14.7%, 100B +8.9%, 1KB +7.5%, 10KB +7.0%, 100KB +8.5%. Full encode is flat to small (-0.1% to -2.6%) at large sizes, plausibly from header-upvalue layout. JIT trace gate: 37/37, all bridges still 0. Tests: 745/745. Also documented in bench/PERF_LOG.md, including the methodology note that h8v's earlier numbers used single-run baselines and are therefore ~3-5% optimistic; medians-of-3 are the standard now. beads-tarantool-protobuf-4kj
bench: record gcy (inline nested-message decode) post-mortem Implemented and benchmarked the inline-nested-decode plan from tarantool-protobuf-gcy. Test suite and JIT gate both pass, but median-of-3 bench shows 3-8% regressions on Person 1KB/10KB/100KB encode AND decode. Profile's "100% interpreter bail at Address_decode call" turned out to be a vl trace-attribution artifact; LuaJIT was already handling the call well. Lesson recorded in bench/PERF_LOG.md so the next person who reads the profile entry knows the obvious-looking inline transformation does not deliver here. Code change reverted; only the writeup lands. beads-tarantool-protobuf-gcy
codegen: inline 1-byte varint length prefix at every LEN emit site Eliminates the wire.encode_varint(#body) call + dispatch for every length-delimited field in mode=full codegen. Profile flagged the out[n] = wire.encode_varint(#_b) line as ~33% of hello.Person 1KB encode time, with another ~17% in encode_varint dispatch — together ~50% of encode time. Lifting the 1-byte fast path (the dominant case for proto strings and small message bodies) to the call site removes the function frame entirely for lengths < 128. Applied at every LEN emit site: singular/repeated message body, singular/repeated string|bytes, packed scalar bundle, packed enum bundle, map entry. Map value pieces (emitMapPiece message branch) left as-is — they sit inside a single slot assignment that would require a deeper restructure, and maps are not on the current hot benchmark. Results (hello.Person full encode, msgs/s): 10B +6.9%, 100B +7.9%, 1KB +25.7%, 10KB +48.1%, 100KB +31.9%. Decode flat (unchanged path). Runtime mode flat (descriptor dispatch still calls encode_varint). JIT trace gate: 37/37. Test suite: 745/745. Bench history saved to bench/PERF_LOG.md with full numbers and the workflow this iteration follows. beads-tarantool-protobuf-h8v