|
| 1 | +// Arithmetic overflow/wrapping behavior tests. |
| 2 | +// |
| 3 | +// WASM integers use two's complement wrapping arithmetic with no overflow traps. |
| 4 | +// These tests verify that boundary arithmetic (max+1, min-1, negating min, etc.) |
| 5 | +// wraps correctly for i32, i64, and u32 types. |
| 6 | +// |
| 7 | +// Expected wrapping behavior: |
| 8 | +// i32_max_plus_one: 2147483647 + 1 = -2147483648 (wraps to i32::MIN) |
| 9 | +// i32_min_minus_one: -2147483648 - 1 = 2147483647 (wraps to i32::MAX) |
| 10 | +// i64_max_plus_one: i64::MAX + 1 = i64::MIN (wraps to i64::MIN) |
| 11 | +// i64_min_minus_one: i64::MIN - 1 = i64::MAX (wraps to i64::MAX) |
| 12 | +// u32_max_plus_one: 4294967295 + 1 = 0 (wraps to zero) |
| 13 | +// i32_mul_overflow: 2147483647 * 2 = -2 (truncated to i32) |
| 14 | +// i32_neg_min: -(-2147483648) = -2147483648 (negating MIN wraps to MIN) |
| 15 | +// i64_neg_min: -(i64::MIN) = i64::MIN (negating MIN wraps to MIN) |
| 16 | +// |
| 17 | +// Total: 6 binary expressions, 2 prefix unary (neg), 11 constant definitions. |
| 18 | + |
| 19 | +#[cfg(test)] |
| 20 | +mod arith_overflow_tests { |
| 21 | + use crate::utils::{ |
| 22 | + assert_wasms_modules_equivalence, assert_wat_equivalence, get_test_file_path, |
| 23 | + get_test_wasm_path, wasm_codegen, |
| 24 | + }; |
| 25 | + |
| 26 | + #[test] |
| 27 | + fn arith_overflow_test() { |
| 28 | + cov_mark::check_count!(wasm_codegen_emit_binary_expression, 6); |
| 29 | + cov_mark::check_count!(wasm_codegen_emit_prefix_unary_expression, 2); |
| 30 | + cov_mark::check_count!(wasm_codegen_emit_unary_neg, 2); |
| 31 | + cov_mark::check_count!(wasm_codegen_emit_constant_definition, 11); |
| 32 | + let test_name = "arith_overflow"; |
| 33 | + let test_file_path = get_test_file_path(module_path!(), test_name); |
| 34 | + let source_code = std::fs::read_to_string(&test_file_path) |
| 35 | + .unwrap_or_else(|_| panic!("Failed to read test file: {test_file_path:?}")); |
| 36 | + let actual = wasm_codegen(&source_code); |
| 37 | + inf_wasmparser::validate(&actual) |
| 38 | + .unwrap_or_else(|e| panic!("Generated Wasm module is invalid: {}", e)); |
| 39 | + let expected = get_test_wasm_path(module_path!(), test_name); |
| 40 | + let expected = std::fs::read(&expected) |
| 41 | + .unwrap_or_else(|_| panic!("Failed to read expected wasm file for test: {test_name}")); |
| 42 | + assert_wasms_modules_equivalence(&expected, &actual); |
| 43 | + assert_wat_equivalence(&actual, module_path!(), test_name); |
| 44 | + } |
| 45 | + |
| 46 | + #[test] |
| 47 | + fn arith_overflow_execution_test() { |
| 48 | + use wasmtime::{Engine, Module, Store, TypedFunc}; |
| 49 | + |
| 50 | + let test_name = "arith_overflow"; |
| 51 | + let test_file_path = get_test_file_path(module_path!(), test_name); |
| 52 | + let source_code = std::fs::read_to_string(&test_file_path) |
| 53 | + .unwrap_or_else(|_| panic!("Failed to read test file: {test_file_path:?}")); |
| 54 | + let wasm_bytes = wasm_codegen(&source_code); |
| 55 | + |
| 56 | + let engine = Engine::default(); |
| 57 | + let module = Module::new(&engine, &wasm_bytes) |
| 58 | + .unwrap_or_else(|e| panic!("Failed to create Wasm module: {e}")); |
| 59 | + let mut store = Store::new(&engine, ()); |
| 60 | + let instance = wasmtime::Instance::new(&mut store, &module, &[]) |
| 61 | + .unwrap_or_else(|e| panic!("Failed to instantiate Wasm module: {e}")); |
| 62 | + |
| 63 | + macro_rules! call { |
| 64 | + ($name:expr, $ty:ty, $args:expr, $expected:expr) => {{ |
| 65 | + let f: TypedFunc<_, $ty> = instance |
| 66 | + .get_typed_func(&mut store, $name) |
| 67 | + .unwrap_or_else(|e| panic!("Failed to get '{}': {e}", $name)); |
| 68 | + let result = f |
| 69 | + .call(&mut store, $args) |
| 70 | + .unwrap_or_else(|e| panic!("Call to '{}' failed: {e}", $name)); |
| 71 | + assert_eq!(result, $expected, "{}({:?}) expected {:?}", $name, $args, $expected); |
| 72 | + }}; |
| 73 | + } |
| 74 | + |
| 75 | + // --- i32 wrapping --- |
| 76 | + |
| 77 | + // i32::MAX + 1 wraps to i32::MIN |
| 78 | + call!("i32_max_plus_one", i32, (), i32::MIN); |
| 79 | + // i32::MIN - 1 wraps to i32::MAX |
| 80 | + call!("i32_min_minus_one", i32, (), i32::MAX); |
| 81 | + |
| 82 | + // --- i64 wrapping --- |
| 83 | + |
| 84 | + // i64::MAX + 1 wraps to i64::MIN |
| 85 | + call!("i64_max_plus_one", i64, (), i64::MIN); |
| 86 | + // i64::MIN - 1 wraps to i64::MAX |
| 87 | + call!("i64_min_minus_one", i64, (), i64::MAX); |
| 88 | + |
| 89 | + // --- u32 wrapping (returned as i32 in WASM) --- |
| 90 | + |
| 91 | + // u32::MAX + 1 wraps to 0 |
| 92 | + call!("u32_max_plus_one", i32, (), 0_i32); |
| 93 | + |
| 94 | + // --- Multiplication overflow --- |
| 95 | + |
| 96 | + // 2147483647 * 2 = 4294967294 = -2 as i32 |
| 97 | + call!("i32_mul_overflow", i32, (), -2_i32); |
| 98 | + |
| 99 | + // --- Negation of MIN wraps back to MIN --- |
| 100 | + |
| 101 | + // -i32::MIN = i32::MIN (two's complement: no positive representation) |
| 102 | + call!("i32_neg_min", i32, (), i32::MIN); |
| 103 | + // -i64::MIN = i64::MIN |
| 104 | + call!("i64_neg_min", i64, (), i64::MIN); |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +/// Test data regeneration helper. |
| 109 | +/// |
| 110 | +/// Regenerates the expected `.wasm` and `.wat` golden files from the current compiler output. |
| 111 | +/// Run with `--ignored` flag: |
| 112 | +/// |
| 113 | +/// ```bash |
| 114 | +/// cargo test -p inference-tests codegen::wasm::arith_overflow::regenerate -- --ignored |
| 115 | +/// ``` |
| 116 | +#[cfg(test)] |
| 117 | +mod regenerate { |
| 118 | + use crate::utils::{get_test_data_path, regenerate_wat, wasm_codegen}; |
| 119 | + |
| 120 | + fn test_dir() -> std::path::PathBuf { |
| 121 | + get_test_data_path() |
| 122 | + .join("codegen") |
| 123 | + .join("wasm") |
| 124 | + .join("arith_overflow") |
| 125 | + } |
| 126 | + |
| 127 | + #[test] |
| 128 | + #[ignore] |
| 129 | + fn regenerate_arith_overflow_wasm() { |
| 130 | + let dir = test_dir(); |
| 131 | + let source_code = std::fs::read_to_string(dir.join("arith_overflow.inf")) |
| 132 | + .expect("Failed to read arith_overflow.inf"); |
| 133 | + let actual = wasm_codegen(&source_code); |
| 134 | + inf_wasmparser::validate(&actual) |
| 135 | + .unwrap_or_else(|e| panic!("Generated Wasm module is invalid: {}", e)); |
| 136 | + let wasm_path = dir.join("arith_overflow.wasm"); |
| 137 | + std::fs::write(&wasm_path, &actual) |
| 138 | + .unwrap_or_else(|e| panic!("Failed to write {}: {e}", wasm_path.display())); |
| 139 | + println!( |
| 140 | + "Regenerated: {} ({} bytes)", |
| 141 | + wasm_path.display(), |
| 142 | + actual.len() |
| 143 | + ); |
| 144 | + regenerate_wat(&actual, &dir, "arith_overflow"); |
| 145 | + } |
| 146 | +} |
0 commit comments