Coverage Report

Created: 2026-03-26 07:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/dynasmrt-5.0.0/src/aarch64.rs
Line
Count
Source
1
//! Runtime support for the aarch64 architecture assembling target.
2
//!
3
//! The aarch64 instruction set features fixed-width 32-bit instructions and relative relocations up to 28 bits in size.
4
//!
5
//! The core relocation behaviour for this architecture is provided by the [`Aarch64Relocation`] type.
6
//!
7
//! Next to that, this module contains the following:
8
//!
9
//! ## Type aliases
10
//!
11
//! Several specialized type aliases of the generic [`Assembler`] are provided as these are by far the most common usecase.
12
//!
13
//! ## Enums
14
//!
15
//! There are enumerations of every logically distinct register family usable in aarch64.
16
//! These enums implement the [`Register`] trait and their discriminant values match their numeric encoding in dynamic register literals.
17
//!
18
//! *Note: The presence of some registers listed here is purely what is encodable. Check the relevant architecture documentation to find what is architecturally valid.*
19
//!
20
//! ## Functions
21
//!
22
//! The aarch64 architecture allows encoding several special types of immediates. The encoding implementations for these immediate types have been exposed to assist the user
23
//! in correctly using these instructions. They will return `Some(encoding)` only if the given value can be encoded losslessly in that immediate type.
24
25
use crate::Register;
26
use crate::relocations::{ArchitectureRelocationEncoding, Relocation, RelocationType, RelocationSize, RelocationKind, RelocationEncoding, ImpossibleRelocation, fits_signed_bitfield};
27
use byteorder::{ByteOrder, LittleEndian};
28
use std::convert::TryFrom;
29
30
#[derive(Debug, Clone, Copy)]
31
enum Aarch64RelocationEncoding {
32
    // b, bl 26 bits, dword aligned
33
    B,
34
    // b.cond, cbnz, cbz, ldr, ldrsw, prfm: 19 bits, dword aligned
35
    BCOND,
36
    // adr split 21 bit, byte aligned
37
    ADR,
38
    // adrp split 21 bit, 4096-byte aligned
39
    ADRP,
40
    // tbnz, tbz: 14 bits, dword aligned
41
    TBZ,
42
}
43
44
impl ArchitectureRelocationEncoding for Aarch64RelocationEncoding {
45
0
    fn decode(code: u8) -> Self {
46
0
        match code {
47
0
            0 => Self::B,
48
0
            1 => Self::BCOND,
49
0
            2 => Self::ADR,
50
0
            3 => Self::ADRP,
51
0
            4 => Self::TBZ,
52
0
            n => panic!("Invalid complex relocation code {n} given for the current architecture")
53
        }
54
0
    }
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64RelocationEncoding as dynasmrt::relocations::ArchitectureRelocationEncoding>::decode
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64RelocationEncoding as dynasmrt::relocations::ArchitectureRelocationEncoding>::decode
55
}
56
57
impl Aarch64RelocationEncoding {
58
0
    fn op_mask(&self) -> u32 {
59
0
        match self {
60
0
            Self::B => 0xFC00_0000,
61
0
            Self::BCOND => 0xFF00_001F,
62
0
            Self::ADR => 0x9F00_001F,
63
0
            Self::ADRP => 0x9F00_001F,
64
0
            Self::TBZ => 0xFFF8_001F
65
        }
66
0
    }
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64RelocationEncoding>::op_mask
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64RelocationEncoding>::op_mask
67
68
0
    fn encode(&self, value: isize) -> Result<u32, ImpossibleRelocation> {
69
0
        let value = i64::try_from(value).map_err(|_| ImpossibleRelocation { } )?;
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64RelocationEncoding>::encode::{closure#0}
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64RelocationEncoding>::encode::{closure#0}
70
0
        Ok(match self {
71
            Self::B => {
72
0
                if value & 3 != 0 || !fits_signed_bitfield(value >> 2, 26) {
73
0
                    return Err(ImpossibleRelocation { } );
74
0
                }
75
0
                let value = (value >> 2) as u32;
76
0
                value & 0x3FF_FFFF
77
            },
78
            Self::BCOND => {
79
0
                if value & 3 != 0 || !fits_signed_bitfield(value >> 2, 19) {
80
0
                    return Err(ImpossibleRelocation { } );
81
0
                }
82
0
                let value = (value >> 2) as u32;
83
0
                (value & 0x7FFFF) << 5
84
            },
85
            Self::ADR => {
86
0
                if !fits_signed_bitfield(value, 21) {
87
0
                    return Err(ImpossibleRelocation { } );
88
0
                }
89
0
                let low = (value) as u32;
90
0
                let high = (value >> 2) as u32;
91
0
                ((high & 0x7FFFF) << 5) | ((low & 3) << 29)
92
            },
93
            Self::ADRP => {
94
0
                let value = value + 0xFFF;
95
0
                if !fits_signed_bitfield(value >> 12, 21) {
96
0
                    return Err(ImpossibleRelocation { } );
97
0
                }
98
0
                let low = (value >> 12) as u32;
99
0
                let high = (value >> 14) as u32;
100
0
                ((high & 0x7FFFF) << 5) | ((low & 3) << 29)
101
            },
102
            Self::TBZ => {
103
0
                if value & 3 != 0 || !fits_signed_bitfield(value >> 2, 14) {
104
0
                    return Err(ImpossibleRelocation { } );
105
0
                }
106
0
                let value = (value >> 2) as u32;
107
0
                (value & 0x3FFF) << 5
108
            }
109
        })
110
0
    }
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64RelocationEncoding>::encode
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64RelocationEncoding>::encode
111
}
112
113
/// Relocation implementation for the aarch64 architecture.
114
#[derive(Debug, Clone, Copy)]
115
pub struct Aarch64Relocation(RelocationType<Aarch64RelocationEncoding>);
116
117
impl Relocation for Aarch64Relocation {
118
0
    fn from_encoding(encoding: u8) -> Self {
119
0
        Aarch64Relocation(RelocationType::decode(encoding))
120
0
    }
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::from_encoding
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::from_encoding
121
0
    fn from_size(kind: RelocationKind, size: RelocationSize) -> Self {
122
0
        Aarch64Relocation(RelocationType::from_size(kind, size))
123
0
    }
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::from_size
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::from_size
124
0
    fn size(&self) -> usize {
125
0
        match self.0.encoding {
126
0
            RelocationEncoding::Simple(s) => s.size(),
127
0
            RelocationEncoding::ArchSpecific(_) => 4
128
        }
129
0
    }
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::size
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::size
130
0
    fn write_value(&self, buf: &mut [u8], value: isize) -> Result<(), ImpossibleRelocation> {
131
0
        match self.0.encoding {
132
0
            RelocationEncoding::Simple(s) => s.write_value(buf, value),
133
0
            RelocationEncoding::ArchSpecific(c) => {
134
0
                let mask = c.op_mask();
135
0
                let template = LittleEndian::read_u32(buf) & mask;
136
0
                let packed = c.encode(value)?;
137
0
                LittleEndian::write_u32(buf, template | packed);
138
0
                Ok(())
139
            }
140
        }
141
0
    }
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::write_value
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::write_value
142
0
    fn read_value(&self, buf: &[u8]) -> isize {
143
0
        match self.0.encoding {
144
0
            RelocationEncoding::Simple(s) => s.read_value(buf),
145
0
            RelocationEncoding::ArchSpecific(c) => {
146
0
                let mask = !c.op_mask();
147
0
                let value = LittleEndian::read_u32(buf);
148
0
                let unpacked = match c {
149
0
                    Aarch64RelocationEncoding::B => u64::from(
150
0
                        value & mask
151
0
                    ) << 2,
152
0
                    Aarch64RelocationEncoding::BCOND => u64::from(
153
0
                        (value & mask) >> 5
154
0
                    ) << 2,
155
0
                    Aarch64RelocationEncoding::ADR  => u64::from(
156
0
                        (((value >> 5 ) & 0x7FFFF) << 2) |
157
0
                        ((value >> 29) & 3 )
158
                    ),
159
0
                    Aarch64RelocationEncoding::ADRP => u64::from(
160
0
                        (((value >> 5 ) & 0x7FFFF) << 2) |
161
0
                        ((value >> 29) & 3 )
162
0
                    ) << 12,
163
0
                    Aarch64RelocationEncoding::TBZ => u64::from(
164
0
                        (value & mask) >> 5
165
0
                    ) << 2
166
                };
167
168
                // Sign extend.
169
0
                let bits = match c {
170
0
                    Aarch64RelocationEncoding::B => 26,
171
0
                    Aarch64RelocationEncoding::BCOND => 19,
172
0
                    Aarch64RelocationEncoding::ADR => 21,
173
0
                    Aarch64RelocationEncoding::ADRP => 33,
174
0
                    Aarch64RelocationEncoding::TBZ => 14
175
                };
176
0
                let offset = 1u64 << (bits - 1);
177
0
                let value: u64 = (unpacked ^ offset).wrapping_sub(offset);
178
179
0
                value as i64 as isize
180
            }
181
        }
182
0
    }
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::read_value
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::read_value
183
0
    fn kind(&self) -> RelocationKind {
184
0
        self.0.kind
185
0
    }
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::kind
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::kind
186
0
    fn page_size() -> usize {
187
0
        4096
188
0
    }
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::page_size
Unexecuted instantiation: <dynasmrt::aarch64::Aarch64Relocation as dynasmrt::relocations::Relocation>::page_size
189
}
190
191
/// An aarch64 Assembler. This is aliased here for backwards compatability.
192
pub type Assembler = crate::Assembler<Aarch64Relocation>;
193
/// An aarch64 AssemblyModifier. This is aliased here for backwards compatability.
194
pub type AssemblyModifier<'a> = crate::Modifier<'a, Aarch64Relocation>;
195
/// An aarch64 UncommittedModifier. This is aliased here for backwards compatability.
196
pub type UncommittedModifier<'a> = crate::UncommittedModifier<'a>;
197
198
199
// these should explicitly never be inlined, as this is the slow path.
200
// that's also why these aren't made generic.
201
202
/// Handler for `f32` out-of-range aarch64 immediates.
203
#[inline(never)]
204
0
pub fn immediate_out_of_range_unsigned_f32(immediate: f32) -> ! {
205
0
    panic!("Cannot assemble this Aarch64 instruction. Immediate {immediate} is out of range.")
Unexecuted instantiation: dynasmrt::aarch64::immediate_out_of_range_unsigned_f32
Unexecuted instantiation: dynasmrt::aarch64::immediate_out_of_range_unsigned_f32
206
}
207
208
/// Handler for `u64` out-of-range aarch64 immediates.
209
#[inline(never)]
210
0
pub fn immediate_out_of_range_unsigned_64(immediate: u64) -> ! {
211
0
    panic!("Cannot assemble this Aarch64 instruction. Immediate {immediate} is out of range.")
Unexecuted instantiation: dynasmrt::aarch64::immediate_out_of_range_unsigned_64
Unexecuted instantiation: dynasmrt::aarch64::immediate_out_of_range_unsigned_64
212
}
213
214
/// Handler for `u32` out-of-range aarch64 immediates.
215
#[inline(never)]
216
0
pub fn immediate_out_of_range_unsigned_32(immediate: u32) -> ! {
217
0
    panic!("Cannot assemble this Aarch64 instruction. Immediate {immediate} is out of range.")
Unexecuted instantiation: dynasmrt::aarch64::immediate_out_of_range_unsigned_32
Unexecuted instantiation: dynasmrt::aarch64::immediate_out_of_range_unsigned_32
218
}
219
220
/// Handler for `i32` out-of-range aarch64 immediates.
221
#[inline(never)]
222
0
pub fn immediate_out_of_range_signed_32(immediate: i32) -> ! {
223
0
    panic!("Cannot assemble this Aarch64 instruction. Immediate {immediate} is out of range.")
Unexecuted instantiation: dynasmrt::aarch64::immediate_out_of_range_signed_32
Unexecuted instantiation: dynasmrt::aarch64::immediate_out_of_range_signed_32
224
}
225
226
227
/// Helper function for validating that a given value can be encoded as a 32-bit logical immediate
228
0
pub fn encode_logical_immediate_32bit(value: u32) -> Option<u16> {
229
0
    let transitions = value ^ value.rotate_right(1);
230
0
    let element_size = (64u32).checked_div(transitions.count_ones())?;
231
232
    // confirm that the elements are identical
233
0
    if value != value.rotate_left(element_size) {
234
0
        return None;
235
0
    }
236
237
0
    let element = value & 1u32.checked_shl(element_size).unwrap_or(0).wrapping_sub(1);
238
0
    let ones = element.count_ones();
239
0
    let imms = (!((element_size << 1) - 1) & 0x3F) | (ones - 1);
240
241
0
    let immr = if (element & 1) != 0 {
242
0
        ones - (!element).trailing_zeros()
243
    } else {
244
0
        element_size - element.trailing_zeros()
245
    };
246
247
0
    Some(((immr as u16) << 6) | (imms as u16))
248
0
}
Unexecuted instantiation: dynasmrt::aarch64::encode_logical_immediate_32bit
Unexecuted instantiation: dynasmrt::aarch64::encode_logical_immediate_32bit
249
250
/// Helper function for validating that a given value can be encoded as a 64-bit logical immediate
251
0
pub fn encode_logical_immediate_64bit(value: u64) -> Option<u16> {
252
0
    let transitions = value ^ value.rotate_right(1);
253
0
    let element_size = (128u32).checked_div(transitions.count_ones())?;
254
255
    // confirm that the elements are identical
256
0
    if value != value.rotate_left(element_size) {
257
0
        return None;
258
0
    }
259
260
0
    let element = value & 1u64.checked_shl(element_size).unwrap_or(0).wrapping_sub(1);
261
0
    let ones = element.count_ones();
262
0
    let imms = (!((element_size << 1) - 1) & 0x7F) | (ones - 1);
263
264
0
    let immr = if (element & 1) != 0 {
265
0
        ones - (!element).trailing_zeros()
266
    } else {
267
0
        element_size - element.trailing_zeros()
268
    };
269
270
0
    let n = imms & 0x40 == 0;
271
0
    let imms = imms & 0x3F;
272
273
0
    Some(((n as u16) << 12) | ((immr as u16) << 6) | (imms as u16))
274
0
}
Unexecuted instantiation: dynasmrt::aarch64::encode_logical_immediate_64bit
Unexecuted instantiation: dynasmrt::aarch64::encode_logical_immediate_64bit
275
276
/// Helper function for validating that a given value can be encoded as a floating point immediate
277
0
pub fn encode_floating_point_immediate(value: f32) -> Option<u8> {
278
    // floating point ARM immediates are encoded as
279
    // abcdefgh => aBbbbbbc defgh000 00000000 00000000
280
    // where B = !b
281
    // which means we can just slice out "a" and "bcdefgh" and assume the rest was correct
282
283
0
    let bits = value.to_bits();
284
285
0
    let check = (bits >> 25) & 0x3F;
286
0
    if (check == 0b10_0000 || check == 0b01_1111) && (bits & 0x7_FFFF) == 0 {
287
0
        Some((((bits >> 24) & 0x80) | ((bits >> 19) & 0x7F)) as u8)
288
    } else {
289
0
        None
290
    }
291
0
}
Unexecuted instantiation: dynasmrt::aarch64::encode_floating_point_immediate
Unexecuted instantiation: dynasmrt::aarch64::encode_floating_point_immediate
292
293
294
/// 4 or 8-byte general purpopse registers, where X31 is the zero register.
295
#[allow(missing_docs)]
296
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
297
pub enum RX {
298
    X0 = 0x00, X1 = 0x01, X2 = 0x02, X3 = 0x03,
299
    X4 = 0x04, X5 = 0x05, X6 = 0x06, X7 = 0x07,
300
    X8 = 0x08, X9 = 0x09, X10= 0x0A, X11= 0x0B,
301
    X12= 0x0C, X13= 0x0D, X14= 0x0E, X15= 0x0F,
302
    X16= 0x10, X17= 0x11, X18= 0x12, X19= 0x13,
303
    X20= 0x14, X21= 0x15, X22= 0x16, X23= 0x17,
304
    X24= 0x18, X25= 0x19, X26= 0x1A, X27= 0x1B,
305
    X28= 0x1C, X29= 0x1D, X30= 0x1E, XZR= 0x1F,
306
}
307
reg_impls!(RX);
308
309
/// 0x1F addresses both XZR and SP (disambiguated by context). This enum is a mirror of RX just
310
/// with the SP in place of XZR.
311
#[allow(missing_docs)]
312
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
313
pub enum RXSP {
314
    X0 = 0x00, X1 = 0x01, X2 = 0x02, X3 = 0x03,
315
    X4 = 0x04, X5 = 0x05, X6 = 0x06, X7 = 0x07,
316
    X8 = 0x08, X9 = 0x09, X10= 0x0A, X11= 0x0B,
317
    X12= 0x0C, X13= 0x0D, X14= 0x0E, X15= 0x0F,
318
    X16= 0x10, X17= 0x11, X18= 0x12, X19= 0x13,
319
    X20= 0x14, X21= 0x15, X22= 0x16, X23= 0x17,
320
    X24= 0x18, X25= 0x19, X26= 0x1A, X27= 0x1B,
321
    X28= 0x1C, X29= 0x1D, X30= 0x1E, SP = 0x1F,
322
}
323
reg_impls!(RXSP);
324
325
/// 1, 2, 4, 8 or 16-bytes scalar FP / vector SIMD registers. 
326
#[allow(missing_docs)]
327
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
328
pub enum RV {
329
    V0 = 0x00, V1 = 0x01, V2 = 0x02, V3 = 0x03,
330
    V4 = 0x04, V5 = 0x05, V6 = 0x06, V7 = 0x07,
331
    V8 = 0x08, V9 = 0x09, V10= 0x0A, V11= 0x0B,
332
    V12= 0x0C, V13= 0x0D, V14= 0x0E, V15= 0x0F,
333
    V16= 0x10, V17= 0x11, V18= 0x12, V19= 0x13,
334
    V20= 0x14, V21= 0x15, V22= 0x16, V23= 0x17,
335
    V24= 0x18, V25= 0x19, V26= 0x1A, V27= 0x1B,
336
    V28= 0x1C, V29= 0x1D, V30= 0x1E, V31= 0x1F,
337
}
338
reg_impls!(RV);
339
340
#[cfg(test)]
341
mod tests {
342
    use super::RX::*;
343
    use crate::Register;
344
345
    #[test]
346
    fn reg_code() {
347
        assert_eq!(X2.code(), 2);
348
    }
349
350
    #[test]
351
    fn reg_code_from() {
352
        assert_eq!(u8::from(X24), 0x18);
353
    }
354
}