feat(arch): add the scaled index to the amd64 memory operands

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 02:06:11 +02:00
1 parent ccb155437e
commit 11cac26508
4 files changed
+183 -44

No files matched your search

+106 -44
View File
@@ -22,9 +22,9 @@
// memory forms beside them, the scalar ones the manual spells m16 and the
// packed ones with the {1toN} broadcast, base-relative operands with the
// ModR/M disp8 and disp32 choices and the SIB byte RSP and R12 demand, the
// broadcast laying EVEX.b over the same displacement semantics. A scaled
// index, write masking ({k1}{z}) and embedded rounding still arrive with a
// later slice.
// scaled index and the broadcast laying the SIB byte and EVEX.b over the
// same displacement semantics. Write masking ({k1}{z}) and embedded
// rounding still arrive with a later slice.
package arch
@@ -198,6 +198,94 @@ func amd64EncodeBroadcast(b []byte, dest, vvvv, base int, disp int64) []byte {
return out
}
// amd64EncodeScaledMemory returns the register-form template with a
// base-plus-scaled-index memory operand filled in: the SIB byte follows the
// ModR/M and carries the scale field, the index and the base, whose number
// rides the r/m field as 100. In a SIB form EVEX.B keeps carrying base bit
// three, as amd64Encode laid it from the base, and EVEX.X changes meaning
// from the register's bit four to the index's bit three, so it clears when
// the index sits above 7. The ModR/M and displacement choices stay the
// canonical ones amd64EncodeMemory makes, with the RBP and R13 bases
// keeping their forced displacement: with a SIB byte present, mod 00 with
// base 101 addresses baseless disp32, never through the base.
func amd64EncodeScaledMemory(b []byte, dest, vvvv, base, index, scale int, disp int64) []byte {
out := amd64Encode(b, dest, vvvv, base)
if index&8 != 0 {
out[1] &^= 0x40
}
rm := base & 7
var tail []byte
mod := byte(0)
switch {
case rm == 5 || disp != 0:
if disp >= -128 && disp <= 127 {
mod = 1
tail = []byte{byte(disp)}
} else {
mod = 2
tail = []byte{byte(disp), byte(disp >> 8), byte(disp >> 16), byte(disp >> 24)}
}
}
sib := byte(rm)
sib |= byte(index&7) << 3
switch scale {
case 2:
sib |= 1 << 6
case 4:
sib |= 2 << 6
case 8:
sib |= 3 << 6
}
tail = append([]byte{sib}, tail...)
out[5] = out[5]&0x38 | mod<<6 | 4
return append(out, tail...)
}
// amd64Index validates the scaled index of a memory operand: a general
// register inside 0-15 and never RSP, whose SIB encoding 100 means no index,
// and a scale the byte multipliers carry.
func (in ExtInstr) amd64Index(op ExtOperand, pos int) (index, scale int, err error) {
if op.Index < 0 || op.Index > 15 {
return 0, 0, fmt.Errorf("%s: operand %d names index register %d, outside 0-15", in.Name, pos, op.Index)
}
if op.Index == 4 {
return 0, 0, fmt.Errorf("%s: operand %d names RSP as the index, which the SIB byte cannot encode", in.Name, pos)
}
switch op.Scale {
case 1, 2, 4, 8:
default:
return 0, 0, fmt.Errorf("%s: operand %d carries a scale of %d, outside the byte multipliers 1, 2, 4 and 8", in.Name, pos, op.Scale)
}
return op.Index, op.Scale, nil
}
// amd64MemBytes encodes one validated memory position: the plain
// base-plus-displacement form, the scaled index over it, and the broadcast
// bit over either, each an additive layer on the same displacement
// semantics. The operand must have passed amd64Memory's kind gate, which
// the encode paths reach only at the entry's Mem position.
func (in ExtInstr) amd64MemBytes(b []byte, dest, vvvv int, op ExtOperand, pos int) ([]byte, error) {
base, disp, err := in.amd64Memory(op, pos)
if err != nil {
return nil, err
}
if op.HasIndex {
index, scale, err := in.amd64Index(op, pos)
if err != nil {
return nil, err
}
out := amd64EncodeScaledMemory(b, dest, vvvv, base, index, scale, disp)
if op.Broadcast {
out[3] |= 0x10
}
return out, nil
}
if op.Broadcast {
return amd64EncodeBroadcast(b, dest, vvvv, base, disp), nil
}
return amd64EncodeMemory(b, dest, vvvv, base, disp), nil
}
// amd64PlainReg checks the invariants every amd64 register operand carries:
// no arm64 arrangement, no predicate qualifier, and a register number inside
// the class the instruction encodes. A broadcast spelling names a memory
@@ -294,17 +382,10 @@ func (in ExtInstr) encodeAmdVec3(ops []ExtOperand) ([]byte, error) {
return nil, err
}
if in.Mem == 2 && ops[1].Kind == ExtMem {
base, disp, err := in.amd64Memory(ops[1], 2)
if err != nil {
return nil, err
}
if err := in.amd64Vector(ops[2], class, 3); err != nil {
return nil, err
}
if ops[1].Broadcast {
return amd64EncodeBroadcast(in.Bytes, ops[2].Reg, ops[0].Reg, base, disp), nil
}
return amd64EncodeMemory(in.Bytes, ops[2].Reg, ops[0].Reg, base, disp), nil
return in.amd64MemBytes(in.Bytes, ops[2].Reg, ops[0].Reg, ops[1], 2)
}
for i, op := range ops[1:] {
if err := in.amd64Vector(op, class, i+2); err != nil {
@@ -320,14 +401,10 @@ func (in ExtInstr) encodeAmdVec3(ops []ExtOperand) ([]byte, error) {
// unused, which the encoding spells as vvvv 1111.
func (in ExtInstr) encodeAmdMemVec(ops []ExtOperand) ([]byte, error) {
class := amd64LengthClass(in.Bytes)
base, disp, err := in.amd64Memory(ops[0], 1)
if err != nil {
return nil, err
}
if err := in.amd64Vector(ops[1], class, 2); err != nil {
return nil, err
}
return amd64EncodeMemory(in.Bytes, ops[1].Reg, -1, base, disp), nil
return in.amd64MemBytes(in.Bytes, ops[1].Reg, -1, ops[0], 1)
}
// encodeAmdVecMem fills the memory-store form: src, mem. VMOVSH 4660(R9),
@@ -339,11 +416,7 @@ func (in ExtInstr) encodeAmdVecMem(ops []ExtOperand) ([]byte, error) {
if err := in.amd64Vector(ops[0], class, 1); err != nil {
return nil, err
}
base, disp, err := in.amd64Memory(ops[1], 2)
if err != nil {
return nil, err
}
return amd64EncodeMemory(in.Bytes, ops[0].Reg, -1, base, disp), nil
return in.amd64MemBytes(in.Bytes, ops[0].Reg, -1, ops[1], 2)
}
// encodeAmdVec2 fills the two-vector form: src, dest. The half form narrows
@@ -360,27 +433,22 @@ func (in ExtInstr) encodeAmdVec2(ops []ExtOperand) ([]byte, error) {
destClass = amd64HalfClass(class)
}
if in.Mem == 1 && ops[0].Kind == ExtMem {
base, disp, err := in.amd64Memory(ops[0], 1)
if err != nil {
// The memory spelling is validated before the destination: on the
// half form the destination's narrowed class is the likelier
// rejection, but a miswritten source spelling names itself first.
if _, _, err := in.amd64Memory(ops[0], 1); err != nil {
return nil, err
}
if err := in.amd64Vector(ops[1], destClass, 2); err != nil {
return nil, err
}
if ops[0].Broadcast {
return amd64EncodeBroadcast(in.Bytes, ops[1].Reg, -1, base, disp), nil
}
return amd64EncodeMemory(in.Bytes, ops[1].Reg, -1, base, disp), nil
return in.amd64MemBytes(in.Bytes, ops[1].Reg, -1, ops[0], 1)
}
if in.Mem == 2 && ops[1].Kind == ExtMem {
if err := in.amd64Vector(ops[0], class, 1); err != nil {
return nil, err
}
base, disp, err := in.amd64Memory(ops[1], 2)
if err != nil {
return nil, err
}
return amd64EncodeMemory(in.Bytes, ops[0].Reg, -1, base, disp), nil
return in.amd64MemBytes(in.Bytes, ops[0].Reg, -1, ops[1], 2)
}
if err := in.amd64Vector(ops[0], class, 1); err != nil {
return nil, err
@@ -449,14 +517,13 @@ func (in ExtInstr) encodeAmdVec3Imm(ops []ExtOperand) ([]byte, error) {
return nil, err
}
if in.Mem == 3 && ops[2].Kind == ExtMem {
base, disp, err := in.amd64Memory(ops[2], 3)
if err != nil {
return nil, err
}
if err := in.amd64Vector(ops[3], class, 4); err != nil {
return nil, err
}
out := amd64EncodeMemory(in.Bytes, ops[3].Reg, ops[1].Reg, base, disp)
out, err := in.amd64MemBytes(in.Bytes, ops[3].Reg, ops[1].Reg, ops[2], 3)
if err != nil {
return nil, err
}
return append(out, imm), nil
}
if err := in.amd64Vector(ops[2], class, 3); err != nil {
@@ -489,11 +556,10 @@ func (in ExtInstr) encodeAmdMask2Imm(ops []ExtOperand) ([]byte, error) {
}
var out []byte
if in.Mem == 3 && ops[2].Kind == ExtMem {
base, disp, err := in.amd64Memory(ops[2], 3)
out, err := in.amd64MemBytes(in.Bytes, ops[3].Reg, ops[1].Reg, ops[2], 3)
if err != nil {
return nil, err
}
out = amd64EncodeMemory(in.Bytes, ops[3].Reg, ops[1].Reg, base, disp)
return append(out, imm), nil
}
if err := in.amd64Vector(ops[2], class, 3); err != nil {
@@ -543,14 +609,10 @@ func (in ExtInstr) encodeAmdVecGprVec(ops []ExtOperand) ([]byte, error) {
return nil, err
}
if in.Mem == 2 && ops[1].Kind == ExtMem {
base, disp, err := in.amd64Memory(ops[1], 2)
if err != nil {
return nil, err
}
if err := in.amd64Vector(ops[2], class, 3); err != nil {
return nil, err
}
return amd64EncodeMemory(in.Bytes, ops[2].Reg, ops[0].Reg, base, disp), nil
return in.amd64MemBytes(in.Bytes, ops[2].Reg, ops[0].Reg, ops[1], 2)
}
if err := in.amd64Gpr(ops[1], 2); err != nil {
return nil, err