-- prim_ffi.lua -- Strategy 2: per-primitive FFI bindings. -- -- Dispatch (per-field branch logic) is in Lua. Each wire primitive -- crosses the FFI boundary into prim.c. Used by spike_bench.lua via -- require('prim_ffi'). local ffi = require('ffi') local bit = require('bit') ffi.cdef[[ typedef struct ibuf_s { uint8_t *data; size_t len; size_t cap; } ibuf_t; void pb_ibuf_init(ibuf_t *b); void pb_ibuf_free(ibuf_t *b); void pb_ibuf_reset(ibuf_t *b); void pb_write_varint(ibuf_t *b, uint64_t v); void pb_write_bytes(ibuf_t *b, const uint8_t *src, size_t n); void pb_write_string_field(ibuf_t *b, uint32_t tag, const uint8_t *src, size_t n); const uint8_t *pb_read_varint(const uint8_t *p, const uint8_t *end, uint64_t *out); ]] local function find_lib() local SCRIPT_DIR = (debug.getinfo(1, 'S').source:match('@?(.*/)') or './') local UNAME = io.popen('uname -s'):read('*l') local ext = (UNAME == 'Darwin') and '.dylib' or '.so' return SCRIPT_DIR .. 'libpb_prim' .. ext end local C = ffi.load(find_lib()) local outbuf = ffi.new('ibuf_t') C.pb_ibuf_init(outbuf) local subbuf = ffi.new('ibuf_t') C.pb_ibuf_init(subbuf) local sub2buf = ffi.new('ibuf_t') -- for one extra level of nesting C.pb_ibuf_init(sub2buf) local v_out = ffi.new('uint64_t[1]') local rshift, band = bit.rshift, bit.band local M = {} -- ---------------------------------------------------------------- -- Address (sub-message used by Person.address) encode/decode helpers -- ---------------------------------------------------------------- local function address_encode_into(buf, t) if t.street then C.pb_write_string_field(buf, 0x0A, t.street, #t.street) end if t.city then C.pb_write_string_field(buf, 0x12, t.city, #t.city) end if t.zip then C.pb_write_varint(buf, 0x18) C.pb_write_varint(buf, t.zip) end end -- ---------------------------------------------------------------- -- Person encode -- ---------------------------------------------------------------- function M.Person_encode(t) C.pb_ibuf_reset(outbuf) if t.name then C.pb_write_string_field(outbuf, 0x0A, t.name, #t.name) end if t.age then C.pb_write_varint(outbuf, 0x10) C.pb_write_varint(outbuf, t.age) end if t.emails then local emails = t.emails for i = 1, #emails do local e = emails[i] C.pb_write_string_field(outbuf, 0x1A, e, #e) end end if t.address then C.pb_ibuf_reset(subbuf) address_encode_into(subbuf, t.address) C.pb_write_varint(outbuf, 0x2A) C.pb_write_varint(outbuf, subbuf.len) C.pb_write_bytes(outbuf, subbuf.data, subbuf.len) end if t.lucky_numbers then C.pb_ibuf_reset(subbuf) local lucky = t.lucky_numbers for i = 1, #lucky do C.pb_write_varint(subbuf, lucky[i]) end C.pb_write_varint(outbuf, 0x3A) C.pb_write_varint(outbuf, subbuf.len) C.pb_write_bytes(outbuf, subbuf.data, subbuf.len) end return ffi.string(outbuf.data, outbuf.len) end -- ---------------------------------------------------------------- -- Person decode -- ---------------------------------------------------------------- local function decode_address(p, endp) local result = {} while p < endp do p = C.pb_read_varint(p, endp, v_out) if p == nil then break end local tag = tonumber(v_out[0]) local field = rshift(tag, 3) local wt = band(tag, 7) if wt == 2 then p = C.pb_read_varint(p, endp, v_out) local slen = tonumber(v_out[0]) if field == 1 then result.street = ffi.string(p, slen) elseif field == 2 then result.city = ffi.string(p, slen) end p = p + slen elseif wt == 0 then p = C.pb_read_varint(p, endp, v_out) if field == 3 then result.zip = tonumber(v_out[0]) end else break end end return result end function M.Person_decode(s) local p = ffi.cast('const uint8_t*', s) local endp = p + #s local result = {} local emails = nil local n_emails = 0 local lucky = nil local n_lucky = 0 while p < endp do p = C.pb_read_varint(p, endp, v_out) if p == nil then break end local tag = tonumber(v_out[0]) local field = rshift(tag, 3) local wt = band(tag, 7) if wt == 2 then p = C.pb_read_varint(p, endp, v_out) local slen = tonumber(v_out[0]) if field == 1 then result.name = ffi.string(p, slen) p = p + slen elseif field == 3 then if emails == nil then emails = {} end n_emails = n_emails + 1 emails[n_emails] = ffi.string(p, slen) p = p + slen elseif field == 5 then result.address = decode_address(p, p + slen) p = p + slen elseif field == 7 then if lucky == nil then lucky = {} end local fend = p + slen while p < fend do p = C.pb_read_varint(p, fend, v_out) if p == nil then break end n_lucky = n_lucky + 1 lucky[n_lucky] = tonumber(v_out[0]) end else p = p + slen end elseif wt == 0 then p = C.pb_read_varint(p, endp, v_out) if field == 2 then result.age = tonumber(v_out[0]) end else break end end if emails then result.emails = emails end if lucky then result.lucky_numbers = lucky end return result end return M