rexcode/arm32: MVE VHCADD (saturating halving complex add) + VCMLA

New MVE_ROT_HCADD (#90/#270 at bit12) and MVE_ROT_CMLA (#0/90/180/270 at
bits 24:23) rotation encodings -- the rotation degrees round-trip
properly (unlike the existing FCMA VCMLA which leaves it unencoded). One
form each with the element-size bits left variable (MVE convention).
Verify round-trips; all rotations byte-exact vs llvm-mc; 600 tests green.

(VMLSV/VMLSVA reduce ops deferred: their format decode-collides with
other MVE encodings given the 4-bit VN_Q vs MVE's 3-bit Qn.)
This commit is contained in:
Brendan Punsky
2026-06-18 01:47:44 -04:00
committed by Flāvius
parent 55463b6719
commit 239dea4f55
15 changed files with 721 additions and 682 deletions

View File

@@ -352,6 +352,10 @@ unpack_operand :: proc(word: u32, enc: Operand_Encoding, ot: Operand_Type) -> Op
lane = (word >> 21) & 1
}
return op_dpr_lane(Register(REG_DPR | u16(n)), u8(lane))
case .MVE_ROT_HCADD:
return op_imm(((word >> 12) & 1) == 1 ? 270 : 90)
case .MVE_ROT_CMLA:
return op_imm(i64((word >> 23) & 0x3) * 90)
case .VD_Q:
n := (((word >> 22) & 1) << 4 | ((word >> 12) & 0xF)) >> 1
return op_reg(Register(REG_QPR | u16(n)))

View File

@@ -436,6 +436,10 @@ pack_operand_inline :: #force_inline proc(
v |= (l & 1) << 21
}
return v
case .MVE_ROT_HCADD:
return (u32(op.immediate) == 270 ? 1 : 0) << 12
case .MVE_ROT_CMLA:
return ((u32(op.immediate) / 90) & 0x3) << 23
case .VFP_IMM8:
// Run the VFP 8-bit float encoder; the user supplies the wire-format
// 32-bit float bit pattern (for F32). The encoder finds the abcdefgh.

View File

@@ -312,6 +312,10 @@ Operand_Encoding :: enum u8 {
VMOV_LANE_8,
VMOV_LANE_16,
VMOV_LANE_32,
// MVE complex-op rotation immediate (user passes degrees):
// MVE_ROT_HCADD: #90/#270 -> bit 12; MVE_ROT_CMLA: #0/90/180/270 -> bits 24:23
MVE_ROT_HCADD,
MVE_ROT_CMLA,
VFP_IMM8, // VFP immediate (VMOV.F32/F64 #imm)
NEON_IMM8_ABCDEFGH, // bits 18-16 (abc) + bits 3-0 (defgh)
NEON_CMODE, // bits 11-8 (cmode for VMOV/VMVN immediate)

View File

@@ -1388,6 +1388,10 @@ inst_vqrdmlsdh_q_q_q :: #force_inline proc "contextless" (dst: Regis
emit_vqrdmlsdh_q_q_q :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Register, src: Register, src2: Register) { append(instructions, inst_vqrdmlsdh_q_q_q(dst, src, src2)) }
inst_vqrdmlsdhx_q_q_q :: #force_inline proc "contextless" (dst: Register, src: Register, src2: Register) -> Instruction { return Instruction{mnemonic = .VQRDMLSDHX, operand_count = 3, mode = .T32, cond = 14, length = 4, ops = {op_reg(dst), op_reg(src), op_reg(src2), {}}} }
emit_vqrdmlsdhx_q_q_q :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Register, src: Register, src2: Register) { append(instructions, inst_vqrdmlsdhx_q_q_q(dst, src, src2)) }
inst_vhcadd_sat_q_q_q_imm :: #force_inline proc "contextless" (dst: Register, src: Register, src2: Register, imm: i64) -> Instruction { return Instruction{mnemonic = .VHCADD_SAT, operand_count = 4, mode = .T32, cond = 14, length = 4, ops = {op_reg(dst), op_reg(src), op_reg(src2), op_imm(imm)}} }
emit_vhcadd_sat_q_q_q_imm :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Register, src: Register, src2: Register, imm: i64) { append(instructions, inst_vhcadd_sat_q_q_q_imm(dst, src, src2, imm)) }
inst_vcmla_mve_q_q_q_imm :: #force_inline proc "contextless" (dst: Register, src: Register, src2: Register, imm: i64) -> Instruction { return Instruction{mnemonic = .VCMLA_MVE, operand_count = 4, mode = .T32, cond = 14, length = 4, ops = {op_reg(dst), op_reg(src), op_reg(src2), op_imm(imm)}} }
emit_vcmla_mve_q_q_q_imm :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Register, src: Register, src2: Register, imm: i64) { append(instructions, inst_vcmla_mve_q_q_q_imm(dst, src, src2, imm)) }
inst_vldrb_q_mem :: #force_inline proc "contextless" (dst: Register, src: Memory) -> Instruction { return Instruction{mnemonic = .VLDRB, operand_count = 2, mode = .T32, cond = 14, length = 4, ops = {op_reg(dst), op_mem(src), {}, {}}} }
emit_vldrb_q_mem :: #force_inline proc(instructions: ^[dynamic]Instruction, dst: Register, src: Memory) { append(instructions, inst_vldrb_q_mem(dst, src)) }
inst_vldrh_q_mem :: #force_inline proc "contextless" (dst: Register, src: Memory) -> Instruction { return Instruction{mnemonic = .VLDRH, operand_count = 2, mode = .T32, cond = 14, length = 4, ops = {op_reg(dst), op_mem(src), {}, {}}} }
@@ -2637,6 +2641,10 @@ inst_vqrdmlsdh :: inst_vqrdmlsdh_q_q_q
emit_vqrdmlsdh :: emit_vqrdmlsdh_q_q_q
inst_vqrdmlsdhx :: inst_vqrdmlsdhx_q_q_q
emit_vqrdmlsdhx :: emit_vqrdmlsdhx_q_q_q
inst_vhcadd_sat :: inst_vhcadd_sat_q_q_q_imm
emit_vhcadd_sat :: emit_vhcadd_sat_q_q_q_imm
inst_vcmla_mve :: inst_vcmla_mve_q_q_q_imm
emit_vcmla_mve :: emit_vcmla_mve_q_q_q_imm
inst_vldrb :: inst_vldrb_q_mem
emit_vldrb :: emit_vldrb_q_mem
inst_vldrh :: inst_vldrh_q_mem

View File

@@ -3688,6 +3688,14 @@ ENCODING_TABLE := #partial [Mnemonic][]Encoding{
{.VMOV_LANE, {.DPR_ELEM, .GPR, .NONE, .NONE}, {.VMOV_LANE_32, .RT_A32, .NONE, .NONE}, 0x0E000B10, 0x0FD00F7F, .VFPV2, .A32, {}},
},
// MVE (M-profile vector) multiply-subtract-across-vector reduce, halving
// complex add (saturating), and complex multiply-accumulate. T32; the
// complex ops carry a rotation immediate (degrees).
// One form per mnemonic with the element-size bits left variable in the
// mask (the MVE convention); the complex ops encode the rotation immediate.
.VHCADD_SAT = { {.VHCADD_SAT, {.QPR, .QPR, .QPR, .IMM}, {.VD_Q, .VN_Q, .VM_Q, .MVE_ROT_HCADD}, 0xEE000F00, 0xFFE10FF1, .MVE_INT, .T32, {thumb32=true, cond_in_28=false}} },
.VCMLA_MVE = { {.VCMLA_MVE, {.QPR, .QPR, .QPR, .IMM}, {.VD_Q, .VN_Q, .VM_Q, .MVE_ROT_CMLA}, 0xFC200840, 0xFE611FF1, .MVE_FP, .T32, {thumb32=true, cond_in_28=false}} },
// SPECGEN:BEGIN
.VADDL = {
{.VADDL, {.QPR, .DPR, .DPR, .NONE}, {.VD_Q, .VN_D, .VM_D, .NONE}, 0xF2800000, 0xFFB01F50, .NEON, .A32, {cond_in_28=false}},

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ package rexcode_arm32_generated
import lib "../.."
@(rodata)
ENCODE_FORMS := [1671]lib.Encoding{
ENCODE_FORMS := [1673]lib.Encoding{
// .AND
{ .AND, {.GPR,.GPR,.IMM_MOD,.NONE}, {.RD,.RN_A32,.A32_IMM_MOD,.NONE}, 0x02000000, 0x0FE00000, .BASE, .A32, {} },
{ .AND, {.GPR,.GPR,.GPR_SHIFTED,.NONE}, {.RD,.RN_A32,.RM_A32,.NONE}, 0x00000000, 0x0FE00010, .BASE, .A32, {} },
@@ -2262,6 +2262,10 @@ ENCODE_FORMS := [1671]lib.Encoding{
{ .VQRDMLSDH, {.QPR,.QPR,.QPR,.NONE}, {.VD_Q,.VN_Q,.VM_Q,.NONE}, 0xFE000E01, 0xFF811F51, .MVE_INT, .T32, {thumb32=true} },
// .VQRDMLSDHX
{ .VQRDMLSDHX, {.QPR,.QPR,.QPR,.NONE}, {.VD_Q,.VN_Q,.VM_Q,.NONE}, 0xFE001E01, 0xFF811F51, .MVE_INT, .T32, {thumb32=true} },
// .VHCADD_SAT
{ .VHCADD_SAT, {.QPR,.QPR,.QPR,.IMM}, {.VD_Q,.VN_Q,.VM_Q,.MVE_ROT_HCADD}, 0xEE000F00, 0xFFE10FF1, .MVE_INT, .T32, {thumb32=true} },
// .VCMLA_MVE
{ .VCMLA_MVE, {.QPR,.QPR,.QPR,.IMM}, {.VD_Q,.VN_Q,.VM_Q,.MVE_ROT_CMLA}, 0xFC200840, 0xFE611FF1, .MVE_FP, .T32, {thumb32=true} },
// .VLDRB
{ .VLDRB, {.QPR,.MEM,.NONE,.NONE}, {.VD_Q,.MEM_IMM12_OFFSET,.NONE,.NONE}, 0xED901E00, 0xFFB01F00, .MVE_INT, .T32, {thumb32=true} },
// .VLDRH
@@ -2918,27 +2922,27 @@ ENCODE_RUNS := [lib.Mnemonic]lib.Encode_Run{
.VQRDMLSDH = { 1649, 1},
.VQRDMLSDHX = { 1650, 1},
.VPRINT = { 1651, 0},
.VHCADD_SAT = { 1651, 0},
.VCMLA_MVE = { 1651, 0},
.VLDRB = { 1651, 1},
.VLDRH = { 1652, 1},
.VLDRW = { 1653, 1},
.VLDRD = { 1654, 1},
.VSTRB = { 1655, 1},
.VSTRH = { 1656, 1},
.VSTRW = { 1657, 1},
.VSTRD = { 1658, 1},
.VLD20 = { 1659, 1},
.VLD21 = { 1660, 1},
.VLD40 = { 1661, 1},
.VLD41 = { 1662, 1},
.VLD42 = { 1663, 1},
.VLD43 = { 1664, 1},
.VST20 = { 1665, 1},
.VST21 = { 1666, 1},
.VST40 = { 1667, 1},
.VST41 = { 1668, 1},
.VST42 = { 1669, 1},
.VST43 = { 1670, 1},
._COUNT = { 1671, 0},
.VHCADD_SAT = { 1651, 1},
.VCMLA_MVE = { 1652, 1},
.VLDRB = { 1653, 1},
.VLDRH = { 1654, 1},
.VLDRW = { 1655, 1},
.VLDRD = { 1656, 1},
.VSTRB = { 1657, 1},
.VSTRH = { 1658, 1},
.VSTRW = { 1659, 1},
.VSTRD = { 1660, 1},
.VLD20 = { 1661, 1},
.VLD21 = { 1662, 1},
.VLD40 = { 1663, 1},
.VLD41 = { 1664, 1},
.VLD42 = { 1665, 1},
.VLD43 = { 1666, 1},
.VST20 = { 1667, 1},
.VST21 = { 1668, 1},
.VST40 = { 1669, 1},
.VST41 = { 1670, 1},
.VST42 = { 1671, 1},
.VST43 = { 1672, 1},
._COUNT = { 1673, 0},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB