diff --git a/Justfile b/Justfile index 53d9e896cf032170f8e0f4336b5e1170170c79f7..b1b6eab73c9a32d190e64c5a97f72a2e8b48023f 100644 --- a/Justfile +++ b/Justfile @@ -71,16 +71,9 @@ build-doc: go build -o {{doc_plugin}} ./cmd/protoc-gen-tarantool-doc # Build the optional C-acceleration runtime into runtime/pb/c_runtime.{so,dylib}. -# Source lives under runtime/pb/c/ once bd-ra6 lands; until then this recipe -# prints a helpful error and exits 1. The runtime is opt-in via PB_ENABLE_C=1 -# — see docs/specs/c_accel_build_packaging.md for the full contract. +# The runtime is opt-in via PB_ENABLE_C=1 — see docs/specs/c_accel_build_packaging.md +# for the full contract. build-c: - @if [ ! -d runtime/pb/c ]; then \ - echo "build-c: runtime/pb/c/ does not exist yet."; \ - echo " The C runtime arrives with bd-ra6 (generic C codec)."; \ - echo " See docs/specs/c_accel_build_packaging.md."; \ - exit 1; \ - fi make -C runtime/pb/c # Remove built C-runtime artifacts. @@ -180,6 +173,19 @@ # just test-one protobuf_test.lua::hello.full.test_packed_repeated_int32 test-one filter: gen LUA_PATH="{{lua_path}}" LUA_CPATH="{{lua_cpath}}" {{luatest}} -v test/{{filter}} +# Same as `test`, but with PB_ENABLE_C=1 so pb.encode / pb.decode dispatch +# through the C runtime (runtime/pb/c_runtime.{so,dylib}). Builds the C +# module first. Together with `test`, this is the parity gate — every +# luatest assertion is against a reference output (golden bytes, txtpb, +# conformance result), so if Lua passes and C passes, both equal the +# reference and Lua ≡ C by transitivity. See bd-43t. +test-c: build-c gen + PB_ENABLE_C=1 LUA_PATH="{{lua_path}}" LUA_CPATH="{{lua_cpath}}" {{luatest}} -v test/ + +# Parity gate: run the suite under both codecs. Use before pushing C-runtime +# or codec changes. +test-all: test test-c + # --------------------------------------------------------------------------- # Bench # --------------------------------------------------------------------------- @@ -187,6 +193,15 @@ # Microbench: alloc + throughput per op across 5 payload sizes, both modes. bench: gen tarantool bench/bench.lua --print + +# Same as `bench`, but with PB_ENABLE_C=1 so the runtime-mode results +# exercise the C codec. The runtime-mode column is relabelled to +# `c-runtime` in the output. Full-mode results are unchanged (full mode +# uses inline wire calls, not pb.encode). Allocation baselines (`bench-baseline` +# / `bench-compare`) remain Lua-only — C alloc shape differs by design +# and would noise the gate. +bench-c: build-c gen + PB_ENABLE_C=1 tarantool bench/bench.lua --print # Overwrite bench/baseline.json with current alloc-per-op numbers. bench-baseline: gen diff --git a/README.md b/README.md index 2a5df5f44adaaae8e42a2574882345f5f0fb10af..5b8073a1850d24cfc045f4b3f83ff9c3dca0f6e4 100644 --- a/README.md +++ b/README.md @@ -385,12 +385,33 @@ `ignore_unknown_fields=true` to `pb.json.decode`). The self-test in `test/conformance_test.lua` exercises the runner with crafted requests on every `just test` run. +### C-runtime parity + +The optional C acceleration runtime (`runtime/pb/c_runtime.{so,dylib}`, +built via `just build-c`, activated by `PB_ENABLE_C=1`) must produce +byte-identical output to the pure-Lua codec. The parity gate reuses the +existing test suites — no separate diff harness: + +```bash +just test-all # luatest under both codecs (752 + 1043 tests) +just conformance # Google suite, Lua codecs +just conformance-c # Google suite, PB_ENABLE_C=1 +``` + +Every assertion checks against a reference (golden bytes, txtpb, +conformance result). If Lua passes and C passes, both equal the +reference, so Lua ≡ C by transitivity. The interop fixtures under +`test/interop/fixtures/` are parameterized over both codegen modes; +under `PB_ENABLE_C=1` the runtime-mode iteration transparently +exercises the C codec. + [gconf]: https://github.com/protocolbuffers/protobuf/tree/main/conformance ## Benchmarks ```bash just bench # print throughput + alloc per op (5 sizes × 2 modes) +just bench-c # same with PB_ENABLE_C=1 — runtime column → `c-runtime` just bench-baseline # overwrite bench/baseline.json (run on a quiet machine) just bench-compare # exit 1 if any alloc-per-op regressed >5% vs baseline ``` diff --git a/bench/bench.lua b/bench/bench.lua index bcb14e5927dc81efa66f81ba298a302b48d0ca6a..0a868011abdd96794b89ac0dc6923c5d387df082 100644 --- a/bench/bench.lua +++ b/bench/bench.lua @@ -17,6 +17,13 @@ package.path = './runtime/?.lua;./runtime/?/init.lua;' .. './examples/expected/?.lua;./examples/expected/?/init.lua;' .. package.path +-- Extend cpath so `require('pb.c_runtime')` finds runtime/pb/c_runtime.{so,dylib} +-- when PB_ENABLE_C=1. `.dylib` first matches the order in the Justfile (see +-- the comment there for why this matters when both extensions coexist). +package.cpath = './runtime/?.dylib;./runtime/?.so;' + .. './runtime/?/init.dylib;./runtime/?/init.so;' + .. package.cpath + local clock = require('clock') local json = require('json') local fio = require('fio') @@ -436,8 +443,35 @@ local args = {...} local mode_flag = args[1] or '--print' -io.stderr:write(string.format('tarantool-protobuf bench (%s)\n', _TARANTOOL)) +local C_ENABLED = (os.getenv('PB_ENABLE_C') == '1') and (require('pb').c_runtime ~= nil) + +if C_ENABLED and (mode_flag == '--baseline' or mode_flag == '--compare') then + -- The alloc-per-op baseline is Lua-only by design. The C codec has a + -- different allocation shape (C-side scratch + a single Lua-side + -- result string) that would noise the gate. Re-run without + -- PB_ENABLE_C=1 for baseline/compare ops. + io.stderr:write('bench.lua: --baseline and --compare are Lua-only; ' + .. 'unset PB_ENABLE_C and rerun\n') + os.exit(2) +end + +io.stderr:write(string.format('tarantool-protobuf bench (%s)%s\n', + _TARANTOOL, C_ENABLED and ', C runtime ENABLED' or '')) local doc = run_all() + +-- Relabel runtime-mode results to `c-runtime` when the C dispatch is +-- active. The bench fixture iterates over generated modules from +-- examples/expected/runtime/, which call pb.encode / pb.decode — those +-- dispatch through the C codec when desc.c_plan compiles. Full-mode +-- modules inline wire calls and don't go through pb.encode, so the +-- full-mode column is unchanged from the Lua run. +if C_ENABLED then + for _, schema in ipairs(doc.schemas) do + for _, r in ipairs(schema.results) do + if r.mode == 'runtime' then r.mode = 'c-runtime' end + end + end +end if mode_flag == '--print' then io.write(render(doc))