blob: 68a226c4f69c7115687396992bd3a3d773840a43 [file] [edit]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// genavxtests generates test cases by running the assembler test
// files in reverse: given a binary encoding, expect the Plan 9/Go
// assembly input that produces the encoding. For GNU and Intel
// syntaxes, it uses objdump to retrieve the disassembly output.
package main
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
)
var tmpDir string
func main() {
files := []string{
runtime.GOROOT() + "/src/cmd/asm/internal/asm/testdata/amd64enc.s",
runtime.GOROOT() + "/src/cmd/asm/internal/asm/testdata/amd64enc_extra.s",
}
// Assembler test is like
// " VPGATHERDQ Y2, (BP)(X7*2), Y1 // c4e2ed904c7d00"
hexRe := regexp.MustCompile(`^\s*(.*?)\s*//\s*([0-9a-fA-F]+)\s*$`)
type testCase struct {
hex string
plan9 string
}
var testCases []testCase
seen := make(map[string]bool)
for _, file := range files {
f, err := os.Open(file)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
m := hexRe.FindStringSubmatch(line)
if len(m) == 3 {
plan9 := strings.TrimSpace(m[1])
if plan9 == "" || strings.HasPrefix(plan9, "//") {
continue
}
if !strings.HasPrefix(plan9, "V") && !strings.HasPrefix(plan9, "K") && !strings.HasPrefix(plan9, "SHA") {
// Test only AVX and SHA instructions for now. (There exist tests for
// non-AVX ones).
continue
}
h := strings.ToLower(m[2])
if !seen[h] {
seen[h] = true
testCases = append(testCases, testCase{hex: h, plan9: plan9})
}
}
}
f.Close()
}
var buf bytes.Buffer
var err error
tmpDir, err = os.MkdirTemp("", "avxtest")
if err != nil {
fmt.Println("Error creating temp dir:", err)
return
}
defer os.RemoveAll(tmpDir)
for _, tc := range testCases {
processHex(tc.hex, tc.plan9, &buf)
}
fmt.Println(buf.String())
}
func processHex(h string, plan9Asm string, buf *bytes.Buffer) {
b, err := hex.DecodeString(h)
if err != nil {
fmt.Printf("Error decoding hex %s: %v\n", h, err)
return
}
var s bytes.Buffer
s.WriteString(".text\n")
s.WriteString(".byte ")
for i, v := range b {
if i > 0 {
s.WriteString(",")
}
fmt.Fprintf(&s, "0x%02x", v)
}
s.WriteString("\n")
asmFile := filepath.Join(tmpDir, "testtmp.s")
objFile := filepath.Join(tmpDir, "testtmp.o")
err = os.WriteFile(asmFile, s.Bytes(), 0644)
if err != nil {
fmt.Println("Error writing testtmp.s:", err)
return
}
cmd := exec.Command("cc", "-arch", "x86_64", "-c", asmFile, "-o", objFile)
err = cmd.Run()
if err != nil {
fmt.Printf("Error compiling hex %s: %v\n", h, err)
return
}
gnuOut, _ := exec.Command("objdump", "-d", objFile).CombinedOutput()
intelOut, _ := exec.Command("objdump", "--x86-asm-syntax=intel", "-d", objFile).CombinedOutput()
gnuAsm := parseObjdump(string(gnuOut))
intelAsm := parseObjdump(string(intelOut))
// Normalize spaces
gnuAsm = strings.ReplaceAll(gnuAsm, "\t", " ")
intelAsm = strings.ReplaceAll(intelAsm, "\t", " ")
gnuAsm = strings.ReplaceAll(gnuAsm, ", ", ",")
intelAsm = strings.ReplaceAll(intelAsm, " + ", "+")
intelAsm = strings.ReplaceAll(intelAsm, " - ", "-")
// Normalize Plan 9 decimal offsets to hex since x86asm outputs them as hex.
rePlan9Num := regexp.MustCompile(`([\s\$])(-?[0-9]+)([\(,])`)
plan9Asm = rePlan9Num.ReplaceAllStringFunc(plan9Asm, func(m string) string {
matches := rePlan9Num.FindStringSubmatch(m)
prefix := matches[1]
numStr := matches[2]
suffix := matches[3]
n, _ := strconv.ParseInt(numStr, 10, 64)
if prefix == "$" && n < 0 && n >= -128 {
// Use unsigned immediate.
// TODO: check.
n = n & 0xff
}
if prefix != "$" && n == 0 { // 0 offset remains 0, not 0x0
return fmt.Sprintf("%s0%s", prefix, suffix)
}
if n < 0 {
return fmt.Sprintf("%s-0x%x%s", prefix, -n, suffix)
}
return fmt.Sprintf("%s0x%x%s", prefix, n, suffix)
})
rePlan9Mem := regexp.MustCompile(` \(([A-Z0-9]+)\)`)
plan9Asm = rePlan9Mem.ReplaceAllString(plan9Asm, " 0(${1})")
plan9Asm = strings.ReplaceAll(plan9Asm, ",", ", ")
plan9Asm = strings.ReplaceAll(plan9Asm, ", ", ", ")
padSize := 16 - len(b)
padding := ""
if padSize > 0 {
padding = strings.Repeat("5f", padSize)
}
hexStr := fmt.Sprintf("%s|%s", h, padding)
buf.WriteString(fmt.Sprintf("%s\t64\tplan9\t%s\n", hexStr, plan9Asm))
buf.WriteString(fmt.Sprintf("%s\t64\tgnu\t%s\n", hexStr, gnuAsm))
buf.WriteString(fmt.Sprintf("%s\t64\tintel\t%s\n", hexStr, intelAsm))
}
// parseObjdump extracts the assembly string from objdump output.
// It expects lines like: " 0: 62 f1 fd 48 2d c2 vaddpd %zmm2,%zmm21,%zmm2"
func parseObjdump(output string) string {
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.Contains(line, ":") && (strings.Contains(line, "\t") || strings.Contains(line, " ")) {
parts := strings.SplitN(strings.TrimSpace(line), ":", 2)
if len(parts) < 2 {
continue
}
body := strings.TrimSpace(parts[1])
var asm string
subParts := strings.SplitN(body, "\t", 2)
if len(subParts) == 2 {
asm = strings.TrimSpace(subParts[1])
} else {
idx := strings.Index(body, " ")
if idx > 0 {
asm = strings.TrimSpace(body[idx:])
} else {
continue
}
}
asm = strings.ReplaceAll(asm, "\t", " ")
if idx := strings.Index(asm, " ##"); idx >= 0 {
asm = strings.TrimSpace(asm[:idx])
}
if idx := strings.Index(asm, " #"); idx >= 0 {
asm = strings.TrimSpace(asm[:idx])
}
if idx := strings.Index(asm, " <"); idx >= 0 {
asm = strings.TrimSpace(asm[:idx])
}
return asm
}
}
return ""
}