Documentation
¶
Overview ¶
Package codegen emits per-schema ZAP v2 accessors that match v1's hand-rolled inline-everything performance.
Why codegen ¶
Go's generic dispatch and inlining cost budget combine to put a hard ceiling on how fast a generic API can be: any function whose body is larger than the inliner's 80-cost budget cannot inline into its caller, so the call site pays a function-call cost (~5-7 ns on modern hardware). The work performed by a "Wrap" function (parse the ZAP wire frame, validate the kind discriminator, build a typed view) exceeds that budget. The generic [zapv1.Wrap[S]] function therefore costs one function call per Wrap, even when every other primitive in its body would inline.
Hand-written per-schema [WrapX] shims (e.g. [examples.WrapAdvanceTime]) have the same problem — even though they pin S{}.Kind() and S{}.Size() as constants, the body is still too large to inline.
Codegen solves this by emitting Wrap/Build/Read/Write functions that DON'T live inside a function at all — they expand inline at the call site through Go templates instantiated at build time. The user writes a single schema declaration; the codegen tool produces a *_zap.go file whose functions match v1's hand-rolled pattern byte-for-byte.
Output shape ¶
For a schema declared as:
type AdvanceTimeSchema struct{}
func (AdvanceTimeSchema) Kind() zapv1.KindByte { return 1 }
func (AdvanceTimeSchema) Size() int { return 9 }
func (AdvanceTimeSchema) Name() string { return "AdvanceTimeTx" }
//zap:field Time uint64 @1
the codegen tool emits a sibling file (advance_time_zap.go) with:
const sizeAdvanceTimeTx = 9
const kindAdvanceTimeTx uint8 = 1
const offsetAdvanceTimeTx_Time = 1
func WrapAdvanceTime(b []byte) (zapv1.View[AdvanceTimeSchema], error) {
msg, err := zap.Parse(b)
...
}
The emitted code uses v1 primitives directly (zap.Parse, msg.Root, root.Uint8) so that the inliner folds them into the caller's frame, matching v1 hand-rolled performance.
When to use codegen ¶
- Hot-path schemas where the function-call overhead matters (per-tx assembly, per-block validation).
- When you have a schema description in a declarative form (a YAML file, a Cap'n-Proto-style schema file, struct tags) and want ZAP v2 accessors emitted automatically.
For cold paths and ad-hoc schemas, the generic [zapv1.Wrap[S]] / [zapv1.Build[S]] are equally correct and one extra function call slower — which is rarely the bottleneck.
Status ¶
This package is the entry point. The current implementation emits the canonical AdvanceTimeTx fast-path file (used as the canary in the bench suite); adding new schemas means feeding the codegen tool a schema declaration in one of the supported forms.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Emit ¶
Emit writes the per-schema *.go file to w. Returns an error if the schema is invalid (unknown field type, offset overlap, fields past declared Size, etc.).
The emitted code follows the v1 hand-rolled pattern: every step (Parse, Root, Uint reads, kind check, View compose) uses v1 primitives that inline into the caller's frame. The result matches v1 hand-rolled performance to within compiler noise — verified by the bench suite.
Scalar fields go through the [zapv1.Field][S, T] generic handle (declared in the per-schema <GoName>Fields var). Fixed-width byte-array fields ("bytes<N>") are emitted as standalone typed accessor functions that call v1's SetBytesFixed / BytesFixedSlice — they do NOT appear in the Fields struct because [N]byte is not a [zapv1.FieldKind] member. Both forms produce the same wire layout.
Types ¶
type Field ¶
type Field struct {
// Name is the Go-visible field name (e.g. "Time"). Emitted into
// the schema's Fields struct as `<SchemaGoName>Fields.Name`.
Name string
// Type is the Go type as a string. Supported: scalar FieldKind
// types, "bytes<N>" for fixed byte arrays, or "string"/"bytes" for
// variable-length tails.
Type string
// Offset is the byte position within the fixed-size payload. For
// variable-length fields this is where the 8-byte tail pointer sits.
Offset uint32
// Elem, when non-nil, marks this field as a variable-length LIST of a
// fixed-size element schema (Type is ignored for list fields). The
// field occupies an 8-byte list pointer {relOffset, length} at Offset;
// the elements live in the object tail and are read via [zapv1.ListAt].
Elem *ListElem
// Nested, when non-nil, marks this field as a SINGULAR nested object
// (Type is ignored). The field occupies a 4-byte object pointer
// {relOffset} at Offset; the nested object lives in the object tail and
// is read via [zapv1.NestedAt]. The proto3 "message field" case — the
// singular peer of Elem. An unset value (nil) encodes as a null pointer.
Nested *NestedMsg
}
Field describes one fixed-size field in a schema.
Two field kinds are supported:
Scalar fields. Type is one of the [zapv1.FieldKind] members (bool, int8/16/32/64, uint8/16/32/64, float32/64). The emitted code uses a [zapv1.Field][S, T] handle and the standard zapv1.Read/Write generic functions.
Fixed-width byte-array fields. Type is "bytes<N>" where N is a positive integer (e.g., "bytes20" for NodeID, "bytes32" for hashes, "bytes16" for session IDs). The emitted code uses the v1 ObjectBuilder.SetBytesFixed / Object.BytesFixedSlice accessors and returns the value as a [N]byte. Byte-array fields do NOT use the zapv1.Field generic handle because [N]byte is not a [zapv1.FieldKind] member; instead they get a typed accessor function emitted alongside the schema.
Variable-length tail fields. Type is "string" or "bytes" (no <N> suffix). These occupy an 8-byte tail pointer {relOffset uint32, length uint32} in the fixed payload; the data lives in the object tail after the fixed section. The constructor uses v1 ObjectBuilder.SetText / SetBytes; reads go through a standalone accessor over v1's Object.Text / Object.Bytes (zero-copy sub-slice of the buffer). Like byte-array fields, they do NOT use the zapv1.Field generic handle (string/[]byte are not FieldKind members).
List and nested-object tail fields are still hand-written (the generic [zapv1.ListAt] / out-of-line pointer machinery).
func (Field) IsBytes ¶
IsBytes reports whether the field is a fixed-width byte-array field (type "bytes<N>"). Returns (n, true) on a match; (0, false) for scalar fields.
func (Field) IsList ¶ added in v1.0.1
IsList reports whether the field is a variable-length list of a fixed-size element schema, returning the element descriptor.
func (Field) IsNested ¶ added in v1.0.1
IsNested reports whether the field is a singular nested object, returning the nested-message descriptor.
func (Field) IsVarBytes ¶
IsVarBytes reports whether the field is a variable-length byte slice (Type == "bytes", no <N> suffix). Same 8-byte tail-pointer layout as IsVarString; read access is over v1's Object.Bytes.
func (Field) IsVarString ¶
IsVarString reports whether the field is a variable-length string (Type == "string"). A variable-length field occupies an 8-byte tail pointer {relOffset uint32, length uint32} in the fixed payload; the string bytes live in the object tail after the fixed section. Read access is a standalone accessor over v1's Object.Text (a zero-copy sub-slice of the buffer).
func (Field) IsVariable ¶
IsVariable reports whether the field is any variable-length tail field (string or bytes). Variable fields are emitted as standalone accessor functions (like fixed byte arrays) rather than in the Fields struct, because string/[]byte are not zapv1.FieldKind members.
type ListElem ¶ added in v1.0.1
type ListElem struct {
// Schema is the element schema's marker GoName, e.g. "ItemSchema".
// Emitted as the List[E] / WriteList[S,E] / ListAt[S,E] type param.
Schema string
// Wire is the element schema's WireName, e.g. "BatchItem". Needed to
// reference the element's variable-length Offset<Wire>_<Field>
// constants when an element carries string/bytes sub-fields. May be
// empty for scalar-only elements (which use only <Schema>Fields).
Wire string
// Value is the Go value-struct the constructor accepts per element,
// e.g. "Item". Emitted as `type Value struct { ...Fields... }` and the
// list parameter is `[]Value`.
Value string
// Stride is the element's fixed payload size in bytes (its Size()).
Stride int
// Fields are the element's fields, in order (scalar, string, bytes).
// Used to emit the Value struct and the per-element WriteList body
// (one write per field, from the value struct into the element Setter).
Fields []Field
}
ListElem describes the element type of a list field. The element must be a FIXED-SIZE schema (scalars + fixed byte arrays only — no variable-length tails of its own), so every element is a flat Stride-byte slot the list machinery can index in O(1).
type NestedMsg ¶ added in v1.0.1
type NestedMsg struct {
// Schema is the nested schema's marker GoName, e.g. "InnerSchema".
// Emitted as the View[N] / WriteNested[S,N] / NestedAt[S,N] type param.
Schema string
// Wire is the nested schema's WireName, e.g. "Inner". Referenced for
// the nested's Offset<Wire>_<Field> constants when it carries
// string/bytes sub-fields.
Wire string
// Value is the Go value-struct the constructor accepts (by POINTER, so
// nil encodes the unset/null case), e.g. "Inner". Emitted as
// `type Value struct { ...Fields... }`; the parameter is `*Value`.
Value string
// Fields are the nested object's fields, in order. Used to emit the
// Value struct and the WriteNested body (one write per field).
Fields []Field
}
NestedMsg describes a singular nested-object field — the proto3 message field. The nested object must be a FIXED-SIZE (flat, no-kind) Element schema, reached through a 4-byte object pointer; its flat payload lives in the parent's object tail. The singular peer of ListElem.
type Schema ¶
type Schema struct {
// GoName is the Go type name for the schema marker struct (e.g.
// "AdvanceTimeSchema"). Used as the View[S] type parameter.
GoName string
// WireName is the human-readable name of the schema (e.g.
// "AdvanceTimeTx"). Used in error messages and Registry lookup.
// Stable across versions — part of the schema's identity.
WireName string
// Kind is the discriminator byte at object offset 0.
Kind uint8
// Size is the fixed object payload in bytes (excluding header).
Size int
// Package is the Go package the emitted file should live in.
Package string
// Fields is the ordered list of fields: scalars, fixed byte arrays
// ("bytes<N>"), and variable-length tails ("string"/"bytes"). Each
// occupies a slot in the fixed payload (variable-length fields hold
// an 8-byte tail pointer there). List and nested-object tails are
// not declared here; they use the generic [zapv1.ListAt] machinery.
Fields []Field
// SkipRegistry suppresses the emit of `init() { zapv1.Register[S]
// (zapv1.DefaultRegistry) }`. Use for schema families that have a
// PRIVATE Kind namespace (the discriminator byte is unique only
// within a per-package registry, not globally). Examples:
//
// - LP-186 chains-VM wire: each <vm>wire package has its own
// KindBlock=0x01 / KindTx=0x02 / etc. Registering all twelve
// KindBlock=0x01 implementations into a single global registry
// would panic on duplicate kind byte at init time.
// - LP-182 consensus wire: the 0x01..0x0D kind bytes are local to
// the consensus-wire registry (`pkg/wire/zap/schemas.go`), not
// the global zapv1.DefaultRegistry shared with P2P/light-client
// schemas at 0xD0+/0xF0+.
//
// Default is false — schemas with globally-unique Kind bytes
// (LP-201, LP-208, LP-211, LP-214, LP-218) DO register at init.
SkipRegistry bool
// Element marks this schema as a LIST ELEMENT: a flat, fixed-size
// value slot with NO kind discriminator byte at offset 0 (fields may
// start at offset 0), built inline by a parent's [zapv1.WriteList] and
// read via [zapv1.List][E].At — never wrapped as a top-level message.
// The Kind()/Size()/Name() methods are still emitted (List[E] and the
// registry need the Schema interface), but Kind is NOT stored in the
// slot and Wrap does not validate it. Default false (top-level kinded
// message: offset 0 reserved for the discriminator).
Element bool
}
Schema is the declarative description of a ZAP v1 schema. The codegen tool consumes a Schema and emits a per-schema *.go file with the Wrap/Build/Read/Write functions hand-rolled to inline.
One schema description, one emitted file, one and only one way to access the wire format from Go code — Hickey-style.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
gennest
command
Command gennest generates the nestwire test package: a singular nested object (Outer carrying an Inner that itself has a string tail).
|
Command gennest generates the nestwire test package: a singular nested object (Outer carrying an Inner that itself has a string tail). |
|
zapgen
command
Command zapgen emits per-schema ZAP v2 accessors from a declarative schema description.
|
Command zapgen emits per-schema ZAP v2 accessors from a declarative schema description. |
|
zapgen-all
command
Command zapgen-all bulk-emits per-schema ZAP v2 accessor files from a directory of JSON schema declarations.
|
Command zapgen-all bulk-emits per-schema ZAP v2 accessor files from a directory of JSON schema declarations. |