package gen import ( "fmt" "math" "strconv" "strings" "github.com/mmcloughlin/avo/internal/inst" "github.com/mmcloughlin/avo/internal/prnt" "github.com/mmcloughlin/avo/printer" ) type asmtest struct { cfg printer.Config sym string // reference to the test function symbol rel8 string // label to be used for near jumps rel32 string // label for far jumps prnt.Generator } // NewAsmTest prints one massive assembly function containing a line for every // instruction form in the database. The intention is to pass this to the Go // assembler and confirm there are no errors, thus helping to ensure our // database is compatible. func NewAsmTest(cfg printer.Config) Interface { return &asmtest{cfg: cfg} } func (a *asmtest) Generate(is []inst.Instruction) ([]byte, error) { a.Printf("// %s\n\n", a.cfg.GeneratedWarning()) a.sym = "\u00b7loadertest(SB)" a.Printf("TEXT %s, 0, $0\n", a.sym) // Define a label for far jumps. a.Printf("rel32:\n") a.rel32 = "rel32" for _, i := range is { a.Printf("\t// %s %s\n", i.Opcode, i.Summary) if skip, msg := a.skip(i.Opcode); skip { a.Printf("\t// SKIP: %s\n", msg) continue } if i.Opcode[0] == 'J' { label := fmt.Sprintf("rel8_%s", strings.ToLower(i.Opcode)) a.Printf("%s:\n", label) a.rel8 = label } for _, f := range i.Forms { as, err := a.args(i.Opcode, f.Operands) if err != nil { return nil, fmt.Errorf("tests for %s: %w", i.Opcode, err) } for _, suffixes := range f.SupportedSuffixes() { opcode := i.Opcode if len(suffixes) > 0 { opcode += "." + suffixes.String() } a.Printf("\t%s\t%s\n", opcode, strings.Join(as, ", ")) } } a.Printf("\n") } a.Printf("\tRET\n") return a.Result() } func (a asmtest) skip(opcode string) (bool, string) { prefixes := map[string]string{ "PUSH": "PUSH can produce 'unbalanced PUSH/POP' assembler error", "POP": "POP can produce 'unbalanced PUSH/POP' assembler error", } for p, m := range prefixes { if strings.HasPrefix(opcode, p) { return true, m } } return false, "" } func (a asmtest) args(opcode string, ops []inst.Operand) ([]string, error) { // Special case for CALL, since it needs a different type of rel32 argument than others. if opcode == "CALL" { return []string{a.sym}, nil } as := make([]string, len(ops)) for i, op := range ops { a := a.arg(op.Type, i) if a == "" { return nil, fmt.Errorf("unsupported operand type %q", op.Type) } as[i] = a } return as, nil } // arg generates an argument for an operand of the given type. func (a asmtest) arg(t string, i int) string { m := map[string]string{ "1": "$1", // "3": "$3", // "imm2u": "$3", // "imm8": fmt.Sprintf("$%d", math.MaxInt8), // "imm16": fmt.Sprintf("$%d", math.MaxInt16), // "imm32": fmt.Sprintf("$%d", math.MaxInt32), // "imm64": fmt.Sprintf("$%d", math.MaxInt64), // "al": "AL", // "cl": "CL", // "r8": "CH", // "ax": "AX", // "r16": "SI", // "eax": "AX", // "r32": "DX", // "rax": "AX", // "r64": "R15", // "mm": "M5", // "xmm0": "X0", // "xmm": "X" + strconv.Itoa(7+i), // // // "ymm": "Y" + strconv.Itoa(3+i), // // // "zmm": "Z" + strconv.Itoa(16+i), // // // "k": "K" + strconv.Itoa(1+i), // // // // "m": "0(AX)(CX*2)", // "m8": "8(AX)(CX*2)", // "m16": "16(AX)(CX*2)", // // "m32": "32(AX)(CX*2)", // // // "m64": "64(AX)(CX*2)", // // // "m128": "128(AX)(CX*2)", // // "m256": "256(AX)(CX*2)", // // "m512": "512(AX)(CX*2)", // // "vm32x": "32(X14*8)", // // "vm64x": "64(X14*8)", // // "vm32y": "32(Y13*8)", // // "vm64y": "64(Y13*8)", // // "vm32z": "32(Z13*8)", // // "vm64z": "64(Z13*8)", // // "rel8": a.rel8, // "rel32": a.rel32, // // // // Appear unused: "r8l": "????", // "r16l": "????", // "r32l": "????", // } return m[t] }