feat(asm): encode the amd64 XSAVE family
XSAVE, XSAVEOPT, XSAVEC and XSAVES with their restore twins, plain and 64, each pinned byte for byte against go tool asm through every corpus line the toolchain's own amd64enc.s carries for the family (24 lines). The toolchain emits XSAVEOPT without the manual's 0x66 prefix; the bytes are the oracle, so the family carries none. Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
f83bc8ddef
commit
257feace6e
3 files changed
+131
-1
No files matched your search
+17
-1
@@ -3,6 +3,8 @@
|
||||
|
||||
package asm
|
||||
|
||||
import "strings"
|
||||
|
||||
// encodeAmd64Family routes a mnemonic through the amd64 corpus families the
|
||||
// dedicated amd64_*.go files implement. The first family that owns the name
|
||||
// decides the outcome: its bytes, or its error. A name no family claims
|
||||
@@ -14,6 +16,9 @@ func (e *enc) encodeAmd64Family(upper string, ops []Operand) (bool, error) {
|
||||
if ok, err := e.encodeSystem(upper, ops); ok {
|
||||
return true, err
|
||||
}
|
||||
if ok, err := e.encodeXsave(upper, ops); ok {
|
||||
return true, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -83,5 +88,16 @@ func amd64FamilyEncodable(upper string) bool {
|
||||
if _, ok := farSegLoad[base(upper, 3)]; ok {
|
||||
return true
|
||||
}
|
||||
return upper == "CMPXCHG8B" || upper == "CMPXCHG16B"
|
||||
if upper == "CMPXCHG8B" || upper == "CMPXCHG16B" {
|
||||
return true
|
||||
}
|
||||
if _, ok := xsaveTable[upper]; ok {
|
||||
return true
|
||||
}
|
||||
if before, ok := strings.CutSuffix(upper, "64"); ok {
|
||||
if _, ok := xsaveTable[before]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package asm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// This file implements the XSAVE family: the extended-state save and restore
|
||||
// pair in its four generations (XSAVE/XRSTOR, XSAVEOPT, XSAVEC, XSAVES) and
|
||||
// their 64 spellings. Each takes one memory operand alone; the 64 spellings
|
||||
// carry REX.W. Every encoding here is pinned byte for byte against
|
||||
// go tool asm through the corpus lines in amd64_xsave_test.go.
|
||||
|
||||
// xsaveSpec is one XSAVE family member: the opcode group and the /digit.
|
||||
// XSAVEOPT carries no mandatory prefix in the toolchain's encoding despite
|
||||
// the manual's 0x66, so the family has none anywhere.
|
||||
type xsaveSpec struct {
|
||||
op []byte
|
||||
digit int
|
||||
}
|
||||
|
||||
// xsaveTable maps the save/restore mnemonics to their encodings. The 64
|
||||
// spellings share the base's digit with REX.W.
|
||||
var xsaveTable = map[string]xsaveSpec{
|
||||
"XSAVE": {[]byte{0x0F, 0xAE}, 4},
|
||||
"XRSTOR": {[]byte{0x0F, 0xAE}, 5},
|
||||
"XSAVEOPT": {[]byte{0x0F, 0xAE}, 6},
|
||||
"XSAVEC": {[]byte{0x0F, 0xC7}, 4},
|
||||
"XSAVES": {[]byte{0x0F, 0xC7}, 5},
|
||||
"XRSTORS": {[]byte{0x0F, 0xC7}, 3},
|
||||
}
|
||||
|
||||
// encodeXsave encodes the XSAVE family. It reports whether the mnemonic
|
||||
// belongs to the family.
|
||||
func (e *enc) encodeXsave(upper string, ops []Operand) (bool, error) {
|
||||
name, rexW := upper, false
|
||||
if base, ok := strings.CutSuffix(upper, "64"); ok {
|
||||
name, rexW = base, true
|
||||
}
|
||||
spec, ok := xsaveTable[name]
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
if len(ops) != 1 {
|
||||
return true, fmt.Errorf("%s expects 1 memory operand, got %d", upper, len(ops))
|
||||
}
|
||||
if !isX86Mem(ops[0]) {
|
||||
return true, fmt.Errorf("%s requires a memory operand", upper)
|
||||
}
|
||||
i := &instr{opcode: append([]byte(nil), spec.op...), modrm: -1, sib: -1, rexW: rexW}
|
||||
if err := setRMDigit(i, spec.digit, ops[0], 8); err != nil {
|
||||
return true, err
|
||||
}
|
||||
return true, e.emit(i)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package asm
|
||||
|
||||
import "testing"
|
||||
|
||||
// amd64XsaveCorpus holds every line the Go toolchain's own
|
||||
// amd64enc.s carries for the XSAVE family (XSAVE, XSAVEOPT, XSAVEC, XSAVES and the restores, plain and 64), with the bytes go tool asm
|
||||
// emits for each: the differential ground truth the family is proven
|
||||
// against, line for line.
|
||||
var amd64XsaveCorpus = []struct {
|
||||
line string
|
||||
want string
|
||||
}{
|
||||
{"XRSTOR (BX)", "0f ae 2b"},
|
||||
{"XRSTOR (R11)", "41 0f ae 2b"},
|
||||
{"XRSTOR64 (BX)", "48 0f ae 2b"},
|
||||
{"XRSTOR64 (R11)", "49 0f ae 2b"},
|
||||
{"XRSTORS (BX)", "0f c7 1b"},
|
||||
{"XRSTORS (R11)", "41 0f c7 1b"},
|
||||
{"XRSTORS64 (BX)", "48 0f c7 1b"},
|
||||
{"XRSTORS64 (R11)", "49 0f c7 1b"},
|
||||
{"XSAVE (BX)", "0f ae 23"},
|
||||
{"XSAVE (R11)", "41 0f ae 23"},
|
||||
{"XSAVE64 (BX)", "48 0f ae 23"},
|
||||
{"XSAVE64 (R11)", "49 0f ae 23"},
|
||||
{"XSAVEC (BX)", "0f c7 23"},
|
||||
{"XSAVEC (R11)", "41 0f c7 23"},
|
||||
{"XSAVEC64 (BX)", "48 0f c7 23"},
|
||||
{"XSAVEC64 (R11)", "49 0f c7 23"},
|
||||
{"XSAVEOPT (BX)", "0f ae 33"},
|
||||
{"XSAVEOPT (R11)", "41 0f ae 33"},
|
||||
{"XSAVEOPT64 (BX)", "48 0f ae 33"},
|
||||
{"XSAVEOPT64 (R11)", "49 0f ae 33"},
|
||||
{"XSAVES (BX)", "0f c7 2b"},
|
||||
{"XSAVES (R11)", "41 0f c7 2b"},
|
||||
{"XSAVES64 (BX)", "48 0f c7 2b"},
|
||||
{"XSAVES64 (R11)", "49 0f c7 2b"},
|
||||
}
|
||||
|
||||
// TestAmd64XsaveCorpus assembles every corpus line and requires the same bytes
|
||||
// go tool asm emits for it.
|
||||
func TestAmd64XsaveCorpus(t *testing.T) {
|
||||
for _, tc := range amd64XsaveCorpus {
|
||||
fn := firstText(t, "TEXT ·p(SB), 4, $0\n\t"+tc.line+"\n")
|
||||
code, _, err := Assemble(fn)
|
||||
if err != nil {
|
||||
t.Errorf("%s: %v", tc.line, err)
|
||||
continue
|
||||
}
|
||||
if got := hexBytes(code); got != tc.want {
|
||||
t.Errorf("%s: got %s, want %s", tc.line, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user