compiler: Check ownership transitions with immutable binding metadata
cfe03900b1cfa109486a9dfa6643e1d34a99d58bf67a75e10f57aeceedcca4ac
1 parent
9c9a3c12
lib/std/lang/resolver.rad
+33 -28
| 897 | 897 | Place, |
|
| 898 | 898 | /// Evaluate a place prefix after checking the complete place. |
|
| 899 | 899 | Locate, |
|
| 900 | 900 | } |
|
| 901 | 901 | ||
| 902 | - | /// Symbol identity and storage retained for ownership diagnostics. |
|
| 902 | + | /// Consumption rule for a tracked move-only binding. |
|
| 903 | + | union BindingUse: Copy { |
|
| 904 | + | /// The binding can be consumed at most once. |
|
| 905 | + | Affine, |
|
| 906 | + | /// The binding must be consumed exactly once. |
|
| 907 | + | Linear, |
|
| 908 | + | } |
|
| 909 | + | ||
| 910 | + | /// Resolved binding metadata retained for ownership checks and diagnostics. |
|
| 903 | 911 | record TrackedSymbol: Copy { |
|
| 904 | 912 | /// Resolver-local symbol identity. |
|
| 905 | 913 | id: u32, |
|
| 906 | - | /// Symbol storage used to report ownership errors. |
|
| 907 | - | symbol: *unsafe mut Symbol, |
|
| 914 | + | /// Source name used in ownership diagnostics. |
|
| 915 | + | name: *[u8], |
|
| 916 | + | /// Declaration used to locate an unconsumed binding. |
|
| 917 | + | node: *ast::Node, |
|
| 918 | + | /// Consumption rule fixed before ownership analysis. |
|
| 919 | + | usage: BindingUse, |
|
| 908 | 920 | } |
|
| 909 | 921 | ||
| 910 | 922 | /// Per-control-flow-path ownership state. |
|
| 911 | 923 | /// Read only the initialized symbol prefix below `len`. |
|
| 912 | 924 | record LinearEnv: Copy { |
|
| 913 | 925 | /// Active full-region loans, indexed by the checker's regional loan table. |
|
| 914 | 926 | regionalLoans: u64, |
|
| 915 | - | /// Symbol identities and pointers. Entries below `len` are initialized. |
|
| 927 | + | /// Resolved binding metadata. Entries below `len` are initialized. |
|
| 916 | 928 | symbols: [TrackedSymbol; MAX_LINEAR_BINDINGS], |
|
| 917 | 929 | /// Bit set for each binding that remains available. |
|
| 918 | 930 | available: u64, |
|
| 919 | 931 | /// Number of initialized entries in `symbols`. |
|
| 920 | 932 | len: u32, |
| 9915 | 9927 | return; |
|
| 9916 | 9928 | } |
|
| 9917 | 9929 | if env.len >= MAX_LINEAR_BINDINGS { |
|
| 9918 | 9930 | throw emitError(checker.resolver, node, ErrorKind::Internal); |
|
| 9919 | 9931 | } |
|
| 9920 | - | set env.symbols[env.len] = TrackedSymbol { id: sym.id, symbol: sym }; |
|
| 9932 | + | let usage = BindingUse::Linear if isLinear(ty) else BindingUse::Affine; |
|
| 9933 | + | set env.symbols[env.len] = TrackedSymbol { id: sym.id, name: sym.name, node: sym.node, usage }; |
|
| 9921 | 9934 | set env.available |= (1 as u64) << (env.len as u64); |
|
| 9922 | 9935 | set env.len += 1; |
|
| 9923 | 9936 | } |
|
| 9924 | 9937 | ||
| 9925 | 9938 | /// Mark a tracked binding as uninitialized. |
| 9928 | 9941 | let index = findLinearBinding(env, sym.id) else return; |
|
| 9929 | 9942 | set env.available &= ~((1 as u64) << (index as u64)); |
|
| 9930 | 9943 | } |
|
| 9931 | 9944 | ||
| 9932 | 9945 | /// Require exact-use bindings introduced after `start` to be consumed. |
|
| 9933 | - | unsafe fn finishLinearScope 'arena 'checking ( |
|
| 9946 | + | fn finishLinearScope 'arena 'checking ( |
|
| 9934 | 9947 | checker: &mut LinearChecker 'arena 'checking, |
|
| 9935 | 9948 | env: &mut LinearEnv, |
|
| 9936 | 9949 | start: u32, |
|
| 9937 | 9950 | ) throws (ResolveError) where 'arena: 'checking { |
|
| 9938 | 9951 | if not env.terminated { |
|
| 9939 | 9952 | for i in start..env.len { |
|
| 9940 | 9953 | if linearBindingAvailable(env, i) { |
|
| 9941 | - | let sym = env.symbols[i].symbol; |
|
| 9942 | - | let case SymbolData::Value { type: ty, .. } = sym.data |
|
| 9943 | - | else panic "finishLinearScope: expected value symbol"; |
|
| 9944 | - | if isLinear(ty) { |
|
| 9954 | + | let sym = env.symbols[i]; |
|
| 9955 | + | if sym.usage == BindingUse::Linear { |
|
| 9945 | 9956 | throw emitError( |
|
| 9946 | 9957 | checker.resolver, |
|
| 9947 | 9958 | sym.node, |
|
| 9948 | 9959 | ErrorKind::LinearNotConsumed(sym.name), |
|
| 9949 | 9960 | ); |
| 9983 | 9994 | set env.available &= ~((1 as u64) << (index as u64)); |
|
| 9984 | 9995 | } |
|
| 9985 | 9996 | ||
| 9986 | 9997 | /// Merge ownership availability across two live branches. |
|
| 9987 | 9998 | /// Validate both inputs before writing to an output that can alias either input. |
|
| 9988 | - | unsafe fn joinLinearBranches 'arena 'checking ( |
|
| 9999 | + | fn joinLinearBranches 'arena 'checking ( |
|
| 9989 | 10000 | checker: &mut LinearChecker 'arena 'checking, |
|
| 9990 | 10001 | env: &mut LinearEnv, |
|
| 9991 | 10002 | left: &LinearEnv, |
|
| 9992 | 10003 | right: &LinearEnv, |
|
| 9993 | 10004 | node: *ast::Node, |
| 10007 | 10018 | } |
|
| 10008 | 10019 | assert left.len == right.len, "joinLinearBranches: scope mismatch"; |
|
| 10009 | 10020 | let mut available = left.available; |
|
| 10010 | 10021 | for i in 0..left.len { |
|
| 10011 | 10022 | if linearBindingAvailable(left, i) <> linearBindingAvailable(right, i) { |
|
| 10012 | - | let sym = left.symbols[i].symbol; |
|
| 10013 | - | let case SymbolData::Value { type: ty, .. } = sym.data |
|
| 10014 | - | else panic "joinLinearBranches: expected value symbol"; |
|
| 10015 | - | if isLinear(ty) { |
|
| 10023 | + | let sym = left.symbols[i]; |
|
| 10024 | + | if sym.usage == BindingUse::Linear { |
|
| 10016 | 10025 | throw emitError( |
|
| 10017 | 10026 | checker.resolver, |
|
| 10018 | 10027 | node, |
|
| 10019 | 10028 | ErrorKind::LinearBranchMismatch(sym.name), |
|
| 10020 | 10029 | ); |
| 10027 | 10036 | set env.available = available; |
|
| 10028 | 10037 | set env.regionalLoans = regionalLoans; |
|
| 10029 | 10038 | } |
|
| 10030 | 10039 | ||
| 10031 | 10040 | /// Require all available exact-use bindings to be consumed at a function exit. |
|
| 10032 | - | unsafe fn finishLinearExit 'arena 'checking ( |
|
| 10041 | + | fn finishLinearExit 'arena 'checking ( |
|
| 10033 | 10042 | checker: &mut LinearChecker 'arena 'checking, |
|
| 10034 | 10043 | env: &mut LinearEnv, |
|
| 10035 | 10044 | ) throws (ResolveError) where 'arena: 'checking { |
|
| 10036 | 10045 | if env.terminated { |
|
| 10037 | 10046 | return; |
|
| 10038 | 10047 | } |
|
| 10039 | 10048 | for i in 0..env.len { |
|
| 10040 | 10049 | if linearBindingAvailable(env, i) { |
|
| 10041 | - | let sym = env.symbols[i].symbol; |
|
| 10042 | - | let case SymbolData::Value { type: ty, .. } = sym.data |
|
| 10043 | - | else panic "finishLinearExit: expected value symbol"; |
|
| 10044 | - | if isLinear(ty) { |
|
| 10050 | + | let sym = env.symbols[i]; |
|
| 10051 | + | if sym.usage == BindingUse::Linear { |
|
| 10045 | 10052 | throw emitError( |
|
| 10046 | 10053 | checker.resolver, |
|
| 10047 | 10054 | sym.node, |
|
| 10048 | 10055 | ErrorKind::LinearNotConsumed(sym.name), |
|
| 10049 | 10056 | ); |
| 10517 | 10524 | set checker.loopBreakSeen[depth] = false; |
|
| 10518 | 10525 | set checker.loopDepth += 1; |
|
| 10519 | 10526 | } |
|
| 10520 | 10527 | ||
| 10521 | 10528 | /// Require a repeated body's outer bindings to match its entry state. |
|
| 10522 | - | unsafe fn checkLinearLoopBackEdge 'arena 'checking ( |
|
| 10529 | + | fn checkLinearLoopBackEdge 'arena 'checking ( |
|
| 10523 | 10530 | checker: &mut LinearChecker 'arena 'checking, |
|
| 10524 | 10531 | env: &LinearEnv, |
|
| 10525 | 10532 | node: *ast::Node, |
|
| 10526 | 10533 | ) throws (ResolveError) where 'arena: 'checking { |
|
| 10527 | 10534 | if env.terminated { |
| 10533 | 10540 | let mark = checker.loopMarks[depth]; |
|
| 10534 | 10541 | let entryAvailable = checker.loopAvailable[depth]; |
|
| 10535 | 10542 | for i in 0..mark { |
|
| 10536 | 10543 | let bit = (1 as u64) << (i as u64); |
|
| 10537 | 10544 | if (env.available & bit) <> (entryAvailable & bit) { |
|
| 10538 | - | let sym = env.symbols[i].symbol; |
|
| 10545 | + | let sym = env.symbols[i]; |
|
| 10539 | 10546 | throw emitError( |
|
| 10540 | 10547 | checker.resolver, |
|
| 10541 | 10548 | node, |
|
| 10542 | 10549 | ErrorKind::LinearBranchMismatch(sym.name), |
|
| 10543 | 10550 | ); |
| 10553 | 10560 | set checker.loopExitAvailable[depth] = env.available; |
|
| 10554 | 10561 | set checker.loopHasNaturalExit[depth] = true; |
|
| 10555 | 10562 | } |
|
| 10556 | 10563 | ||
| 10557 | 10564 | /// Require a break exit to agree with every other exit from this loop. |
|
| 10558 | - | unsafe fn checkLinearLoopBreak 'arena 'checking ( |
|
| 10565 | + | fn checkLinearLoopBreak 'arena 'checking ( |
|
| 10559 | 10566 | checker: &mut LinearChecker 'arena 'checking, |
|
| 10560 | 10567 | env: &LinearEnv, |
|
| 10561 | 10568 | node: *ast::Node, |
|
| 10562 | 10569 | ) throws (ResolveError) where 'arena: 'checking { |
|
| 10563 | 10570 | assert checker.loopDepth > 0, "linear loop break outside loop"; |
| 10567 | 10574 | if checker.loopHasNaturalExit[depth] or checker.loopBreakSeen[depth] { |
|
| 10568 | 10575 | let expected = checker.loopExitAvailable[depth]; |
|
| 10569 | 10576 | for i in 0..mark { |
|
| 10570 | 10577 | let bit = (1 as u64) << (i as u64); |
|
| 10571 | 10578 | if (env.available & bit) <> (expected & bit) { |
|
| 10572 | - | let sym = env.symbols[i].symbol; |
|
| 10579 | + | let sym = env.symbols[i]; |
|
| 10573 | 10580 | throw emitError( |
|
| 10574 | 10581 | checker.resolver, |
|
| 10575 | 10582 | node, |
|
| 10576 | 10583 | ErrorKind::LinearBranchMismatch(sym.name), |
|
| 10577 | 10584 | ); |
| 10666 | 10673 | } |
|
| 10667 | 10674 | case ast::ProngArm::Else => {} |
|
| 10668 | 10675 | } |
|
| 10669 | 10676 | if prong.guard <> nil { |
|
| 10670 | 10677 | for i in bindingsStart..branch.len { |
|
| 10671 | - | let sym = branch.symbols[i].symbol; |
|
| 10672 | - | let case SymbolData::Value { type: ty, .. } = sym.data |
|
| 10673 | - | else panic "checkLinearMatch: expected value symbol"; |
|
| 10674 | - | if isLinear(ty) { |
|
| 10678 | + | let sym = branch.symbols[i]; |
|
| 10679 | + | if sym.usage == BindingUse::Linear { |
|
| 10675 | 10680 | throw emitError(checker.resolver, prongNode, ErrorKind::LinearDiscard); |
|
| 10676 | 10681 | } |
|
| 10677 | 10682 | } |
|
| 10678 | 10683 | } |
|
| 10679 | 10684 | if let guard = prong.guard { |