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:
petrbalvin committed 2026-10-06 23:59:47 +02:00
1 parent f83bc8ddef
commit 257feace6e
3 files changed
+131 -1

No files matched your search

+58
View File
@@ -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)
}