// 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-devkit supports every instruction the real assembler does, // with no hand-maintained (and therefore inevitably incomplete) lists. // // 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" ) // archDirs maps a gasm-devkit architecture name to its obj sub-directory. var archDirs = []struct { arch string sub string }{ {"amd64", "x86"}, {"arm64", "arm64"}, {"riscv", "riscv"}, {"loong64", "loong64"}, } func main() { goroot := strings.TrimSpace(runGoEnvGOROOT()) if goroot == "" { fatal("could not determine GOROOT") } // 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)) for _, a := range archDirs { path := filepath.Join(goroot, "src", "cmd", "internal", "obj", a.sub, "anames.go") names, err := extractInstrs(path) if err != nil { fatal("extract %s: %v", a.arch, err) } if err := writeGen(a.arch, a.sub, names); 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) } } // 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-devkit _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-devkit _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) } func runGoEnvGOROOT() string { out, err := exec.Command("go", "env", "GOROOT").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) }