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