@@ 84,6 84,81 @@ macOS arm64.
<!-- Append new entries below this line. Newest at the top. -->
+## 2026-05-18 — gcy: inline nested-message decode at the call site (REVERTED)
+
+**Task:** [tarantool-protobuf-gcy] Decoder: inline nested-message decode at
+the call site. Profile flagged `result.address = M.Address_decode(payload)`
+at hello_pb.lua:1031 as a 100% interpreter bail in the `vl` trace; thesis
+was that replacing the call with the inlined Address decode body would
+eliminate the bail and lift Person decode by 20–40%.
+
+**Change attempted:** In `cmd/protoc-gen-tarantool/internal/gen/inline.go`,
+added `inlineCandidateForDecode` (singular, non-group, non-WKT message
+fields whose target has no further message subfields) and
+`emitInlineNestedDecode`, which emits Address's decode loop inline in
+Person_decode using a `do/end` scope to shadow the outer `result`. The
+length prefix was read in-place (no `wire.decode_len` substring alloc).
+
+**Tests:** 745/745 pass. JIT trace gate: 37/37 (after a flaky first-run
+0/37 caused by the documented macOS arm64 mcode alloc issue).
+
+**Bench (hello.Person — full, msgs/s, median of 3 runs):**
+
+| size | dir | h8v baseline | after gcy | Δ |
+|-------|-----|--------------|-----------|----------|
+| 1KB | enc | 302,117 | 292,680 | **-3.0%** |
+| 1KB | dec | 110,115 | 104,182 | **-5.3%** |
+| 10KB | enc | 64,052 | 59,543 | **-6.4%** |
+| 10KB | dec | 15,086 | 14,113 | **-6.6%** |
+| 100KB | enc | 6,487 | 6,661 | +2.7% |
+| 100KB | dec | 1,519 | 1,396 | **-8.1%** |
+
+**Outcome: reverted.** Tests passed but the change is a net regression at
+every size that exercises the inlined nested decode (Person 1KB/10KB
+have Address embedded; 100KB grows the Address body but is dominated by
+emails). Encode also regressed because Person_encode's trace had to
+account for a larger Person_decode (shared mcode arena / instruction
+cache pressure on macOS arm64, or LuaJIT abandoning some inlining of
+Person_encode under the new pressure).
+
+**Why the profile claim didn't translate:**
+
+1. `vl` (and `jit.p` count) reported `Address_decode` as 100% Interpreted
+ for the *parent line*, but in the live benchmark LuaJIT was already
+ inlining the small Address_decode body into Person_decode's trace
+ when entering. The "bail" was a tooling artifact of how
+ `jit.attach('trace')` attributes side traces, not a sustained
+ interpreter fallback. The benchmark numbers refute the trace
+ interpretation.
+2. Person_decode trace went from N stops to **22 stops** after the
+ inline. Larger root traces compile more slowly, are more sensitive
+ to side-trace stitching limits, and produce more mcode — exactly
+ the failure mode CLAUDE.md's "Keep hot wire helpers small" warning
+ describes, applied at the call-site instead of the helper.
+3. The inlined-body's `do/end` scope with shadowed `result` may
+ introduce extra upvalue references that LuaJIT 2.1 doesn't optimize
+ as well as a plain function call into a trace it has already
+ inlined.
+
+**What would actually help here:** the *real* decode bottleneck on
+hello.Person (per profile recap) is still `decode_string` (utf8
+validation = ~16% of decode time), `decode_tag` (21%), and
+`list[#list+1] = val` at the email append loop (6%). Those are the next
+targets — see entries on **6bb** (skip_utf8_validation), **4kj**
+(generated tag/length fast path), **cch** (local counter for repeated
+append).
+
+**Beads:** issue closed with `--reason` referencing this entry; not
+reopened. Inlining nested decode bodies can come back if a workload
+shows that the nested call site is *the* hot trace boundary, but the
+candidate restriction + bench coupling needs to be rethought first
+(measure each call-site in isolation, not just at the trace level).
+
+**Commit:** none — change reverted before commit. PERF_LOG entry is the
+only artifact.
+
+---
+
## 2026-05-18 — h8v: inline 1-byte varint length prefix at codegen sites
**Task:** [tarantool-protobuf-h8v] Encoder: codegen-time inline FFI writes (mode=full)