~bigbes/tarantool

tarantool-protobuf

ref: b09c7213874b18bd0f8d1c4d60d3ec8253f1be6d tarantool-protobuf/Justfile -rw-r--r-- 13.1 KiB
5dd21dea — Eugene Blikh 2 months ago
c_runtime: parity gate — test-c / test-all / bench-c (43t)

Adds the C-acceleration parity gate that bd-43t mandates: every test
asserts against a reference output (golden bytes, txtpb, conformance
result), so passing the same suite under both `PB_ENABLE_C=1` and the
default Lua codec proves Lua ≡ C by transitivity. No separate diff
harness needed.

Justfile recipes:
  - `test-c`  — same luatest suite with PB_ENABLE_C=1 (1043 tests;
                unlocks the c_runtime_* groups via build-c)
  - `test-all` — `test` + `test-c` (parity gate)
  - `bench-c` — bench.lua under PB_ENABLE_C=1; runtime column is
                relabelled `c-runtime`. Also tidies build-c — the stale
                "directory doesn't exist yet" branch goes away now that
                ra6 has landed.

bench.lua: extends package.cpath so `require('pb.c_runtime')` finds
runtime/pb/c_runtime.{so,dylib} when invoked outside the Justfile.
Detects `PB_ENABLE_C=1`, renames `runtime` → `c-runtime` in the
output, and refuses --baseline / --compare (alloc shape differs
between codecs by design — would noise the gate).

README: documents the parity strategy and lists `test-all`,
`conformance-c`, `bench-c` as the dual-codec entry points.

Scope notes: c-generated column dropped (c0i is deferred per
docs/c-accel.md); starwing column already lives in
bench/starwing_bench.lua.

bd-43t
1eb062c1 — Eugene Blikh 2 months ago
c_runtime: wire encode/decode dispatch + conformance-c harness

pb.encode / pb.decode lazy-compile desc.c_plan on first call and route
to pb.c_runtime.encode/decode when PB_ENABLE_C=1 loaded the module.
Eager compile at finalize_message time fails on codegen's
forward-declared descriptors — sub-messages don't have .fields yet —
so compilation is deferred until first encode/decode, by which time
the whole module table is populated and sub-plan chase resolves.

Full-mode codegen wrappers (M.<Type>_{encode,decode}) gain the same
lazy-compile prologue, bypassing the inline body when the C runtime
is loaded. Runtime-mode wrappers already call pb.encode and pick up
dispatch centrally.

Harness side: the conformance Docker image now installs tarantool-dev
+ build-essential so the C runtime can be built in-container; a new
`just conformance-c` recipe builds runtime/pb/c_runtime.so inside the
container, runs the suite with PB_ENABLE_C=1, then cleans the .so to
keep the bind mount free of foreign-platform binaries. The runner
script also pre-populates package.loaded.pb (avoids
.rocks/lib/tarantool/pb.so from starwing lua-protobuf masking ours)
and orders package.cpath by jit.os so mixed .dylib/.so trees from
host/container interleavings don't cross-load.

Verified:
- just test                 → 748 pass, 291 C-conditional skipped
- PB_ENABLE_C=1 just test   → 1026/1039 pass; 13 fails are strict-decode
                              gaps in the C decoder (bd-rc8)
- just conformance-c        → 2729/2806 binary suite pass; 77 unexpected
                              failures match the same gap categories
                              (illegal wire-type 6/7, field-num 0/over,
                              overlong tag varint, UTF-8 rejection,
                              message merge for oneof/repeated)

Strict-decode parity tracked in bd-rc8; this commit closes the wiring
half of bd-43t (conformance gate for C paths).
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.
07eef11c — Eugene Blikh 3 months ago
bench: Go cross-runtime comparison harness

Adds bench/go/ — single-threaded Go benchmarks against the same proto
schemas and payload sizes as bench/bench.lua, run against
google.golang.org/protobuf v1.36 (reflective apiv2) and
planetscale/vtprotobuf v0.6 (codegen marshalers). Wired through
`just gen-go` + `just bench-go`. *.pb.go is gitignored repo-wide so
the generated outputs are regenerated locally — not committed.

bench/COMPARISON.md documents the full table per fixture / size / op:
MB/s for Lua full + Lua runtime + apiv2 + vtproto, alloc bytes/op and
allocs/op side-by-side, and ratios. proto2 BenchPayload vtproto cases
are intentionally skipped — MarshalVT drops proto2 extensions and
would understate bytes vs apiv2 / Lua.
4fbdb65d — Eugene Blikh 3 months ago
codegen: proto2 baseline — required, optional, custom defaults

Lifts the proto3-only syntax gate in the plugin and threads three
new field-descriptor attributes through codegen and the codec:

  * required=true   — fields declared with the proto2 `required` keyword.
                      Inline codegen and the runtime codec both error when
                      a required field is missing on encode (vs the silent
                      elide that proto3 implicit-presence fields get).
  * optional=true   — already wired for proto3 explicit `optional`; in
                      proto2 every singular field carries it via the
                      existing HasOptionalKeyword() check, giving presence
                      semantics without a separate emission path.
  * default_value=… — proto2 [default = X] from the field descriptor,
                      rendered as a Lua literal (cdata for 64-bit ints,
                      symbolic name for enums) so consumers can surface
                      it; the codec itself does not auto-materialize
                      defaults on decode, matching how proto3 absent
                      fields stay nil.

Packed-by-default already flips correctly because we ask
protoreflect's `IsPacked()`, which is syntax-aware.

Adds test/proto/proto2_basic.proto with 33 luatest cases covering
required validation, optional presence, custom defaults, the proto2
unpacked-by-default repeated rule, nested-required messages, and
full-vs-runtime mode parity. `just gen-proto2-tests` regenerates the
fixture into examples/expected/{full,runtime}/.

Out of scope: extensions, extend, group; conformance harness still
skips TestAllTypesProto2.
a0005a05 — Eugene Blikh 3 months ago
docs: full reference + how-to set, migrate Makefile to Justfile

Documentation overhaul that adds the missing user-facing surface:
four reference pages (runtime-api, generated-api, cli, grpc-contract),
twelve how-tos walking from first-message through custom transports,
a troubleshooting page, and a docs/index map. Every how-to references
a runnable artifact under examples/, all of them verified end-to-end.

Build system migration: the Makefile is gone; the Justfile is now
the canonical entry point and absorbs every target. examples/Justfile
ships one recipe per runnable example, forwarded via top-level
'just examples <name>'. The 'examples are part of the documented
surface' convention is pinned in CLAUDE.md, alongside a dedicated
section on updating the conformance harness (PROTOBUF_TAG bumps,
libjsoncpp path drift, new test-category wiring).

Stale-number sweep across README/PLAN/CLAUDE: fixture count 18→10,
test count 613/130→639, wire.lua LOC dropped, M7 marked done.
Descriptor-shape block deduplicated against codegen.md as the
canonical source. gRPC transports spec status reframed from
'draft / decision deferred' to 'shipped contract; external
transports deferred'.

.gitignore picks up *.snap / *.xlog / *.vylog / *.run / *.pid /
512.lock so example state can't leak into the working tree.
d855bcfd — Eugene Blikh 3 months ago
conformance: local Docker pipeline + cdata int64 map dedup

Wires up the Google protobuf conformance harness as a local target.
docker/conformance.Dockerfile builds conformance_test_runner from
upstream protobuf v34.1 source (matching the host's libprotoc 34.1)
and bundles Tarantool 3 from the official installer. `just conformance`
regenerates Lua, then runs the harness against cmd/conformance-runner.lua
with the repo mounted as a volume.

Six bugs surfaced and got fixed on the way to green:

  1. conformance_test_runner uses execv (not execvp): bare `tarantool`
     hits ENOENT. Pass /usr/bin/tarantool in CMD and Justfile.
  2. The harness strips LUA_PATH from the child: the runner now
     self-bootstraps package.path from debug.getinfo(1, 'S').source.
  3. C-stdio buffering on pipe stdin made io.stdin:read(n) wait for a
     full BUFSIZ before returning, deadlocking against the parent.
     setvbuf('no') on stdin/stdout.
  4. v34.1 fetches libjsoncpp via CMake FetchContent under
     _deps/jsoncpp-build/...; the runtime image now COPYs the matching
     .so* and runs ldconfig.
  5. The harness's strict jsoncpp comparator crashes on our currently-
     imperfect JSON output (enum numerics, map<K,V> shape, oneof
     object form). Gate JSON output behind PB_CONFORMANCE_SKIP_JSON=1,
     set in the container ENV; host-side `make test` still exercises
     the full JSON path.
  6. Codec bug — LuaJIT hashes cdata int64 by pointer, so duplicate-
     key map entries (per proto3's "last value wins" semantics) split
     across hash buckets even though __eq matches. Codec walks the
     map once on insert to find a canonical key, gated by a
     precomputed `f.key_dedup` flag so the dedup only fires for
     int64/uint64/sint64/fixed64/sfixed64 keys. inline.go emits the
     same `for _k in pairs(map) do` walk only when the static key
     kind is 64-bit, so string/int32-keyed map decode stays
     JIT-traceable.

Watchlists at test/conformance/known_failures.txt (binary + JSON) and
test/conformance/known_failures_text.txt (text-format) hold the
deferred failures. Current baseline:

  - Binary + JSON suite: 803 ✓ / 1864 skipped / 139 expected fails
  - Text-format suite:     0 ✓ /  430 skipped /   4 expected fails

403/403 luatest green, 19/19 jit-trace gate green.