feat(cmd): GOOS-aware headers, audit battery shapes and semicolon spacing
Test / test (push) Canceled after 1m8s
Test / test (push) Canceled after 1m8s
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+34
-21
@@ -27,7 +27,10 @@ import (
|
||||
// GOROOT assembly includes it, and a standalone assembler has no compiler
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
}
|
||||
|
||||
// generateGoAsmHeader type-checks the Go package in pkgDir for goarch,
|
||||
// writes its go_asm.h equivalent into dir, and returns dir. The caller
|
||||
// owns the directory and its removal.
|
||||
func generateGoAsmHeader(pkgDir, goarch, dir string) (string, error) {
|
||||
imp := newSourceImporter(goarch)
|
||||
// generateGoAsmHeader type-checks the Go package in pkgDir for goos and
|
||||
// goarch, writes its go_asm.h equivalent into dir, and returns dir. An
|
||||
// empty goos means the ambient one. The caller owns the directory and its
|
||||
// removal.
|
||||
func generateGoAsmHeader(pkgDir, goos, goarch, dir string) (string, error) {
|
||||
if goos == "" {
|
||||
goos = build.Default.GOOS
|
||||
}
|
||||
imp := newSourceImporter(goos, goarch)
|
||||
if imp.sizes == nil {
|
||||
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
|
||||
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
|
||||
// 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
|
||||
@@ -203,13 +210,14 @@ type sourceImporter struct {
|
||||
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
|
||||
// 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.
|
||||
func newSourceImporter(goarch string) *sourceImporter {
|
||||
func newSourceImporter(goos, goarch string) *sourceImporter {
|
||||
ctxt := new(build.Context)
|
||||
*ctxt = build.Default
|
||||
ctxt.GOOS = goos
|
||||
ctxt.GOARCH = goarch
|
||||
ctxt.CgoEnabled = false
|
||||
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 asmhdrCache struct {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -304,17 +312,22 @@ func newAsmhdrCache() (*asmhdrCache, error) {
|
||||
}
|
||||
|
||||
// dirFor returns the directory holding the generated go_asm.h for pkgDir
|
||||
// and goarch, generating it on first use.
|
||||
func (c *asmhdrCache) dirFor(pkgDir, goarch string) (string, error) {
|
||||
key := pkgDir + "\x00" + goarch
|
||||
// under goos and goarch, generating it on first use. An empty goos means
|
||||
// the ambient one, resolved here so that one package cannot generate twice
|
||||
// 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 {
|
||||
return dir, nil
|
||||
}
|
||||
if err, ok := c.errs[key]; ok {
|
||||
return "", err
|
||||
}
|
||||
dir := filepath.Join(c.root, fmt.Sprintf("h%d_%s", len(c.dirs), goarch))
|
||||
if _, err := generateGoAsmHeader(pkgDir, goarch, dir); err != nil {
|
||||
dir := filepath.Join(c.root, fmt.Sprintf("h%d_%s_%s", len(c.dirs), goos, goarch))
|
||||
if _, err := generateGoAsmHeader(pkgDir, goos, goarch, dir); err != nil {
|
||||
c.errs[key] = err
|
||||
return "", err
|
||||
}
|
||||
@@ -327,10 +340,10 @@ func (c *asmhdrCache) close() { os.RemoveAll(c.root) }
|
||||
|
||||
// ensureGoAsmHeader prepares the include directory a file that includes
|
||||
// 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
|
||||
// architecture cannot be determined, and passes through the generator's
|
||||
// diagnostics, which name the package.
|
||||
func ensureGoAsmHeader(path string, target arch.Arch, cache *asmhdrCache) (string, func(), error) {
|
||||
// for the file's target GOOS and architecture. It reports a usage error
|
||||
// when the architecture cannot be determined, and passes through the
|
||||
// generator's diagnostics, which name the package.
|
||||
func ensureGoAsmHeader(path string, target arch.Arch, goos string, cache *asmhdrCache) (string, func(), error) {
|
||||
if path == "-" {
|
||||
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")
|
||||
}
|
||||
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
|
||||
}
|
||||
root, err := os.MkdirTemp("", "gasm-asmhdr")
|
||||
if err != nil {
|
||||
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 {
|
||||
os.RemoveAll(root)
|
||||
return "", nil, err
|
||||
|
||||
+68
-9
@@ -5,6 +5,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"os/exec"
|
||||
"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",
|
||||
"$4, V1.B16, V2.B16, V3.B16", "$15, V1", "V1, V2, 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:
|
||||
return []string{
|
||||
@@ -307,6 +313,9 @@ func probeShapes(a arch.Arch) []string {
|
||||
"X5, X6, p2", "R5, R6, p2",
|
||||
"X5, E8, M8, TA, MA, X6", "$4, E32, M1, TA, MA, X1",
|
||||
"(X5), X6, V1, V2",
|
||||
// The CSR immediate forms the toolchain's testdata spells:
|
||||
// immediate, CSR name, destination.
|
||||
"$2, TIME, X5",
|
||||
"",
|
||||
}
|
||||
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",
|
||||
// The vector compare-to-flag forms land in an FCC register.
|
||||
"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.
|
||||
var goOSNames = map[string]bool{
|
||||
"aix": true, "darwin": true, "dragonfly": true, "freebsd": true,
|
||||
"ios": true, "js": true, "linux": true, "netbsd": true,
|
||||
"aix": true, "android": true, "darwin": true, "dragonfly": 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,
|
||||
"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
|
||||
@@ -543,6 +595,12 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
|
||||
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
|
||||
if a := arch.FromFilename(path); a != arch.Unknown {
|
||||
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:
|
||||
// the defines differ per architecture (internal/cpu's layout, for
|
||||
// one), so the parse cannot be shared the way a header-free file's
|
||||
// can. A generation failure is a failure for every target, named
|
||||
// for the package rather than a bare "include not found". A header
|
||||
// already resolvable in the package directory or the -I list is
|
||||
// left alone.
|
||||
// one) and per GOOS (sys_darwin_arm64.s's trampoline constants,
|
||||
// for another), so the parse cannot be shared the way a
|
||||
// header-free file's can. A generation failure is a failure for
|
||||
// every target, named for the package rather than a bare "include
|
||||
// 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 hdr == nil {
|
||||
if hdr, err = newAsmhdrCache(); err != nil {
|
||||
@@ -583,7 +642,7 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
|
||||
for _, i := range wanted {
|
||||
tg, t := targets[i], tallies[i]
|
||||
t.attempted++
|
||||
hdrDir, err := hdr.dirFor(pkgDir, goarchName(tg.a))
|
||||
hdrDir, err := hdr.dirFor(pkgDir, goos, goarchName(tg.a))
|
||||
if err != nil {
|
||||
ok = false
|
||||
t.fail(path, corpusReason(err))
|
||||
|
||||
+18
-5
@@ -486,7 +486,7 @@ hover, document symbols, diagnostics and semantic-token highlighting.
|
||||
}
|
||||
|
||||
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
|
||||
machine code and printed as a hex dump. Supported architectures: amd64
|
||||
(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
|
||||
the package it lives in (the .go files beside it, type-checked for the
|
||||
target architecture, the toolchain's own defines), so GOROOT assembly
|
||||
assembles without a compiler. A package that has no Go files for the
|
||||
target or does not type-check is a hard error naming the package.
|
||||
assembles without a compiler. -GOOS selects the type-checking GOOS for
|
||||
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")
|
||||
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)")
|
||||
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
|
||||
fs.Var(&dirs, "I", "directory to search for #include files (may be repeated)")
|
||||
fs.Parse(args)
|
||||
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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
// is left alone.
|
||||
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 {
|
||||
fmt.Fprintln(os.Stderr, "gasm asm:", err)
|
||||
return 1
|
||||
|
||||
+12
-2
@@ -141,7 +141,7 @@ gasm lint kernel_amd64.s
|
||||
## asm
|
||||
|
||||
```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 |
|
||||
@@ -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 |
|
||||
| `-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 |
|
||||
| `-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 |
|
||||
|
||||
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
|
||||
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
|
||||
parameterised) expand at the point of use, `#undef`, `#ifdef`, `#ifndef`,
|
||||
`#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.
|
||||
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
|
||||
`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
|
||||
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
|
||||
|
||||
+14
-1
@@ -2,7 +2,7 @@
|
||||
.SH NAME
|
||||
gasm-asm \- assemble Plan 9 assembly without the Go toolchain
|
||||
.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
|
||||
Assemble FILE without the Go toolchain: every TEXT function is encoded
|
||||
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
|
||||
morestack block, byte-identical to the toolchain's output, so split
|
||||
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
|
||||
.TP
|
||||
.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
|
||||
files (cpu_x86.s, stub.s, ...) become assemblable.
|
||||
.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
|
||||
Write the output to this file instead of a hex dump on stdout.
|
||||
.SH EXIT STATUS
|
||||
|
||||
@@ -241,6 +241,13 @@ func renderInstr(line []token.Token, width int) string {
|
||||
if line[0].Kind != token.Ident {
|
||||
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) {
|
||||
width = len(mnem)
|
||||
}
|
||||
|
||||
@@ -364,6 +364,17 @@ func TestSemicolonSeparators(t *testing.T) {
|
||||
in: "TEXT ·f(SB), $0\nBYTE $1;\nRET\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 {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user