feat(cmd): GOOS-aware headers, audit battery shapes and semicolon spacing
Test / test (push) Failing after 2m21s
Test / test (push) Failing after 2m21s
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
|
||||
|
||||
+122
-11
@@ -22,12 +22,13 @@ func writePkg(t *testing.T, files map[string]string) string {
|
||||
return dir
|
||||
}
|
||||
|
||||
// generateFor generates the header for dir and returns its text.
|
||||
func generateFor(t *testing.T, dir, goarch string) string {
|
||||
// generateFor generates the header for dir and returns its text. An empty
|
||||
// goos means the ambient one.
|
||||
func generateFor(t *testing.T, dir, goos, goarch string) string {
|
||||
t.Helper()
|
||||
hdrDir, err := generateGoAsmHeader(dir, goarch, t.TempDir())
|
||||
hdrDir, err := generateGoAsmHeader(dir, goos, goarch, t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("generateGoAsmHeader(%q, %s): %v", dir, goarch, err)
|
||||
t.Fatalf("generateGoAsmHeader(%q, %s, %s): %v", dir, goos, goarch, err)
|
||||
}
|
||||
b, err := os.ReadFile(filepath.Join(hdrDir, "go_asm.h"))
|
||||
if err != nil {
|
||||
@@ -72,7 +73,7 @@ type aliased struct {
|
||||
|
||||
type alias = aliased
|
||||
`})
|
||||
hdr := generateFor(t, dir, "amd64")
|
||||
hdr := generateFor(t, dir, "", "amd64")
|
||||
want := []string{
|
||||
"#define const_bufSize 1024",
|
||||
// iota resolves through go/types, one define per name.
|
||||
@@ -133,8 +134,8 @@ package perarch
|
||||
const flavour = 2
|
||||
`,
|
||||
})
|
||||
amd64 := generateFor(t, dir, "amd64")
|
||||
arm64 := generateFor(t, dir, "arm64")
|
||||
amd64 := generateFor(t, dir, "", "amd64")
|
||||
arm64 := generateFor(t, dir, "", "arm64")
|
||||
if !strings.Contains(amd64, "#define const_flavour 1\n") {
|
||||
t.Errorf("amd64 header misses const_flavour 1:\n%s", amd64)
|
||||
}
|
||||
@@ -149,19 +150,90 @@ const flavour = 2
|
||||
if !strings.Contains(amd64, "#define layout__size 16\n") || !strings.Contains(amd64, "#define layout_p 8\n") {
|
||||
t.Errorf("amd64 layout wrong:\n%s", amd64)
|
||||
}
|
||||
w386 := generateFor(t, dir, "386")
|
||||
w386 := generateFor(t, dir, "", "386")
|
||||
if !strings.Contains(w386, "#define layout__size 8\n") || !strings.Contains(w386, "#define layout_p 4\n") {
|
||||
t.Errorf("386 layout wrong:\n%s", w386)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateGoAsmHeaderGOOS pins the GOOS half of the target: only the
|
||||
// platform's own files type-check into the header, which is why
|
||||
// sys_darwin_arm64.s cannot assemble against a linux-generated one.
|
||||
func TestGenerateGoAsmHeaderGOOS(t *testing.T) {
|
||||
dir := writePkg(t, map[string]string{
|
||||
"common.go": `package goosaware
|
||||
|
||||
type shared struct {
|
||||
a int32
|
||||
}
|
||||
`,
|
||||
"plat_darwin.go": `//go:build darwin
|
||||
|
||||
package goosaware
|
||||
|
||||
type platform struct {
|
||||
trampoline_numer int64
|
||||
}
|
||||
`,
|
||||
"plat_windows.go": `//go:build windows
|
||||
|
||||
package goosaware
|
||||
|
||||
type platform struct {
|
||||
callbackArgs__size int32
|
||||
}
|
||||
`,
|
||||
})
|
||||
darwin := generateFor(t, dir, "darwin", "arm64")
|
||||
if !strings.Contains(darwin, "#define platform__size 8\n") || !strings.Contains(darwin, "#define platform_trampoline_numer 0\n") {
|
||||
t.Errorf("darwin header misses the darwin layout:\n%s", darwin)
|
||||
}
|
||||
if strings.Contains(darwin, "callbackArgs") {
|
||||
t.Errorf("darwin header must not carry the windows layout:\n%s", darwin)
|
||||
}
|
||||
windows := generateFor(t, dir, "windows", "arm64")
|
||||
if !strings.Contains(windows, "#define platform_callbackArgs__size 0\n") {
|
||||
t.Errorf("windows header misses the windows layout:\n%s", windows)
|
||||
}
|
||||
if strings.Contains(windows, "trampoline_numer") {
|
||||
t.Errorf("windows header must not carry the darwin layout:\n%s", windows)
|
||||
}
|
||||
// The ambient GOOS is neither of the two, so only shared's defines are
|
||||
// emitted; the shared type keeps its layout there.
|
||||
ambient := generateFor(t, dir, "", "arm64")
|
||||
if !strings.Contains(ambient, "#define shared__size 4\n") {
|
||||
t.Errorf("ambient header misses the shared layout:\n%s", ambient)
|
||||
}
|
||||
if strings.Contains(ambient, "#define platform_") {
|
||||
t.Errorf("ambient header must not carry either platform layout:\n%s", ambient)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoosFromFilename(t *testing.T) {
|
||||
for path, want := range map[string]string{
|
||||
"/x/sys_darwin_arm64.s": "darwin",
|
||||
"/x/sys_windows_arm64.s": "windows",
|
||||
"/x/asm_linux_amd64.s": "linux",
|
||||
"/x/rt0_darwin_arm64.s": "darwin",
|
||||
"/x/vgetrandom_zos_s390x.s": "zos",
|
||||
"/x/rt0_js_wasm.s": "js",
|
||||
"/x/memmove_amd64.s": "",
|
||||
"/x/vlop_arm.s": "",
|
||||
"/x/stubs.s": "",
|
||||
} {
|
||||
if got := goosFromFilename(path); got != want {
|
||||
t.Errorf("goosFromFilename(%q) = %q, want %q", path, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateGoAsmHeaderErrors(t *testing.T) {
|
||||
t.Run("type error", func(t *testing.T) {
|
||||
dir := writePkg(t, map[string]string{"bad.go": `package bad
|
||||
|
||||
const x = undefinedIdent
|
||||
`})
|
||||
_, err := generateGoAsmHeader(dir, "amd64", t.TempDir())
|
||||
_, err := generateGoAsmHeader(dir, "", "amd64", t.TempDir())
|
||||
if err == nil {
|
||||
t.Fatal("generation must fail for a package that does not type-check")
|
||||
}
|
||||
@@ -174,7 +246,7 @@ const x = undefinedIdent
|
||||
})
|
||||
t.Run("no go files", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
_, err := generateGoAsmHeader(dir, "amd64", t.TempDir())
|
||||
_, err := generateGoAsmHeader(dir, "", "amd64", t.TempDir())
|
||||
if err == nil {
|
||||
t.Fatal("generation must fail without Go files")
|
||||
}
|
||||
@@ -281,6 +353,45 @@ type header struct {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunCorpusAuditGOOS covers the filename-derived GOOS end to end: a
|
||||
// kernel whose name names darwin must have its header type-checked with
|
||||
// GOOS=darwin, so the darwin-only constant it offsets with is defined. The
|
||||
// operand mirrors sys_darwin_arm64.s's trampoline, where a missing define
|
||||
// leaves an unexpanded symbol in the offset and fails.
|
||||
func TestRunCorpusAuditGOOS(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
write := func(name, src string) {
|
||||
t.Helper()
|
||||
if err := os.WriteFile(filepath.Join(dir, name), []byte(src), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
write("pkg.go", "package corpus\n")
|
||||
write("plat_darwin.go", "//go:build darwin\n\npackage corpus\n\nconst trampolineNumer = 8\n")
|
||||
write("kern_darwin_arm64.s", "#include \"go_asm.h\"\n"+
|
||||
"GLOBL timebase<>(SB), NOPTR, $16\n"+
|
||||
"TEXT \xc2\xb7g(SB), NOSPLIT, $0-0\n"+
|
||||
"\tMOVD\ttimebase<>+const_trampolineNumer(SB), R0\n"+
|
||||
"\tRET\n")
|
||||
|
||||
stats, err := runCorpusAudit(dir, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("runCorpusAudit: %v", err)
|
||||
}
|
||||
var arm *corpusTally
|
||||
for i, tg := range stats.targets {
|
||||
if tg.name == "arm64" {
|
||||
arm = stats.tallies[i]
|
||||
}
|
||||
}
|
||||
if arm == nil {
|
||||
t.Fatal("no arm64 tally")
|
||||
}
|
||||
if arm.attempted != 1 || arm.assembled != 1 {
|
||||
t.Errorf("arm64 = %d/%d, want 1/1; reasons: %v", arm.assembled, arm.attempted, arm.reasons)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateGoAsmHeaderRuntime pins the generator against the real thing:
|
||||
// the runtime package, whose header the toolchain's own -asmhdr output was
|
||||
// sampled from. Skipped in short mode: it type-checks the whole package.
|
||||
@@ -288,7 +399,7 @@ func TestGenerateGoAsmHeaderRuntime(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("type-checks the whole runtime package")
|
||||
}
|
||||
dir, err := generateGoAsmHeader("/usr/local/go/src/runtime", "amd64", t.TempDir())
|
||||
dir, err := generateGoAsmHeader("/usr/local/go/src/runtime", "", "amd64", t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("generateGoAsmHeader(runtime): %v", 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