~bigbes/tarantool

tarantool-protobuf

ref: 2df86386f587e8b84867c9f144bd76a801c3a9d6 tarantool-protobuf/.beads/interactions.jsonl -rw-r--r-- 27.3 KiB
2df86386 — Eugene Blikh 2 months ago
beads: close drm (encode/decode B/op floor investigation)
ed383c92 — Eugene Blikh 2 months ago
codegen: inline proto2 extension writers/readers (qwt) + table.new(N,0) for packed lists (2sn)

qwt closes the proto2_basic.BenchPayload `min` bench's worst data point:
encode 1.89M → 3.30M msgs/s (+75%), decode 1.02M → 1.26M msgs/s (+23%).
Mechanism: collectExtsByExtendee groups all `extend Foo { ... }`
declarations by extendee FullName across input files; emitInlineEncode
emits one dedicated writer per extension instead of dispatching through
pb.codec.encode_field, and emitInlineDecode adds an elseif arm per
extension id straight into result._extensions[full_name]. Dynamic
extensions_list walk preserved for forward compat, gated on
#_elist > N so it pays one int compare when no runtime extension was
registered.

2sn pre-sizes packed-scalar repeated lists via table.new(N, 0) where
the count is recoverable from the LEN payload — exact (lim >> 2 or
lim >> 3) for fixed-width, upper bound (lim) for varint-packed. The
allocation is deferred into the `if wt == 2 then` branch so the
per-element fallback and non-packable types keep the bare-`{}`
alloc. Avoids u39's regression mode because the call cost is paid
once per repeated-field-first-occurrence, not per message decode.

Bench summary for hello.Person full mode: encode +3-5% across sizes,
decode +1-3% across sizes. Tests: 752/752 pure-Lua, 1043/1043 with
PB_ENABLE_C=1, JIT 37/37. PERF_LOG entry covers the rationale and
caveats.
578ed4c9 — Eugene Blikh 2 months ago
beads: log 21d closure and h8x creation
0fb62135 — Eugene Blikh 2 months ago
codegen: localize wire.* upvalues per generated message function

Capture each _encode/_decode body, scan for wire.<name> refs, and
rewrite refs that appear >=2 times to bare locals with a
"local X = wire.X" prelude. Single-use refs stay as wire.X — without
the threshold the prelude TGETS outweighed the in-body saving on
sparse small-message decode.

Measured (median-of-3, shapes bench full mode):
- scalar-heavy: enc +38.5%, dec +39.2%
- packed-int32x100: enc +51.1%, dec +38.2%
- map-strxi32-*: enc +5.5%, dec +6-7%
- nested/oneof/wkt: +1-5% (within ~5% variance band)
- bench.lua Person small sizes: neutral

closes kot
5de883e9 — Eugene Blikh 2 months ago
beads: close 4ql (ibuf encoder not viable) + memory note on FFI boundary tax

Hand-coded single-pass backpatched Person_encode_ibuf wins by 1.7x at
10-100B but loses 1.4-2.7x at 1KB-100KB. Crossover at ~26 emails:
per-field ffi.copy boundaries scale linearly while Person_encode pays
one bulk table.concat memcpy regardless of count. Three ibuf attempts
total (per-byte alloc, two-pass reserve, single-pass backpatch), same
root cause each time. Not revivable without LuaJIT FFI sinking or a
cdata-string API contract.
b624a6bf — Eugene Blikh 2 months ago
beads: close bgu (ffi.string decode disproven) + memory note on buffer-reuse trap

Two-round microbench shows ffi.string(scratch + off, len) loses 2-2.7x
to buf:sub even with a pre-allocated stable scratch cdata and ffi.copy
amortized over 26 strings. The per-call pointer-arith cdata crosses
the ffi.string frame and can't be sunk — same root cause as a6n.
48ef4180 — Eugene Blikh 2 months ago
beads: close a6n + memory note on ffi.cast cdata allocation

a6n
ee07048d — Eugene Blikh 2 months ago
beads: close 74c

bd-74c
d7b0364a — Eugene Blikh 2 months ago
beads: close ra6, 43t + drop deferred c0i dep from 43t

ra6 (generic C runtime codec): all 16 sub-tasks (3a-3l) plus rc8 strict-
decode parity bug closed. Umbrella issue closed as scope-complete.

43t (parity gate): closed after Justfile + bench.lua wiring. The c0i
dependency was removed first — c0i is deferred indefinitely per the
04c spike conclusion, so keeping it as a blocker would prevent 43t
from ever closing despite the parity work being complete.

Also added the c-runtime-parity-gate-43t persistent memory recording
the parity strategy and initial perf snapshot vs Lua-runtime.

bd-ra6, bd-43t
5cc94314 — Eugene Blikh 2 months ago
beads: close rc8
c91a5ee5 — Eugene Blikh 2 months ago
beads: close ra6 3i
7edbf973 — Eugene Blikh 2 months ago
c_runtime: unknown-fields capture + re-emission (ra6 3j)
b840992a — Eugene Blikh 2 months ago
c_runtime: oneof encode + decode dispatch (ra6 3g)

Encode resolves the active member per oneof group (last-non-nil wins in
declaration order via plan->oneofs[].member_indices), skips non-active
members, and force-emits the active branch so default values like
text="" still carry presence.

Decode clears sibling result-table entries after writing any field with
oneof_idx >= 0, mirroring codec.lua's oneof_siblings handling — wire
last-wins. Sibling-clear runs for both the scalar/string/enum/bytes arm
and the sub-message arm.

20 new tests in test/c_runtime_oneof_test.lua cover all three Result
branches across both codegen modes: byte-equal vs full.hello reference,
round-trip preservation of the active branch and absence of siblings,
default-value emission for active branches, and last-wins on both
encode (multi-branch input) and decode (multi-occurrence wire bytes).

Acceptance per bd-w3u
fa57c1e4 — Eugene Blikh 2 months ago
c_runtime: 64-bit cdata fidelity tests (ra6 3l)

Adds the c_int64.Wide fixture (one singular field per 64-bit kind)
plus an 18-test luatest group that round-trips each kind past 2^53
through the C runtime in both codegen modes. Confirms encode accepts
both LuaJIT int64_t/uint64_t cdata and Lua numbers, and decode
surfaces values >DBL_INT_MAX as cdata (matching msgpackffi /
net.box / box.tuple / built-in protobuf convention).

The C runtime already had the dispatch — to_int64_at / to_uint64_at
flow through luaL_toint64 / luaL_touint64 for cdata inputs, and
dec_push_one calls luaL_pushint64 / luaL_pushuint64 for every 64-bit
kind. This change pins the behavior under acceptance.

Closes tarantool-protobuf-awv (ra6 3l)
e875519e — Eugene Blikh 2 months ago
c_runtime: repeated + packed scalar encode/decode (ra6 3e)

Add repeated dispatch to the C-runtime encode/decode loop. Encode
side: encode_repeated_field walks Lua arrays via lua_objlen + per-
index rawgeti, dispatches on element kind. Packed numerics build
their payload in a stack-backed sub-buffer then emit `tag(LEN) +
varint(len) + body`; unpacked emit `tag + value` per element via
encode_one_field with force_emit=1 to bypass zero-suppression;
strings/bytes flow through the same path (never packable); repeated
messages reuse encode_submessage_field per element.

Decode side: per-field stack-slot cache (list_stack_idx[]) tied to
list_count[] avoids the per-element lua_getfield(result, name) round
trip the c-accel spike measured at 2x slower at 100KB. On first hit
for a repeated field we lua_createtable + write result[name] AND dup-
push the list onto the stack; subsequent hits lua_rawseti through the
cached absolute stack index. Lists stay valid across recursive sub-
message decodes because each child decode_body cleans up its own
scratch back to the caller's frame.

Packed/unpacked symmetry on read: a wt==LEN payload for any packable
scalar is decoded as a packed blob regardless of the schema's packed
flag, and a per-element-tagged stream is decoded element-by-element
even on a schema that defaults to packed — per proto3 reader rules.

New test/proto/c_repeated.proto fixture carries packed + explicit-
unpacked + repeated string/bytes + repeated message branches. The
encode and decode tests round-trip at 10/100/1000 elements per
branch. The two existing "skip repeated and map" marker tests
collapse to "skip map" — only map fields remain out of scope for
3e (bd-asz / 3h lands them next). 854 → 896 passing tests.

bd-jc9
6e7835a2 — Eugene Blikh 2 months ago
c_runtime: encode/decode singular sub-messages (ra6 3d)

Refactor encode_lua/decode_lua into encode_body/decode_body so the
field-walk loop is callable recursively, then dispatch the MESSAGE
kind into a per-side sub-handler. Repeated and map fields still skip
at the field-walk level — 3e (jc9) and 3h (asz) land them next.

encode_submessage_field force-establishes the parent enc_buf's
heap_idx via a no-op ebuf_grow before recursing. Without that the
final ebuf_reserve on the parent could land its new userdata above
sub-encode's leaked stack slots, making the closing lua_settop drop
the parent's heap.

decode_submessage_field bounds the inner read by temporarily
shrinking c->len to the sub-message end offset; the wire-prim
helpers already bounds-check against c->len, so a malformed inner
payload can't over-read into the outer message's bytes.

New fixture test/proto/c_nested.proto carries a 5-level singular
chain (L1->L2->L3->L4->L5) for the depth test. The two existing
"skip message" tests are renamed to "skip repeated and map" — sub-
messages now encode and decode end-to-end.

bd-hwe
82717b15 — Eugene Blikh 2 months ago
beads: sync mz6 closure to interactions.jsonl
5e8f5f60 — Eugene Blikh 2 months ago
c_runtime: encode singular scalars (ra6 3b)

New entry pb.c_runtime.encode(plan, msg) -> string. Covers int32/64,
uint32/64, sint32/64, bool, fixed32/64, sfixed32/64, float, double,
enum (number or by_name lookup), string, bytes. Repeated, map, and
message-typed fields are silently skipped — those land in 3d/3e.

Buffer is a 4KB stack scratch promoted to a lua_newuserdata on
overflow, so a mid-encode luaL_error doesn't leak: the userdata is
GC'd at the unwind point.

Proto3 zero-suppression mirrors mode=full byte-for-byte: empty strings,
zero ints/fixed/enum skipped, +0.0 double skipped while -0.0 is emitted
via type-pun u64 equality (matches the `1/v == -math.huge` guard on
the Lua side). Proto3-optional fields bypass suppression. Plans with
desc.encode overrides (WKT) are rejected here — bd-rmf scope.

Tests: 16 cases per codegen mode (full + runtime), covering the bd-y1n
acceptance (Person {name='x', age=42, balance=-7,
user_id=0xDEADBEEFCAFEBABEULL, weight_kg=3.14} byte-equal to mode=full)
plus per-kind sweep and heap-grow path at 8KB. Suite: 806/806 with
PB_ENABLE_C=1, 748/748 + 58 skipped without.

Closes bd-y1n
5c705557 — Eugene Blikh 3 months ago
c-accel: descriptor -> C plan compiler (bd-mq7)

First C source for the pb.c_runtime module. Compiles a finalized Lua
descriptor into an opaque pb_plan userdata, the foundation that
bd-ra6's encode/decode entry points will walk.

What the plan carries (per docs/specs/c_accel_strategy.md):

  * Per-field records: field_number, wire_type, kind, repeated/packed/
    optional flags, pre-encoded tag bytes (varint, up to 5 bytes),
    sub_plan_idx (1-based into the sub-plans table), enum_ref
    (luaL_ref for enum descriptor), oneof_idx back-pointer.
  * Map fields capture map_key_kind, map_value_kind, and the value's
    sub_plan_idx when the value is a message.
  * Oneofs as a parallel array of {name, member_indices[]}, with
    fields' oneof_idx pointing back to their group.
  * WKT override detection: when desc.encode/desc.decode are set, the
    plan flips has_override=1 and skips field-walk entirely.
  * Extension range hooks captured (proto2 scaffolding for bd-3i).
  * Field-name cache as a Lua table referenced via luaL_ref, so
    encode/decode can do lua_rawgeti instead of re-interning C strings.

Cycle handling: compile_plan stashes the new plan userdata on
desc.c_plan BEFORE recursing into sub-message fields. Person.friends
→ Person resolves to the same userdata; the test asserts identity.

Build entry: 'just build-c' compiles runtime/pb/c/c_runtime.c into
runtime/pb/c_runtime.dylib (or .so on Linux) via the local Makefile.
Module-h location auto-detected the same way bench/c_accel/Makefile
does it. Tarantool's LuaJIT-on-5.1 means we use lua_objlen (not
lua_rawlen) and provide a local abs_idx helper since lua_absindex
isn't available in the 5.1 compat layer.

Smoke test at test/c_runtime_plan_test.lua exercises both codegen
modes (full + runtime). 26 assertions cover:
  - module surface + ABI version + KIND/WIRE constants
  - Address: 4 fields, names, kinds, wire types, tag bytes, optional
  - Person: 14 fields, scalars/enum/message/map/repeated/packed
  - Sub-plan resolution + Person.friends self-reference cycle break
  - Idempotent compile (second call returns cached plan)
  - Result.outcome oneof: 3 members, oneof_idx back-pointers
  - WKT Timestamp: has_override=true, field-walk skipped

Full suite: 771/771 with PB_ENABLE_C=1 (745 existing + 26 new),
745+26 skipped without (silent fallback verified).

Justfile fix: 'just build-c' / 'clean-c' used $(MAKE) which Just
doesn't expand — switched to plain 'make'.

Unblocks bd-y1n (encode scalars), bd-mz6 (decode scalars),
bd-awv (64-bit cdata), bd-rmf (WKT passthrough). bd-mq7 closed.
3042384b — Eugene Blikh 3 months ago
c-accel: arch prereqs — compat contract, C-side strategy, build scaffolding

Three companion specs under docs/specs/ formalize the boundaries
established in docs/c-accel.md, unblocking bd-mq7 (descriptor → C
plan compiler):

* c_accel_compat.md (bd-47e) — pinpoints what must stay byte-equal
  between PB_ENABLE_C unset and =1: public surface, generated
  module wrappers, 64-bit cdata, WKT shapes, unknown fields,
  extensions, errors. Calls out the lazy-view exclusion.

* c_accel_strategy.md (bd-z7x) — pb_plan struct layout, field-name
  luaL_ref caching, 4 KB stack-backed pb_buf, cached per-field
  stack indices (the 2× win from spike Phase B), sub-buffer over
  backpatching, map/oneof/unknown handling.

* c_accel_build_packaging.md (bd-wky) — where the C module lives
  (runtime/pb/c/), how it builds, what the rockspec gains, the CI
  matrix shape.

Scaffolding that lands now:

* runtime/pb/init.lua — PB_ENABLE_C=1 opt-in pcall hook; the
  loaded module (or nil) is exposed as pb.c_runtime for
  introspection. Silent fallback when the module is absent.

* Justfile — `build-c` / `clean-c` recipes (stub erroring cleanly
  until bd-ra6 lands runtime/pb/c/), new lua_cpath constant,
  LUA_CPATH wired through `test` and `test-one`.

* .builds/{pure-lua,c-enabled}.yml — sourcehut CI manifests, one
  per activation mode (sourcehut has no matrix; parallel jobs go
  in separate files). ubuntu/noble images.

* .sourcehut/conformance.yml — outside .builds/ so it doesn't
  auto-submit; trigger manually with `hut builds submit` before
  releases.

* .gitignore — runtime/pb/c_runtime.{so,dylib} and runtime/pb/c/*.o.

745/745 tests pass with PB_ENABLE_C unset and PB_ENABLE_C=1
(silent fallback verified).

Closes bd-47e, bd-z7x, bd-wky. Unblocks bd-mq7.
Next