Codync
免費、開源的 1:1 替代方案,取代 Grok Bot 與 Muse。從 iPhone、Mac 或 Linux 桌面將 Claude Code、Codex、Cursor 與 40+ 程式碼代理人作為持續機器人傳訊。群組聊天、主題串…
AI 翻譯
製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Ergonomic generation of Go-compatible Plan 9 assembly for every 64-bit Go
target — amd64, arm64, riscv64, loong64, ppc64le, and
s390x. Encoding is
delegated to the Go toolchain assembler (cmd/asm); go-asmgen computes the ABI0
frame layout and emits well-formed Plan 9 instruction text.
avo does this for amd64 by encoding instruction bytes itself — powerful, but
exactly what makes extending it to new ISAs expensive. go-asmgen instead emits
Plan 9 text and lets cmd/asm encode, so each architecture is just a thin
move/register surface over a shared ABI0 layout model (abi). avo
remains the richer choice for amd64-specific work; go-asmgen's niche is one
uniform builder across all six targets (and it is the only such tool for the
non-amd64 ones — now including ppc64le's VSX and s390x's vector facility). The architectures differ only in their move tables:
| 8-byte int | float32 | float64 | |
|---|---|---|---|
MOVQ | MOVSS | MOVSD | amd64 |
MOVD | FMOVS | FMOVD | arm64 |
MOV | MOVF | MOVD | riscv64 |
MOVV | MOVF | MOVD | loong64 |
MOVD | FMOVS | FMOVD | ppc64le |
MOVD | FMOVS | FMOVD | s390x (big-endian) |
Go's compiler does not emit v128 from Go source, and Plan 9 assembly has
no wasm dialect — so wasm-SIMD kernels have to be external, imported via
//go:wasmimport. The wasm package ports the same layering
pattern to a seventh target: a programmatic WAT text emitter that
wat2wasm compiles into .wasm, byte-equivalent to a hand-authored
kernel and drift-gated against the generator in consumer CI.
Thirteen kernels ship in examples/wasm/ (matchlen, hex,
hex_decode, popcount, toupper, memchr, isascii, utf8len, json_clean,
adler32, base64_encode, indexany4, base64_decode), each with a
golden-file test and a wazero cross-check against a Go stdlib reference.
The CI wasm-e2e job regenerates every kernel, drift-gates against the
committed golden, compiles via wat2wasm, and runs the wazero verifier
on all thirteen. See the
wasm docs section for
the emitter surface, kernel signatures, and the drift + regression CI
pattern used by consumers.
The wasm package was previously shipped as a standalone
go-asmgen/wasm module (tags v0.1.0 – v0.3.0) — that repo has since
been retired in favor of this fold. Pin
github.com/go-asmgen/asmgen/wasm/… and
github.com/go-asmgen/asmgen/examples/wasm/<kernel>.
v0 — amd64, arm64, riscv64, loong64, ppc64le, s390x,
ABI0. Correct for
sequences of scalars in any combination: signed/unsigned integers of 1/2/4/8
bytes, pointers, and 32/64-bit floats. Each builder selects the right move per
type and the shared layout computes ABI0 offsets (result area word-aligned,
sub-word loads sign/zero-extended). Every emitted offset and access width is
cross-checked by go vet asmdecl and exercised by runtime tests — natively on
amd64 and arm64, and under qemu-user for riscv64, loong64, ppc64le, and s390x
(s390x exercising the big-endian path).
Aggregates and arrays are supported: struct, slice, and string parameters
(fields addressed as name_field+offset(FP), e.g. s_base/s_len/s_cap) and
fixed-size [n]T arrays passed by value (element-wise, name_0…name_(n-1)).
The layout math is architecture-independent and proven generically in abi's
own tests; amd64, arm64, riscv64, and loong64 additionally have dedicated,
runtime-tested example programs for both — see examples. ppc64le
and s390x don't have dedicated aggregate/array examples yet (SIMD examples
exist for all six — see below).
SIMD works through the Raw escape hatch over loaded pointers: go-asmgen lays
out the ABI0 frame and the vector body is emitted directly. Runtime-tested
packed-add on all six targets — SSE2 + AVX2 (amd64), NEON (arm64),
RVV (riscv64), LSX + LASX (loong64), VSX (ppc64le), vector facility
(s390x, big-endian), up to 256-bit. See
examples/simd.
Stack frames and TEXT flags: NewFuncFlags emits any flags (NOSPLIT,
NOSPLIT|NOFRAME, or none for the stack-growth preamble); frameSize > 0
reserves locals addressed name-N(SP). See examples/frame.
CPU feature probes: amd64.FeatureProbe(name, amd64.AVX2) emits the
CPUID/XGETBV gate a dispatched kernel needs in front of it, as a func() bool,
so a package that wants no runtime dependency does not have to hand-write CPUID
to get one. The OS half is the part hand-rolled probes drop: a CPU can report
AVX2 on a kernel that does not save YMM state. POPCNT, FMA (CPUID leaf 1,
separate from AVX2's leaf-7 bit) and AVX512F are
there too (AVX512F also requires the OS to save opmask and ZMM state, XCR0
mask 0xE6; on macOS it answers false, since Darwin enables that state lazily),
and the list stops at what has callers. amd64.VendorProbe(name, "GenuineIntel")
(v0.12.0) compares CPUID leaf 0's vendor string, for tuning choices measured to
differ by vendor (go-fft's radix rule); it never gates correctness, since a
hypervisor may report its own vendor.
arm64 vector float64 arithmetic: Builder.VFADD2D, VFSUB2D, VFMUL2D,
VFNEG2D, VFMLA2D and VFMLS2D emit VFADD/VFSUB/VFMUL/VFNEG/VFMLA/
VFMLS on the .D2 arrangement and leave the encoding to cmd/asm, which has
had the first four since Go 1.27; the module therefore requires Go 1.27 (1.27.1 since v0.15.2). (Up
to v0.14 they were hand-encoded WORDs; the output is now mnemonics, the
machine code is the same.) A test assembles them with cmd/asm and compares
the result with the system assembler's encodings. Kernels built from them ran
on Apple M4 and Neoverse-N1 against Go's own arithmetic and math.FMA:
bit-identical over 2.4M lanes. Their float32 counterparts on the .S4
arrangement (four float32 lanes) are VFADD4S, VFSUB4S, VFMUL4S,
VFNEG4S, VFMLA4S and VFMLS4S, checked against the system assembler the
same way.
arm64 multiply by element, which the Go assembler lacks: cmd/asm has
the vector-by-vector VFMLA/VFMLS/VFMUL but not their indexed forms
(fmla vd.2d, vn.2d, vm.d[i]: every lane times one lane of Vm, the broadcast
a GEMM micro-kernel needs). Builder.VFMLAElem2D, VFMLSElem2D,
VFMULElem2D and their .4S counterparts VFMLAElem4S, VFMLSElem4S,
VFMULElem4S take (d, n, m, index) in A64 order and emit WORDs. Vm is any
of V0–V31, and the index is 0–3 for .4S and 0–1 for .2D. Each WORD
carries the spelling from CL 764224 (Jonathan
Swinney) as its comment, for example VFMLA V20.D[0], V16.D2, V0.D2. The 76
reference encodings come from llvm-mc 23, and GNU as 2.44 agrees on every
case. They include the sixteen WORDs go-ndarray's GEMM kernel writes by hand.
CL 764224 patchset 6, applied to master with the two CLs it is stacked on,
assembles all 76 identically (goasmgap verify). That CL is not one of ours,
so it is not in goasm-patches/cls.txt. The weekly check reports the day
master or a release assembles these forms.
ppc64le VSX float64 arithmetic the Go assembler lacks: cmd/asm has the
VSX loads, stores and permutes (LXVD2X, LXVDSX, STXVD2X, XXPERMDI) but
no vector double arithmetic. Builder.XVADDDP, XVSUBDP, XVMULDP,
XVDIVDP, XVMADDADP (fused), XVMAXDP, XVMINDP and XVSQRTDP emit them as
WORDs over VSX registers 0–63. Each WORD carries the spelling proposed for
cmd/asm as its comment. The encodings are pinned against GNU as 2.44 on a
POWER9 (70 cases, every register field at 0, 31, 32 and 63). The example
kernels in examples/simd/ppc64 were run on POWER8 and POWER9 against Go's
arithmetic, math.Sqrt and math.FMA, and the results were bit-identical.
(XVMAXDP/XVMINDP follow the ISA's NaN rule, not Go's NaN-propagating
max.)
Their float32 counterparts (four float32 lanes) are XVADDSP, XVSUBSP,
XVMULSP, XVDIVSP, XVMADDASP, XVMAXSP, XVMINSP and XVSQRTSP, pinned
the same way (70 cases, GNU as 2.44 and llvm-mc agreeing). go-ndarray's
float32 pairwise-sum block runs on XVADDSP and matches the scalar loop bit
for bit on a POWER9.
s390x vector float32 arithmetic the Go assembler lacks: cmd/asm has the
float64 forms (VFADB, WFADB, VFMADB, ...) but not the float32 ones added
by the vector-enhancements facility 1 (z14). Builder.VFASB, VFSSB, VFMSB,
VFDSB, VFMASB, VFMSSB (both fused) and VFSQSB work on four float32
lanes, and WFASB ... WFSQSB on element 0 only. Operands are vector registers
0–31 in ISA order, the target first: b.VFASB(3, 1, 2) is V3 = V1 + V2. The
instructions are 6 bytes long. Each one is emitted on a single line as
WORD $0x<bytes 0–3>; BYTE $0x<byte 4>; BYTE $0x<byte 5>, with the spelling from
CL 846286 as its comment. cmd/asm writes these
directives big-endian and without padding. The encodings are pinned against
GNU as 2.42 on an IBM z with vxe/vxe2 (126 cases, every register field at 0,
15, 16 and 31, so the RXB bits are covered). llvm-mc 23 agrees on every case.
Kernels emitted by these methods were run on that machine, with operands in
V5, V17, V30 and the result in V20. They matched Go's scalar float32
arithmetic, math.Sqrt and an exactly rounded fused reference bit for bit, over
8.4M lanes. A caller must check cpu.S390X.HasVXE before using them.
loong64 LSX/LASX fused multiply-add and broadcast load:
Builder.XVFMADDD/VFMADDD (float64, four/two lanes) and
XVFMADDF/VFMADDF (float32, eight/four lanes) are WORDs, because
cmd/asm has no vector float FMA (VMADDV/XVMADDV are the integer ones).
XVLDREPLD/VLDREPLD (one float64 into every lane) emit XVMOVQ off(R), X.V4 / VMOVQ off(R), V.V2, which is how cmd/asm spells xvldrepl.d /
vldrepl.d. It has done so since at least Go 1.26. The one exception is
offset −2048: the ISA allows it, cmd/asm refuses it, so it stays a WORD.
The encodings are pinned against GNU as 2.43 on a Loongson 3C5000L (32 cases),
and the example kernels in examples/simd/loong64 were run on that machine
against math.FMA, with bit-identical results.
loong64 privileged CSR access (bare-metal code at PLV0): Builder.CSRRD,
CSRWR and CSRXCHG emit csrrd, csrwr and csrxchg as WORDs, because
cmd/asm has none of them. tamago's bare-metal loong64 port writes the same
WORDs by hand. The CSR number is 0 to 0x3fff. CSRXCHG refuses mask register
0 or 1, which would encode csrrd or csrwr. The 19 reference encodings come
from GNU as 2.44, and llvm-mc 23 agrees on every case. They are proposed for
cmd/asm as CSRRD $csr, Rd, CSRWR $csr, Rd and CSRXCHG $csr, Rj, Rd in
CL 853786.
Instructions cmd/asm lacks are transitional. Every WORD above has an
entry in internal/gap: its encoding, the independent references, the
opcode mask, and the Plan 9 spelling proposed upstream. tools/goasmgap
works from that registry:
cmd/asm test lines for an upstream patch;The patches live in goasm-patches/. A weekly workflow
opens an issue when a Go release or master gains one of these instructions,
and checks that the patches still apply to master and still encode correctly.
A typed vector-load helper (to drop the Raw boilerplate) and first-class vector
types are the main remaining items.
Three small packages: an architecture builder (amd64 / arm64 / riscv64 /
loong64 / ppc64 / s390x), the ABI0 layout model (abi), and the Plan 9 file
writer (emit).
go get github.com/go-asmgen/asmgen@latest
package main
import (
"os"
"github.com/go-asmgen/asmgen/arm64"
"github.com/go-asmgen/asmgen/emit"
)
func main() {
// func add(a, b int64) int64
sig := arm64.Layout(
[]string{"a", "b"}, []arm64.Type{arm64.Int64, arm64.Int64},
[]string{"ret"}, []arm64.Type{arm64.Int64},
)
b := arm64.NewFunc("add", sig, 0)
b.LoadArg("a", "R0").
LoadArg("b", "R1").
Raw("ADD R1, R0, R2").
StoreRet("R2", "ret").
Ret()
f := emit.NewFile("arm64")
f.Add(b.Func())
os.WriteFile("add_arm64.s", []byte(f.String()), 0o644)
}
For struct/slice/string parameters, build the layout with
abi.LayoutArgs and the Struct/Slice/String constructors; see
examples/aggregate.
fft/)The SIMD kernels of go-fft are built here, one package per architecture, on top of the builders above:
| package | kernels |
|---|---|
fft/amd64 | Stockham passes of radix 2, 3, 4, 5, 8, 9, 10, 12, 15, 16 and 20 (AVX2), and of radix 2, 4 and 8 at 512 bits; final passes, batched, strided and run-writing passes; the split layouts at 256 and 512 bits; radix-32 passes with fused multiply-adds (split layout at 256 and 512 bits, with the values between their stages in the output or in their frame, and the interleaved final pass); float32 passes; the real-FFT untangle and retangle; SSE2 and AVX2 complex products; the two- and three-pass stack-frame trampolines and their address table |
fft/arm64 | NEON Stockham passes of radix 2, 3, 4, 5 and 8 in float64 and float32 (pass, final pass, batched pass, block-split layout); the untangle and retangle; the complex product |
fft/riscv64, fft/s390x | the RVV and vector-facility complex products |
Each builder appends one TEXT block to an emit.File. The caller supplies
what belongs to it: the symbol, the offsets of the constants in its tables
(Consts, SplitConsts) and, on amd64, whether the products are fused
(Kernels.FMA). The arguments, registers and frame are the kernel's own and
are documented on each builder. go-fft keeps a thin generator that names its
kernels and writes its files:
k := &fft.Kernels{K: fft.Consts{Sign: 0, Half: 32 /* ... */}}
f := emit.NewFile("amd64")
k.Pass(f, "skPass4AVX2", 4) // name(cc, ch, tw *complex128, k *float64, ido, l1 int)
k.Last(f, "skLast4AVX2", 4) // name(cc, ch *complex128, k *float64, l1 int)
Every kernel is bit-identical to a scalar Go reference in go-fft, whose tests
check it. Here, testdata/gofft.sha256 pins every file go-fft generates to the
bytes go-fft commits, golden files pin one or two kernels of every builder,
every one of them goes through the Go assembler, and
examples/fft runs some of them (complex products, a 16-point
FFT) on each architecture.
The library packages (abi, emit, amd64, arm64, riscv64, loong64,
ppc64, s390x, wasm) are architecture-independent and held to 100% test
coverage:
go test ./abi/... ./emit/... ./amd64/... ./arm64/... ./riscv64/... ./loong64/... ./ppc64/... ./s390x/... ./wasm/... ./internal/...
The generated assembly is the real test of correctness. On an arm64 host (Apple Silicon or arm64 Linux):
go generate ./examples/add/... ./examples/types/...
GOARCH=arm64 go vet ./examples/add/... ./examples/types/... # asmdecl
go test ./examples/add/... ./examples/types/... # runtime
riscv64, loong64, ppc64le and s390x have no common native host, so validate them statically
anywhere (asmdecl + cmd/asm) and run them under emulation (shown for riscv64;
loong64 is identical with GOARCH=loong64 / qemu-loongarch64-static):
go generate ./examples/riscv64/...
GOOS=linux GOARCH=riscv64 go vet ./examples/riscv64/... # asmdecl
GOOS=linux GOARCH=riscv64 go build ./examples/riscv64/... # cmd/asm checks mnemonics
# runtime under qemu-user (e.g. in CI), or via Docker's emulation:
GOARCH=riscv64 go test -exec=qemu-riscv64-static ./examples/riscv64/...
go vet asmdecl passes. The cheapest, strongest check: it verifies every
name+offset(FP) in the .s matches the Go declaration. Wrong offsets are
caught before runtime.cmd/asm accepts every instruction, and the committed .s is regenerated
and diffed — a stale or invalid .s fails the build.abi, emit, internal/gap and the seven builders.FMOVS/FMOVD), word-aligned result area, sub-word sign/zero
extension. Validated against asmdecl + runtime tests. (done)internal/abi) and add riscv64 as a thin
second architecture over it, runtime-proven under qemu-user. (done)MOVV, MOVF/MOVD), runtime-proven under qemu-user. (done)name_field+offset(FP), asmdecl- and runtime-validated.
(done)abi and emit out of internal/. (done)MOVxQSX/ZX sub-word
loads and SSE float moves), runtime-proven natively, and SIMD examples
(amd64 SSE + arm64 NEON packed add) through Raw. (done — here)[4]float32 by value; RVV on riscv64,
LSX/LASX on loong64 — all six assemblers support SIMD), and array value args.