Snapshot aggregate arguments before later evaluation

03643fc52334d25c62b16504609d72843599f440462b584f233e7d1e72f84fc9
Aggregate call arguments are represented in IL as addresses to places.
Keeping those addresses live while evaluating later arguments allowed
a later side effect to mutate an earlier by-value argument before the
callee observed it.

Classify resolved arguments by `effectiveType` and copy aggregate
places into snapshots before lowering subsequent arguments across
function, method, and trait calls.
Alexis Sellier committed ago 1 parent 8064a56d
lib/std/lang/lower.rad +27 -3
6637 6637
    let argOffset: u32 = 1 if requiresReturnParam(methodFnType) else 0;
6638 6638
    let args = try allocVals(self, call.args.len + 1 + argOffset);
6639 6639
    set args[argOffset] = il::Val::Reg(dataReg);
6640 6640
6641 6641
    for arg, i in call.args {
6642 -
        set args[i + 1 + argOffset] = try lowerExpr(self, arg);
6642 +
        set args[i + 1 + argOffset] = try lowerCallArg(
6643 +
            self, arg, i + 1 < call.args.len
6644 +
        );
6643 6645
    }
6644 6646
    return try emitCallValue(self, il::Val::Reg(fnPtrReg), methodFnType, args);
6645 6647
}
6646 6648
6649 +
/// Lower a call argument, snapshotting aggregate place expressions before
6650 +
/// evaluating later arguments. Aggregates are represented by addresses in the
6651 +
/// IL, so retaining the original address would let a later argument mutation
6652 +
/// change the value already supplied for this argument.
6653 +
fn lowerCallArg(self: *mut FnLowerer, arg: *ast::Node, hasLater: bool) -> il::Val
6654 +
    throws (LowerError)
6655 +
{
6656 +
    let val = try lowerExpr(self, arg);
6657 +
6658 +
    if hasLater and ast::isPlaceExpr(arg) {
6659 +
        let argType = try effectiveType(self, arg);
6660 +
        if isAggregateType(argType) {
6661 +
            return try emitStackVal(self, argType, val);
6662 +
        }
6663 +
    }
6664 +
    return val;
6665 +
}
6666 +
6647 6667
/// Emit a function call with return-parameter and small-aggregate handling.
6648 6668
///
6649 6669
/// All call lowering paths (regular, trait method, standalone method) converge
6650 6670
/// here after preparing the callee value, function type, and argument array.
6651 6671
/// The `args` slice must already include a slot at index zero for the hidden
6757 6777
    // Build args: optional return param slot + receiver + user args.
6758 6778
    let argOffset: u32 = 1 if requiresReturnParam(fnInfo) else 0;
6759 6779
    let args = try allocVals(self, call.args.len + 1 + argOffset);
6760 6780
    set args[argOffset] = receiverVal;
6761 6781
    for arg, i in call.args {
6762 -
        set args[i + 1 + argOffset] = try lowerExpr(self, arg);
6782 +
        set args[i + 1 + argOffset] = try lowerCallArg(
6783 +
            self, arg, i + 1 < call.args.len
6784 +
        );
6763 6785
    }
6764 6786
    return try emitCallValue(self, il::Val::FnAddr(qualName), fnInfo, args);
6765 6787
}
6766 6788
6767 6789
/// Check if a call is to a compiler intrinsic and lower it directly.
6846 6868
    };
6847 6869
    let callee = try lowerCallee(self, call.callee);
6848 6870
    let offset: u32 = 1 if requiresReturnParam(fnInfo) else 0;
6849 6871
    let args = try allocVals(self, call.args.len + offset);
6850 6872
    for arg, i in call.args {
6851 -
        set args[i + offset] = try lowerExpr(self, arg);
6873 +
        set args[i + offset] = try lowerCallArg(
6874 +
            self, arg, i + 1 < call.args.len
6875 +
        );
6852 6876
    }
6853 6877
6854 6878
    return try emitCallValue(self, callee, fnInfo, args);
6855 6879
}
6856 6880
test/tests/call.aggregate.arg.snapshot.rad added +25 -0
1 +
//! returns: 0
2 +
//! Aggregate call arguments must capture their value when evaluated, before
3 +
//! later arguments run and mutate the source object.
4 +
5 +
record Pair {
6 +
    x: i32,
7 +
    y: i32,
8 +
}
9 +
10 +
fn mutate(pair: *mut Pair) -> i32 {
11 +
    set pair.x = 99;
12 +
    return 0;
13 +
}
14 +
15 +
fn first(pair: Pair, ignored: i32) -> i32 {
16 +
    return pair.x;
17 +
}
18 +
19 +
@default fn main() -> i32 {
20 +
    let mut pair = Pair { x: 7, y: 8 };
21 +
    let observed = first(pair, mutate(&mut pair));
22 +
    assert observed == 7;
23 +
    assert pair.x == 99;
24 +
    return 0;
25 +
}