diff --git a/asm/amd64_ext_asm_test.go b/asm/amd64_ext_asm_test.go index 4c38737..37bc3ba 100644 --- a/asm/amd64_ext_asm_test.go +++ b/asm/amd64_ext_asm_test.go @@ -6,6 +6,7 @@ package asm import ( "bytes" "encoding/hex" + "fmt" "strings" "testing" @@ -282,3 +283,227 @@ func TestAmd64AssembleExtensionLeavesTheMainEncoderAlone(t *testing.T) { t.Errorf("VPOPCNTD Z1, Z2: got %x, want the main encoder's %x", body, e.out) } } + +// amd64SweepClass recomputes the vector class an entry encodes, the read of +// the template's length field the arch package keeps private: EVEX.L'L in +// byte three, VEX.L in byte two. +func amd64SweepClass(in arch.ExtInstr) arch.ExtOperandKind { + if in.Vex { + if in.Bytes[2]&0x04 != 0 { + return arch.ExtYMM + } + return arch.ExtXMM + } + switch (in.Bytes[3] >> 5) & 3 { + case 0: + return arch.ExtXMM + case 1: + return arch.ExtYMM + default: + return arch.ExtZMM + } +} + +// amd64SweepVec spells and models one vector register of the class, the +// house names X, Y and Z the layer's text forms carry. +func amd64SweepVec(kind arch.ExtOperandKind, n int) (string, arch.ExtOperand) { + switch kind { + case arch.ExtXMM: + return fmt.Sprintf("X%d", n), arch.ExtXmm(n) + case arch.ExtYMM: + return fmt.Sprintf("Y%d", n), arch.ExtYmm(n) + default: + return fmt.Sprintf("Z%d", n), arch.ExtZmm(n) + } +} + +// amd64SweepGpr spells and models the general register of an entry: the W bit +// picks the width, and an entry that ignores W takes the 64-bit spelling. +func amd64SweepGpr(in arch.ExtInstr) (string, arch.ExtOperand) { + if in.Wig || in.Bytes[2]&0x80 != 0 { + return "R12", arch.ExtGpr64(12) + } + return "R12D", arch.ExtGpr32(12) +} + +// amd64SweepImm spells and models the control immediate of an entry: the +// mantissa control keeps its reserved upper nibble at zero, every other +// layout takes a whole byte. +func amd64SweepImm(in arch.ExtInstr) (string, arch.ExtOperand) { + if in.Imm8 == arch.ExtImm8GetMant { + return "$0x0b", arch.ExtImmediate(0x0b) + } + return "$0x7b", arch.ExtImmediate(0x7b) +} + +// amd64SweepDest spells and models the register destination with the +// decorations the entry carries on its register form: the write mask beside +// every masked entry, the rounding or the exception suppression beside every +// entry that takes one. +func amd64SweepDest(in arch.ExtInstr, kind arch.ExtOperandKind, n int) (string, arch.ExtOperand) { + text, model := amd64SweepVec(kind, n) + dec := "" + if in.Mask { + dec += "{K5}" + } + switch { + case in.Er: + dec += "{RZ-SAE}" + case in.Sae: + dec += "{SAE}" + } + if dec == "" { + return text, model + } + if in.Mask { + model = arch.ExtWriteMasked(model, 5, false) + } + switch { + case in.Er: + model = arch.ExtRounded(model, arch.ExtRoundTruncate) + case in.Sae: + model = arch.ExtRounded(model, arch.ExtRoundSAE) + } + return text + dec, model +} + +// amd64SweepMaskedDest spells and models the destination with the write mask +// alone, the one decoration the memory shape keeps. +func amd64SweepMaskedDest(in arch.ExtInstr, kind arch.ExtOperandKind, n int) (string, arch.ExtOperand) { + text, model := amd64SweepVec(kind, n) + if in.Mask { + return text + "{K5}", arch.ExtWriteMasked(model, 5, false) + } + return text, model +} + +// amd64ExtSweepStatements builds, for the first registered entry of every +// distinct amd64 mnemonic, the register-form statement the sweep drives and, +// where the entry carries a memory position, the memory-form statement beside +// it. Each statement comes back with its mnemonic and the model operands the +// text spells, so the sweep can pin the assembled bytes against +// EncodeExtension. The statements follow the registry: a mnemonic registered +// on a form this builder knows lands in the sweep in the same change. +func amd64ExtSweepStatements() (stmts, mnems []string, models [][]arch.ExtOperand, distinct int) { + memText, memModel := "(R9)", arch.ExtMemory(9, 0) + seen := make(map[string]bool) + for _, in := range arch.Extensions(arch.AMD64) { + if seen[in.Name] { + continue + } + seen[in.Name] = true + distinct++ + class := amd64SweepClass(in) + // The two-vector forms narrow one side: the half form the + // destination, the wide form the source, and the quarter forms pin + // one side to the XMM class. + srcClass, destClass := class, class + switch in.Form { + case arch.ExtFormAmdVec2Half: + destClass = arch.ExtXMM + if class == arch.ExtZMM { + destClass = arch.ExtYMM + } + case arch.ExtFormAmdVec2Wide: + srcClass = arch.ExtXMM + if class == arch.ExtZMM { + srcClass = arch.ExtYMM + } + case arch.ExtFormAmdVec2Quarter: + srcClass = arch.ExtXMM + case arch.ExtFormAmdVec2ToQuarter: + destClass = arch.ExtXMM + } + srcText, srcModel := amd64SweepVec(srcClass, 1) + src2Text, src2Model := amd64SweepVec(class, 2) + gprText, gprModel := amd64SweepGpr(in) + immText, immModel := amd64SweepImm(in) + add := func(stmt, mnem string, ops ...arch.ExtOperand) { + stmts = append(stmts, stmt) + mnems = append(mnems, mnem) + models = append(models, ops) + } + switch in.Form { + case arch.ExtFormAmdVec3: + dText, dModel := amd64SweepDest(in, class, 3) + add(fmt.Sprintf("%s %s, %s, %s", in.Name, srcText, src2Text, dText), in.Name, srcModel, src2Model, dModel) + if in.Mem == 2 { + dText, dModel = amd64SweepMaskedDest(in, class, 3) + add(fmt.Sprintf("%s %s, %s, %s", in.Name, srcText, memText, dText), in.Name, srcModel, memModel, dModel) + } + case arch.ExtFormAmdVec2, arch.ExtFormAmdVec2Half, arch.ExtFormAmdVec2Wide, + arch.ExtFormAmdVec2Quarter, arch.ExtFormAmdVec2ToQuarter: + dText, dModel := amd64SweepDest(in, destClass, 2) + add(fmt.Sprintf("%s %s, %s", in.Name, srcText, dText), in.Name, srcModel, dModel) + if in.Mem == 1 { + dText, dModel = amd64SweepMaskedDest(in, destClass, 2) + add(fmt.Sprintf("%s %s, %s", in.Name, memText, dText), in.Name, memModel, dModel) + } + case arch.ExtFormAmdMask2: + add(fmt.Sprintf("%s %s, %s, K3", in.Name, srcText, src2Text), in.Name, srcModel, src2Model, arch.ExtMask(3)) + case arch.ExtFormAmdVecGprVec: + dText, dModel := amd64SweepVec(class, 2) + add(fmt.Sprintf("%s %s, %s, %s", in.Name, srcText, gprText, dText), in.Name, srcModel, gprModel, dModel) + case arch.ExtFormAmdGprVec: + dText, dModel := amd64SweepVec(class, 1) + add(fmt.Sprintf("%s %s, %s", in.Name, gprText, dText), in.Name, gprModel, dModel) + case arch.ExtFormAmdVecGpr: + add(fmt.Sprintf("%s %s, %s", in.Name, srcText, gprText), in.Name, srcModel, gprModel) + case arch.ExtFormAmdVec3Imm: + dText, dModel := amd64SweepMaskedDest(in, class, 4) + add(fmt.Sprintf("%s %s, %s, %s, %s", in.Name, immText, srcText, src2Text, dText), in.Name, immModel, srcModel, src2Model, dModel) + if in.Mem == 3 { + add(fmt.Sprintf("%s %s, %s, %s, %s", in.Name, immText, srcText, memText, dText), in.Name, immModel, srcModel, memModel, dModel) + } + case arch.ExtFormAmdMask2Imm: + add(fmt.Sprintf("%s %s, %s, %s, K3", in.Name, immText, srcText, src2Text), in.Name, immModel, srcModel, src2Model, arch.ExtMask(3)) + if in.Mem == 3 { + add(fmt.Sprintf("%s %s, %s, %s, K3", in.Name, immText, srcText, memText), in.Name, immModel, srcModel, memModel, arch.ExtMask(3)) + } + case arch.ExtFormAmdVec2Imm: + dText, dModel := amd64SweepMaskedDest(in, class, 3) + add(fmt.Sprintf("%s %s, %s, %s", in.Name, immText, srcText, dText), in.Name, immModel, srcModel, dModel) + if in.Mem == 2 { + add(fmt.Sprintf("%s %s, %s, %s", in.Name, immText, memText, dText), in.Name, immModel, memModel, dModel) + } + case arch.ExtFormAmdMemVec: + dText, dModel := amd64SweepVec(class, 1) + add(fmt.Sprintf("%s %s, %s", in.Name, memText, dText), in.Name, memModel, dModel) + case arch.ExtFormAmdVecMem: + add(fmt.Sprintf("%s %s, %s", in.Name, srcText, memText), in.Name, srcModel, memModel) + default: + stmts = append(stmts, "") + mnems = append(mnems, in.Name) + models = append(models, nil) + } + } + return stmts, mnems, models, distinct +} + +// TestAmd64AssembleExtensionSweep drives every distinct registered mnemonic +// through the full assembler from .s text: the register-form statement of the +// mnemonic's first entry, and the memory-form statement beside it where the +// entry carries a memory position. Every statement must assemble, and every +// body must equal EncodeExtension's encoding of the model operands the text +// spells, so the front end and the registry cannot drift apart on any family. +func TestAmd64AssembleExtensionSweep(t *testing.T) { + stmts, mnems, models, distinct := amd64ExtSweepStatements() + if distinct != 86 { + t.Errorf("the amd64 layer registers %d distinct mnemonics, want 86", distinct) + } + for i, stmt := range stmts { + if stmt == "" { + t.Errorf("%s registers a form the sweep builder does not spell", mnems[i]) + continue + } + body := amd64ExtProbe(t, stmt) + want, err := EncodeExtension(arch.AMD64, mnems[i], models[i]...) + if err != nil { + t.Errorf("%s: registry encode: %v", stmt, err) + continue + } + if !bytes.Equal(body, want) { + t.Errorf("%s:\n got %x\n want %x (the registry encoding)", stmt, body, want) + } + } +}