fix(arm64): store-exclusive operand order and large-frame parity
Assisted-by: GLM 5.3
This commit is contained in:
+60
-18
@@ -314,7 +314,8 @@ func encodeARM64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi arm64
|
||||
return encodeARM64CRC32(mnem, enc.op, ops)
|
||||
}
|
||||
|
||||
// Exclusive load/store (LDXR, STXR, LDAXR, STLXR).
|
||||
// Exclusive load/store (LDXR, STXR, LDAXR, STLXR and the register-pair
|
||||
// forms LDXP, STXP).
|
||||
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FExcl {
|
||||
return encodeARM64Excl(mnem, enc.op, ops)
|
||||
}
|
||||
@@ -771,14 +772,17 @@ func encodeARM64LoadImm(rd int, v int64, mnem string) ([]byte, error) {
|
||||
return a64wordLE(op | 31<<16 | 31<<5 | uint32(rd)), nil
|
||||
}
|
||||
|
||||
// The Go toolchain classifies immediates:
|
||||
// - C_ABCON0 (0 < v ≤ 4095): bitmask first for positive values
|
||||
// - Negative values: MOVN first, then bitmask
|
||||
// - C_MOVCON (movcon-eligible, outside ABCON range): MOVZ/MOVN first
|
||||
tryBitmaskFirst := d > 0 && d <= 0xFFF
|
||||
// The Go toolchain classifies immediates (asm7.go conclass):
|
||||
// - inside the imm12/shifted-imm12 "addcon" band (C_ABCON0/C_ABCON,
|
||||
// 0 < v ≤ 4095 or a 4096 multiple up to 0xFFF000): bitmask first, so
|
||||
// `MOVD $4096, R27` is ORR $4096, not MOVZ $(1<<12)
|
||||
// - outside that band: MOVZ/MOVN first (C_MOVCON before C_BITCON), and
|
||||
// negative values reach MOVN before the bitmask test
|
||||
tryBitmaskFirst := d > 0 && (d <= 0xFFF || (d&0xFFF == 0 && d <= 0xFFF000))
|
||||
|
||||
if tryBitmaskFirst {
|
||||
// Small immediate: try bitmask first (Go uses ORR for values like $1, $256).
|
||||
// Addcon-band immediate: try bitmask first (Go uses ORR for values
|
||||
// like $1, $256 and $65536).
|
||||
N, immr, imms, ok := arm64Bitmask(uint64(d), int(sf))
|
||||
if ok {
|
||||
return a64wordLE(sf<<31 | 1<<29 | 0x24<<23 | N<<22 | immr<<16 | imms<<10 | 31<<5 | uint32(rd)), nil
|
||||
@@ -1354,14 +1358,43 @@ func arm64ExclMem(mnem string, op *ast.Operand) (int, error) {
|
||||
return rn, nil
|
||||
}
|
||||
|
||||
// encodeARM64Excl encodes an exclusive load/store instruction.
|
||||
// LDXR (Rn), Rt → LDXR Rt, [Rn] (2 operands: mem, reg)
|
||||
// STXR Rs, (Rn), Rt → STXR Rs, Rt, [Rn] (3 operands: Rs, mem, Rt-status)
|
||||
// arm64PairOf parses a register-pair operand `(R1, R2)`, reporting false
|
||||
// when the operand is not a pair. The toolchain takes the second register of
|
||||
// the pair from the operand's Offset (its C_PAIR class,
|
||||
// cmd/internal/obj/arm64/asm7.go cases 58/59).
|
||||
func arm64PairOf(op *ast.Operand) (int, int, bool) {
|
||||
raw := strings.TrimSpace(op.Raw)
|
||||
if !strings.HasPrefix(raw, "(") || !strings.HasSuffix(raw, ")") {
|
||||
return -1, -1, false
|
||||
}
|
||||
parts := strings.Split(raw[1:len(raw)-1], ",")
|
||||
if len(parts) != 2 {
|
||||
return -1, -1, false
|
||||
}
|
||||
r1 := arm64RegNum(strings.TrimSpace(parts[0]))
|
||||
r2 := arm64RegNum(strings.TrimSpace(parts[1]))
|
||||
if r1 < 0 || r2 < 0 {
|
||||
return -1, -1, false
|
||||
}
|
||||
return r1, r2, true
|
||||
}
|
||||
|
||||
// encodeARM64Excl encodes the exclusive load/store family with the operand
|
||||
// order the toolchain parses (cmd/internal/obj/arm64/asm7.go cases 58 and 59,
|
||||
// and its own spellings in arm64enc.s):
|
||||
//
|
||||
// STXR Rt, (Rn), Rs store, single register
|
||||
// STXP (Rt1, Rt2), (Rn), Rs store, register pair
|
||||
// LDXR (Rn), Rt load, single register
|
||||
// LDXP (Rn), (Rt1, Rt2) load, register pair
|
||||
//
|
||||
// Decoded toolchain evidence: `STXR R1, (R2), R3` assembles to 0xc8037c41,
|
||||
// whose fields are Rs=3, Rn=2, Rt=1: the FIRST register operand is the data
|
||||
// register and the LAST the status register.
|
||||
func encodeARM64Excl(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||
// LDXR/STXR have different operand forms.
|
||||
isLoad := strings.HasPrefix(mnem, "LD")
|
||||
if isLoad {
|
||||
// LDXR (Rn), Rt → 2 operands: mem, reg
|
||||
// LDXR (Rn), Rt / LDXP (Rn), (Rt1, Rt2): 2 operands.
|
||||
if len(ops) != 2 {
|
||||
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
@@ -1369,25 +1402,34 @@ func encodeARM64Excl(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, er
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rt1, rt2, ok := arm64PairOf(ops[1]); ok {
|
||||
// The single-register opcodes pre-set the unused Rs (bits 20:16)
|
||||
// and Rt2 (bits 14:10) fields to 31; the pair forms carry a real
|
||||
// Rt2 and keep Rs at 31.
|
||||
return a64wordLE(baseOp | 0x1F<<16 | uint32(rt2)<<10 | uint32(rn)<<5 | uint32(rt1)), nil
|
||||
}
|
||||
rt := arm64RegNum(operandRegName(ops[1]))
|
||||
if rt < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
}
|
||||
return a64wordLE(baseOp | uint32(rn)<<5 | uint32(rt)), nil
|
||||
}
|
||||
// STXR Rs, (Rn), Rt → 3 operands: Rs, mem, Rt
|
||||
// STXR Rt, (Rn), Rs / STXP (Rt1, Rt2), (Rn), Rs: 3 operands.
|
||||
if len(ops) != 3 {
|
||||
return nil, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
rs := arm64RegNum(operandRegName(ops[0]))
|
||||
if rs < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
}
|
||||
rn, err := arm64ExclMem(mnem, ops[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rt := arm64RegNum(operandRegName(ops[2]))
|
||||
rs := arm64RegNum(operandRegName(ops[2]))
|
||||
if rs < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
}
|
||||
if rt1, rt2, ok := arm64PairOf(ops[0]); ok {
|
||||
return a64wordLE(baseOp | uint32(rs)<<16 | uint32(rt2)<<10 | uint32(rn)<<5 | uint32(rt1)), nil
|
||||
}
|
||||
rt := arm64RegNum(operandRegName(ops[0]))
|
||||
if rt < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user