AGENTS.md@runtimes/go · diff
git:20261031.084a463 to git:20261102.b5c65bf
2 added, 2 removed. Audit A to A.
# Go runtime: file map
> Read when: reading or changing the Go runtime (runtimes/go, package nino), its string kernels, struct tags, sample codecs or Go benchmark.
Module `github.com/JasonXuDeveloper/Nino/runtimes/go/v4` (Go 1.22, no dependencies): the Reader and Writer
generated Go code calls, struct tags as a reflection-based alternative, and the corpus tests; README.md documents
the API and its guarantees. nino-gen's Go target (`src/Nino.Codegen`) writes code in the style of
`internal/sample`. `audit-runtimes` audits it, `audit-security` its read paths.
```
README.md the API, guarantees, struct tags, tags on generated structs, strings, tests, benchmarks
go.mod the module (no requirements)
.gitignore test binaries and profiles
doc.go the package documentation: what it is and its guarantees
api.go Deserialize / DeserializeExact / Serialize / Append with a pooled reader or writer
reader.go Reader: bounds-checked reads, sticky errors (zeros after the first failure)
writer.go Writer: appends to a doubling buffer, sticky errors, Reset for reuse
errors.go Error (the one error type) and its Code
wire.go the wire constants (Nino.Core's NinoWire and NinoDepth)
types.go .NET value types without a Go equivalent (Decimal, Guid, DateTime, TimeSpan, DateOnly,
TimeOnly, DateTimeOffset, Int128, Half, Version, BigInteger), read and written as .NET does
raw.go blocks of primitives as one copy (ReadPrimitives / WritePrimitives), bools checked 0 or 1
block.go ReadBlock / WriteBlock: lists of dates, Decimal, Guid and Int128 (BlockValue) as one block,
each value checked as when read alone
endian_little.go littleEndian = true on little-endian architectures (blocks copied as memory)
endian_other.go littleEndian = false elsewhere (blocks converted element by element)
unique.go duplicate keys of sets and maps read in data order: numbers and strings compared as .NET
does, pairwise or in a seeded open-addressing table (the reader's scratch), no map
collections.go slices, maps, sets, entries, fixed-size runs, multidimensional shapes (the func-value
helpers unexported, for the runtime's rare paths); counts checked against the bytes left
depth.go class bodies (Class, ReadClass, WriteClass), regions, deferral past 128, the 512 limit
poly.go polymorphism: TypeID (FNV-1a), Subtype, TypeSet, ReadAny / WriteAny (allowed ids only)
refs.go reference tracking (wire-format spec §7.9): reference headers, Track, Back
layout.go layout strings: a type's NinoLayout in the [NinoTypeLayout] grammar (spec §8), parsed
strings.go strings: the default encoding's header, Latin-1 / UTF-16 / [NinoUtf8], the readers
strings_write.go writing strings straight into the buffer, eight bytes at a time
strings_wire.go what the string paths' Go code and kernels share (constants for go_asm.h)
strings_wire_amd64.go x64 kernel tiers (SSSE3, AVX2, AVX-512) and their detection; go:generate for internal/asm
strings_wire_amd64.s generated by internal/asm (avo): do not edit
strings_wire_arm64.go arm64 NEON kernels' Go side
strings_wire_arm64.s generated by internal/asm: do not edit
strings_wire_cpu_amd64.go osAVX512 from XCR0 (not darwin)
strings_wire_cpu_darwin_amd64.go osAVX512 on macOS (sysctl), as golang.org/x/sys/cpu decides it
strings_wire_tables.go shuffle tables of the vector string paths
strings_wire_other.go no vector string paths (other architectures, or the purego tag)
strings_asm.go asciiCopy / widenASCII declarations (amd64, arm64)
strings_amd64.s asciiCopy / widenASCII for amd64 (hand-written)
strings_arm64.s asciiCopy / widenASCII for arm64 (hand-written)
strings_noasm.go their no-op forms elsewhere
tagged.go struct tags: Go structs mapped to [NinoType]s without generated code (overview)
tagged_plan.go struct plans from the tags and the declared layout
tagged_build.go the builder: the codec of each Go type a plan reaches (TagError for what cannot map)
tagged_codec.go the codecs plans are made of
tagged_cache.go built codecs cached per reflect.Type (safe and unsafe field access)
marshal.go Marshal / Unmarshal through the tag plans
block_test.go block sizes and ReadBlock / WriteBlock against element-by-element reads; BenchmarkBlocks
(checked blocks: dates, decimals, bools, raw structs with a bool; Guids as the copy alone)
raw_test.go bool blocks, raw struct blocks and padded structs
unique_test.go duplicate keys: integer keys, large sets, primitive entries
collections_ref_test.go the func-value collection helpers the runtime dropped, as the tests' references
reader_test.go the Reader and Writer against hand-written bytes
race_test.go, norace_test.go raceEnabled: allocation counts are checked without the race detector (sync.Pool drops)
strings_test.go strings against a plain char-by-char reference; BenchmarkWriteString, BenchmarkUTF16
strings_utf8_test.go the [NinoUtf8] paths against the plain ones
strings_wire_test.go each vector tier against the Go loops, byte for byte
strings_wire_amd64_test.go tier detection (NINO_WANT_TIERS) and darwin
strings_wire_linux_test.go strings ending or starting at an unreadable page
tagged_test.go struct tags (the spec's appendix A types)
tagged_bench_test.go the tag path against hand-written code
fuzz_test.go FuzzReader: programs of reads over random bytes
tagged_fuzz_test.go FuzzTagged: random bytes into tag-mapped types
internal/
asm/ command (own module, avo) writing strings_wire_amd64.s and strings_wire_arm64.s;
main_test.go checks the committed files are current
corpustest/ runs the conformance corpus (valid, malicious, truncations) over a set of codecs;
render.go: values to the corpus's JSON (reflection, test code only)
sample/ the corpus's types (conformance/Nino.Conformance/Types.cs) with hand-written codecs in the
style a generator emits (codec_*.go; their collection loops and raw values in loops.go, as
the template writes them), their ids, layouts and tags; conformance, deep, registry and
tagged tests; FuzzDecode, FuzzTaggedDecode
generated/ nino_types.go: the corpus's types as nino-gen writes them (do not edit), its corpus test and
into_test.go (DeserializeInto: objects, slices, slots, failures, deferral);
shapes/: tests/Nino.Codegen.Tests/Shapes.cs as nino-gen writes it, round-tripped
bench/ module of its own: the runtime against Protocol Buffers (benchpb/, buf), MessagePack,
encoding/json and encoding/gob on the .NET benchmark's SimpleClass, strings, sets
(sets.go) and a scattered list of 1,000,000 records (scattered.go);
tiers_amd64_test.go checks tier detection against golang.org/x/sys/cpu
```
Generated files: `internal/generated` is rewritten by `NINO_UPDATE_GENERATED=1 dotnet test tests/Nino.Codegen.Tests`;
the `.s` kernels by `go generate` (internal/asm).
## Go runtime
`runtimes/go` (package `nino`, see its README.md) reads and writes the wire format for Go; `internal/sample` holds the
corpus's types with codecs in the style a generator would write. Both performance and safety on untrusted input are
requirements:
- Never panic on any input, and never rely on `recover`: check every bound, and keep errors sticky (after a failure
the reader returns zeros, empty slices and null, so every loop ends). Check a length against the bytes left
before allocating, with .NET's caps (`SliceCap`, `capacity`); grow, never presize, what the data only claims.
- Count depth exactly as .NET does (`ReadClass`, `WriteClass`, regions and sections): the bytes must match, and
the stack must stay bounded however deep the data. A polymorphic slot accepts only the ids of the types it allows.
- Hot paths: no reflection, no boxing into `any` outside polymorphic slots; a run of fixed-size members is one
`Take`/`Extend` and `binary.LittleEndian` at constant offsets; blocks of primitives are one copy (guarded for
big-endian hosts). Keep rare paths (`Fail`, growth, deferral) in `//go:noinline` functions. Check
`go build -gcflags=-d=ssa/check_bce/debug=1` and `-gcflags=-m` on what you change, and keep writing a value free
of allocations (`bench`, `-benchmem`). A collection of fixed-size elements takes a block helper
(`ReadPrimitives`, `ReadPrimitiveEntries`, `ReadPrimitiveMap`), never the generic helpers that take element
functions: those are an indirect call per element (`(*Reader).I32` is never inlined through a func value) and a
bounds check per value. Any other collection loop in generated code is written out with the element's call made directly
- (nino-gen's `read_loop` / `write_loop`), never a helper taking a func value (the runtime keeps `readSlice` and the like unexported, for its own rare paths) (`optimize-runtimes`, "Dispatch
- outside .NET"). `ReadPrimitiveMap` read a 10-pair `map[int32]int32` 31 % faster than `ReadMap` (now unexported), and
+ (nino-gen's `read_loop` / `write_loop`), never a helper taking a func value (the runtime keeps `readSlice` and the like unexported, for its own rare paths) (`optimize-runtimes`, references/dispatch.md).
+ `ReadPrimitiveMap` read a 10-pair `map[int32]int32` 31 % faster than `ReadMap` (now unexported), and
SimpleClass 4-7 % faster on runners (7ac8b46). Walk a block with a cursor (`for len(b) >= wire { ...; b =
b[wire:] }`): indexing `b[i*wire:]` kept a bounds check per pair. A runner A/B can move code you did not touch by
layout: the first map round left the untouched tag write 1.5-7.7 % slower on x64 only; make that path faster too
(it then measured 7-14 % faster) rather than land a loss.
- Checks over a block (ranges of dates and decimals, the bools of raw structs) and UTF-16 copies: what measured
faster and slower is in the `optimize-runtimes` skill, "Checking blocks in Go"; time them with BenchmarkBlocks and BenchmarkUTF16.
- Struct tags (`tagged.go`, `layout.go`, `marshal.go`) are the one place reflection is allowed: a plan built once
per type, then walked. Anything the wire format cannot express is a `*TagError` naming the field when the plan
is built, never a silent default; reads go through the same `Reader` calls as generated code, so the guarantees
above hold there too. The sample types carry the tags and `NinoLayout` a generator emits (README, "Tags on
generated structs"); keep them and `internal/sample/tagged_test.go` in step with the hand-written codecs.
- Before committing: `go vet ./...`, `go test -race ./...` with the conformance corpus present, a short run of
`FuzzReader`, `FuzzDecode`, `FuzzTagged` and `FuzzTaggedDecode`, and the benchmarks back to back against the
previous commit for a change to a read or write path. The runtime's module has no dependencies; competitors live in the `bench` module.
- The module says Go 1.22 and CI builds and tests it with Go 1.22: no API newer than that outside the `bench` module,
tests and benchmarks included (`testing.B.Loop` is Go 1.24: loop with `for range b.N`). Check with
`GOTOOLCHAIN=go1.22.12 go vet ./...` before pushing. CI also tests 386, Arm32 and big-endian s390x: run
`GOARCH=386 go test ./...` (a constant such as `1<<31` compared with an `int` does not build there, and a test that
hard-codes a pointer's size as 8 reads past its slice; both reached main in r101) and, with qemu-user,
`GOARCH=arm GOMAXPROCS=1 go test -exec qemu-arm ./...` (several Ps crash Go 1.27 under qemu: `platforms`) and `GOARCH=s390x go test -exec qemu-s390x ./...`.