feat(cmd): GOOS-aware headers, audit battery shapes and semicolon spacing
Test / test (push) Canceled after 1m8s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-20 22:02:19 +02:00
parent a7744c24bd
commit cf05b9b384
7 changed files with 164 additions and 38 deletions
+34 -21
View File
@@ -27,7 +27,10 @@ import (
// GOROOT assembly includes it, and a standalone assembler has no compiler // GOROOT assembly includes it, and a standalone assembler has no compiler
// to have produced it, so gasm generates the equivalent itself: the package // to have produced it, so gasm generates the equivalent itself: the package
// the .s file lives in is parsed and type-checked here, with the target // the .s file lives in is parsed and type-checked here, with the target
// architecture's own sizes, and the same defines are written out. // architecture's own sizes, and the same defines are written out. The
// type-checking GOOS is selected by the caller: a GOOS-specific file
// (sys_darwin_arm64.s) needs its platform's defines, which a header from
// the ambient GOOS silently omits.
// //
// The emitter mirrors cmd/compile's dumpasmhdr exactly: constants come out // The emitter mirrors cmd/compile's dumpasmhdr exactly: constants come out
// as "const_NAME", struct entries as "NAME__size" followed by the fields in // as "const_NAME", struct entries as "NAME__size" followed by the fields in
@@ -67,11 +70,15 @@ func goAsmHeaderResolved(asmDir string, dirs []string) bool {
return false return false
} }
// generateGoAsmHeader type-checks the Go package in pkgDir for goarch, // generateGoAsmHeader type-checks the Go package in pkgDir for goos and
// writes its go_asm.h equivalent into dir, and returns dir. The caller // goarch, writes its go_asm.h equivalent into dir, and returns dir. An
// owns the directory and its removal. // empty goos means the ambient one. The caller owns the directory and its
func generateGoAsmHeader(pkgDir, goarch, dir string) (string, error) { // removal.
imp := newSourceImporter(goarch) func generateGoAsmHeader(pkgDir, goos, goarch, dir string) (string, error) {
if goos == "" {
goos = build.Default.GOOS
}
imp := newSourceImporter(goos, goarch)
if imp.sizes == nil { if imp.sizes == nil {
return "", fmt.Errorf("go_asm.h: unknown GOARCH %q", goarch) return "", fmt.Errorf("go_asm.h: unknown GOARCH %q", goarch)
} }
@@ -89,7 +96,7 @@ func generateGoAsmHeader(pkgDir, goarch, dir string) (string, error) {
} }
var b strings.Builder var b strings.Builder
fmt.Fprintf(&b, "// generated by gasm from package %s (GOARCH %s)\n\n", bp.Name, goarch) fmt.Fprintf(&b, "// generated by gasm from package %s (GOOS %s, GOARCH %s)\n\n", bp.Name, goos, goarch)
// Files in the build's own order and declarations in source order: the // Files in the build's own order and declarations in source order: the
// same walk the compiler's reader makes, so the header reads the same // same walk the compiler's reader makes, so the header reads the same
// way the toolchain's does. Order carries no meaning to the assembler // way the toolchain's does. Order carries no meaning to the assembler
@@ -203,13 +210,14 @@ type sourceImporter struct {
pkgs map[string]*types.Package pkgs map[string]*types.Package
} }
// newSourceImporter returns the importer for one target architecture. // newSourceImporter returns the importer for one target GOOS and GOARCH.
// Cgo is disabled so the file set is deterministic and independent of the // Cgo is disabled so the file set is deterministic and independent of the
// host's C toolchain: cgo-tagged files drop out of the build exactly as // host's C toolchain: cgo-tagged files drop out of the build exactly as
// they do from a CGO_ENABLED=0 build, whose assembly is what gasm targets. // they do from a CGO_ENABLED=0 build, whose assembly is what gasm targets.
func newSourceImporter(goarch string) *sourceImporter { func newSourceImporter(goos, goarch string) *sourceImporter {
ctxt := new(build.Context) ctxt := new(build.Context)
*ctxt = build.Default *ctxt = build.Default
ctxt.GOOS = goos
ctxt.GOARCH = goarch ctxt.GOARCH = goarch
ctxt.CgoEnabled = false ctxt.CgoEnabled = false
return &sourceImporter{ return &sourceImporter{
@@ -291,7 +299,7 @@ func (im *sourceImporter) checkPackage(bp *build.Package, files []*ast.File) (*t
// type-check must not be re-checked once per file. // type-check must not be re-checked once per file.
type asmhdrCache struct { type asmhdrCache struct {
root string root string
dirs map[string]string // "pkgDir\x00goarch" -> directory holding go_asm.h dirs map[string]string // "pkgDir\x00goos\x00goarch" -> directory holding go_asm.h
errs map[string]error errs map[string]error
} }
@@ -304,17 +312,22 @@ func newAsmhdrCache() (*asmhdrCache, error) {
} }
// dirFor returns the directory holding the generated go_asm.h for pkgDir // dirFor returns the directory holding the generated go_asm.h for pkgDir
// and goarch, generating it on first use. // under goos and goarch, generating it on first use. An empty goos means
func (c *asmhdrCache) dirFor(pkgDir, goarch string) (string, error) { // the ambient one, resolved here so that one package cannot generate twice
key := pkgDir + "\x00" + goarch // under an explicit and an implicit spelling of the same GOOS.
func (c *asmhdrCache) dirFor(pkgDir, goos, goarch string) (string, error) {
if goos == "" {
goos = build.Default.GOOS
}
key := pkgDir + "\x00" + goos + "\x00" + goarch
if dir, ok := c.dirs[key]; ok { if dir, ok := c.dirs[key]; ok {
return dir, nil return dir, nil
} }
if err, ok := c.errs[key]; ok { if err, ok := c.errs[key]; ok {
return "", err return "", err
} }
dir := filepath.Join(c.root, fmt.Sprintf("h%d_%s", len(c.dirs), goarch)) dir := filepath.Join(c.root, fmt.Sprintf("h%d_%s_%s", len(c.dirs), goos, goarch))
if _, err := generateGoAsmHeader(pkgDir, goarch, dir); err != nil { if _, err := generateGoAsmHeader(pkgDir, goos, goarch, dir); err != nil {
c.errs[key] = err c.errs[key] = err
return "", err return "", err
} }
@@ -327,10 +340,10 @@ func (c *asmhdrCache) close() { os.RemoveAll(c.root) }
// ensureGoAsmHeader prepares the include directory a file that includes // ensureGoAsmHeader prepares the include directory a file that includes
// go_asm.h needs: the generated header for the package in path's directory, // go_asm.h needs: the generated header for the package in path's directory,
// for the file's target architecture. It reports a usage error when the // for the file's target GOOS and architecture. It reports a usage error
// architecture cannot be determined, and passes through the generator's // when the architecture cannot be determined, and passes through the
// diagnostics, which name the package. // generator's diagnostics, which name the package.
func ensureGoAsmHeader(path string, target arch.Arch, cache *asmhdrCache) (string, func(), error) { func ensureGoAsmHeader(path string, target arch.Arch, goos string, cache *asmhdrCache) (string, func(), error) {
if path == "-" { if path == "-" {
return "", nil, errors.New("cannot generate go_asm.h for standard input (no package directory)") return "", nil, errors.New("cannot generate go_asm.h for standard input (no package directory)")
} }
@@ -338,14 +351,14 @@ func ensureGoAsmHeader(path string, target arch.Arch, cache *asmhdrCache) (strin
return "", nil, errors.New("a file that includes go_asm.h needs a target architecture: name the file _<arch>.s or pass -GOARCH") return "", nil, errors.New("a file that includes go_asm.h needs a target architecture: name the file _<arch>.s or pass -GOARCH")
} }
if cache != nil { if cache != nil {
dir, err := cache.dirFor(filepath.Dir(path), goarchName(target)) dir, err := cache.dirFor(filepath.Dir(path), goos, goarchName(target))
return dir, func() {}, err return dir, func() {}, err
} }
root, err := os.MkdirTemp("", "gasm-asmhdr") root, err := os.MkdirTemp("", "gasm-asmhdr")
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
dir, err := generateGoAsmHeader(filepath.Dir(path), goarchName(target), root) dir, err := generateGoAsmHeader(filepath.Dir(path), goos, goarchName(target), root)
if err != nil { if err != nil {
os.RemoveAll(root) os.RemoveAll(root)
return "", nil, err return "", nil, err
+68 -9
View File
@@ -5,6 +5,7 @@ package main
import ( import (
"fmt" "fmt"
"maps"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -288,6 +289,11 @@ func probeShapes(a arch.Arch) []string {
"V1.B16, [V2.B16], V3.B16", "V1.B8, [V2.B16, V3.B16], V4.B8", "V1.B16, [V2.B16], V3.B16", "V1.B8, [V2.B16, V3.B16], V4.B8",
"$4, V1.B16, V2.B16, V3.B16", "$15, V1", "V1, V2, p2", "$4, V1.B16, V2.B16, V3.B16", "$15, V1", "V1, V2, p2",
"R0, R1, $1, $4, p2", "R0, R1, $1, $4, p2",
// The landing-pad kind, the compiler's PCDATA
// bookkeeping and the four-operand bitfield
// insert/extract family, as the toolchain's own
// testdata spells them.
"C", "$1, $0", "$0, R1, $1, R2",
} }
case arch.RISCV: case arch.RISCV:
return []string{ return []string{
@@ -307,6 +313,9 @@ func probeShapes(a arch.Arch) []string {
"X5, X6, p2", "R5, R6, p2", "X5, X6, p2", "R5, R6, p2",
"X5, E8, M8, TA, MA, X6", "$4, E32, M1, TA, MA, X1", "X5, E8, M8, TA, MA, X6", "$4, E32, M1, TA, MA, X1",
"(X5), X6, V1, V2", "(X5), X6, V1, V2",
// The CSR immediate forms the toolchain's testdata spells:
// immediate, CSR name, destination.
"$2, TIME, X5",
"", "",
} }
case arch.LOONG64: case arch.LOONG64:
@@ -323,6 +332,12 @@ func probeShapes(a arch.Arch) []string {
"V1, V2, V3", "X1, X2, X3", "V1, V2", "X1, X2", "V1", "X1", "V1, V2, V3", "X1, X2, X3", "V1, V2", "X1, X2", "V1", "X1",
// The vector compare-to-flag forms land in an FCC register. // The vector compare-to-flag forms land in an FCC register.
"V1, FCC0", "X1, FCC0", "V1, FCC0", "X1, FCC0",
// The compiler's bookkeeping pair and the raw spellings the
// toolchain's own testdata carries: JIRL rd, rj, offset (the
// form RET lowers to), the prefetch with a 32-bit address and
// hint, and the byte-shuffle quads.
"$1, $0", "R1, R5, 0", "0(R7), $5, $0", "(R7), $5, $0",
"V1, V2, V3, V4", "X1, X2, X3, X4",
"", "",
} }
} }
@@ -487,10 +502,47 @@ func otherPortFile(path string) bool {
// goOSNames are the GOOS values go/build recognises in file names. // goOSNames are the GOOS values go/build recognises in file names.
var goOSNames = map[string]bool{ var goOSNames = map[string]bool{
"aix": true, "darwin": true, "dragonfly": true, "freebsd": true, "aix": true, "android": true, "darwin": true, "dragonfly": true,
"ios": true, "js": true, "linux": true, "netbsd": true, "freebsd": true, "hurd": true, "illumos": true, "ios": true,
"js": true, "linux": true, "nacl": true, "netbsd": true,
"openbsd": true, "plan9": true, "solaris": true, "wasip1": true, "openbsd": true, "plan9": true, "solaris": true, "wasip1": true,
"windows": true, "windows": true, "zos": true,
}
// resolveGOOS validates a -GOOS flag value, mirroring the architecture
// check's surface: a usage error naming what the tool accepts.
func resolveGOOS(name string) (string, error) {
lower := strings.ToLower(name)
if goOSNames[lower] {
return lower, nil
}
return "", &usageError{fmt.Errorf("unknown GOOS %q: want one of %s", name, strings.Join(slices.Sorted(maps.Keys(goOSNames)), ", "))}
}
// goosFromFilename returns the GOOS the file's name carries, by go/build's
// goodOSArchFile rule: the GOOS segment sits last, or last before the
// architecture segment (sys_darwin_arm64.s, vlop_arm.s carries none). An
// empty result means the name names no GOOS and the ambient one applies.
func goosFromFilename(path string) string {
base := path
if i := strings.LastIndexByte(base, '/'); i >= 0 {
base = base[i+1:]
}
base = strings.TrimSuffix(base, ".s")
// go/build ignores everything before the first underscore, so a GOOS
// segment is only ever looked for from there on.
i := strings.IndexByte(base, '_')
if i < 0 {
return ""
}
segs := strings.Split(base[i:], "_")
if n := len(segs); n >= 2 && goOSNames[segs[n-2]] && slices.Contains(goPortSuffixes, segs[n-1]) {
return segs[n-2]
}
if goOSNames[segs[len(segs)-1]] {
return segs[len(segs)-1]
}
return ""
} }
// otherGOOSFile reports whether the file's name names a GOOS other than the // otherGOOSFile reports whether the file's name names a GOOS other than the
@@ -543,6 +595,12 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
return nil, err return nil, err
} }
// The GOOS the header generation type-checks under follows the
// file's name when the name carries one; the ambient GOOS is the
// honest guess otherwise (a build tag naming another GOOS is
// invisible to a file-name rule).
goos := goosFromFilename(path)
var wanted []int // indexes into targets var wanted []int // indexes into targets
if a := arch.FromFilename(path); a != arch.Unknown { if a := arch.FromFilename(path); a != arch.Unknown {
for i, tg := range targets { for i, tg := range targets {
@@ -567,11 +625,12 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
// A file that includes go_asm.h parses against a per-target header: // A file that includes go_asm.h parses against a per-target header:
// the defines differ per architecture (internal/cpu's layout, for // the defines differ per architecture (internal/cpu's layout, for
// one), so the parse cannot be shared the way a header-free file's // one) and per GOOS (sys_darwin_arm64.s's trampoline constants,
// can. A generation failure is a failure for every target, named // for another), so the parse cannot be shared the way a
// for the package rather than a bare "include not found". A header // header-free file's can. A generation failure is a failure for
// already resolvable in the package directory or the -I list is // every target, named for the package rather than a bare "include
// left alone. // not found". A header already resolvable in the package
// directory or the -I list is left alone.
if len(wanted) > 0 && needsGoAsmHeader(src) && !goAsmHeaderResolved(filepath.Dir(path), dirs) { if len(wanted) > 0 && needsGoAsmHeader(src) && !goAsmHeaderResolved(filepath.Dir(path), dirs) {
if hdr == nil { if hdr == nil {
if hdr, err = newAsmhdrCache(); err != nil { if hdr, err = newAsmhdrCache(); err != nil {
@@ -583,7 +642,7 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
for _, i := range wanted { for _, i := range wanted {
tg, t := targets[i], tallies[i] tg, t := targets[i], tallies[i]
t.attempted++ t.attempted++
hdrDir, err := hdr.dirFor(pkgDir, goarchName(tg.a)) hdrDir, err := hdr.dirFor(pkgDir, goos, goarchName(tg.a))
if err != nil { if err != nil {
ok = false ok = false
t.fail(path, corpusReason(err)) t.fail(path, corpusReason(err))
+18 -5
View File
@@ -486,7 +486,7 @@ hover, document symbols, diagnostics and semantic-token highlighting.
} }
func cmdAsm(args []string) int { func cmdAsm(args []string) int {
fs := newCommand("asm", "gasm asm [--format raw|elf|goobj] [-I dir] [-p pkg] [-GOARCH arch] [-o out] <file>", ` fs := newCommand("asm", "gasm asm [--format raw|elf|goobj] [-I dir] [-p pkg] [-GOARCH arch] [-GOOS os] [-o out] <file>", `
Assemble FILE without the Go toolchain: every TEXT function is encoded to Assemble FILE without the Go toolchain: every TEXT function is encoded to
machine code and printed as a hex dump. Supported architectures: amd64 machine code and printed as a hex dump. Supported architectures: amd64
(including VEX/AVX2 and EVEX/AVX-512), arm64 (AArch64 integer, FP, (including VEX/AVX2 and EVEX/AVX-512), arm64 (AArch64 integer, FP,
@@ -507,18 +507,22 @@ and the format version from go version).
A file that includes go_asm.h gets that header generated automatically from A file that includes go_asm.h gets that header generated automatically from
the package it lives in (the .go files beside it, type-checked for the the package it lives in (the .go files beside it, type-checked for the
target architecture, the toolchain's own defines), so GOROOT assembly target architecture, the toolchain's own defines), so GOROOT assembly
assembles without a compiler. A package that has no Go files for the assembles without a compiler. -GOOS selects the type-checking GOOS for
target or does not type-check is a hard error naming the package. that header: a GOOS-specific file (sys_darwin_arm64.s) needs its platform's
defines, which a header from the ambient GOOS silently omits. A package
that has no Go files for the target or does not type-check is a hard error
naming the package.
`) `)
out := fs.String("o", "", "write the output to this file") out := fs.String("o", "", "write the output to this file")
format := fs.String("format", "raw", "output format: raw (concatenated image), elf or goobj (Go object)") format := fs.String("format", "raw", "output format: raw (concatenated image), elf or goobj (Go object)")
pkg := fs.String("p", "", "package path for --format goobj (qualifies the exported symbols)") pkg := fs.String("p", "", "package path for --format goobj (qualifies the exported symbols)")
archName := fs.String("GOARCH", "", "target architecture: amd64, arm64, riscv64 or loong64 (overrides the file-name suffix)") archName := fs.String("GOARCH", "", "target architecture: amd64, arm64, riscv64 or loong64 (overrides the file-name suffix)")
goosName := fs.String("GOOS", "", "operating system for go_asm.h generation: a GOOS go/build recognises (default: the host's)")
var dirs includeDirs var dirs includeDirs
fs.Var(&dirs, "I", "directory to search for #include files (may be repeated)") fs.Var(&dirs, "I", "directory to search for #include files (may be repeated)")
fs.Parse(args) fs.Parse(args)
if fs.NArg() != 1 { if fs.NArg() != 1 {
fmt.Fprintln(os.Stderr, "usage: gasm asm [--format raw|elf|goobj] [-I dir] [-p pkg] [-GOARCH arch] [-o out] <file>") fmt.Fprintln(os.Stderr, "usage: gasm asm [--format raw|elf|goobj] [-I dir] [-p pkg] [-GOARCH arch] [-GOOS os] [-o out] <file>")
return 2 return 2
} }
// The format is validated before anything else, so a bogus value exits 2 // The format is validated before anything else, so a bogus value exits 2
@@ -539,6 +543,15 @@ target or does not type-check is a hard error naming the package.
} }
targetArch = a targetArch = a
} }
goos := ""
if *goosName != "" {
g, err := resolveGOOS(*goosName)
if err != nil {
fmt.Fprintf(os.Stderr, "gasm asm: %v\n", err)
return 2
}
goos = g
}
src, err := readSource(path) src, err := readSource(path)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "gasm:", err) fmt.Fprintln(os.Stderr, "gasm:", err)
@@ -553,7 +566,7 @@ target or does not type-check is a hard error naming the package.
// A go_asm.h that already resolves (placed by hand, or passed with -I) // A go_asm.h that already resolves (placed by hand, or passed with -I)
// is left alone. // is left alone.
if needsGoAsmHeader(src) && !goAsmHeaderResolved(filepath.Dir(path), dirs) { if needsGoAsmHeader(src) && !goAsmHeaderResolved(filepath.Dir(path), dirs) {
hdrDir, cleanup, err := ensureGoAsmHeader(path, targetArch, nil) hdrDir, cleanup, err := ensureGoAsmHeader(path, targetArch, goos, nil)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "gasm asm:", err) fmt.Fprintln(os.Stderr, "gasm asm:", err)
return 1 return 1
+12 -2
View File
@@ -141,7 +141,7 @@ gasm lint kernel_amd64.s
## asm ## asm
```text ```text
Usage: gasm asm [--format raw|elf|goobj] [-I dir] [-p pkg] [-GOARCH arch] [-o out] <file> Usage: gasm asm [--format raw|elf|goobj] [-I dir] [-p pkg] [-GOARCH arch] [-GOOS os] [-o out] <file>
``` ```
| Flag | Default | Effect | | Flag | Default | Effect |
@@ -150,6 +150,7 @@ Usage: gasm asm [--format raw|elf|goobj] [-I dir] [-p pkg] [-GOARCH arch] [-o ou
| `-I` | empty | directory to search for `#include` files; may be repeated, searched in order after the source directory | | `-I` | empty | directory to search for `#include` files; may be repeated, searched in order after the source directory |
| `-p` | empty | package path for `--format goobj`, qualifying the exported symbols | | `-p` | empty | package path for `--format goobj`, qualifying the exported symbols |
| `-GOARCH` | empty | target architecture: `amd64`, `arm64`, `riscv64` or `loong64`; overrides the file-name suffix | | `-GOARCH` | empty | target architecture: `amd64`, `arm64`, `riscv64` or `loong64`; overrides the file-name suffix |
| `-GOOS` | empty | operating system for the generated `go_asm.h`: any GOOS `go/build` recognises in file names; default is the host's |
| `-o` | empty | write the output to this file instead of a hex dump on stdout | | `-o` | empty | write the output to this file instead of a hex dump on stdout |
Supported architectures: amd64 (VEX/AVX2 and EVEX/AVX-512 included), arm64, Supported architectures: amd64 (VEX/AVX2 and EVEX/AVX-512 included), arm64,
@@ -163,6 +164,13 @@ system toolchain; `goobj` emits the Go toolchain's own object format, which
installed: the object preamble is captured from `go tool asm` and the format installed: the object preamble is captured from `go tool asm` and the format
version from `go version`. `raw` and `elf` need no toolchain at all. version from `go version`. `raw` and `elf` need no toolchain at all.
A file that includes `go_asm.h` gets that header generated from the Go
files beside it, type-checked for the target. `-GOOS` selects the
type-checking GOOS for that header, because a GOOS-specific file needs its
platform's defines: `sys_darwin_arm64.s` fails against the ambient GOOS
(`machTimebaseInfo_numer` is missing from a linux type-check) and assembles
with `-GOOS darwin`.
Assembly preprocessing matches the toolchain's: `#define` macros (object and Assembly preprocessing matches the toolchain's: `#define` macros (object and
parameterised) expand at the point of use, `#undef`, `#ifdef`, `#ifndef`, parameterised) expand at the point of use, `#undef`, `#ifdef`, `#ifndef`,
`#else` and `#endif` behave as in `go tool asm`, `;` separates statements, `#else` and `#endif` behave as in `go tool asm`, `;` separates statements,
@@ -390,7 +398,9 @@ With `--corpus` the audit changes shape: it assembles every `.s` file under
DIR (default `GOROOT/src`) with the gasm encoder only, no toolchain probing. DIR (default `GOROOT/src`) with the gasm encoder only, no toolchain probing.
A file whose name carries a recognisable `_arch` suffix is attempted for that A file whose name carries a recognisable `_arch` suffix is attempted for that
architecture; a file without one is attempted for all four, exactly as a architecture; a file without one is attempted for all four, exactly as a
`GOARCH` build would compile it. The report gives the headline number (files `GOARCH` build would compile it, and a name that names a GOOS
(`sys_darwin_arm64.s`) type-checks its generated `go_asm.h` for that GOOS.
The report gives the headline number (files
that assemble for every target architecture), the per-architecture pass rates that assemble for every target architecture), the per-architecture pass rates
and the most common failure reasons with one representative file each, which and the most common failure reasons with one representative file each, which
drive the encodability backlog by frequency rather than by table order. A run drive the encodability backlog by frequency rather than by table order. A run
+14 -1
View File
@@ -2,7 +2,7 @@
.SH NAME .SH NAME
gasm-asm \- assemble Plan 9 assembly without the Go toolchain gasm-asm \- assemble Plan 9 assembly without the Go toolchain
.SH SYNOPSIS .SH SYNOPSIS
.B gasm asm [\-\-format raw|elf|goobj] [\-I dir] [\-p pkg] [\-GOARCH arch] [\-o out] <file> .B gasm asm [\-\-format raw|elf|goobj] [\-I dir] [\-p pkg] [\-GOARCH arch] [\-GOOS os] [\-o out] <file>
.SH DESCRIPTION .SH DESCRIPTION
Assemble FILE without the Go toolchain: every TEXT function is encoded Assemble FILE without the Go toolchain: every TEXT function is encoded
to machine code and printed as a hex dump. Supported architectures: to machine code and printed as a hex dump. Supported architectures:
@@ -42,6 +42,15 @@ need no toolchain at all.
Framed functions receive the stack-split guard and the trailing Framed functions receive the stack-split guard and the trailing
morestack block, byte-identical to the toolchain's output, so split morestack block, byte-identical to the toolchain's output, so split
functions link too. functions link too.
.PP
A file that includes go_asm.h gets that header generated from the Go
files beside it, type-checked for the target.
.B \-GOOS
selects the type-checking GOOS for that header, because a GOOS-specific
file needs its platform's defines: sys_darwin_arm64.s fails against the
ambient GOOS (machTimebaseInfo_numer is missing from a linux type-check)
and assembles with
.BR "\-GOOS darwin" .
.SH OPTIONS .SH OPTIONS
.TP .TP
.B \-\-format \fIraw|elf|goobj\fR .B \-\-format \fIraw|elf|goobj\fR
@@ -59,6 +68,10 @@ Target architecture: amd64, arm64, riscv64 or loong64; overrides the
file-name suffix, which is how the suffix-less majority of GOROOT's file-name suffix, which is how the suffix-less majority of GOROOT's
files (cpu_x86.s, stub.s, ...) become assemblable. files (cpu_x86.s, stub.s, ...) become assemblable.
.TP .TP
.B \-GOOS \fIos\fR
Operating system for the generated go_asm.h: any GOOS go/build
recognises in file names; the default is the host's.
.TP
.B \-o \fIfile\fR .B \-o \fIfile\fR
Write the output to this file instead of a hex dump on stdout. Write the output to this file instead of a hex dump on stdout.
.SH EXIT STATUS .SH EXIT STATUS
+7
View File
@@ -241,6 +241,13 @@ func renderInstr(line []token.Token, width int) string {
if line[0].Kind != token.Ident { if line[0].Kind != token.Ident {
return "\t" + mnem + " " + ops return "\t" + mnem + " " + ops
} }
// A statement separator belongs to the statement it ends: when the
// operands open with a ';', the alignment padding would land between
// the mnemonic and its own separator (REP ; MOVSQ), so such a line
// renders with a single space whatever the function's width.
if strings.HasPrefix(ops, ";") {
return "\t" + mnem + " " + ops
}
if width < len(mnem) { if width < len(mnem) {
width = len(mnem) width = len(mnem)
} }
+11
View File
@@ -364,6 +364,17 @@ func TestSemicolonSeparators(t *testing.T) {
in: "TEXT ·f(SB), $0\nBYTE $1;\nRET\n", in: "TEXT ·f(SB), $0\nBYTE $1;\nRET\n",
want: "TEXT ·f(SB), $0\n\tBYTE $1;\n\tRET\n", want: "TEXT ·f(SB), $0\n\tBYTE $1;\n\tRET\n",
}, },
{
// The REP shape: a prefix-style zero-operand statement
// followed by the instruction it prefixes. The separator
// belongs to the statement it ends, so the function's
// alignment width (MOVSQ is the widest mnemonic here) must
// not open a gap before it: one space after the mnemonic
// whatever the neighbours' lengths.
name: "after a prefix-style statement",
in: "TEXT ·f(SB), $0\nMOVQ AX, BX\nREP; MOVSQ\nRET\n",
want: "TEXT ·f(SB), $0\n\tMOVQ AX, BX\n\tREP ; MOVSQ\n\tRET\n",
},
} }
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {