From 910b51c6b232da074b262d16b115e90018cedfc0 Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Tue, 24 Feb 2026 11:55:58 +0000 Subject: [PATCH 01/10] Add several missing instructions Note that paired vmov_double_r2v instructions are destructive, but that this is not currently tracked. --- slothy/targets/arm_v81m/arch_v81m.py | 50 +++++++++++++++++++++++++ slothy/targets/arm_v81m/cortex_m55r1.py | 36 ++++++++++++++++++ slothy/targets/arm_v81m/cortex_m85r1.py | 37 ++++++++++++++++++ 3 files changed, 123 insertions(+) diff --git a/slothy/targets/arm_v81m/arch_v81m.py b/slothy/targets/arm_v81m/arch_v81m.py index b2cf68d58..c5748b9e6 100644 --- a/slothy/targets/arm_v81m/arch_v81m.py +++ b/slothy/targets/arm_v81m/arch_v81m.py @@ -1041,6 +1041,35 @@ class eor_shifted(MVEInstruction): inputs = ["Rn", "Rm"] outputs = ["Rd"] +class bic(MVEInstruction): + pattern = "bic , , " + inputs = ["Rn", "Rm"] + outputs = ["Rd"] + +class bic_ror(MVEInstruction): + pattern = "bic , , , ror " + inputs = ["Rn", "Rm"] + outputs = ["Rd"] + +class ror(MVEInstruction): + pattern = "ror , , " + inputs = ["Rn", "Rm"] + outputs = ["Rd"] + +class ror_imm(MVEInstruction): + pattern = "ror , , " + inputs = ["Rn"] + outputs = ["Rd"] + +class cmp_reg(MVEInstruction): + pattern = "cmp , " + inputs = ["Rn", "Rm"] + modifiesFlags = True + +class cmp_imm(MVEInstruction): + pattern = "cmp , " + inputs = ["Rn", "Rm"] + modifiesFlags = True class sub(MVEInstruction): pattern = "sub , , " @@ -1295,6 +1324,17 @@ def make(cls, src): obj.addr = obj.args_in_out[0] return obj +class str_reg(MVEInstruction): + pattern = "str , [, ]" + inputs = ["Rn", "Rt"] + + @classmethod + def make(cls, src): + obj = MVEInstruction.build(cls, src) + obj.increment = None + obj.pre_index = obj.immediate + obj.addr = obj.args_in[0] + return obj class vrshr(MVEInstruction): pattern = "vrshr.
, , " @@ -1348,6 +1388,12 @@ class vmov_double_v2r(MVEInstruction): outputs = ["Rt0", "Rt1"] +class vmov_double_r2v(MVEInstruction): + pattern = "vmov [], [], , " + inputs = ["Rt0", "Rt1"] + inouts = ["Qd", "Qa"] + + class mov(MVEInstruction): pattern = "mov , " inputs = ["Rm"] @@ -1447,6 +1493,10 @@ class vsli(MVEInstruction): inputs = ["Qm"] in_outs = ["Qd"] +class vsri(MVEInstruction): + pattern = "vsri.
, , " + inputs = ["Qm"] + in_outs = ["Qd"] class vmovlb(MVEInstruction): pattern = "vmovlb.
, " diff --git a/slothy/targets/arm_v81m/cortex_m55r1.py b/slothy/targets/arm_v81m/cortex_m55r1.py index 8ac154073..70b25d1bf 100644 --- a/slothy/targets/arm_v81m/cortex_m55r1.py +++ b/slothy/targets/arm_v81m/cortex_m55r1.py @@ -44,6 +44,7 @@ ldr, ldr_with_writeback, ldr_with_post, + str_reg, mov_imm, mvn_imm, mov, @@ -56,6 +57,12 @@ orr_shifted, eor, eor_shifted, + bic, + bic_ror, + ror, + ror_imm, + cmp_reg, + cmp_imm, sub, pkhbt_shifted, add_imm, @@ -72,6 +79,7 @@ vshllb, vshllt, vsli, + vsri, vmovlb, vmovlt, vrev16, @@ -81,6 +89,7 @@ vmov_imm, vmov_vector, vmov_double_v2r, + vmov_double_r2v, vadd_sv, vadd_vv, vsub, @@ -320,6 +329,12 @@ def get_min_max_objective(slothy): orr_shifted: ExecutionUnit.SCALAR, eor: ExecutionUnit.SCALAR, eor_shifted: ExecutionUnit.SCALAR, + bic: ExecutionUnit.SCALAR, + bic_ror: ExecutionUnit.SCALAR, + ror: ExecutionUnit.SCALAR, + ror_imm: ExecutionUnit.SCALAR, + cmp_reg: ExecutionUnit.SCALAR, + cmp_imm: ExecutionUnit.SCALAR, sub: ExecutionUnit.SCALAR, pkhbt_shifted: ExecutionUnit.SCALAR, add_imm: ExecutionUnit.SCALAR, @@ -343,6 +358,7 @@ def get_min_max_objective(slothy): vshllb: ExecutionUnit.VEC_INT, vshllt: ExecutionUnit.VEC_INT, vsli: ExecutionUnit.VEC_INT, + vsri: ExecutionUnit.VEC_INT, vmovlb: ExecutionUnit.VEC_INT, vmovlt: ExecutionUnit.VEC_INT, vrev16: ExecutionUnit.VEC_INT, @@ -352,6 +368,7 @@ def get_min_max_objective(slothy): vmov_imm: [ExecutionUnit.VEC_INT, ExecutionUnit.VEC_MUL], vmov_vector: [ExecutionUnit.VEC_INT, ExecutionUnit.VEC_MUL], vmov_double_v2r: [ExecutionUnit.VEC_INT, ExecutionUnit.VEC_MUL], + vmov_double_r2v: [ExecutionUnit.VEC_INT, ExecutionUnit.VEC_MUL], vadd_sv: ExecutionUnit.VEC_INT, vadd_vv: ExecutionUnit.VEC_INT, vsub: ExecutionUnit.VEC_INT, @@ -396,6 +413,7 @@ def get_min_max_objective(slothy): strd: ExecutionUnit.STORE, strd_with_writeback: ExecutionUnit.STORE, strd_with_post: ExecutionUnit.STORE, + str_reg: ExecutionUnit.STORE, restored: ExecutionUnit.STACK, restore: ExecutionUnit.STACK, saved: ExecutionUnit.STACK, @@ -477,6 +495,12 @@ def get_min_max_objective(slothy): orr_shifted, eor, eor_shifted, + bic, + bic_ror, + ror, + ror_imm, + cmp_reg, + cmp_imm, sub, pkhbt_shifted, add_imm, @@ -484,6 +508,7 @@ def get_min_max_objective(slothy): sub_imm, vmov_imm, vmov_double_v2r, + vmov_double_r2v, ldr, ldr_with_writeback, ldr_with_post, @@ -499,6 +524,7 @@ def get_min_max_objective(slothy): strd, strd_with_writeback, strd_with_post, + str_reg, restored, restore, saved, @@ -525,6 +551,7 @@ def get_min_max_objective(slothy): vshllb, vshllt, vsli, + vsri, vmovlb, vmovlt, vrev16, @@ -653,6 +680,11 @@ def get_min_max_objective(slothy): log_and, orr, eor, + bic, + ror, + ror_imm, + cmp_reg, + cmp_imm, sub, pkhbt_shifted, add_imm, @@ -669,6 +701,7 @@ def get_min_max_objective(slothy): vmov_imm, vmov_vector, vmov_double_v2r, + vmov_double_r2v, vadd_vv, vadd_sv, vsub, @@ -740,6 +773,7 @@ def get_min_max_objective(slothy): eor_shifted, orr_shifted, log_and_shifted, + bic_ror, ): 2, # NOTE: latency would be 1 if shift amount is 0 in m55 (vld20, vld21): 2, (vld20_with_writeback, vld21_with_writeback): 2, @@ -758,6 +792,7 @@ def get_min_max_objective(slothy): vshllb, vshllt, vsli, + vsri, vmovlb, vmovlt, vmulh, @@ -881,6 +916,7 @@ def get_latency(src, out_idx, dst): vshllb, vshllt, vsli, + vsri, vmovlb, vmovlt, vmulh, diff --git a/slothy/targets/arm_v81m/cortex_m85r1.py b/slothy/targets/arm_v81m/cortex_m85r1.py index d7d503b98..ed1649d43 100644 --- a/slothy/targets/arm_v81m/cortex_m85r1.py +++ b/slothy/targets/arm_v81m/cortex_m85r1.py @@ -44,6 +44,7 @@ ldr, ldr_with_writeback, ldr_with_post, + str_reg, mov_imm, mvn_imm, mov, @@ -56,6 +57,12 @@ orr_shifted, eor, eor_shifted, + bic, + bic_ror, + ror, + ror_imm, + cmp_reg, + cmp_imm, sub, pkhbt_shifted, add_imm, @@ -72,6 +79,7 @@ vshllb, vshllt, vsli, + vsri, vmovlb, vmovlt, vrev16, @@ -81,6 +89,7 @@ vmov_imm, vmov_vector, vmov_double_v2r, + vmov_double_r2v, vadd_sv, vadd_vv, vsub, @@ -314,6 +323,12 @@ def get_min_max_objective(slothy): orr_shifted: ExecutionUnit.SCALAR, eor: ExecutionUnit.SCALAR, eor_shifted: ExecutionUnit.SCALAR, + bic: ExecutionUnit.SCALAR, + bic_ror: ExecutionUnit.SCALAR, + ror: ExecutionUnit.SCALAR, + ror_imm: ExecutionUnit.SCALAR, + cmp_reg: ExecutionUnit.SCALAR, + cmp_imm: ExecutionUnit.SCALAR, sub: ExecutionUnit.SCALAR, pkhbt_shifted: ExecutionUnit.SCALAR, and_imm: ExecutionUnit.SCALAR, @@ -337,6 +352,7 @@ def get_min_max_objective(slothy): vshllb: ExecutionUnit.VEC_SHFT, vshllt: ExecutionUnit.VEC_SHFT, vsli: ExecutionUnit.VEC_SHFT, + vsri: ExecutionUnit.VEC_SHFT, vmovlb: ExecutionUnit.VEC_VMOVLN, vmovlt: ExecutionUnit.VEC_VMOVLN, vrev16: [ExecutionUnit.VEC_BITWA, ExecutionUnit.VEC_BITWB], @@ -346,6 +362,7 @@ def get_min_max_objective(slothy): vmov_imm: [ExecutionUnit.VEC_VMOVA, ExecutionUnit.VEC_VMOVB], vmov_vector: [ExecutionUnit.VEC_VMOVA, ExecutionUnit.VEC_VMOVB], vmov_double_v2r: [ExecutionUnit.VEC_VMOVA, ExecutionUnit.VEC_VMOVB], + vmov_double_r2v: [ExecutionUnit.VEC_VMOVA, ExecutionUnit.VEC_VMOVB], vadd_sv: ExecutionUnit.VEC_INT, vadd_vv: ExecutionUnit.VEC_INT, vcadd: ExecutionUnit.VEC_INT, @@ -398,6 +415,7 @@ def get_min_max_objective(slothy): strd: ExecutionUnit.STORE, strd_with_writeback: ExecutionUnit.STORE, strd_with_post: ExecutionUnit.STORE, + str_reg: ExecutionUnit.STORE, restored: ExecutionUnit.STACK_LD, restore: ExecutionUnit.STACK_LD, saved: ExecutionUnit.STACK_ST, @@ -481,6 +499,12 @@ def get_min_max_objective(slothy): orr_shifted, eor, eor_shifted, + bic, + bic_ror, + ror, + ror_imm, + cmp_reg, + cmp_imm, sub, pkhbt_shifted, add_imm, @@ -489,6 +513,7 @@ def get_min_max_objective(slothy): vmov_imm, vmov_vector, vmov_double_v2r, + vmov_double_r2v, ldr, ldr_with_writeback, ldr_with_post, @@ -504,6 +529,7 @@ def get_min_max_objective(slothy): strd, strd_with_writeback, strd_with_post, + str_reg, restored, restore, and_imm, @@ -530,6 +556,7 @@ def get_min_max_objective(slothy): vshllb, vshllt, vsli, + vsri, vmovlb, vmovlt, vrev16, @@ -653,6 +680,12 @@ def get_min_max_objective(slothy): orr_shifted, eor, eor_shifted, + bic, + bic_ror, + ror, + ror_imm, + cmp_reg, + cmp_imm, sub, pkhbt_shifted, add_imm, @@ -669,6 +702,7 @@ def get_min_max_objective(slothy): vmov_imm, vmov_vector, vmov_double_v2r, + vmov_double_r2v, vadd_vv, vadd_sv, vcadd, @@ -747,6 +781,7 @@ def get_min_max_objective(slothy): vshllb, vshllt, vsli, + vsri, vmovlb, vmovlt, vmulh, @@ -808,6 +843,7 @@ def get_latency(src, out_idx, dst): eor_shifted, orr_shifted, log_and_shifted, + bic_ror, ] and dst.args_in[1] in src.args_out ): @@ -924,6 +960,7 @@ def get_latency(src, out_idx, dst): vshllb, vshllt, vsli, + vsri vmovlb, vmovlt, vmulh, From 52a670f3ea0d9c37958722c8fc9cd185f1fd1d2f Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Tue, 24 Feb 2026 14:48:32 +0000 Subject: [PATCH 02/10] Address review comments --- slothy/targets/arm_v81m/arch_v81m.py | 4 ++-- slothy/targets/arm_v81m/cortex_m55r1.py | 8 ++++---- slothy/targets/arm_v81m/cortex_m85r1.py | 10 +++++----- tests/naive/armv8m/instructions.s | 13 +++++++++++++ 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/slothy/targets/arm_v81m/arch_v81m.py b/slothy/targets/arm_v81m/arch_v81m.py index c5748b9e6..69ca5a6e6 100644 --- a/slothy/targets/arm_v81m/arch_v81m.py +++ b/slothy/targets/arm_v81m/arch_v81m.py @@ -1046,8 +1046,8 @@ class bic(MVEInstruction): inputs = ["Rn", "Rm"] outputs = ["Rd"] -class bic_ror(MVEInstruction): - pattern = "bic , , , ror " +class bic_shifted(MVEInstruction): + pattern = "bic , , , " inputs = ["Rn", "Rm"] outputs = ["Rd"] diff --git a/slothy/targets/arm_v81m/cortex_m55r1.py b/slothy/targets/arm_v81m/cortex_m55r1.py index 70b25d1bf..538ea18ed 100644 --- a/slothy/targets/arm_v81m/cortex_m55r1.py +++ b/slothy/targets/arm_v81m/cortex_m55r1.py @@ -58,7 +58,7 @@ eor, eor_shifted, bic, - bic_ror, + bic_shifted, ror, ror_imm, cmp_reg, @@ -330,7 +330,7 @@ def get_min_max_objective(slothy): eor: ExecutionUnit.SCALAR, eor_shifted: ExecutionUnit.SCALAR, bic: ExecutionUnit.SCALAR, - bic_ror: ExecutionUnit.SCALAR, + bic_shifted: ExecutionUnit.SCALAR, ror: ExecutionUnit.SCALAR, ror_imm: ExecutionUnit.SCALAR, cmp_reg: ExecutionUnit.SCALAR, @@ -496,7 +496,7 @@ def get_min_max_objective(slothy): eor, eor_shifted, bic, - bic_ror, + bic_shifted, ror, ror_imm, cmp_reg, @@ -773,7 +773,7 @@ def get_min_max_objective(slothy): eor_shifted, orr_shifted, log_and_shifted, - bic_ror, + bic_shifted, ): 2, # NOTE: latency would be 1 if shift amount is 0 in m55 (vld20, vld21): 2, (vld20_with_writeback, vld21_with_writeback): 2, diff --git a/slothy/targets/arm_v81m/cortex_m85r1.py b/slothy/targets/arm_v81m/cortex_m85r1.py index ed1649d43..7ce2871ed 100644 --- a/slothy/targets/arm_v81m/cortex_m85r1.py +++ b/slothy/targets/arm_v81m/cortex_m85r1.py @@ -58,7 +58,7 @@ eor, eor_shifted, bic, - bic_ror, + bic_shifted, ror, ror_imm, cmp_reg, @@ -324,7 +324,7 @@ def get_min_max_objective(slothy): eor: ExecutionUnit.SCALAR, eor_shifted: ExecutionUnit.SCALAR, bic: ExecutionUnit.SCALAR, - bic_ror: ExecutionUnit.SCALAR, + bic_shifted: ExecutionUnit.SCALAR, ror: ExecutionUnit.SCALAR, ror_imm: ExecutionUnit.SCALAR, cmp_reg: ExecutionUnit.SCALAR, @@ -500,7 +500,7 @@ def get_min_max_objective(slothy): eor, eor_shifted, bic, - bic_ror, + bic_shifted, ror, ror_imm, cmp_reg, @@ -681,7 +681,7 @@ def get_min_max_objective(slothy): eor, eor_shifted, bic, - bic_ror, + bic_shifted, ror, ror_imm, cmp_reg, @@ -843,7 +843,7 @@ def get_latency(src, out_idx, dst): eor_shifted, orr_shifted, log_and_shifted, - bic_ror, + bic_shifted, ] and dst.args_in[1] in src.args_out ): diff --git a/tests/naive/armv8m/instructions.s b/tests/naive/armv8m/instructions.s index c2b42c0c2..0c53d84f4 100644 --- a/tests/naive/armv8m/instructions.s +++ b/tests/naive/armv8m/instructions.s @@ -22,6 +22,8 @@ ldr r1, [r13, #8] ldr r0, [sp, #4] ldr r1, [r13, 8] +str r0, [sp, #4] + vmulh.u8 q2, q0, q1 vmulh.u16 q2, q0, q1 vmulh.u32 q2, q0, q1 @@ -151,6 +153,8 @@ vdup.u32 q0, r0 vmov r0, r1, q0[2], q0[0] vmov r0, r1, q0[3], q0[1] +vmov q0[2], q0[0], r0, r1 +vmov q0[3], q0[1], r0, r1 mov r0, #16 @@ -419,6 +423,15 @@ lsl r5, r5, #2 eor r0, r1, r2 eor r5, r5, r8, lsl #7 +bic r0, r1, r2 +bic r5, r5, r8, lsl #7 + +ror r0, r1, r2 +ror r5, r5, #2 +cmp r0, r1 +cmp r0, #2 + + ldrb r0, [r0, #16] ldrb r0, [r0], #16 From ce971bf5cd0c0d8c6031688eaeca3e9f1ea3ea7a Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Tue, 24 Feb 2026 15:03:59 +0000 Subject: [PATCH 03/10] Fix linting --- slothy/targets/arm_v81m/arch_v81m.py | 13 ++++++++++++- slothy/targets/arm_v81m/cortex_m85r1.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/slothy/targets/arm_v81m/arch_v81m.py b/slothy/targets/arm_v81m/arch_v81m.py index 69ca5a6e6..a30dfebac 100644 --- a/slothy/targets/arm_v81m/arch_v81m.py +++ b/slothy/targets/arm_v81m/arch_v81m.py @@ -1041,36 +1041,43 @@ class eor_shifted(MVEInstruction): inputs = ["Rn", "Rm"] outputs = ["Rd"] + class bic(MVEInstruction): pattern = "bic , , " inputs = ["Rn", "Rm"] outputs = ["Rd"] + class bic_shifted(MVEInstruction): pattern = "bic , , , " inputs = ["Rn", "Rm"] outputs = ["Rd"] + class ror(MVEInstruction): pattern = "ror , , " inputs = ["Rn", "Rm"] outputs = ["Rd"] + class ror_imm(MVEInstruction): pattern = "ror , , " inputs = ["Rn"] outputs = ["Rd"] + class cmp_reg(MVEInstruction): pattern = "cmp , " inputs = ["Rn", "Rm"] modifiesFlags = True + class cmp_imm(MVEInstruction): pattern = "cmp , " inputs = ["Rn", "Rm"] modifiesFlags = True + class sub(MVEInstruction): pattern = "sub , , " inputs = ["Rn", "Rm"] @@ -1324,6 +1331,7 @@ def make(cls, src): obj.addr = obj.args_in_out[0] return obj + class str_reg(MVEInstruction): pattern = "str , [, ]" inputs = ["Rn", "Rt"] @@ -1336,6 +1344,7 @@ def make(cls, src): obj.addr = obj.args_in[0] return obj + class vrshr(MVEInstruction): pattern = "vrshr.
, , " inputs = ["Qm"] @@ -1391,7 +1400,7 @@ class vmov_double_v2r(MVEInstruction): class vmov_double_r2v(MVEInstruction): pattern = "vmov [], [], , " inputs = ["Rt0", "Rt1"] - inouts = ["Qd", "Qa"] + in_outs = ["Qd", "Qa"] class mov(MVEInstruction): @@ -1493,11 +1502,13 @@ class vsli(MVEInstruction): inputs = ["Qm"] in_outs = ["Qd"] + class vsri(MVEInstruction): pattern = "vsri.
, , " inputs = ["Qm"] in_outs = ["Qd"] + class vmovlb(MVEInstruction): pattern = "vmovlb.
, " inputs = ["Qm"] diff --git a/slothy/targets/arm_v81m/cortex_m85r1.py b/slothy/targets/arm_v81m/cortex_m85r1.py index 7ce2871ed..1d3983aa9 100644 --- a/slothy/targets/arm_v81m/cortex_m85r1.py +++ b/slothy/targets/arm_v81m/cortex_m85r1.py @@ -960,7 +960,7 @@ def get_latency(src, out_idx, dst): vshllb, vshllt, vsli, - vsri + vsri, vmovlb, vmovlt, vmulh, From 7d21f3344dc5ba4dd674815e237ee447f948e9fa Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Tue, 24 Feb 2026 15:48:02 +0000 Subject: [PATCH 04/10] Fix error in cmp --- slothy/targets/arm_v81m/arch_v81m.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slothy/targets/arm_v81m/arch_v81m.py b/slothy/targets/arm_v81m/arch_v81m.py index a30dfebac..a61319547 100644 --- a/slothy/targets/arm_v81m/arch_v81m.py +++ b/slothy/targets/arm_v81m/arch_v81m.py @@ -1074,7 +1074,7 @@ class cmp_reg(MVEInstruction): class cmp_imm(MVEInstruction): pattern = "cmp , " - inputs = ["Rn", "Rm"] + inputs = ["Rn"] modifiesFlags = True From 14e9826a4224e3b8ced6a0ddadd159d4ca879b5e Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Wed, 25 Feb 2026 11:40:22 +0000 Subject: [PATCH 05/10] Add vmov double r2v pair tracking Still does not pass instruction test. --- slothy/targets/arm_v81m/arch_v81m.py | 48 ++++++++++++++++++++++++++++ tests/naive/armv8m/instructions.s | 1 + 2 files changed, 49 insertions(+) diff --git a/slothy/targets/arm_v81m/arch_v81m.py b/slothy/targets/arm_v81m/arch_v81m.py index a61319547..01e6144c7 100644 --- a/slothy/targets/arm_v81m/arch_v81m.py +++ b/slothy/targets/arm_v81m/arch_v81m.py @@ -1402,6 +1402,12 @@ class vmov_double_r2v(MVEInstruction): inputs = ["Rt0", "Rt1"] in_outs = ["Qd", "Qa"] + @classmethod + def make(cls, src): + obj = MVEInstruction.build(cls, src) + obj.detected_vmov_double_r2v_pair = False + return obj + class mov(MVEInstruction): pattern = "mov , " @@ -2721,6 +2727,48 @@ def core(inst, t, log=None): vqdmlsdh.global_parsing_cb = vqdmlsdh_vqdmladhx_parsing_cb(vqdmlsdh, vqdmladhx) vqdmladhx.global_parsing_cb = vqdmlsdh_vqdmladhx_parsing_cb(vqdmladhx, vqdmlsdh) +def vmov_double_r2v_parsing_cb(this_class): + def core(inst, t, log=None): + assert isinstance(inst, this_class) + succ = None + if inst.detected_vmov_double_r2v_pair: + return False + # Check if this is the first in a pair of vmov_double_r2v + if len(t.dst_in_out[0]) == 1: + r = t.dst_in_out[0][0] + if isinstance(r.inst, this_class): + if ( + r.inst.args_in_out == inst.args_in_out + and r.inst.args_in == inst.args_in + ): + succ = r + + if succ is None: + return False + + # If so, mark in/out as output only, and signal the need for re-building + # the dataflow graph + inst.num_out = 1 + inst.args_out = [inst.args_in_out[0]] + inst.arg_types_out = [RegisterType.MVE] + inst.args_out_restrictions = inst.args_in_out_restrictions + inst.outputs = inst.in_outs + inst.pattern_outputs = inst.pattern_in_outs + + inst.num_in_out = 0 + inst.args_in_out = [] + inst.in_outs = [] + inst.pattern_in_outs = [] + inst.arg_types_in_out = [] + inst.args_in_out_restrictions = [] + + inst.detected_vmov_double_r2v_pair = True + return True + + return core + + +vmov_double_r2v.global_parsing_cb = vmov_double_r2v_parsing_cb(vmov_double_r2v) # Returns the list of all subclasses of a class which don't have # subclasses themselves diff --git a/tests/naive/armv8m/instructions.s b/tests/naive/armv8m/instructions.s index 0c53d84f4..940d166f9 100644 --- a/tests/naive/armv8m/instructions.s +++ b/tests/naive/armv8m/instructions.s @@ -23,6 +23,7 @@ ldr r0, [sp, #4] ldr r1, [r13, 8] str r0, [sp, #4] +str r1, [r13, #4] vmulh.u8 q2, q0, q1 vmulh.u16 q2, q0, q1 From bb7089a2505240a89e6f010b4a20e858abb08eb4 Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Mon, 27 Apr 2026 10:47:07 +0100 Subject: [PATCH 06/10] Fix dual vmov_r2v grouping --- slothy/targets/arm_v81m/arch_v81m.py | 53 ++++++++++++++++++---------- tests/naive/armv8m/instructions.s | 4 +-- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/slothy/targets/arm_v81m/arch_v81m.py b/slothy/targets/arm_v81m/arch_v81m.py index 01e6144c7..e8e7cbb20 100644 --- a/slothy/targets/arm_v81m/arch_v81m.py +++ b/slothy/targets/arm_v81m/arch_v81m.py @@ -2730,30 +2730,45 @@ def core(inst, t, log=None): def vmov_double_r2v_parsing_cb(this_class): def core(inst, t, log=None): assert isinstance(inst, this_class) - succ = None if inst.detected_vmov_double_r2v_pair: return False - # Check if this is the first in a pair of vmov_double_r2v - if len(t.dst_in_out[0]) == 1: - r = t.dst_in_out[0][0] - if isinstance(r.inst, this_class): - if ( - r.inst.args_in_out == inst.args_in_out - and r.inst.args_in == inst.args_in - ): - succ = r + # Find the earliest successor (by original program order) that is another + # vmov_double_r2v with identical in/outs. Here we allow + # multiple dependants and select the nearest matching successor. + + def _is_match(node): + return ( + isinstance(node.inst, this_class) + and node.inst.args_in_out == inst.args_in_out + # Note: inputs need not match; we only care that the pair + # jointly overwrites the same Q register(s). + ) + + # Consider dependants from any in/out operand + deps = [d for dep_list in t.dst_in_out for d in dep_list] + + # Prefer successors that come later in source order + later_matches = [ + d for d in deps if _is_match(d) and isinstance(d.id, int) and isinstance(t.id, int) and d.id > t.id + ] + succ = min(later_matches, key=lambda n: n.id) if later_matches else None + + # Fallback for unusual cases where ids aren't integers if succ is None: + any_matches = [d for d in deps if _is_match(d)] + succ = any_matches[0] if any_matches else None + + if succ is None and False: return False - - # If so, mark in/out as output only, and signal the need for re-building - # the dataflow graph - inst.num_out = 1 - inst.args_out = [inst.args_in_out[0]] - inst.arg_types_out = [RegisterType.MVE] - inst.args_out_restrictions = inst.args_in_out_restrictions - inst.outputs = inst.in_outs - inst.pattern_outputs = inst.pattern_in_outs + + # Mark both in/out operands as outputs-only and trigger a dataflow rebuild + inst.num_out = len(inst.args_in_out) + inst.args_out = list(inst.args_in_out) + inst.arg_types_out = [RegisterType.MVE for _ in inst.args_in_out] + inst.args_out_restrictions = list(inst.args_in_out_restrictions) + inst.outputs = list(inst.in_outs) + inst.pattern_outputs = list(inst.pattern_in_outs) inst.num_in_out = 0 inst.args_in_out = [] diff --git a/tests/naive/armv8m/instructions.s b/tests/naive/armv8m/instructions.s index 940d166f9..f3bcda8e6 100644 --- a/tests/naive/armv8m/instructions.s +++ b/tests/naive/armv8m/instructions.s @@ -154,8 +154,8 @@ vdup.u32 q0, r0 vmov r0, r1, q0[2], q0[0] vmov r0, r1, q0[3], q0[1] -vmov q0[2], q0[0], r0, r1 -vmov q0[3], q0[1], r0, r1 +vmov q1[2], q1[0], r0, r1 +vmov q1[3], q1[1], r2, r3 mov r0, #16 From 07831841670ffbd190fe1f5db27ca623eb72003a Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Thu, 30 Apr 2026 10:03:47 +0100 Subject: [PATCH 07/10] Fix vmov double r2v linting --- slothy/targets/arm_v81m/arch_v81m.py | 100 ++++++++++++++++++--------- 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/slothy/targets/arm_v81m/arch_v81m.py b/slothy/targets/arm_v81m/arch_v81m.py index e8e7cbb20..58719282a 100644 --- a/slothy/targets/arm_v81m/arch_v81m.py +++ b/slothy/targets/arm_v81m/arch_v81m.py @@ -2728,56 +2728,88 @@ def core(inst, t, log=None): vqdmladhx.global_parsing_cb = vqdmlsdh_vqdmladhx_parsing_cb(vqdmladhx, vqdmlsdh) def vmov_double_r2v_parsing_cb(this_class): + def mark_outputs_only(inst): + inst.num_out = len(inst.args_in_out) + inst.args_out = list(inst.args_in_out) + inst.arg_types_out = [RegisterType.MVE for _ in inst.args_in_out] + inst.args_out_restrictions = list(inst.args_in_out_restrictions) + inst.outputs = list(inst.in_outs) + inst.pattern_outputs = list(inst.pattern_in_outs) + + inst.num_in_out = 0 + inst.args_in_out = [] + inst.in_outs = [] + inst.pattern_in_outs = [] + inst.arg_types_in_out = [] + inst.args_in_out_restrictions = [] + + inst.detected_vmov_double_r2v_pair = True + def core(inst, t, log=None): + # Special-case two back-to-back vmov r2v that jointly overwrite q*. + # Conditions: + # - Both vmovs target the same vector register + # (Qd==Qa, and equal across the pair) + # - The two vmovs jointly cover all lanes {0,1,2,3} + assert isinstance(inst, this_class) - if inst.detected_vmov_double_r2v_pair: + if getattr(inst, "detected_vmov_double_r2v_pair", False): return False - # Find the earliest successor (by original program order) that is another - # vmov_double_r2v with identical in/outs. Here we allow - # multiple dependants and select the nearest matching successor. - def _is_match(node): - return ( - isinstance(node.inst, this_class) - and node.inst.args_in_out == inst.args_in_out - # Note: inputs need not match; we only care that the pair - # jointly overwrites the same Q register(s). - ) + return isinstance(node.inst, this_class) and getattr( + node.inst, "args_in_out", None + ) == getattr(inst, "args_in_out", None) - # Consider dependants from any in/out operand - deps = [d for dep_list in t.dst_in_out for d in dep_list] - - # Prefer successors that come later in source order + deps = [d for dep_list in getattr(t, "dst_in_out", []) for d in dep_list] later_matches = [ - d for d in deps if _is_match(d) and isinstance(d.id, int) and isinstance(t.id, int) and d.id > t.id + d + for d in deps + if _is_match(d) + and isinstance(getattr(d, "id", None), int) + and isinstance(getattr(t, "id", None), int) + and d.id > t.id ] succ = min(later_matches, key=lambda n: n.id) if later_matches else None - - # Fallback for unusual cases where ids aren't integers if succ is None: any_matches = [d for d in deps if _is_match(d)] succ = any_matches[0] if any_matches else None - - if succ is None and False: + if succ is None: + if isinstance(getattr(inst, "index", None), list) and len(inst.index) == 2: + mark_outputs_only(inst) + return True return False - # Mark both in/out operands as outputs-only and trigger a dataflow rebuild - inst.num_out = len(inst.args_in_out) - inst.args_out = list(inst.args_in_out) - inst.arg_types_out = [RegisterType.MVE for _ in inst.args_in_out] - inst.args_out_restrictions = list(inst.args_in_out_restrictions) - inst.outputs = list(inst.in_outs) - inst.pattern_outputs = list(inst.pattern_in_outs) + same_q_this = ( + hasattr(inst, "args_in_out") + and len(inst.args_in_out) == 2 + and inst.args_in_out[0] == inst.args_in_out[1] + ) + same_q_next = ( + hasattr(succ.inst, "args_in_out") + and len(succ.inst.args_in_out) == 2 + and succ.inst.args_in_out[0] == succ.inst.args_in_out[1] + ) + same_q_across = ( + same_q_this + and same_q_next + and (inst.args_in_out[0] == succ.inst.args_in_out[0]) + ) - inst.num_in_out = 0 - inst.args_in_out = [] - inst.in_outs = [] - inst.pattern_in_outs = [] - inst.arg_types_in_out = [] - inst.args_in_out_restrictions = [] + idx_this = getattr(inst, "index", None) + idx_next = getattr(succ.inst, "index", None) + full_cover = ( + isinstance(idx_this, list) + and isinstance(idx_next, list) + and len(idx_this) == 2 + and len(idx_next) == 2 + and set(idx_this + idx_next) == {0, 1, 2, 3} + ) - inst.detected_vmov_double_r2v_pair = True + if not (same_q_across and full_cover): + return False + + mark_outputs_only(inst) return True return core From 783df977cb73ba2b280b64b680b2cc5241a6a2fc Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Thu, 30 Apr 2026 11:07:51 +0100 Subject: [PATCH 08/10] Fix linting --- slothy/targets/arm_v81m/arch_v81m.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slothy/targets/arm_v81m/arch_v81m.py b/slothy/targets/arm_v81m/arch_v81m.py index 58719282a..fe8024237 100644 --- a/slothy/targets/arm_v81m/arch_v81m.py +++ b/slothy/targets/arm_v81m/arch_v81m.py @@ -2727,6 +2727,7 @@ def core(inst, t, log=None): vqdmlsdh.global_parsing_cb = vqdmlsdh_vqdmladhx_parsing_cb(vqdmlsdh, vqdmladhx) vqdmladhx.global_parsing_cb = vqdmlsdh_vqdmladhx_parsing_cb(vqdmladhx, vqdmlsdh) + def vmov_double_r2v_parsing_cb(this_class): def mark_outputs_only(inst): inst.num_out = len(inst.args_in_out) @@ -2817,6 +2818,7 @@ def _is_match(node): vmov_double_r2v.global_parsing_cb = vmov_double_r2v_parsing_cb(vmov_double_r2v) + # Returns the list of all subclasses of a class which don't have # subclasses themselves def all_subclass_leaves(c): From 4dc714d837ae143c1a75815d83c9bcd14e79ba4e Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Tue, 7 Jul 2026 14:31:29 +0100 Subject: [PATCH 09/10] Armv8.1-M: Fix VMOV r2v same-register pairs Model same-register vmov_double_r2v lane writes as a single Q-register input/output operand. This avoids duplicate whole-register outputs for forms such as: vmov q1[2], q1[0], r0, r1 and keeps the full-cover pair rewrite valid for back-to-back lane writes. Also extend the Armv8-M instruction parser test coverage for vsri forms and the bic ror shifted form using the shared parser machinery. --- slothy/targets/arm_v81m/arch_v81m.py | 21 ++++++++++----------- tests/naive/armv8m/instructions.s | 5 +++++ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/slothy/targets/arm_v81m/arch_v81m.py b/slothy/targets/arm_v81m/arch_v81m.py index fe8024237..fca16f8d0 100644 --- a/slothy/targets/arm_v81m/arch_v81m.py +++ b/slothy/targets/arm_v81m/arch_v81m.py @@ -1405,6 +1405,14 @@ class vmov_double_r2v(MVEInstruction): @classmethod def make(cls, src): obj = MVEInstruction.build(cls, src) + if len(obj.args_in_out) == 2 and obj.args_in_out[0] == obj.args_in_out[1]: + obj.pattern = "vmov [], [], , " + obj.args_in_out = [obj.args_in_out[0]] + obj.arg_types_in_out = [obj.arg_types_in_out[0]] + obj.args_in_out_restrictions = [obj.args_in_out_restrictions[0]] + obj.in_outs = [obj.in_outs[0]] + obj.pattern_in_outs = [obj.pattern_in_outs[0]] + obj.num_in_out = 1 obj.detected_vmov_double_r2v_pair = False return obj @@ -2776,20 +2784,11 @@ def _is_match(node): any_matches = [d for d in deps if _is_match(d)] succ = any_matches[0] if any_matches else None if succ is None: - if isinstance(getattr(inst, "index", None), list) and len(inst.index) == 2: - mark_outputs_only(inst) - return True return False - same_q_this = ( - hasattr(inst, "args_in_out") - and len(inst.args_in_out) == 2 - and inst.args_in_out[0] == inst.args_in_out[1] - ) + same_q_this = hasattr(inst, "args_in_out") and len(inst.args_in_out) == 1 same_q_next = ( - hasattr(succ.inst, "args_in_out") - and len(succ.inst.args_in_out) == 2 - and succ.inst.args_in_out[0] == succ.inst.args_in_out[1] + hasattr(succ.inst, "args_in_out") and len(succ.inst.args_in_out) == 1 ) same_q_across = ( same_q_this diff --git a/tests/naive/armv8m/instructions.s b/tests/naive/armv8m/instructions.s index f3bcda8e6..3c0984cc8 100644 --- a/tests/naive/armv8m/instructions.s +++ b/tests/naive/armv8m/instructions.s @@ -194,6 +194,10 @@ vsli.u8 q0, q1, #6 vsli.u16 q0, q1, #8 vsli.s8 q0, q1, #6 vsli.s16 q0, q1, #8 +vsri.u8 q0, q1, #6 +vsri.u16 q0, q1, #8 +vsri.s8 q0, q1, #6 +vsri.s16 q0, q1, #8 vmovlb.u8 q1, q0 vmovlb.u16 q1, q0 @@ -426,6 +430,7 @@ eor r5, r5, r8, lsl #7 bic r0, r1, r2 bic r5, r5, r8, lsl #7 +bic r5, r5, r8, ror #7 ror r0, r1, r2 ror r5, r5, #2 From 7d88c94daed0a7427fb2898d7e1c2fae68c00d1c Mon Sep 17 00:00:00 2001 From: Brendan Moran Date: Tue, 7 Jul 2026 18:22:34 +0100 Subject: [PATCH 10/10] armv8m: cover instruction model fixes in test runner Add instruction-model checks for recent Armv8-M parsing and data-flow fixes, including flags handling, issue #419 forms, and vmov r2v pair detection. Register the checks with the existing test.py runner and keep the Python CI path on test.py. --- .github/workflows/python.yml | 2 +- slothy/targets/arm_v81m/arch_v81m.py | 81 ++++----- slothy/targets/arm_v81m/cortex_m55r1.py | 4 + slothy/targets/arm_v81m/cortex_m85r1.py | 4 + tests/naive/armv8m/_test.py | 26 ++- tests/naive/armv8m/instructions.s | 12 +- tests/naive/armv8m/test_instruction_model.py | 170 +++++++++++++++++++ 7 files changed, 255 insertions(+), 44 deletions(-) create mode 100644 tests/naive/armv8m/test_instruction_model.py diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index a3bcb062e..eb6e5269f 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -26,4 +26,4 @@ jobs: python-version: ${{ matrix.python-version}} - name: Run tests run: | - python3 -W error test.py \ No newline at end of file + python3 -W error test.py diff --git a/slothy/targets/arm_v81m/arch_v81m.py b/slothy/targets/arm_v81m/arch_v81m.py index fca16f8d0..f2148c599 100644 --- a/slothy/targets/arm_v81m/arch_v81m.py +++ b/slothy/targets/arm_v81m/arch_v81m.py @@ -62,6 +62,7 @@ class RegisterType(Enum): StackMVE = (3,) StackGPR = (4,) HINT = (5,) + FLAGS = (6,) def __str__(self): return self.name @@ -74,6 +75,8 @@ def is_renamed(ty): """Indicate if register type should be subject to renaming""" if ty == RegisterType.HINT: return False + if ty == RegisterType.FLAGS: + return False return True @staticmethod @@ -111,6 +114,7 @@ def list_registers( RegisterType.StackMVE: qstack_locations, RegisterType.MVE: vregs, RegisterType.HINT: [], + RegisterType.FLAGS: ["flags"], }[reg_type] @staticmethod @@ -133,6 +137,7 @@ def from_string(string): "mve": RegisterType.MVE, "gpr": RegisterType.GPR, "hint": RegisterType.HINT, + "flags": RegisterType.FLAGS, }.get(string, None) def default_aliases(): @@ -140,7 +145,9 @@ def default_aliases(): def default_reserved(): """Return the list of registers that should be reserved by default""" - return set(["r13", "r14"] + RegisterType.list_registers(RegisterType.HINT)) + return set( + ["flags", "r13", "r14"] + RegisterType.list_registers(RegisterType.HINT) + ) class LeLoop(Loop): @@ -375,6 +382,7 @@ def is_load_store_instruction(self): strd, strd_with_writeback, strd_with_post, + str_reg, qsave, qrestore, save, @@ -715,14 +723,13 @@ def __init__( arg_types_out = [MVEInstruction._infer_register_type(r) for r in outputs] arg_types_in_out = [MVEInstruction._infer_register_type(r) for r in in_outs] - # TODO: add flags - # if modifiesFlags: - # arg_types_out += [RegisterType.FLAGS] - # outputs += ["flags"] + if modifiesFlags: + arg_types_out += [RegisterType.FLAGS] + outputs += ["flags"] - # if dependsOnFlags: - # arg_types_in += [RegisterType.FLAGS] - # inputs += ["flags"] + if dependsOnFlags: + arg_types_in += [RegisterType.FLAGS] + inputs += ["flags"] super().__init__( mnemonic=pattern, @@ -745,6 +752,8 @@ def __init__( @staticmethod def _to_reg(ty, s): + if ty == RegisterType.FLAGS: + return "flags" if ty == RegisterType.GPR: c = "r" elif ty == RegisterType.MVE: @@ -769,8 +778,8 @@ def _build_pattern_replacement(s, ty, arg): @staticmethod def _instantiate_pattern(s, ty, arg, out): - # if ty == RegisterType.FLAGS: - # return out + if ty == RegisterType.FLAGS: + return out rep = MVEInstruction._build_pattern_replacement(s, ty, arg) res = out.replace(f"<{s}>", rep) if res == out: @@ -808,15 +817,15 @@ def group_name_i(i): group_to_attribute("barrel", "barrel") for s, ty in obj.pattern_inputs: - # if ty == RegisterType.FLAGS: - # obj.args_in.append("flags") - # else: - obj.args_in.append(MVEInstruction._to_reg(ty, res[s])) + if ty == RegisterType.FLAGS: + obj.args_in.append("flags") + else: + obj.args_in.append(MVEInstruction._to_reg(ty, res[s])) for s, ty in obj.pattern_outputs: - # if ty == RegisterType.FLAGS: - # obj.args_out.append("flags") - # else: - obj.args_out.append(MVEInstruction._to_reg(ty, res[s])) + if ty == RegisterType.FLAGS: + obj.args_out.append("flags") + else: + obj.args_out.append(MVEInstruction._to_reg(ty, res[s])) for s, ty in obj.pattern_in_outs: obj.args_in_out.append(MVEInstruction._to_reg(ty, res[s])) @@ -1066,6 +1075,11 @@ class ror_imm(MVEInstruction): outputs = ["Rd"] +class ror_short(MVEInstruction): + pattern = "ror , " + in_outs = ["Rd"] + + class cmp_reg(MVEInstruction): pattern = "cmp , " inputs = ["Rn", "Rm"] @@ -2755,35 +2769,25 @@ def mark_outputs_only(inst): inst.detected_vmov_double_r2v_pair = True def core(inst, t, log=None): - # Special-case two back-to-back vmov r2v that jointly overwrite q*. + # Special-case two vmov r2v that jointly overwrite q*. # Conditions: # - Both vmovs target the same vector register # (Qd==Qa, and equal across the pair) # - The two vmovs jointly cover all lanes {0,1,2,3} + # - The first vmov has exactly one in/out data-flow successor, which + # is the second vmov. Unrelated intervening instructions are allowed; + # intervening q-register consumers are not. assert isinstance(inst, this_class) if getattr(inst, "detected_vmov_double_r2v_pair", False): return False + if getattr(inst, "detected_vmov_double_r2v_pair_successor", False): + return False - def _is_match(node): - return isinstance(node.inst, this_class) and getattr( - node.inst, "args_in_out", None - ) == getattr(inst, "args_in_out", None) - - deps = [d for dep_list in getattr(t, "dst_in_out", []) for d in dep_list] - later_matches = [ - d - for d in deps - if _is_match(d) - and isinstance(getattr(d, "id", None), int) - and isinstance(getattr(t, "id", None), int) - and d.id > t.id - ] - succ = min(later_matches, key=lambda n: n.id) if later_matches else None - if succ is None: - any_matches = [d for d in deps if _is_match(d)] - succ = any_matches[0] if any_matches else None - if succ is None: + if len(t.dst_in_out) != 1 or len(t.dst_in_out[0]) != 1: + return False + succ = t.dst_in_out[0][0] + if not isinstance(succ.inst, this_class): return False same_q_this = hasattr(inst, "args_in_out") and len(inst.args_in_out) == 1 @@ -2810,6 +2814,7 @@ def _is_match(node): return False mark_outputs_only(inst) + succ.inst.detected_vmov_double_r2v_pair_successor = True return True return core diff --git a/slothy/targets/arm_v81m/cortex_m55r1.py b/slothy/targets/arm_v81m/cortex_m55r1.py index 538ea18ed..ad7c9e19e 100644 --- a/slothy/targets/arm_v81m/cortex_m55r1.py +++ b/slothy/targets/arm_v81m/cortex_m55r1.py @@ -61,6 +61,7 @@ bic_shifted, ror, ror_imm, + ror_short, cmp_reg, cmp_imm, sub, @@ -333,6 +334,7 @@ def get_min_max_objective(slothy): bic_shifted: ExecutionUnit.SCALAR, ror: ExecutionUnit.SCALAR, ror_imm: ExecutionUnit.SCALAR, + ror_short: ExecutionUnit.SCALAR, cmp_reg: ExecutionUnit.SCALAR, cmp_imm: ExecutionUnit.SCALAR, sub: ExecutionUnit.SCALAR, @@ -499,6 +501,7 @@ def get_min_max_objective(slothy): bic_shifted, ror, ror_imm, + ror_short, cmp_reg, cmp_imm, sub, @@ -683,6 +686,7 @@ def get_min_max_objective(slothy): bic, ror, ror_imm, + ror_short, cmp_reg, cmp_imm, sub, diff --git a/slothy/targets/arm_v81m/cortex_m85r1.py b/slothy/targets/arm_v81m/cortex_m85r1.py index 1d3983aa9..aa6be9b49 100644 --- a/slothy/targets/arm_v81m/cortex_m85r1.py +++ b/slothy/targets/arm_v81m/cortex_m85r1.py @@ -61,6 +61,7 @@ bic_shifted, ror, ror_imm, + ror_short, cmp_reg, cmp_imm, sub, @@ -327,6 +328,7 @@ def get_min_max_objective(slothy): bic_shifted: ExecutionUnit.SCALAR, ror: ExecutionUnit.SCALAR, ror_imm: ExecutionUnit.SCALAR, + ror_short: ExecutionUnit.SCALAR, cmp_reg: ExecutionUnit.SCALAR, cmp_imm: ExecutionUnit.SCALAR, sub: ExecutionUnit.SCALAR, @@ -503,6 +505,7 @@ def get_min_max_objective(slothy): bic_shifted, ror, ror_imm, + ror_short, cmp_reg, cmp_imm, sub, @@ -684,6 +687,7 @@ def get_min_max_objective(slothy): bic_shifted, ror, ror_imm, + ror_short, cmp_reg, cmp_imm, sub, diff --git a/tests/naive/armv8m/_test.py b/tests/naive/armv8m/_test.py index a86b84d41..3264e08ae 100644 --- a/tests/naive/armv8m/_test.py +++ b/tests/naive/armv8m/_test.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 Arm Limited +# Copyright (c) 2026 Arm Limited # Copyright (c) 2022 Hanno Becker # Copyright (c) 2023 Amin Abdulrahman, Matthias Kannwischer # SPDX-License-Identifier: MIT @@ -29,6 +29,7 @@ import slothy.targets.arm_v81m.arch_v81m as Arch_Armv81M import slothy.targets.arm_v81m.cortex_m55r1 as Target_CortexM55r1 import slothy.targets.arm_v81m.cortex_m85r1 as Target_CortexM85r1 +from tests.naive.armv8m.test_instruction_model import run_instruction_model_tests class Instructions(OptimizationRunner): @@ -130,6 +131,28 @@ def core(self, slothy): slothy.optimize() +class InstructionModelTest: + name = "instruction_model_m55" + + def run( + self, + debug=False, + log_model=False, + log_model_dir="models", + dry_run=False, + silent=False, + timeout=0, + debug_logfile=None, + only_target=None, + ): + if only_target is not None and only_target != Target_CortexM55r1.__name__: + return + + if not silent: + print(f"* Example: {self.name}...") + run_instruction_model_tests() + + test_instances = [ Instructions(), Instructions(target=Target_CortexM85r1), @@ -141,4 +164,5 @@ def core(self, slothy): LoopLetp(), HintTest(), TagTest(), + InstructionModelTest(), ] diff --git a/tests/naive/armv8m/instructions.s b/tests/naive/armv8m/instructions.s index 3c0984cc8..e5193dcc9 100644 --- a/tests/naive/armv8m/instructions.s +++ b/tests/naive/armv8m/instructions.s @@ -419,23 +419,26 @@ lsr r0, r0, #1 vstrb.u8 q0, [r2], #16 rsb r0, r0, #0 and r1, r0, #1 -and r0, r1, r2 +and r0, r1, r2 and r8, r9, r5, lsr #7 lsr r4, r4, r5 lsl r5, r5, #2 -eor r0, r1, r2 +eor r0, r1, r2 eor r5, r5, r8, lsl #7 -bic r0, r1, r2 +bic r0, r1, r2 bic r5, r5, r8, lsl #7 bic r5, r5, r8, ror #7 +bic r1, r5, r4, ror #24 -ror r0, r1, r2 +ror r0, r1, r2 ror r5, r5, #2 +ror r3, #10 cmp r0, r1 cmp r0, #2 +cmp r7, #0xFF @@ -443,6 +446,7 @@ ldrb r0, [r0, #16] ldrb r0, [r0], #16 ldrb r0, [r0, #16]! ldrb r5, [r12, r5] +str r6, [r13, #0] sbfx r6, r5, #0, #1 ubfx r9, r9, #8, #4 diff --git a/tests/naive/armv8m/test_instruction_model.py b/tests/naive/armv8m/test_instruction_model.py new file mode 100644 index 000000000..47ba99088 --- /dev/null +++ b/tests/naive/armv8m/test_instruction_model.py @@ -0,0 +1,170 @@ +# +# Copyright (c) 2026 Arm Limited +# SPDX-License-Identifier: MIT +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Author: Brendan Moran +# + +import logging + +from slothy.core.config import Config +from slothy.core.dataflow import DataFlowGraph +from slothy.helper import SourceLine +import slothy.targets.arm_v81m.arch_v81m as Arch +import slothy.targets.arm_v81m.cortex_m55r1 as Target + + +def _graph(src, outputs=None, allow_useless=True): + logger = logging.getLogger(__name__) + logger.addHandler(logging.NullHandler()) + logger.propagate = False + config = Config(Arch, Target, logger) + config.allow_useless_instructions = allow_useless + config.outputs = set(outputs or []) + return DataFlowGraph(SourceLine.read_multiline(src), logger, config) + + +def test_adjacent_vmov_pair_rewrites_first_only(): + graph = _graph( + """ + vmov q1[2], q1[0], r0, r1 + vmov q1[3], q1[1], r2, r3 + """, + outputs=["q1"], + ) + + first, second = graph.nodes + assert first.inst.args_out == ["q1"] + assert first.inst.args_in_out == [] + assert getattr(first.inst, "detected_vmov_double_r2v_pair", False) + assert second.inst.args_in_out == ["q1"] + assert second.src_in_out[0].src is first + + +def test_vmov_pair_rewrites_across_intervening_unrelated_instruction(): + graph = _graph( + """ + vmov q1[2], q1[0], r0, r1 + ror r4, #10 + vmov q1[3], q1[1], r2, r3 + """, + outputs=["q1", "r4"], + ) + + first, unrelated, second = graph.nodes + assert first.inst.args_out == ["q1"] + assert first.inst.args_in_out == [] + assert getattr(first.inst, "detected_vmov_double_r2v_pair", False) + assert unrelated.inst.args_in_out == ["r4"] + assert second.inst.args_in_out == ["q1"] + assert second.src_in_out[0].src is first + + +def test_vmov_pair_does_not_rewrite_across_intervening_read(): + graph = _graph( + """ + vmov q1[2], q1[0], r0, r1 + vadd.u32 q2, q1, q3 + vmov q1[3], q1[1], r2, r3 + """, + outputs=["q1", "q2"], + ) + + first, reader, second = graph.nodes + assert first.inst.args_out == [] + assert first.inst.args_in_out == ["q1"] + assert not getattr(first.inst, "detected_vmov_double_r2v_pair", False) + assert reader.src_in[0].src is first + assert second.src_in_out[0].src is first + + +def test_repeated_vmov_pairs_do_not_chain_from_successor(): + graph = _graph( + """ + vmov q1[2], q1[0], r0, r1 + vmov q1[3], q1[1], r2, r3 + vmov q1[2], q1[0], r4, r5 + vmov q1[3], q1[1], r6, r7 + """, + outputs=["q1"], + ) + + first, second, third, fourth = graph.nodes + assert first.inst.args_out == ["q1"] + assert first.inst.args_in_out == [] + assert second.inst.args_out == [] + assert second.inst.args_in_out == ["q1"] + assert not getattr(second.inst, "detected_vmov_double_r2v_pair", False) + assert getattr(second.inst, "detected_vmov_double_r2v_pair_successor", False) + assert second.src_in_out[0].src is first + + assert third.inst.args_out == ["q1"] + assert third.inst.args_in_out == [] + assert fourth.inst.args_in_out == ["q1"] + assert fourth.src_in_out[0].src is third + + +def test_cmp_outputs_flags(): + inst = Arch.Instruction.parser(SourceLine("cmp r7, #0xFF"))[0] + assert inst.args_in == ["r7"] + assert inst.args_out == ["flags"] + assert inst.arg_types_out == [Arch.RegisterType.FLAGS] + + +def test_flags_can_be_declared_as_region_output(): + graph = _graph( + "cmp r7, #0xFF\n", + outputs=["flags"], + allow_useless=False, + ) + + assert graph.nodes[0].inst.args_out == ["flags"] + assert "flags" in graph.outputs + + +def test_issue_419_reproducer_forms_parse(): + insts = [ + Arch.Instruction.parser(SourceLine(line))[0] + for line in [ + "bic r1, r5, r4, ror #24", + "str r6, [r13, #0]", + "ror r3, #10", + "cmp r7, #0xFF", + ] + ] + + assert insts[2].args_in_out == ["r3"] + assert insts[1].is_load_store_instruction() + assert insts[3].args_out == ["flags"] + + +def run_instruction_model_tests(): + test_adjacent_vmov_pair_rewrites_first_only() + test_vmov_pair_rewrites_across_intervening_unrelated_instruction() + test_vmov_pair_does_not_rewrite_across_intervening_read() + test_repeated_vmov_pairs_do_not_chain_from_successor() + test_cmp_outputs_flags() + test_flags_can_be_declared_as_region_output() + test_issue_419_reproducer_forms_parse() + + +if __name__ == "__main__": + run_instruction_model_tests()