// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause // Command gen regenerates the architecture instruction tables from the Go // toolchain's own assembler source. Go's Plan 9 assembler defines the exact, // complete set of mnemonics it accepts for each architecture in // $GOROOT/src/cmd/internal/obj//anames.go; this tool extracts those // names so gasm-sdk supports every instruction the real assembler does, // with no hand-maintained (and therefore inevitably incomplete) lists. // // The same data feeds the generated instruction appendices of the assembly // language reference, docs/asm/INSTRUCTIONS-.md, so that the reference // cannot drift from the tables it documents. // // Usage (via the justfile): // // just gen // // The generated files are committed; regenerating requires a Go installation // but the toolkit itself has no dependency on the toolchain source at runtime. package main import ( "fmt" "go/ast" "go/parser" "go/token" "os" "os/exec" "path/filepath" "sort" "strings" "sourcedock.dev/petrbalvin/gasm-sdk/arch" "sourcedock.dev/petrbalvin/gasm-sdk/asm" ) // archDirs maps a gasm-sdk architecture name to its obj sub-directory. var archDirs = []struct { arch string sub string }{ {"amd64", "x86"}, {"arm64", "arm64"}, {"riscv", "riscv"}, {"loong64", "loong64"}, } // docPages maps an architecture to its generated appendix in the language // reference. The amd64 page carries a per-mnemonic encodability column, // decided by asm.Encodable, which mirrors the encoder's own dispatch; the // other targets have no single cheap predicate, so their pages carry the // inventory and point at the live measurement instead. var docPages = []struct { arch arch.Arch title string file string anames string encodable bool }{ {arch.AMD64, "AMD64", "INSTRUCTIONS-AMD64.md", "cmd/internal/obj/x86/anames.go", true}, {arch.ARM64, "ARM64", "INSTRUCTIONS-ARM64.md", "cmd/internal/obj/arm64/anames.go", false}, {arch.RISCV, "RISC-V 64", "INSTRUCTIONS-RISCV64.md", "cmd/internal/obj/riscv/anames.go", false}, {arch.LOONG64, "LoongArch 64", "INSTRUCTIONS-LOONG64.md", "cmd/internal/obj/loong64/anames.go", false}, } func main() { goroot := strings.TrimSpace(runGoEnvGOROOT()) if goroot == "" { fatal("could not determine GOROOT") } version := strings.TrimSpace(runGoEnv("GOVERSION")) // The common opcodes shared by every architecture (RET, JMP, NOP, CALL, // TEXT, FUNCDATA, …) live in cmd/internal/obj/util.go. commonPath := filepath.Join(goroot, "src", "cmd", "internal", "obj", "util.go") common, err := extractInstrs(commonPath) if err != nil { fatal("extract common: %v", err) } common = filterCommon(common) if err := writeCommon(common); err != nil { fatal("write common: %v", err) } fmt.Printf("%-8s %4d instructions -> arch/common_gen.go\n", "common", len(common)) names := map[string][]string{} for _, a := range archDirs { path := filepath.Join(goroot, "src", "cmd", "internal", "obj", a.sub, "anames.go") names[a.arch], err = extractInstrs(path) if err != nil { fatal("extract %s: %v", a.arch, err) } if err := writeGen(a.arch, a.sub, names[a.arch]); err != nil { fatal("write %s: %v", a.arch, err) } fmt.Printf("%-8s %4d instructions -> arch/%s_gen.go\n", a.arch, len(names[a.arch]), a.arch) } for _, p := range docPages { if err := writeDocPage(p.arch, p.title, p.file, p.anames, version, p.encodable); err != nil { fatal("write %s: %v", p.file, err) } fmt.Printf("%-8s -> docs/asm/%s\n", p.arch, p.file) } } // filterCommon drops opcode names that are not user-writable instructions. func filterCommon(names []string) []string { drop := map[string]bool{"XXX": true, "LAST": true} var out []string for _, n := range names { if !drop[n] { out = append(out, n) } } return out } // writeCommon emits arch/common_gen.go. func writeCommon(names []string) error { var b strings.Builder b.WriteString("// Code generated by gasm-sdk _gen; DO NOT EDIT.\n") b.WriteString("// Source: cmd/internal/obj/util.go from the Go toolchain.\n") b.WriteString("//\n") b.WriteString("// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org)\n") b.WriteString("// SPDX-License-Identifier: BSD-3-Clause\n\n") b.WriteString("package arch\n\n") b.WriteString("// commonGeneratedInstrs is the set of opcodes shared by every architecture\n") b.WriteString("// (RET, JMP, NOP, CALL, TEXT, FUNCDATA, PCDATA, …).\n") b.WriteString("var commonGeneratedInstrs = []string{\n") for _, n := range names { fmt.Fprintf(&b, "\t%q,\n", n) } b.WriteString("}\n") return os.WriteFile(filepath.Join("arch", "common_gen.go"), []byte(b.String()), 0o644) } // extractInstrs parses an anames.go file and returns the sorted, de-duplicated // instruction names from its `var Anames = []string{...}` literal. func extractInstrs(path string) ([]string, error) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, path, nil, 0) if err != nil { return nil, err } seen := map[string]bool{} var names []string for _, decl := range f.Decls { gd, ok := decl.(*ast.GenDecl) if !ok || gd.Tok != token.VAR { continue } for _, spec := range gd.Specs { vs, ok := spec.(*ast.ValueSpec) if !ok || len(vs.Names) == 0 || vs.Names[0].Name != "Anames" { continue } for _, val := range vs.Values { cl, ok := val.(*ast.CompositeLit) if !ok { continue } for _, elt := range cl.Elts { if lit := stringLit(elt); lit != "" && lit != "LAST" && !seen[lit] { seen[lit] = true names = append(names, lit) } } } } } sort.Strings(names) return names, nil } // stringLit returns the string value of a composite-literal element, whether it // is a plain literal or a keyed entry such as `obj.A_ARCHSPECIFIC: "AAA"`. func stringLit(elt ast.Expr) string { switch e := elt.(type) { case *ast.BasicLit: if e.Kind == token.STRING { return strings.Trim(e.Value, `"`) } case *ast.KeyValueExpr: return stringLit(e.Value) } return "" } // writeGen emits arch/_gen.go. func writeGen(arch, sub string, names []string) error { var b strings.Builder b.WriteString("// Code generated by gasm-sdk _gen; DO NOT EDIT.\n") b.WriteString("// Source: cmd/internal/obj/" + sub + "/anames.go from the Go toolchain.\n") b.WriteString("//\n") b.WriteString("// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org)\n") b.WriteString("// SPDX-License-Identifier: BSD-3-Clause\n\n") b.WriteString("package arch\n\n") b.WriteString("// " + arch + "GeneratedInstrs is the complete set of " + arch + " mnemonics accepted by\n// Go's Plan 9 assembler.\n") b.WriteString("var " + arch + "GeneratedInstrs = []string{\n") for _, n := range names { fmt.Fprintf(&b, "\t%q,\n", n) } b.WriteString("}\n") return os.WriteFile(filepath.Join("arch", arch+"_gen.go"), []byte(b.String()), 0o644) } // writeDocPage emits docs/asm/, the generated instruction appendix of // the language reference for one architecture: every mnemonic the toolchain // accepts, with the curated summary where the architecture table carries one // and, on amd64, a per-mnemonic encodability column. func writeDocPage(a arch.Arch, title, file, anames, version string, encodable bool) error { table := arch.ForArch(a) instrs := table.Instructions() var b strings.Builder b.WriteString("# " + title + ": instruction inventory\n\n") b.WriteString("Generated by gasm-sdk's `_gen` from the Go toolchain's instruction table\n") b.WriteString("(`" + anames + "`, " + version + "); DO NOT EDIT. This page lists every mnemonic\n") b.WriteString("`go tool asm` accepts on this target, which is the upper bound of the\n") b.WriteString("language on it: a name absent here is not an instruction of the target,\n") b.WriteString("and a name present here may still be one gasm's encoder cannot emit yet.\n\n") encodableCount := 0 if encodable { b.WriteString("The `gasm encodes` column reports whether gasm's encoder can emit the\n") b.WriteString("mnemonic today; the gap is the encoder backlog, measured live by\n") b.WriteString("`gasm audit-instructions`.\n\n") b.WriteString("| Mnemonic | gasm encodes | Notes |\n") b.WriteString("|---|---|---|\n") for _, in := range instrs { ok := asm.Encodable(in.Name) if ok { encodableCount++ } b.WriteString("| `" + in.Name + "` | " + yesNo(ok) + " | " + in.Summary + " |\n") } b.WriteString("\n") fmt.Fprintf(&b, "Recognised: %d mnemonics. gasm encodes: %d.\n", len(instrs), encodableCount) } else { b.WriteString("The inventory carries no per-mnemonic encoder column: on this target\n") b.WriteString("encodability is decided per operand shape, and the live measured\n") b.WriteString("coverage is reported by `gasm audit-instructions`.\n\n") b.WriteString("| Mnemonic | Notes |\n") b.WriteString("|---|---|\n") for _, in := range instrs { b.WriteString("| `" + in.Name + "` | " + in.Summary + " |\n") } b.WriteString("\n") fmt.Fprintf(&b, "Recognised: %d mnemonics.\n", len(instrs)) } return os.WriteFile(filepath.Join("docs", "asm", file), []byte(b.String()), 0o644) } // yesNo renders a boolean as the word the appendix tables use. func yesNo(v bool) string { if v { return "yes" } return "no" } func runGoEnvGOROOT() string { out, err := exec.Command("go", "env", "GOROOT").Output() if err != nil { return "" } return string(out) } // runGoEnv runs `go env` for a single variable. func runGoEnv(name string) string { out, err := exec.Command("go", "env", name).Output() if err != nil { return "" } return string(out) } func fatal(format string, args ...any) { fmt.Fprintf(os.Stderr, "gen: "+format+"\n", args...) os.Exit(1) }