~bigbes/tarantool

tarantool-protobuf

117c6ef9c9919d3c49dc1712df9f3b604fdb607b — Eugene Blikh 2 months ago 7b3d690
codegen: type-elision for sint64/fixed64/sfixed64 encode (a7l)

For mode=full the field type is statically known, so the codegen can
skip wire.to_int64 / wire.to_uint64's runtime type dispatch by pre-
casting at the call site:

  -- before
  out[n] = wire.encode_sint64(v)        -- calls to_int64(v) inside
  out[n] = wire.encode_fixed64(v)       -- calls to_uint64(v) inside

  -- after
  out[n] = wire.encode_sint64_i(INT64(v))   -- direct cdata path
  out[n] = wire.encode_fixed64_u(UINT64(v))
  out[n] = wire.encode_sfixed64_u(UINT64(v))

Wire-level: added encode_sint64_i / encode_fixed64_u / encode_sfixed64_u
alongside the existing encoders; encode_fixed64 is now a thin wrapper
that calls to_uint64 + encode_fixed64_u so the generic API surface stays
intact. INT64/UINT64 added as file-header upvalues in generated modules.

int32/int64/uint32/uint64 unchanged: those alias to encode_varint, which
only dispatches through to_uint64 on the slow-slow cdata path (large
negatives) — not worth the codegen surface.

Applied at every encode emit site that took a scalar: singular,
required, repeated non-packed, extension singular, extension repeated,
packed slow path, proto2 extension repeated. Map-value path also picks
it up via the shared helper.

c_int64.Wide encode (full mode, 4-run median):
  lua-num inputs   2769 -> 2520 ns/op   (+9.9% throughput)
  cdata inputs    12993 -> 12935 ns/op  (unchanged; to_uint64 already
                                         takes the cdata fast path)

Just below the issue's optimistic 10-15% but exactly the case that
matters — Lua-number inputs are the common case for user code on
sint64/fixed64 fields. Suites: test 766/766, test-c 1057/1057.
M .beads/issues.jsonl => .beads/issues.jsonl +1 -1
@@ 69,7 69,7 @@
{"_type":"issue","id":"tarantool-protobuf-o8n","title":"gRPC: reference network transports (http_server, netbox, http_client_unary)","description":"Ship reference transports as separate Tarantool modules (deferred from M4). Designs in docs/specs/grpc_transports.md. Three transports: (1) pb.grpc.transport.http_server — Connect-JSON over HTTP/1.1 via tarantool/http; default external transport; works with browsers and curl without an HTTP/2 proxy. (2) pb.grpc.transport.netbox — gRPC tunneled over net.box calls; first-class in-cluster path. (3) pb.grpc.transport.http_client_unary — outbound unary only via http_client. HTTP/2 termination explicitly out of scope; the transport contract is HTTP/2-shaped so the same generated code works behind Envoy. Conformance anchor: connectrpc/conformance harness (memory: connect_conformance_suite).","status":"open","priority":3,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-05-17T15:47:14Z","created_by":"Eugene Blikh","updated_at":"2026-05-17T15:47:14Z","labels":["grpc","transport"],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-2me","title":"Decide lazy-default for repeated string|bytes fields","description":"Open design question. For routing/proxy workloads that decode but don't iterate every element, repeated LEN fields could stay as unread ArrayViews until first access. Eager callers pay one extra table alloc; lazy callers skip N per-element string allocations. At 100KB Person, 26+ emails = 26+ string allocations deferrable. Decide: (a) flip default for repeated LEN, (b) gate behind a codegen mode/option, (c) leave eager as default and rely on _decode_lazy. Surface compatibility implications (callers iterate with ipairs today and expect a real array) before deciding.","status":"open","priority":3,"issue_type":"decision","owner":"bigbes@gmail.com","created_at":"2026-05-17T15:47:13Z","created_by":"Eugene Blikh","updated_at":"2026-05-17T15:47:13Z","labels":["decoder","design","perf"],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-0u1","title":"Decoder: decode_len-style byte-then-varint symmetry in typed decoders","description":"The single-byte peek shortcut in decode_len (wire.lua:410-419) isn't replicated in decode_int32 / decode_uint32 / decode_int64 / decode_uint64 / decode_bool / decode_enum, which always call decode_varint(). Mirror the fast path in each typed decoder so the dominant 1-byte varint case skips a function call. Expected: 5-10% on scalar-heavy decodes. Memory: wire_2byte_varint_cliff (same shape, encode side, already fixed).","status":"open","priority":3,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-05-17T15:47:11Z","created_by":"Eugene Blikh","updated_at":"2026-05-17T15:47:11Z","labels":["decoder","perf","wire"],"dependencies":[{"issue_id":"tarantool-protobuf-0u1","depends_on_id":"tarantool-protobuf-0an","type":"blocks","created_at":"2026-05-17T18:47:21Z","created_by":"Eugene Blikh","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-a7l","title":"Encoder: static type-elision on scalars in mode=full","description":"wire.to_uint64(v) does a type(v) dispatch in three branches (number/cdata/boolean); in mode=full codegen the field's static type is known. Emit UINT64(v) / INT64(v) directly at the call site and let LuaJIT type-guard the trace. Expected: 10-15% on scalar-heavy messages.","status":"open","priority":3,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-17T15:47:07Z","created_by":"Eugene Blikh","updated_at":"2026-05-24T20:14:50Z","started_at":"2026-05-24T20:11:25Z","labels":["codegen","encoder","perf"],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-a7l","title":"Encoder: static type-elision on scalars in mode=full","description":"wire.to_uint64(v) does a type(v) dispatch in three branches (number/cdata/boolean); in mode=full codegen the field's static type is known. Emit UINT64(v) / INT64(v) directly at the call site and let LuaJIT type-guard the trace. Expected: 10-15% on scalar-heavy messages.","status":"closed","priority":3,"issue_type":"task","assignee":"Eugene Blikh","owner":"bigbes@gmail.com","created_at":"2026-05-17T15:47:07Z","created_by":"Eugene Blikh","updated_at":"2026-05-24T21:02:23Z","started_at":"2026-05-24T20:11:25Z","closed_at":"2026-05-24T21:02:23Z","close_reason":"Codegen emits typed fast variants for sint64/fixed64/sfixed64 — wire.encode_sint64_i(INT64(v)), wire.encode_fixed64_u(UINT64(v)), wire.encode_sfixed64_u(UINT64(v)) — skipping the to_int64/to_uint64 runtime type dispatch at every call site where the field type is statically known. Wire-level: added encode_sint64_i / encode_fixed64_u / encode_sfixed64_u alongside the existing encoders; encode_fixed64 is now a thin wrapper over encode_fixed64_u. INT64/UINT64 added as file-header upvalues alongside ffi.\n\nCodegen applied at: singular non-string scalar, singular required scalar, repeated non-packed per-element, extension singular, extension repeated per-element, packed scalar slow path, proto2 extension repeated. int32/int64/uint32/uint64 unchanged since they alias to encode_varint which doesn't call to_uint64 except on the slow-slow path.\n\nMeasured on c_int64.Wide encode (full mode, no PB_ENABLE_C, 4-run median):\n  lua-num inputs   2769 -\u003e 2520 ns/op   (+9.9% throughput)\n  cdata inputs    12993 -\u003e 12935 ns/op  (unchanged; to_int64 already takes the cdata fast path there)\n\nJust below the issue's 10-15% claim but real and measurable, and exactly the case where it matters (Lua-number-input is the common case for user code on sint64/fixed64 fields). Suites: test 766/766, test-c 1057/1057.","labels":["codegen","encoder","perf"],"dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-cma","title":"Encoder: inline zigzag_encode32 into encode_sint32 (one function call vs two)","description":"encode_sint32(v) = encode_varint(zigzag_encode32(v)) — two function calls. Inline as:\n\n  local function encode_sint32(v)\n      local n = bit.bxor(bit.lshift(v, 1), bit.arshift(v, 31))\n      -- existing 1-byte fast path\n      if n \u003e= 0 and n \u003c 0x80 then return string.char(n) end\n      return encode_varint_slow(n)\n  end\n\nSame for encode_sint64 (with the int64 zigzag formula).\n\nTiny win per sint encode — only matters for sint-heavy payloads. Filed for completeness; h8v makes this moot for mode=full (FFI direct writes inline the whole thing anyway). Real applicability: runtime mode encoders.","notes":"If h8v lands first, this becomes runtime-mode-only and may not be worth the duplication. Defer the call until h8v ships.","status":"open","priority":4,"issue_type":"task","owner":"bigbes@gmail.com","created_at":"2026-05-18T17:15:08Z","created_by":"Eugene Blikh","updated_at":"2026-05-18T17:15:08Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-yvc","title":"Feature: gRPC server reflection service","description":"PLAN.md section 8 lists gRPC server reflection as a non-goal until after M5. M5 is complete, so keep this as deferred future work. Implement standard reflection once real transport/server use cases require it.","status":"open","priority":4,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-05-17T16:29:18Z","created_by":"Eugene Blikh","updated_at":"2026-05-17T16:29:18Z","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"tarantool-protobuf-0j5","title":"Feature: Tarantool 3.x config integration for schema registration","description":"PLAN.md section 7 mentions a pb.types.\u003cname\u003e declarative config role as a possible Tarantool 3.x integration. Define the use case first, then design how protobuf schemas/descriptors are declared, loaded, validated, and made available to application roles.","status":"open","priority":4,"issue_type":"feature","owner":"bigbes@gmail.com","created_at":"2026-05-17T16:29:17Z","created_by":"Eugene Blikh","updated_at":"2026-05-17T16:29:17Z","dependency_count":0,"dependent_count":0,"comment_count":0}

M cmd/protoc-gen-tarantool/internal/gen/gen.go => cmd/protoc-gen-tarantool/internal/gen/gen.go +6 -0
@@ 223,6 223,12 @@ func emitHeader(w *writer, file *protogen.File) {
	// element count is recoverable from the LEN payload (2sn). LuaJIT
	// 2.1 (Tarantool's bundled fork) ships `table.new`.
	w.line("local table_new = require('table.new')")
	// Pre-typed cdata constructors for sint64/fixed64/sfixed64 singular
	// encode sites — the codegen casts at the call site instead of going
	// through wire.to_int64 / wire.to_uint64's runtime type dispatch (a7l).
	w.line("local ffi = require('ffi')")
	w.line("local INT64  = ffi.typeof('int64_t')")
	w.line("local UINT64 = ffi.typeof('uint64_t')")
}

// collectImports returns the deduplicated set of Lua require paths for all

M cmd/protoc-gen-tarantool/internal/gen/inline.go => cmd/protoc-gen-tarantool/internal/gen/inline.go +33 -13
@@ 10,6 10,26 @@ import (
	"google.golang.org/protobuf/reflect/protoreflect"
)

// encodeCallExpr returns the Lua expression that encodes a single value
// of the given scalar type `st`. For types where mode=full can elide
// wire.to_int64 / wire.to_uint64's runtime type dispatch (sint64, fixed64,
// sfixed64), the expression pre-casts the value to int64_t / uint64_t at
// the call site and routes through the typed fast variant (encode_sint64_i
// / encode_fixed64_u / encode_sfixed64_u). For everything else it emits
// the generic wire.encode_<st> call. INT64 and UINT64 are localized in
// the file header (see emitHeader). (a7l)
func encodeCallExpr(st, valExpr string) string {
	switch st {
	case "sint64":
		return fmt.Sprintf("wire.encode_sint64_i(INT64(%s))", valExpr)
	case "fixed64":
		return fmt.Sprintf("wire.encode_fixed64_u(UINT64(%s))", valExpr)
	case "sfixed64":
		return fmt.Sprintf("wire.encode_sfixed64_u(UINT64(%s))", valExpr)
	}
	return fmt.Sprintf("wire.encode_%s(%s)", st, valExpr)
}

// wireRefRe matches `wire.<name>` references inside generated function
// bodies. Generated code never embeds `wire.` inside a string literal, so a
// straight textual sweep is safe here. (Verified: error messages mention


@@ 314,8 334,8 @@ func emitPackedVarintElem(w *writer, indent, list, idx, valExpr, st string) {
		w.line("%s    %s = %s + 1; %s[%s] = CHARS[bit.bxor(bit.lshift(_e, 1), bit.arshift(_e, 31))]",
			indent, idx, idx, list, idx)
		w.line("%selse", indent)
		w.line("%s    %s = %s + 1; %s[%s] = wire.encode_%s(_e)",
			indent, idx, idx, list, idx, st)
		w.line("%s    %s = %s + 1; %s[%s] = %s",
			indent, idx, idx, list, idx, encodeCallExpr(st, "_e"))
		w.line("%send", indent)
	case "int32", "int64", "uint32", "uint64":
		w.line("%slocal _e = %s", indent, valExpr)


@@ 323,12 343,12 @@ func emitPackedVarintElem(w *writer, indent, list, idx, valExpr, st string) {
		w.line("%s    %s = %s + 1; %s[%s] = CHARS[_e]",
			indent, idx, idx, list, idx)
		w.line("%selse", indent)
		w.line("%s    %s = %s + 1; %s[%s] = wire.encode_%s(_e)",
			indent, idx, idx, list, idx, st)
		w.line("%s    %s = %s + 1; %s[%s] = %s",
			indent, idx, idx, list, idx, encodeCallExpr(st, "_e"))
		w.line("%send", indent)
	default:
		w.line("%s%s = %s + 1; %s[%s] = wire.encode_%s(%s)",
			indent, idx, idx, list, idx, st, valExpr)
		w.line("%s%s = %s + 1; %s[%s] = %s",
			indent, idx, idx, list, idx, encodeCallExpr(st, valExpr))
	}
}



@@ 455,7 475,7 @@ func emitInlineEncodeField(w *writer, f *protogen.Field, file *protogen.File, se
			w.line("        n = n + 1; out[n] = v")
		} else {
			w.line("        n = n + 1; out[n] = %s", tag)
			w.line("        n = n + 1; out[n] = wire.encode_%s(v)", st)
			w.line("        n = n + 1; out[n] = %s", encodeCallExpr(st, "v"))
		}
		w.line("    end")
	}


@@ 498,7 518,7 @@ func emitInlineEncodeRequiredField(w *writer, f *protogen.Field, tag string, fil
			w.line("    n = n + 1; out[n] = v")
		} else {
			w.line("    n = n + 1; out[n] = %s", tag)
			w.line("    n = n + 1; out[n] = wire.encode_%s(v)", st)
			w.line("    n = n + 1; out[n] = %s", encodeCallExpr(st, "v"))
		}
	}
}


@@ 579,7 599,7 @@ func emitInlineEncodeRepeated(w *writer, f *protogen.Field, tag, fname string, f
			w.line("        local _tag = %s", tag)
			w.line("        for _i = 1, #v do")
			w.line("            n = n + 1; out[n] = _tag")
			w.line("            n = n + 1; out[n] = wire.encode_%s(v[_i])", st)
			w.line("            n = n + 1; out[n] = %s", encodeCallExpr(st, "v[_i]"))
			w.line("        end")
			w.line("    end")
		}


@@ 646,7 666,7 @@ func emitInlineEncodeExtension(w *writer, ext *protogen.Extension, file *protoge
			w.line("            n = n + 1; out[n] = _ev")
		} else {
			w.line("            n = n + 1; out[n] = %s", tag)
			w.line("            n = n + 1; out[n] = wire.encode_%s(_ev)", st)
			w.line("            n = n + 1; out[n] = %s", encodeCallExpr(st, "_ev"))
		}
		w.line("        end")
	}


@@ 747,7 767,7 @@ func emitInlineEncodeExtensionRepeated(w *writer, ext *protogen.Extension, tag, 
			w.line("            local _tag = %s", tag)
			w.line("            for _i = 1, #_ev do")
			w.line("                n = n + 1; out[n] = _tag")
			w.line("                n = n + 1; out[n] = wire.encode_%s(_ev[_i])", st)
			w.line("                n = n + 1; out[n] = %s", encodeCallExpr(st, "_ev[_i]"))
			w.line("            end")
			w.line("        end")
		}


@@ 1373,8 1393,8 @@ func emitMapPiece(w *writer, list, idx, tag, valExpr string, f *protogen.Field, 
	default:
		st := scalarName(f.Desc.Kind())
		w.line("                %s = %s + 1; %s[%s] = %s", idx, idx, list, idx, tag)
		w.line("                %s = %s + 1; %s[%s] = wire.encode_%s(%s)",
			idx, idx, list, idx, st, valExpr)
		w.line("                %s = %s + 1; %s[%s] = %s",
			idx, idx, list, idx, encodeCallExpr(st, valExpr))
	}
}


M examples/expected/full/c_int64/c_int64_pb.lua => examples/expected/full/c_int64/c_int64_pb.lua +6 -3
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}



@@ 79,19 82,19 @@ function M.Wide_encode(t)
    v = t.a_sint64
    if v ~= nil and v ~= 0 then
        n = n + 1; out[n] = "\x18"
        n = n + 1; out[n] = wire.encode_sint64(v)
        n = n + 1; out[n] = wire.encode_sint64_i(INT64(v))
    end
    -- field 4: a_fixed64
    v = t.a_fixed64
    if v ~= nil and v ~= 0 then
        n = n + 1; out[n] = "\x21"
        n = n + 1; out[n] = wire.encode_fixed64(v)
        n = n + 1; out[n] = wire.encode_fixed64_u(UINT64(v))
    end
    -- field 5: a_sfixed64
    v = t.a_sfixed64
    if v ~= nil and v ~= 0 then
        n = n + 1; out[n] = "\x29"
        n = n + 1; out[n] = wire.encode_sfixed64(v)
        n = n + 1; out[n] = wire.encode_sfixed64_u(UINT64(v))
    end
    local _uf = t._unknown_fields
    if _uf ~= nil and _uf ~= '' then n = n + 1; out[n] = _uf end

M examples/expected/full/c_nested/c_nested_pb.lua => examples/expected/full/c_nested/c_nested_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/full/c_repeated/c_repeated_pb.lua => examples/expected/full/c_repeated/c_repeated_pb.lua +6 -3
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}



@@ 331,7 334,7 @@ function M.Holder_new(t) return t or {} end
---@param t c_repeated.Holder
---@return string
function M.Holder_encode(t)
    local encode_fixed64 = wire.encode_fixed64
    local encode_fixed64_u = wire.encode_fixed64_u
    local encode_int32 = wire.encode_int32
    local encode_sint32 = wire.encode_sint32
    local encode_varint = wire.encode_varint


@@ 461,7 464,7 @@ function M.Holder_encode(t)
        local _n = #v
        local parts, m = table_new(_n, 0), 0
        for _i = 1, _n do
            m = m + 1; parts[m] = encode_fixed64(v[_i])
            m = m + 1; parts[m] = encode_fixed64_u(UINT64(v[_i]))
        end
        local _b = table.concat(parts)
        n = n + 1; out[n] = "\x32"


@@ 542,7 545,7 @@ function M.Holder_encode(t)
        local _tag = "\xa9\x01"
        for _i = 1, #v do
            n = n + 1; out[n] = _tag
            n = n + 1; out[n] = encode_fixed64(v[_i])
            n = n + 1; out[n] = encode_fixed64_u(UINT64(v[_i]))
        end
    end
    -- field 22: unpacked_sint32

M examples/expected/full/conformance/conformance_pb.lua => examples/expected/full/conformance/conformance_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/full/hello/hello_pb.lua => examples/expected/full/hello/hello_pb.lua +4 -1
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}



@@ 1915,7 1918,7 @@ function M.Person_encode(t)
    v = t.user_id
    if v ~= nil and v ~= 0 then
        n = n + 1; out[n] = "\x49"
        n = n + 1; out[n] = wire.encode_fixed64(v)
        n = n + 1; out[n] = wire.encode_fixed64_u(UINT64(v))
    end
    -- field 10: balance
    v = t.balance

M examples/expected/full/proto2_basic/proto2_basic_pb.lua => examples/expected/full/proto2_basic/proto2_basic_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/full/protobuf_test_messages/proto2/test_messages_proto2_pb.lua => examples/expected/full/protobuf_test_messages/proto2/test_messages_proto2_pb.lua +36 -33
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}



@@ 908,15 911,15 @@ function M.TestAllTypesProto2_encode(t)
    local encode_bool = wire.encode_bool
    local encode_double = wire.encode_double
    local encode_fixed32 = wire.encode_fixed32
    local encode_fixed64 = wire.encode_fixed64
    local encode_fixed64_u = wire.encode_fixed64_u
    local encode_float = wire.encode_float
    local encode_int32 = wire.encode_int32
    local encode_int64 = wire.encode_int64
    local encode_len = wire.encode_len
    local encode_sfixed32 = wire.encode_sfixed32
    local encode_sfixed64 = wire.encode_sfixed64
    local encode_sfixed64_u = wire.encode_sfixed64_u
    local encode_sint32 = wire.encode_sint32
    local encode_sint64 = wire.encode_sint64
    local encode_sint64_i = wire.encode_sint64_i
    local encode_string = wire.encode_string
    local encode_uint32 = wire.encode_uint32
    local encode_uint64 = wire.encode_uint64


@@ 975,7 978,7 @@ function M.TestAllTypesProto2_encode(t)
    v = t.optional_sint64
    if v ~= nil then
        n = n + 1; out[n] = "\x30"
        n = n + 1; out[n] = encode_sint64(v)
        n = n + 1; out[n] = encode_sint64_i(INT64(v))
    end
    -- field 7: optional_fixed32
    v = t.optional_fixed32


@@ 987,7 990,7 @@ function M.TestAllTypesProto2_encode(t)
    v = t.optional_fixed64
    if v ~= nil then
        n = n + 1; out[n] = "\x41"
        n = n + 1; out[n] = encode_fixed64(v)
        n = n + 1; out[n] = encode_fixed64_u(UINT64(v))
    end
    -- field 9: optional_sfixed32
    v = t.optional_sfixed32


@@ 999,7 1002,7 @@ function M.TestAllTypesProto2_encode(t)
    v = t.optional_sfixed64
    if v ~= nil then
        n = n + 1; out[n] = "\x51"
        n = n + 1; out[n] = encode_sfixed64(v)
        n = n + 1; out[n] = encode_sfixed64_u(UINT64(v))
    end
    -- field 11: optional_float
    v = t.optional_float


@@ 1179,7 1182,7 @@ function M.TestAllTypesProto2_encode(t)
        local _tag = "\xa0\x02"
        for _i = 1, #v do
            n = n + 1; out[n] = _tag
            n = n + 1; out[n] = encode_sint64(v[_i])
            n = n + 1; out[n] = encode_sint64_i(INT64(v[_i]))
        end
    end
    -- field 37: repeated_fixed32


@@ 1197,7 1200,7 @@ function M.TestAllTypesProto2_encode(t)
        local _tag = "\xb1\x02"
        for _i = 1, #v do
            n = n + 1; out[n] = _tag
            n = n + 1; out[n] = encode_fixed64(v[_i])
            n = n + 1; out[n] = encode_fixed64_u(UINT64(v[_i]))
        end
    end
    -- field 39: repeated_sfixed32


@@ 1215,7 1218,7 @@ function M.TestAllTypesProto2_encode(t)
        local _tag = "\xc1\x02"
        for _i = 1, #v do
            n = n + 1; out[n] = _tag
            n = n + 1; out[n] = encode_sfixed64(v[_i])
            n = n + 1; out[n] = encode_sfixed64_u(UINT64(v[_i]))
        end
    end
    -- field 41: repeated_float


@@ 1524,7 1527,7 @@ function M.TestAllTypesProto2_encode(t)
            if type(_e) == 'number' and _e >= -64 and _e <= 63 then
                m = m + 1; parts[m] = CHARS[bit.bxor(bit.lshift(_e, 1), bit.arshift(_e, 31))]
            else
                m = m + 1; parts[m] = encode_sint64(_e)
                m = m + 1; parts[m] = encode_sint64_i(INT64(_e))
            end
        end
        local _b = table.concat(parts)


@@ 1561,7 1564,7 @@ function M.TestAllTypesProto2_encode(t)
        local _n = #v
        local parts, m = table_new(_n, 0), 0
        for _i = 1, _n do
            m = m + 1; parts[m] = encode_fixed64(v[_i])
            m = m + 1; parts[m] = encode_fixed64_u(UINT64(v[_i]))
        end
        local _b = table.concat(parts)
        n = n + 1; out[n] = "\x92\x05"


@@ 1597,7 1600,7 @@ function M.TestAllTypesProto2_encode(t)
        local _n = #v
        local parts, m = table_new(_n, 0), 0
        for _i = 1, _n do
            m = m + 1; parts[m] = encode_sfixed64(v[_i])
            m = m + 1; parts[m] = encode_sfixed64_u(UINT64(v[_i]))
        end
        local _b = table.concat(parts)
        n = n + 1; out[n] = "\xa2\x05"


@@ 1743,7 1746,7 @@ function M.TestAllTypesProto2_encode(t)
        local _tag = "\xf0\x05"
        for _i = 1, #v do
            n = n + 1; out[n] = _tag
            n = n + 1; out[n] = encode_sint64(v[_i])
            n = n + 1; out[n] = encode_sint64_i(INT64(v[_i]))
        end
    end
    -- field 95: unpacked_fixed32


@@ 1761,7 1764,7 @@ function M.TestAllTypesProto2_encode(t)
        local _tag = "\x81\x06"
        for _i = 1, #v do
            n = n + 1; out[n] = _tag
            n = n + 1; out[n] = encode_fixed64(v[_i])
            n = n + 1; out[n] = encode_fixed64_u(UINT64(v[_i]))
        end
    end
    -- field 97: unpacked_sfixed32


@@ 1779,7 1782,7 @@ function M.TestAllTypesProto2_encode(t)
        local _tag = "\x91\x06"
        for _i = 1, #v do
            n = n + 1; out[n] = _tag
            n = n + 1; out[n] = encode_sfixed64(v[_i])
            n = n + 1; out[n] = encode_sfixed64_u(UINT64(v[_i]))
        end
    end
    -- field 99: unpacked_float


@@ 1971,11 1974,11 @@ function M.TestAllTypesProto2_encode(t)
            local entry, _m = {}, 0
            if _k ~= 0 then
                _m = _m + 1; entry[_m] = _ktag
                _m = _m + 1; entry[_m] = encode_sint64(_k)
                _m = _m + 1; entry[_m] = encode_sint64_i(INT64(_k))
            end
            if _val ~= 0 then
                _m = _m + 1; entry[_m] = _vtag
                _m = _m + 1; entry[_m] = encode_sint64(_val)
                _m = _m + 1; entry[_m] = encode_sint64_i(INT64(_val))
            end
            n = n + 1; out[n] = _tag
            local _b = table.concat(entry)


@@ 2021,11 2024,11 @@ function M.TestAllTypesProto2_encode(t)
            local entry, _m = {}, 0
            if _k ~= 0 then
                _m = _m + 1; entry[_m] = _ktag
                _m = _m + 1; entry[_m] = encode_fixed64(_k)
                _m = _m + 1; entry[_m] = encode_fixed64_u(UINT64(_k))
            end
            if _val ~= 0 then
                _m = _m + 1; entry[_m] = _vtag
                _m = _m + 1; entry[_m] = encode_fixed64(_val)
                _m = _m + 1; entry[_m] = encode_fixed64_u(UINT64(_val))
            end
            n = n + 1; out[n] = _tag
            local _b = table.concat(entry)


@@ 2071,11 2074,11 @@ function M.TestAllTypesProto2_encode(t)
            local entry, _m = {}, 0
            if _k ~= 0 then
                _m = _m + 1; entry[_m] = _ktag
                _m = _m + 1; entry[_m] = encode_sfixed64(_k)
                _m = _m + 1; entry[_m] = encode_sfixed64_u(UINT64(_k))
            end
            if _val ~= 0 then
                _m = _m + 1; entry[_m] = _vtag
                _m = _m + 1; entry[_m] = encode_sfixed64(_val)
                _m = _m + 1; entry[_m] = encode_sfixed64_u(UINT64(_val))
            end
            n = n + 1; out[n] = _tag
            local _b = table.concat(entry)


@@ 2499,7 2502,7 @@ function M.TestAllTypesProto2_encode(t)
    v = t.default_sint64
    if v ~= nil then
        n = n + 1; out[n] = "\xb0\x0f"
        n = n + 1; out[n] = encode_sint64(v)
        n = n + 1; out[n] = encode_sint64_i(INT64(v))
    end
    -- field 247: default_fixed32
    v = t.default_fixed32


@@ 2511,7 2514,7 @@ function M.TestAllTypesProto2_encode(t)
    v = t.default_fixed64
    if v ~= nil then
        n = n + 1; out[n] = "\xc1\x0f"
        n = n + 1; out[n] = encode_fixed64(v)
        n = n + 1; out[n] = encode_fixed64_u(UINT64(v))
    end
    -- field 249: default_sfixed32
    v = t.default_sfixed32


@@ 2523,7 2526,7 @@ function M.TestAllTypesProto2_encode(t)
    v = t.default_sfixed64
    if v ~= nil then
        n = n + 1; out[n] = "\xd1\x0f"
        n = n + 1; out[n] = encode_sfixed64(v)
        n = n + 1; out[n] = encode_sfixed64_u(UINT64(v))
    end
    -- field 251: default_float
    v = t.default_float


@@ 9920,14 9923,14 @@ function M.TestAllRequiredTypesProto2_encode(t)
    local encode_bool = wire.encode_bool
    local encode_double = wire.encode_double
    local encode_fixed32 = wire.encode_fixed32
    local encode_fixed64 = wire.encode_fixed64
    local encode_fixed64_u = wire.encode_fixed64_u
    local encode_float = wire.encode_float
    local encode_int32 = wire.encode_int32
    local encode_int64 = wire.encode_int64
    local encode_sfixed32 = wire.encode_sfixed32
    local encode_sfixed64 = wire.encode_sfixed64
    local encode_sfixed64_u = wire.encode_sfixed64_u
    local encode_sint32 = wire.encode_sint32
    local encode_sint64 = wire.encode_sint64
    local encode_sint64_i = wire.encode_sint64_i
    local encode_uint32 = wire.encode_uint32
    local encode_uint64 = wire.encode_uint64
    local encode_varint = wire.encode_varint


@@ 9982,7 9985,7 @@ function M.TestAllRequiredTypesProto2_encode(t)
        error("required field missing on encode: protobuf_test_messages.proto2.TestAllRequiredTypesProto2.required_sint64", 0)
    end
    n = n + 1; out[n] = "\x30"
    n = n + 1; out[n] = encode_sint64(v)
    n = n + 1; out[n] = encode_sint64_i(INT64(v))
    -- field 7: required_fixed32
    v = t.required_fixed32
    if v == nil then


@@ 9996,7 9999,7 @@ function M.TestAllRequiredTypesProto2_encode(t)
        error("required field missing on encode: protobuf_test_messages.proto2.TestAllRequiredTypesProto2.required_fixed64", 0)
    end
    n = n + 1; out[n] = "\x41"
    n = n + 1; out[n] = encode_fixed64(v)
    n = n + 1; out[n] = encode_fixed64_u(UINT64(v))
    -- field 9: required_sfixed32
    v = t.required_sfixed32
    if v == nil then


@@ 10010,7 10013,7 @@ function M.TestAllRequiredTypesProto2_encode(t)
        error("required field missing on encode: protobuf_test_messages.proto2.TestAllRequiredTypesProto2.required_sfixed64", 0)
    end
    n = n + 1; out[n] = "\x51"
    n = n + 1; out[n] = encode_sfixed64(v)
    n = n + 1; out[n] = encode_sfixed64_u(UINT64(v))
    -- field 11: required_float
    v = t.required_float
    if v == nil then


@@ 10222,7 10225,7 @@ function M.TestAllRequiredTypesProto2_encode(t)
        error("required field missing on encode: protobuf_test_messages.proto2.TestAllRequiredTypesProto2.default_sint64", 0)
    end
    n = n + 1; out[n] = "\xb0\x0f"
    n = n + 1; out[n] = encode_sint64(v)
    n = n + 1; out[n] = encode_sint64_i(INT64(v))
    -- field 247: default_fixed32
    v = t.default_fixed32
    if v == nil then


@@ 10236,7 10239,7 @@ function M.TestAllRequiredTypesProto2_encode(t)
        error("required field missing on encode: protobuf_test_messages.proto2.TestAllRequiredTypesProto2.default_fixed64", 0)
    end
    n = n + 1; out[n] = "\xc1\x0f"
    n = n + 1; out[n] = encode_fixed64(v)
    n = n + 1; out[n] = encode_fixed64_u(UINT64(v))
    -- field 249: default_sfixed32
    v = t.default_sfixed32
    if v == nil then


@@ 10250,7 10253,7 @@ function M.TestAllRequiredTypesProto2_encode(t)
        error("required field missing on encode: protobuf_test_messages.proto2.TestAllRequiredTypesProto2.default_sfixed64", 0)
    end
    n = n + 1; out[n] = "\xd1\x0f"
    n = n + 1; out[n] = encode_sfixed64(v)
    n = n + 1; out[n] = encode_sfixed64_u(UINT64(v))
    -- field 251: default_float
    v = t.default_float
    if v == nil then

M examples/expected/full/protobuf_test_messages/proto3/test_messages_proto3_pb.lua => examples/expected/full/protobuf_test_messages/proto3/test_messages_proto3_pb.lua +24 -21
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}



@@ 602,15 605,15 @@ function M.TestAllTypesProto3_encode(t)
    local encode_bool = wire.encode_bool
    local encode_double = wire.encode_double
    local encode_fixed32 = wire.encode_fixed32
    local encode_fixed64 = wire.encode_fixed64
    local encode_fixed64_u = wire.encode_fixed64_u
    local encode_float = wire.encode_float
    local encode_int32 = wire.encode_int32
    local encode_int64 = wire.encode_int64
    local encode_len = wire.encode_len
    local encode_sfixed32 = wire.encode_sfixed32
    local encode_sfixed64 = wire.encode_sfixed64
    local encode_sfixed64_u = wire.encode_sfixed64_u
    local encode_sint32 = wire.encode_sint32
    local encode_sint64 = wire.encode_sint64
    local encode_sint64_i = wire.encode_sint64_i
    local encode_string = wire.encode_string
    local encode_uint32 = wire.encode_uint32
    local encode_uint64 = wire.encode_uint64


@@ 670,7 673,7 @@ function M.TestAllTypesProto3_encode(t)
    v = t.optional_sint64
    if v ~= nil and v ~= 0 then
        n = n + 1; out[n] = "\x30"
        n = n + 1; out[n] = encode_sint64(v)
        n = n + 1; out[n] = encode_sint64_i(INT64(v))
    end
    -- field 7: optional_fixed32
    v = t.optional_fixed32


@@ 682,7 685,7 @@ function M.TestAllTypesProto3_encode(t)
    v = t.optional_fixed64
    if v ~= nil and v ~= 0 then
        n = n + 1; out[n] = "\x41"
        n = n + 1; out[n] = encode_fixed64(v)
        n = n + 1; out[n] = encode_fixed64_u(UINT64(v))
    end
    -- field 9: optional_sfixed32
    v = t.optional_sfixed32


@@ 694,7 697,7 @@ function M.TestAllTypesProto3_encode(t)
    v = t.optional_sfixed64
    if v ~= nil and v ~= 0 then
        n = n + 1; out[n] = "\x51"
        n = n + 1; out[n] = encode_sfixed64(v)
        n = n + 1; out[n] = encode_sfixed64_u(UINT64(v))
    end
    -- field 11: optional_float
    v = t.optional_float


@@ 965,7 968,7 @@ function M.TestAllTypesProto3_encode(t)
            if type(_e) == 'number' and _e >= -64 and _e <= 63 then
                m = m + 1; parts[m] = CHARS[bit.bxor(bit.lshift(_e, 1), bit.arshift(_e, 31))]
            else
                m = m + 1; parts[m] = encode_sint64(_e)
                m = m + 1; parts[m] = encode_sint64_i(INT64(_e))
            end
        end
        local _b = table.concat(parts)


@@ 1002,7 1005,7 @@ function M.TestAllTypesProto3_encode(t)
        local _n = #v
        local parts, m = table_new(_n, 0), 0
        for _i = 1, _n do
            m = m + 1; parts[m] = encode_fixed64(v[_i])
            m = m + 1; parts[m] = encode_fixed64_u(UINT64(v[_i]))
        end
        local _b = table.concat(parts)
        n = n + 1; out[n] = "\xb2\x02"


@@ 1038,7 1041,7 @@ function M.TestAllTypesProto3_encode(t)
        local _n = #v
        local parts, m = table_new(_n, 0), 0
        for _i = 1, _n do
            m = m + 1; parts[m] = encode_sfixed64(v[_i])
            m = m + 1; parts[m] = encode_sfixed64_u(UINT64(v[_i]))
        end
        local _b = table.concat(parts)
        n = n + 1; out[n] = "\xc2\x02"


@@ 1383,7 1386,7 @@ function M.TestAllTypesProto3_encode(t)
            if type(_e) == 'number' and _e >= -64 and _e <= 63 then
                m = m + 1; parts[m] = CHARS[bit.bxor(bit.lshift(_e, 1), bit.arshift(_e, 31))]
            else
                m = m + 1; parts[m] = encode_sint64(_e)
                m = m + 1; parts[m] = encode_sint64_i(INT64(_e))
            end
        end
        local _b = table.concat(parts)


@@ 1420,7 1423,7 @@ function M.TestAllTypesProto3_encode(t)
        local _n = #v
        local parts, m = table_new(_n, 0), 0
        for _i = 1, _n do
            m = m + 1; parts[m] = encode_fixed64(v[_i])
            m = m + 1; parts[m] = encode_fixed64_u(UINT64(v[_i]))
        end
        local _b = table.concat(parts)
        n = n + 1; out[n] = "\x92\x05"


@@ 1456,7 1459,7 @@ function M.TestAllTypesProto3_encode(t)
        local _n = #v
        local parts, m = table_new(_n, 0), 0
        for _i = 1, _n do
            m = m + 1; parts[m] = encode_sfixed64(v[_i])
            m = m + 1; parts[m] = encode_sfixed64_u(UINT64(v[_i]))
        end
        local _b = table.concat(parts)
        n = n + 1; out[n] = "\xa2\x05"


@@ 1602,7 1605,7 @@ function M.TestAllTypesProto3_encode(t)
        local _tag = "\xf0\x05"
        for _i = 1, #v do
            n = n + 1; out[n] = _tag
            n = n + 1; out[n] = encode_sint64(v[_i])
            n = n + 1; out[n] = encode_sint64_i(INT64(v[_i]))
        end
    end
    -- field 95: unpacked_fixed32


@@ 1620,7 1623,7 @@ function M.TestAllTypesProto3_encode(t)
        local _tag = "\x81\x06"
        for _i = 1, #v do
            n = n + 1; out[n] = _tag
            n = n + 1; out[n] = encode_fixed64(v[_i])
            n = n + 1; out[n] = encode_fixed64_u(UINT64(v[_i]))
        end
    end
    -- field 97: unpacked_sfixed32


@@ 1638,7 1641,7 @@ function M.TestAllTypesProto3_encode(t)
        local _tag = "\x91\x06"
        for _i = 1, #v do
            n = n + 1; out[n] = _tag
            n = n + 1; out[n] = encode_sfixed64(v[_i])
            n = n + 1; out[n] = encode_sfixed64_u(UINT64(v[_i]))
        end
    end
    -- field 99: unpacked_float


@@ 1830,11 1833,11 @@ function M.TestAllTypesProto3_encode(t)
            local entry, _m = {}, 0
            if _k ~= 0 then
                _m = _m + 1; entry[_m] = _ktag
                _m = _m + 1; entry[_m] = encode_sint64(_k)
                _m = _m + 1; entry[_m] = encode_sint64_i(INT64(_k))
            end
            if _val ~= 0 then
                _m = _m + 1; entry[_m] = _vtag
                _m = _m + 1; entry[_m] = encode_sint64(_val)
                _m = _m + 1; entry[_m] = encode_sint64_i(INT64(_val))
            end
            n = n + 1; out[n] = _tag
            local _b = table.concat(entry)


@@ 1880,11 1883,11 @@ function M.TestAllTypesProto3_encode(t)
            local entry, _m = {}, 0
            if _k ~= 0 then
                _m = _m + 1; entry[_m] = _ktag
                _m = _m + 1; entry[_m] = encode_fixed64(_k)
                _m = _m + 1; entry[_m] = encode_fixed64_u(UINT64(_k))
            end
            if _val ~= 0 then
                _m = _m + 1; entry[_m] = _vtag
                _m = _m + 1; entry[_m] = encode_fixed64(_val)
                _m = _m + 1; entry[_m] = encode_fixed64_u(UINT64(_val))
            end
            n = n + 1; out[n] = _tag
            local _b = table.concat(entry)


@@ 1930,11 1933,11 @@ function M.TestAllTypesProto3_encode(t)
            local entry, _m = {}, 0
            if _k ~= 0 then
                _m = _m + 1; entry[_m] = _ktag
                _m = _m + 1; entry[_m] = encode_sfixed64(_k)
                _m = _m + 1; entry[_m] = encode_sfixed64_u(UINT64(_k))
            end
            if _val ~= 0 then
                _m = _m + 1; entry[_m] = _vtag
                _m = _m + 1; entry[_m] = encode_sfixed64(_val)
                _m = _m + 1; entry[_m] = encode_sfixed64_u(UINT64(_val))
            end
            n = n + 1; out[n] = _tag
            local _b = table.concat(entry)

M examples/expected/full/quickstart/quickstart_pb.lua => examples/expected/full/quickstart/quickstart_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/runtime/c_int64/c_int64_pb.lua => examples/expected/runtime/c_int64/c_int64_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/runtime/c_nested/c_nested_pb.lua => examples/expected/runtime/c_nested/c_nested_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/runtime/c_repeated/c_repeated_pb.lua => examples/expected/runtime/c_repeated/c_repeated_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/runtime/conformance/conformance_pb.lua => examples/expected/runtime/conformance/conformance_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/runtime/hello/hello_pb.lua => examples/expected/runtime/hello/hello_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/runtime/proto2_basic/proto2_basic_pb.lua => examples/expected/runtime/proto2_basic/proto2_basic_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/runtime/protobuf_test_messages/proto2/test_messages_proto2_pb.lua => examples/expected/runtime/protobuf_test_messages/proto2/test_messages_proto2_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/runtime/protobuf_test_messages/proto3/test_messages_proto3_pb.lua => examples/expected/runtime/protobuf_test_messages/proto3/test_messages_proto3_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M examples/expected/runtime/quickstart/quickstart_pb.lua => examples/expected/runtime/quickstart/quickstart_pb.lua +3 -0
@@ 11,6 11,9 @@ local band = bit.band
local rshift = bit.rshift
local CHARS = wire.CHARS
local table_new = require('table.new')
local ffi = require('ffi')
local INT64  = ffi.typeof('int64_t')
local UINT64 = ffi.typeof('uint64_t')

local M = {}


M runtime/pb/wire.lua => runtime/pb/wire.lua +30 -5
@@ 286,6 286,16 @@ local function zigzag_encode64(n)
end
M.zigzag_encode64 = zigzag_encode64

-- Variant that takes an int64_t cdata directly, skipping the to_int64()
-- type-dispatch wrapper. Use from mode=full codegen sites where the field
-- type is statically known (a7l).
local function zigzag_encode64_i(i)
    local doubled = bit.lshift(UINT64(i), 1)
    if i >= 0 then return doubled end
    return bit.bnot(doubled)
end
M.zigzag_encode64_i = zigzag_encode64_i

local function zigzag_decode64(u)
    u = to_uint64(u)
    local half = bit.rshift(u, 1)


@@ 323,8 333,8 @@ local function decode_fixed32(buf, pos)
end
M.decode_fixed32 = decode_fixed32

local function encode_fixed64(v)
    local u = to_uint64(v)
-- Body shared by encode_fixed64 and encode_fixed64_u. Takes a uint64_t cdata.
local function encode_fixed64_u(u)
    local lo = tonumber(bit.band(u, 0xffffffff))
    local hi = tonumber(bit.rshift(u, 32))
    return string.char(


@@ 337,7 347,16 @@ local function encode_fixed64(v)
        bit.band(bit.rshift(hi, 16), 0xff),
        bit.band(bit.rshift(hi, 24), 0xff))
end
M.encode_fixed64 = encode_fixed64
-- Generic wrapper: accepts Lua number / cdata / boolean via to_uint64.
local function encode_fixed64(v)
    return encode_fixed64_u(to_uint64(v))
end
M.encode_fixed64   = encode_fixed64
-- Variant for mode=full codegen sites where the value is already a uint64_t
-- cdata (caller wraps with UINT64() at the static-type-known call site).
-- Used for fixed64 and sfixed64 (sfixed64 reuses this body — same bit
-- pattern, different sign interpretation). (a7l)
M.encode_fixed64_u = encode_fixed64_u

-- decode_fixed64(buf, pos) -> uint64_t cdata, new_pos
-- Reading via the FFI union avoids the multi-step `UINT64(lo) +


@@ 461,8 480,14 @@ M.encode_sint32 = encode_sint32
M.encode_sint64 = encode_sint64
M.encode_bool   = encode_bool

M.encode_sfixed32 = encode_fixed32  -- bits are identical, only interpretation differs
M.encode_sfixed64 = encode_fixed64
-- Fast variant for mode=full codegen: takes an int64_t cdata, skips to_int64.
local function encode_sint64_i(i) return encode_varint(zigzag_encode64_i(i)) end
M.encode_sint64_i = encode_sint64_i

M.encode_sfixed32   = encode_fixed32  -- bits are identical, only interpretation differs
M.encode_sfixed64   = encode_fixed64
-- sfixed64 reuses encode_fixed64_u — same 8-byte little-endian shape.
M.encode_sfixed64_u = encode_fixed64_u
M.encode_string   = encode_len
M.encode_bytes    = encode_len
-- (encode_fixed32, encode_fixed64, encode_float, encode_double already on M)