/* * c_runtime.c -- C-acceleration runtime for tarantool-protobuf. * * Phase 1 (bd-mq7): descriptor -> plan compiler. Walks a finalized * Lua descriptor table and produces an opaque `pb_plan` userdata. * Encode/decode entry points arrive with bd-3b/3c/etc.; this file * only carries the plan compilation surface and enough introspection * to make a smoke test possible. * * Conventions follow docs/specs/c_accel_strategy.md. */ /* strdup is POSIX, not C99; declare we want the POSIX surface from * before any system header pulls it in. */ #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #include #include #include #include #include #define PB_PLAN_MT "pb.plan" #define PB_ABI_VERSION "1" /* ---------------------------------------------------------------- * * Kind / wire-type taxonomy. * * * * Mirrors runtime/pb/wire.lua's TYPE_INFO. The numbering is * * internal — only the C runtime needs to agree with itself. * * ---------------------------------------------------------------- */ enum { PB_KIND_NONE = 0, PB_KIND_INT32, PB_KIND_INT64, PB_KIND_UINT32, PB_KIND_UINT64, PB_KIND_SINT32, PB_KIND_SINT64, PB_KIND_FIXED32, PB_KIND_FIXED64, PB_KIND_SFIXED32, PB_KIND_SFIXED64, PB_KIND_FLOAT, PB_KIND_DOUBLE, PB_KIND_BOOL, PB_KIND_STRING, PB_KIND_BYTES, PB_KIND_ENUM, PB_KIND_MESSAGE, PB_KIND_MAP, }; /* Wire types per proto3 spec. */ enum { PB_WIRE_VARINT = 0, PB_WIRE_I64 = 1, PB_WIRE_LEN = 2, PB_WIRE_I32 = 5, }; struct kind_info { const char *proto_type; uint8_t kind; uint8_t wire_type; }; /* Lookup table keyed by proto3 field type string. Linear scan is fine — * a finalized descriptor calls this once per field at plan-compile time, * not per encode/decode. */ static const struct kind_info kind_table[] = { {"int32", PB_KIND_INT32, PB_WIRE_VARINT}, {"int64", PB_KIND_INT64, PB_WIRE_VARINT}, {"uint32", PB_KIND_UINT32, PB_WIRE_VARINT}, {"uint64", PB_KIND_UINT64, PB_WIRE_VARINT}, {"sint32", PB_KIND_SINT32, PB_WIRE_VARINT}, {"sint64", PB_KIND_SINT64, PB_WIRE_VARINT}, {"bool", PB_KIND_BOOL, PB_WIRE_VARINT}, {"fixed32", PB_KIND_FIXED32, PB_WIRE_I32}, {"sfixed32", PB_KIND_SFIXED32, PB_WIRE_I32}, {"float", PB_KIND_FLOAT, PB_WIRE_I32}, {"fixed64", PB_KIND_FIXED64, PB_WIRE_I64}, {"sfixed64", PB_KIND_SFIXED64, PB_WIRE_I64}, {"double", PB_KIND_DOUBLE, PB_WIRE_I64}, {"string", PB_KIND_STRING, PB_WIRE_LEN}, {"bytes", PB_KIND_BYTES, PB_WIRE_LEN}, }; static const struct kind_info * lookup_kind(const char *proto_type) { if (proto_type == NULL) return NULL; for (size_t i = 0; i < sizeof(kind_table) / sizeof(kind_table[0]); i++) { if (strcmp(kind_table[i].proto_type, proto_type) == 0) return &kind_table[i]; } return NULL; } /* ---------------------------------------------------------------- * * Plan struct layout. * * * * Spec: docs/specs/c_accel_strategy.md § The plan userdata. * * Simplifications from the spec for mq7: * * - oneofs[] populated but member dispatch lives in 3i * * - extension_range_* populated but dispatch lives in 3i * * - sub_plan_idx points into `sub_plans_ref` table (1-based) * * - Field name strings live in a Lua table keyed by 1..n; * * lookup via `lua_rawgeti(L, names, i+1)` per spec. * * ---------------------------------------------------------------- */ typedef struct pb_plan_field { uint32_t field_number; uint8_t wire_type; uint8_t kind; uint8_t packed; uint8_t repeated; uint8_t optional; uint8_t tag_len; uint8_t tag_bytes[5]; int sub_plan_idx; /* 1-based into sub_plans table; 0 if none */ uint8_t map_key_kind; uint8_t map_value_kind; int map_value_sub_plan_idx; /* 1-based; 0 if value is scalar */ int oneof_idx; /* 0-based into plan->oneofs; -1 if none */ int enum_ref; /* LUA_REGISTRYINDEX ref for enum desc; LUA_NOREF if none */ } pb_plan_field; typedef struct pb_plan_oneof { char *name; /* malloc'd */ int n_members; int *member_indices; /* indices into plan->fields */ } pb_plan_oneof; typedef struct pb_plan { char *name; /* malloc'd descriptor name */ int n_fields; pb_plan_field *fields; int n_oneofs; pb_plan_oneof *oneofs; int extension_range_start; int extension_range_end; uint8_t has_override; int override_encode_ref; /* LUA_NOREF if absent */ int override_decode_ref; int field_names_ref; /* table { [1]=name1, ... } */ int sub_plans_ref; /* table { [1]=plan_userdata, ... } */ } pb_plan; /* ---------------------------------------------------------------- * * Tag encoding. * * * * Pre-encodes the (field_number << 3) | wire_type varint so the * * hot encode path emits a fixed memcpy instead of recomputing. * * Up to 5 bytes for any legal field number (2^29 - 1 max). * * ---------------------------------------------------------------- */ static void encode_tag(uint32_t field_number, uint8_t wire_type, uint8_t *out, uint8_t *out_len) { uint64_t v = ((uint64_t)field_number << 3) | wire_type; uint8_t i = 0; while (v >= 0x80) { out[i++] = (uint8_t)(v | 0x80); v >>= 7; } out[i++] = (uint8_t)v; *out_len = i; } /* ---------------------------------------------------------------- * * Plan lifecycle: alloc / free. * * ---------------------------------------------------------------- */ static void plan_free(lua_State *L, pb_plan *p) { if (p->name != NULL) free(p->name); if (p->fields != NULL) { for (int i = 0; i < p->n_fields; i++) { if (p->fields[i].enum_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, p->fields[i].enum_ref); } free(p->fields); } if (p->oneofs != NULL) { for (int i = 0; i < p->n_oneofs; i++) { free(p->oneofs[i].name); free(p->oneofs[i].member_indices); } free(p->oneofs); } if (p->override_encode_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, p->override_encode_ref); if (p->override_decode_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, p->override_decode_ref); if (p->field_names_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, p->field_names_ref); if (p->sub_plans_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, p->sub_plans_ref); memset(p, 0, sizeof(*p)); p->override_encode_ref = LUA_NOREF; p->override_decode_ref = LUA_NOREF; p->field_names_ref = LUA_NOREF; p->sub_plans_ref = LUA_NOREF; } static int plan_gc(lua_State *L) { pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT); plan_free(L, p); return 0; } static int plan_tostring(lua_State *L) { pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT); lua_pushfstring(L, "pb.plan: %s (n_fields=%d)", p->name != NULL ? p->name : "(unnamed)", p->n_fields); return 1; } /* ---------------------------------------------------------------- * * Field compilation. * * * * Reads one descriptor-field table (at the top of the stack) and * * fills the corresponding pb_plan_field. Recurses into sub-message * * plans by calling compile_plan_impl. * * * * Stack expectations on entry: * * -1: field-descriptor table (e.g. {name="foo", id=1, kind=...})* * sub_plans_stack_idx: the sub-plans table being filled * * * * Stack on exit: same (we pop everything we push). * * ---------------------------------------------------------------- */ static int compile_plan_impl(lua_State *L, int desc_idx); /* Append a sub-plan userdata (at -1) to the sub-plans table at * sub_plans_idx; return its 1-based index. Pops the userdata. */ static int push_sub_plan(lua_State *L, int sub_plans_idx) { int n = (int)lua_objlen(L, sub_plans_idx); lua_rawseti(L, sub_plans_idx, n + 1); return n + 1; } /* Resolve a sub-message descriptor (at stack idx `sub_desc_idx`) into a * 1-based index into the sub-plans table. Stack-neutral: pushes/pops the * intermediate desc copy and plan userdata internally. */ static int resolve_sub_plan(lua_State *L, int sub_desc_idx, int sub_plans_idx) { int saved_top = lua_gettop(L); /* compile_plan_impl is idempotent — if desc.c_plan exists it just * leaves the cached userdata on top. */ lua_pushvalue(L, sub_desc_idx); int dup_idx = lua_gettop(L); compile_plan_impl(L, dup_idx); if (!lua_isuserdata(L, -1)) luaL_error(L, "compile_plan_impl did not return a userdata"); int idx = push_sub_plan(L, sub_plans_idx); /* pops plan userdata */ lua_settop(L, saved_top); /* drop sub_desc copy */ return idx; } /* Compile one field. `f_desc_idx` is the absolute stack index of the * field descriptor table. `field` points at the pb_plan_field slot to * fill. `field_names_idx`, `sub_plans_idx` are absolute indices of the * field-names and sub-plans tables being populated. `field_idx_1based` * is the 1-based position used for the field-names lookup table. */ static void compile_field(lua_State *L, int f_desc_idx, pb_plan_field *field, int field_names_idx, int sub_plans_idx, int field_idx_1based) { memset(field, 0, sizeof(*field)); field->enum_ref = LUA_NOREF; field->oneof_idx = -1; /* field number */ lua_getfield(L, f_desc_idx, "id"); if (!lua_isnumber(L, -1)) luaL_error(L, "field descriptor missing 'id'"); field->field_number = (uint32_t)lua_tointeger(L, -1); lua_pop(L, 1); /* name (interned in field_names_ref under field_idx_1based) */ lua_getfield(L, f_desc_idx, "name"); if (!lua_isstring(L, -1)) luaL_error(L, "field descriptor missing 'name'"); lua_pushvalue(L, -1); /* dup */ lua_rawseti(L, field_names_idx, field_idx_1based); lua_pop(L, 1); /* repeated / packed / optional */ lua_getfield(L, f_desc_idx, "repeated"); field->repeated = lua_toboolean(L, -1) ? 1 : 0; lua_pop(L, 1); lua_getfield(L, f_desc_idx, "packed"); field->packed = lua_toboolean(L, -1) ? 1 : 0; lua_pop(L, 1); lua_getfield(L, f_desc_idx, "optional"); field->optional = lua_toboolean(L, -1) ? 1 : 0; lua_pop(L, 1); /* kind dispatch on desc.kind */ lua_getfield(L, f_desc_idx, "kind"); const char *kind_str = lua_tostring(L, -1); if (kind_str == NULL) luaL_error(L, "field descriptor missing 'kind'"); uint8_t element_wire_type; if (strcmp(kind_str, "scalar") == 0) { lua_pop(L, 1); /* pop kind */ lua_getfield(L, f_desc_idx, "proto_type"); const char *pt = lua_tostring(L, -1); const struct kind_info *ki = lookup_kind(pt); if (ki == NULL) luaL_error(L, "unknown scalar proto_type: %s", pt != NULL ? pt : "(nil)"); field->kind = ki->kind; element_wire_type = ki->wire_type; lua_pop(L, 1); } else if (strcmp(kind_str, "enum") == 0) { lua_pop(L, 1); field->kind = PB_KIND_ENUM; element_wire_type = PB_WIRE_VARINT; lua_getfield(L, f_desc_idx, "enum"); if (lua_istable(L, -1)) { field->enum_ref = luaL_ref(L, LUA_REGISTRYINDEX); } else { lua_pop(L, 1); } } else if (strcmp(kind_str, "message") == 0) { lua_pop(L, 1); field->kind = PB_KIND_MESSAGE; element_wire_type = PB_WIRE_LEN; lua_getfield(L, f_desc_idx, "message"); if (!lua_istable(L, -1)) luaL_error(L, "message field '%s' missing 'message' descriptor", "?"); int sub_desc = lua_gettop(L); field->sub_plan_idx = resolve_sub_plan(L, sub_desc, sub_plans_idx); lua_pop(L, 1); /* sub-desc table */ } else if (strcmp(kind_str, "map") == 0) { lua_pop(L, 1); field->kind = PB_KIND_MAP; element_wire_type = PB_WIRE_LEN; /* key */ lua_getfield(L, f_desc_idx, "key"); if (!lua_istable(L, -1)) luaL_error(L, "map field missing 'key' descriptor"); lua_getfield(L, -1, "proto_type"); const struct kind_info *ki = lookup_kind(lua_tostring(L, -1)); if (ki == NULL) luaL_error(L, "map key has unknown proto_type"); field->map_key_kind = ki->kind; lua_pop(L, 2); /* proto_type + key */ /* value */ lua_getfield(L, f_desc_idx, "value"); if (!lua_istable(L, -1)) luaL_error(L, "map field missing 'value' descriptor"); lua_getfield(L, -1, "kind"); const char *vk = lua_tostring(L, -1); lua_pop(L, 1); if (vk != NULL && strcmp(vk, "scalar") == 0) { lua_getfield(L, -1, "proto_type"); const struct kind_info *vki = lookup_kind(lua_tostring(L, -1)); if (vki == NULL) luaL_error(L, "map value has unknown scalar proto_type"); field->map_value_kind = vki->kind; lua_pop(L, 1); } else if (vk != NULL && strcmp(vk, "message") == 0) { field->map_value_kind = PB_KIND_MESSAGE; lua_getfield(L, -1, "message"); if (lua_istable(L, -1)) { int sub_desc = lua_gettop(L); field->map_value_sub_plan_idx = resolve_sub_plan(L, sub_desc, sub_plans_idx); } lua_pop(L, 1); } else if (vk != NULL && strcmp(vk, "enum") == 0) { field->map_value_kind = PB_KIND_ENUM; } else { luaL_error(L, "map value has unknown kind: %s", vk != NULL ? vk : "(nil)"); } lua_pop(L, 1); /* value table */ } else { luaL_error(L, "unknown field kind: %s", kind_str); return; /* unreachable */ } /* Wire type: repeated+packed → LEN regardless of element type; * repeated unpacked → element type per tag; singular → element. */ if (field->repeated && field->packed) { field->wire_type = PB_WIRE_LEN; } else { field->wire_type = element_wire_type; } encode_tag(field->field_number, field->wire_type, field->tag_bytes, &field->tag_len); } /* ---------------------------------------------------------------- * * Oneof compilation. * * * * desc.oneofs is the hash table {[name]=members}. We walk it and * * build pb_plan_oneof[]. For each member field, set its oneof_idx * * to point back at the plan's oneof entry. * * ---------------------------------------------------------------- */ static int find_field_by_name(pb_plan *p, lua_State *L, int field_names_idx, const char *name) { for (int i = 0; i < p->n_fields; i++) { lua_rawgeti(L, field_names_idx, i + 1); const char *fn = lua_tostring(L, -1); int match = (fn != NULL && strcmp(fn, name) == 0); lua_pop(L, 1); if (match) return i; } return -1; } static void compile_oneofs(lua_State *L, pb_plan *p, int desc_idx, int field_names_idx) { lua_getfield(L, desc_idx, "oneofs"); if (!lua_istable(L, -1)) { lua_pop(L, 1); return; } /* First pass: count. */ int count = 0; lua_pushnil(L); while (lua_next(L, -2) != 0) { count++; lua_pop(L, 1); } if (count == 0) { lua_pop(L, 1); return; } p->oneofs = (pb_plan_oneof *)calloc(count, sizeof(pb_plan_oneof)); p->n_oneofs = count; int idx = 0; lua_pushnil(L); while (lua_next(L, -2) != 0) { /* key at -2 (oneof name), value at -1 (members array) */ const char *oname = lua_tostring(L, -2); p->oneofs[idx].name = strdup(oname != NULL ? oname : ""); int n_members = (int)lua_objlen(L, -1); p->oneofs[idx].n_members = n_members; p->oneofs[idx].member_indices = (int *)calloc(n_members, sizeof(int)); for (int j = 0; j < n_members; j++) { lua_rawgeti(L, -1, j + 1); const char *mname = lua_tostring(L, -1); int fi = find_field_by_name(p, L, field_names_idx, mname != NULL ? mname : ""); lua_pop(L, 1); p->oneofs[idx].member_indices[j] = fi; if (fi >= 0) p->fields[fi].oneof_idx = idx; } lua_pop(L, 1); /* value */ idx++; } lua_pop(L, 1); /* oneofs table */ } /* ---------------------------------------------------------------- * * Main compile entry. * * * * Idempotent. If desc.c_plan exists and is a pb.plan userdata, * * returns it. Otherwise allocates a new one, stashes it on * * desc.c_plan BEFORE recursing into sub-plans (breaks cycles for * * self-referencing messages like Person.friends → Person), then * * walks fields and oneofs. * * ---------------------------------------------------------------- */ /* Lua 5.1 lacks lua_absindex; LuaJIT's compatibility layer too. */ static inline int abs_idx(lua_State *L, int idx) { if (idx < 0 && idx > LUA_REGISTRYINDEX) return lua_gettop(L) + idx + 1; return idx; } static int compile_plan_impl(lua_State *L, int desc_idx) { desc_idx = abs_idx(L, desc_idx); /* Idempotency check. */ lua_getfield(L, desc_idx, "c_plan"); if (lua_isuserdata(L, -1)) { /* Already compiled; leave on top of stack and pop the original * desc-table push pattern wasn't done — caller still owns. */ return 1; } lua_pop(L, 1); /* Allocate the plan userdata and stash it immediately. */ pb_plan *p = (pb_plan *)lua_newuserdata(L, sizeof(pb_plan)); memset(p, 0, sizeof(*p)); p->override_encode_ref = LUA_NOREF; p->override_decode_ref = LUA_NOREF; p->field_names_ref = LUA_NOREF; p->sub_plans_ref = LUA_NOREF; luaL_getmetatable(L, PB_PLAN_MT); lua_setmetatable(L, -2); int plan_idx = lua_gettop(L); /* Stash on desc.c_plan first to break sub-message cycles. */ lua_pushvalue(L, plan_idx); lua_setfield(L, desc_idx, "c_plan"); /* desc.name */ lua_getfield(L, desc_idx, "name"); const char *dname = lua_tostring(L, -1); p->name = strdup(dname != NULL ? dname : ""); lua_pop(L, 1); /* WKT override pointers — desc.encode / desc.decode. */ lua_getfield(L, desc_idx, "encode"); if (lua_isfunction(L, -1)) { p->has_override = 1; p->override_encode_ref = luaL_ref(L, LUA_REGISTRYINDEX); } else { lua_pop(L, 1); } lua_getfield(L, desc_idx, "decode"); if (lua_isfunction(L, -1)) { p->has_override = 1; p->override_decode_ref = luaL_ref(L, LUA_REGISTRYINDEX); } else { lua_pop(L, 1); } /* Extension ranges (proto2). desc.extension_ranges = {{start, end}, ...} */ lua_getfield(L, desc_idx, "extension_ranges"); if (lua_istable(L, -1) && lua_objlen(L, -1) >= 1) { lua_rawgeti(L, -1, 1); if (lua_istable(L, -1)) { lua_rawgeti(L, -1, 1); p->extension_range_start = (int)lua_tointeger(L, -1); lua_pop(L, 1); lua_rawgeti(L, -1, 2); p->extension_range_end = (int)lua_tointeger(L, -1); lua_pop(L, 1); } lua_pop(L, 1); } lua_pop(L, 1); /* If this descriptor has an override, we skip field-walk entirely — * the override owns encode/decode and the field array is unused. */ if (p->has_override) { return 1; } /* Create the field-names and sub-plans tables. */ lua_newtable(L); int field_names_idx = lua_gettop(L); lua_newtable(L); int sub_plans_idx = lua_gettop(L); /* Walk desc.fields. */ lua_getfield(L, desc_idx, "fields"); if (!lua_istable(L, -1)) luaL_error(L, "descriptor '%s' has no 'fields'", p->name != NULL ? p->name : "?"); int n_fields = (int)lua_objlen(L, -1); int fields_table_idx = lua_gettop(L); p->n_fields = n_fields; p->fields = (pb_plan_field *)calloc( n_fields > 0 ? n_fields : 1, sizeof(pb_plan_field)); for (int i = 0; i < n_fields; i++) { lua_rawgeti(L, fields_table_idx, i + 1); int f_desc_idx = lua_gettop(L); compile_field(L, f_desc_idx, &p->fields[i], field_names_idx, sub_plans_idx, i + 1); lua_pop(L, 1); } lua_pop(L, 1); /* fields table */ /* Compile oneofs after fields so oneof_idx back-pointers can be set. */ compile_oneofs(L, p, desc_idx, field_names_idx); /* Stash the field-names + sub-plans tables in the registry. */ lua_pushvalue(L, sub_plans_idx); p->sub_plans_ref = luaL_ref(L, LUA_REGISTRYINDEX); lua_pop(L, 1); /* sub_plans_idx (now unreferenced from stack) */ lua_pushvalue(L, field_names_idx); p->field_names_ref = luaL_ref(L, LUA_REGISTRYINDEX); lua_pop(L, 1); /* Stack now has just the plan userdata on top. */ return 1; } static int compile_plan_lua(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); return compile_plan_impl(L, 1); } /* ---------------------------------------------------------------- * * Introspection (for the smoke test and future debugging). * * * * These are intentionally lean — enough to let a Lua test assert * * the plan-build produced sensible values without a C-side test * * harness. * * ---------------------------------------------------------------- */ static int plan_n_fields(lua_State *L) { pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT); lua_pushinteger(L, p->n_fields); return 1; } static int plan_name(lua_State *L) { pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT); lua_pushstring(L, p->name != NULL ? p->name : ""); return 1; } static int plan_field_info(lua_State *L) { pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT); int i = luaL_checkint(L, 2); if (i < 1 || i > p->n_fields) return luaL_error(L, "field index %d out of range [1, %d]", i, p->n_fields); pb_plan_field *f = &p->fields[i - 1]; lua_createtable(L, 0, 9); lua_pushinteger(L, f->field_number); lua_setfield(L, -2, "field_number"); lua_pushinteger(L, f->wire_type); lua_setfield(L, -2, "wire_type"); lua_pushinteger(L, f->kind); lua_setfield(L, -2, "kind"); lua_pushboolean(L, f->repeated); lua_setfield(L, -2, "repeated"); lua_pushboolean(L, f->packed); lua_setfield(L, -2, "packed"); lua_pushboolean(L, f->optional); lua_setfield(L, -2, "optional"); lua_pushinteger(L, f->sub_plan_idx); lua_setfield(L, -2, "sub_plan_idx"); lua_pushinteger(L, f->oneof_idx); lua_setfield(L, -2, "oneof_idx"); lua_pushlstring(L, (const char *)f->tag_bytes, f->tag_len); lua_setfield(L, -2, "tag_bytes"); /* Field name from cached table. */ lua_rawgeti(L, LUA_REGISTRYINDEX, p->field_names_ref); lua_rawgeti(L, -1, i); lua_remove(L, -2); lua_setfield(L, -2, "name"); /* Map kinds, if any. */ if (f->kind == PB_KIND_MAP) { lua_pushinteger(L, f->map_key_kind); lua_setfield(L, -2, "map_key_kind"); lua_pushinteger(L, f->map_value_kind); lua_setfield(L, -2, "map_value_kind"); lua_pushinteger(L, f->map_value_sub_plan_idx); lua_setfield(L, -2, "map_value_sub_plan_idx"); } return 1; } static int plan_n_oneofs(lua_State *L) { pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT); lua_pushinteger(L, p->n_oneofs); return 1; } static int plan_oneof_info(lua_State *L) { pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT); int i = luaL_checkint(L, 2); if (i < 1 || i > p->n_oneofs) return luaL_error(L, "oneof index %d out of range [1, %d]", i, p->n_oneofs); pb_plan_oneof *o = &p->oneofs[i - 1]; lua_createtable(L, 0, 2); lua_pushstring(L, o->name != NULL ? o->name : ""); lua_setfield(L, -2, "name"); lua_createtable(L, o->n_members, 0); for (int j = 0; j < o->n_members; j++) { lua_pushinteger(L, o->member_indices[j]); lua_rawseti(L, -2, j + 1); } lua_setfield(L, -2, "member_indices"); return 1; } static int plan_has_override(lua_State *L) { pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT); lua_pushboolean(L, p->has_override); return 1; } static int plan_sub_plan(lua_State *L) { pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT); int i = luaL_checkint(L, 2); if (p->sub_plans_ref == LUA_NOREF || i < 1) { lua_pushnil(L); return 1; } lua_rawgeti(L, LUA_REGISTRYINDEX, p->sub_plans_ref); lua_rawgeti(L, -1, i); lua_remove(L, -2); return 1; } /* ---------------------------------------------------------------- * * Encode (bd-y1n / ra6 3b): singular scalar fields. * * * * Repeated/packed, map, and message-typed fields are skipped — * * those land in 3d (sub-message) and 3e (repeated/packed). * * * * Buffer strategy: a 4KB stack-backed scratch buffer that promotes * * to a Lua userdata (GC'd automatically) on overflow. Using * * `lua_newuserdata` for heap growth means a luaL_error mid-encode * * doesn't leak — the userdata is still on the stack at the unwind * * point and gets collected normally. * * ---------------------------------------------------------------- */ #define ENC_STACK_BUF 4096 typedef struct enc_buf { uint8_t stack[ENC_STACK_BUF]; uint8_t *heap; /* pointer into Lua userdata when grown; NULL while on stack */ int heap_idx; /* stack slot of the userdata; 0 if not yet on heap */ size_t cap; size_t used; } enc_buf; static inline uint8_t * ebuf_base(enc_buf *b) { return b->heap != NULL ? b->heap : b->stack; } static void ebuf_init(enc_buf *b) { b->heap = NULL; b->heap_idx = 0; b->cap = ENC_STACK_BUF; b->used = 0; } static void ebuf_grow(lua_State *L, enc_buf *b, size_t needed) { size_t new_cap = b->cap * 2; while (new_cap - b->used < needed) new_cap *= 2; uint8_t *new_buf = (uint8_t *)lua_newuserdata(L, new_cap); memcpy(new_buf, ebuf_base(b), b->used); if (b->heap_idx == 0) { b->heap_idx = lua_gettop(L); } else { lua_replace(L, b->heap_idx); } b->heap = new_buf; b->cap = new_cap; } static inline void ebuf_reserve(lua_State *L, enc_buf *b, size_t needed) { if (b->cap - b->used < needed) ebuf_grow(L, b, needed); } static inline void ebuf_put_byte(enc_buf *b, uint8_t v) { ebuf_base(b)[b->used++] = v; } static inline void ebuf_put_bytes(enc_buf *b, const uint8_t *src, size_t n) { memcpy(ebuf_base(b) + b->used, src, n); b->used += n; } static inline void ebuf_put_varint(enc_buf *b, uint64_t v) { uint8_t *p = ebuf_base(b) + b->used; while (v >= 0x80) { *p++ = (uint8_t)(v | 0x80); v >>= 7; } *p++ = (uint8_t)v; b->used = (size_t)(p - ebuf_base(b)); } static inline void ebuf_put_fixed32(enc_buf *b, uint32_t v) { uint8_t *p = ebuf_base(b) + b->used; p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8); p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24); b->used += 4; } static inline void ebuf_put_fixed64(enc_buf *b, uint64_t v) { uint8_t *p = ebuf_base(b) + b->used; p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8); p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24); p[4] = (uint8_t)(v >> 32); p[5] = (uint8_t)(v >> 40); p[6] = (uint8_t)(v >> 48); p[7] = (uint8_t)(v >> 56); b->used += 8; } static inline void ebuf_put_tag(enc_buf *b, const pb_plan_field *f) { memcpy(ebuf_base(b) + b->used, f->tag_bytes, f->tag_len); b->used += f->tag_len; } /* Read a Lua value as uint64. Mirrors wire.lua's to_uint64: negative * Lua numbers are sign-extended through int64 (proto3 wire spec for * int32 fields). int64/uint64 cdata flow through luaL_touint64. */ static uint64_t to_uint64_at(lua_State *L, int idx) { if (lua_type(L, idx) == LUA_TNUMBER) { double d = lua_tonumber(L, idx); if (d < 0) return (uint64_t)(int64_t)d; return (uint64_t)d; } return luaL_touint64(L, idx); } static int32_t to_int32_at(lua_State *L, int idx) { if (lua_type(L, idx) == LUA_TNUMBER) return (int32_t)lua_tointeger(L, idx); return (int32_t)luaL_toint64(L, idx); } static int64_t to_int64_at(lua_State *L, int idx) { if (lua_type(L, idx) == LUA_TNUMBER) return (int64_t)lua_tonumber(L, idx); return luaL_toint64(L, idx); } static inline uint32_t zigzag32(int32_t n) { return ((uint32_t)n << 1) ^ (uint32_t)(n >> 31); } static inline uint64_t zigzag64(int64_t n) { return ((uint64_t)n << 1) ^ (uint64_t)(n >> 63); } static inline uint32_t f32_to_u32(float f) { union { float f; uint32_t u; } pun; pun.f = f; return pun.u; } static inline uint64_t f64_to_u64(double d) { union { double d; uint64_t u; } pun; pun.d = d; return pun.u; } /* Resolve an enum field's Lua value to its int32 ordinal. Accepts * numbers (returned directly), cdata int64 (downcast), or strings * (looked up in field->enum_ref's by_name table). Mirrors the * `if type(v) == 'string' then nv = M.Status[v]` branch in mode=full. */ static int32_t enum_value_at(lua_State *L, pb_plan_field *f, int idx) { int t = lua_type(L, idx); if (t == LUA_TSTRING) { const char *s = lua_tostring(L, idx); if (f->enum_ref == LUA_NOREF) luaL_error(L, "enum field '%s' has no enum descriptor", s); lua_rawgeti(L, LUA_REGISTRYINDEX, f->enum_ref); lua_getfield(L, -1, "by_name"); if (!lua_istable(L, -1)) { luaL_error(L, "enum descriptor missing by_name"); } lua_pushvalue(L, idx); lua_rawget(L, -2); if (lua_isnil(L, -1)) { luaL_error(L, "unknown enum value '%s'", s); } int32_t v = (int32_t)lua_tointeger(L, -1); lua_pop(L, 3); /* value + by_name + desc */ return v; } return (int32_t)to_int64_at(L, idx); } /* Encode one singular scalar/enum/string field. Returns 1 if bytes * were written, 0 if the value collapsed to its proto3 default and * was suppressed. Zero-suppression skipped for proto3-optional. */ static int encode_one_field(lua_State *L, enc_buf *b, pb_plan_field *f, int val_idx) { switch (f->kind) { case PB_KIND_INT32: case PB_KIND_INT64: case PB_KIND_UINT32: case PB_KIND_UINT64: { uint64_t u = to_uint64_at(L, val_idx); if (!f->optional && u == 0) return 0; ebuf_reserve(L, b, f->tag_len + 10); ebuf_put_tag(b, f); ebuf_put_varint(b, u); return 1; } case PB_KIND_SINT32: { int32_t s = to_int32_at(L, val_idx); if (!f->optional && s == 0) return 0; ebuf_reserve(L, b, f->tag_len + 5); ebuf_put_tag(b, f); ebuf_put_varint(b, zigzag32(s)); return 1; } case PB_KIND_SINT64: { int64_t s = to_int64_at(L, val_idx); if (!f->optional && s == 0) return 0; ebuf_reserve(L, b, f->tag_len + 10); ebuf_put_tag(b, f); ebuf_put_varint(b, zigzag64(s)); return 1; } case PB_KIND_BOOL: { int truthy = lua_toboolean(L, val_idx); if (!f->optional && !truthy) return 0; ebuf_reserve(L, b, f->tag_len + 1); ebuf_put_tag(b, f); ebuf_put_byte(b, truthy ? 1 : 0); return 1; } case PB_KIND_FIXED32: case PB_KIND_SFIXED32: { uint64_t u = to_uint64_at(L, val_idx); uint32_t u32 = (uint32_t)u; if (!f->optional && u32 == 0) return 0; ebuf_reserve(L, b, f->tag_len + 4); ebuf_put_tag(b, f); ebuf_put_fixed32(b, u32); return 1; } case PB_KIND_FIXED64: case PB_KIND_SFIXED64: { uint64_t u = to_uint64_at(L, val_idx); if (!f->optional && u == 0) return 0; ebuf_reserve(L, b, f->tag_len + 8); ebuf_put_tag(b, f); ebuf_put_fixed64(b, u); return 1; } case PB_KIND_FLOAT: { double d = lua_tonumber(L, val_idx); uint32_t u = f32_to_u32((float)d); /* +0.0 -> u==0 (skip); -0.0 -> u==0x80000000 (emit). Matches the * Lua-side `(v ~= 0 or 1/v == -math.huge)` guard. */ if (!f->optional && u == 0) return 0; ebuf_reserve(L, b, f->tag_len + 4); ebuf_put_tag(b, f); ebuf_put_fixed32(b, u); return 1; } case PB_KIND_DOUBLE: { double d = lua_tonumber(L, val_idx); uint64_t u = f64_to_u64(d); if (!f->optional && u == 0) return 0; ebuf_reserve(L, b, f->tag_len + 8); ebuf_put_tag(b, f); ebuf_put_fixed64(b, u); return 1; } case PB_KIND_ENUM: { int32_t e = enum_value_at(L, f, val_idx); if (!f->optional && e == 0) return 0; ebuf_reserve(L, b, f->tag_len + 10); ebuf_put_tag(b, f); /* enum is wire-equivalent to int32: sign-extend to uint64 then varint. */ ebuf_put_varint(b, (uint64_t)(int64_t)e); return 1; } case PB_KIND_STRING: case PB_KIND_BYTES: { if (lua_type(L, val_idx) != LUA_TSTRING) luaL_error(L, "string/bytes field requires a string value"); size_t len; const char *s = lua_tolstring(L, val_idx, &len); if (!f->optional && len == 0) return 0; ebuf_reserve(L, b, f->tag_len + 10 + len); ebuf_put_tag(b, f); ebuf_put_varint(b, (uint64_t)len); if (len > 0) ebuf_put_bytes(b, (const uint8_t *)s, len); return 1; } default: /* Repeated/map/message are 3d/3e scope — silently skip. */ return 0; } } static int encode_lua(lua_State *L) { pb_plan *plan = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT); luaL_checktype(L, 2, LUA_TTABLE); int msg_idx = 2; if (plan->has_override) { return luaL_error(L, "C encode on plan with override is unsupported (bd-rmf)"); } /* Pin the field-names table on the stack for the loop's rawgeti. */ if (plan->field_names_ref == LUA_NOREF || plan->n_fields == 0) { lua_pushliteral(L, ""); return 1; } lua_rawgeti(L, LUA_REGISTRYINDEX, plan->field_names_ref); int names_idx = lua_gettop(L); enc_buf b; ebuf_init(&b); for (int i = 0; i < plan->n_fields; i++) { pb_plan_field *f = &plan->fields[i]; /* 3b scope: singular scalar/enum/string/bytes. Repeated, map, * and message-typed fields are silently skipped — they land * in 3d/3e and will overwrite this short-circuit. */ if (f->repeated) continue; if (f->kind == PB_KIND_MAP || f->kind == PB_KIND_MESSAGE) continue; lua_rawgeti(L, names_idx, i + 1); /* push field name */ lua_rawget(L, msg_idx); /* push msg[name] */ int val_idx = lua_gettop(L); if (lua_isnil(L, val_idx)) { lua_pop(L, 1); continue; } encode_one_field(L, &b, f, val_idx); lua_pop(L, 1); } lua_pushlstring(L, (const char *)ebuf_base(&b), b.used); return 1; } /* ---------------------------------------------------------------- * * Module entry. * * ---------------------------------------------------------------- */ static const struct luaL_Reg c_runtime_methods[] = { {"compile_plan", compile_plan_lua}, {"plan_n_fields", plan_n_fields}, {"plan_name", plan_name}, {"plan_field_info", plan_field_info}, {"plan_n_oneofs", plan_n_oneofs}, {"plan_oneof_info", plan_oneof_info}, {"plan_has_override", plan_has_override}, {"plan_sub_plan", plan_sub_plan}, {"encode", encode_lua}, {NULL, NULL}, }; static const struct luaL_Reg plan_mt_methods[] = { {"__gc", plan_gc}, {"__tostring", plan_tostring}, {NULL, NULL}, }; LUA_API int luaopen_pb_c_runtime(lua_State *L) { /* Register the plan metatable. */ luaL_newmetatable(L, PB_PLAN_MT); luaL_register(L, NULL, plan_mt_methods); lua_pop(L, 1); /* Build the module table. */ lua_newtable(L); luaL_register(L, NULL, c_runtime_methods); lua_pushliteral(L, PB_ABI_VERSION); lua_setfield(L, -2, "_abi_version"); /* Kind constants — exported so Lua tests can compare without * duplicating the enum. */ lua_createtable(L, 0, 19); lua_pushinteger(L, PB_KIND_NONE); lua_setfield(L, -2, "NONE"); lua_pushinteger(L, PB_KIND_INT32); lua_setfield(L, -2, "INT32"); lua_pushinteger(L, PB_KIND_INT64); lua_setfield(L, -2, "INT64"); lua_pushinteger(L, PB_KIND_UINT32); lua_setfield(L, -2, "UINT32"); lua_pushinteger(L, PB_KIND_UINT64); lua_setfield(L, -2, "UINT64"); lua_pushinteger(L, PB_KIND_SINT32); lua_setfield(L, -2, "SINT32"); lua_pushinteger(L, PB_KIND_SINT64); lua_setfield(L, -2, "SINT64"); lua_pushinteger(L, PB_KIND_FIXED32); lua_setfield(L, -2, "FIXED32"); lua_pushinteger(L, PB_KIND_FIXED64); lua_setfield(L, -2, "FIXED64"); lua_pushinteger(L, PB_KIND_SFIXED32); lua_setfield(L, -2, "SFIXED32"); lua_pushinteger(L, PB_KIND_SFIXED64); lua_setfield(L, -2, "SFIXED64"); lua_pushinteger(L, PB_KIND_FLOAT); lua_setfield(L, -2, "FLOAT"); lua_pushinteger(L, PB_KIND_DOUBLE); lua_setfield(L, -2, "DOUBLE"); lua_pushinteger(L, PB_KIND_BOOL); lua_setfield(L, -2, "BOOL"); lua_pushinteger(L, PB_KIND_STRING); lua_setfield(L, -2, "STRING"); lua_pushinteger(L, PB_KIND_BYTES); lua_setfield(L, -2, "BYTES"); lua_pushinteger(L, PB_KIND_ENUM); lua_setfield(L, -2, "ENUM"); lua_pushinteger(L, PB_KIND_MESSAGE); lua_setfield(L, -2, "MESSAGE"); lua_pushinteger(L, PB_KIND_MAP); lua_setfield(L, -2, "MAP"); lua_setfield(L, -2, "KIND"); lua_createtable(L, 0, 4); lua_pushinteger(L, PB_WIRE_VARINT); lua_setfield(L, -2, "VARINT"); lua_pushinteger(L, PB_WIRE_I64); lua_setfield(L, -2, "I64"); lua_pushinteger(L, PB_WIRE_LEN); lua_setfield(L, -2, "LEN"); lua_pushinteger(L, PB_WIRE_I32); lua_setfield(L, -2, "I32"); lua_setfield(L, -2, "WIRE"); return 1; }