xpile 0.1.1

Polyglot transpile workbench (Python/C/C++/Rust/Ruchy/Lean ↔ Rust/Ruchy/PTX/WGSL/SPIR-V) with provable contracts at every layer.
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
//! Runtime stratum fixtures (PMAT-267).
//!
//! Closes the "Run=1 demo fixture" caveat (audit-design.md §4) for one
//! Layer-1 contract at a time. Each fixture in this file is a real
//! Runtime-stratum oracle vote: it compiles the transpiled Rust output
//! through `rustc` and *executes* it against a property-style sweep,
//! asserting behavioral equivalence between Python source semantics
//! and the emitted Rust.
//!
//! Mechanism: a tiny deterministic LCG produces input pairs inside the
//! emitted binary itself, so one rustc invocation amortizes the cost
//! of thousands of runtime checks. The LCG seed is fixed so failures
//! are reproducible; CI sees the same trace every run.
//!
//! ## What this is the first of
//!
//! Per the xpile-spec.md §29 status and the audit-design.md §4 caveat,
//! every existing contract reached §14.4 N-of-M QUORUM at *Bronze
//! tier* (Lean refinement theorem + Kani BMC harness — Sem + Sym
//! strata) but no contract had a real Runtime stratum vote. This file
//! ships that vote for `py-int-arith-v1`, making it the FIRST contract
//! with full §14.4 Sem + Sym + Run coverage. Future PRs extend the
//! same pattern to other Layer-1 / Layer-2 contracts.
//!
//! ## Why a single in-binary sweep, not 1000 separate test cases
//!
//! Two reasons:
//! 1. **Speed.** rustc takes ~1s per invocation; 1000 inputs as
//!    separate `#[test]`s would be 1000s, impractical at workspace
//!    test time. One in-binary loop = one rustc + ~ms of native
//!    execution.
//! 2. **Provenance.** The fixture as-emitted is exactly the binary
//!    users would ship; testing it as a unit is closer to the actual
//!    deployment configuration.

use std::path::PathBuf;
use std::process::Command;

fn fixture(name: &str) -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures")
        .join(name)
}

fn run_xpile(args: &[&str]) -> std::process::Output {
    let bin = PathBuf::from(env!("CARGO_BIN_EXE_xpile"));
    Command::new(bin)
        .args(args)
        .output()
        .expect("spawn xpile binary")
}

fn xpile_transpile_to_rust(fixture_name: &str) -> String {
    let py = fixture(fixture_name);
    let out = run_xpile(&["transpile", py.to_str().unwrap()]);
    assert!(
        out.status.success(),
        "xpile failed on {fixture_name}: stderr={}",
        String::from_utf8_lossy(&out.stderr)
    );
    String::from_utf8(out.stdout).expect("stdout is UTF-8")
}

fn rust_target_dir(name: &str) -> PathBuf {
    let dir = std::env::temp_dir().join("xpile-runtime-strata").join(name);
    std::fs::create_dir_all(&dir).expect("create temp dir");
    dir
}

/// Compile + execute the merged source. Returns `Ok` if the binary
/// exits 0, `Err(stderr+stdout)` otherwise.
fn build_and_run(name: &str, merged: &str) -> Result<(), String> {
    if Command::new("rustc").arg("--version").output().is_err() {
        // Skipping on CI runners without rustc is fine — the test
        // body is a no-op rather than a panic. CI gates that DO have
        // rustc will still catch regressions.
        return Ok(());
    }
    let dir = rust_target_dir(name);
    let file = dir.join(format!("{name}.rs"));
    std::fs::write(&file, merged).expect("write merged rust");
    let bin = dir.join(name);
    let compile = Command::new("rustc")
        .arg("--edition=2021")
        .arg("-O")
        .arg("-o")
        .arg(&bin)
        .arg(&file)
        .output()
        .expect("spawn rustc");
    if !compile.status.success() {
        return Err(format!(
            "rustc failed:\n=== source ===\n{merged}\n=== stderr ===\n{}",
            String::from_utf8_lossy(&compile.stderr)
        ));
    }
    let run = Command::new(&bin).output().expect("spawn binary");
    if !run.status.success() {
        return Err(format!(
            "binary {name} exited non-zero (assertion tripped):\n=== source ===\n{merged}\n=== stdout ===\n{}\n=== stderr ===\n{}",
            String::from_utf8_lossy(&run.stdout),
            String::from_utf8_lossy(&run.stderr),
        ));
    }
    Ok(())
}

/// PMAT-267 — FIRST contract Runtime stratum fixture.
///
/// Asserts the emitted Rust `add(a, b)` matches Python integer
/// arithmetic semantics under `i64::checked_add` across a 4096-pair
/// LCG-generated sweep. Each pair is classified by whether `checked_add`
/// returns `Some` or `None`:
///
/// * **Some path (no overflow):** transpiled `add` must equal
///   `a.wrapping_add(b)` (which equals `a + b` in the non-overflow
///   case). The transpiled code uses `checked_add(...).expect(...)`
///   per PMAT-002, so success means it didn't panic.
/// * **None path (overflow):** the transpiled `add` MUST panic. This
///   is the runtime equivalent of the C-PY-INT-ARITH precondition that
///   overflow halts rather than wrapping silently.
///
/// Both paths are exercised by spawning the binary twice (once per
/// path) and comparing observed behavior.
///
/// Closes the audit-design.md §4 "Run=1 demo fixture" caveat for
/// C-PY-INT-ARITH. Sets the precedent for Runtime stratum fixtures on
/// `xlate-py-list-to-vec-v1`, `xlate-rust-fn-to-lean-thm-v1`, and other
/// Layer-1 contracts.
#[test]
fn py_int_arith_runtime_stratum_add_matches_python_semantics() {
    let transpiled = xpile_transpile_to_rust("add.py");

    // Happy path: 4096 LCG-generated (a, b) pairs where checked_add
    // succeeds. The driver computes `add(a, b)` and asserts it equals
    // `a.wrapping_add(b)`. We *cannot* use `a + b` directly because
    // that would panic on debug overflow — we only feed it pairs that
    // can't overflow. The reference is `wrapping_add` to avoid that
    // concern entirely.
    let driver = r#"
fn main() {
    // Linear congruential generator — same Numerical Recipes
    // constants Rust's old `rand` test code used. Period 2^64; for
    // 4096 samples we won't approach it. Seed fixed for reproducibility.
    //
    // We shift each LCG output right by 2 (sign-extending) before
    // casting to i64 so the pair (a, b) has |a|+|b| ≤ i64::MAX/2.
    // That makes overflow impossible — every pair is on the happy
    // path and the sweep tests the full 4096 cases. The overflow arm
    // is exercised by the companion test.
    let mut state: u64 = 0xdead_beef_cafe_f00d;
    for i in 0..4096u64 {
        state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        let a: i64 = (state as i64) >> 2;
        state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        let b: i64 = (state as i64) >> 2;
        let expected = a.checked_add(b)
            .expect("sweep generator should not produce overflowing pairs");
        let got = add(a, b);
        assert_eq!(got, expected, "iter {i}: add({a}, {b}) gave {got}, expected {expected}");
    }
    println!("ok happy path: 4096/4096");
}
"#;

    let merged = format!("{transpiled}\n\n{driver}\n");
    build_and_run("py_int_arith_runtime_happy", &merged)
        .expect("runtime stratum: add(a, b) must match checked_add semantics across the sweep");
}

/// PMAT-268 — Runtime-stratum sweep for branching + unary negation
/// (abs_val).
///
/// `abs_val.py` lowers to a Rust function that exercises `if/else`
/// control flow + unary `-`. The sweep runs 4096 LCG-generated inputs
/// (right-shifted by 1 so negation can't overflow) through the
/// transpiled function and verifies the result matches the
/// hand-written reference. The `i64::MIN.wrapping_abs() == i64::MIN`
/// edge case is intentionally excluded by the shift — the contract
/// for that case is unspecified at v0.1.0.
#[test]
fn py_int_arith_runtime_stratum_abs_val_matches_sign_branch() {
    let transpiled = xpile_transpile_to_rust("abs_val.py");

    let driver = r#"
fn main() {
    let mut state: u64 = 0xdead_beef_cafe_f00d;
    for i in 0..4096u64 {
        state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        let x: i64 = (state as i64) >> 1;
        let expected: i64 = if x < 0 { -x } else { x };
        let got = abs_val(x);
        assert_eq!(got, expected, "iter {i}: abs_val({x}) gave {got}, expected {expected}");
    }
    println!("ok abs_val sweep: 4096/4096");
}
"#;

    let merged = format!("{transpiled}\n\n{driver}\n");
    build_and_run("py_int_arith_runtime_abs_val", &merged)
        .expect("runtime stratum: abs_val(x) must match Python sign-branching semantics");
}

/// PMAT-268 — Runtime-stratum sweep for binary recursion (fib).
///
/// `fib.py` lowers to a recursive Rust function with TWO recursive
/// calls per invocation. The sweep computes the first 24 Fibonacci
/// numbers (small enough that exponential recursion is tractable but
/// well past the boundary cases) and asserts each matches an
/// iteratively-computed reference. Verifies recursion + branch +
/// addition end-to-end.
#[test]
fn py_int_arith_runtime_stratum_fib_matches_iterative_reference() {
    let transpiled = xpile_transpile_to_rust("fib.py");

    let driver = r#"
fn fib_iter(n: i64) -> i64 {
    if n <= 1 { return n; }
    let (mut a, mut b) = (0i64, 1i64);
    for _ in 1..n { let t = b; b = a + b; a = t; }
    b
}

fn main() {
    for n in 0..24i64 {
        let expected = fib_iter(n);
        let got = fib(n);
        assert_eq!(got, expected, "fib({n}) gave {got}, expected {expected}");
    }
    println!("ok fib recursion: 24/24");
}
"#;

    let merged = format!("{transpiled}\n\n{driver}\n");
    build_and_run("py_int_arith_runtime_fib", &merged)
        .expect("runtime stratum: fib(n) must match iterative reference for n in 0..24");
}

/// PMAT-268 — Runtime-stratum sweep for modulo + tail recursion (gcd).
///
/// `gcd.py` exercises `%` (modulo) plus structural recursion. The
/// sweep generates 1024 LCG pairs of positive i64s clamped to
/// `[1, i64::MAX/4]` (so recursion depth stays bounded and modulo
/// is well-defined) and compares against an iterative Euclidean GCD.
/// GCD on negatives is out-of-scope — Python and Rust `%` semantics
/// differ on sign.
#[test]
fn py_int_arith_runtime_stratum_gcd_matches_euclidean_reference() {
    let transpiled = xpile_transpile_to_rust("gcd.py");

    let driver = r#"
fn gcd_iter(mut a: i64, mut b: i64) -> i64 {
    while b != 0 {
        let t = b;
        b = a % b;
        a = t;
    }
    a
}

fn main() {
    let mut state: u64 = 0xdead_beef_cafe_f00d;
    for i in 0..1024u64 {
        state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        let a: i64 = ((state >> 2) as i64).abs().max(1).min(i64::MAX / 4);
        state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        let b: i64 = ((state >> 2) as i64).abs().max(1).min(i64::MAX / 4);
        let expected = gcd_iter(a, b);
        let got = gcd(a, b);
        assert_eq!(got, expected, "iter {i}: gcd({a}, {b}) gave {got}, expected {expected}");
    }
    println!("ok gcd sweep: 1024/1024");
}
"#;

    let merged = format!("{transpiled}\n\n{driver}\n");
    build_and_run("py_int_arith_runtime_gcd", &merged)
        .expect("runtime stratum: gcd(a, b) must match iterative Euclidean reference");
}

/// Companion test — verifies the OVERFLOW arm of the C-PY-INT-ARITH
/// contract. Per PMAT-002, the transpiled code uses
/// `checked_add(...).expect(...)` so adding `i64::MAX + 1` must panic.
/// The driver `main` invokes the function unconditionally on a known
/// overflowing pair, asserts the binary exits non-zero, and the
/// `build_and_run` helper inverts the expected exit status.
#[test]
fn py_int_arith_runtime_stratum_overflow_panics() {
    let transpiled = xpile_transpile_to_rust("add.py");

    // Driver invokes add(i64::MAX, 1) which MUST overflow. Expected
    // behavior: process aborts with a panic. The build_and_run helper
    // treats non-zero exit as failure, so we invert by wrapping the
    // call in `std::panic::catch_unwind` and exit 0 IFF the inner call
    // panicked (which is the contract'd behavior).
    let driver = r#"
fn main() {
    let result = std::panic::catch_unwind(|| {
        add(i64::MAX, 1)
    });
    match result {
        Err(_) => {
            // Expected: checked_add overflowed and `.expect(...)` panicked.
            println!("ok overflow panicked");
            std::process::exit(0);
        }
        Ok(v) => {
            eprintln!("contract violated: add(i64::MAX, 1) returned {v}, expected panic");
            std::process::exit(1);
        }
    }
}
"#;

    let merged = format!("{transpiled}\n\n{driver}\n");
    build_and_run("py_int_arith_runtime_overflow", &merged)
        .expect("runtime stratum: add(i64::MAX, 1) must panic per checked_add semantics");
}

/// PMAT-271 — Runtime-stratum sweep for `for-in-range` desugaring
/// (for_sum.py).
///
/// `for_sum.py` defines three functions exercising the PMAT-007
/// for-in-range → while-loop desugaring:
///
/// * `for_sum(n)`: `range(n)` — single-arg range with implicit start=0
///   and step=1.
/// * `range_with_start(a, b)`: `range(a, b)` — two-arg range with
///   explicit start.
/// * `range_with_step(stop)`: `range(0, stop, 2)` — three-arg range
///   with step != 1.
///
/// The fixed-input tests in transpile_e2e.rs cover ~6 cases. This
/// sweep covers 200 contiguous `n` values for `for_sum` + 100 LCG
/// pairs for `range_with_start` + 100 LCG `stop` values for
/// `range_with_step`, all compared against hand-written Rust
/// references. Verifies the desugaring is correct across the
/// boundary cases the existing tests don't hit (n=0 edge, negative
/// ranges that should produce empty loops, large strides).
///
/// All inputs clamped to small bounds so checked-arithmetic overflow
/// can't fire — overflow is exercised by the `overflow_panics`
/// fixture, not here.
#[test]
fn py_int_arith_runtime_stratum_for_loop_desugaring_matches_reference() {
    let transpiled = xpile_transpile_to_rust("for_sum.py");

    let driver = r#"
fn ref_for_sum(n: i64) -> i64 {
    // CPython `range(n)` is empty for n <= 0; sum over 0..n.
    if n <= 0 { return 0; }
    (0..n).sum()
}

fn ref_range_with_start(a: i64, b: i64) -> i64 {
    // CPython `range(a, b)` is empty if a >= b.
    if a >= b { return 0; }
    (a..b).sum()
}

fn ref_range_with_step(stop: i64) -> i64 {
    // CPython `range(0, stop, 2)` is empty if stop <= 0; otherwise
    // sums 0, 2, 4, ... < stop.
    if stop <= 0 { return 0; }
    (0..stop).step_by(2).sum()
}

fn main() {
    // Sweep 1: for_sum(n) over n in 0..200 — contiguous coverage of
    // small n including the n=0 edge.
    for n in 0i64..200 {
        let expected = ref_for_sum(n);
        let got = for_sum(n);
        assert_eq!(got, expected, "for_sum({n}) gave {got}, expected {expected}");
    }

    // Sweep 2: range_with_start(a, b) over 100 LCG pairs clamped to
    // small range. Some pairs will have a >= b (empty loop case).
    let mut state: u64 = 0xdead_beef_cafe_f00d;
    for i in 0..100u64 {
        state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        let a: i64 = (state as i64) % 100; // [-99..99]
        state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        let b: i64 = (state as i64) % 100;
        let expected = ref_range_with_start(a, b);
        let got = range_with_start(a, b);
        assert_eq!(got, expected, "iter {i}: range_with_start({a}, {b}) gave {got}, expected {expected}");
    }

    // Sweep 3: range_with_step(stop) over 100 LCG values clamped to
    // small positive bound — including stop <= 0 boundary.
    for i in 0..100u64 {
        state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
        let stop: i64 = (state as i64) % 200; // [-199..199]
        let expected = ref_range_with_step(stop);
        let got = range_with_step(stop);
        assert_eq!(got, expected, "iter {i}: range_with_step({stop}) gave {got}, expected {expected}");
    }

    println!("ok for-loop sweeps: 200 + 100 + 100");
}
"#;

    let merged = format!("{transpiled}\n\n{driver}\n");
    build_and_run("py_int_arith_runtime_for_loop", &merged).expect(
        "runtime stratum: for-in-range desugaring must match CPython range semantics across the sweep",
    );
}