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.
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 | + | } |