|
| 1 | +//! This crate is for integration testing and fuzz testing of functions in `compiler-builtins`. This |
| 2 | +//! includes publicly documented intrinsics and some internal alternative implementation functions |
| 3 | +//! such as `usize_leading_zeros_riscv` (which are tested because they are configured for |
| 4 | +//! architectures not tested by the CI). |
| 5 | +//! |
| 6 | +//! The general idea is to use a combination of edge case testing and randomized fuzz testing. The |
| 7 | +//! edge case testing is crucial for checking cases like where both inputs are equal or equal to |
| 8 | +//! special values such as `i128::MIN`, which is unlikely for the random fuzzer by itself to |
| 9 | +//! encounter. The randomized fuzz testing is specially designed to cover wide swaths of search |
| 10 | +//! space in as few iterations as possible. See `fuzz_values` in `testcrate/tests/misc.rs` for an |
| 11 | +//! example. |
| 12 | +//! |
| 13 | +//! Some floating point tests are disabled for specific architectures, because they do not have |
| 14 | +//! correct rounding. |
1 | 15 | #![no_std]
|
| 16 | + |
| 17 | +use compiler_builtins::float::Float; |
| 18 | +use compiler_builtins::int::Int; |
| 19 | + |
| 20 | +use rand_xoshiro::rand_core::{RngCore, SeedableRng}; |
| 21 | +use rand_xoshiro::Xoshiro128StarStar; |
| 22 | + |
| 23 | +/// Sets the number of fuzz iterations run for most tests. In practice, the vast majority of bugs |
| 24 | +/// are caught by the edge case testers. Most of the remaining bugs triggered by more complex |
| 25 | +/// sequences are caught well within 10_000 fuzz iterations. For classes of algorithms like division |
| 26 | +/// that are vulnerable to rare edge cases, we want 1_000_000 iterations to be more confident. In |
| 27 | +/// practical CI, however, we only want to run the more strenuous test once to catch algorithmic |
| 28 | +/// level bugs, and run the 10_000 iteration test on most targets. Target-dependent bugs are likely |
| 29 | +/// to involve miscompilation and misconfiguration that is likely to break algorithms in quickly |
| 30 | +/// caught ways. We choose to configure `N = 1_000_000` iterations for `x86_64` targets (and if |
| 31 | +/// debug assertions are disabled. Tests without `--release` would take too long) which are likely |
| 32 | +/// to have fast hardware, and run `N = 10_000` for all other targets. |
| 33 | +pub const N: u32 = if cfg!(target_arch = "x86_64") && !cfg!(debug_assertions) { |
| 34 | + 1_000_000 |
| 35 | +} else { |
| 36 | + 10_000 |
| 37 | +}; |
| 38 | + |
| 39 | +/// Random fuzzing step. When run several times, it results in excellent fuzzing entropy such as: |
| 40 | +/// 11110101010101011110111110011111 |
| 41 | +/// 10110101010100001011101011001010 |
| 42 | +/// 1000000000000000 |
| 43 | +/// 10000000000000110111110000001010 |
| 44 | +/// 1111011111111101010101111110101 |
| 45 | +/// 101111111110100000000101000000 |
| 46 | +/// 10000000110100000000100010101 |
| 47 | +/// 1010101010101000 |
| 48 | +fn fuzz_step<I: Int>(rng: &mut Xoshiro128StarStar, x: &mut I) { |
| 49 | + let ones = !I::ZERO; |
| 50 | + let bit_indexing_mask: u32 = I::BITS - 1; |
| 51 | + // It happens that all the RNG we need can come from one call. 7 bits are needed to index a |
| 52 | + // worst case 128 bit integer, and there are 4 indexes that need to be made plus 4 bits for |
| 53 | + // selecting operations |
| 54 | + let rng32 = rng.next_u32(); |
| 55 | + |
| 56 | + // Randomly OR, AND, and XOR randomly sized and shifted continuous strings of |
| 57 | + // ones with `lhs` and `rhs`. |
| 58 | + let r0 = bit_indexing_mask & rng32; |
| 59 | + let r1 = bit_indexing_mask & (rng32 >> 7); |
| 60 | + let mask = ones.wrapping_shl(r0).rotate_left(r1); |
| 61 | + match (rng32 >> 14) % 4 { |
| 62 | + 0 => *x |= mask, |
| 63 | + 1 => *x &= mask, |
| 64 | + // both 2 and 3 to make XORs as common as ORs and ANDs combined |
| 65 | + _ => *x ^= mask, |
| 66 | + } |
| 67 | + |
| 68 | + // Alternating ones and zeros (e.x. 0b1010101010101010). This catches second-order |
| 69 | + // problems that might occur for algorithms with two modes of operation (potentially |
| 70 | + // there is some invariant that can be broken and maintained via alternating between modes, |
| 71 | + // breaking the algorithm when it reaches the end). |
| 72 | + let mut alt_ones = I::ONE; |
| 73 | + for _ in 0..(I::BITS / 2) { |
| 74 | + alt_ones <<= 2; |
| 75 | + alt_ones |= I::ONE; |
| 76 | + } |
| 77 | + let r0 = bit_indexing_mask & (rng32 >> 16); |
| 78 | + let r1 = bit_indexing_mask & (rng32 >> 23); |
| 79 | + let mask = alt_ones.wrapping_shl(r0).rotate_left(r1); |
| 80 | + match rng32 >> 30 { |
| 81 | + 0 => *x |= mask, |
| 82 | + 1 => *x &= mask, |
| 83 | + _ => *x ^= mask, |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +// We need macros like this, because `#![no_std]` prevents us from using iterators |
| 88 | +macro_rules! edge_cases { |
| 89 | + ($I:ident, $case:ident, $inner:block) => { |
| 90 | + for i0 in 0..$I::FUZZ_NUM { |
| 91 | + let mask_lo = (!$I::UnsignedInt::ZERO).wrapping_shr($I::FUZZ_LENGTHS[i0] as u32); |
| 92 | + for i1 in i0..I::FUZZ_NUM { |
| 93 | + let mask_hi = |
| 94 | + (!$I::UnsignedInt::ZERO).wrapping_shl($I::FUZZ_LENGTHS[i1 - i0] as u32); |
| 95 | + let $case = I::from_unsigned(mask_lo & mask_hi); |
| 96 | + $inner |
| 97 | + } |
| 98 | + } |
| 99 | + }; |
| 100 | +} |
| 101 | + |
| 102 | +/// Feeds a series of fuzzing inputs to `f`. The fuzzer first uses an algorithm designed to find |
| 103 | +/// edge cases, followed by a more random fuzzer that runs `n` times. |
| 104 | +pub fn fuzz<I: Int, F: FnMut(I)>(n: u32, mut f: F) { |
| 105 | + // edge case tester. Calls `f` 210 times for u128. |
| 106 | + // zero gets skipped by the loop |
| 107 | + f(I::ZERO); |
| 108 | + edge_cases!(I, case, { |
| 109 | + f(case); |
| 110 | + }); |
| 111 | + |
| 112 | + // random fuzzer |
| 113 | + let mut rng = Xoshiro128StarStar::seed_from_u64(0); |
| 114 | + let mut x: I = Int::ZERO; |
| 115 | + for _ in 0..n { |
| 116 | + fuzz_step(&mut rng, &mut x); |
| 117 | + f(x) |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +/// The same as `fuzz`, except `f` has two inputs. |
| 122 | +pub fn fuzz_2<I: Int, F: Fn(I, I)>(n: u32, f: F) { |
| 123 | + // Check cases where the first and second inputs are zero. Both call `f` 210 times for `u128`. |
| 124 | + edge_cases!(I, case, { |
| 125 | + f(I::ZERO, case); |
| 126 | + }); |
| 127 | + edge_cases!(I, case, { |
| 128 | + f(case, I::ZERO); |
| 129 | + }); |
| 130 | + // Nested edge tester. Calls `f` 44100 times for `u128`. |
| 131 | + edge_cases!(I, case0, { |
| 132 | + edge_cases!(I, case1, { |
| 133 | + f(case0, case1); |
| 134 | + }) |
| 135 | + }); |
| 136 | + |
| 137 | + // random fuzzer |
| 138 | + let mut rng = Xoshiro128StarStar::seed_from_u64(0); |
| 139 | + let mut x: I = I::ZERO; |
| 140 | + let mut y: I = I::ZERO; |
| 141 | + for _ in 0..n { |
| 142 | + fuzz_step(&mut rng, &mut x); |
| 143 | + fuzz_step(&mut rng, &mut y); |
| 144 | + f(x, y) |
| 145 | + } |
| 146 | +} |
| 147 | + |
| 148 | +/// Tester for shift functions |
| 149 | +pub fn fuzz_shift<I: Int, F: Fn(I, u32)>(f: F) { |
| 150 | + // Shift functions are very simple and do not need anything other than shifting a small |
| 151 | + // set of random patterns for every fuzz length. |
| 152 | + let mut rng = Xoshiro128StarStar::seed_from_u64(0); |
| 153 | + let mut x: I = Int::ZERO; |
| 154 | + for i in 0..I::FUZZ_NUM { |
| 155 | + fuzz_step(&mut rng, &mut x); |
| 156 | + f(x, Int::ZERO); |
| 157 | + f(x, I::FUZZ_LENGTHS[i] as u32); |
| 158 | + } |
| 159 | +} |
| 160 | + |
| 161 | +fn fuzz_float_step<F: Float>(rng: &mut Xoshiro128StarStar, f: &mut F) { |
| 162 | + let rng32 = rng.next_u32(); |
| 163 | + // we need to fuzz the different parts of the float separately, because the masking on larger |
| 164 | + // significands will tend to set the exponent to all ones or all zeros frequently |
| 165 | + |
| 166 | + // sign bit fuzzing |
| 167 | + let sign = (rng32 & 1) != 0; |
| 168 | + |
| 169 | + // exponent fuzzing. Only 4 bits for the selector needed. |
| 170 | + let ones = (F::Int::ONE << F::EXPONENT_BITS) - F::Int::ONE; |
| 171 | + let r0 = (rng32 >> 1) % F::EXPONENT_BITS; |
| 172 | + let r1 = (rng32 >> 5) % F::EXPONENT_BITS; |
| 173 | + // custom rotate shift. Note that `F::Int` is unsigned, so we can shift right without smearing |
| 174 | + // the sign bit. |
| 175 | + let mask = if r1 == 0 { |
| 176 | + ones.wrapping_shr(r0) |
| 177 | + } else { |
| 178 | + let tmp = ones.wrapping_shr(r0); |
| 179 | + (tmp.wrapping_shl(r1) | tmp.wrapping_shr(F::EXPONENT_BITS - r1)) & ones |
| 180 | + }; |
| 181 | + let mut exp = (f.repr() & F::EXPONENT_MASK) >> F::SIGNIFICAND_BITS; |
| 182 | + match (rng32 >> 9) % 4 { |
| 183 | + 0 => exp |= mask, |
| 184 | + 1 => exp &= mask, |
| 185 | + _ => exp ^= mask, |
| 186 | + } |
| 187 | + |
| 188 | + // significand fuzzing |
| 189 | + let mut sig = f.repr() & F::SIGNIFICAND_MASK; |
| 190 | + fuzz_step(rng, &mut sig); |
| 191 | + sig &= F::SIGNIFICAND_MASK; |
| 192 | + |
| 193 | + *f = F::from_parts(sign, exp, sig); |
| 194 | +} |
| 195 | + |
| 196 | +macro_rules! float_edge_cases { |
| 197 | + ($F:ident, $case:ident, $inner:block) => { |
| 198 | + for exponent in [ |
| 199 | + F::Int::ZERO, |
| 200 | + F::Int::ONE, |
| 201 | + F::Int::ONE << (F::EXPONENT_BITS / 2), |
| 202 | + (F::Int::ONE << (F::EXPONENT_BITS - 1)) - F::Int::ONE, |
| 203 | + F::Int::ONE << (F::EXPONENT_BITS - 1), |
| 204 | + (F::Int::ONE << (F::EXPONENT_BITS - 1)) + F::Int::ONE, |
| 205 | + (F::Int::ONE << F::EXPONENT_BITS) - F::Int::ONE, |
| 206 | + ] |
| 207 | + .iter() |
| 208 | + { |
| 209 | + for significand in [ |
| 210 | + F::Int::ZERO, |
| 211 | + F::Int::ONE, |
| 212 | + F::Int::ONE << (F::SIGNIFICAND_BITS / 2), |
| 213 | + (F::Int::ONE << (F::SIGNIFICAND_BITS - 1)) - F::Int::ONE, |
| 214 | + F::Int::ONE << (F::SIGNIFICAND_BITS - 1), |
| 215 | + (F::Int::ONE << (F::SIGNIFICAND_BITS - 1)) + F::Int::ONE, |
| 216 | + (F::Int::ONE << F::SIGNIFICAND_BITS) - F::Int::ONE, |
| 217 | + ] |
| 218 | + .iter() |
| 219 | + { |
| 220 | + for sign in [false, true].iter() { |
| 221 | + let $case = F::from_parts(*sign, *exponent, *significand); |
| 222 | + $inner |
| 223 | + } |
| 224 | + } |
| 225 | + } |
| 226 | + }; |
| 227 | +} |
| 228 | + |
| 229 | +pub fn fuzz_float<F: Float, E: Fn(F)>(n: u32, f: E) { |
| 230 | + float_edge_cases!(F, case, { |
| 231 | + f(case); |
| 232 | + }); |
| 233 | + |
| 234 | + // random fuzzer |
| 235 | + let mut rng = Xoshiro128StarStar::seed_from_u64(0); |
| 236 | + let mut x = F::ZERO; |
| 237 | + for _ in 0..n { |
| 238 | + fuzz_float_step(&mut rng, &mut x); |
| 239 | + f(x); |
| 240 | + } |
| 241 | +} |
| 242 | + |
| 243 | +pub fn fuzz_float_2<F: Float, E: Fn(F, F)>(n: u32, f: E) { |
| 244 | + float_edge_cases!(F, case0, { |
| 245 | + float_edge_cases!(F, case1, { |
| 246 | + f(case0, case1); |
| 247 | + }); |
| 248 | + }); |
| 249 | + |
| 250 | + // random fuzzer |
| 251 | + let mut rng = Xoshiro128StarStar::seed_from_u64(0); |
| 252 | + let mut x = F::ZERO; |
| 253 | + let mut y = F::ZERO; |
| 254 | + for _ in 0..n { |
| 255 | + fuzz_float_step(&mut rng, &mut x); |
| 256 | + fuzz_float_step(&mut rng, &mut y); |
| 257 | + f(x, y) |
| 258 | + } |
| 259 | +} |
0 commit comments