1
//! Define the internal hash program representation used by HashX.
2

            
3
use crate::generator::Generator;
4
use crate::register::{RegisterFile, RegisterId};
5
use crate::Error;
6
use fixed_capacity_vec::FixedCapacityVec;
7
use rand_core::RngCore;
8
use std::fmt;
9
use std::ops::BitXor;
10

            
11
/// Maximum number of instructions in the program
12
///
13
/// Programs with fewer instructions may be generated (for example, after
14
/// a timing stall when register allocation fails) but they will not pass
15
/// whole-program constraint tests.
16
pub(crate) const NUM_INSTRUCTIONS: usize = 512;
17

            
18
/// Type alias for a full-size array of [`Instruction`]s
19
pub(crate) type InstructionArray = [Instruction; NUM_INSTRUCTIONS];
20

            
21
/// Type alias for a [`FixedCapacityVec`] that can build [`InstructionArray`]s
22
pub(crate) type InstructionVec = FixedCapacityVec<Instruction, NUM_INSTRUCTIONS>;
23

            
24
/// Define the HashX virtual instruction set
25
#[derive(Debug, Clone, Eq, PartialEq)]
26
pub(crate) enum Instruction {
27
    /// 64-bit multiply of two registers, discarding overflow.
28
    Mul {
29
        /// Destination register
30
        dst: RegisterId,
31
        /// Source register
32
        src: RegisterId,
33
    },
34

            
35
    /// Unsigned 64x64 to 128-bit multiply, saving only the upper half.
36
    ///
37
    /// Result is written to dst, and the low 32 bits are saved for the
38
    /// next Branch test.
39
    UMulH {
40
        /// Destination register
41
        dst: RegisterId,
42
        /// Source register
43
        src: RegisterId,
44
    },
45

            
46
    /// Signed 64x64 to 128-bit multiply, saving only the upper half.
47
    ///
48
    /// Result is written to dst, and the low 32 bits are saved for the
49
    /// next Branch test.
50
    SMulH {
51
        /// Destination register
52
        dst: RegisterId,
53
        /// Source register
54
        src: RegisterId,
55
    },
56

            
57
    /// Shift source register left by a constant amount, add, discard overflow.
58
    AddShift {
59
        /// Destination register
60
        dst: RegisterId,
61
        /// Source register
62
        src: RegisterId,
63
        /// Number of bits to left shift by (0..=3 only)
64
        left_shift: u8,
65
    },
66

            
67
    /// 64-bit addition by a sign-extended 32-bit constant.
68
    AddConst {
69
        /// Destination register
70
        dst: RegisterId,
71
        /// Source immediate, sign-extended from 32-bit to 64-bit
72
        src: i32,
73
    },
74

            
75
    /// 64-bit subtraction (dst - src), discarding overflow.
76
    Sub {
77
        /// Destination register
78
        dst: RegisterId,
79
        /// Source register
80
        src: RegisterId,
81
    },
82

            
83
    /// 64-bit XOR of two registers.
84
    Xor {
85
        /// Destination register
86
        dst: RegisterId,
87
        /// Source register
88
        src: RegisterId,
89
    },
90

            
91
    /// XOR a 64-bit register with a sign-extended 32-bit constant.
92
    XorConst {
93
        /// Destination register
94
        dst: RegisterId,
95
        /// Source immediate, sign-extended from 32-bit to 64-bit
96
        src: i32,
97
    },
98

            
99
    /// Rotate a 64-bit register right by a constant amount.
100
    Rotate {
101
        /// Destination register
102
        dst: RegisterId,
103
        /// Number of bits to rotate right by (0..=63 only)
104
        right_rotate: u8,
105
    },
106

            
107
    /// Become the target for the next taken branch, if any.
108
    Target,
109

            
110
    /// One-shot conditional branch to the last Target.
111
    Branch {
112
        /// 32-bit branch condition mask
113
        ///
114
        /// This is tested against the last `UMulH`/`SMulH` result. (The low 32
115
        /// bits of the instruction result, which itself is the upper 64 bits
116
        /// of the multiplication result.)     
117
        ///
118
        /// If `(result & mask)` is zero and no branches have been previously
119
        /// taken, we jump back to the Target and remember not to take any
120
        /// future branches. A well formed program will always have a `Target`
121
        /// prior to any `Branch`.
122
        mask: u32,
123
    },
124
}
125

            
126
/// An instruction operation, without any of its arguments
127
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
128
pub(crate) enum Opcode {
129
    /// Opcode for [`Instruction::Mul`]
130
    Mul,
131
    /// Opcode for [`Instruction::UMulH`]
132
    UMulH,
133
    /// Opcode for [`Instruction::SMulH`]
134
    SMulH,
135
    /// Opcode for [`Instruction::AddShift`]
136
    AddShift,
137
    /// Opcode for [`Instruction::AddConst`]
138
    AddConst,
139
    /// Opcode for [`Instruction::Sub`]
140
    Sub,
141
    /// Opcode for [`Instruction::Xor`]
142
    Xor,
143
    /// Opcode for [`Instruction::XorConst`]
144
    XorConst,
145
    /// Opcode for [`Instruction::Rotate`]
146
    Rotate,
147
    /// Opcode for [`Instruction::Target`]
148
    Target,
149
    /// Opcode for [`Instruction::Branch`]
150
    Branch,
151
}
152

            
153
impl Instruction {
154
    /// Get this instruction's [`Opcode`].
155
    #[inline(always)]
156
18593640
    pub(crate) fn opcode(&self) -> Opcode {
157
18593640
        match self {
158
2566200
            Instruction::AddConst { .. } => Opcode::AddConst,
159
1271920
            Instruction::AddShift { .. } => Opcode::AddShift,
160
443040
            Instruction::Branch { .. } => Opcode::Branch,
161
5902520
            Instruction::Mul { .. } => Opcode::Mul,
162
1443260
            Instruction::Rotate { .. } => Opcode::Rotate,
163
589420
            Instruction::SMulH { .. } => Opcode::SMulH,
164
1315080
            Instruction::Sub { .. } => Opcode::Sub,
165
443040
            Instruction::Target => Opcode::Target,
166
592020
            Instruction::UMulH { .. } => Opcode::UMulH,
167
1429740
            Instruction::Xor { .. } => Opcode::Xor,
168
2597400
            Instruction::XorConst { .. } => Opcode::XorConst,
169
        }
170
18593640
    }
171

            
172
    /// Get this instruction's destination register, if any.
173
    #[inline(always)]
174
9444500
    pub(crate) fn destination(&self) -> Option<RegisterId> {
175
9444500
        match self {
176
1283100
            Instruction::AddConst { dst, .. } => Some(*dst),
177
635960
            Instruction::AddShift { dst, .. } => Some(*dst),
178
295360
            Instruction::Branch { .. } => None,
179
2951260
            Instruction::Mul { dst, .. } => Some(*dst),
180
721630
            Instruction::Rotate { dst, .. } => Some(*dst),
181
294710
            Instruction::SMulH { dst, .. } => Some(*dst),
182
657540
            Instruction::Sub { dst, .. } => Some(*dst),
183
295360
            Instruction::Target => None,
184
296010
            Instruction::UMulH { dst, .. } => Some(*dst),
185
714870
            Instruction::Xor { dst, .. } => Some(*dst),
186
1298700
            Instruction::XorConst { dst, .. } => Some(*dst),
187
        }
188
9444500
    }
189
}
190

            
191
/// Generated `HashX` program, as a boxed array of instructions
192
#[derive(Clone)]
193
pub struct Program(Box<InstructionArray>);
194

            
195
impl fmt::Debug for Program {
196
130
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197
130
        writeln!(f, "Program {{")?;
198
66560
        for (addr, inst) in self.0.iter().enumerate() {
199
66560
            writeln!(f, " [{:3}]: {:?}", addr, inst)?;
200
        }
201
130
        write!(f, "}}")
202
130
    }
203
}
204

            
205
impl Program {
206
    /// Generate a new `Program` from an arbitrary [`RngCore`] implementer
207
    ///
208
    /// This can return [`Error::ProgramConstraints`] if the HashX
209
    /// post-generation program verification fails. During normal use this
210
    /// will happen once per several thousand random seeds, and the caller
211
    /// should skip to another seed.
212
9230
    pub(crate) fn generate<T: RngCore>(rng: &mut T) -> Result<Self, Error> {
213
9230
        let mut instructions = FixedCapacityVec::new();
214
9230
        Generator::new(rng).generate_program(&mut instructions)?;
215
8060
        Ok(Program(
216
8060
            instructions
217
8060
                .try_into()
218
8060
                .map_err(|_| ())
219
8060
                .expect("wrong length!"),
220
8060
        ))
221
9230
    }
222

            
223
    /// Reference implementation for `Program` behavior
224
    ///
225
    /// Run the program from start to finish, with up to one branch,
226
    /// in the provided register file.
227
260
    pub(crate) fn interpret(&self, regs: &mut RegisterFile) {
228
260
        let mut program_counter = 0;
229
260
        let mut allow_branch = true;
230
260
        let mut branch_target = None;
231
260
        let mut mulh_result: u32 = 0;
232

            
233
        /// Common implementation for binary operations on registers
234
        macro_rules! binary_reg_op {
235
            ($dst:ident, $src:ident, $fn:ident, $pc:ident) => {{
236
                let a = regs.load(*$dst);
237
                let b = regs.load(*$src);
238
                regs.store(*$dst, a.$fn(b));
239
                $pc
240
            }};
241
        }
242

            
243
        /// Common implementation for binary operations with a const operand
244
        macro_rules! binary_const_op {
245
            ($dst:ident, $src:ident, $fn:ident, $pc:ident) => {{
246
                let a = regs.load(*$dst);
247
                let b_sign_extended = i64::from(*$src) as u64;
248
                regs.store(*$dst, a.$fn(b_sign_extended));
249
                $pc
250
            }};
251
        }
252

            
253
        /// Common implementation for wide multiply operations
254
        ///
255
        /// This stores the low 32 bits of its result for later branch tests.
256
        macro_rules! mulh_op {
257
            ($dst:ident, $src:ident, $sign:ty, $wide:ty, $pc:ident) => {{
258
                let a = <$wide>::from(regs.load(*$dst) as $sign);
259
                let b = <$wide>::from(regs.load(*$src) as $sign);
260
                let r = (a.wrapping_mul(b) >> 64) as u64;
261
                mulh_result = r as u32;
262
                regs.store(*$dst, r);
263
                $pc
264
            }};
265
        }
266

            
267
135590
        while program_counter < self.0.len() {
268
135330
            let next_pc = program_counter + 1;
269
135330
            program_counter = match &self.0[program_counter] {
270
                Instruction::Target => {
271
4290
                    branch_target = Some(program_counter);
272
4290
                    next_pc
273
                }
274

            
275
4290
                Instruction::Branch { mask } => {
276
4290
                    if allow_branch && (mask & mulh_result) == 0 {
277
130
                        allow_branch = false;
278
130
                        branch_target
279
130
                            .expect("generated programs always have a target before branch")
280
                    } else {
281
4160
                        next_pc
282
                    }
283
                }
284

            
285
                Instruction::AddShift {
286
8775
                    dst,
287
8775
                    src,
288
8775
                    left_shift,
289
8775
                } => {
290
8775
                    let a = regs.load(*dst);
291
8775
                    let b = regs.load(*src);
292
8775
                    let r = a.wrapping_add(b.wrapping_shl((*left_shift).into()));
293
8775
                    regs.store(*dst, r);
294
8775
                    next_pc
295
                }
296

            
297
10530
                Instruction::Rotate { dst, right_rotate } => {
298
10530
                    let a = regs.load(*dst);
299
10530
                    let r = a.rotate_right((*right_rotate).into());
300
10530
                    regs.store(*dst, r);
301
10530
                    next_pc
302
                }
303

            
304
42250
                Instruction::Mul { dst, src } => binary_reg_op!(dst, src, wrapping_mul, next_pc),
305
10140
                Instruction::Sub { dst, src } => binary_reg_op!(dst, src, wrapping_sub, next_pc),
306
8255
                Instruction::Xor { dst, src } => binary_reg_op!(dst, src, bitxor, next_pc),
307
3770
                Instruction::UMulH { dst, src } => mulh_op!(dst, src, u64, u128, next_pc),
308
4680
                Instruction::SMulH { dst, src } => mulh_op!(dst, src, i64, i128, next_pc),
309
20085
                Instruction::XorConst { dst, src } => binary_const_op!(dst, src, bitxor, next_pc),
310
18265
                Instruction::AddConst { dst, src } => {
311
18265
                    binary_const_op!(dst, src, wrapping_add, next_pc)
312
                }
313
            }
314
        }
315
260
    }
316
}
317

            
318
impl<'a> From<&'a Program> for &'a InstructionArray {
319
    #[inline(always)]
320
7930
    fn from(prog: &'a Program) -> Self {
321
7930
        &prog.0
322
7930
    }
323
}