Skip to main content

r2smt_patch/
aarch64_encoding.rs

1//! Operand-aware helpers for the `AArch64` `cset` / `csel` / `csinc` /
2//! `csinv` / `csneg` family.
3//!
4//! These pseudo-instructions all derive their result from a flag
5//! predicate plus one or two general-purpose registers. When r2SMT
6//! proves the predicate constant, the `cs*` instruction collapses to
7//! a deterministic data-movement form. The helpers in this module
8//! synthesize the equivalent textual assembly so [`BytePatcher::assemble`]
9//! can encode it (see ARM ARM Vol. C §C6.2 for the canonical
10//! reference encodings):
11//!
12//! - `cset`    → `mov  Rd, #imm`          (`csinc Rd, RZR, RZR, !cond`)
13//! - `csetm`   → `mov  Rd, #imm`          (`csinv Rd, RZR, RZR, !cond`)
14//! - `csel`    → `mov  Rd, Rn` or `Rm`
15//! - `csinc`   → `mov  Rd, Rn` or `add Rd, Rm, #1`
16//! - `csinv`   → `mov  Rd, Rn` or `mvn Rd, Rm`
17//! - `csneg`   → `mov  Rd, Rn` or `neg Rd, Rm`
18//!
19//! The functions intentionally avoid hand-encoding instruction bytes:
20//! delegating to `r2`'s assembler (or to the in-memory test double's
21//! `add_assemble`) keeps the encoding path identical to the
22//! `replace_jcc_with_jmp` strategy and avoids reimplementing the
23//! Armv8 `MOV (register)` / `ADD (immediate)` aliases by hand.
24//!
25//! [`BytePatcher::assemble`]: r2smt_ir::byte_patcher::BytePatcher::assemble
26
27/// Canonical "wide" zero register name. Returned by [`parse_xreg`]
28/// when the operand text is one of `xzr` / `wzr`.
29pub const ZERO_REGISTER_X: &str = "xzr";
30
31/// Canonical "wide" zero register name for 32-bit views.
32pub const ZERO_REGISTER_W: &str = "wzr";
33
34/// Parse a raw operand string into a canonical `AArch64` GPR name.
35///
36/// Accepts the textual forms r2 emits (`x0`, `w0`, `xzr`, `wzr`,
37/// optional whitespace, optional `,` from the operand splitter) and
38/// returns the lower-cased canonical name. Returns `None` for the
39/// stack-pointer aliases (`sp`, `wsp`) — patching them is unsafe in
40/// the general case (function epilogues, frame setup), and for any
41/// other shape (SIMD registers, memory operands, immediates).
42#[must_use]
43pub fn parse_xreg(raw: &str) -> Option<String> {
44    let trimmed = raw.trim().trim_end_matches(',').trim();
45    let lower = trimmed.to_ascii_lowercase();
46    if lower == ZERO_REGISTER_X || lower == ZERO_REGISTER_W {
47        return Some(lower);
48    }
49    if let Some(rest) = lower.strip_prefix('x').or_else(|| lower.strip_prefix('w'))
50        && let Ok(n) = rest.parse::<u8>()
51        && n <= 30
52    {
53        return Some(lower);
54    }
55    None
56}
57
58/// Assemble syntax for `mov Rd, Rs` (the `AArch64` register-move alias).
59#[must_use]
60pub fn mov_reg(dst: &str, src: &str) -> String {
61    format!("mov {dst}, {src}")
62}
63
64/// Assemble syntax for `mov Rd, #imm` (a `MOVZ` / `MOVN` alias).
65#[must_use]
66pub fn mov_imm(dst: &str, imm: i64) -> String {
67    if imm < 0 {
68        format!("mov {dst}, #-{abs}", abs = imm.unsigned_abs())
69    } else {
70        format!("mov {dst}, #{imm}")
71    }
72}
73
74/// Assemble syntax for `mvn Rd, Rs` (bitwise NOT — `ORN Rd, RZR, Rs`).
75#[must_use]
76pub fn mvn_reg(dst: &str, src: &str) -> String {
77    format!("mvn {dst}, {src}")
78}
79
80/// Assemble syntax for `neg Rd, Rs` (two's-complement negation —
81/// `SUB Rd, RZR, Rs`).
82#[must_use]
83pub fn neg_reg(dst: &str, src: &str) -> String {
84    format!("neg {dst}, {src}")
85}
86
87/// Assemble syntax for `add Rd, Rs, #imm`.
88#[must_use]
89pub fn add_imm(dst: &str, src: &str, imm: i64) -> String {
90    if imm < 0 {
91        format!("sub {dst}, {src}, #{abs}", abs = imm.unsigned_abs())
92    } else {
93        format!("add {dst}, {src}, #{imm}")
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn parse_xreg_accepts_x_and_w_views() {
103        assert_eq!(parse_xreg("x0").as_deref(), Some("x0"));
104        assert_eq!(parse_xreg("w15").as_deref(), Some("w15"));
105        assert_eq!(parse_xreg("x30").as_deref(), Some("x30"));
106    }
107
108    #[test]
109    fn parse_xreg_accepts_zero_registers() {
110        assert_eq!(parse_xreg("xzr").as_deref(), Some("xzr"));
111        assert_eq!(parse_xreg("WZR").as_deref(), Some("wzr"));
112    }
113
114    #[test]
115    fn parse_xreg_strips_trailing_comma_and_whitespace() {
116        assert_eq!(parse_xreg(" x0 , ").as_deref(), Some("x0"));
117    }
118
119    #[test]
120    fn parse_xreg_rejects_stack_pointer() {
121        assert!(parse_xreg("sp").is_none());
122        assert!(parse_xreg("wsp").is_none());
123    }
124
125    #[test]
126    fn parse_xreg_rejects_out_of_range_indices() {
127        assert!(parse_xreg("x31").is_none());
128        assert!(parse_xreg("w99").is_none());
129    }
130
131    #[test]
132    fn parse_xreg_rejects_simd_and_memory_operands() {
133        assert!(parse_xreg("v0").is_none());
134        assert!(parse_xreg("q3").is_none());
135        assert!(parse_xreg("[x0]").is_none());
136        assert!(parse_xreg("#1").is_none());
137    }
138
139    #[test]
140    fn mov_imm_handles_positive_zero_and_negative() {
141        assert_eq!(mov_imm("x0", 0), "mov x0, #0");
142        assert_eq!(mov_imm("x0", 1), "mov x0, #1");
143        assert_eq!(mov_imm("x0", -1), "mov x0, #-1");
144    }
145
146    #[test]
147    fn add_imm_falls_through_to_sub_on_negative() {
148        assert_eq!(add_imm("x0", "x1", 4), "add x0, x1, #4");
149        assert_eq!(add_imm("x0", "x1", -4), "sub x0, x1, #4");
150    }
151
152    #[test]
153    fn register_alias_helpers_emit_canonical_aliases() {
154        assert_eq!(mov_reg("x0", "x1"), "mov x0, x1");
155        assert_eq!(mvn_reg("x0", "x1"), "mvn x0, x1");
156        assert_eq!(neg_reg("x0", "x1"), "neg x0, x1");
157    }
158}