summaryrefslogtreecommitdiff
path: root/yjit/src/asm/arm64/inst/test_bit.rs
blob: c57a05ad2b83c65e6a99c6755bf0945e4488868c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use super::super::arg::truncate_imm;

/// The upper bit of the bit number to test.
#[derive(Debug)]
enum B5 {
    /// When the bit number is below 32.
    B532 = 0,

    /// When the bit number is equal to or above 32.
    B564 = 1
}

/// A convenience function so that we can convert the bit number directly into a
/// B5 variant.
impl From<u8> for B5 {
    fn from(bit_num: u8) -> Self {
        match bit_num {
            0..=31 => B5::B532,
            32..=63 => B5::B564,
            _ => panic!("Invalid bit number: {}", bit_num)
        }
    }
}

/// The operation to perform for this instruction.
enum Op {
    /// The test bit zero operation.
    TBZ = 0,

    /// The test bit not zero operation.
    TBNZ = 1
}

/// The struct that represents an A64 test bit instruction that can be encoded.
///
/// TBNZ/TBZ
/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+
/// | 31 30 29 28 | 27 26 25 24 | 23 22 21 20 | 19 18 17 16 | 15 14 13 12 | 11 10 09 08 | 07 06 05 04 | 03 02 01 00 |
/// |     0  1  1    0  1  1                                                                                        |
/// | b5                     op   b40............. imm14.......................................... rt.............. |
/// +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+
///
pub struct TestBit {
    /// The number of the register to test.
    rt: u8,

    /// The PC-relative offset to the target instruction in term of number of
    /// instructions.
    imm14: i16,

    /// The lower 5 bits of the bit number to be tested.
    b40: u8,

    /// The operation to perform for this instruction.
    op: Op,

    /// The upper bit of the bit number to test.
    b5: B5
}

impl TestBit {
    /// TBNZ
    /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/TBNZ--Test-bit-and-Branch-if-Nonzero-?lang=en
    pub fn tbnz(rt: u8, bit_num: u8, offset: i16) -> Self {
        Self { rt, imm14: offset, b40: bit_num & 0b11111, op: Op::TBNZ, b5: bit_num.into() }
    }

    /// TBZ
    /// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/TBZ--Test-bit-and-Branch-if-Zero-?lang=en
    pub fn tbz(rt: u8, bit_num: u8, offset: i16) -> Self {
        Self { rt, imm14: offset, b40: bit_num & 0b11111, op: Op::TBZ, b5: bit_num.into() }
    }
}

/// https://developer.arm.com/documentation/ddi0602/2022-03/Index-by-Encoding/Branches--Exception-Generating-and-System-instructions?lang=en
const FAMILY: u32 = 0b11011;

impl From<TestBit> for u32 {
    /// Convert an instruction into a 32-bit value.
    fn from(inst: TestBit) -> Self {
        let b40 = (inst.b40 & 0b11111) as u32;
        let imm14 = truncate_imm::<_, 14>(inst.imm14);

        0
        | ((inst.b5 as u32) << 31)
        | (FAMILY << 25)
        | ((inst.op as u32) << 24)
        | (b40 << 19)
        | (imm14 << 5)
        | inst.rt as u32
    }
}

impl From<TestBit> for [u8; 4] {
    /// Convert an instruction into a 4 byte array.
    fn from(inst: TestBit) -> [u8; 4] {
        let result: u32 = inst.into();
        result.to_le_bytes()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_tbnz() {
        let inst = TestBit::tbnz(0, 0, 0);
        let result: u32 = inst.into();
        assert_eq!(0x37000000, result);
    }

    #[test]
    fn test_tbnz_negative() {
        let inst = TestBit::tbnz(0, 0, -1);
        let result: u32 = inst.into();
        assert_eq!(0x3707ffe0, result);
    }

    #[test]
    fn test_tbz() {
        let inst = TestBit::tbz(0, 0, 0);
        let result: u32 = inst.into();
        assert_eq!(0x36000000, result);
    }

    #[test]
    fn test_tbz_negative() {
        let inst = TestBit::tbz(0, 0, -1);
        let result: u32 = inst.into();
        assert_eq!(0x3607ffe0, result);
    }
}