feat(amd64): LOCK and REP prefixes, literal data pseudo-ops and ADJSP
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -925,3 +925,142 @@ func TestMOVQXMMGroundTruth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPrefixStatements pins LOCK, REP and REPN. go tool asm encodes each as
|
||||
// a standalone one-byte instruction with a PC of its own (F0, F3, F2), not a
|
||||
// prefix field merged into the following instruction, and it validates
|
||||
// nothing about the pairing (LOCK before NOP assembles). The prefixed
|
||||
// atomic and string shapes are the bytes the runtime's own kernels need.
|
||||
func TestPrefixStatements(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
mnem string
|
||||
ops []Operand
|
||||
want string
|
||||
}{
|
||||
{"LOCK", "LOCK", nil, "f0"},
|
||||
{"REP", "REP", nil, "f3"},
|
||||
{"REPN", "REPN", nil, "f2"},
|
||||
// LOCK; CMPXCHGQ AX, (BX)
|
||||
{"LOCK CMPXCHGQ", "CMPXCHGQ", []Operand{AX, Ptr(BX, 0, 8)}, "480fb103"},
|
||||
// REP; MOVSQ
|
||||
{"REP MOVSQ", "MOVSQ", nil, "48a5"},
|
||||
// REPN; MOVSB
|
||||
{"REPN MOVSB", "MOVSB", nil, "a4"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
code, err := Encode(c.mnem, c.ops...)
|
||||
if err != nil {
|
||||
t.Errorf("%s: %v", c.name, err)
|
||||
continue
|
||||
}
|
||||
if got := fmt.Sprintf("%x", code); got != c.want {
|
||||
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
// The prefix statements take no operands, as the toolchain reports for
|
||||
// LOCK AX.
|
||||
if _, err := Encode("LOCK", AX); err == nil {
|
||||
t.Error("LOCK AX assembled, want an error")
|
||||
}
|
||||
if _, err := Encode("REP", Imm(1)); err == nil {
|
||||
t.Error("REP $1 assembled, want an error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDataEmission pins BYTE, WORD, LONG and QUAD: the immediate lands in
|
||||
// the text stream as 1, 2, 4 or 8 little-endian bytes with no opcode
|
||||
// lookup, truncated to the width rather than range-checked (go tool asm
|
||||
// emits FF for BYTE $0x1FF and 45 23 for WORD $0x12345, both silently).
|
||||
func TestDataEmission(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
mnem string
|
||||
imm Imm
|
||||
want string
|
||||
}{
|
||||
{"BYTE", "BYTE", 0x0f, "0f"},
|
||||
{"BYTE negative", "BYTE", -1, "ff"},
|
||||
{"BYTE truncated", "BYTE", 0x1ff, "ff"},
|
||||
{"WORD", "WORD", 0x1234, "3412"},
|
||||
{"WORD negative", "WORD", -1, "ffff"},
|
||||
{"WORD truncated", "WORD", 0x12345, "4523"},
|
||||
{"LONG", "LONG", 0x11223344, "44332211"},
|
||||
{"LONG negative", "LONG", -1, "ffffffff"},
|
||||
{"QUAD", "QUAD", 0x1122334455667788, "8877665544332211"},
|
||||
{"QUAD negative", "QUAD", -2, "feffffffffffffff"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
code, err := Encode(c.mnem, c.imm)
|
||||
if err != nil {
|
||||
t.Errorf("%s: %v", c.name, err)
|
||||
continue
|
||||
}
|
||||
if got := fmt.Sprintf("%x", code); got != c.want {
|
||||
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
// Exactly one immediate: the toolchain rejects BYTE $1, $2, $3, and a
|
||||
// register or a missing operand is no immediate at all.
|
||||
if _, err := Encode("BYTE"); err == nil {
|
||||
t.Error("BYTE with no operand assembled, want an error")
|
||||
}
|
||||
if _, err := Encode("BYTE", Imm(1), Imm(2)); err == nil {
|
||||
t.Error("BYTE $1, $2 assembled, want an error")
|
||||
}
|
||||
if _, err := Encode("WORD", AX); err == nil {
|
||||
t.Error("WORD AX assembled, want an error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestEndIgnored pins END: go tool asm drops the statement entirely, so it
|
||||
// encodes to zero bytes and takes any operands without complaint (the
|
||||
// toolchain accepts END $0 and END AX alike).
|
||||
func TestEndIgnored(t *testing.T) {
|
||||
for _, ops := range [][]Operand{nil, {Imm(0)}, {AX}} {
|
||||
code, err := Encode("END", ops...)
|
||||
if err != nil {
|
||||
t.Errorf("END: %v", err)
|
||||
continue
|
||||
}
|
||||
if len(code) != 0 {
|
||||
t.Errorf("END = %x, want no bytes", code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdjsp pins ADJSP: a positive immediate is SUBQ $imm, SP, a negative
|
||||
// one ADDQ $-imm, SP, in the imm8 or imm32 form the magnitude picks; $0
|
||||
// has no encoding (go tool asm refuses ADJSP $0 outright).
|
||||
func TestAdjsp(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
imm Imm
|
||||
want string
|
||||
}{
|
||||
{"imm8", 112, "4883ec70"},
|
||||
{"imm8 negative", -112, "4883c470"},
|
||||
{"imm32", 200, "4881ecc8000000"},
|
||||
{"imm32 negative", -200, "4881c4c8000000"},
|
||||
{"small", 8, "4883ec08"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
code, err := Encode("ADJSP", c.imm)
|
||||
if err != nil {
|
||||
t.Errorf("%s: %v", c.name, err)
|
||||
continue
|
||||
}
|
||||
if got := fmt.Sprintf("%x", code); got != c.want {
|
||||
t.Errorf("ADJSP %d = %s, want %s", int64(c.imm), got, c.want)
|
||||
}
|
||||
}
|
||||
if _, err := Encode("ADJSP", Imm(0)); err == nil {
|
||||
t.Error("ADJSP $0 assembled, want an error")
|
||||
}
|
||||
if _, err := Encode("ADJSP"); err == nil {
|
||||
t.Error("ADJSP with no operand assembled, want an error")
|
||||
}
|
||||
if _, err := Encode("ADJSP", AX); err == nil {
|
||||
t.Error("ADJSP AX assembled, want an error")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user