/* * 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; } /* ---------------------------------------------------------------- * * 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}, {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; }