Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1312122a99 | ||
|
|
11f962fbcc | ||
|
|
ee68859beb | ||
|
|
0920edb092 |
+35
-7
@@ -23,7 +23,7 @@ import (
|
|||||||
// operands require relocations and are not yet supported; the SIMD (VEX/AVX2)
|
// operands require relocations and are not yet supported; the SIMD (VEX/AVX2)
|
||||||
// integer and shuffle/extract/permute/move set is in.
|
// integer and shuffle/extract/permute/move set is in.
|
||||||
func Assemble(t *ast.Text) ([]byte, map[string]int, error) {
|
func Assemble(t *ast.Text) ([]byte, map[string]int, error) {
|
||||||
code, _, labels, err := assemble(t, nil)
|
code, _, labels, _, err := assemble(t, nil)
|
||||||
return code, labels, err
|
return code, labels, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +48,19 @@ type sbPatch struct {
|
|||||||
addend int64
|
addend int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// spadjStep is one stack-adjustment boundary within a function: Value is the
|
||||||
|
// SP delta from the entry state (just below the return address) in effect
|
||||||
|
// from PC (function-relative) until the next step. The steps feed the
|
||||||
|
// pcsp table of the object-file emitters.
|
||||||
|
type spadjStep struct {
|
||||||
|
pc int
|
||||||
|
value int
|
||||||
|
}
|
||||||
|
|
||||||
// assemble encodes a TEXT body, returning the machine code, the static-symbol
|
// assemble encodes a TEXT body, returning the machine code, the static-symbol
|
||||||
// patch sites (for the file-level layout to resolve) and the label table.
|
// patch sites (for the file-level layout to resolve), the label table and the
|
||||||
func assemble(t *ast.Text, link *linkInfo) ([]byte, []sbPatch, map[string]int, error) {
|
// stack-adjustment boundaries.
|
||||||
|
func assemble(t *ast.Text, link *linkInfo) ([]byte, []sbPatch, map[string]int, []spadjStep, error) {
|
||||||
fi := computeFrame(t)
|
fi := computeFrame(t)
|
||||||
chain := jumpChain(t)
|
chain := jumpChain(t)
|
||||||
resolve := func(name string) string {
|
resolve := func(name string) string {
|
||||||
@@ -74,7 +84,7 @@ func assemble(t *ast.Text, link *linkInfo) ([]byte, []sbPatch, map[string]int, e
|
|||||||
case *ast.Instr:
|
case *ast.Instr:
|
||||||
sz, err := instrSize(s, fi, long[i], link)
|
sz, err := instrSize(s, fi, long[i], link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, fmt.Errorf("%s: %w", s.Mnemonic.Text, err)
|
return nil, nil, nil, nil, fmt.Errorf("%s: %w", s.Mnemonic.Text, err)
|
||||||
}
|
}
|
||||||
sizes[i] = sz
|
sizes[i] = sz
|
||||||
pcs[i] = pos
|
pcs[i] = pos
|
||||||
@@ -114,24 +124,42 @@ func assemble(t *ast.Text, link *linkInfo) ([]byte, []sbPatch, map[string]int, e
|
|||||||
// Pass 2: emit.
|
// Pass 2: emit.
|
||||||
out := append([]byte(nil), fi.prologue...)
|
out := append([]byte(nil), fi.prologue...)
|
||||||
var patches []sbPatch
|
var patches []sbPatch
|
||||||
|
var steps []spadjStep
|
||||||
|
if fi.useFP {
|
||||||
|
// PUSHQ BP saves the return-address-relative base (+8); the MOVQ
|
||||||
|
// changes nothing; SUBQ $size, SP completes the frame.
|
||||||
|
steps = append(steps,
|
||||||
|
spadjStep{1, 8},
|
||||||
|
spadjStep{len(fi.prologue), 8 + fi.size},
|
||||||
|
)
|
||||||
|
}
|
||||||
pos := len(fi.prologue)
|
pos := len(fi.prologue)
|
||||||
for i, stmt := range t.Body {
|
for i, stmt := range t.Body {
|
||||||
s, ok := stmt.(*ast.Instr)
|
s, ok := stmt.(*ast.Instr)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if strings.ToUpper(s.Mnemonic.Text) == "RET" && fi.useFP {
|
||||||
|
// The RET's epilogue prefix unwinds: ADDQ $size, SP restores
|
||||||
|
// the saved-BP-only stack, POPQ BP the entry state.
|
||||||
|
epi := len(fi.epilogue)
|
||||||
|
steps = append(steps,
|
||||||
|
spadjStep{pos + epi - 1, 8},
|
||||||
|
spadjStep{pos + epi, 0},
|
||||||
|
)
|
||||||
|
}
|
||||||
code, ps, err := encodeInstr(s, pos, offsets, fi, long[i], resolve, link)
|
code, ps, err := encodeInstr(s, pos, offsets, fi, long[i], resolve, link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, fmt.Errorf("%s: %w", s.Mnemonic.Text, err)
|
return nil, nil, nil, nil, fmt.Errorf("%s: %w", s.Mnemonic.Text, err)
|
||||||
}
|
}
|
||||||
if len(code) != sizes[i] {
|
if len(code) != sizes[i] {
|
||||||
return nil, nil, nil, fmt.Errorf("%s: size mismatch (%d vs %d)", s.Mnemonic.Text, len(code), sizes[i])
|
return nil, nil, nil, nil, fmt.Errorf("%s: size mismatch (%d vs %d)", s.Mnemonic.Text, len(code), sizes[i])
|
||||||
}
|
}
|
||||||
patches = append(patches, ps...)
|
patches = append(patches, ps...)
|
||||||
out = append(out, code...)
|
out = append(out, code...)
|
||||||
pos += len(code)
|
pos += len(code)
|
||||||
}
|
}
|
||||||
return out, patches, offsets, nil
|
return out, patches, offsets, steps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// jumpChain precomputes jump-to-jump folding: a label whose first instruction
|
// jumpChain precomputes jump-to-jump folding: a label whose first instruction
|
||||||
|
|||||||
+28
-15
@@ -51,16 +51,17 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
|
|
||||||
// VEX (AVX/AVX2) and EVEX (AVX-512) instructions: the trailing
|
// VEX (AVX/AVX2) and EVEX (AVX-512) instructions: the trailing
|
||||||
// B/W/L/Q/D is part of the mnemonic, not a size suffix, so dispatch
|
// B/W/L/Q/D is part of the mnemonic, not a size suffix, so dispatch
|
||||||
// before splitSize. A ".Z" suffix requests EVEX zeroing.
|
// before splitSize. EVEX suffixes (.Z, .SAE, rounding, .BCST) split
|
||||||
base, zeroing, err := stripEvexSuffix(upper)
|
// off the mnemonic too.
|
||||||
|
base, sfx, err := parseEvexSuffix(upper)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if isVex(base) || isEvex(base) || base == "KMOVW" {
|
if isVex(base) || isEvex(base) || isKOp(base) || isGather(base) || isScatter(base) || base == "KMOVW" || base == "KMOVQ" {
|
||||||
return e.encodeVec(base, ops, zeroing)
|
return e.encodeVec(base, ops, sfx)
|
||||||
}
|
}
|
||||||
if zeroing {
|
if sfx.any() {
|
||||||
return fmt.Errorf("%s: the .Z suffix requires an EVEX instruction", mnem)
|
return fmt.Errorf("%s: the suffix requires an EVEX instruction", mnem)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CMOVcc and SETcc carry the condition in the mnemonic (CMOVLGT, SETNE).
|
// CMOVcc and SETcc carry the condition in the mnemonic (CMOVLGT, SETNE).
|
||||||
@@ -128,20 +129,32 @@ func splitSize(upper string) (base string, size int) {
|
|||||||
// its own direction-dependent opcodes; KTESTW is always VEX; everything else
|
// its own direction-dependent opcodes; KTESTW is always VEX; everything else
|
||||||
// takes EVEX when an operand demands it (a ZMM or K register, or an
|
// takes EVEX when an operand demands it (a ZMM or K register, or an
|
||||||
// EVEX-only mnemonic) and VEX otherwise.
|
// EVEX-only mnemonic) and VEX otherwise.
|
||||||
func (e *enc) encodeVec(upper string, ops []Operand, zeroing bool) error {
|
func (e *enc) encodeVec(upper string, ops []Operand, sfx evexSuffix) error {
|
||||||
if upper == "KMOVW" {
|
if gs, ok := gatherTable[upper]; ok {
|
||||||
if zeroing {
|
return e.encodeGather(upper, gs, ops, sfx)
|
||||||
return fmt.Errorf("KMOVW takes no .Z suffix")
|
|
||||||
}
|
|
||||||
return e.encodeKmovw(ops)
|
|
||||||
}
|
}
|
||||||
if upper == "KTESTW" || !evexRequired(upper, ops) {
|
if ss, ok := scatterTable[upper]; ok {
|
||||||
if zeroing {
|
return e.encodeScatter(upper, ss, ops, sfx)
|
||||||
|
}
|
||||||
|
if upper == "KMOVW" || upper == "KMOVQ" {
|
||||||
|
if sfx.any() {
|
||||||
|
return fmt.Errorf("%s takes no EVEX suffixes", upper)
|
||||||
|
}
|
||||||
|
return e.encodeKmov(upper, ops)
|
||||||
|
}
|
||||||
|
if isKOp(upper) {
|
||||||
|
if sfx.any() {
|
||||||
|
return fmt.Errorf("%s takes no EVEX suffixes", upper)
|
||||||
|
}
|
||||||
|
return e.encodeKOp(upper, ops)
|
||||||
|
}
|
||||||
|
if upper == "KTESTW" || (!evexRequired(upper, ops) && !sfx.evexOnly()) {
|
||||||
|
if sfx.any() {
|
||||||
return fmt.Errorf("%s: the .Z suffix requires an EVEX instruction", upper)
|
return fmt.Errorf("%s: the .Z suffix requires an EVEX instruction", upper)
|
||||||
}
|
}
|
||||||
return e.encodeVex(upper, ops)
|
return e.encodeVex(upper, ops)
|
||||||
}
|
}
|
||||||
return e.encodeEvex(upper, ops, zeroing)
|
return e.encodeEvex(upper, ops, sfx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- instruction components -------------------------------------------------
|
// --- instruction components -------------------------------------------------
|
||||||
|
|||||||
+796
-79
File diff suppressed because it is too large
Load Diff
+318
-1
@@ -230,7 +230,11 @@ func TestEvexMasking(t *testing.T) {
|
|||||||
{"K0 mask", "VPADDD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K0"), vreg(t, "Z3")}},
|
{"K0 mask", "VPADDD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K0"), vreg(t, "Z3")}},
|
||||||
{"two masks", "VPADDD", []Operand{vreg(t, "Z1"), vreg(t, "K1"), vreg(t, "K2"), vreg(t, "Z3")}},
|
{"two masks", "VPADDD", []Operand{vreg(t, "Z1"), vreg(t, "K1"), vreg(t, "K2"), vreg(t, "Z3")}},
|
||||||
{".Z on VEX-only", "VPSHUFD.Z", []Operand{Imm(1), vreg(t, "X0"), vreg(t, "X1")}},
|
{".Z on VEX-only", "VPSHUFD.Z", []Operand{Imm(1), vreg(t, "X0"), vreg(t, "X1")}},
|
||||||
{"unsupported suffix", "VPADDD.BCST", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}},
|
{"broadcast unsupported", "VPXORD.BCST", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}},
|
||||||
|
{"rounding unsupported", "VPXORD.RN_SAE", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}},
|
||||||
|
{"bcst with rounding", "VADDPD.BCST.RN_SAE", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}},
|
||||||
|
{"Z not last", "VADDPD.Z.RN_SAE", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}},
|
||||||
|
{"duplicate suffix", "VADDPD.Z.Z", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}},
|
||||||
{"KMOVW.Z", "KMOVW.Z", []Operand{vreg(t, "K1"), vreg(t, "K2")}},
|
{"KMOVW.Z", "KMOVW.Z", []Operand{vreg(t, "K1"), vreg(t, "K2")}},
|
||||||
}
|
}
|
||||||
for _, c := range bad {
|
for _, c := range bad {
|
||||||
@@ -240,6 +244,319 @@ func TestEvexMasking(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestEvexExtendedGroundTruth covers the wider EVEX/AVX-512 set — ternary
|
||||||
|
// logic, lane shuffles/inserts/extracts, compares with a K destination,
|
||||||
|
// permutes, the wider integer families, expand/compress, broadcasts,
|
||||||
|
// rotates and word shifts, the opmask instructions, the EVEX suffixes
|
||||||
|
// (rounding/SAE/broadcast) and the aligned/scalar moves — byte for byte
|
||||||
|
// against the Go assembler.
|
||||||
|
func TestEvexExtendedGroundTruth(t *testing.T) {
|
||||||
|
mem64 := func(base Reg) Operand { return Ptr(base, 0, 64) }
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// Ternary logic and lane shuffles (NDS + imm8).
|
||||||
|
{"VPTERNLOGD", "VPTERNLOGD", []Operand{Imm(0xE8), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f36d4825d9e8"},
|
||||||
|
{"VPTERNLOGQ", "VPTERNLOGQ", []Operand{Imm(0x96), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f3ed4825d996"},
|
||||||
|
{"VSHUFI32X4", "VSHUFI32X4", []Operand{Imm(0x4E), vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3")}, "62f36d2843d94e"},
|
||||||
|
{"VSHUFF64X2", "VSHUFF64X2", []Operand{Imm(1), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f3ed4823d901"},
|
||||||
|
{"VPALIGNR", "VPALIGNR", []Operand{Imm(7), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f36d480fd907"},
|
||||||
|
// Permutes.
|
||||||
|
{"VPERMB", "VPERMB", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f26d488dd9"},
|
||||||
|
{"VPERMW", "VPERMW", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f2ed488dd9"},
|
||||||
|
{"VPERMI2D", "VPERMI2D", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f26d4876d9"},
|
||||||
|
{"VPERMT2PD", "VPERMT2PD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f2ed487fd9"},
|
||||||
|
// Compare with a K destination (and an immediate predicate).
|
||||||
|
{"VCMPPD", "VCMPPD", []Operand{Imm(4), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K3")}, "62f1ed48c2d904"},
|
||||||
|
{"VCMPPS", "VCMPPS", []Operand{Imm(0), vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "K4")}, "62f16c28c2e100"},
|
||||||
|
{"VCMPSD", "VCMPSD", []Operand{Imm(17), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "K5")}, "62f1ef08c2e911"},
|
||||||
|
// Rounding / SAE / broadcast suffixes.
|
||||||
|
{"VADDPD.RN_SAE", "VADDPD.RN_SAE", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f1ed1858d9"},
|
||||||
|
{"VMULPD.RZ_SAE.Z", "VMULPD.RZ_SAE.Z", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "K1"), vreg(t, "Z3")}, "62f1edf959d9"},
|
||||||
|
{"VMAXPD.SAE", "VMAXPD.SAE", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f1ed585fd9"},
|
||||||
|
{"VADDPD.BCST", "VADDPD.BCST", []Operand{mem64(AX), vreg(t, "Z1"), vreg(t, "Z2")}, "62f1f5585810"},
|
||||||
|
// Packed single arithmetic (same opcodes, no mandatory prefix) —
|
||||||
|
// ZMM, YMM and XMM widths, rounding and broadcast.
|
||||||
|
{"VADDPS", "VADDPS", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f16c4858d9"},
|
||||||
|
{"VMULPS", "VMULPS", []Operand{vreg(t, "Y1"), vreg(t, "Y2"), vreg(t, "Y3")}, "c5ec59d9"},
|
||||||
|
{"VMAXPS", "VMAXPS", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "c5e85fd9"},
|
||||||
|
{"VDIVPS.RD_SAE", "VDIVPS.RD_SAE", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f16c385ed9"},
|
||||||
|
{"VADDPS.BCST", "VADDPS.BCST", []Operand{mem64(AX), vreg(t, "Z1"), vreg(t, "Z2")}, "62f174585810"},
|
||||||
|
// Compress / expand.
|
||||||
|
{"VCOMPRESSPD", "VCOMPRESSPD", []Operand{vreg(t, "Z1"), mem64(DI)}, "62f2fd488a0f"},
|
||||||
|
{"VEXPANDPS", "VEXPANDPS", []Operand{mem64(SI), vreg(t, "Y2")}, "62f27d288816"},
|
||||||
|
{"VPCOMPRESSD.Z", "VPCOMPRESSD.Z", []Operand{vreg(t, "Z1"), vreg(t, "K2"), mem64(DI)}, "62f27dca8b0f"},
|
||||||
|
// Broadcasts.
|
||||||
|
{"VPBROADCASTB gpr", "VPBROADCASTB", []Operand{BX, vreg(t, "Z1")}, "62f27d487acb"},
|
||||||
|
{"VPBROADCASTW mem", "VPBROADCASTW", []Operand{mem64(AX), vreg(t, "Z2")}, "62f27d487910"},
|
||||||
|
{"VBROADCASTSS", "VBROADCASTSS", []Operand{mem64(AX), vreg(t, "Y3")}, "c4e27d1818"},
|
||||||
|
{"VBROADCASTSD", "VBROADCASTSD", []Operand{mem64(AX), vreg(t, "Z4")}, "62f2fd481920"},
|
||||||
|
// Wider integer families.
|
||||||
|
{"VPMADDWD", "VPMADDWD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f16d48f5d9"},
|
||||||
|
{"VPMADDUBSW", "VPMADDUBSW", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f26d4804d9"},
|
||||||
|
{"VPMULHUW", "VPMULHUW", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f16d48e4d9"},
|
||||||
|
{"VPSLLVW", "VPSLLVW", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f2ed4812d9"},
|
||||||
|
{"VPACKSSWB", "VPACKSSWB", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f16d4863d9"},
|
||||||
|
{"VPACKUSDW", "VPACKUSDW", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f26d482bd9"},
|
||||||
|
// Absolute values and replicating moves.
|
||||||
|
{"VPABSD", "VPABSD", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f27d481ed1"},
|
||||||
|
{"VPABSQ mem", "VPABSQ", []Operand{mem64(AX), vreg(t, "Z2")}, "62f2fd481f10"},
|
||||||
|
{"VMOVSLDUP", "VMOVSLDUP", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "c5fa12d1"},
|
||||||
|
{"VMOVSHDUP", "VMOVSHDUP", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f17e4816d1"},
|
||||||
|
// Rotates and word/qword shifts.
|
||||||
|
{"VPROLD", "VPROLD", []Operand{Imm(5), vreg(t, "Z1"), vreg(t, "Z2")}, "62f16d4872c905"},
|
||||||
|
{"VPRORQ", "VPRORQ", []Operand{Imm(63), vreg(t, "Z1"), vreg(t, "Z2")}, "62f1ed4872c13f"},
|
||||||
|
{"VPSLLW", "VPSLLW", []Operand{Imm(9), vreg(t, "X1"), vreg(t, "X2")}, "c5e971f109"},
|
||||||
|
{"VPSRLQ", "VPSRLQ", []Operand{Imm(3), vreg(t, "Z1"), vreg(t, "Z2")}, "62f1ed4873d103"},
|
||||||
|
// Opmask instructions (VEX-encoded, the width in the L/W/pp bits).
|
||||||
|
{"KANDW", "KANDW", []Operand{vreg(t, "K1"), vreg(t, "K2"), vreg(t, "K3")}, "c5ec41d9"},
|
||||||
|
{"KORD", "KORD", []Operand{vreg(t, "K4"), vreg(t, "K5"), vreg(t, "K6")}, "c4e1d545f4"},
|
||||||
|
{"KXNORQ", "KXNORQ", []Operand{vreg(t, "K1"), vreg(t, "K2"), vreg(t, "K3")}, "c4e1ec46d9"},
|
||||||
|
{"KNOTB", "KNOTB", []Operand{vreg(t, "K4"), vreg(t, "K5")}, "c5f944ec"},
|
||||||
|
{"KUNPCKBW", "KUNPCKBW", []Operand{vreg(t, "K1"), vreg(t, "K2"), vreg(t, "K3")}, "c5ed4bd9"},
|
||||||
|
{"KSHIFTLW", "KSHIFTLW", []Operand{Imm(2), vreg(t, "K1"), vreg(t, "K2")}, "c4e3f932d102"},
|
||||||
|
{"KADDQ", "KADDQ", []Operand{vreg(t, "K1"), vreg(t, "K2"), vreg(t, "K3")}, "c4e1ec4ad9"},
|
||||||
|
{"KORTESTD", "KORTESTD", []Operand{vreg(t, "K1"), vreg(t, "K2")}, "c4e1f998d1"},
|
||||||
|
{"KMOVQ k,k", "KMOVQ", []Operand{vreg(t, "K1"), vreg(t, "K2")}, "c4e1f890d1"},
|
||||||
|
{"KMOVQ gpr,k", "KMOVQ", []Operand{BX, vreg(t, "K1")}, "c4e1fb92cb"},
|
||||||
|
// Lane extract / insert.
|
||||||
|
{"VEXTRACTF32X4", "VEXTRACTF32X4", []Operand{Imm(1), vreg(t, "Y1"), vreg(t, "X2")}, "62f37d2819ca01"},
|
||||||
|
{"VEXTRACTI64X2", "VEXTRACTI64X2", []Operand{Imm(1), vreg(t, "Y1"), vreg(t, "X2")}, "62f3fd2839ca01"},
|
||||||
|
{"VINSERTF32X8", "VINSERTF32X8", []Operand{Imm(1), vreg(t, "Y1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f36d481ad901"},
|
||||||
|
{"VINSERTI64X4", "VINSERTI64X4", []Operand{Imm(1), vreg(t, "Y1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f3ed483ad901"},
|
||||||
|
// Aligned moves and the scalar single move.
|
||||||
|
{"VMOVAPS", "VMOVAPS", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f17c4829ca"},
|
||||||
|
{"VMOVDQA64 mem", "VMOVDQA64", []Operand{mem64(AX), vreg(t, "Z2")}, "62f1fd486f10"},
|
||||||
|
{"VMOVSS mem", "VMOVSS", []Operand{mem64(AX), vreg(t, "X2")}, "c5fa1010"},
|
||||||
|
// Conversions and extending/narrowing moves.
|
||||||
|
{"VCVTPS2DQ", "VCVTPS2DQ", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f17d485bd1"},
|
||||||
|
{"VCVTTPS2DQ", "VCVTTPS2DQ", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f17e485bd1"},
|
||||||
|
{"VPMOVZXBW", "VPMOVZXBW", []Operand{vreg(t, "X1"), vreg(t, "Y2")}, "c4e27d30d1"},
|
||||||
|
{"VPMOVSXBW mem", "VPMOVSXBW", []Operand{mem64(AX), vreg(t, "Z2")}, "62f27d482010"},
|
||||||
|
{"VPMOVWB", "VPMOVWB", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f27e4830ca"},
|
||||||
|
{"VPMOVQB", "VPMOVQB", []Operand{vreg(t, "Z1"), vreg(t, "X2")}, "62f27e4832ca"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode(c.mnem, c.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Encode: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := hexCompact(code); got != c.want {
|
||||||
|
t.Errorf("%s: bytes %s, want %s", c.name, got, c.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inst, err := x86asm.Decode(code, 64)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Decode(%x): %v", c.name, code, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
want := c.mnem
|
||||||
|
if i := strings.IndexByte(want, '.'); i > 0 {
|
||||||
|
want = want[:i]
|
||||||
|
}
|
||||||
|
if inst.Op.String() != want {
|
||||||
|
t.Errorf("%s: decoded as %s", c.name, inst.Op.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEvexHelperGroundTruth covers the floating-point helper and conversion
|
||||||
|
// tail of the EVEX set — reciprocals, rsqrt, getexp/getmant, scalef,
|
||||||
|
// rndscale, reduce, fixupimm, range, fpclass, the remaining conversions —
|
||||||
|
// plus gather/scatter with VSIB addressing, byte for byte against the Go
|
||||||
|
// assembler.
|
||||||
|
func TestEvexHelperGroundTruth(t *testing.T) {
|
||||||
|
vsib := func(base, idx string, scale int) Operand {
|
||||||
|
return Idx(vreg(t, base), vreg(t, idx), scale, 0, 0)
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// Reciprocals and rsqrt (packed RM, scalar NDS).
|
||||||
|
{"VRCP14PD", "VRCP14PD", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f2fd484cd1"},
|
||||||
|
{"VRCP14PS", "VRCP14PS", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f27d484cd1"},
|
||||||
|
{"VRCP14SD", "VRCP14SD", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f2ed084dd9"},
|
||||||
|
{"VRCP14SS", "VRCP14SS", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f26d084dd9"},
|
||||||
|
{"VRSQRT14PD", "VRSQRT14PD", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f2fd484ed1"},
|
||||||
|
{"VRSQRT14PS", "VRSQRT14PS", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f27d484ed1"},
|
||||||
|
{"VRSQRT14SD", "VRSQRT14SD", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f2ed084fd9"},
|
||||||
|
{"VRSQRT14SS", "VRSQRT14SS", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f26d084fd9"},
|
||||||
|
// Getexp (packed RM, scalar NDS).
|
||||||
|
{"VGETEXPPD", "VGETEXPPD", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f2fd4842d1"},
|
||||||
|
{"VGETEXPPS", "VGETEXPPS", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f27d4842d1"},
|
||||||
|
{"VGETEXPSD", "VGETEXPSD", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f2ed0843d9"},
|
||||||
|
{"VGETEXPSS", "VGETEXPSS", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f26d0843d9"},
|
||||||
|
// Scalef (NDS).
|
||||||
|
{"VSCALEFPD", "VSCALEFPD", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f2ed482cd9"},
|
||||||
|
{"VSCALEFPS", "VSCALEFPS", []Operand{vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f26d482cd9"},
|
||||||
|
{"VSCALEFSD", "VSCALEFSD", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f2ed082dd9"},
|
||||||
|
{"VSCALEFSS", "VSCALEFSS", []Operand{vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f26d082dd9"},
|
||||||
|
// Rndscale / getmant / reduce (packed $imm,src,dst; scalar NDS+imm).
|
||||||
|
{"VRNDSCALEPD", "VRNDSCALEPD", []Operand{Imm(4), vreg(t, "Z1"), vreg(t, "Z2")}, "62f3fd4809d104"},
|
||||||
|
{"VRNDSCALEPS", "VRNDSCALEPS", []Operand{Imm(4), vreg(t, "Z1"), vreg(t, "Z2")}, "62f37d4808d104"},
|
||||||
|
{"VRNDSCALESD", "VRNDSCALESD", []Operand{Imm(4), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f3ed080bd904"},
|
||||||
|
{"VRNDSCALESS", "VRNDSCALESS", []Operand{Imm(4), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f36d080ad904"},
|
||||||
|
{"VGETMANTPD", "VGETMANTPD", []Operand{Imm(3), vreg(t, "Z1"), vreg(t, "Z2")}, "62f3fd4826d103"},
|
||||||
|
{"VGETMANTPS", "VGETMANTPS", []Operand{Imm(3), vreg(t, "Z1"), vreg(t, "Z2")}, "62f37d4826d103"},
|
||||||
|
{"VGETMANTSD", "VGETMANTSD", []Operand{Imm(3), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f3ed0827d903"},
|
||||||
|
{"VGETMANTSS", "VGETMANTSS", []Operand{Imm(3), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f36d0827d903"},
|
||||||
|
{"VREDUCEPD", "VREDUCEPD", []Operand{Imm(4), vreg(t, "Z1"), vreg(t, "Z2")}, "62f3fd4856d104"},
|
||||||
|
{"VREDUCEPS", "VREDUCEPS", []Operand{Imm(4), vreg(t, "Z1"), vreg(t, "Z2")}, "62f37d4856d104"},
|
||||||
|
{"VREDUCESD", "VREDUCESD", []Operand{Imm(4), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f3ed0857d904"},
|
||||||
|
{"VREDUCESS", "VREDUCESS", []Operand{Imm(4), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f36d0857d904"},
|
||||||
|
// Fixupimm / range (NDS + imm8).
|
||||||
|
{"VFIXUPIMMPD", "VFIXUPIMMPD", []Operand{Imm(2), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f3ed4854d902"},
|
||||||
|
{"VFIXUPIMMPS", "VFIXUPIMMPS", []Operand{Imm(2), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f36d4854d902"},
|
||||||
|
{"VFIXUPIMMSD", "VFIXUPIMMSD", []Operand{Imm(2), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f3ed0855d902"},
|
||||||
|
{"VFIXUPIMMSS", "VFIXUPIMMSS", []Operand{Imm(2), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f36d0855d902"},
|
||||||
|
{"VRANGEPD", "VRANGEPD", []Operand{Imm(1), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f3ed4850d901"},
|
||||||
|
{"VRANGEPS", "VRANGEPS", []Operand{Imm(1), vreg(t, "Z1"), vreg(t, "Z2"), vreg(t, "Z3")}, "62f36d4850d901"},
|
||||||
|
{"VRANGESD", "VRANGESD", []Operand{Imm(1), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f3ed0851d901"},
|
||||||
|
{"VRANGESS", "VRANGESS", []Operand{Imm(1), vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")}, "62f36d0851d901"},
|
||||||
|
// FP class test ($imm, src, kdst; packed forms carry the length in
|
||||||
|
// the X/Y/Z mnemonic suffix the decoder drops).
|
||||||
|
{"VFPCLASSPDZ", "VFPCLASSPDZ", []Operand{Imm(4), vreg(t, "Z1"), vreg(t, "K2")}, "62f3fd4866d104"},
|
||||||
|
{"VFPCLASSPSY", "VFPCLASSPSY", []Operand{Imm(4), vreg(t, "Y1"), vreg(t, "K2")}, "62f37d2866d104"},
|
||||||
|
{"VFPCLASSSD", "VFPCLASSSD", []Operand{Imm(4), vreg(t, "X1"), vreg(t, "K2")}, "62f3fd0867d104"},
|
||||||
|
{"VFPCLASSSS", "VFPCLASSSS", []Operand{Imm(4), vreg(t, "X1"), vreg(t, "K2")}, "62f37d0867d104"},
|
||||||
|
// Gather: VEX spelling (mask register, VSIB, destination) and EVEX
|
||||||
|
// spelling (VSIB, K mask, destination; L'L follows the VSIB index).
|
||||||
|
{"VGATHERDPS vex", "VGATHERDPS", []Operand{vreg(t, "X2"), vsib("SI", "X1", 4), vreg(t, "X3")}, "c4e269921c8e"},
|
||||||
|
{"VPGATHERDD vex", "VPGATHERDD", []Operand{vreg(t, "Y2"), vsib("SI", "Y1", 4), vreg(t, "Y3")}, "c4e26d901c8e"},
|
||||||
|
{"VGATHERDPS evex", "VGATHERDPS", []Operand{vsib("SI", "X1", 4), vreg(t, "K2"), vreg(t, "X3")}, "62f27d0a921c8e"},
|
||||||
|
{"VPGATHERQD evex", "VPGATHERQD", []Operand{vsib("SI", "Z1", 8), vreg(t, "K2"), vreg(t, "Y3")}, "62f27d4a911cce"},
|
||||||
|
// Scatter (EVEX only: source, K mask, VSIB).
|
||||||
|
{"VSCATTERDPS", "VSCATTERDPS", []Operand{vreg(t, "X3"), vreg(t, "K1"), vsib("SI", "X1", 4)}, "62f27d09a21c8e"},
|
||||||
|
{"VSCATTERQPD", "VSCATTERQPD", []Operand{vreg(t, "Z3"), vreg(t, "K1"), vsib("SI", "Z1", 8)}, "62f2fd49a31cce"},
|
||||||
|
// The remaining conversions.
|
||||||
|
{"VCVTDQ2PS", "VCVTDQ2PS", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f17c485bd1"},
|
||||||
|
{"VCVTQQ2PS", "VCVTQQ2PS", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f1fc485bd1"},
|
||||||
|
{"VCVTPD2QQ", "VCVTPD2QQ", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f1fd487bd1"},
|
||||||
|
{"VCVTPS2QQ", "VCVTPS2QQ", []Operand{vreg(t, "Y1"), vreg(t, "Z2")}, "62f17d487bd1"},
|
||||||
|
{"VCVTUDQ2PD", "VCVTUDQ2PD", []Operand{vreg(t, "X1"), vreg(t, "Y2")}, "62f17e287ad1"},
|
||||||
|
{"VCVTPH2PS", "VCVTPH2PS", []Operand{vreg(t, "Y1"), vreg(t, "Z2")}, "62f27d4813d1"},
|
||||||
|
{"VCVTPS2PH", "VCVTPS2PH", []Operand{Imm(4), vreg(t, "Y1"), vreg(t, "X2")}, "c4e37d1dca04"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode(c.mnem, c.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Encode: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := hexCompact(code); got != c.want {
|
||||||
|
t.Errorf("%s: bytes %s, want %s", c.name, got, c.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inst, err := x86asm.Decode(code, 64)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Decode(%x): %v", c.name, code, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
want := c.mnem
|
||||||
|
got := inst.Op.String()
|
||||||
|
if got != want && !(len(want) > len(got) && want[:len(got)] == got) {
|
||||||
|
t.Errorf("%s: decoded as %s", c.name, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEvexConversionGroundTruth covers the unsigned and truncating VCVT*
|
||||||
|
// conversions, the remaining sign/zero-extending moves, the signed/unsigned
|
||||||
|
// narrowing stores and the mask/vector conversions, byte for byte against
|
||||||
|
// the Go assembler.
|
||||||
|
func TestEvexConversionGroundTruth(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// Unsigned and truncating conversions.
|
||||||
|
{"VCVTPD2PS", "VCVTPD2PS", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f1fd485ad1"},
|
||||||
|
{"VCVTPD2PSX", "VCVTPD2PSX", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "c5f95ad1"},
|
||||||
|
{"VCVTPD2PSY", "VCVTPD2PSY", []Operand{vreg(t, "Y1"), vreg(t, "X2")}, "c5fd5ad1"},
|
||||||
|
{"VCVTPD2UDQ", "VCVTPD2UDQ", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f1fc4879d1"},
|
||||||
|
{"VCVTPD2UDQX", "VCVTPD2UDQX", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "62f1fc0879d1"},
|
||||||
|
{"VCVTTPD2UDQ", "VCVTTPD2UDQ", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f1fc4878d1"},
|
||||||
|
{"VCVTTPD2UDQY", "VCVTTPD2UDQY", []Operand{vreg(t, "Y1"), vreg(t, "X2")}, "62f1fc2878d1"},
|
||||||
|
{"VCVTTPD2UQQ", "VCVTTPD2UQQ", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f1fd4878d1"},
|
||||||
|
{"VCVTPS2UDQ", "VCVTPS2UDQ", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f17c4879d1"},
|
||||||
|
{"VCVTTPS2UDQ", "VCVTTPS2UDQ", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f17c4878d1"},
|
||||||
|
{"VCVTPS2UQQ", "VCVTPS2UQQ", []Operand{vreg(t, "Y1"), vreg(t, "Z2")}, "62f17d4879d1"},
|
||||||
|
{"VCVTTPS2UQQ", "VCVTTPS2UQQ", []Operand{vreg(t, "Y1"), vreg(t, "Z2")}, "62f17d4878d1"},
|
||||||
|
{"VCVTTPD2QQ", "VCVTTPD2QQ", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f1fd487ad1"},
|
||||||
|
{"VCVTTPS2QQ", "VCVTTPS2QQ", []Operand{vreg(t, "Y1"), vreg(t, "Z2")}, "62f17d487ad1"},
|
||||||
|
{"VCVTUQQ2PD", "VCVTUQQ2PD", []Operand{vreg(t, "Z1"), vreg(t, "Z2")}, "62f1fe487ad1"},
|
||||||
|
{"VCVTUQQ2PS", "VCVTUQQ2PS", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f1ff487ad1"},
|
||||||
|
{"VCVTUQQ2PSX", "VCVTUQQ2PSX", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "62f1ff087ad1"},
|
||||||
|
{"VCVTQQ2PSX", "VCVTQQ2PSX", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "62f1fc085bd1"},
|
||||||
|
{"VCVTQQ2PSY", "VCVTQQ2PSY", []Operand{vreg(t, "Y1"), vreg(t, "X2")}, "62f1fc285bd1"},
|
||||||
|
// The remaining sign/zero-extending moves.
|
||||||
|
{"VPMOVSXBD", "VPMOVSXBD", []Operand{vreg(t, "X1"), vreg(t, "Y2")}, "c4e27d21d1"},
|
||||||
|
{"VPMOVSXBQ evex", "VPMOVSXBQ", []Operand{vreg(t, "X1"), vreg(t, "Z2")}, "62f27d4822d1"},
|
||||||
|
{"VPMOVSXWQ", "VPMOVSXWQ", []Operand{vreg(t, "X1"), vreg(t, "Y2")}, "c4e27d24d1"},
|
||||||
|
{"VPMOVSXWD", "VPMOVSXWD", []Operand{vreg(t, "X1"), vreg(t, "Y2")}, "c4e27d23d1"},
|
||||||
|
{"VPMOVZXBD", "VPMOVZXBD", []Operand{vreg(t, "X1"), vreg(t, "Y2")}, "c4e27d31d1"},
|
||||||
|
{"VPMOVZXBQ evex", "VPMOVZXBQ", []Operand{vreg(t, "X1"), vreg(t, "Z2")}, "62f27d4832d1"},
|
||||||
|
{"VPMOVZXWD", "VPMOVZXWD", []Operand{vreg(t, "X1"), vreg(t, "Y2")}, "c4e27d33d1"},
|
||||||
|
{"VPMOVZXWQ", "VPMOVZXWQ", []Operand{vreg(t, "X1"), vreg(t, "Y2")}, "c4e27d34d1"},
|
||||||
|
// Signed narrowing stores.
|
||||||
|
{"VPMOVSDB", "VPMOVSDB", []Operand{vreg(t, "Z1"), vreg(t, "X2")}, "62f27e4821ca"},
|
||||||
|
{"VPMOVSDW", "VPMOVSDW", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f27e4823ca"},
|
||||||
|
{"VPMOVSQB", "VPMOVSQB", []Operand{vreg(t, "Z1"), vreg(t, "X2")}, "62f27e4822ca"},
|
||||||
|
{"VPMOVSQD", "VPMOVSQD", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f27e4825ca"},
|
||||||
|
{"VPMOVSQW", "VPMOVSQW", []Operand{vreg(t, "Z1"), vreg(t, "X2")}, "62f27e4824ca"},
|
||||||
|
{"VPMOVSWB", "VPMOVSWB", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f27e4820ca"},
|
||||||
|
// Unsigned narrowing stores.
|
||||||
|
{"VPMOVUSDB", "VPMOVUSDB", []Operand{vreg(t, "Z1"), vreg(t, "X2")}, "62f27e4811ca"},
|
||||||
|
{"VPMOVUSDW", "VPMOVUSDW", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f27e4813ca"},
|
||||||
|
{"VPMOVUSQB", "VPMOVUSQB", []Operand{vreg(t, "Z1"), vreg(t, "X2")}, "62f27e4812ca"},
|
||||||
|
{"VPMOVUSQD", "VPMOVUSQD", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f27e4815ca"},
|
||||||
|
{"VPMOVUSQW", "VPMOVUSQW", []Operand{vreg(t, "Z1"), vreg(t, "X2")}, "62f27e4814ca"},
|
||||||
|
{"VPMOVUSWB", "VPMOVUSWB", []Operand{vreg(t, "Z1"), vreg(t, "Y2")}, "62f27e4810ca"},
|
||||||
|
{"VPMOVDB", "VPMOVDB", []Operand{vreg(t, "Z1"), vreg(t, "X2")}, "62f27e4831ca"},
|
||||||
|
{"VPMOVQW", "VPMOVQW", []Operand{vreg(t, "Z1"), vreg(t, "X2")}, "62f27e4834ca"},
|
||||||
|
// Mask/vector conversions (the K register is an operand, not a
|
||||||
|
// mask).
|
||||||
|
{"VPMOVM2B", "VPMOVM2B", []Operand{vreg(t, "K1"), vreg(t, "X2")}, "62f27e0828d1"},
|
||||||
|
{"VPMOVM2W", "VPMOVM2W", []Operand{vreg(t, "K1"), vreg(t, "X2")}, "62f2fe0828d1"},
|
||||||
|
{"VPMOVM2D", "VPMOVM2D", []Operand{vreg(t, "K1"), vreg(t, "X2")}, "62f27e0838d1"},
|
||||||
|
{"VPMOVM2Q", "VPMOVM2Q", []Operand{vreg(t, "K1"), vreg(t, "Z2")}, "62f2fe4838d1"},
|
||||||
|
{"VPMOVB2M", "VPMOVB2M", []Operand{vreg(t, "X1"), vreg(t, "K2")}, "62f27e0829d1"},
|
||||||
|
{"VPMOVW2M", "VPMOVW2M", []Operand{vreg(t, "X1"), vreg(t, "K2")}, "62f2fe0829d1"},
|
||||||
|
{"VPMOVD2M", "VPMOVD2M", []Operand{vreg(t, "Z1"), vreg(t, "K2")}, "62f27e4839d1"},
|
||||||
|
{"VPMOVQ2M", "VPMOVQ2M", []Operand{vreg(t, "Z1"), vreg(t, "K2")}, "62f2fe4839d1"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode(c.mnem, c.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Encode: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := hexCompact(code); got != c.want {
|
||||||
|
t.Errorf("%s: bytes %s, want %s", c.name, got, c.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inst, err := x86asm.Decode(code, 64)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: Decode(%x): %v", c.name, code, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
want := c.mnem
|
||||||
|
got := inst.Op.String()
|
||||||
|
if got != want && !(len(want) > len(got) && want[:len(got)] == got) {
|
||||||
|
t.Errorf("%s: decoded as %s", c.name, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestEvexErrors checks the EVEX-specific error paths.
|
// TestEvexErrors checks the EVEX-specific error paths.
|
||||||
func TestEvexErrors(t *testing.T) {
|
func TestEvexErrors(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
|
|||||||
+453
@@ -0,0 +1,453 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file emits GOOBJ — the Go toolchain's object format, which cmd/link
|
||||||
|
// consumes directly — so gasm-assembled functions drop into a go build
|
||||||
|
// without the Go assembler. The layout follows cmd/internal/goobj: a
|
||||||
|
// toolchain preamble ("go object ...\n!\n"), the go120ld header with its
|
||||||
|
// block offsets, a string table, symbol definitions, the relocation /
|
||||||
|
// aux / data index arrays, and the three blocks themselves.
|
||||||
|
//
|
||||||
|
// The object carries what the linker requires of an assembly object: the
|
||||||
|
// functions (non-package symbols, as cmd/asm emits them), the GLOBL data,
|
||||||
|
// one FuncInfo per function, and the pc-value tables (pcsp, pcfile,
|
||||||
|
// pcline, pcinline). DWARF and the implicit funcdata symbols are omitted;
|
||||||
|
// the linker fills their defaults.
|
||||||
|
|
||||||
|
// GOOBJ block indices (cmd/internal/goobj).
|
||||||
|
const (
|
||||||
|
blkAutolib = iota
|
||||||
|
blkPkgIdx
|
||||||
|
blkFile
|
||||||
|
blkSymdef
|
||||||
|
blkHashed64def
|
||||||
|
blkHasheddef
|
||||||
|
blkNonpkgdef
|
||||||
|
blkNonpkgref
|
||||||
|
blkRefFlags
|
||||||
|
blkHash64
|
||||||
|
blkHash
|
||||||
|
blkRelocIdx
|
||||||
|
blkAuxIdx
|
||||||
|
blkDataIdx
|
||||||
|
blkReloc
|
||||||
|
blkAux
|
||||||
|
blkData
|
||||||
|
blkRefName
|
||||||
|
blkEnd
|
||||||
|
)
|
||||||
|
|
||||||
|
// Symbol kinds used by assembly objects (cmd/internal/objabi).
|
||||||
|
const (
|
||||||
|
kindSTEXT = 1
|
||||||
|
kindSRODATA = 3
|
||||||
|
kindSDATA = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
// Symbol flags (cmd/internal/goobj).
|
||||||
|
const (
|
||||||
|
symFlagDupok = 0x01
|
||||||
|
symFlagNoSplit = 0x10
|
||||||
|
symFlag2Link = 0x10 // asm objects flag every named symbol as linkname
|
||||||
|
symABIStatic = 0xffff
|
||||||
|
)
|
||||||
|
|
||||||
|
// Aux entry types (cmd/internal/goobj).
|
||||||
|
const (
|
||||||
|
auxFuncInfo = 1
|
||||||
|
auxPcsp = 7
|
||||||
|
auxPcfile = 8
|
||||||
|
auxPcline = 9
|
||||||
|
auxPcinline = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
// FuncInfo flags (internal/abi).
|
||||||
|
const (
|
||||||
|
funcFlagSPWrite = 2
|
||||||
|
funcFlagAsm = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// Relocation types (cmd/internal/objabi).
|
||||||
|
const relocPCRel = 14
|
||||||
|
|
||||||
|
// Special package indices for symbol references.
|
||||||
|
const (
|
||||||
|
pkgIdxNone = 0x7fffffff
|
||||||
|
pkgIdxSelf = 0x7ffffffb
|
||||||
|
)
|
||||||
|
|
||||||
|
const goobjMagic = "\x00go120ld"
|
||||||
|
|
||||||
|
// goSym is one symbol definition under construction.
|
||||||
|
type goSym struct {
|
||||||
|
name string
|
||||||
|
abi uint16
|
||||||
|
typ uint8
|
||||||
|
flag uint8
|
||||||
|
flag2 uint8
|
||||||
|
size uint32
|
||||||
|
align uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s goSym) append(b []byte, strOff map[string]uint32) []byte {
|
||||||
|
b = binary.LittleEndian.AppendUint32(b, uint32(len(s.name)))
|
||||||
|
b = binary.LittleEndian.AppendUint32(b, strOff[s.name])
|
||||||
|
b = binary.LittleEndian.AppendUint16(b, s.abi)
|
||||||
|
b = append(b, s.typ, s.flag, s.flag2)
|
||||||
|
b = binary.LittleEndian.AppendUint32(b, s.size)
|
||||||
|
return binary.LittleEndian.AppendUint32(b, s.align)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOObject returns the image as a GOOBJ object file for the given package
|
||||||
|
// path (the linker qualifies the exported symbols with it, the way cmd/asm
|
||||||
|
// does with its -p flag). srcPath names the source file recorded in the
|
||||||
|
// object's file table and line tables. The toolchain's object preamble is
|
||||||
|
// captured from the installed go tool asm, so the output links with the
|
||||||
|
// toolchain it was produced on — exactly like a real assembly object.
|
||||||
|
func (img *Image) GOObject(pkgPath, srcPath string) ([]byte, error) {
|
||||||
|
if pkgPath == "" {
|
||||||
|
return nil, fmt.Errorf("GOOBJ emission requires a package path (-p)")
|
||||||
|
}
|
||||||
|
pre, err := toolchainObjectPreamble()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The symbol tables. Package definitions: the GLOBL symbols, then one
|
||||||
|
// anonymous FuncInfo symbol per function. Non-package definitions: the
|
||||||
|
// pc-value tables and the functions themselves, as cmd/asm lays them
|
||||||
|
// out. defIdx maps a GLOBL's bare name to its definition index for the
|
||||||
|
// relocations; fnNpIdx maps a function to its non-package index.
|
||||||
|
var defs []goSym
|
||||||
|
var defData [][]byte
|
||||||
|
defIdx := map[string]int{}
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
name := d.Name
|
||||||
|
if !d.Static {
|
||||||
|
name = pkgPath + "." + name
|
||||||
|
}
|
||||||
|
typ := uint8(kindSDATA)
|
||||||
|
if d.Rodata {
|
||||||
|
typ = kindSRODATA
|
||||||
|
}
|
||||||
|
flag := uint8(0)
|
||||||
|
if d.Dupok {
|
||||||
|
flag = symFlagDupok
|
||||||
|
}
|
||||||
|
abi := uint16(0)
|
||||||
|
if d.Static {
|
||||||
|
abi = symABIStatic
|
||||||
|
}
|
||||||
|
defIdx[d.Name] = len(defs)
|
||||||
|
defs = append(defs, goSym{name: name, abi: abi, typ: typ, flag: flag, flag2: symFlag2Link, size: uint32(d.Size)})
|
||||||
|
defData = append(defData, img.Data[d.Offset:d.Offset+d.Size])
|
||||||
|
}
|
||||||
|
fnFiIdx := make([]int, len(img.Funcs))
|
||||||
|
for i := range img.Funcs {
|
||||||
|
data := marshalFuncInfo(img.Funcs[i])
|
||||||
|
fnFiIdx[i] = len(defs)
|
||||||
|
defs = append(defs, goSym{typ: kindSDATA, size: uint32(len(data))})
|
||||||
|
defData = append(defData, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
type npSym struct {
|
||||||
|
sym goSym
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
var nps []npSym
|
||||||
|
type pcRefs struct{ sp, file, line, inl int }
|
||||||
|
pcIdx := make([]pcRefs, len(img.Funcs))
|
||||||
|
fnNpIdx := make([]int, len(img.Funcs))
|
||||||
|
for i, fn := range img.Funcs {
|
||||||
|
tables := []struct {
|
||||||
|
data []byte
|
||||||
|
dst *int
|
||||||
|
}{
|
||||||
|
{pcspTable(fn), &pcIdx[i].sp},
|
||||||
|
{pcValueFlat(0, fn.Size), &pcIdx[i].file},
|
||||||
|
{pcValueFlat(int32(fn.Line), fn.Size), &pcIdx[i].line},
|
||||||
|
{pcValueFlat(-1, fn.Size), &pcIdx[i].inl},
|
||||||
|
}
|
||||||
|
for _, t := range tables {
|
||||||
|
*t.dst = len(nps)
|
||||||
|
nps = append(nps, npSym{
|
||||||
|
sym: goSym{typ: kindSRODATA, size: uint32(len(t.data)), align: 1},
|
||||||
|
data: t.data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
name := fn.Name
|
||||||
|
abi := uint16(0)
|
||||||
|
if fn.Static {
|
||||||
|
abi = symABIStatic
|
||||||
|
} else {
|
||||||
|
name = pkgPath + "." + name
|
||||||
|
}
|
||||||
|
flag := uint8(0)
|
||||||
|
if fn.NoSplit {
|
||||||
|
flag |= symFlagNoSplit
|
||||||
|
}
|
||||||
|
fnNpIdx[i] = len(nps)
|
||||||
|
code := append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...)
|
||||||
|
for _, r := range fn.Relocs {
|
||||||
|
// The linker writes the resolved displacement into the field;
|
||||||
|
// leave it zero, as cmd/asm's object does.
|
||||||
|
if r.Off >= 0 && r.Off+4 <= len(code) {
|
||||||
|
code[r.Off], code[r.Off+1], code[r.Off+2], code[r.Off+3] = 0, 0, 0, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nps = append(nps, npSym{
|
||||||
|
sym: goSym{name: name, abi: abi, typ: kindSTEXT, flag: flag, flag2: symFlag2Link, size: uint32(fn.Size)},
|
||||||
|
data: code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relocations, per defined symbol in definition order (package defs,
|
||||||
|
// then non-package defs). Only file-local GLOBL references resolve;
|
||||||
|
// external symbols need the import machinery of a later increment.
|
||||||
|
nsyms := len(defs) + len(nps)
|
||||||
|
symRelocs := make([][]byte, nsyms) // flat 23-byte records
|
||||||
|
for i, fn := range img.Funcs {
|
||||||
|
si := len(defs) + fnNpIdx[i]
|
||||||
|
for _, r := range fn.Relocs {
|
||||||
|
if r.External {
|
||||||
|
return nil, fmt.Errorf("GOOBJ emission: external symbol %q is not supported yet", r.Name)
|
||||||
|
}
|
||||||
|
di, ok := defIdx[r.Name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("GOOBJ emission: reference to unknown symbol %q", r.Name)
|
||||||
|
}
|
||||||
|
var rec [23]byte
|
||||||
|
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
||||||
|
rec[4] = 4 // field width
|
||||||
|
binary.LittleEndian.PutUint16(rec[5:], relocPCRel)
|
||||||
|
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
||||||
|
binary.LittleEndian.PutUint32(rec[15:], pkgIdxSelf)
|
||||||
|
binary.LittleEndian.PutUint32(rec[19:], uint32(di))
|
||||||
|
symRelocs[si] = append(symRelocs[si], rec[:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aux entries per function: FuncInfo, then the four pc tables.
|
||||||
|
// References into the non-package table use pkgIdxNone.
|
||||||
|
symAux := make([][]byte, nsyms)
|
||||||
|
for i := range img.Funcs {
|
||||||
|
si := len(defs) + fnNpIdx[i]
|
||||||
|
aux := func(typ uint8, pkg, idx uint32) {
|
||||||
|
var rec [9]byte
|
||||||
|
rec[0] = typ
|
||||||
|
binary.LittleEndian.PutUint32(rec[1:], pkg)
|
||||||
|
binary.LittleEndian.PutUint32(rec[5:], idx)
|
||||||
|
symAux[si] = append(symAux[si], rec[:]...)
|
||||||
|
}
|
||||||
|
aux(auxFuncInfo, pkgIdxSelf, uint32(fnFiIdx[i]))
|
||||||
|
aux(auxPcsp, pkgIdxNone, uint32(len(defs)+pcIdx[i].sp))
|
||||||
|
aux(auxPcfile, pkgIdxNone, uint32(len(defs)+pcIdx[i].file))
|
||||||
|
aux(auxPcline, pkgIdxNone, uint32(len(defs)+pcIdx[i].line))
|
||||||
|
aux(auxPcinline, pkgIdxNone, uint32(len(defs)+pcIdx[i].inl))
|
||||||
|
}
|
||||||
|
|
||||||
|
// The string table. Absolute offsets: it starts right after the
|
||||||
|
// 96-byte header (magic, fingerprint, flags, the 19 block offsets).
|
||||||
|
const headerSize = 8 + 8 + 4 + 4*(blkEnd+1)
|
||||||
|
strTab := []byte{}
|
||||||
|
strOff := map[string]uint32{}
|
||||||
|
addStr := func(s string) {
|
||||||
|
if _, ok := strOff[s]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strOff[s] = uint32(headerSize + len(strTab))
|
||||||
|
strTab = append(strTab, s...)
|
||||||
|
}
|
||||||
|
addStr("")
|
||||||
|
addStr(srcPath)
|
||||||
|
for _, s := range defs {
|
||||||
|
addStr(s.name)
|
||||||
|
}
|
||||||
|
for _, s := range nps {
|
||||||
|
addStr(s.sym.name)
|
||||||
|
}
|
||||||
|
stringRef := func(b []byte, s string) []byte {
|
||||||
|
b = binary.LittleEndian.AppendUint32(b, uint32(len(s)))
|
||||||
|
return binary.LittleEndian.AppendUint32(b, strOff[s])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialise the block bodies.
|
||||||
|
var symdefBlk, npdefBlk []byte
|
||||||
|
for _, s := range defs {
|
||||||
|
symdefBlk = s.append(symdefBlk, strOff)
|
||||||
|
}
|
||||||
|
for _, s := range nps {
|
||||||
|
npdefBlk = s.sym.append(npdefBlk, strOff)
|
||||||
|
}
|
||||||
|
pkgIdxBlk := stringRef(nil, "") // index 0: the dummy invalid package
|
||||||
|
fileBlk := stringRef(nil, srcPath)
|
||||||
|
|
||||||
|
var relocBlk, auxBlk, dataBlk []byte
|
||||||
|
relocIdxBlk := make([]byte, 0, 4*(nsyms+1))
|
||||||
|
auxIdxBlk := make([]byte, 0, 4*(nsyms+1))
|
||||||
|
dataIdxBlk := make([]byte, 0, 4*(nsyms+1))
|
||||||
|
var nr, na, nd uint32
|
||||||
|
for si := 0; si < nsyms; si++ {
|
||||||
|
relocIdxBlk = binary.LittleEndian.AppendUint32(relocIdxBlk, nr)
|
||||||
|
auxIdxBlk = binary.LittleEndian.AppendUint32(auxIdxBlk, na)
|
||||||
|
dataIdxBlk = binary.LittleEndian.AppendUint32(dataIdxBlk, nd)
|
||||||
|
relocBlk = append(relocBlk, symRelocs[si]...)
|
||||||
|
auxBlk = append(auxBlk, symAux[si]...)
|
||||||
|
var d []byte
|
||||||
|
if si < len(defData) {
|
||||||
|
d = defData[si]
|
||||||
|
} else {
|
||||||
|
d = nps[si-len(defData)].data
|
||||||
|
}
|
||||||
|
dataBlk = append(dataBlk, d...)
|
||||||
|
nr += uint32(len(symRelocs[si])) / 23
|
||||||
|
na += uint32(len(symAux[si])) / 9
|
||||||
|
nd += uint32(len(d))
|
||||||
|
}
|
||||||
|
relocIdxBlk = binary.LittleEndian.AppendUint32(relocIdxBlk, nr)
|
||||||
|
auxIdxBlk = binary.LittleEndian.AppendUint32(auxIdxBlk, na)
|
||||||
|
dataIdxBlk = binary.LittleEndian.AppendUint32(dataIdxBlk, nd)
|
||||||
|
|
||||||
|
blocks := [blkEnd][]byte{
|
||||||
|
blkPkgIdx: pkgIdxBlk,
|
||||||
|
blkFile: fileBlk,
|
||||||
|
blkSymdef: symdefBlk,
|
||||||
|
blkNonpkgdef: npdefBlk,
|
||||||
|
blkRelocIdx: relocIdxBlk,
|
||||||
|
blkAuxIdx: auxIdxBlk,
|
||||||
|
blkDataIdx: dataIdxBlk,
|
||||||
|
blkReloc: relocBlk,
|
||||||
|
blkAux: auxBlk,
|
||||||
|
blkData: dataBlk,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble the payload: header (offsets filled once known), string
|
||||||
|
// table, blocks in order.
|
||||||
|
payload := make([]byte, headerSize)
|
||||||
|
copy(payload, goobjMagic)
|
||||||
|
// The fingerprint stays zero, as cmd/asm leaves it.
|
||||||
|
binary.LittleEndian.PutUint32(payload[16:], 4) // ObjFlagFromAssembly
|
||||||
|
off := uint32(headerSize + len(strTab))
|
||||||
|
for i := 0; i < blkEnd; i++ {
|
||||||
|
binary.LittleEndian.PutUint32(payload[20+4*i:], off)
|
||||||
|
off += uint32(len(blocks[i]))
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint32(payload[20+4*blkEnd:], off)
|
||||||
|
payload = append(payload, strTab...)
|
||||||
|
for _, blk := range blocks {
|
||||||
|
payload = append(payload, blk...)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]byte, 0, len(pre)+len(payload))
|
||||||
|
out = append(out, pre...)
|
||||||
|
return append(out, payload...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalFuncInfo serialises a function's goobj.FuncInfo: sizes, flags,
|
||||||
|
// start line, the one-element file table and an empty inline tree.
|
||||||
|
func marshalFuncInfo(fn FuncLayout) []byte {
|
||||||
|
flag := uint8(funcFlagAsm)
|
||||||
|
if fn.SPWrite {
|
||||||
|
flag |= funcFlagSPWrite
|
||||||
|
}
|
||||||
|
b := make([]byte, 0, 28)
|
||||||
|
b = binary.LittleEndian.AppendUint32(b, uint32(fn.Args))
|
||||||
|
b = binary.LittleEndian.AppendUint32(b, uint32(fn.Frame))
|
||||||
|
b = append(b, 0, flag, 0, 0) // FuncID normal, flags, padding
|
||||||
|
b = binary.LittleEndian.AppendUint32(b, uint32(int32(fn.Line)))
|
||||||
|
b = binary.LittleEndian.AppendUint32(b, 1) // one file
|
||||||
|
b = binary.LittleEndian.AppendUint32(b, 0) // file index 0
|
||||||
|
b = binary.LittleEndian.AppendUint32(b, 0) // no inline tree
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// pcValueFlat encodes a pc-value table holding v over the whole function.
|
||||||
|
func pcValueFlat(v int32, size int) []byte {
|
||||||
|
// The table is delta-encoded from an implicit value of -1: a varint
|
||||||
|
// value delta, an unsigned pc delta to the end, and a zero terminator.
|
||||||
|
out := binary.AppendVarint(nil, int64(v)+1)
|
||||||
|
out = binary.AppendUvarint(out, uint64(size))
|
||||||
|
return append(out, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pcspTable encodes the stack-adjustment table: the SP delta in effect at
|
||||||
|
// every pc, from the function's prologue and epilogue boundaries.
|
||||||
|
func pcspTable(fn FuncLayout) []byte {
|
||||||
|
if len(fn.Spadj) == 0 {
|
||||||
|
return pcValueFlat(0, fn.Size)
|
||||||
|
}
|
||||||
|
pts := make([]SpadjStep, 0, len(fn.Spadj)+1)
|
||||||
|
pts = append(pts, SpadjStep{PC: 0, Value: 0})
|
||||||
|
pts = append(pts, fn.Spadj...)
|
||||||
|
out := binary.AppendVarint(nil, int64(pts[0].Value)+1)
|
||||||
|
cur, old := pts[0].PC, pts[0].Value
|
||||||
|
for _, p := range pts[1:] {
|
||||||
|
out = binary.AppendUvarint(out, uint64(p.PC-cur))
|
||||||
|
out = binary.AppendVarint(out, int64(p.Value-old))
|
||||||
|
cur, old = p.PC, p.Value
|
||||||
|
}
|
||||||
|
out = binary.AppendUvarint(out, uint64(fn.Size-cur))
|
||||||
|
return append(out, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// toolchainObjectPreamble returns the "go object ...\n!\n" header the
|
||||||
|
// installed go tool asm writes, captured by assembling a one-instruction
|
||||||
|
// probe. The linker compares this string verbatim against its own, so it
|
||||||
|
// must come from the toolchain itself, not be reconstructed.
|
||||||
|
var (
|
||||||
|
preambleOnce sync.Once
|
||||||
|
preamble []byte
|
||||||
|
preambleErr error
|
||||||
|
)
|
||||||
|
|
||||||
|
func toolchainObjectPreamble() ([]byte, error) {
|
||||||
|
preambleOnce.Do(func() {
|
||||||
|
goBin, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
preambleErr = fmt.Errorf("GOOBJ emission needs the Go toolchain: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dir, err := os.MkdirTemp("", "gasm-preamble")
|
||||||
|
if err != nil {
|
||||||
|
preambleErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
src := filepath.Join(dir, "probe_amd64.s")
|
||||||
|
if err := os.WriteFile(src, []byte("TEXT \u00b7x(SB), $0-0\n\tRET\n"), 0o644); err != nil {
|
||||||
|
preambleErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obj := filepath.Join(dir, "probe.o")
|
||||||
|
cmd := exec.Command(goBin, "tool", "asm", "-p", "probe", "-o", obj, src)
|
||||||
|
cmd.Env = append(os.Environ(), "GOARCH=amd64")
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
preambleErr = fmt.Errorf("probing the assembler for the object header: %v\n%s", err, out)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(obj)
|
||||||
|
if err != nil {
|
||||||
|
preambleErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := bytes.Index(data, []byte("\n!\n"))
|
||||||
|
if i < 0 || !bytes.HasPrefix(data[i+3:], []byte(goobjMagic)) {
|
||||||
|
preambleErr = fmt.Errorf("unrecognised assembler object layout")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
preamble = data[:i+3]
|
||||||
|
})
|
||||||
|
return preamble, preambleErr
|
||||||
|
}
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// goobjView is a minimal parsed view of a GOOBJ payload, enough to check
|
||||||
|
// the emitter's output block by block.
|
||||||
|
type goobjView struct {
|
||||||
|
t *testing.T
|
||||||
|
b []byte
|
||||||
|
offs [blkEnd + 1]uint32
|
||||||
|
strOff uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func openGoobj(t *testing.T, data []byte) *goobjView {
|
||||||
|
t.Helper()
|
||||||
|
i := bytes.Index(data, []byte(goobjMagic))
|
||||||
|
if i < 0 {
|
||||||
|
t.Fatal("no GOOBJ magic in output")
|
||||||
|
}
|
||||||
|
v := &goobjView{t: t, b: data[i:], strOff: uint32(i + 96)}
|
||||||
|
for j := 0; j <= blkEnd; j++ {
|
||||||
|
v.offs[j] = binary.LittleEndian.Uint32(v.b[20+4*j:])
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *goobjView) blk(i int) []byte { return v.b[v.offs[i]:v.offs[i+1]] }
|
||||||
|
|
||||||
|
func (v *goobjView) str(off, ln uint32) string {
|
||||||
|
return string(v.b[off : off+ln])
|
||||||
|
}
|
||||||
|
|
||||||
|
type goobjSymView struct {
|
||||||
|
name string
|
||||||
|
abi uint16
|
||||||
|
typ uint8
|
||||||
|
flag uint8
|
||||||
|
flag2 uint8
|
||||||
|
size uint32
|
||||||
|
align uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *goobjView) syms(i int) []goobjSymView {
|
||||||
|
var out []goobjSymView
|
||||||
|
for x := v.blk(i); len(x) >= 21; x = x[21:] {
|
||||||
|
le := binary.LittleEndian
|
||||||
|
out = append(out, goobjSymView{
|
||||||
|
name: v.str(le.Uint32(x[4:]), le.Uint32(x[0:])),
|
||||||
|
abi: le.Uint16(x[8:]),
|
||||||
|
typ: x[10],
|
||||||
|
flag: x[11],
|
||||||
|
flag2: x[12],
|
||||||
|
size: le.Uint32(x[13:]),
|
||||||
|
align: le.Uint32(x[17:]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGOObjectStructure checks the emitted object's blocks against the
|
||||||
|
// ground truth captured from go tool asm: the symbol tables, the FuncInfo
|
||||||
|
// contents, the pc-value tables, the relocation and the aux wiring.
|
||||||
|
func TestGOObjectStructure(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("t_amd64.s", `
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·addq(SB), NOSPLIT, $0-24
|
||||||
|
MOVQ a+0(FP), AX
|
||||||
|
MOVQ b+8(FP), CX
|
||||||
|
ADDQ CX, AX
|
||||||
|
MOVQ AX, ret+16(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·loadmask(SB), NOSPLIT, $0-8
|
||||||
|
VMOVDQU mask<>(SB), X0
|
||||||
|
VPMOVMSKB X0, AX
|
||||||
|
MOVQ AX, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
GLOBL mask<>(SB), RODATA, $16
|
||||||
|
DATA mask<>+0(SB)/8, $0x0807060504030201
|
||||||
|
DATA mask<>+8(SB)/8, $0x800f0e0d0c0b0a09
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFile: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := img.GOObject("testpkg", "t_amd64.s")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GOObject: %v", err)
|
||||||
|
}
|
||||||
|
v := openGoobj(t, obj)
|
||||||
|
|
||||||
|
if flags := binary.LittleEndian.Uint32(v.b[16:]); flags != 4 {
|
||||||
|
t.Errorf("flags = %#x, want ObjFlagFromAssembly (4)", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package defs: the static GLOBL, then one anonymous FuncInfo per
|
||||||
|
// function.
|
||||||
|
defs := v.syms(blkSymdef)
|
||||||
|
if len(defs) != 3 {
|
||||||
|
t.Fatalf("symdefs = %d, want 3", len(defs))
|
||||||
|
}
|
||||||
|
if defs[0].name != "mask" || defs[0].abi != 0xffff || defs[0].typ != kindSRODATA || defs[0].size != 16 || defs[0].flag2 != symFlag2Link {
|
||||||
|
t.Errorf("mask symbol = %+v", defs[0])
|
||||||
|
}
|
||||||
|
if defs[1].name != "" || defs[1].typ != kindSDATA || defs[1].size != 28 {
|
||||||
|
t.Errorf("funcinfo symbol = %+v", defs[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-package defs: four pc tables and the function, per function.
|
||||||
|
nps := v.syms(blkNonpkgdef)
|
||||||
|
if len(nps) != 10 {
|
||||||
|
t.Fatalf("nonpkgdefs = %d, want 10", len(nps))
|
||||||
|
}
|
||||||
|
fn := nps[4]
|
||||||
|
if fn.name != "testpkg.addq" || fn.typ != kindSTEXT || fn.flag != symFlagNoSplit || fn.size != 19 {
|
||||||
|
t.Errorf("addq symbol = %+v", fn)
|
||||||
|
}
|
||||||
|
for i, s := range []int{0, 1, 2, 3, 5, 6, 7, 8} {
|
||||||
|
if nps[s].typ != kindSRODATA || nps[s].align != 1 || nps[s].name != "" {
|
||||||
|
t.Errorf("pc table %d = %+v", i, nps[s])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FuncInfo: args 24, FuncFlag Asm, one file, no inline tree.
|
||||||
|
le := binary.LittleEndian
|
||||||
|
data := v.blk(blkData)
|
||||||
|
fi := data[16:44]
|
||||||
|
if le.Uint32(fi[0:]) != 24 || le.Uint32(fi[4:]) != 0 || fi[8] != 0 || fi[9] != funcFlagAsm ||
|
||||||
|
le.Uint32(fi[16:]) != 1 || le.Uint32(fi[20:]) != 0 || le.Uint32(fi[24:]) != 0 {
|
||||||
|
t.Errorf("funcinfo bytes %x", fi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pcsp: a flat zero over the whole function (zero-frame NOSPLIT).
|
||||||
|
if got := data[72:75]; !bytes.Equal(got, []byte{0x02, 19, 0x00}) {
|
||||||
|
t.Errorf("pcsp = %x, want 021300", got)
|
||||||
|
}
|
||||||
|
// pcinline: a flat -1.
|
||||||
|
if got := data[81:84]; !bytes.Equal(got, []byte{0x00, 19, 0x00}) {
|
||||||
|
t.Errorf("pcinline = %x, want 001300", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The one relocation: R_PCREL, four bytes wide, against the GLOBL,
|
||||||
|
// with the field in the function code left zero. The loadmask code's
|
||||||
|
// offset comes from the data index (symbol 3 defs + 9 non-package).
|
||||||
|
relocs := v.blk(blkReloc)
|
||||||
|
if len(relocs) != 23 {
|
||||||
|
t.Fatalf("relocs = %d bytes, want one 23-byte entry", len(relocs))
|
||||||
|
}
|
||||||
|
off := int32(le.Uint32(relocs[0:]))
|
||||||
|
if off != 4 || relocs[4] != 4 || le.Uint16(relocs[5:]) != relocPCRel ||
|
||||||
|
le.Uint64(relocs[7:]) != 0 || le.Uint32(relocs[15:]) != pkgIdxSelf || le.Uint32(relocs[19:]) != 0 {
|
||||||
|
t.Errorf("reloc = %x", relocs)
|
||||||
|
}
|
||||||
|
didx := v.blk(blkDataIdx)
|
||||||
|
lm := le.Uint32(didx[4*(3+9):])
|
||||||
|
code := data[lm : lm+18]
|
||||||
|
if !bytes.Equal(code[4:8], []byte{0, 0, 0, 0}) {
|
||||||
|
t.Errorf("relocated field = %x, want zeroed", code[4:8])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aux wiring: FuncInfo (package symbol), then the four pc tables
|
||||||
|
// (non-package symbols).
|
||||||
|
auxs := v.blk(blkAux)
|
||||||
|
if len(auxs) != 2*5*9 {
|
||||||
|
t.Fatalf("aux = %d bytes, want 10 entries", len(auxs))
|
||||||
|
}
|
||||||
|
wantAux := []struct {
|
||||||
|
typ uint8
|
||||||
|
pkg uint32
|
||||||
|
idx uint32
|
||||||
|
}{
|
||||||
|
{auxFuncInfo, pkgIdxSelf, 1},
|
||||||
|
{auxPcsp, pkgIdxNone, uint32(len(defs) + 0)},
|
||||||
|
{auxPcfile, pkgIdxNone, uint32(len(defs) + 1)},
|
||||||
|
{auxPcline, pkgIdxNone, uint32(len(defs) + 2)},
|
||||||
|
{auxPcinline, pkgIdxNone, uint32(len(defs) + 3)},
|
||||||
|
{auxFuncInfo, pkgIdxSelf, 2},
|
||||||
|
{auxPcsp, pkgIdxNone, uint32(len(defs) + 5)},
|
||||||
|
{auxPcfile, pkgIdxNone, uint32(len(defs) + 6)},
|
||||||
|
{auxPcline, pkgIdxNone, uint32(len(defs) + 7)},
|
||||||
|
{auxPcinline, pkgIdxNone, uint32(len(defs) + 8)},
|
||||||
|
}
|
||||||
|
for i, w := range wantAux {
|
||||||
|
e := auxs[i*9:]
|
||||||
|
if e[0] != w.typ || le.Uint32(e[1:]) != w.pkg || le.Uint32(e[5:]) != w.idx {
|
||||||
|
t.Errorf("aux[%d] = {%d,%d,%d}, want {%d,%d,%d}", i, e[0], le.Uint32(e[1:]), le.Uint32(e[5:]), w.typ, w.pkg, w.idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodePCValues decodes a pc-value table into (pc, value) steps. The
|
||||||
|
// table ends with a final unsigned pc delta covering the rest of the
|
||||||
|
// function, followed by a zero byte that carries no value delta.
|
||||||
|
func decodePCValues(b []byte) (pcs, vals []int64) {
|
||||||
|
val, n := binary.Varint(b)
|
||||||
|
b = b[n:]
|
||||||
|
val-- // the first delta is against the implicit -1
|
||||||
|
var pc int64
|
||||||
|
pcs = append(pcs, pc)
|
||||||
|
vals = append(vals, val)
|
||||||
|
for {
|
||||||
|
pcd, n := binary.Uvarint(b)
|
||||||
|
b = b[n:]
|
||||||
|
if pcd == 0 { // zero pc delta terminates the table
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pc += int64(pcd)
|
||||||
|
if len(b) == 1 && b[0] == 0 { // final coverage, no value change
|
||||||
|
break
|
||||||
|
}
|
||||||
|
vd, n := binary.Varint(b)
|
||||||
|
b = b[n:]
|
||||||
|
val += vd
|
||||||
|
pcs = append(pcs, pc)
|
||||||
|
vals = append(vals, val)
|
||||||
|
}
|
||||||
|
return pcs, vals
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGOObjectPcspFrame checks the pcsp table of a frame-pointer function:
|
||||||
|
// the prologue raises the stack delta to 8+frame, the RET's epilogue
|
||||||
|
// restores it to zero.
|
||||||
|
func TestGOObjectPcspFrame(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("frame_amd64.s", `
|
||||||
|
#include "textflag.h"
|
||||||
|
TEXT ·framed(SB), NOSPLIT, $8-0
|
||||||
|
MOVQ BP, AX
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFile: %v", err)
|
||||||
|
}
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
pcs, vals := decodePCValues(pcspTable(fn))
|
||||||
|
// Prologue: PUSHQ BP (1 byte, +8), MOVQ SP, BP (3 bytes, no change),
|
||||||
|
// SUBQ $8, SP (4 bytes, +16 in total); the RET's epilogue unwinds
|
||||||
|
// ADDQ $8, SP (+8) then POPQ BP (0).
|
||||||
|
wantPCs := []int64{0, 1, 8}
|
||||||
|
wantVals := []int64{0, 8, 16}
|
||||||
|
if len(pcs) < len(wantPCs) {
|
||||||
|
t.Fatalf("pcsp pcs = %v vals = %v", pcs, vals)
|
||||||
|
}
|
||||||
|
for i := range wantPCs {
|
||||||
|
if pcs[i] != wantPCs[i] || vals[i] != wantVals[i] {
|
||||||
|
t.Errorf("pcsp[%d] = (%d,%d), want (%d,%d) — all: %v %v", i, pcs[i], vals[i], wantPCs[i], wantVals[i], pcs, vals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The last two steps unwind the epilogue to zero.
|
||||||
|
n := len(pcs)
|
||||||
|
if vals[n-1] != 0 || vals[n-2] != 8 {
|
||||||
|
t.Errorf("epilogue steps = %v %v, want …8, 0", pcs, vals)
|
||||||
|
}
|
||||||
|
// The table covers the whole function.
|
||||||
|
if last := pcs[n-1]; last >= int64(fn.Size) {
|
||||||
|
t.Errorf("last pc %d beyond function size %d", last, fn.Size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGOObjectExternalRejected checks that a reference to a symbol no GLOBL
|
||||||
|
// defines is reported: GOOBJ emission resolves only file-local symbols so
|
||||||
|
// far.
|
||||||
|
func TestGOObjectExternalRejected(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("ext_amd64.s", `
|
||||||
|
#include "textflag.h"
|
||||||
|
TEXT ·useext(SB), NOSPLIT, $0-8
|
||||||
|
MOVQ elsewhere(SB), AX
|
||||||
|
MOVQ AX, ret+0(FP)
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFile: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := img.GOObject("p", "ext_amd64.s"); err == nil || !strings.Contains(err.Error(), "external") {
|
||||||
|
t.Errorf("error = %v, want an external-symbol error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGOObjectLinkAndRun is the end-to-end check: assemble the test
|
||||||
|
// functions to a GOOBJ, swap it into a go build in place of the toolchain's
|
||||||
|
// assembly object, link, and run — the output must match the baseline
|
||||||
|
// binary the Go assembler produced. Skipped when no Go toolchain is
|
||||||
|
// available.
|
||||||
|
func TestGOObjectLinkAndRun(t *testing.T) {
|
||||||
|
goBin, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no Go toolchain available")
|
||||||
|
}
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
const asmSrc = `
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·addq(SB), NOSPLIT, $0-24
|
||||||
|
MOVQ a+0(FP), AX
|
||||||
|
MOVQ b+8(FP), CX
|
||||||
|
ADDQ CX, AX
|
||||||
|
MOVQ AX, ret+16(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·loadmask(SB), NOSPLIT, $0-8
|
||||||
|
VMOVDQU mask<>(SB), X0
|
||||||
|
VPMOVMSKB X0, AX
|
||||||
|
MOVQ AX, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
GLOBL mask<>(SB), RODATA, $16
|
||||||
|
DATA mask<>+0(SB)/8, $0x0807060504030201
|
||||||
|
DATA mask<>+8(SB)/8, $0x800f0e0d0c0b0a09
|
||||||
|
`
|
||||||
|
const mainSrc = `package main
|
||||||
|
|
||||||
|
func addq(a, b int64) int64
|
||||||
|
func loadmask() int64
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
println(addq(41, 1))
|
||||||
|
println(loadmask())
|
||||||
|
}
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "main_amd64.s"), []byte(asmSrc), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "main.go"), []byte(mainSrc), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module goobjtest\n\ngo 1.26\n"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baseline build with the toolchain's assembler; keep the work
|
||||||
|
// directory and the commands the build used.
|
||||||
|
cmd := exec.Command(goBin, "build", "-x", "-work", "-o", "app", ".")
|
||||||
|
cmd.Dir = dir
|
||||||
|
buildLog, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||||
|
}
|
||||||
|
var work string
|
||||||
|
var asmObj, pkgArch, linkLine string
|
||||||
|
for _, line := range strings.Split(string(buildLog), "\n") {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(line, "WORK="):
|
||||||
|
work = strings.TrimPrefix(line, "WORK=")
|
||||||
|
case strings.Contains(line, "/asm ") && strings.Contains(line, "-o ") && strings.Contains(line, "main_amd64.s") && !strings.Contains(line, "-gensymabis"):
|
||||||
|
asmObj = fieldAfter(line, "-o")
|
||||||
|
case strings.Contains(line, "pack r") && strings.Contains(line, "_pkg_.a"):
|
||||||
|
pkgArch = strings.TrimSpace(strings.SplitN(line, "pack r", 2)[1])
|
||||||
|
pkgArch = strings.Fields(strings.SplitN(pkgArch, "#", 2)[0])[0]
|
||||||
|
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
|
||||||
|
linkLine = line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if work == "" || asmObj == "" || pkgArch == "" || linkLine == "" {
|
||||||
|
t.Fatalf("could not locate the build steps:\n%s", buildLog)
|
||||||
|
}
|
||||||
|
asmObj = strings.ReplaceAll(asmObj, "$WORK", work)
|
||||||
|
pkgArch = strings.ReplaceAll(pkgArch, "$WORK", work)
|
||||||
|
|
||||||
|
// The baseline's answer.
|
||||||
|
baseOut, err := exec.Command(filepath.Join(dir, "app")).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("run baseline: %v\n%s", err, baseOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble the same source with gasm and swap the object in.
|
||||||
|
pf, perrs := parser.Parse(filepath.Join(dir, "main_amd64.s"), asmSrc)
|
||||||
|
if len(perrs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", perrs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(pf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFile: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := img.GOObject("main", filepath.Join(dir, "main_amd64.s"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GOObject: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(asmObj, obj, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild the package archive with our object in place of the
|
||||||
|
// toolchain's (go tool pack has no replace-in-place that dedupes, so
|
||||||
|
// extract, substitute and repack).
|
||||||
|
extract := exec.Command(goBin, "tool", "pack", "x", pkgArch)
|
||||||
|
membersDir := filepath.Join(dir, "members")
|
||||||
|
if err := os.MkdirAll(membersDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
extract.Dir = membersDir
|
||||||
|
if out, err := extract.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("pack x: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
listCmd := exec.Command(goBin, "tool", "pack", "t", pkgArch)
|
||||||
|
listOut, err := listCmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("pack t: %v\n%s", err, listOut)
|
||||||
|
}
|
||||||
|
newArch := filepath.Join(dir, "pkg.a")
|
||||||
|
args := []string{"tool", "pack", "c", newArch}
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, m := range strings.Fields(string(listOut)) {
|
||||||
|
if seen[m] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[m] = true
|
||||||
|
if err := os.Chmod(filepath.Join(membersDir, m), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
args = append(args, filepath.Join(membersDir, m))
|
||||||
|
}
|
||||||
|
pack := exec.Command(goBin, args...)
|
||||||
|
pack.Dir = membersDir
|
||||||
|
if out, err := pack.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("pack c: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link with our archive. The link line carries a GOROOT assignment
|
||||||
|
// and $WORK placeholders; run it through the shell with the
|
||||||
|
// GOEXPERIMENT the toolchain expects (the linker compares the object
|
||||||
|
// header against its own, experiments included).
|
||||||
|
goExp, _ := exec.Command(goBin, "env", "GOEXPERIMENT").Output()
|
||||||
|
linkLine = strings.ReplaceAll(linkLine, "$WORK", work)
|
||||||
|
linkLine = strings.ReplaceAll(linkLine, filepath.Join(work, "b001", "_pkg_.a"), newArch)
|
||||||
|
linkLine = strings.ReplaceAll(linkLine, filepath.Join(work, "b001", "exe", "a.out"), filepath.Join(dir, "app2"))
|
||||||
|
link := exec.Command("sh", "-c", linkLine)
|
||||||
|
link.Dir = dir
|
||||||
|
link.Env = append(os.Environ(), "GOEXPERIMENT="+strings.TrimSpace(string(goExp)))
|
||||||
|
if out, err := link.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("link with gasm object: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
got, err := exec.Command(filepath.Join(dir, "app2")).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("run gasm-linked binary: %v\n%s", err, got)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, baseOut) {
|
||||||
|
t.Errorf("gasm-linked output %q, want baseline %q", got, baseOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldAfter returns the whitespace-delimited field following the first
|
||||||
|
// occurrence of flag in line.
|
||||||
|
func fieldAfter(line, flag string) string {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
for i, f := range fields {
|
||||||
|
if f == flag && i+1 < len(fields) {
|
||||||
|
return fields[i+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
+78
-12
@@ -28,13 +28,26 @@ type Image struct {
|
|||||||
|
|
||||||
// FuncLayout describes one assembled function within an Image.
|
// FuncLayout describes one assembled function within an Image.
|
||||||
type FuncLayout struct {
|
type FuncLayout struct {
|
||||||
Name string
|
Name string
|
||||||
Pkg string // explicit package prefix ("" = the current package)
|
Pkg string // explicit package prefix ("" = the current package)
|
||||||
Static bool // the <> marker: file-local, not exported
|
Static bool // the <> marker: file-local, not exported
|
||||||
Offset int // start offset within the image (== offset within Code)
|
Offset int // start offset within the image (== offset within Code)
|
||||||
Size int
|
Size int
|
||||||
Labels map[string]int // local labels, function-relative
|
Args int // declared argument/result area (the TEXT size suffix)
|
||||||
Relocs []Reloc // static-symbol references, in emission order
|
Frame int // local frame size (the TEXT $framesize)
|
||||||
|
NoSplit bool // the NOSPLIT flag
|
||||||
|
SPWrite bool // the SPWRITE flag: writes an arbitrary value to SP
|
||||||
|
Line int // source line of the TEXT directive
|
||||||
|
Labels map[string]int // local labels, function-relative
|
||||||
|
Relocs []Reloc // static-symbol references, in emission order
|
||||||
|
Spadj []SpadjStep // stack-adjustment boundaries, ascending by PC
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpadjStep is one stack-adjustment boundary: Value is the SP delta from the
|
||||||
|
// entry state in effect from PC (function-relative) until the next step.
|
||||||
|
type SpadjStep struct {
|
||||||
|
PC int
|
||||||
|
Value int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reloc is one static-symbol reference within a function body: the disp32
|
// Reloc is one static-symbol reference within a function body: the disp32
|
||||||
@@ -57,6 +70,8 @@ type DataSymbol struct {
|
|||||||
Offset int // byte offset within Data
|
Offset int // byte offset within Data
|
||||||
Size int
|
Size int
|
||||||
Static bool // the <> marker: file-local, not exported
|
Static bool // the <> marker: file-local, not exported
|
||||||
|
Rodata bool // the RODATA flag: read-only data
|
||||||
|
Dupok bool // the DUPOK flag: duplicate-OK
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes returns the whole image: code, then data.
|
// Bytes returns the whole image: code, then data.
|
||||||
@@ -95,18 +110,33 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
code, patches, labels, err := assemble(t, link)
|
code, patches, labels, steps, err := assemble(t, link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
||||||
}
|
}
|
||||||
img.Funcs = append(img.Funcs, FuncLayout{
|
fl := FuncLayout{
|
||||||
Name: t.Name.Name,
|
Name: t.Name.Name,
|
||||||
Pkg: t.Name.Pkg,
|
Pkg: t.Name.Pkg,
|
||||||
Static: t.Name.Static,
|
Static: t.Name.Static,
|
||||||
Offset: len(img.Code),
|
Offset: len(img.Code),
|
||||||
Size: len(code),
|
Size: len(code),
|
||||||
|
Frame: frameSize(t),
|
||||||
|
Args: argsSize(t),
|
||||||
|
Line: t.Pos().Line,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
})
|
}
|
||||||
|
for _, f := range t.Flags {
|
||||||
|
switch f {
|
||||||
|
case "NOSPLIT":
|
||||||
|
fl.NoSplit = true
|
||||||
|
case "SPWRITE":
|
||||||
|
fl.SPWrite = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range steps {
|
||||||
|
fl.Spadj = append(fl.Spadj, SpadjStep{PC: s.pc, Value: s.value})
|
||||||
|
}
|
||||||
|
img.Funcs = append(img.Funcs, fl)
|
||||||
img.Code = append(img.Code, code...)
|
img.Code = append(img.Code, code...)
|
||||||
funcs = append(funcs, asmFunc{name: t.Name.Name, patches: patches})
|
funcs = append(funcs, asmFunc{name: t.Name.Name, patches: patches})
|
||||||
}
|
}
|
||||||
@@ -124,6 +154,8 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
|||||||
Offset: len(img.Data),
|
Offset: len(img.Data),
|
||||||
Size: len(d.buf),
|
Size: len(d.buf),
|
||||||
Static: d.static,
|
Static: d.static,
|
||||||
|
Rodata: d.rodata,
|
||||||
|
Dupok: d.dupok,
|
||||||
})
|
})
|
||||||
img.Data = append(img.Data, d.buf...)
|
img.Data = append(img.Data, d.buf...)
|
||||||
}
|
}
|
||||||
@@ -163,6 +195,8 @@ type dataSym struct {
|
|||||||
pkg string
|
pkg string
|
||||||
buf []byte
|
buf []byte
|
||||||
static bool
|
static bool
|
||||||
|
rodata bool
|
||||||
|
dupok bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// collectData gathers the file's static symbols (GLOBL) and their initial
|
// collectData gathers the file's static symbols (GLOBL) and their initial
|
||||||
@@ -185,12 +219,28 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
|||||||
size = int(dd.Size.Imm.Val)
|
size = int(dd.Size.Imm.Val)
|
||||||
}
|
}
|
||||||
index[name] = len(syms)
|
index[name] = len(syms)
|
||||||
syms = append(syms, dataSym{
|
ds := dataSym{
|
||||||
name: name,
|
name: name,
|
||||||
pkg: dd.Name.Pkg,
|
pkg: dd.Name.Pkg,
|
||||||
buf: make([]byte, size),
|
buf: make([]byte, size),
|
||||||
static: dd.Name.Static,
|
static: dd.Name.Static,
|
||||||
})
|
}
|
||||||
|
for _, f := range dd.Flags {
|
||||||
|
switch f {
|
||||||
|
case "RODATA":
|
||||||
|
ds.rodata = true
|
||||||
|
case "DUPOK":
|
||||||
|
ds.dupok = true
|
||||||
|
case "1":
|
||||||
|
ds.dupok = true
|
||||||
|
case "8":
|
||||||
|
ds.rodata = true
|
||||||
|
case "9":
|
||||||
|
ds.dupok = true
|
||||||
|
ds.rodata = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syms = append(syms, ds)
|
||||||
|
|
||||||
case *ast.Data:
|
case *ast.Data:
|
||||||
if dd.Name == nil || dd.Name.Pseudo != "SB" {
|
if dd.Name == nil || dd.Name.Pseudo != "SB" {
|
||||||
@@ -230,3 +280,19 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
|||||||
func align16(n int) int {
|
func align16(n int) int {
|
||||||
return (n + 15) &^ 15
|
return (n + 15) &^ 15
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// frameSize returns the local frame size declared on the TEXT directive.
|
||||||
|
func frameSize(t *ast.Text) int {
|
||||||
|
if t.Frame != nil && t.Frame.Imm.HasVal {
|
||||||
|
return int(t.Frame.Imm.Val)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// argsSize returns the argument/result area declared on the TEXT directive.
|
||||||
|
func argsSize(t *ast.Text) int {
|
||||||
|
if t.Args != nil && t.Args.Imm.HasVal {
|
||||||
|
return int(t.Args.Imm.Val)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|||||||
+52
-6
@@ -91,12 +91,19 @@ var vexTable = map[string]vexSpec{
|
|||||||
"VPCMPGTQ": {2, 0x37, 0, 1, -1, vexNDS3},
|
"VPCMPGTQ": {2, 0x37, 0, 1, -1, vexNDS3},
|
||||||
|
|
||||||
// VEX.128/256.66.0F.WIG — packed double-precision arithmetic / logic.
|
// VEX.128/256.66.0F.WIG — packed double-precision arithmetic / logic.
|
||||||
"VADDPD": {1, 0x58, 0, 1, -1, vexNDS3},
|
"VADDPD": {1, 0x58, 0, 1, -1, vexNDS3},
|
||||||
"VMULPD": {1, 0x59, 0, 1, -1, vexNDS3},
|
"VMULPD": {1, 0x59, 0, 1, -1, vexNDS3},
|
||||||
"VSUBPD": {1, 0x5C, 0, 1, -1, vexNDS3},
|
"VSUBPD": {1, 0x5C, 0, 1, -1, vexNDS3},
|
||||||
"VDIVPD": {1, 0x5E, 0, 1, -1, vexNDS3},
|
"VDIVPD": {1, 0x5E, 0, 1, -1, vexNDS3},
|
||||||
"VMINPD": {1, 0x5D, 0, 1, -1, vexNDS3},
|
"VMINPD": {1, 0x5D, 0, 1, -1, vexNDS3},
|
||||||
"VMAXPD": {1, 0x5F, 0, 1, -1, vexNDS3},
|
"VMAXPD": {1, 0x5F, 0, 1, -1, vexNDS3},
|
||||||
|
// VEX.128/256.0F.WIG — packed single-precision arithmetic.
|
||||||
|
"VADDPS": {1, 0x58, 0, 0, -1, vexNDS3},
|
||||||
|
"VMULPS": {1, 0x59, 0, 0, -1, vexNDS3},
|
||||||
|
"VSUBPS": {1, 0x5C, 0, 0, -1, vexNDS3},
|
||||||
|
"VDIVPS": {1, 0x5E, 0, 0, -1, vexNDS3},
|
||||||
|
"VMINPS": {1, 0x5D, 0, 0, -1, vexNDS3},
|
||||||
|
"VMAXPS": {1, 0x5F, 0, 0, -1, vexNDS3},
|
||||||
"VXORPD": {1, 0x57, 0, 1, -1, vexNDS3},
|
"VXORPD": {1, 0x57, 0, 1, -1, vexNDS3},
|
||||||
"VUNPCKHPD": {1, 0x15, 0, 1, -1, vexNDS3},
|
"VUNPCKHPD": {1, 0x15, 0, 1, -1, vexNDS3},
|
||||||
"VUNPCKLPD": {1, 0x14, 0, 1, -1, vexNDS3},
|
"VUNPCKLPD": {1, 0x14, 0, 1, -1, vexNDS3},
|
||||||
@@ -123,7 +130,15 @@ var vexTable = map[string]vexSpec{
|
|||||||
// no vvvv).
|
// no vvvv).
|
||||||
"VPMOVSXWD": {2, 0x23, 0, 1, -1, vexRM},
|
"VPMOVSXWD": {2, 0x23, 0, 1, -1, vexRM},
|
||||||
"VPMOVSXDQ": {2, 0x25, 0, 1, -1, vexRM},
|
"VPMOVSXDQ": {2, 0x25, 0, 1, -1, vexRM},
|
||||||
|
"VPMOVSXBD": {2, 0x21, 0, 1, -1, vexRM},
|
||||||
|
"VPMOVSXBQ": {2, 0x22, 0, 1, -1, vexRM},
|
||||||
|
"VPMOVSXWQ": {2, 0x24, 0, 1, -1, vexRM},
|
||||||
"VPMOVZXDQ": {2, 0x35, 0, 1, -1, vexRM},
|
"VPMOVZXDQ": {2, 0x35, 0, 1, -1, vexRM},
|
||||||
|
"VPMOVZXBW": {2, 0x30, 0, 1, -1, vexRM},
|
||||||
|
"VPMOVZXBD": {2, 0x31, 0, 1, -1, vexRM},
|
||||||
|
"VPMOVZXBQ": {2, 0x32, 0, 1, -1, vexRM},
|
||||||
|
"VPMOVZXWD": {2, 0x33, 0, 1, -1, vexRM},
|
||||||
|
"VPMOVZXWQ": {2, 0x34, 0, 1, -1, vexRM},
|
||||||
"VPBROADCASTD": {2, 0x58, 0, 1, -1, vexRM},
|
"VPBROADCASTD": {2, 0x58, 0, 1, -1, vexRM},
|
||||||
"VPBROADCASTQ": {2, 0x59, 0, 1, -1, vexRM},
|
"VPBROADCASTQ": {2, 0x59, 0, 1, -1, vexRM},
|
||||||
// VEX.128/256.F3.0F.WIG — signed dword to packed double conversion
|
// VEX.128/256.F3.0F.WIG — signed dword to packed double conversion
|
||||||
@@ -169,6 +184,9 @@ var vexTable = map[string]vexSpec{
|
|||||||
// VEX.256.66.0F3A.W0 — lane extract (reg=YMM src, rm=XMM/memory dst, imm8).
|
// VEX.256.66.0F3A.W0 — lane extract (reg=YMM src, rm=XMM/memory dst, imm8).
|
||||||
"VEXTRACTI128": {3, 0x39, 0, 1, -1, vexExtract},
|
"VEXTRACTI128": {3, 0x39, 0, 1, -1, vexExtract},
|
||||||
"VEXTRACTF128": {3, 0x19, 0, 1, -1, vexExtract},
|
"VEXTRACTF128": {3, 0x19, 0, 1, -1, vexExtract},
|
||||||
|
// VEX.128/256.66.0F3A.W0 — half-precision convert back ($imm, src, dst:
|
||||||
|
// reg=src, rm=XMM/memory dst, imm8 — the extract layout).
|
||||||
|
"VCVTPS2PH": {3, 0x1D, 0, 1, -1, vexExtract},
|
||||||
|
|
||||||
// VEX.128.0F.W0 — no operands.
|
// VEX.128.0F.W0 — no operands.
|
||||||
"VZEROUPPER": {1, 0x77, 0, 0, -1, vexZero},
|
"VZEROUPPER": {1, 0x77, 0, 0, -1, vexZero},
|
||||||
@@ -176,6 +194,27 @@ var vexTable = map[string]vexSpec{
|
|||||||
// VEX.128.0F.W0 — mask-register test (KTESTW k1, k2: reg = dst, rm = src).
|
// VEX.128.0F.W0 — mask-register test (KTESTW k1, k2: reg = dst, rm = src).
|
||||||
"KTESTW": {1, 0x99, 0, 0, -1, vexRM},
|
"KTESTW": {1, 0x99, 0, 0, -1, vexRM},
|
||||||
|
|
||||||
|
// VEX.66.0F38.W0 — broadcast a single/double to all lanes (reg=dst,
|
||||||
|
// rm=scalar memory; SD is 256-bit only).
|
||||||
|
"VBROADCASTSS": {2, 0x18, 0, 1, -1, vexRM},
|
||||||
|
"VBROADCASTSD": {2, 0x19, 0, 1, -1, vexRM},
|
||||||
|
// VEX.66.0F38.W0 — half-precision convert (reg=dst, rm=half-width
|
||||||
|
// source).
|
||||||
|
"VCVTPH2PS": {2, 0x13, 0, 1, -1, vexRM},
|
||||||
|
// VEX.F3.0F.WIG — replicate even/odd singles (reg=dst, rm=src).
|
||||||
|
"VMOVSLDUP": {1, 0x12, 0, 2, -1, vexRM},
|
||||||
|
"VMOVSHDUP": {1, 0x16, 0, 2, -1, vexRM},
|
||||||
|
// VEX.66.0F.WIG — packed double to packed single conversion, the X/Y
|
||||||
|
// spellings: the destination is always XMM and the spelling fixes the
|
||||||
|
// source length (X = 128, Y = 256).
|
||||||
|
"VCVTPD2PSX": {1, 0x5A, 0, 1, -1, vexRMSrcLen},
|
||||||
|
"VCVTPD2PSY": {1, 0x5A, 0, 1, -1, vexRMSrcLen},
|
||||||
|
|
||||||
|
// VEX.128/256.66.0F.WIG — word shifts (opdigit selects the shift).
|
||||||
|
"VPSRLW": {1, 0x71, 0, 1, 2, vexShiftImm},
|
||||||
|
"VPSRAW": {1, 0x71, 0, 1, 4, vexShiftImm},
|
||||||
|
"VPSLLW": {1, 0x71, 0, 1, 6, vexShiftImm},
|
||||||
|
|
||||||
// VEX.F2.0F — packed double to packed dword conversions, truncating and
|
// VEX.F2.0F — packed double to packed dword conversions, truncating and
|
||||||
// non-truncating. The destination is always XMM; the X/Y spellings fix
|
// non-truncating. The destination is always XMM; the X/Y spellings fix
|
||||||
// the source length (XMM/YMM), and VEX.L follows it — see vexSrcLen.
|
// the source length (XMM/YMM), and VEX.L follows it — see vexSrcLen.
|
||||||
@@ -194,6 +233,8 @@ var vexSrcLen = map[string]int{
|
|||||||
"VCVTPD2DQY": 1,
|
"VCVTPD2DQY": 1,
|
||||||
"VCVTTPD2DQX": 0,
|
"VCVTTPD2DQX": 0,
|
||||||
"VCVTTPD2DQY": 1,
|
"VCVTTPD2DQY": 1,
|
||||||
|
"VCVTPD2PSX": 0,
|
||||||
|
"VCVTPD2PSY": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// vexVarShift maps the shift mnemonics to their variable-count opcode — the
|
// vexVarShift maps the shift mnemonics to their variable-count opcode — the
|
||||||
@@ -238,6 +279,11 @@ var vexMoveTable = map[string]vexMoveSpec{
|
|||||||
// VEX.128.F2.0F.WIG — scalar double move, memory operands only (the
|
// VEX.128.F2.0F.WIG — scalar double move, memory operands only (the
|
||||||
// register form takes three operands and is not supported yet).
|
// register form takes three operands and is not supported yet).
|
||||||
"VMOVSD": {1, 3, 0x10, 0x11, 0, 0, 0, 0, false, false, true},
|
"VMOVSD": {1, 3, 0x10, 0x11, 0, 0, 0, 0, false, false, true},
|
||||||
|
// VEX.128.F3.0F.WIG — scalar single move, memory operands only.
|
||||||
|
"VMOVSS": {1, 2, 0x10, 0x11, 0, 0, 0, 0, false, false, true},
|
||||||
|
// VEX.128/256 — aligned packed moves.
|
||||||
|
"VMOVAPS": {1, 0, 0x28, 0x29, 0, 0, 0, 0, true, false, false},
|
||||||
|
"VMOVAPD": {1, 1, 0x28, 0x29, 0, 0, 0, 0, true, false, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
// isVex reports whether the mnemonic is a VEX-encoded instruction we handle.
|
// isVex reports whether the mnemonic is a VEX-encoded instruction we handle.
|
||||||
|
|||||||
+13
-7
@@ -28,7 +28,7 @@ import (
|
|||||||
|
|
||||||
// version is the release version, stamped at build time via
|
// version is the release version, stamped at build time via
|
||||||
// -ldflags "-X main.version=…" (defaulting to the current release).
|
// -ldflags "-X main.version=…" (defaulting to the current release).
|
||||||
var version = "0.11.0"
|
var version = "0.15.0"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
@@ -94,6 +94,7 @@ Examples:
|
|||||||
gasm lint go-flac/*.s run static checks over the kernels
|
gasm lint go-flac/*.s run static checks over the kernels
|
||||||
gasm asm -o k.bin kern_amd64.s
|
gasm asm -o k.bin kern_amd64.s
|
||||||
gasm asm --format elf -o k.o kern_amd64.s
|
gasm asm --format elf -o k.o kern_amd64.s
|
||||||
|
gasm asm --format goobj -p pkg/path -o k.o kern_amd64.s
|
||||||
`, version)
|
`, version)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +341,7 @@ hover, document symbols, diagnostics and semantic-token highlighting.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cmdAsm(args []string) int {
|
func cmdAsm(args []string) int {
|
||||||
fs := newCommand("asm", "gasm asm [--format raw|elf|macho] [-o out] <file>", `
|
fs := newCommand("asm", "gasm asm [--format raw|elf|macho|goobj] [-p pkg] [-o out] <file>", `
|
||||||
Assemble FILE (amd64) without the Go toolchain: every TEXT function is
|
Assemble FILE (amd64) without the Go toolchain: every TEXT function is
|
||||||
encoded to machine code — scalar, VEX/AVX2 and EVEX/AVX-512 instructions,
|
encoded to machine code — scalar, VEX/AVX2 and EVEX/AVX-512 instructions,
|
||||||
FP/SP frame mapping, local labels and file-local static symbols (GLOBL/DATA)
|
FP/SP frame mapping, local labels and file-local static symbols (GLOBL/DATA)
|
||||||
@@ -350,14 +351,16 @@ With -o the output is written to a file instead. The --format flag selects
|
|||||||
what is written: raw (the default) concatenates the functions and the data
|
what is written: raw (the default) concatenates the functions and the data
|
||||||
section into one self-consistent image; elf and macho emit a relocatable
|
section into one self-consistent image; elf and macho emit a relocatable
|
||||||
object (.text/.data sections, a symbol table and one PC32 relocation per
|
object (.text/.data sections, a symbol table and one PC32 relocation per
|
||||||
static-symbol reference) that links with the system toolchain — references
|
static-symbol reference) that links with the system toolchain; goobj emits
|
||||||
to symbols no GLOBL in the file defines become undefined external symbols.
|
the Go toolchain's own object format, which cmd/link consumes directly (it
|
||||||
|
requires -p, the package path, and the installed Go toolchain).
|
||||||
`)
|
`)
|
||||||
out := fs.String("o", "", "write the output to this file")
|
out := fs.String("o", "", "write the output to this file")
|
||||||
format := fs.String("format", "raw", "output format: raw (concatenated image), elf or macho (relocatable object)")
|
format := fs.String("format", "raw", "output format: raw (concatenated image), elf, macho or goobj (Go object)")
|
||||||
|
pkg := fs.String("p", "", "package path for --format goobj (qualifies the exported symbols)")
|
||||||
fs.Parse(args)
|
fs.Parse(args)
|
||||||
if fs.NArg() != 1 {
|
if fs.NArg() != 1 {
|
||||||
fmt.Fprintln(os.Stderr, "usage: gasm asm [--format raw|elf|macho] [-o out] <file>")
|
fmt.Fprintln(os.Stderr, "usage: gasm asm [--format raw|elf|macho|goobj] [-p pkg] [-o out] <file>")
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
path := fs.Arg(0)
|
path := fs.Arg(0)
|
||||||
@@ -444,8 +447,11 @@ to symbols no GLOBL in the file defines become undefined external symbols.
|
|||||||
case "macho":
|
case "macho":
|
||||||
obj, err = img.MachOObject()
|
obj, err = img.MachOObject()
|
||||||
kind = "Mach-O object"
|
kind = "Mach-O object"
|
||||||
|
case "goobj":
|
||||||
|
obj, err = img.GOObject(*pkg, path)
|
||||||
|
kind = "Go object"
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "gasm asm: unknown format %q (want raw, elf or macho)\n", *format)
|
fmt.Fprintf(os.Stderr, "gasm asm: unknown format %q (want raw, elf, macho or goobj)\n", *format)
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+36
-9
@@ -228,11 +228,29 @@ explicit merging/zeroing masks — written the way Go writes them, as a K
|
|||||||
operand among the operands plus a `.Z` mnemonic suffix), and the compressed
|
operand among the operands plus a `.Z` mnemonic suffix), and the compressed
|
||||||
disp8×N displacement, whose multiplier follows the memory operand's size —
|
disp8×N displacement, whose multiplier follows the memory operand's size —
|
||||||
covering every instruction the go-flac and go-lz4 AVX2/AVX-512 kernels use,
|
covering every instruction the go-flac and go-lz4 AVX2/AVX-512 kernels use,
|
||||||
plus the common AVX-512 F/BW integer set and the floating-point and
|
plus the common AVX-512 F/BW integer set, the floating-point and conversion
|
||||||
conversion set (the packed double arithmetic, the scalar SD/SS forms —
|
set (the packed double and single arithmetic, the scalar SD/SS forms —
|
||||||
whose EVEX encodings serve masked and zeroing use — `VMOVDDUP`, and the
|
whose EVEX encodings serve masked and zeroing use — `VMOVDDUP`, the
|
||||||
width-changing conversions, including the `VCVTPD2DQ`/`VCVTTPD2DQ` family
|
replicating moves, and the width-changing conversions, including the
|
||||||
whose length follows the wider source operand). Every encoding is validated two ways: by
|
`VCVTPD2DQ`/`VCVTTPD2DQ` family whose length follows the wider source
|
||||||
|
operand), and the wider AVX-512 set: ternary logic, lane shuffles, inserts
|
||||||
|
and extracts, compares with an opmask destination, the permutes, the
|
||||||
|
expand/compress family, the broadcasts, the opmask-register instructions
|
||||||
|
(KAND/KOR/KXNOR/KADD/KUNPCK/KNOT/KSHIFTL/KORTEST and KMOVQ), the aligned
|
||||||
|
moves and the remaining extending/narrowing moves, the floating-point
|
||||||
|
helper and conversion tail (VRCP14*, VRSQRT14*, VGETEXP*, VGETMANT*,
|
||||||
|
VSCALEF*, VRNDSCALE*, VREDUCE*, VFIXUPIMM*, VRANGE*, VFPCLASS* with an
|
||||||
|
opmask destination, and the VCVT* conversions — signed, unsigned and
|
||||||
|
truncating, including the length-suffixed X/Y spellings and the
|
||||||
|
mask/vector conversions VPMOVM2*/VPMOV*2M), and gather/scatter with VSIB addressing — both the
|
||||||
|
VEX spelling with a vector mask register and the EVEX spelling with an
|
||||||
|
explicit K mask, where the EVEX length follows the VSIB index register,
|
||||||
|
not the data register. The EVEX mnemonic
|
||||||
|
suffixes — rounding modes (.RN_SAE/.RD_SAE/.RU_SAE/.RZ_SAE),
|
||||||
|
suppress-all-exceptions (.SAE) and memory broadcast (.BCST) — set the EVEX
|
||||||
|
b bit and the L'L rounding-control field (broadcast keeps the vector length
|
||||||
|
and scales disp8 by the element size), and combine with the .Z zeroing
|
||||||
|
suffix. Every encoding is validated two ways: by
|
||||||
round-trip decoding through `golang.org/x/arch`, and byte-for-byte against
|
round-trip decoding through `golang.org/x/arch`, and byte-for-byte against
|
||||||
the machine code the real Go assembler emits — a comparison that holds for
|
the machine code the real Go assembler emits — a comparison that holds for
|
||||||
whole functions: all 27 functions of both kernels assemble to exactly the Go
|
whole functions: all 27 functions of both kernels assemble to exactly the Go
|
||||||
@@ -250,10 +268,19 @@ and Mach-O writers (`gasm asm --format elf|macho`) lay the code and data out
|
|||||||
as `.text`/`.data` (or `__text`/`__data`) sections, export a symbol per
|
as `.text`/`.data` (or `__text`/`__data`) sections, export a symbol per
|
||||||
`TEXT` and `GLOBL` (the `<>` ones local, the rest global) and emit one
|
`TEXT` and `GLOBL` (the `<>` ones local, the rest global) and emit one
|
||||||
PC-relative relocation per static-symbol reference — undefined external
|
PC-relative relocation per static-symbol reference — undefined external
|
||||||
symbols included, so the output links with the system toolchain. GOOBJ
|
symbols included, so the output links with the system toolchain. The GOOBJ
|
||||||
emission, the format the Go linker consumes directly, is the remaining
|
emitter (`gasm asm --format goobj`) writes the format the Go linker consumes
|
||||||
piece of Phase 2 (together with the rest of the EVEX set and the other
|
directly: the functions as non-package symbols (the way `cmd/asm` records
|
||||||
architectures).
|
assembly symbols), the `GLOBL` data, one `FuncInfo` per function and the
|
||||||
|
pc-value tables — `pcsp` built from the prologue and epilogue stack
|
||||||
|
boundaries, plus flat `pcfile`, `pcline` and `pcinline` tables — so a
|
||||||
|
gasm-assembled object drops into a `go build` in place of the toolchain's.
|
||||||
|
The object preamble (the version-and-experiment header the linker compares
|
||||||
|
verbatim) is captured from the installed `go tool asm`, so the output is
|
||||||
|
always consistent with the toolchain that links it. External cross-package
|
||||||
|
references and the implicit funcdata/DWARF symbols remain future work (the
|
||||||
|
linker fills the latter's defaults); the rest of Phase 2 is those, the
|
||||||
|
remaining EVEX forms and the other architectures.
|
||||||
|
|
||||||
## Extension points
|
## Extension points
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# gasm-devkit — developer tooling for Go's Plan 9 assembler (GAsm).
|
# gasm-devkit — developer tooling for Go's Plan 9 assembler (GAsm).
|
||||||
|
|
||||||
version := "0.11.0"
|
version := "0.15.0"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@just --list
|
@just --list
|
||||||
|
|||||||
@@ -375,6 +375,11 @@ func parseImmediate(g []token.Token) ast.Immediate {
|
|||||||
if v, ok := tryInt(text); ok {
|
if v, ok := tryInt(text); ok {
|
||||||
imm.Val = v
|
imm.Val = v
|
||||||
imm.HasVal = true
|
imm.HasVal = true
|
||||||
|
} else if u, err := strconv.ParseUint(text, 0, 64); err == nil && !imm.Neg {
|
||||||
|
// Unsigned 64-bit literals (DATA mask<>+8(SB)/8, $0x8000…)
|
||||||
|
// overflow int64; keep the bit pattern.
|
||||||
|
imm.Val = int64(u)
|
||||||
|
imm.HasVal = true
|
||||||
} else {
|
} else {
|
||||||
imm.Float = text
|
imm.Float = text
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user