rexcode/arm32: the width and saturate fields hold one less than they name

SSAT, SSAT16, USAT and USAT16 encode their saturate position minus one,
and SBFX and UBFX their width minus one, so a field of zero means one.
All of them printed the raw field, which is not a value the instruction
can take -- `ssat r0, #0, r0` is not assemblable.

BFI and BFC are different again: their field is the top bit's position,
and the width the syntax wants is msb - lsb + 1. They shared an encoding
with SBFX and UBFX, which need the opposite arithmetic, so they now have
their own. Packing an msb needs the lsb from a sibling operand, so the
packer takes the instruction rather than one operand in isolation.

Also worth recording: the sweep had been running llvm-mc with
`-mattr=+all`, which that target does not recognise and silently
ignores, so every CRC32, FP16, v8.1a and dot-product entry looked like a
reserved encoding. With the features actually enabled, 815 of 1139 A32
entries round-trip, and 38 rather than 143 are genuinely reserved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UmHLRF11EoWwNWCJ7JGaA
This commit is contained in:
Brendan Punsky
2026-08-29 00:23:24 -04:00
committed by Flāvius
parent d1cb08b72f
commit 32ef068223
8 changed files with 39 additions and 21 deletions

View File

@@ -529,8 +529,15 @@ unpack_operand :: proc(word: u32, enc: Operand_Encoding, ot: Operand_Type) -> Op
return op_imm(i64((word >> 18) & 0xF))
// ---- Saturate / bit field ----
case .SAT_IMM5, .SAT_IMM5_T32, .BFI_MSB:
return op_imm(i64((word >> 16) & 0x1F))
case .SAT_IMM5, .SAT_IMM5_T32:
// The field holds one less than the saturate position it names.
return op_imm(i64(((word >> 16) & 0x1F) + 1))
case .BFX_WIDTH:
// One less than the width.
return op_imm(i64(((word >> 16) & 0x1F) + 1))
case .BFI_MSB:
// The top bit's position; the syntax wants the width.
return op_imm(i64(((word >> 16) & 0x1F) - ((word >> 7) & 0x1F) + 1))
case .BFI_LSB, .BFI_LSB_T32:
return op_imm(i64((word >> 7) & 0x1F))
case .NEON_SHIFT_IMM6:

View File

@@ -202,10 +202,10 @@ encode_one_inline :: #force_inline proc(
word = (word & 0x0FFFFFFF) | (u32(inst.cond) << 28)
}
if form.enc[0] != .NONE { word |= pack_operand_inline(&inst.ops[0], form.enc[0], pc, inst_idx, relocs, form) }
if form.enc[1] != .NONE { word |= pack_operand_inline(&inst.ops[1], form.enc[1], pc, inst_idx, relocs, form) }
if form.enc[2] != .NONE { word |= pack_operand_inline(&inst.ops[2], form.enc[2], pc, inst_idx, relocs, form) }
if form.enc[3] != .NONE { word |= pack_operand_inline(&inst.ops[3], form.enc[3], pc, inst_idx, relocs, form) }
if form.enc[0] != .NONE { word |= pack_operand_inline(&inst.ops[0], form.enc[0], pc, inst_idx, relocs, form, inst) }
if form.enc[1] != .NONE { word |= pack_operand_inline(&inst.ops[1], form.enc[1], pc, inst_idx, relocs, form, inst) }
if form.enc[2] != .NONE { word |= pack_operand_inline(&inst.ops[2], form.enc[2], pc, inst_idx, relocs, form, inst) }
if form.enc[3] != .NONE { word |= pack_operand_inline(&inst.ops[3], form.enc[3], pc, inst_idx, relocs, form, inst) }
return word, inst_size_from_bits(form.bits, form.mode), true
}
@@ -334,6 +334,8 @@ pack_operand_inline :: #force_inline proc(
inst_idx: u16,
relocs: ^[dynamic]Relocation,
form: ^Encoding,
// BFI's msb is lsb + width - 1, so packing it needs a sibling operand.
inst: ^Instruction,
) -> u32 {
switch enc {
case .NONE, .IMPL:
@@ -631,9 +633,15 @@ pack_operand_inline :: #force_inline proc(
case .IT_MASK: return u32(op.immediate) & 0xFF
case .CPS_IFLAGS: return u32(op.immediate) & 0x1FF
case .HINT_FIELD: return u32(op.immediate) & 0xFF
case .SAT_IMM5, .SAT_IMM5_T32:
return (u32(op.immediate) & 0x1F) << 16
case .BFI_MSB: return (u32(op.immediate) & 0x1F) << 16
case .SAT_IMM5, .SAT_IMM5_T32, .BFX_WIDTH:
return ((u32(op.immediate) - 1) & 0x1F) << 16
case .BFI_MSB:
// msb = lsb + width - 1; the lsb rides in whichever slot carries it.
lsb: u32 = 0
for e, k in form.enc {
if e == .BFI_LSB || e == .BFI_LSB_T32 { lsb = u32(inst.ops[k].immediate); break }
}
return ((lsb + u32(op.immediate) - 1) & 0x1F) << 16
case .BFI_LSB, .BFI_LSB_T32:
return (u32(op.immediate) & 0x1F) << 7
case .NEON_SHIFT_IMM6: return (u32(op.immediate) & 0x3F) << 16

View File

@@ -400,6 +400,9 @@ Operand_Encoding :: enum u8 {
SAT_IMM5_T32, // Thumb-2 saturate amount
// ---- BFC/BFI/SBFX/UBFX ----
// SBFX/UBFX hold the width less one, where BFI/BFC hold the top bit's
// position and the width is msb - lsb + 1.
BFX_WIDTH,
BFI_MSB, // bits 20-16 (msb position)
BFI_LSB, // bits 11-7 (lsb position; also shift_imm slot)
BFI_LSB_T32, // Thumb-2 BFI lsb (different layout)

View File

@@ -754,14 +754,14 @@ ENCODING_TABLE := #partial [Mnemonic][]Encoding{
{.BFI, {.GPR, .GPR, .IMM5, .IMM5_W}, {.RD_T32, .RN_T32, .BFI_LSB_T32, .BFI_MSB}, 0xF3600000, 0xFFF08000, .V6T2, .T32, {thumb32=true, cond_in_28=false}, {}},
},
.SBFX = {
{.SBFX, {.GPR, .GPR, .IMM5, .IMM5_W}, {.RD, .RM_A32, .BFI_LSB, .BFI_MSB}, 0x07A00050, 0x0FE00070, .V6T2, .A32, {}, {}},
{.SBFX, {.GPR, .GPR, .IMM5, .IMM5_W}, {.RD, .RM_A32, .BFI_LSB, .BFX_WIDTH}, 0x07A00050, 0x0FE00070, .V6T2, .A32, {}, {}},
// T32 SBFX
{.SBFX, {.GPR, .GPR, .IMM5, .IMM5_W}, {.RD_T32, .RN_T32, .BFI_LSB_T32, .BFI_MSB}, 0xF3400000, 0xFFF08000, .V6T2, .T32, {thumb32=true, cond_in_28=false}, {}},
{.SBFX, {.GPR, .GPR, .IMM5, .IMM5_W}, {.RD_T32, .RN_T32, .BFI_LSB_T32, .BFX_WIDTH}, 0xF3400000, 0xFFF08000, .V6T2, .T32, {thumb32=true, cond_in_28=false}, {}},
},
.UBFX = {
{.UBFX, {.GPR, .GPR, .IMM5, .IMM5_W}, {.RD, .RM_A32, .BFI_LSB, .BFI_MSB}, 0x07E00050, 0x0FE00070, .V6T2, .A32, {}, {}},
{.UBFX, {.GPR, .GPR, .IMM5, .IMM5_W}, {.RD, .RM_A32, .BFI_LSB, .BFX_WIDTH}, 0x07E00050, 0x0FE00070, .V6T2, .A32, {}, {}},
// T32 UBFX
{.UBFX, {.GPR, .GPR, .IMM5, .IMM5_W}, {.RD_T32, .RN_T32, .BFI_LSB_T32, .BFI_MSB}, 0xF3C00000, 0xFFF08000, .V6T2, .T32, {thumb32=true, cond_in_28=false}, {}},
{.UBFX, {.GPR, .GPR, .IMM5, .IMM5_W}, {.RD_T32, .RN_T32, .BFI_LSB_T32, .BFX_WIDTH}, 0xF3C00000, 0xFFF08000, .V6T2, .T32, {thumb32=true, cond_in_28=false}, {}},
},
// =========================================================================

View File

@@ -979,10 +979,10 @@ DECODE_ENTRIES := [1649]lib.Decode_Entry{
{ .LDRB, {.GPR,.MEM,.NONE,.NONE}, {.RT_A32,.MEM_REG_OFFSET,.NONE,.NONE}, 0x07500000, 0x0F700010, .BASE, .A32, {}, {.NONE,.NONE} },
{ .USAD8, {.GPR,.GPR,.GPR,.NONE}, {.RN_A32,.RM_A32,.RS_A32,.NONE}, 0x0780F010, 0x0FF0F0F0, .V6, .A32, {}, {.NONE,.NONE} },
{ .USADA8, {.GPR,.GPR,.GPR,.GPR}, {.RN_A32,.RM_A32,.RS_A32,.RD}, 0x07800010, 0x0FF000F0, .V6, .A32, {}, {.NONE,.NONE} },
{ .SBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD,.RM_A32,.BFI_LSB,.BFI_MSB}, 0x07A00050, 0x0FE00070, .V6T2, .A32, {}, {.NONE,.NONE} },
{ .SBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD,.RM_A32,.BFI_LSB,.BFX_WIDTH}, 0x07A00050, 0x0FE00070, .V6T2, .A32, {}, {.NONE,.NONE} },
{ .BFC, {.GPR,.IMM5,.IMM5_W,.NONE}, {.RD,.BFI_LSB,.BFI_MSB,.NONE}, 0x07C0001F, 0x0FE0007F, .V6T2, .A32, {}, {.NONE,.NONE} },
{ .BFI, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD,.RM_A32,.BFI_LSB,.BFI_MSB}, 0x07C00010, 0x0FE00070, .V6T2, .A32, {}, {.NONE,.NONE} },
{ .UBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD,.RM_A32,.BFI_LSB,.BFI_MSB}, 0x07E00050, 0x0FE00070, .V6T2, .A32, {}, {.NONE,.NONE} },
{ .UBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD,.RM_A32,.BFI_LSB,.BFX_WIDTH}, 0x07E00050, 0x0FE00070, .V6T2, .A32, {}, {.NONE,.NONE} },
{ .UDF, {.IMM,.NONE,.NONE,.NONE}, {.NONE,.NONE,.NONE,.NONE}, 0xE7F000F0, 0xFFF000F0, .BASE, .A32, {}, {.NONE,.NONE} },
{ .STMDA, {.GPR,.GPR_LIST,.NONE,.NONE}, {.RN_A32,.A32_REG_LIST,.NONE,.NONE}, 0x08000000, 0x0FD00000, .BASE, .A32, {}, {.NONE,.NONE} },
{ .RFE, {.GPR,.NONE,.NONE,.NONE}, {.RN_A32,.NONE,.NONE,.NONE}, 0xF8100A00, 0xFE10FFFF, .V6, .A32, {}, {.NONE,.NONE} },
@@ -1407,8 +1407,8 @@ DECODE_ENTRIES := [1649]lib.Decode_Entry{
{ .USAT16, {.GPR,.IMM4_SAT,.GPR,.NONE}, {.RD_T32,.SAT_IMM5_T32,.RN_T32,.NONE}, 0xF3A00000, 0xFFF0F0F0, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
{ .BFC, {.GPR,.IMM5,.IMM5_W,.NONE}, {.RD_T32,.BFI_LSB_T32,.BFI_MSB,.NONE}, 0xF36F0000, 0xFFFF8000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
{ .BFI, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD_T32,.RN_T32,.BFI_LSB_T32,.BFI_MSB}, 0xF3600000, 0xFFF08000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
{ .SBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD_T32,.RN_T32,.BFI_LSB_T32,.BFI_MSB}, 0xF3400000, 0xFFF08000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
{ .UBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD_T32,.RN_T32,.BFI_LSB_T32,.BFI_MSB}, 0xF3C00000, 0xFFF08000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
{ .SBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD_T32,.RN_T32,.BFI_LSB_T32,.BFX_WIDTH}, 0xF3400000, 0xFFF08000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
{ .UBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD_T32,.RN_T32,.BFI_LSB_T32,.BFX_WIDTH}, 0xF3C00000, 0xFFF08000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
{ .SSAT, {.GPR,.IMM4_SAT,.GPR_SHIFTED,.NONE}, {.RD_T32,.SAT_IMM5_T32,.RN_T32,.NONE}, 0xF3000000, 0xFFD08020, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
{ .USAT, {.GPR,.IMM4_SAT,.GPR_SHIFTED,.NONE}, {.RD_T32,.SAT_IMM5_T32,.RN_T32,.NONE}, 0xF3800000, 0xFFD08020, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
{ .MOVW, {.GPR,.IMM16_LO_HI,.NONE,.NONE}, {.RD_T32,.NONE,.NONE,.NONE}, 0xF2400000, 0xFBF08000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },

View File

@@ -209,11 +209,11 @@ ENCODE_FORMS := [1649]lib.Encoding{
{ .BFI, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD,.RM_A32,.BFI_LSB,.BFI_MSB}, 0x07C00010, 0x0FE00070, .V6T2, .A32, {}, {.NONE,.NONE} },
{ .BFI, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD_T32,.RN_T32,.BFI_LSB_T32,.BFI_MSB}, 0xF3600000, 0xFFF08000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
// .SBFX
{ .SBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD,.RM_A32,.BFI_LSB,.BFI_MSB}, 0x07A00050, 0x0FE00070, .V6T2, .A32, {}, {.NONE,.NONE} },
{ .SBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD_T32,.RN_T32,.BFI_LSB_T32,.BFI_MSB}, 0xF3400000, 0xFFF08000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
{ .SBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD,.RM_A32,.BFI_LSB,.BFX_WIDTH}, 0x07A00050, 0x0FE00070, .V6T2, .A32, {}, {.NONE,.NONE} },
{ .SBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD_T32,.RN_T32,.BFI_LSB_T32,.BFX_WIDTH}, 0xF3400000, 0xFFF08000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
// .UBFX
{ .UBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD,.RM_A32,.BFI_LSB,.BFI_MSB}, 0x07E00050, 0x0FE00070, .V6T2, .A32, {}, {.NONE,.NONE} },
{ .UBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD_T32,.RN_T32,.BFI_LSB_T32,.BFI_MSB}, 0xF3C00000, 0xFFF08000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
{ .UBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD,.RM_A32,.BFI_LSB,.BFX_WIDTH}, 0x07E00050, 0x0FE00070, .V6T2, .A32, {}, {.NONE,.NONE} },
{ .UBFX, {.GPR,.GPR,.IMM5,.IMM5_W}, {.RD_T32,.RN_T32,.BFI_LSB_T32,.BFX_WIDTH}, 0xF3C00000, 0xFFF08000, .V6T2, .T32, {thumb32=true}, {.NONE,.NONE} },
// .SXTB
{ .SXTB, {.GPR,.GPR,.NONE,.NONE}, {.RD,.RM_A32,.NONE,.NONE}, 0x06AF0070, 0x0FFF0070, .V6, .A32, {}, {.NONE,.NONE} },
{ .SXTB, {.GPR_LOW,.GPR_LOW,.NONE,.NONE}, {.RD_T16_LO,.RM_T16_LO,.NONE,.NONE}, 0x0000B240, 0x0000FFC0, .V6, .T32, {}, {.NONE,.NONE} },