/* * person_codec.c -- hand-written C codec for hello.Person. * * Strategy 4 of tarantool-protobuf-04c: upper-bound measurement. * * Scope: ONLY the fields exercised by bench/bench.lua's Person payloads: * name (string, 1), age (int32, 2), emails (repeated string, 3), * address (Address message, 5), lucky_numbers (packed int32, 7). * * Other Person fields (status, friends, avatar, user_id, balance, * weight_kg, maps) are intentionally absent. The spike measures the * upper bound of C boundary perf for the bench shapes, not full * codec coverage. * * Type-check parity with generic_codec.c: field presence is via * lua_isnil, with no per-element lua_type check inside repeated * loops. The original (2026-05-18) version did defensive lua_type * checks per element, which added ~2700 extra C calls per message * at 100KB and made S4 look slower than S3 at 1KB+. Don't add them * back without re-measuring; see README for the post-correction * numbers. */ #include #include #include #include #include /* ---------------------------------------------------------------- * * Growable byte buffer with a 4 KiB stack-backed initial region. * * ---------------------------------------------------------------- */ typedef struct { uint8_t *data; size_t len; size_t cap; uint8_t stack[4096]; } buf_t; static inline void buf_init(buf_t *b) { b->data = b->stack; b->len = 0; b->cap = sizeof(b->stack); } static inline void buf_free(buf_t *b) { if (b->data != b->stack) free(b->data); } static void buf_grow(buf_t *b, size_t need) { size_t nc = b->cap ? b->cap * 2 : 64; while (nc < b->len + need) nc *= 2; uint8_t *nd = (uint8_t *)malloc(nc); memcpy(nd, b->data, b->len); if (b->data != b->stack) free(b->data); b->data = nd; b->cap = nc; } static inline void buf_reserve(buf_t *b, size_t need) { if (b->len + need > b->cap) buf_grow(b, need); } /* ---------------------------------------------------------------- * * Wire primitives. * * ---------------------------------------------------------------- */ static inline void write_varint(buf_t *b, uint64_t v) { buf_reserve(b, 10); while (v >= 0x80) { b->data[b->len++] = (uint8_t)(v | 0x80); v >>= 7; } b->data[b->len++] = (uint8_t)v; } static inline void write_bytes(buf_t *b, const void *src, size_t n) { buf_reserve(b, n); memcpy(b->data + b->len, src, n); b->len += n; } static inline void write_string_field(buf_t *b, uint32_t tag, const char *s, size_t n) { write_varint(b, tag); write_varint(b, (uint64_t)n); write_bytes(b, s, n); } /* Read a varint. Returns new pointer on success, NULL on truncation. */ static const uint8_t * read_varint(const uint8_t *p, const uint8_t *end, uint64_t *out) { uint64_t v = 0; int shift = 0; while (p < end) { uint8_t c = *p++; v |= (uint64_t)(c & 0x7f) << shift; if (!(c & 0x80)) { *out = v; return p; } shift += 7; if (shift >= 64) return NULL; } return NULL; } /* ---------------------------------------------------------------- * * Address encode/decode helpers (sub-message, fields used in * * bench payloads: street/1, city/2, zip/3). * * ---------------------------------------------------------------- */ static void encode_address_body(buf_t *b, lua_State *L, int t) { lua_getfield(L, t, "street"); if (!lua_isnil(L, -1)) { size_t n; const char *s = lua_tolstring(L, -1, &n); write_string_field(b, (1 << 3) | 2, s, n); } lua_pop(L, 1); lua_getfield(L, t, "city"); if (!lua_isnil(L, -1)) { size_t n; const char *s = lua_tolstring(L, -1, &n); write_string_field(b, (2 << 3) | 2, s, n); } lua_pop(L, 1); lua_getfield(L, t, "zip"); if (!lua_isnil(L, -1)) { write_varint(b, (3 << 3) | 0); write_varint(b, (uint64_t)(int64_t)lua_tointeger(L, -1)); } lua_pop(L, 1); } static const uint8_t * decode_address(lua_State *L, const uint8_t *p, const uint8_t *end) { lua_createtable(L, 0, 3); while (p < end) { uint64_t tag; p = read_varint(p, end, &tag); if (!p) return NULL; int field = (int)(tag >> 3); int wt = (int)(tag & 7); if (wt == 2) { uint64_t slen; p = read_varint(p, end, &slen); if (!p || (size_t)(end - p) < slen) return NULL; if (field == 1) { lua_pushlstring(L, (const char *)p, (size_t)slen); lua_setfield(L, -2, "street"); } else if (field == 2) { lua_pushlstring(L, (const char *)p, (size_t)slen); lua_setfield(L, -2, "city"); } p += slen; } else if (wt == 0) { uint64_t v; p = read_varint(p, end, &v); if (!p) return NULL; if (field == 3) { lua_pushinteger(L, (lua_Integer)(int32_t)v); lua_setfield(L, -2, "zip"); } } else { /* Unknown wire types ignored in spike. */ return NULL; } } return p; } /* ---------------------------------------------------------------- * * Person_encode(tbl) -> string * * ---------------------------------------------------------------- */ static int Person_encode(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); const int t = 1; buf_t b; buf_init(&b); /* name (1, string) */ lua_getfield(L, t, "name"); if (!lua_isnil(L, -1)) { size_t n; const char *s = lua_tolstring(L, -1, &n); write_string_field(&b, (1 << 3) | 2, s, n); } lua_pop(L, 1); /* age (2, int32) */ lua_getfield(L, t, "age"); if (!lua_isnil(L, -1)) { write_varint(&b, (2 << 3) | 0); write_varint(&b, (uint64_t)(int64_t)lua_tointeger(L, -1)); } lua_pop(L, 1); /* emails (3, repeated string) */ lua_getfield(L, t, "emails"); if (!lua_isnil(L, -1)) { int idx = lua_gettop(L); int n_emails = (int)lua_objlen(L, idx); for (int i = 1; i <= n_emails; i++) { lua_rawgeti(L, idx, i); size_t n; const char *s = lua_tolstring(L, -1, &n); write_string_field(&b, (3 << 3) | 2, s, n); lua_pop(L, 1); } } lua_pop(L, 1); /* address (5, sub-message) */ lua_getfield(L, t, "address"); if (!lua_isnil(L, -1)) { int addr_idx = lua_gettop(L); buf_t sub; buf_init(&sub); encode_address_body(&sub, L, addr_idx); write_varint(&b, (5 << 3) | 2); write_varint(&b, (uint64_t)sub.len); write_bytes(&b, sub.data, sub.len); buf_free(&sub); } lua_pop(L, 1); /* lucky_numbers (7, packed int32) */ lua_getfield(L, t, "lucky_numbers"); if (!lua_isnil(L, -1)) { int idx = lua_gettop(L); int n = (int)lua_objlen(L, idx); buf_t sub; buf_init(&sub); for (int i = 1; i <= n; i++) { lua_rawgeti(L, idx, i); write_varint(&sub, (uint64_t)(int64_t)lua_tointeger(L, -1)); lua_pop(L, 1); } write_varint(&b, (7 << 3) | 2); write_varint(&b, (uint64_t)sub.len); write_bytes(&b, sub.data, sub.len); buf_free(&sub); } lua_pop(L, 1); lua_pushlstring(L, (const char *)b.data, b.len); buf_free(&b); return 1; } /* ---------------------------------------------------------------- * * Person_decode(string) -> tbl * * ---------------------------------------------------------------- */ static int Person_decode(lua_State *L) { size_t len; const char *buf = luaL_checklstring(L, 1, &len); const uint8_t *p = (const uint8_t *)buf; const uint8_t *end = p + len; lua_createtable(L, 0, 5); const int result_idx = lua_gettop(L); /* Lazy arrays: stash stack index of the array table once created. */ int emails_stkidx = 0; int n_emails = 0; int lucky_stkidx = 0; int n_lucky = 0; while (p < end) { uint64_t tag; p = read_varint(p, end, &tag); if (!p) break; int field = (int)(tag >> 3); int wt = (int)(tag & 7); if (wt == 2) { uint64_t slen; p = read_varint(p, end, &slen); if (!p || (size_t)(end - p) < slen) break; const uint8_t *fend = p + slen; switch (field) { case 1: /* name */ lua_pushlstring(L, (const char *)p, (size_t)slen); lua_setfield(L, result_idx, "name"); break; case 3: /* emails (repeated string) */ if (emails_stkidx == 0) { lua_createtable(L, 4, 0); emails_stkidx = lua_gettop(L); } lua_pushlstring(L, (const char *)p, (size_t)slen); lua_rawseti(L, emails_stkidx, ++n_emails); break; case 5: /* address (sub-message) */ if (decode_address(L, p, fend) == NULL) goto done; lua_setfield(L, result_idx, "address"); break; case 7: { /* lucky_numbers (packed int32) */ if (lucky_stkidx == 0) { lua_createtable(L, 8, 0); lucky_stkidx = lua_gettop(L); } const uint8_t *q = p; while (q < fend) { uint64_t v; q = read_varint(q, fend, &v); if (!q) break; lua_pushinteger(L, (lua_Integer)(int32_t)v); lua_rawseti(L, lucky_stkidx, ++n_lucky); } break; } default: /* Unknown LEN field -- skip silently. */ break; } p = fend; } else if (wt == 0) { uint64_t v; p = read_varint(p, end, &v); if (!p) break; if (field == 2) { /* age */ lua_pushinteger(L, (lua_Integer)(int32_t)v); lua_setfield(L, result_idx, "age"); } } else { /* Other wire types not exercised by bench payloads. */ break; } } done: if (emails_stkidx) { lua_pushvalue(L, emails_stkidx); lua_setfield(L, result_idx, "emails"); } if (lucky_stkidx) { lua_pushvalue(L, lucky_stkidx); lua_setfield(L, result_idx, "lucky_numbers"); } lua_settop(L, result_idx); return 1; } /* ---------------------------------------------------------------- * * Module entry. * * ---------------------------------------------------------------- */ static const struct luaL_Reg lib[] = { {"Person_encode", Person_encode}, {"Person_decode", Person_decode}, {NULL, NULL}, }; LUA_API int luaopen_pb_c_person(lua_State *L) { luaL_register(L, "pb_c_person", lib); return 1; }