compiler: Initialize loan and loop checker scratch state
44c1050ddc2bbb7d4c41911ead751e9fa11a57bba518b9b96252e52bef5dd2c1
1 parent
ecf232b6
lib/std/lang/resolver.rad
+51 -28
| 978 | 978 | /// `enterLinearLoop` initializes each slot before it increases `loopDepth`. |
|
| 979 | 979 | record LinearChecker: 'arena + 'checking where 'arena: 'checking { |
|
| 980 | 980 | /// Resolver that owns the symbols and diagnostics. |
|
| 981 | 981 | resolver: &'checking mut Resolver 'arena, |
|
| 982 | 982 | /// Regional projections discovered in this function. |
|
| 983 | - | regional: [RegionalLoan; MAX_REGIONAL_LOANS], |
|
| 984 | - | /// Number of initialized regional loan entries. |
|
| 983 | + | regional: [?RegionalLoan; MAX_REGIONAL_LOANS], |
|
| 984 | + | /// Number of active regional loan entries. |
|
| 985 | 985 | regionalLen: u32, |
|
| 986 | 986 | /// Named regions active at the current source location. |
|
| 987 | 987 | regions: ?*RegionScope, |
|
| 988 | 988 | /// Regional loans carried to each loop's next iteration. |
|
| 989 | 989 | loopBackLoans: [u64; MAX_LINEAR_LOOP_DEPTH], |
| 991 | 991 | loopExitLoans: [u64; MAX_LINEAR_LOOP_DEPTH], |
|
| 992 | 992 | /// Regions active at each loop's entry and exit. |
|
| 993 | 993 | loopRegions: [?*RegionScope; MAX_LINEAR_LOOP_DEPTH], |
|
| 994 | 994 | /// Source places protected by active pattern references. |
|
| 995 | 995 | loans: [BorrowPlace; MAX_LINEAR_BINDINGS], |
|
| 996 | - | /// Number of initialized entries in `loans`. |
|
| 996 | + | /// Number of active entries in `loans`. |
|
| 997 | 997 | loanLen: u32, |
|
| 998 | 998 | /// Reference locals in active lexical scopes. |
|
| 999 | 999 | locals: [LocalLoan; MAX_LINEAR_BINDINGS], |
|
| 1000 | - | /// Number of initialized local loans. |
|
| 1000 | + | /// Number of active local loans. |
|
| 1001 | 1001 | localLen: u32, |
|
| 1002 | 1002 | /// Binding count at entry to each active loop. |
|
| 1003 | 1003 | loopMarks: [u32; MAX_LINEAR_LOOP_DEPTH], |
|
| 1004 | 1004 | /// Available bindings at entry to each active loop. |
|
| 1005 | 1005 | loopAvailable: [u64; MAX_LINEAR_LOOP_DEPTH], |
| 10006 | 10006 | for stmt in block.statements { |
|
| 10007 | 10007 | try visitDecl(res, stmt); |
|
| 10008 | 10008 | } |
|
| 10009 | 10009 | } |
|
| 10010 | 10010 | ||
| 10011 | + | /// Create a place with no storage root or field projections. |
|
| 10012 | + | fn emptyBorrowPlace() -> BorrowPlace { |
|
| 10013 | + | return BorrowPlace { root: nil, fields: [0; MAX_BORROW_FIELDS], len: 0, precise: true }; |
|
| 10014 | + | } |
|
| 10015 | + | ||
| 10016 | + | /// Create initialized loan and loop scratch state for one function. |
|
| 10017 | + | fn linearChecker 'arena 'checking ( |
|
| 10018 | + | resolver: &'checking mut Resolver 'arena, regions: ?*RegionScope |
|
| 10019 | + | ) -> LinearChecker 'arena 'checking where 'arena: 'checking { |
|
| 10020 | + | let place = emptyBorrowPlace(); |
|
| 10021 | + | return LinearChecker 'arena 'checking { |
|
| 10022 | + | resolver, |
|
| 10023 | + | regional: [nil; MAX_REGIONAL_LOANS], |
|
| 10024 | + | regionalLen: 0, |
|
| 10025 | + | regions, |
|
| 10026 | + | loopBackLoans: [0; MAX_LINEAR_LOOP_DEPTH], |
|
| 10027 | + | loopExitLoans: [0; MAX_LINEAR_LOOP_DEPTH], |
|
| 10028 | + | loopRegions: [nil; MAX_LINEAR_LOOP_DEPTH], |
|
| 10029 | + | loans: [place; MAX_LINEAR_BINDINGS], |
|
| 10030 | + | loanLen: 0, |
|
| 10031 | + | locals: [LocalLoan { binding: nil, place, exclusive: false }; MAX_LINEAR_BINDINGS], |
|
| 10032 | + | localLen: 0, |
|
| 10033 | + | loopMarks: [0; MAX_LINEAR_LOOP_DEPTH], |
|
| 10034 | + | loopAvailable: [0; MAX_LINEAR_LOOP_DEPTH], |
|
| 10035 | + | loopExitAvailable: [0; MAX_LINEAR_LOOP_DEPTH], |
|
| 10036 | + | loopHasNaturalExit: [false; MAX_LINEAR_LOOP_DEPTH], |
|
| 10037 | + | loopBreakSeen: [false; MAX_LINEAR_LOOP_DEPTH], |
|
| 10038 | + | loopDepth: 0, |
|
| 10039 | + | }; |
|
| 10040 | + | } |
|
| 10041 | + | ||
| 10011 | 10042 | /// Create an empty ownership environment with initialized binding slots. |
|
| 10012 | 10043 | fn linearEnv() -> LinearEnv { |
|
| 10013 | 10044 | return LinearEnv { |
|
| 10014 | 10045 | regionalLoans: 0, |
|
| 10015 | 10046 | symbols: [nil; MAX_LINEAR_BINDINGS], |
| 10209 | 10240 | place: BorrowPlace, |
|
| 10210 | 10241 | /// Whether accesses through other references are excluded. |
|
| 10211 | 10242 | exclusive: bool, |
|
| 10212 | 10243 | } |
|
| 10213 | 10244 | ||
| 10245 | + | /// Read an initialized regional loan from the active prefix. |
|
| 10246 | + | fn regionalLoan 'arena 'checking ( |
|
| 10247 | + | checker: &LinearChecker 'arena 'checking, index: u32 |
|
| 10248 | + | ) -> RegionalLoan where 'arena: 'checking { |
|
| 10249 | + | assert index < checker.regionalLen, "regionalLoan: index outside active prefix"; |
|
| 10250 | + | let loan = checker.regional[index] else panic "regionalLoan: missing active loan"; |
|
| 10251 | + | return loan; |
|
| 10252 | + | } |
|
| 10253 | + | ||
| 10214 | 10254 | /// Return whether a region identity is visible in a lexical environment. |
|
| 10215 | 10255 | fn regionInScope(scope: ?*RegionScope, regionId: u32) -> bool { |
|
| 10216 | 10256 | let mut cursor = scope; |
|
| 10217 | 10257 | while let current = cursor { |
|
| 10218 | 10258 | if regionIndex(current, regionId) <> nil { |
| 10226 | 10266 | /// Retain only loans whose regions remain active at a control-flow destination. |
|
| 10227 | 10267 | fn regionalLoansInScope 'arena 'checking (checker: &LinearChecker 'arena 'checking, mask: u64, scope: ?*RegionScope) -> u64 where 'arena: 'checking { |
|
| 10228 | 10268 | let mut result: u64 = 0; |
|
| 10229 | 10269 | for i in 0..checker.regionalLen { |
|
| 10230 | 10270 | let bit = (1 as u64) << (i as u64); |
|
| 10231 | - | if (mask & bit) <> 0 and regionInScope(scope, checker.regional[i].regionId) { |
|
| 10271 | + | if (mask & bit) <> 0 and regionInScope(scope, regionalLoan(checker, i).regionId) { |
|
| 10232 | 10272 | set result |= bit; |
|
| 10233 | 10273 | } |
|
| 10234 | 10274 | } |
|
| 10235 | 10275 | return result; |
|
| 10236 | 10276 | } |
| 10253 | 10293 | ) where 'arena: 'checking { |
|
| 10254 | 10294 | let oldLen = checker.regionalLen; |
|
| 10255 | 10295 | let mut mapping: [u64; MAX_REGIONAL_LOANS] = [0; MAX_REGIONAL_LOANS]; |
|
| 10256 | 10296 | let mut next: u32 = 0; |
|
| 10257 | 10297 | for i in 0..oldLen { |
|
| 10258 | - | let loan = checker.regional[i]; |
|
| 10298 | + | let loan = regionalLoan(checker, i); |
|
| 10259 | 10299 | if regionInScope(scope, loan.regionId) { |
|
| 10260 | 10300 | set checker.regional[next] = loan; |
|
| 10261 | 10301 | set mapping[i] = (1 as u64) << (next as u64); |
|
| 10262 | 10302 | set next += 1; |
|
| 10263 | 10303 | } |
| 10300 | 10340 | } |
|
| 10301 | 10341 | let place = borrowPlace(checker.resolver, address.target); |
|
| 10302 | 10342 | if place.root == nil { |
|
| 10303 | 10343 | return; |
|
| 10304 | 10344 | } |
|
| 10305 | - | for loan, i in &checker.regional[..checker.regionalLen] { |
|
| 10345 | + | for i in 0..checker.regionalLen { |
|
| 10346 | + | let loan = regionalLoan(checker, i); |
|
| 10306 | 10347 | if loan.source.id == node.id { |
|
| 10307 | 10348 | set env.regionalLoans |= (1 as u64) << (i as u64); |
|
| 10308 | 10349 | return; |
|
| 10309 | 10350 | } |
|
| 10310 | 10351 | } |
| 10351 | 10392 | return nil; |
|
| 10352 | 10393 | } |
|
| 10353 | 10394 | ||
| 10354 | 10395 | /// Resolve a place through reference locals without extending its storage lifetime. |
|
| 10355 | 10396 | unsafe fn borrowPlace 'arena (self: &mut Resolver 'arena, node: *ast::Node) -> BorrowPlace { |
|
| 10356 | - | let mut place = BorrowPlace { root: nil, fields: undefined, len: 0, precise: true }; |
|
| 10397 | + | let mut place = emptyBorrowPlace(); |
|
| 10357 | 10398 | match node.value { |
|
| 10358 | 10399 | case ast::NodeValue::Ident(_), ast::NodeValue::ScopeAccess(_) => { |
|
| 10359 | 10400 | let sym = symbolFor(self, node) else return place; |
|
| 10360 | 10401 | if let source = localReferenceSource(sym) { |
|
| 10361 | 10402 | let origin = borrowPlace(self, source); |
| 10439 | 10480 | let root = place.root else return; |
|
| 10440 | 10481 | for i in 0..checker.regionalLen { |
|
| 10441 | 10482 | if (env.regionalLoans & ((1 as u64) << (i as u64))) == 0 { |
|
| 10442 | 10483 | continue; |
|
| 10443 | 10484 | } |
|
| 10444 | - | let loan = checker.regional[i]; |
|
| 10485 | + | let loan = regionalLoan(checker, i); |
|
| 10445 | 10486 | if (exclusive or loan.exclusive) and placesOverlap(&place, &loan.place) |
|
| 10446 | 10487 | and not usesRegionalLoan(checker.resolver, node, loan.source) |
|
| 10447 | 10488 | { |
|
| 10448 | 10489 | throw emitError(checker.resolver, node, ErrorKind::BorrowConflict(root.name)); |
|
| 10449 | 10490 | } |
| 11643 | 11684 | body: *ast::Node, |
|
| 11644 | 11685 | ) throws (ResolveError) { |
|
| 11645 | 11686 | try resolveNominalApplications(self, body); |
|
| 11646 | 11687 | let regions = self.regionScope; |
|
| 11647 | 11688 | let resolved: 'checking = &mut *self where 'arena: 'checking in { |
|
| 11648 | - | let mut checker = LinearChecker 'arena 'checking { |
|
| 11649 | - | resolver: resolved, |
|
| 11650 | - | regional: undefined, |
|
| 11651 | - | regionalLen: 0, |
|
| 11652 | - | regions, |
|
| 11653 | - | loopBackLoans: undefined, |
|
| 11654 | - | loopExitLoans: undefined, |
|
| 11655 | - | loopRegions: undefined, |
|
| 11656 | - | loans: undefined, |
|
| 11657 | - | loanLen: 0, |
|
| 11658 | - | locals: undefined, |
|
| 11659 | - | localLen: 0, |
|
| 11660 | - | loopMarks: undefined, |
|
| 11661 | - | loopAvailable: undefined, |
|
| 11662 | - | loopExitAvailable: undefined, |
|
| 11663 | - | loopHasNaturalExit: undefined, |
|
| 11664 | - | loopBreakSeen: undefined, |
|
| 11665 | - | loopDepth: 0, |
|
| 11666 | - | }; |
|
| 11689 | + | let mut checker = linearChecker(resolved, regions); |
|
| 11667 | 11690 | let mut env = linearEnv(); |
|
| 11668 | 11691 | if let receiverNode = receiver { |
|
| 11669 | 11692 | try addLinearBinding(&mut checker, &mut env, receiverNode); |
|
| 11670 | 11693 | } |
|
| 11671 | 11694 | for paramNode in params { |
lib/std/lang/resolver/tests/regions.rad
+16 -0
| 1066 | 1066 | try super::expectErrorKind(&result, resolver::ErrorKind::InvalidRefPosition); |
|
| 1067 | 1067 | } |
|
| 1068 | 1068 | } |
|
| 1069 | 1069 | } |
|
| 1070 | 1070 | ||
| 1071 | + | /// Loan compaction preserves outer-region conflicts after child slots expire. |
|
| 1072 | + | @test unsafe fn testRegionalLoanCompactionConflict() throws (testing::TestError) { |
|
| 1073 | + | for program in [ |
|
| 1074 | + | "fn f 'r (p: &'r mut u32) { let value: u32 = 0; let child: 'child = &value where 'r: 'child in { let local = &*child; let retained = &*p; } set *p = 1; }", |
|
| 1075 | + | "fn f 'r (p: &'r mut u32) { let value: u32 = 0; loop { let child: 'child = &value where 'r: 'child in { let local = &*child; let retained = &*p; } break; } set *p = 1; }", |
|
| 1076 | + | ] { |
|
| 1077 | + | let mut arena = super::testArena(); |
|
| 1078 | + | let storage: 'test = &mut arena in { |
|
| 1079 | + | let mut res = super::testResolver(storage); |
|
| 1080 | + | let result = try super::resolveProgramStr(&mut res, program); |
|
| 1081 | + | let error = try super::expectError(&result); |
|
| 1082 | + | let case resolver::ErrorKind::BorrowConflict(_) = error.kind else throw testing::TestError::Failed; |
|
| 1083 | + | } |
|
| 1084 | + | } |
|
| 1085 | + | } |
|
| 1086 | + | ||
| 1071 | 1087 | /// Regional projection tracking limits simultaneous live projections. |
|
| 1072 | 1088 | @test unsafe fn testRegionalLoanCapacity() throws (testing::TestError) { |
|
| 1073 | 1089 | let mut reuseArena = super::testArena(); |
|
| 1074 | 1090 | let reuseStorage: 'reuse = &mut reuseArena in { |
|
| 1075 | 1091 | let mut res = super::testResolver(reuseStorage); |
test/tests/regions.loan.scratch.rad
added
+103 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Retain every supported regional projection slot at once. |
|
| 4 | + | fn full 'r (p: &'r u32) -> u32 { |
|
| 5 | + | let refs: [&'r u32; 32] = [&*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p, &*p]; |
|
| 6 | + | let mut sum: u32 = 0; |
|
| 7 | + | for item in refs { |
|
| 8 | + | set sum += *item; |
|
| 9 | + | } |
|
| 10 | + | return sum; |
|
| 11 | + | } |
|
| 12 | + | ||
| 13 | + | /// Reuse child-region loans across loop exits and later iterations. |
|
| 14 | + | fn recycle() -> u32 { |
|
| 15 | + | let mut value: u32 = 0; |
|
| 16 | + | let outer: 'outer = &mut value in { |
|
| 17 | + | for i in 0..3 { |
|
| 18 | + | let inner: 'inner = &mut *outer in { |
|
| 19 | + | set *inner += 1; |
|
| 20 | + | } |
|
| 21 | + | if i == 1 { continue; } |
|
| 22 | + | let read: 'read = &*outer in { |
|
| 23 | + | assert *read == i + 1; |
|
| 24 | + | } |
|
| 25 | + | } |
|
| 26 | + | loop { |
|
| 27 | + | let inner: 'last = &mut *outer in { |
|
| 28 | + | set *inner += 1; |
|
| 29 | + | } |
|
| 30 | + | break; |
|
| 31 | + | } |
|
| 32 | + | set *outer += 1; |
|
| 33 | + | } |
|
| 34 | + | return value; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Initialize and unwind every supported nested loop slot. |
|
| 38 | + | fn nested() -> u32 { |
|
| 39 | + | let mut value: u32 = 0; |
|
| 40 | + | loop { |
|
| 41 | + | loop { |
|
| 42 | + | loop { |
|
| 43 | + | loop { |
|
| 44 | + | loop { |
|
| 45 | + | loop { |
|
| 46 | + | loop { |
|
| 47 | + | loop { |
|
| 48 | + | loop { |
|
| 49 | + | loop { |
|
| 50 | + | loop { |
|
| 51 | + | loop { |
|
| 52 | + | loop { |
|
| 53 | + | loop { |
|
| 54 | + | loop { |
|
| 55 | + | loop { |
|
| 56 | + | let p: 'value = &mut value in { |
|
| 57 | + | set *p = 9; |
|
| 58 | + | } |
|
| 59 | + | break; |
|
| 60 | + | } |
|
| 61 | + | break; |
|
| 62 | + | } |
|
| 63 | + | break; |
|
| 64 | + | } |
|
| 65 | + | break; |
|
| 66 | + | } |
|
| 67 | + | break; |
|
| 68 | + | } |
|
| 69 | + | break; |
|
| 70 | + | } |
|
| 71 | + | break; |
|
| 72 | + | } |
|
| 73 | + | break; |
|
| 74 | + | } |
|
| 75 | + | break; |
|
| 76 | + | } |
|
| 77 | + | break; |
|
| 78 | + | } |
|
| 79 | + | break; |
|
| 80 | + | } |
|
| 81 | + | break; |
|
| 82 | + | } |
|
| 83 | + | break; |
|
| 84 | + | } |
|
| 85 | + | break; |
|
| 86 | + | } |
|
| 87 | + | break; |
|
| 88 | + | } |
|
| 89 | + | break; |
|
| 90 | + | } |
|
| 91 | + | return value; |
|
| 92 | + | } |
|
| 93 | + | ||
| 94 | + | /// Execute full and reused loan tables with fresh checker state per function. |
|
| 95 | + | @default fn main() -> u32 { |
|
| 96 | + | let value: u32 = 2; |
|
| 97 | + | let p: 'value = &value in { |
|
| 98 | + | assert full(p) == 64; |
|
| 99 | + | } |
|
| 100 | + | assert recycle() == 5; |
|
| 101 | + | assert nested() == 9; |
|
| 102 | + | return 0; |
|
| 103 | + | } |