1use proptest::prelude::*;
15use synth_core::WasmOp;
16#[cfg(feature = "arm")]
17use synth_synthesis::{ArmOp, Operand2, Reg};
18
19pub struct CompilerProperties;
21
22impl CompilerProperties {
23 pub fn arithmetic_overflow_consistency() -> impl Strategy<Value = (i32, i32)> {
28 (any::<i32>(), any::<i32>())
29 }
30
31 pub fn bitwise_bit_precision() -> impl Strategy<Value = (u32, u32)> {
36 (any::<u32>(), any::<u32>())
37 }
38
39 pub fn comparison_correctness() -> impl Strategy<Value = (i32, i32)> {
41 (any::<i32>(), any::<i32>())
42 }
43
44 pub fn shift_edge_cases() -> impl Strategy<Value = (i32, u32)> {
49 (any::<i32>(), any::<u32>())
50 }
51
52 pub fn division_by_zero() -> impl Strategy<Value = i32> {
56 any::<i32>()
57 }
58
59 pub fn optimization_soundness() -> impl Strategy<Value = Vec<WasmOp>> {
64 prop::collection::vec(Self::arbitrary_wasm_op(), 1..10)
66 }
67
68 fn arbitrary_wasm_op() -> impl Strategy<Value = WasmOp> {
70 prop_oneof![
71 Just(WasmOp::I32Add),
72 Just(WasmOp::I32Sub),
73 Just(WasmOp::I32Mul),
74 Just(WasmOp::I32And),
75 Just(WasmOp::I32Or),
76 Just(WasmOp::I32Xor),
77 Just(WasmOp::I32Shl),
78 Just(WasmOp::I32ShrU),
79 Just(WasmOp::I32ShrS),
80 any::<i32>().prop_map(WasmOp::I32Const),
81 ]
82 }
83
84 #[cfg(feature = "arm")]
86 #[allow(dead_code)]
87 fn arbitrary_arm_op() -> impl Strategy<Value = ArmOp> {
88 let reg_strategy = prop_oneof![Just(Reg::R0), Just(Reg::R1), Just(Reg::R2), Just(Reg::R3),];
89
90 prop_oneof![
91 (
92 reg_strategy.clone(),
93 reg_strategy.clone(),
94 reg_strategy.clone()
95 )
96 .prop_map(|(rd, rn, rm)| ArmOp::Add {
97 rd,
98 rn,
99 op2: Operand2::Reg(rm),
100 }),
101 (
102 reg_strategy.clone(),
103 reg_strategy.clone(),
104 reg_strategy.clone()
105 )
106 .prop_map(|(rd, rn, rm)| ArmOp::Sub {
107 rd,
108 rn,
109 op2: Operand2::Reg(rm),
110 }),
111 (
112 reg_strategy.clone(),
113 reg_strategy.clone(),
114 reg_strategy.clone()
115 )
116 .prop_map(|(rd, rn, rm)| ArmOp::Mul { rd, rn, rm }),
117 (
118 reg_strategy.clone(),
119 reg_strategy.clone(),
120 reg_strategy.clone()
121 )
122 .prop_map(|(rd, rn, rm)| ArmOp::And {
123 rd,
124 rn,
125 op2: Operand2::Reg(rm),
126 }),
127 ]
128 }
129}
130
131#[cfg(test)]
133mod arithmetic_tests {
134 use super::*;
135
136 proptest! {
137 #[test]
138 fn test_add_overflow_consistency(a in any::<i32>(), b in any::<i32>()) {
139 let wasm_result = a.wrapping_add(b);
141
142 let arm_result = a.wrapping_add(b);
144
145 assert_eq!(wasm_result, arm_result,
146 "ADD overflow behavior differs: WASM={}, ARM={}", wasm_result, arm_result);
147 }
148
149 #[test]
150 fn test_sub_overflow_consistency(a in any::<i32>(), b in any::<i32>()) {
151 let wasm_result = a.wrapping_sub(b);
152 let arm_result = a.wrapping_sub(b);
153
154 assert_eq!(wasm_result, arm_result,
155 "SUB overflow behavior differs: WASM={}, ARM={}", wasm_result, arm_result);
156 }
157
158 #[test]
159 fn test_mul_overflow_consistency(a in any::<i32>(), b in any::<i32>()) {
160 let wasm_result = a.wrapping_mul(b);
161 let arm_result = a.wrapping_mul(b);
162
163 assert_eq!(wasm_result, arm_result,
164 "MUL overflow behavior differs: WASM={}, ARM={}", wasm_result, arm_result);
165 }
166 }
167}
168
169#[cfg(test)]
171mod bitwise_tests {
172 use super::*;
173
174 proptest! {
175 #[test]
176 fn test_and_bit_precision(a in any::<u32>(), b in any::<u32>()) {
177 let wasm_result = a & b;
178 let arm_result = a & b;
179
180 assert_eq!(wasm_result, arm_result,
181 "AND bit precision differs: WASM={:032b}, ARM={:032b}", wasm_result, arm_result);
182 }
183
184 #[test]
185 fn test_or_bit_precision(a in any::<u32>(), b in any::<u32>()) {
186 let wasm_result = a | b;
187 let arm_result = a | b;
188
189 assert_eq!(wasm_result, arm_result);
190 }
191
192 #[test]
193 fn test_xor_bit_precision(a in any::<u32>(), b in any::<u32>()) {
194 let wasm_result = a ^ b;
195 let arm_result = a ^ b;
196
197 assert_eq!(wasm_result, arm_result);
198 }
199 }
200}
201
202#[cfg(test)]
204mod shift_tests {
205 use super::*;
206
207 proptest! {
208 #[test]
209 fn test_shl_edge_cases(value in any::<i32>(), shift in any::<u32>()) {
210 let wasm_shift = shift % 32;
212 let wasm_result = value.wrapping_shl(wasm_shift);
213
214 let arm_result = value.wrapping_shl(wasm_shift);
216
217 assert_eq!(wasm_result, arm_result,
218 "SHL edge case: value={}, shift={}, WASM={}, ARM={}",
219 value, shift, wasm_result, arm_result);
220 }
221
222 #[test]
223 fn test_shr_logical_edge_cases(value in any::<u32>(), shift in any::<u32>()) {
224 let wasm_shift = shift % 32;
225 let wasm_result = value.wrapping_shr(wasm_shift);
226 let arm_result = value.wrapping_shr(wasm_shift);
227
228 assert_eq!(wasm_result, arm_result);
229 }
230
231 #[test]
232 fn test_shr_arithmetic_edge_cases(value in any::<i32>(), shift in any::<u32>()) {
233 let wasm_shift = shift % 32;
234 let wasm_result = value.wrapping_shr(wasm_shift);
235 let arm_result = value.wrapping_shr(wasm_shift);
236
237 assert_eq!(wasm_result, arm_result);
238 }
239 }
240}
241
242#[cfg(test)]
244mod comparison_tests {
245 use super::*;
246
247 proptest! {
248 #[test]
249 fn test_eq_correctness(a in any::<i32>(), b in any::<i32>()) {
250 let wasm_result = if a == b { 1 } else { 0 };
251 let arm_result = if a == b { 1 } else { 0 };
252
253 assert_eq!(wasm_result, arm_result);
254 }
255
256 #[test]
257 fn test_ne_correctness(a in any::<i32>(), b in any::<i32>()) {
258 let wasm_result = if a != b { 1 } else { 0 };
259 let arm_result = if a != b { 1 } else { 0 };
260
261 assert_eq!(wasm_result, arm_result);
262 }
263
264 #[test]
265 fn test_lt_signed_correctness(a in any::<i32>(), b in any::<i32>()) {
266 let wasm_result = if a < b { 1 } else { 0 };
267 let arm_result = if a < b { 1 } else { 0 };
268
269 assert_eq!(wasm_result, arm_result);
270 }
271
272 #[test]
273 fn test_lt_unsigned_correctness(a in any::<u32>(), b in any::<u32>()) {
274 let wasm_result = if a < b { 1 } else { 0 };
275 let arm_result = if a < b { 1 } else { 0 };
276
277 assert_eq!(wasm_result, arm_result);
278 }
279
280 #[test]
281 fn test_le_signed_correctness(a in any::<i32>(), b in any::<i32>()) {
282 let wasm_result = if a <= b { 1 } else { 0 };
283 let arm_result = if a <= b { 1 } else { 0 };
284
285 assert_eq!(wasm_result, arm_result);
286 }
287
288 #[test]
289 fn test_gt_signed_correctness(a in any::<i32>(), b in any::<i32>()) {
290 let wasm_result = if a > b { 1 } else { 0 };
291 let arm_result = if a > b { 1 } else { 0 };
292
293 assert_eq!(wasm_result, arm_result);
294 }
295
296 #[test]
297 fn test_ge_signed_correctness(a in any::<i32>(), b in any::<i32>()) {
298 let wasm_result = if a >= b { 1 } else { 0 };
299 let arm_result = if a >= b { 1 } else { 0 };
300
301 assert_eq!(wasm_result, arm_result);
302 }
303 }
304}
305
306#[cfg(test)]
308mod optimization_tests {
309 use super::*;
310
311 #[test]
312 fn test_constant_folding_soundness() {
313 let _original = [WasmOp::I32Const(5), WasmOp::I32Const(3), WasmOp::I32Add];
316 let _optimized = [WasmOp::I32Const(8)];
317
318 }
322
323 #[test]
324 fn test_dead_code_elimination_soundness() {
325 let _original = [WasmOp::I32Const(42), WasmOp::Return, WasmOp::I32Const(99)];
328 let _optimized = [WasmOp::I32Const(42), WasmOp::Return];
329
330 }
333
334 proptest! {
335 #[test]
336 fn test_algebraic_simplification_soundness(a in any::<i32>()) {
337 let with_identity = a.wrapping_add(0);
339 assert_eq!(with_identity, a);
340
341 let with_mul_identity = a.wrapping_mul(1);
343 assert_eq!(with_mul_identity, a);
344
345 let with_mul_zero = a.wrapping_mul(0);
347 assert_eq!(with_mul_zero, 0);
348 }
349
350 #[test]
351 fn test_strength_reduction_soundness(a in any::<i32>()) {
352 let mul_result = a.wrapping_mul(2);
354 let shift_result = a.wrapping_shl(1);
355 assert_eq!(mul_result, shift_result);
356
357 let mul_result = a.wrapping_mul(4);
359 let shift_result = a.wrapping_shl(2);
360 assert_eq!(mul_result, shift_result);
361
362 let mul_result = a.wrapping_mul(8);
364 let shift_result = a.wrapping_shl(3);
365 assert_eq!(mul_result, shift_result);
366 }
367 }
368}
369
370#[cfg(test)]
372mod memory_tests {
373 use super::*;
374
375 proptest! {
376 #[test]
377 fn test_memory_bounds_checking(offset in 0u32..1024, size in 1u32..64) {
378 let address = offset;
380 let end = address.wrapping_add(size);
381
382 assert!(address <= end || size == 0);
386 }
387
388 #[test]
389 fn test_stack_pointer_validity(sp_offset in -256i32..256) {
390 let base_sp = 0x20010000u32; let new_sp = (base_sp as i32).wrapping_add(sp_offset) as u32;
394
395 assert!(new_sp > 0x20000000 && new_sp < 0x20020000);
398 }
399 }
400}