compiler: Retain explicit argument borrows through call evaluation
bcc4a622ad4d2e8c1d643ffdb138bafa273e948db85c8e3fafe315d19496b4ed
1 parent
2c67cf47
lib/std/lang/resolver.rad
+40 -3
| 924 | 924 | precise: bool, |
|
| 925 | 925 | } |
|
| 926 | 926 | ||
| 927 | 927 | /// A reference binding that protects its source for one lexical scope. |
|
| 928 | 928 | record LocalLoan: Copy { |
|
| 929 | - | /// Local symbol through which the source can be accessed. |
|
| 930 | - | binding: *unsafe mut Symbol, |
|
| 929 | + | /// Local symbol that provides access, or nil for a pending call argument. |
|
| 930 | + | binding: ?*unsafe mut Symbol, |
|
| 931 | 931 | /// Storage retained by the reference. |
|
| 932 | 932 | place: BorrowPlace, |
|
| 933 | 933 | /// Whether other reads of the source are excluded. |
|
| 934 | 934 | exclusive: bool, |
|
| 935 | 935 | } |
| 10272 | 10272 | throw emitError(checker.resolver, node, ErrorKind::BorrowConflict(root.name)); |
|
| 10273 | 10273 | } |
|
| 10274 | 10274 | } |
|
| 10275 | 10275 | for i in 0..checker.localLen { |
|
| 10276 | 10276 | let loan = checker.locals[i]; |
|
| 10277 | + | let mut throughBinding = false; |
|
| 10278 | + | if let binding = loan.binding { |
|
| 10279 | + | set throughBinding = usesLocalLoan(checker.resolver, node, binding); |
|
| 10280 | + | } |
|
| 10277 | 10281 | if (exclusive or loan.exclusive) and placesOverlap(&place, &loan.place) |
|
| 10278 | - | and not usesLocalLoan(checker.resolver, node, loan.binding) |
|
| 10282 | + | and not throughBinding |
|
| 10279 | 10283 | { |
|
| 10280 | 10284 | throw emitError(checker.resolver, node, ErrorKind::BorrowConflict(root.name)); |
|
| 10281 | 10285 | } |
|
| 10282 | 10286 | } |
|
| 10283 | 10287 | } |
| 10639 | 10643 | checker: &mut LinearChecker 'arena 'checking, |
|
| 10640 | 10644 | env: &mut LinearEnv, |
|
| 10641 | 10645 | node: *ast::Node, |
|
| 10642 | 10646 | call: ast::Call, |
|
| 10643 | 10647 | ) throws (ResolveError) where 'arena: 'checking { |
|
| 10648 | + | let localStart = checker.localLen; |
|
| 10644 | 10649 | match checker.resolver.nodeData.entries[node.id].extra { |
|
| 10645 | 10650 | case NodeExtra::SliceAppend { .. }, NodeExtra::SliceDelete { .. } => { |
|
| 10646 | 10651 | let case ast::NodeValue::FieldAccess(access) = call.callee.value |
|
| 10647 | 10652 | else panic "slice mutation without receiver"; |
|
| 10648 | 10653 | try checkPatternLoan(checker, access.parent); |
| 10710 | 10715 | set placesLen += 1; |
|
| 10711 | 10716 | } |
|
| 10712 | 10717 | } |
|
| 10713 | 10718 | if types::isReference(receiverClass) { |
|
| 10714 | 10719 | try checkLinearNode(checker, env, access.parent, LinearUse::Borrow); |
|
| 10720 | + | if createsExplicitBorrow(access.parent) { |
|
| 10721 | + | try retainCallLoan(checker, access.parent, receiverMutable); |
|
| 10722 | + | } |
|
| 10715 | 10723 | } else if receiverClass == types::PointerClass::Owned { |
|
| 10716 | 10724 | try checkLinearNode(checker, env, access.parent, LinearUse::Consume); |
|
| 10717 | 10725 | } |
|
| 10718 | 10726 | } |
|
| 10719 | 10727 | } |
| 10741 | 10749 | if isBorrowedReferenceParameter(expected) { |
|
| 10742 | 10750 | try checkLinearNode(checker, env, arg, LinearUse::Borrow); |
|
| 10743 | 10751 | } else { |
|
| 10744 | 10752 | try checkLinearNode(checker, env, arg, LinearUse::Consume); |
|
| 10745 | 10753 | } |
|
| 10754 | + | if isRefType(expected) and createsExplicitBorrow(arg) { |
|
| 10755 | + | try retainCallLoan(checker, arg, argExclusive); |
|
| 10756 | + | } |
|
| 10746 | 10757 | } |
|
| 10758 | + | set checker.localLen = localStart; |
|
| 10747 | 10759 | if *info.returnType == Type::Never and info.throwList.len == 0 { |
|
| 10748 | 10760 | set env.terminated = true; |
|
| 10749 | 10761 | } |
|
| 10750 | 10762 | } |
|
| 10751 | 10763 | ||
| 10764 | + | /// Return whether evaluating an argument creates an explicit address borrow. |
|
| 10765 | + | fn createsExplicitBorrow(node: *ast::Node) -> bool { |
|
| 10766 | + | match node.value { |
|
| 10767 | + | case ast::NodeValue::AddressOf(_) => return true, |
|
| 10768 | + | case ast::NodeValue::As(cast) => return createsExplicitBorrow(cast.value), |
|
| 10769 | + | case ast::NodeValue::RegionApply { value, .. } => return createsExplicitBorrow(value), |
|
| 10770 | + | else => return false, |
|
| 10771 | + | } |
|
| 10772 | + | } |
|
| 10773 | + | ||
| 10774 | + | /// Protect explicit address arguments until their call begins. |
|
| 10775 | + | unsafe fn retainCallLoan 'arena 'checking ( |
|
| 10776 | + | checker: &mut LinearChecker 'arena 'checking, node: *ast::Node, exclusive: bool |
|
| 10777 | + | ) throws (ResolveError) where 'arena: 'checking { |
|
| 10778 | + | let place = borrowPlace(checker.resolver, node); |
|
| 10779 | + | if place.root == nil { |
|
| 10780 | + | return; |
|
| 10781 | + | } |
|
| 10782 | + | if checker.localLen >= MAX_LINEAR_BINDINGS { |
|
| 10783 | + | throw emitError(checker.resolver, node, ErrorKind::Internal); |
|
| 10784 | + | } |
|
| 10785 | + | set checker.locals[checker.localLen] = LocalLoan { binding: nil, place, exclusive }; |
|
| 10786 | + | set checker.localLen += 1; |
|
| 10787 | + | } |
|
| 10788 | + | ||
| 10752 | 10789 | /// Check a pattern conditional. Linear scrutinees require an exhaustive match. |
|
| 10753 | 10790 | unsafe fn checkLinearIfLet 'arena 'checking ( |
|
| 10754 | 10791 | checker: &mut LinearChecker 'arena 'checking, |
|
| 10755 | 10792 | env: &mut LinearEnv, |
|
| 10756 | 10793 | node: *ast::Node, |
lib/std/lang/resolver/tests/regions.rad
+34 -0
| 3 | 3 | use std::mem; |
|
| 4 | 4 | use std::testing; |
|
| 5 | 5 | use std::lang::types; |
|
| 6 | 6 | use std::lang::resolver; |
|
| 7 | 7 | ||
| 8 | + | /// Earlier reference arguments protect their storage during later arguments. |
|
| 9 | + | @test unsafe fn testRegionalCallArgumentLoans() throws (testing::TestError) { |
|
| 10 | + | for program in [ |
|
| 11 | + | "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &mut u32, n: u32) {} fn f 'r (p: &'r mut u32) { outer(&mut *p, inner(p)); }", |
|
| 12 | + | "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &u32, n: u32) {} fn f 'r (p: &'r mut u32) { outer(&*p, inner(p)); }", |
|
| 13 | + | "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &mut u32, n: u32) {} unsafe fn f 'r (p: &'r mut u32) { outer(&mut *p, inner(p)); }", |
|
| 14 | + | "record R { n: u32 } fn (r: &mut R) call(n: u32) {} fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn f 'r (r: &'r mut R) { (&mut *r).call(inner(&mut r.n)); }", |
|
| 15 | + | ] { |
|
| 16 | + | let mut arena = super::testArena(); |
|
| 17 | + | let storage: 'test = &mut arena in { |
|
| 18 | + | let mut res = super::testResolver(storage); |
|
| 19 | + | let result = try super::resolveProgramStr(&mut res, program); |
|
| 20 | + | let error = try super::expectError(&result); |
|
| 21 | + | let case resolver::ErrorKind::BorrowConflict(_) = error.kind |
|
| 22 | + | else throw testing::TestError::Failed; |
|
| 23 | + | } |
|
| 24 | + | } |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | /// Call loans allow shared reads, separate fields, and access after the call. |
|
| 28 | + | @test unsafe fn testRegionalCallArgumentLoanScopes() throws (testing::TestError) { |
|
| 29 | + | for program in [ |
|
| 30 | + | "fn read(p: &u32) -> u32 { return *p; } fn outer(p: &u32, n: u32) {} fn f 'r (p: &'r mut u32) { outer(&*p, read(p)); set *p = 3; }", |
|
| 31 | + | "record R { a: u32, b: u32 } fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &mut u32, n: u32) {} fn f 'r (r: &'r mut R) { outer(&mut r.a, inner(&mut r.b)); set r.a = 3; }", |
|
| 32 | + | ] { |
|
| 33 | + | let mut arena = super::testArena(); |
|
| 34 | + | let storage: 'test = &mut arena in { |
|
| 35 | + | let mut res = super::testResolver(storage); |
|
| 36 | + | let result = try super::resolveProgramStr(&mut res, program); |
|
| 37 | + | try super::expectNoErrors(&result); |
|
| 38 | + | } |
|
| 39 | + | } |
|
| 40 | + | } |
|
| 41 | + | ||
| 8 | 42 | /// Equal region spellings in separate functions have distinct identities. |
|
| 9 | 43 | @test unsafe fn testRegionIdentities() throws (testing::TestError) { |
|
| 10 | 44 | let mut testArena1 = super::testArena(); |
|
| 11 | 45 | let testStorage1: 'test1 = &mut testArena1 in { |
|
| 12 | 46 | let mut res = super::testResolver(testStorage1); |
lib/std/vec.rad
+6 -4
| 65 | 65 | /// Returns false if the vector is at capacity. |
|
| 66 | 66 | export unsafe fn push(vec: &mut RawVec, elem: &opaque) -> bool { |
|
| 67 | 67 | if vec.len >= capacity(vec) { |
|
| 68 | 68 | return false; |
|
| 69 | 69 | } |
|
| 70 | - | let off: u32 = vec.len * vec.stride; |
|
| 71 | - | copyBytes(&mut vec.data[off..off + vec.stride], @sliceOf(elem as &u8, vec.stride)); |
|
| 70 | + | let stride = vec.stride; |
|
| 71 | + | let off: u32 = vec.len * stride; |
|
| 72 | + | copyBytes(&mut vec.data[off..off + stride], @sliceOf(elem as &u8, stride)); |
|
| 72 | 73 | set vec.len += 1; |
|
| 73 | 74 | ||
| 74 | 75 | return true; |
|
| 75 | 76 | } |
|
| 76 | 77 |
| 95 | 96 | /// Returns false if index is out of bounds. |
|
| 96 | 97 | export unsafe fn put(vec: &mut RawVec, index: u32, elem: &opaque) -> bool { |
|
| 97 | 98 | if index >= vec.len { |
|
| 98 | 99 | return false; |
|
| 99 | 100 | } |
|
| 100 | - | let off: u32 = index * vec.stride; |
|
| 101 | - | copyBytes(&mut vec.data[off..off + vec.stride], @sliceOf(elem as &u8, vec.stride)); |
|
| 101 | + | let stride = vec.stride; |
|
| 102 | + | let off: u32 = index * stride; |
|
| 103 | + | copyBytes(&mut vec.data[off..off + stride], @sliceOf(elem as &u8, stride)); |
|
| 102 | 104 | ||
| 103 | 105 | return true; |
|
| 104 | 106 | } |
|
| 105 | 107 | ||
| 106 | 108 | /// Copy bytes from source to destination. |
test/tests/mutref.loop.bug.rad
+8 -4
| 19 | 19 | /// merge tries to `load` through it as if it were a pointer. |
|
| 20 | 20 | fn testZeroIter(n: u32) -> u32 { |
|
| 21 | 21 | let mut val: u32 = 42; |
|
| 22 | 22 | let mut i: u32 = 0; |
|
| 23 | 23 | while i < n { |
|
| 24 | - | store(&mut val, val + 1); |
|
| 24 | + | let next = val + 1; |
|
| 25 | + | store(&mut val, next); |
|
| 25 | 26 | set i += 1; |
|
| 26 | 27 | } |
|
| 27 | 28 | return val; |
|
| 28 | 29 | } |
|
| 29 | 30 | ||
| 30 | 31 | /// Multiple iterations: accumulate via &mut pointer in a loop. |
|
| 31 | 32 | fn testMultiIter() -> u32 { |
|
| 32 | 33 | let mut acc: u32 = 0; |
|
| 33 | 34 | let mut i: u32 = 0; |
|
| 34 | 35 | while i < 5 { |
|
| 35 | - | store(&mut acc, acc + i); |
|
| 36 | + | let next = acc + i; |
|
| 37 | + | store(&mut acc, next); |
|
| 36 | 38 | set i += 1; |
|
| 37 | 39 | } |
|
| 38 | 40 | return acc; |
|
| 39 | 41 | } |
|
| 40 | 42 |
| 42 | 44 | fn testMultipleVars() -> u32 { |
|
| 43 | 45 | let mut a: u32 = 0; |
|
| 44 | 46 | let mut b: u32 = 100; |
|
| 45 | 47 | let mut i: u32 = 0; |
|
| 46 | 48 | while i < 3 { |
|
| 47 | - | store(&mut a, a + 1); |
|
| 48 | - | store(&mut b, b - 1); |
|
| 49 | + | let nextA = a + 1; |
|
| 50 | + | store(&mut a, nextA); |
|
| 51 | + | let nextB = b - 1; |
|
| 52 | + | store(&mut b, nextB); |
|
| 49 | 53 | set i += 1; |
|
| 50 | 54 | } |
|
| 51 | 55 | return a + b; |
|
| 52 | 56 | } |
|
| 53 | 57 |