//! returns: 0
//! Test that returning aggregate types via hidden return buffer does not
//! corrupt the caller's stack. Modifying a returned aggregate must not affect
//! other locals or previously returned values.

record Pair {
    a: u32,
    b: u32,
}

record Triple {
    x: u32,
    y: u32,
    z: u32,
}

/// Return a Pair aggregate (non-throwing, aggregate return buffer).
fn makePair(a: u32, b: u32) -> Pair {
    return Pair { a, b };
}

/// Return a Triple aggregate.
fn makeTriple(x: u32, y: u32, z: u32) -> Triple {
    return Triple { x, y, z };
}

/// Return aggregate from a throwing function (result return buffer).
fn makePairOrFail(a: u32, b: u32, fail: bool) -> Pair throws (u32) {
    if fail {
        throw 99;
    }
    return Pair { a, b };
}

/// Modify a returned aggregate and verify caller's locals are intact.
fn testReturnDoesNotCorruptLocals() -> i32 {
    let sentinel: u32 = 0xDEAD;
    let p: Pair = makePair(10, 20);
    let sentinel2: u32 = 0xBEEF;

    assert p.a == 10;
    assert p.b == 20;
    assert sentinel == 0xDEAD;
    assert sentinel2 == 0xBEEF;
    return 0;
}

/// Two consecutive aggregate returns must not interfere.
fn testMultipleReturns() -> i32 {
    let p1: Pair = makePair(1, 2);
    let p2: Pair = makePair(3, 4);

    assert p1.a == 1;
    assert p1.b == 2;
    assert p2.a == 3;
    assert p2.b == 4;
    return 0;
}

/// Mutating a returned aggregate must not corrupt the stack.
fn testMutateReturnedAggregate() -> i32 {
    let before: u32 = 42;
    let mut p: Pair = makePair(10, 20);
    let after: u32 = 99;

    p.a = 100;
    p.b = 200;

    assert before == 42;
    assert after == 99;
    assert p.a == 100;
    assert p.b == 200;
    return 0;
}

/// Aggregate return inside a loop must not corrupt accumulator state.
fn testAggregateReturnInLoop() -> i32 {
    let mut total: u32 = 0;
    let mut idx: u32 = 0;
    while idx < 5 {
        let p: Pair = makePair(idx, idx + 10);
        total += p.a + p.b;
        idx += 1;
    }
    // total = sum(i + i+10 for i in 0..5) = sum(2i+10) = 2*(0+1+2+3+4) + 50 = 20 + 50 = 70
    assert total == 70;
    return 0;
}

/// Throwing function returning aggregate (result buffer) must not corrupt stack.
fn testThrowingAggregateReturn() -> i32 {
    let sentinel: u32 = 0xCAFE;
    let p: Pair = try! makePairOrFail(10, 20, false);
    let sentinel2: u32 = 0xFACE;

    assert p.a == 10;
    assert p.b == 20;
    assert sentinel == 0xCAFE;
    assert sentinel2 == 0xFACE;
    return 0;
}

/// Catching a thrown error from aggregate-returning function.
fn testThrowingAggregateCatch() -> i32 {
    let sentinel: u32 = 0xBEEF;
    try makePairOrFail(10, 20, true) catch {};
    assert sentinel == 0xBEEF;
    return 0;
}

/// Triple (12 bytes) aggregate return.
fn testTripleReturn() -> i32 {
    let t: Triple = makeTriple(100, 200, 300);
    assert t.x == 100;
    assert t.y == 200;
    assert t.z == 300;
    return 0;
}

/// Multiple aggregate calls interleaved with scalar work.
fn testInterleavedCalls() -> i32 {
    let p1: Pair = makePair(5, 6);
    let scalar: u32 = p1.a + p1.b;
    let t: Triple = makeTriple(scalar, scalar + 1, scalar + 2);
    let p2: Pair = makePair(t.x, t.z);

    assert scalar == 11;
    assert t.x == 11;
    assert t.y == 12;
    assert t.z == 13;
    assert p2.a == 11;
    assert p2.b == 13;
    return 0;
}

@default fn main() -> i32 {
    let r1: i32 = testReturnDoesNotCorruptLocals();
    if r1 != 0 {
        return 10 + r1;
    }

    let r2: i32 = testMultipleReturns();
    if r2 != 0 {
        return 20 + r2;
    }

    let r3: i32 = testMutateReturnedAggregate();
    if r3 != 0 {
        return 30 + r3;
    }

    let r4: i32 = testAggregateReturnInLoop();
    if r4 != 0 {
        return 40 + r4;
    }

    let r5: i32 = testThrowingAggregateReturn();
    if r5 != 0 {
        return 50 + r5;
    }

    let r6: i32 = testThrowingAggregateCatch();
    if r6 != 0 {
        return 60 + r6;
    }

    let r7: i32 = testTripleReturn();
    if r7 != 0 {
        return 70 + r7;
    }

    let r8: i32 = testInterleavedCalls();
    if r8 != 0 {
        return 80 + r8;
    }
    return 0;
}
