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
134
135
136
137
138
139
140
//! Utils for register specified shift operations, register specified meaning a
//! shift amount that comes from a Risc CPU register. See: [ARM7TDMI Datasheet
//! Page.36-38]
//!
//! Register specified shift special effects list:
//!
//! 1. If the shift amount is 0, the carry-out is the old carry flag and val is
//! untouched.
//!
//! 2. If the shift amount is between 1 and 31, it will operate the same as its
//! [`instruction_specified_shift`] counter part.
//!
//! 3. If the shift amount is 32 or more, there are side effects depending on
//! the shift:
//!
//! - LSL by 32 has result zero, carry out equal to bit 0 of the val.
//!
//! - LSL by more than 32 has result zero, carry out zero.
//!
//! - LSR by 32 has result zero, carry out equal to bit 31 of the val.
//!
//! - LSR by more than 32 has result zero, carry out zero.
//!
//! - ASR by 32 or more has the shift results val filled with and carry out
//!   equal to bit 31 of the val.
//!
//! - ROR by 32 has result equal to Rm, carry out equal to bit 31 of the val.
//!
//! - ROR by n where n is greater than 32 will give the same result and carry
//!   out as ROR by n-32; therefore repeatedly subtract 32 from n until the
//!   amount is in the of range 1 to 32.
//!
//! [ARM7TDMI Datasheet Page.36-38]:https://github.com/gregorygaines/gopherboyadvance/blob/main/docs/references/arm7tdmi-datasheet.pdf

use crate::arch::instruction_specified_shift;
use crate::arch::ShiftResults;
use crate::util::bit_manipulation::is_bit_set_32;

/// Shifts bits left by a register specified amount.
///
/// Bits are moved out of the left-hand end and zeros are filled into the
/// right-hand end. If the shift amount is zero, the carry-out is the old carry
/// flag and val is untouched. If the shift amount is less than 32 and greater
/// than 0, the shift functions identically as
/// [`instruction_specified_shift::shift_left`]. If the shift amount is 32, the
/// result val is zero, and the carry-out is equal to bit 0 of original val. If
/// the shift amount is more than 32, the result is zero and the carry-out is
/// false.
pub fn shift_left(val: u32, shift_amount: u32, carry_flag: bool) -> ShiftResults {
    let shift_amount = shift_amount & 0xFF;
    if shift_amount == 0 {
        ShiftResults { val, carry_out: carry_flag }
    } else if shift_amount < 32 {
        instruction_specified_shift::shift_left(val, shift_amount, carry_flag)
    } else if shift_amount == 32 {
        ShiftResults { val: 0, carry_out: is_bit_set_32(val, /* bit_idx= */ 0) }
    } else {
        // Shift by more than 32
        ShiftResults { val: 0, carry_out: false }
    }
}

/// Shifts bits right by a register specified amount.
///
/// Bits are moved out of the right-hand end and zeros are filled into the
/// left-hand end. If the shift amount is zero, the carry-out is the old carry
/// flag and val is untouched. If the shift amount is less than 32 and greater
/// than 0, the shift functions identically as
/// [`instruction_specified_shift::shift_right`]. If the shift amount is 32, the
/// result val is zero, and the carry-out is equal to bit 31 of original val. If
/// the shift amount is more than 32, the result is zero and the carry-out is
/// false.
pub fn shift_right(val: u32, shift_amount: u32, carry_flag: bool) -> ShiftResults {
    let shift_amount = shift_amount & 0xFF;
    if shift_amount == 0 {
        ShiftResults { val, carry_out: carry_flag }
    } else if shift_amount < 32 {
        instruction_specified_shift::shift_right(val, shift_amount)
    } else if shift_amount == 32 {
        ShiftResults { val: 0, carry_out: is_bit_set_32(val, /* bit_idx= */ 31) }
    } else {
        // Shift by more than 32
        ShiftResults { val: 0, carry_out: false }
    }
}

/// Rotates bits right by a register specified amount.
///
/// Bits moved out of the right-hand end are rotated back into the left-hand
/// end. If the shift amount is specified is zero, the carry-out is the old
/// carry flag and val is untouched. Otherwise, the shift amount is repeatedly
/// subtracted by 32 until its in the range of 1..=32. Once the shift amount is
/// in range, if the shift amount is zero, this means the original shift amount
/// was outside the range of an integer so the val is untouched and the carry
/// out is bit 31 of val. If the shift amount is greater than zero, the shift
/// functions identically as [`instruction_specified_shift::rotate_right`] using
/// the newly in range shift amount.
pub fn rotate_right(val: u32, shift_amount: u32, carry_flag: bool) -> ShiftResults {
    let shift_amount = shift_amount & 0xFF;
    if shift_amount == 0 {
        ShiftResults { val, carry_out: carry_flag }
    } else {
        // Force shift amount into the range of 1..=32
        let in_range_shift_amount = shift_amount & 0x1F;
        if in_range_shift_amount == 0 {
            // Original shift amount was outside the integer range.
            ShiftResults { val, carry_out: is_bit_set_32(val, /* bit_idx= */ 31) }
        } else {
            instruction_specified_shift::rotate_right(val, in_range_shift_amount, carry_flag)
        }
    }
}

/// Shifts bits right by a register specified amount while preserving the sign
/// of the val.
///
/// Bits are moved out of the right-hand end and bit 31 of the val are filled in
/// the left-hand end to preserves the sign in 2's complement notation. If the
/// shift amount is zero, the carry-out is the old carry flag and val is
/// untouched. If the shift amount is greater than zero and less than 32, the
/// shift functions identically as
/// [`instruction_specified_shift::arithmetic_shift_right`]. If the shift amount
/// is greater than 32, the carry-out is bit 32 of the original val the result
/// val is filled with the carry out; either 0xFFFF_FFFF or 0x0.
pub fn arithmetic_shift_right(val: u32, shift_amount: u32, carry_flag: bool) -> ShiftResults {
    let shift_amount = shift_amount & 0xFF;
    if shift_amount == 0 {
        ShiftResults { val, carry_out: carry_flag }
    } else if shift_amount < 32 {
        instruction_specified_shift::arithmetic_shift_right(val, shift_amount)
    } else {
        // Shift by more than 32
        let carry_out = is_bit_set_32(val, /* bit_idx= */ 31);
        if carry_out {
            ShiftResults { val: 0xFFFF_FFFF, carry_out }
        } else {
            ShiftResults { val: 0, carry_out }
        }
    }
}