Support checked local references and disjoint field borrows
56a00753efd746a24c163f94ba7770c8d90cda638c0f2a0ef28c6b456b2367bb
1 parent
db5ee0c4
lib/std/lang/resolver.rad
+235 -47
| 47 | 47 | export constant MAX_INSTANCES: u32 = 128; |
|
| 48 | 48 | /// Maximum standalone methods (across all types). |
|
| 49 | 49 | export constant MAX_METHODS: u32 = 256; |
|
| 50 | 50 | /// Maximum number of linear bindings active in one function. |
|
| 51 | 51 | constant MAX_LINEAR_BINDINGS: u32 = 32; |
|
| 52 | + | /// Maximum inline field depth used to prove borrow separation. |
|
| 53 | + | constant MAX_BORROW_FIELDS: u32 = 16; |
|
| 52 | 54 | /// Maximum nesting depth tracked for loops. |
|
| 53 | 55 | constant MAX_LINEAR_LOOP_DEPTH: u32 = 16; |
|
| 54 | 56 | ||
| 55 | 57 | /// Trait definition stored in the resolver. |
|
| 56 | 58 | export record TraitType: Copy { |
| 625 | 627 | CopyContainsNonCopy, |
|
| 626 | 628 | /// A declaration carries both `Copy` and `Once`. |
|
| 627 | 629 | ConflictingOwnershipMarkers, |
|
| 628 | 630 | /// A reference appears in a storable or escaping position. |
|
| 629 | 631 | InvalidRefPosition, |
|
| 630 | - | /// A reference cannot be bound to a local. |
|
| 632 | + | /// A reference local requires a fixed binding to existing storage. |
|
| 631 | 633 | RefBinding, |
|
| 632 | 634 | /// Call arguments contain overlapping incompatible loans. |
|
| 633 | 635 | BorrowConflict(*[u8]), |
|
| 634 | 636 | /// Unsafe operation outside an unsafe context. |
|
| 635 | 637 | UnsafeOperation, |
| 808 | 810 | Borrow, |
|
| 809 | 811 | /// Discard an unused expression result. |
|
| 810 | 812 | Discard, |
|
| 811 | 813 | /// Use the value as an assignment target. |
|
| 812 | 814 | Place, |
|
| 815 | + | /// Evaluate a place prefix after checking the complete place. |
|
| 816 | + | Locate, |
|
| 813 | 817 | } |
|
| 814 | 818 | ||
| 815 | 819 | /// Per-control-flow-path ownership state. |
|
| 816 | 820 | /// Read only the initialized symbol prefix below `len`. |
|
| 817 | 821 | record LinearEnv: Copy { |
| 823 | 827 | len: u32, |
|
| 824 | 828 | /// Whether this control-flow path has terminated. |
|
| 825 | 829 | terminated: bool, |
|
| 826 | 830 | } |
|
| 827 | 831 | ||
| 832 | + | /// A storage root and its statically distinct record fields. |
|
| 833 | + | record BorrowPlace: Copy { |
|
| 834 | + | /// Symbol that owns or supplies the storage. |
|
| 835 | + | root: ?*unsafe mut Symbol, |
|
| 836 | + | /// Field indices before the first uncertain projection. |
|
| 837 | + | fields: [u32; MAX_BORROW_FIELDS], |
|
| 838 | + | /// Number of initialized field indices. |
|
| 839 | + | len: u32, |
|
| 840 | + | /// Whether further projections can identify distinct storage. |
|
| 841 | + | precise: bool, |
|
| 842 | + | } |
|
| 843 | + | ||
| 844 | + | /// A reference binding that protects its source for one lexical scope. |
|
| 845 | + | record LocalLoan: Copy { |
|
| 846 | + | /// Local symbol through which the source can be accessed. |
|
| 847 | + | binding: *unsafe mut Symbol, |
|
| 848 | + | /// Storage retained by the reference. |
|
| 849 | + | place: BorrowPlace, |
|
| 850 | + | /// Whether other reads of the source are excluded. |
|
| 851 | + | exclusive: bool, |
|
| 852 | + | } |
|
| 853 | + | ||
| 828 | 854 | /// Function-local exact-use checker state. |
|
| 829 | 855 | /// Read loop arrays only at indices below `loopDepth`. |
|
| 830 | 856 | /// `enterLinearLoop` initializes each slot before it increases `loopDepth`. |
|
| 831 | 857 | record LinearChecker: Copy { |
|
| 832 | 858 | /// Resolver that owns the symbols and diagnostics. |
|
| 833 | 859 | resolver: *unsafe mut Resolver, |
|
| 834 | - | /// Source symbols protected by active pattern references. |
|
| 835 | - | loans: [*unsafe mut Symbol; MAX_LINEAR_BINDINGS], |
|
| 860 | + | /// Source places protected by active pattern references. |
|
| 861 | + | loans: [BorrowPlace; MAX_LINEAR_BINDINGS], |
|
| 836 | 862 | /// Number of initialized entries in `loans`. |
|
| 837 | 863 | loanLen: u32, |
|
| 864 | + | /// Reference locals in active lexical scopes. |
|
| 865 | + | locals: [LocalLoan; MAX_LINEAR_BINDINGS], |
|
| 866 | + | /// Number of initialized local loans. |
|
| 867 | + | localLen: u32, |
|
| 838 | 868 | /// Binding count at entry to each active loop. |
|
| 839 | 869 | loopMarks: [u32; MAX_LINEAR_LOOP_DEPTH], |
|
| 840 | 870 | /// Available bindings at entry to each active loop. |
|
| 841 | 871 | loopAvailable: [u64; MAX_LINEAR_LOOP_DEPTH], |
|
| 842 | 872 | /// Available bindings shared by the exits from each active loop. |
| 2839 | 2869 | /// Try to infer a node's type. |
|
| 2840 | 2870 | unsafe fn infer(self: &mut Resolver, node: *ast::Node) -> Type throws (ResolveError) { |
|
| 2841 | 2871 | return try visit(self, node, Type::Unknown); |
|
| 2842 | 2872 | } |
|
| 2843 | 2873 | ||
| 2844 | - | /// Reject nested references while allowing a direct parameter reference. |
|
| 2874 | + | /// Reject nested references while allowing a direct parameter or local reference. |
|
| 2845 | 2875 | unsafe fn validateValueTypeReferences(self: &mut Resolver, node: *ast::Node, ty: Type) |
|
| 2846 | 2876 | throws (ResolveError) |
|
| 2847 | 2877 | { |
|
| 2848 | 2878 | if isRefType(ty) { |
|
| 2849 | 2879 | if let case Type::Pointer { target, .. } = ty { |
| 3135 | 3165 | ||
| 3136 | 3166 | if not isTypeInferrable(bindingTy) { |
|
| 3137 | 3167 | throw emitError(self, decl.value, ErrorKind::CannotInferType); |
|
| 3138 | 3168 | } |
|
| 3139 | 3169 | } |
|
| 3140 | - | // Variables cannot have void type. |
|
| 3141 | - | if containsRef(bindingTy) { |
|
| 3142 | - | throw emitError(self, node, ErrorKind::RefBinding); |
|
| 3170 | + | try validateValueTypeReferences(self, node, bindingTy); |
|
| 3171 | + | if isRefType(bindingTy) { |
|
| 3172 | + | if self.currentFn == nil { |
|
| 3173 | + | throw emitError(self, node, ErrorKind::InvalidRefPosition); |
|
| 3174 | + | } |
|
| 3175 | + | if decl.mutable { |
|
| 3176 | + | throw emitError(self, node, ErrorKind::RefBinding); |
|
| 3177 | + | } |
|
| 3143 | 3178 | } |
|
| 3179 | + | // Variables cannot have void type. |
|
| 3144 | 3180 | if bindingTy == Type::Void { |
|
| 3145 | 3181 | throw emitError(self, decl.value, ErrorKind::CannotAssignVoid); |
|
| 3146 | 3182 | } |
|
| 3147 | 3183 | // Variables cannot have opaque type directly. |
|
| 3148 | 3184 | if bindingTy == Type::Opaque { |
| 7813 | 7849 | case ast::NodeValue::Deref(target) => return linearRootSymbol(self, target), |
|
| 7814 | 7850 | else => return nil, |
|
| 7815 | 7851 | } |
|
| 7816 | 7852 | } |
|
| 7817 | 7853 | ||
| 7854 | + | /// Return the initializer that supplies a local reference's storage. |
|
| 7855 | + | unsafe fn localReferenceSource(sym: *unsafe mut Symbol) -> ?*ast::Node { |
|
| 7856 | + | let case SymbolData::Value { type: ty, .. } = sym.data else return nil; |
|
| 7857 | + | if isRefType(ty) { |
|
| 7858 | + | if let case ast::NodeValue::Let(binding) = sym.node.value { |
|
| 7859 | + | return binding.value; |
|
| 7860 | + | } |
|
| 7861 | + | } |
|
| 7862 | + | return nil; |
|
| 7863 | + | } |
|
| 7864 | + | ||
| 7865 | + | /// Resolve a place through reference locals without extending its storage lifetime. |
|
| 7866 | + | unsafe fn borrowPlace(self: &mut Resolver, node: *ast::Node) -> BorrowPlace { |
|
| 7867 | + | let mut place = BorrowPlace { root: nil, fields: undefined, len: 0, precise: true }; |
|
| 7868 | + | match node.value { |
|
| 7869 | + | case ast::NodeValue::Ident(_), ast::NodeValue::ScopeAccess(_) => { |
|
| 7870 | + | let sym = symbolFor(self, node) else return place; |
|
| 7871 | + | if let source = localReferenceSource(sym) { |
|
| 7872 | + | return borrowPlace(self, source); |
|
| 7873 | + | } |
|
| 7874 | + | set place.root = sym; |
|
| 7875 | + | } |
|
| 7876 | + | case ast::NodeValue::AddressOf(addr) => return borrowPlace(self, addr.target), |
|
| 7877 | + | case ast::NodeValue::As(expr) => return borrowPlace(self, expr.value), |
|
| 7878 | + | case ast::NodeValue::FieldAccess(access) => { |
|
| 7879 | + | set place = borrowPlace(self, access.parent); |
|
| 7880 | + | if let ty = typeFor(self, access.parent) { |
|
| 7881 | + | if let case Type::Pointer { .. } = ty; not isRefType(ty) and place.len > 0 { |
|
| 7882 | + | set place.len = 0; |
|
| 7883 | + | set place.precise = false; |
|
| 7884 | + | } |
|
| 7885 | + | if let case Type::Nominal(NominalType::Record(_)) = autoDeref(ty); |
|
| 7886 | + | place.precise and place.len < MAX_BORROW_FIELDS |
|
| 7887 | + | { |
|
| 7888 | + | if let index = recordFieldIndexFor(self, access.child) { |
|
| 7889 | + | set place.fields[place.len] = index; |
|
| 7890 | + | set place.len += 1; |
|
| 7891 | + | return place; |
|
| 7892 | + | } |
|
| 7893 | + | } |
|
| 7894 | + | } |
|
| 7895 | + | set place.precise = false; |
|
| 7896 | + | } |
|
| 7897 | + | case ast::NodeValue::Subscript { container, .. } => { |
|
| 7898 | + | set place = borrowPlace(self, container); |
|
| 7899 | + | if let ty = typeFor(self, container) { |
|
| 7900 | + | if let case Type::Slice { class, .. } = autoDeref(ty); class <> types::PointerClass::Ref { |
|
| 7901 | + | set place.len = 0; |
|
| 7902 | + | } |
|
| 7903 | + | } |
|
| 7904 | + | set place.precise = false; |
|
| 7905 | + | } |
|
| 7906 | + | case ast::NodeValue::Deref(target) => { |
|
| 7907 | + | set place = borrowPlace(self, target); |
|
| 7908 | + | if let ty = typeFor(self, target); not isRefType(ty) and place.len > 0 { |
|
| 7909 | + | set place.len = 0; |
|
| 7910 | + | set place.precise = false; |
|
| 7911 | + | } |
|
| 7912 | + | } |
|
| 7913 | + | else => {} |
|
| 7914 | + | } |
|
| 7915 | + | return place; |
|
| 7916 | + | } |
|
| 7917 | + | ||
| 7918 | + | /// Two places overlap unless distinct inline fields prove separation. |
|
| 7919 | + | fn placesOverlap(left: &BorrowPlace, right: &BorrowPlace) -> bool { |
|
| 7920 | + | if left.root == nil or left.root <> right.root { |
|
| 7921 | + | return false; |
|
| 7922 | + | } |
|
| 7923 | + | let count = left.len if left.len < right.len else right.len; |
|
| 7924 | + | for i in 0..count { |
|
| 7925 | + | if left.fields[i] <> right.fields[i] { |
|
| 7926 | + | return false; |
|
| 7927 | + | } |
|
| 7928 | + | } |
|
| 7929 | + | return true; |
|
| 7930 | + | } |
|
| 7931 | + | ||
| 7932 | + | /// Check whether access uses a reference or one of its lexical reborrows. |
|
| 7933 | + | unsafe fn usesLocalLoan(self: &mut Resolver, node: *ast::Node, binding: *unsafe mut Symbol) -> bool { |
|
| 7934 | + | let root = linearRootSymbol(self, node) else return false; |
|
| 7935 | + | if root == binding { |
|
| 7936 | + | return true; |
|
| 7937 | + | } |
|
| 7938 | + | let source = localReferenceSource(root) else return false; |
|
| 7939 | + | return usesLocalLoan(self, source, binding); |
|
| 7940 | + | } |
|
| 7941 | + | ||
| 7942 | + | /// Reject accesses that conflict with a reference in an active lexical scope. |
|
| 7943 | + | unsafe fn checkLocalLoans(checker: &mut LinearChecker, node: *ast::Node, exclusive: bool) |
|
| 7944 | + | throws (ResolveError) |
|
| 7945 | + | { |
|
| 7946 | + | let place = borrowPlace(checker.resolver, node); |
|
| 7947 | + | let root = place.root else return; |
|
| 7948 | + | for i in 0..checker.localLen { |
|
| 7949 | + | let loan = checker.locals[i]; |
|
| 7950 | + | if (exclusive or loan.exclusive) and placesOverlap(&place, &loan.place) |
|
| 7951 | + | and not usesLocalLoan(checker.resolver, node, loan.binding) |
|
| 7952 | + | { |
|
| 7953 | + | throw emitError(checker.resolver, node, ErrorKind::BorrowConflict(root.name)); |
|
| 7954 | + | } |
|
| 7955 | + | } |
|
| 7956 | + | } |
|
| 7957 | + | ||
| 7958 | + | /// Retain existing storage until its immutable reference binding leaves scope. |
|
| 7959 | + | unsafe fn addLocalLoan(checker: &mut LinearChecker, node: *ast::Node, binding: ast::Let) |
|
| 7960 | + | throws (ResolveError) |
|
| 7961 | + | { |
|
| 7962 | + | let ty = typeFor(checker.resolver, binding.ident) else return; |
|
| 7963 | + | if not isRefType(ty) { |
|
| 7964 | + | return; |
|
| 7965 | + | } |
|
| 7966 | + | let place = borrowPlace(checker.resolver, binding.value); |
|
| 7967 | + | if place.root == nil { |
|
| 7968 | + | throw emitError(checker.resolver, node, ErrorKind::RefBinding); |
|
| 7969 | + | } |
|
| 7970 | + | if checker.localLen >= MAX_LINEAR_BINDINGS { |
|
| 7971 | + | throw emitError(checker.resolver, node, ErrorKind::Internal); |
|
| 7972 | + | } |
|
| 7973 | + | let sym = symbolFor(checker.resolver, node) else panic "reference without binding"; |
|
| 7974 | + | let exclusive = isExclusiveArgument(ty); |
|
| 7975 | + | try checkLocalLoans(checker, binding.value, exclusive); |
|
| 7976 | + | set checker.locals[checker.localLen] = LocalLoan { binding: sym, place, exclusive }; |
|
| 7977 | + | set checker.localLen += 1; |
|
| 7978 | + | } |
|
| 7979 | + | ||
| 7818 | 7980 | /// Protect a pattern source until its reference bindings leave scope. |
|
| 7819 | 7981 | unsafe fn addPatternLoan(checker: &mut LinearChecker, subject: *ast::Node) |
|
| 7820 | 7982 | throws (ResolveError) |
|
| 7821 | 7983 | { |
|
| 7822 | - | let root = linearRootSymbol(checker.resolver, subject) else return; |
|
| 7984 | + | let place = borrowPlace(checker.resolver, subject); |
|
| 7985 | + | if place.root == nil { |
|
| 7986 | + | return; |
|
| 7987 | + | } |
|
| 7823 | 7988 | if checker.loanLen >= MAX_LINEAR_BINDINGS { |
|
| 7824 | 7989 | throw emitError(checker.resolver, subject, ErrorKind::Internal); |
|
| 7825 | 7990 | } |
|
| 7826 | - | set checker.loans[checker.loanLen] = root; |
|
| 7991 | + | set checker.loans[checker.loanLen] = place; |
|
| 7827 | 7992 | set checker.loanLen += 1; |
|
| 7828 | 7993 | } |
|
| 7829 | 7994 | ||
| 7830 | 7995 | /// Reject a write, mutable loan, or ownership transfer of a pattern source. |
|
| 7831 | 7996 | unsafe fn checkPatternLoan(checker: &mut LinearChecker, node: *ast::Node) |
|
| 7832 | 7997 | throws (ResolveError) |
|
| 7833 | 7998 | { |
|
| 7834 | - | let root = linearRootSymbol(checker.resolver, node) else return; |
|
| 7999 | + | let place = borrowPlace(checker.resolver, node); |
|
| 8000 | + | let root = place.root else return; |
|
| 7835 | 8001 | for i in 0..checker.loanLen { |
|
| 7836 | - | if checker.loans[i] == root { |
|
| 8002 | + | if placesOverlap(&checker.loans[i], &place) { |
|
| 7837 | 8003 | throw emitError(checker.resolver, node, ErrorKind::BorrowConflict(root.name)); |
|
| 7838 | 8004 | } |
|
| 7839 | 8005 | } |
|
| 7840 | 8006 | } |
|
| 7841 | 8007 |
| 7897 | 8063 | checker: &mut LinearChecker, |
|
| 7898 | 8064 | env: &mut LinearEnv, |
|
| 7899 | 8065 | node: *ast::Node, |
|
| 7900 | 8066 | ) throws (ResolveError) { |
|
| 7901 | 8067 | let start = env.len; |
|
| 8068 | + | let localStart = checker.localLen; |
|
| 7902 | 8069 | let case ast::NodeValue::Block(block) = node.value |
|
| 7903 | 8070 | else panic "checkLinearBlock: expected block"; |
|
| 7904 | 8071 | for stmt in block.statements { |
|
| 7905 | 8072 | if env.terminated { |
|
| 7906 | 8073 | break; |
|
| 7907 | 8074 | } |
|
| 7908 | 8075 | try checkLinearNode(checker, env, stmt, LinearUse::Discard); |
|
| 7909 | 8076 | } |
|
| 7910 | 8077 | try finishLinearScope(checker, env, start); |
|
| 8078 | + | set checker.localLen = localStart; |
|
| 7911 | 8079 | } |
|
| 7912 | 8080 | ||
| 7913 | 8081 | /// Push a repeated-control-flow boundary. |
|
| 7914 | 8082 | /// Initialize all loop state at this depth before increasing `loopDepth`. |
|
| 7915 | 8083 | fn enterLinearLoop(checker: &mut LinearChecker, env: &LinearEnv) { |
| 8102 | 8270 | checker: &mut LinearChecker, |
|
| 8103 | 8271 | env: &mut LinearEnv, |
|
| 8104 | 8272 | node: *ast::Node, |
|
| 8105 | 8273 | call: ast::Call, |
|
| 8106 | 8274 | ) throws (ResolveError) { |
|
| 8275 | + | match checker.resolver.nodeData.entries[node.id].extra { |
|
| 8276 | + | case NodeExtra::SliceAppend { .. }, NodeExtra::SliceDelete { .. } => { |
|
| 8277 | + | let case ast::NodeValue::FieldAccess(access) = call.callee.value |
|
| 8278 | + | else panic "slice mutation without receiver"; |
|
| 8279 | + | try checkPatternLoan(checker, access.parent); |
|
| 8280 | + | try checkLocalLoans(checker, access.parent, true); |
|
| 8281 | + | } |
|
| 8282 | + | else => {} |
|
| 8283 | + | } |
|
| 8107 | 8284 | try checkLinearNode(checker, env, call.callee, LinearUse::Observe); |
|
| 8108 | 8285 | let mut fnInfo: ?*FnType = nil; |
|
| 8109 | 8286 | match checker.resolver.nodeData.entries[node.id].extra { |
|
| 8110 | 8287 | case NodeExtra::TraitMethodCall { traitInfo, methodIndex } => |
|
| 8111 | 8288 | set fnInfo = traitInfo.methods[methodIndex].fnType, |
| 8122 | 8299 | for arg in call.args { |
|
| 8123 | 8300 | try checkLinearNode(checker, env, arg, LinearUse::Consume); |
|
| 8124 | 8301 | } |
|
| 8125 | 8302 | return; |
|
| 8126 | 8303 | }; |
|
| 8127 | - | let mut roots: [?*unsafe mut Symbol; MAX_FN_PARAMS + 1] = undefined; |
|
| 8304 | + | let mut places: [BorrowPlace; MAX_FN_PARAMS + 1] = undefined; |
|
| 8128 | 8305 | let mut exclusive: [bool; MAX_FN_PARAMS + 1] = undefined; |
|
| 8129 | - | let mut rootsLen: u32 = 0; |
|
| 8306 | + | let mut placesLen: u32 = 0; |
|
| 8130 | 8307 | ||
| 8131 | 8308 | // Method function types exclude their implicit receiver. Account for it |
|
| 8132 | 8309 | // explicitly so owning receivers are consumed and reference receivers |
|
| 8133 | 8310 | // participate in call-scoped loan conflict checks. |
|
| 8134 | 8311 | if let case ast::NodeValue::FieldAccess(access) = call.callee.value { |
| 8148 | 8325 | set haveReceiver = true; |
|
| 8149 | 8326 | } |
|
| 8150 | 8327 | else => {} |
|
| 8151 | 8328 | } |
|
| 8152 | 8329 | if haveReceiver { |
|
| 8330 | + | try checkLocalLoans(checker, access.parent, |
|
| 8331 | + | receiverMutable or receiverClass == types::PointerClass::Owned); |
|
| 8153 | 8332 | if receiverMutable or receiverClass == types::PointerClass::Owned { |
|
| 8154 | 8333 | try checkPatternLoan(checker, access.parent); |
|
| 8155 | 8334 | } |
|
| 8156 | 8335 | if receiverClass <> types::PointerClass::Unsafe { |
|
| 8157 | - | let root = linearRootSymbol(checker.resolver, access.parent); |
|
| 8158 | - | if let rootSym = root { |
|
| 8159 | - | set roots[rootsLen] = rootSym; |
|
| 8160 | - | set exclusive[rootsLen] = |
|
| 8336 | + | let place = borrowPlace(checker.resolver, access.parent); |
|
| 8337 | + | if place.root <> nil { |
|
| 8338 | + | set places[placesLen] = place; |
|
| 8339 | + | set exclusive[placesLen] = |
|
| 8161 | 8340 | receiverClass == types::PointerClass::Owned or receiverMutable; |
|
| 8162 | - | set rootsLen += 1; |
|
| 8341 | + | set placesLen += 1; |
|
| 8163 | 8342 | } |
|
| 8164 | 8343 | } |
|
| 8165 | 8344 | if receiverClass == types::PointerClass::Ref { |
|
| 8166 | 8345 | try checkLinearNode(checker, env, access.parent, LinearUse::Borrow); |
|
| 8167 | 8346 | } else if receiverClass == types::PointerClass::Owned { |
| 8170 | 8349 | } |
|
| 8171 | 8350 | } |
|
| 8172 | 8351 | ||
| 8173 | 8352 | for arg, i in call.args { |
|
| 8174 | 8353 | let expected = *info.paramTypes[i]; |
|
| 8175 | - | if isExclusiveArgument(expected) { |
|
| 8354 | + | let argExclusive = isExclusiveArgument(expected); |
|
| 8355 | + | if argExclusive { |
|
| 8176 | 8356 | try checkPatternLoan(checker, arg); |
|
| 8177 | 8357 | } |
|
| 8178 | - | let root = linearRootSymbol(checker.resolver, arg); |
|
| 8179 | - | let mut argExclusive = isMoveOnly(expected); |
|
| 8180 | - | if let case Type::Pointer { class: types::PointerClass::Ref, mutable, .. } = expected { |
|
| 8181 | - | set argExclusive = mutable; |
|
| 8182 | - | } else if let case Type::Slice { class: types::PointerClass::Ref, mutable, .. } = expected { |
|
| 8183 | - | set argExclusive = mutable; |
|
| 8184 | - | } else if let case Type::TraitObject { |
|
| 8185 | - | class: types::PointerClass::Ref, mutable, .. |
|
| 8186 | - | } = expected { |
|
| 8187 | - | set argExclusive = mutable; |
|
| 8188 | - | } |
|
| 8358 | + | let place = borrowPlace(checker.resolver, arg); |
|
| 8189 | 8359 | if not isUnsafePointerType(expected) { |
|
| 8190 | - | if let rootSym = root { |
|
| 8191 | - | for j in 0..rootsLen { |
|
| 8192 | - | if let previous = roots[j] { |
|
| 8193 | - | if previous == rootSym and (exclusive[j] or argExclusive) { |
|
| 8194 | - | throw emitError( |
|
| 8195 | - | checker.resolver, |
|
| 8196 | - | arg, |
|
| 8197 | - | ErrorKind::BorrowConflict(rootSym.name), |
|
| 8198 | - | ); |
|
| 8199 | - | } |
|
| 8360 | + | if let rootSym = place.root { |
|
| 8361 | + | for j in 0..placesLen { |
|
| 8362 | + | if (exclusive[j] or argExclusive) and placesOverlap(&places[j], &place) { |
|
| 8363 | + | throw emitError(checker.resolver, arg, ErrorKind::BorrowConflict(rootSym.name)); |
|
| 8200 | 8364 | } |
|
| 8201 | 8365 | } |
|
| 8202 | - | set roots[rootsLen] = rootSym; |
|
| 8203 | - | set exclusive[rootsLen] = argExclusive; |
|
| 8204 | - | set rootsLen += 1; |
|
| 8366 | + | set places[placesLen] = place; |
|
| 8367 | + | set exclusive[placesLen] = argExclusive; |
|
| 8368 | + | set placesLen += 1; |
|
| 8205 | 8369 | } |
|
| 8206 | 8370 | } |
|
| 8371 | + | try checkLocalLoans(checker, arg, argExclusive); |
|
| 8207 | 8372 | if isRefType(expected) { |
|
| 8208 | 8373 | try checkLinearNode(checker, env, arg, LinearUse::Borrow); |
|
| 8209 | 8374 | } else { |
|
| 8210 | 8375 | try checkLinearNode(checker, env, arg, LinearUse::Consume); |
|
| 8211 | 8376 | } |
| 8265 | 8430 | usage: LinearUse, |
|
| 8266 | 8431 | ) throws (ResolveError) { |
|
| 8267 | 8432 | if env.terminated { |
|
| 8268 | 8433 | return; |
|
| 8269 | 8434 | } |
|
| 8435 | + | if usage <> LinearUse::Locate { |
|
| 8436 | + | match node.value { |
|
| 8437 | + | case ast::NodeValue::Ident(_), ast::NodeValue::ScopeAccess(_), |
|
| 8438 | + | ast::NodeValue::FieldAccess(_), ast::NodeValue::Subscript { .. }, |
|
| 8439 | + | ast::NodeValue::Deref(_) => { |
|
| 8440 | + | let mut exclusive = usage == LinearUse::Place; |
|
| 8441 | + | if usage == LinearUse::Consume { |
|
| 8442 | + | if let ty = typeFor(checker.resolver, node) { |
|
| 8443 | + | set exclusive = isExclusiveArgument(ty); |
|
| 8444 | + | } |
|
| 8445 | + | } |
|
| 8446 | + | try checkLocalLoans(checker, node, exclusive); |
|
| 8447 | + | } |
|
| 8448 | + | else => {} |
|
| 8449 | + | } |
|
| 8450 | + | } |
|
| 8270 | 8451 | match node.value { |
|
| 8271 | 8452 | case ast::NodeValue::Ident(_) => { |
|
| 8272 | 8453 | if usage <> LinearUse::Place { |
|
| 8273 | 8454 | try checkLinearIdent(checker, env, node); |
|
| 8274 | 8455 | } |
| 8303 | 8484 | ErrorKind::LinearUndefined, |
|
| 8304 | 8485 | ); |
|
| 8305 | 8486 | } |
|
| 8306 | 8487 | } |
|
| 8307 | 8488 | try checkLinearNode(checker, env, binding.value, LinearUse::Consume); |
|
| 8489 | + | if env.terminated { |
|
| 8490 | + | return; |
|
| 8491 | + | } |
|
| 8308 | 8492 | try addLinearBinding(checker, env, node); |
|
| 8493 | + | try addLocalLoan(checker, node, binding); |
|
| 8309 | 8494 | if isUndefined { |
|
| 8310 | 8495 | markLinearBindingUnavailable(checker.resolver, env, node); |
|
| 8311 | 8496 | } |
|
| 8312 | 8497 | } |
|
| 8313 | 8498 | case ast::NodeValue::Assign(assign) => { |
| 8344 | 8529 | set env.available |= (1 as u64) << (index as u64); |
|
| 8345 | 8530 | } |
|
| 8346 | 8531 | } |
|
| 8347 | 8532 | case ast::NodeValue::Call(call) => try checkLinearCall(checker, env, node, call), |
|
| 8348 | 8533 | case ast::NodeValue::AddressOf(addr) => { |
|
| 8534 | + | try checkLocalLoans(checker, addr.target, addr.mutable); |
|
| 8349 | 8535 | if addr.mutable { |
|
| 8350 | 8536 | try checkPatternLoan(checker, addr.target); |
|
| 8351 | 8537 | } |
|
| 8352 | - | try checkLinearNode(checker, env, addr.target, LinearUse::Borrow); |
|
| 8538 | + | try checkLinearNode(checker, env, addr.target, LinearUse::Locate); |
|
| 8353 | 8539 | } |
|
| 8354 | 8540 | case ast::NodeValue::Deref(target) => { |
|
| 8355 | 8541 | if let resultTy = typeFor(checker.resolver, node) { |
|
| 8356 | 8542 | if isMoveOnly(resultTy) and usage == LinearUse::Consume { |
|
| 8357 | 8543 | throw emitError(checker.resolver, node, ErrorKind::LinearPartialMove); |
|
| 8358 | 8544 | } |
|
| 8359 | 8545 | } |
|
| 8360 | - | try checkLinearNode(checker, env, target, LinearUse::Observe); |
|
| 8546 | + | try checkLinearNode(checker, env, target, LinearUse::Locate); |
|
| 8361 | 8547 | } |
|
| 8362 | 8548 | case ast::NodeValue::FieldAccess(access) => { |
|
| 8363 | 8549 | if let resultTy = typeFor(checker.resolver, node) { |
|
| 8364 | 8550 | if isMoveOnly(resultTy) and usage == LinearUse::Consume { |
|
| 8365 | 8551 | throw emitError(checker.resolver, node, ErrorKind::LinearPartialMove); |
|
| 8366 | 8552 | } |
|
| 8367 | 8553 | } |
|
| 8368 | - | try checkLinearNode(checker, env, access.parent, LinearUse::Observe); |
|
| 8554 | + | try checkLinearNode(checker, env, access.parent, LinearUse::Locate); |
|
| 8369 | 8555 | } |
|
| 8370 | 8556 | case ast::NodeValue::ScopeAccess(_) => {} |
|
| 8371 | 8557 | case ast::NodeValue::Subscript { container, index } => { |
|
| 8372 | 8558 | if let resultTy = typeFor(checker.resolver, node) { |
|
| 8373 | 8559 | if isMoveOnly(resultTy) and usage == LinearUse::Consume { |
|
| 8374 | 8560 | throw emitError(checker.resolver, node, ErrorKind::LinearPartialMove); |
|
| 8375 | 8561 | } |
|
| 8376 | 8562 | } |
|
| 8377 | - | try checkLinearNode(checker, env, container, LinearUse::Observe); |
|
| 8563 | + | try checkLinearNode(checker, env, container, LinearUse::Locate); |
|
| 8378 | 8564 | try checkLinearNode(checker, env, index, LinearUse::Consume); |
|
| 8379 | 8565 | } |
|
| 8380 | 8566 | case ast::NodeValue::RecordLit(lit) => { |
|
| 8381 | 8567 | for fieldNode in lit.fields { |
|
| 8382 | 8568 | let case ast::NodeValue::RecordLitField(field) = fieldNode.value |
| 8710 | 8896 | ) throws (ResolveError) { |
|
| 8711 | 8897 | let mut checker = LinearChecker { |
|
| 8712 | 8898 | resolver: self as *unsafe mut Resolver, |
|
| 8713 | 8899 | loans: undefined, |
|
| 8714 | 8900 | loanLen: 0, |
|
| 8901 | + | locals: undefined, |
|
| 8902 | + | localLen: 0, |
|
| 8715 | 8903 | loopMarks: undefined, |
|
| 8716 | 8904 | loopAvailable: undefined, |
|
| 8717 | 8905 | loopExitAvailable: undefined, |
|
| 8718 | 8906 | loopHasNaturalExit: undefined, |
|
| 8719 | 8907 | loopBreakSeen: undefined, |
lib/std/lang/resolver/printer.rad
+2 -2
| 531 | 531 | } |
|
| 532 | 532 | case super::ErrorKind::ConflictingOwnershipMarkers => { |
|
| 533 | 533 | io::print("a composite cannot be both `Copy` and `Once`"); |
|
| 534 | 534 | } |
|
| 535 | 535 | case super::ErrorKind::InvalidRefPosition => { |
|
| 536 | - | io::print("reference type is only allowed as a function parameter"); |
|
| 536 | + | io::print("reference type is only allowed as a parameter or local binding"); |
|
| 537 | 537 | } |
|
| 538 | 538 | case super::ErrorKind::RefBinding => { |
|
| 539 | - | io::print("references cannot be bound to locals"); |
|
| 539 | + | io::print("reference local requires an immutable binding to existing storage"); |
|
| 540 | 540 | } |
|
| 541 | 541 | case super::ErrorKind::BorrowConflict(name) => { |
|
| 542 | 542 | printQuoted("conflicting borrow of '", name); |
|
| 543 | 543 | } |
|
| 544 | 544 | case super::ErrorKind::UnsafeOperation => { |
lib/std/lang/resolver/tests.rad
+119 -2
| 5722 | 5722 | } |
|
| 5723 | 5723 | ||
| 5724 | 5724 | /// Unsafe blocks preserve reference lifetime checks. |
|
| 5725 | 5725 | @test unsafe fn testUnsafeBlockPreservesReferences() throws (testing::TestError) { |
|
| 5726 | 5726 | let mut a = testResolver(); |
|
| 5727 | - | let result = try resolveProgramStr(&mut a, "fn run() { let n: u32 = 1; unsafe { let p = &n; } }"); |
|
| 5728 | - | try expectErrorKind(&result, super::ErrorKind::RefBinding); |
|
| 5727 | + | let result = try resolveProgramStr(&mut a, "fn run() { let mut n: u32 = 1; let p = &n; unsafe { set n = 2; } }"); |
|
| 5728 | + | try expectErrorKind(&result, super::ErrorKind::BorrowConflict("n")); |
|
| 5729 | 5729 | } |
|
| 5730 | 5730 | ||
| 5731 | 5731 | /// Slice pointer access requires an unsafe context for every slice class. |
|
| 5732 | 5732 | @test unsafe fn testSlicePointerRequiresUnsafe() throws (testing::TestError) { |
|
| 5733 | 5733 | let programs = &[ |
| 6224 | 6224 | let first = super::errorAt(snapshot.errors, 0) else throw testing::TestError::Failed; |
|
| 6225 | 6225 | try testing::expect(first.kind == original.kind); |
|
| 6226 | 6226 | try testing::expect(later.errors[0].kind == super::ErrorKind::Internal); |
|
| 6227 | 6227 | try testing::expect(super::errorAt(snapshot.errors, 1) == nil); |
|
| 6228 | 6228 | } |
|
| 6229 | + | ||
| 6230 | + | /// Prefixes, equal fields, and uncertain element locations must conflict. |
|
| 6231 | + | @test unsafe fn testOverlappingFieldBorrows() throws (testing::TestError) { |
|
| 6232 | + | let programs = &[ |
|
| 6233 | + | "record R: Copy { a: u32, b: u32 } fn take(a: &mut u32, b: &u32) {} fn run(r: &mut R) { take(&mut r.a, &r.a); }", |
|
| 6234 | + | "record R: Copy { a: u32, b: u32 } fn take(a: &mut R, b: &u32) {} fn run(r: &mut R) { take(r, &r.b); }", |
|
| 6235 | + | "record R: Copy { a: [u8; 2], b: [u8; 2] } fn take(a: &mut u8, b: &u8) {} fn run(r: &mut R) { take(&mut r.a[0], &r.a[1]); }", |
|
| 6236 | + | "record R: Copy { a: u32, b: u32 } fn take(a: &mut u32, b: u32) {} fn run(r: &mut R) { take(&mut r.a, r.a); }", |
|
| 6237 | + | ]; |
|
| 6238 | + | for program in programs { |
|
| 6239 | + | let mut a = testResolver(); |
|
| 6240 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 6241 | + | try expectErrorKind(&result, super::ErrorKind::BorrowConflict("r")); |
|
| 6242 | + | } |
|
| 6243 | + | } |
|
| 6244 | + | ||
| 6245 | + | /// Local loans exclude writes, competing references, and source reads. |
|
| 6246 | + | @test unsafe fn testLocalReferenceConflicts() throws (testing::TestError) { |
|
| 6247 | + | let programs = &[ |
|
| 6248 | + | "fn run() { let mut n: u32 = 4; let p = &n; set n = 1; }", |
|
| 6249 | + | "fn run() { let mut n: u32 = 4; let p = &mut n; n; }", |
|
| 6250 | + | "fn run() { let mut n: u32 = 4; let p = &mut n; let q = &n; }", |
|
| 6251 | + | "fn run() { let mut n: u32 = 4; let p = &n; let q = &mut n; }", |
|
| 6252 | + | "fn run() { let mut n: u32 = 4; let p = &mut n; let q = &mut *p; set *p = 1; }", |
|
| 6253 | + | "fn run() { let mut n: u32 = 4; let p = &mut n; let q = &*p; set *p = 1; }", |
|
| 6254 | + | "fn take(p: &mut u32) {} fn run() { let mut n: u32 = 4; let p = &n; take(&mut n); }", |
|
| 6255 | + | "fn run() { let mut n: u32 = 4; let p = &n; unsafe { set n = 1; } }", |
|
| 6256 | + | "fn run() { let mut n: u32 = 4; let p = &mut n; let q = &*p; let moved = p; }", |
|
| 6257 | + | ]; |
|
| 6258 | + | for program in programs { |
|
| 6259 | + | let mut a = testResolver(); |
|
| 6260 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 6261 | + | try expectErrorKind(&result, super::ErrorKind::BorrowConflict("n")); |
|
| 6262 | + | } |
|
| 6263 | + | } |
|
| 6264 | + | ||
| 6265 | + | /// Reference bindings cannot outlive temporary storage or change their source. |
|
| 6266 | + | @test unsafe fn testLocalReferenceStorage() throws (testing::TestError) { |
|
| 6267 | + | let programs = &[ |
|
| 6268 | + | "fn run() { let n: u32 = 4; let mut p: &u32 = &n; }", |
|
| 6269 | + | "record R: Copy { n: u32 } fn make() -> R { return R { n: 4 }; } fn run() { let p: &u32 = &make().n; }", |
|
| 6270 | + | ]; |
|
| 6271 | + | for program in programs { |
|
| 6272 | + | let mut a = testResolver(); |
|
| 6273 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 6274 | + | try expectErrorKind(&result, super::ErrorKind::RefBinding); |
|
| 6275 | + | } |
|
| 6276 | + | } |
|
| 6277 | + | ||
| 6278 | + | /// Stored and returned values cannot contain a local reference. |
|
| 6279 | + | @test unsafe fn testLocalReferenceEscapeRejected() throws (testing::TestError) { |
|
| 6280 | + | let programs = &[ |
|
| 6281 | + | "fn run() -> *u32 { let n: u32 = 1; let p = &n; return p; }", |
|
| 6282 | + | "fn run() -> *u32 { let n: u32 = 1; let p = &n; return p as *u32; }", |
|
| 6283 | + | "static DATA: u32 = 0; static P: *u32 = &DATA; fn run() { let n: u32 = 1; let p = &n; set P = p; }", |
|
| 6284 | + | "record R: Copy { p: *u32 } fn run() { let n: u32 = 1; let p = &n; let r = R { p }; }", |
|
| 6285 | + | ]; |
|
| 6286 | + | for program in programs { |
|
| 6287 | + | let mut a = testResolver(); |
|
| 6288 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 6289 | + | let _ = try expectError(&result); |
|
| 6290 | + | } |
|
| 6291 | + | } |
|
| 6292 | + | ||
| 6293 | + | /// Pointer indirection cannot prove that sibling pointees are disjoint. |
|
| 6294 | + | @test unsafe fn testIndirectFieldBorrowConflict() throws (testing::TestError) { |
|
| 6295 | + | let programs = &[ |
|
| 6296 | + | "record R: Copy { a: *unsafe mut u32, b: *unsafe mut u32 } fn take(a: &mut u32, b: &mut u32) {} unsafe fn run(r: &mut R) { take(&mut *r.a, &mut *r.b); }", |
|
| 6297 | + | "record R: Copy { a: *unsafe mut u32, b: *unsafe mut u32 } unsafe fn run(r: &mut R) { let a: &mut u32 = &mut *r.a; let b: &mut u32 = &mut *r.b; }", |
|
| 6298 | + | ]; |
|
| 6299 | + | for program in programs { |
|
| 6300 | + | let mut a = testResolver(); |
|
| 6301 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 6302 | + | try expectErrorKind(&result, super::ErrorKind::BorrowConflict("r")); |
|
| 6303 | + | } |
|
| 6304 | + | } |
|
| 6305 | + | ||
| 6306 | + | /// Pattern references protect their field and permit writes to disjoint fields. |
|
| 6307 | + | @test unsafe fn testDisjointPatternFieldBorrow() throws (testing::TestError) { |
|
| 6308 | + | let mut a = testResolver(); |
|
| 6309 | + | let result = try resolveProgramStr(&mut a, |
|
| 6310 | + | "union U: Copy { A(u32), B } record R: Copy { u: U, n: u32 } fn run(r: &mut R) { let alias = &mut r.u; match alias { case U::A(p) => { set *alias = U::B; *p; } else => {} } }"); |
|
| 6311 | + | try expectErrorKind(&result, super::ErrorKind::BorrowConflict("r")); |
|
| 6312 | + | } |
|
| 6313 | + | ||
| 6314 | + | /// Slice mutations must preserve storage held by a local reference. |
|
| 6315 | + | @test unsafe fn testLocalReferenceSliceMutationRejected() throws (testing::TestError) { |
|
| 6316 | + | let programs = &[ |
|
| 6317 | + | "fn run(s: *mut [u8]) { let p: &u8 = &s[0]; s.delete(0); }", |
|
| 6318 | + | "record A: Copy { func: unsafe fn(*unsafe mut opaque, u32, u32) -> *mut opaque, ctx: *unsafe mut opaque } fn run(s: *mut [u8], a: A) { let p: &u8 = &s[0]; s.append(1, a); }", |
|
| 6319 | + | ]; |
|
| 6320 | + | for program in programs { |
|
| 6321 | + | let mut a = testResolver(); |
|
| 6322 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 6323 | + | try expectErrorKind(&result, super::ErrorKind::BorrowConflict("s")); |
|
| 6324 | + | } |
|
| 6325 | + | } |
|
| 6326 | + | ||
| 6327 | + | /// Owner moves and mutable methods cannot invalidate a local loan. |
|
| 6328 | + | @test unsafe fn testLocalReferenceOwnerMutationRejected() throws (testing::TestError) { |
|
| 6329 | + | let programs = &[ |
|
| 6330 | + | "fn run(p: *mut u32) { let r: &u32 = &*p; let moved = p; }", |
|
| 6331 | + | "record R: Copy { a: u32 } fn (p: &mut R) change() { set p.a = 1; } fn run(p: &mut R) { let r = &p.a; p.change(); }", |
|
| 6332 | + | ]; |
|
| 6333 | + | for program in programs { |
|
| 6334 | + | let mut a = testResolver(); |
|
| 6335 | + | let result = try resolveProgramStr(&mut a, program); |
|
| 6336 | + | try expectErrorKind(&result, super::ErrorKind::BorrowConflict("p")); |
|
| 6337 | + | } |
|
| 6338 | + | } |
|
| 6339 | + | ||
| 6340 | + | /// Reference locals require a function scope and a reachable initializer result. |
|
| 6341 | + | @test unsafe fn testLocalReferenceDeclarationContext() throws (testing::TestError) { |
|
| 6342 | + | let mut a = testResolver(); |
|
| 6343 | + | let result = try resolveProgramStr(&mut a, "let n: u32 = 1; let p: &u32 = &n;"); |
|
| 6344 | + | try expectErrorKind(&result, super::ErrorKind::InvalidRefPosition); |
|
| 6345 | + | } |
test/tests/borrow.fields.calls.rad
added
+55 -0
| 1 | + | //! Inline record fields can supply separate call-scoped borrows. |
|
| 2 | + | ||
| 3 | + | /// Two scalar fields with separate storage. |
|
| 4 | + | record Pair: Copy { |
|
| 5 | + | /// First scalar. |
|
| 6 | + | a: u32, |
|
| 7 | + | /// Second scalar. |
|
| 8 | + | b: u32, |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Nested scalar storage. |
|
| 12 | + | record Nested: Copy { |
|
| 13 | + | /// Borrowed record. |
|
| 14 | + | pair: Pair, |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | /// Separate fixed buffers. |
|
| 18 | + | record Buffers: Copy { |
|
| 19 | + | /// First buffer. |
|
| 20 | + | a: [u8; 2], |
|
| 21 | + | /// Second buffer. |
|
| 22 | + | b: [u8; 2], |
|
| 23 | + | } |
|
| 24 | + | ||
| 25 | + | /// Accept two mutable scalar borrows. |
|
| 26 | + | fn mutablePair(a: &mut u32, b: &mut u32) {} |
|
| 27 | + | ||
| 28 | + | /// Accept a mutable borrow and a scalar value. |
|
| 29 | + | fn scalarPair(a: &mut u32, b: u32) {} |
|
| 30 | + | ||
| 31 | + | /// Accept mutable and shared scalar borrows. |
|
| 32 | + | fn sharedPair(a: &mut u32, b: &u32) {} |
|
| 33 | + | ||
| 34 | + | /// Accept two mutable buffer borrows. |
|
| 35 | + | fn buffers(a: &mut [u8], b: &mut [u8]) {} |
|
| 36 | + | ||
| 37 | + | /// Borrow two sibling fields mutably. |
|
| 38 | + | fn mutableFields(pair: &mut Pair) { |
|
| 39 | + | mutablePair(&mut pair.a, &mut pair.b); |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | /// Read a sibling field while borrowing another field mutably. |
|
| 43 | + | fn scalarField(pair: &mut Pair) { |
|
| 44 | + | scalarPair(&mut pair.a, pair.b); |
|
| 45 | + | } |
|
| 46 | + | ||
| 47 | + | /// Borrow sibling fields in a nested record. |
|
| 48 | + | fn nestedFields(value: &mut Nested) { |
|
| 49 | + | sharedPair(&mut value.pair.a, &value.pair.b); |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | /// Borrow separate inline arrays as slices. |
|
| 53 | + | fn arrayFields(value: &mut Buffers) { |
|
| 54 | + | buffers(&mut value.a[..], &mut value.b[..]); |
|
| 55 | + | } |
test/tests/borrow.fields.calls.ril
added
+55 -0
| 1 | + | fn w64 $mutablePair(w64 %0, w64 %1) { |
|
| 2 | + | @entry0 |
|
| 3 | + | ret; |
|
| 4 | + | } |
|
| 5 | + | ||
| 6 | + | fn w64 $scalarPair(w64 %0, w32 %1) { |
|
| 7 | + | @entry0 |
|
| 8 | + | ret; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | fn w64 $sharedPair(w64 %0, w64 %1) { |
|
| 12 | + | @entry0 |
|
| 13 | + | ret; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | fn w64 $buffers(w64 %0, w64 %1) { |
|
| 17 | + | @entry0 |
|
| 18 | + | ret; |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | fn w64 $mutableFields(w64 %0) { |
|
| 22 | + | @entry0 |
|
| 23 | + | add w64 %1 %0 4; |
|
| 24 | + | call w64 $mutablePair(%0, %1); |
|
| 25 | + | ret; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | fn w64 $scalarField(w64 %0) { |
|
| 29 | + | @entry0 |
|
| 30 | + | load w32 %1 %0 4; |
|
| 31 | + | call w64 $scalarPair(%0, %1); |
|
| 32 | + | ret; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | fn w64 $nestedFields(w64 %0) { |
|
| 36 | + | @entry0 |
|
| 37 | + | add w64 %1 %0 4; |
|
| 38 | + | call w64 $sharedPair(%0, %1); |
|
| 39 | + | ret; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | fn w64 $arrayFields(w64 %0) { |
|
| 43 | + | @entry0 |
|
| 44 | + | reserve %1 16 8; |
|
| 45 | + | store w64 %0 %1 0; |
|
| 46 | + | store w32 2 %1 8; |
|
| 47 | + | store w32 2 %1 12; |
|
| 48 | + | add w64 %2 %0 2; |
|
| 49 | + | reserve %3 16 8; |
|
| 50 | + | store w64 %2 %3 0; |
|
| 51 | + | store w32 2 %3 8; |
|
| 52 | + | store w32 2 %3 12; |
|
| 53 | + | call w64 $buffers(%1, %3); |
|
| 54 | + | ret; |
|
| 55 | + | } |
test/tests/borrow.fields.pattern.rad
added
+27 -0
| 1 | + | //! Pattern references protect their field and permit writes to disjoint fields. |
|
| 2 | + | ||
| 3 | + | /// Optional scalar payload. |
|
| 4 | + | union Value: Copy { |
|
| 5 | + | /// Present scalar. |
|
| 6 | + | Present(u32), |
|
| 7 | + | /// Empty value. |
|
| 8 | + | Empty, |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Payload and independent output storage. |
|
| 12 | + | record Pair: Copy { |
|
| 13 | + | /// Matched payload. |
|
| 14 | + | value: Value, |
|
| 15 | + | /// Copied scalar. |
|
| 16 | + | output: u32, |
|
| 17 | + | } |
|
| 18 | + | ||
| 19 | + | /// Write a sibling field while a pattern borrows the payload. |
|
| 20 | + | fn copyPayload(pair: &mut Pair) { |
|
| 21 | + | match &pair.value { |
|
| 22 | + | case Value::Present(value) => { |
|
| 23 | + | set pair.output = *value; |
|
| 24 | + | }, |
|
| 25 | + | else => {}, |
|
| 26 | + | } |
|
| 27 | + | } |
test/tests/borrow.fields.pattern.ril
added
+18 -0
| 1 | + | fn w64 $copyPayload(w64 %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | jmp @arm1; |
|
| 4 | + | @arm1 |
|
| 5 | + | load w8 %1 %0 0; |
|
| 6 | + | br.eq w8 %1 0 @case2 @arm3; |
|
| 7 | + | @case2 |
|
| 8 | + | add w64 %2 %0 4; |
|
| 9 | + | load w32 %3 %2 0; |
|
| 10 | + | store w32 %3 %0 8; |
|
| 11 | + | jmp @merge4; |
|
| 12 | + | @arm3 |
|
| 13 | + | jmp @else5; |
|
| 14 | + | @merge4 |
|
| 15 | + | ret; |
|
| 16 | + | @else5 |
|
| 17 | + | jmp @merge4; |
|
| 18 | + | } |
test/tests/ref.local.bindings.rad
added
+77 -0
| 1 | + | //! Reference locals can borrow stack storage and disjoint inline fields. |
|
| 2 | + | ||
| 3 | + | /// Read a scalar through an inferred shared reference. |
|
| 4 | + | fn sharedLocal() -> u32 { |
|
| 5 | + | let n: u32 = 4; |
|
| 6 | + | let p = &n; |
|
| 7 | + | return *p; |
|
| 8 | + | } |
|
| 9 | + | ||
| 10 | + | /// Mutate through an explicit reference, then read its source. |
|
| 11 | + | fn mutableLocal() -> u32 { |
|
| 12 | + | let mut n: u32 = 4; |
|
| 13 | + | { |
|
| 14 | + | let p: &mut u32 = &mut n; |
|
| 15 | + | set *p = 7; |
|
| 16 | + | } |
|
| 17 | + | return n; |
|
| 18 | + | } |
|
| 19 | + | ||
| 20 | + | /// Read through both aliases of a shared reference. |
|
| 21 | + | fn sharedAlias() -> u32 { |
|
| 22 | + | let n: u32 = 4; |
|
| 23 | + | let p: &u32 = &n; |
|
| 24 | + | let q = p; |
|
| 25 | + | return *p + *q; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// Resume access through a parent after its mutable reborrow ends. |
|
| 29 | + | fn reborrow() -> u32 { |
|
| 30 | + | let mut n: u32 = 4; |
|
| 31 | + | { |
|
| 32 | + | let p = &mut n; |
|
| 33 | + | { |
|
| 34 | + | let q = &mut *p; |
|
| 35 | + | set *q = 7; |
|
| 36 | + | } |
|
| 37 | + | set *p = 9; |
|
| 38 | + | } |
|
| 39 | + | return n; |
|
| 40 | + | } |
|
| 41 | + | ||
| 42 | + | /// Two independently borrowed scalar fields. |
|
| 43 | + | record Pair: Copy { |
|
| 44 | + | /// First scalar. |
|
| 45 | + | a: u32, |
|
| 46 | + | /// Second scalar. |
|
| 47 | + | b: u32, |
|
| 48 | + | } |
|
| 49 | + | ||
| 50 | + | /// Mutate sibling fields through separate local references. |
|
| 51 | + | fn disjointLocals(r: &mut Pair) { |
|
| 52 | + | let a = &mut r.a; |
|
| 53 | + | let b = &mut r.b; |
|
| 54 | + | set *a = 1; |
|
| 55 | + | set *b = 2; |
|
| 56 | + | } |
|
| 57 | + | ||
| 58 | + | /// Mutate a stack array through a local slice reference. |
|
| 59 | + | fn sliceLocal() -> u8 { |
|
| 60 | + | let mut data: [u8; 2] = [1, 2]; |
|
| 61 | + | { |
|
| 62 | + | let s = &mut data[..]; |
|
| 63 | + | set s[0] = 3; |
|
| 64 | + | } |
|
| 65 | + | return data[0]; |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | /// Move a mutable reference into another local binding. |
|
| 69 | + | fn moveReference() -> u32 { |
|
| 70 | + | let mut n: u32 = 4; |
|
| 71 | + | { |
|
| 72 | + | let p = &mut n; |
|
| 73 | + | let q = p; |
|
| 74 | + | set *q = 7; |
|
| 75 | + | } |
|
| 76 | + | return n; |
|
| 77 | + | } |
test/tests/ref.local.bindings.ril
added
+74 -0
| 1 | + | fn w32 $sharedLocal() { |
|
| 2 | + | @entry0 |
|
| 3 | + | reserve %0 4 4; |
|
| 4 | + | store w32 4 %0 0; |
|
| 5 | + | load w32 %1 %0 0; |
|
| 6 | + | ret %1; |
|
| 7 | + | } |
|
| 8 | + | ||
| 9 | + | fn w32 $mutableLocal() { |
|
| 10 | + | @entry0 |
|
| 11 | + | reserve %0 4 4; |
|
| 12 | + | store w32 4 %0 0; |
|
| 13 | + | store w32 7 %0 0; |
|
| 14 | + | load w32 %1 %0 0; |
|
| 15 | + | ret %1; |
|
| 16 | + | } |
|
| 17 | + | ||
| 18 | + | fn w32 $sharedAlias() { |
|
| 19 | + | @entry0 |
|
| 20 | + | reserve %0 4 4; |
|
| 21 | + | store w32 4 %0 0; |
|
| 22 | + | load w32 %1 %0 0; |
|
| 23 | + | load w32 %2 %0 0; |
|
| 24 | + | add w32 %3 %1 %2; |
|
| 25 | + | ret %3; |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | fn w32 $reborrow() { |
|
| 29 | + | @entry0 |
|
| 30 | + | reserve %0 4 4; |
|
| 31 | + | store w32 4 %0 0; |
|
| 32 | + | store w32 7 %0 0; |
|
| 33 | + | store w32 9 %0 0; |
|
| 34 | + | load w32 %1 %0 0; |
|
| 35 | + | ret %1; |
|
| 36 | + | } |
|
| 37 | + | ||
| 38 | + | fn w64 $disjointLocals(w64 %0) { |
|
| 39 | + | @entry0 |
|
| 40 | + | add w64 %1 %0 4; |
|
| 41 | + | store w32 1 %0 0; |
|
| 42 | + | store w32 2 %1 0; |
|
| 43 | + | ret; |
|
| 44 | + | } |
|
| 45 | + | ||
| 46 | + | fn w8 $sliceLocal() { |
|
| 47 | + | @entry0 |
|
| 48 | + | reserve %0 2 1; |
|
| 49 | + | store w8 1 %0 0; |
|
| 50 | + | store w8 2 %0 1; |
|
| 51 | + | reserve %1 16 8; |
|
| 52 | + | store w64 %0 %1 0; |
|
| 53 | + | store w32 2 %1 8; |
|
| 54 | + | store w32 2 %1 12; |
|
| 55 | + | load w32 %2 %1 8; |
|
| 56 | + | br.ult w32 0 %2 @guard#pass1 @guard#trap2; |
|
| 57 | + | @guard#pass1 |
|
| 58 | + | load w64 %3 %1 0; |
|
| 59 | + | store w8 3 %3 0; |
|
| 60 | + | load w8 %4 %0 0; |
|
| 61 | + | ret %4; |
|
| 62 | + | @guard#trap2 |
|
| 63 | + | ebreak; |
|
| 64 | + | unreachable; |
|
| 65 | + | } |
|
| 66 | + | ||
| 67 | + | fn w32 $moveReference() { |
|
| 68 | + | @entry0 |
|
| 69 | + | reserve %0 4 4; |
|
| 70 | + | store w32 4 %0 0; |
|
| 71 | + | store w32 7 %0 0; |
|
| 72 | + | load w32 %1 %0 0; |
|
| 73 | + | ret %1; |
|
| 74 | + | } |
test/tests/ref.local.fields.rad
added
+76 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | ||
| 3 | + | /// Two independently writable values. |
|
| 4 | + | record Pair: Copy { |
|
| 5 | + | /// First value. |
|
| 6 | + | left: u32, |
|
| 7 | + | /// Second value. |
|
| 8 | + | right: u32, |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | /// Inline storage for nested borrows and borrowed slices. |
|
| 12 | + | record Storage: Copy { |
|
| 13 | + | /// Nested scalar fields. |
|
| 14 | + | pair: Pair, |
|
| 15 | + | /// First byte buffer. |
|
| 16 | + | first: [u8; 2], |
|
| 17 | + | /// Second byte buffer. |
|
| 18 | + | second: [u8; 2], |
|
| 19 | + | } |
|
| 20 | + | ||
| 21 | + | /// Change two distinct scalar locations. |
|
| 22 | + | fn change(left: &mut u32, right: &mut u32) { |
|
| 23 | + | set *left += 3; |
|
| 24 | + | set *right += 5; |
|
| 25 | + | } |
|
| 26 | + | ||
| 27 | + | /// Copy a byte between separate buffers. |
|
| 28 | + | fn copy(first: &mut [u8], second: &[u8]) { |
|
| 29 | + | set first[0] = second[1]; |
|
| 30 | + | } |
|
| 31 | + | ||
| 32 | + | /// Add a scalar read from a separate field. |
|
| 33 | + | fn add(target: &mut u32, value: u32) { |
|
| 34 | + | set *target += value; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Exercise lexical references and disjoint fields in generated code. |
|
| 38 | + | @default fn main() -> i32 { |
|
| 39 | + | let mut data = Storage { |
|
| 40 | + | pair: Pair { left: 1, right: 2 }, first: [0, 0], second: [7, 9], |
|
| 41 | + | }; |
|
| 42 | + | change(&mut data.pair.left, &mut data.pair.right); |
|
| 43 | + | add(&mut data.pair.left, data.pair.right); |
|
| 44 | + | { |
|
| 45 | + | let pair: &mut Pair = &mut data.pair; |
|
| 46 | + | let left = &mut pair.left; |
|
| 47 | + | let right = &mut pair.right; |
|
| 48 | + | change(left, right); |
|
| 49 | + | { |
|
| 50 | + | let child = &mut *left; |
|
| 51 | + | set *child += 1; |
|
| 52 | + | } |
|
| 53 | + | set *left += 2; |
|
| 54 | + | } |
|
| 55 | + | { |
|
| 56 | + | let first = &mut data.first[..]; |
|
| 57 | + | let second: &[u8] = &data.second[..]; |
|
| 58 | + | copy(first, second); |
|
| 59 | + | } |
|
| 60 | + | if data.pair.left <> 17 or data.pair.right <> 12 or data.first[0] <> 9 { |
|
| 61 | + | return 1; |
|
| 62 | + | } |
|
| 63 | + | for i in 0..3 { |
|
| 64 | + | let value = &mut data.pair.left; |
|
| 65 | + | set *value += i; |
|
| 66 | + | } |
|
| 67 | + | if data.pair.left <> 20 { |
|
| 68 | + | return 2; |
|
| 69 | + | } |
|
| 70 | + | let view = &data.pair; |
|
| 71 | + | let alias = view; |
|
| 72 | + | if view.left <> alias.left { |
|
| 73 | + | return 3; |
|
| 74 | + | } |
|
| 75 | + | return 0; |
|
| 76 | + | } |
test/tests/ref.local.never.rad
added
+11 -0
| 1 | + | //! Reference locals require a function scope and a reachable initializer result. |
|
| 2 | + | ||
| 3 | + | /// Terminate the current control-flow path. |
|
| 4 | + | fn stop() -> ! { |
|
| 5 | + | panic; |
|
| 6 | + | } |
|
| 7 | + | ||
| 8 | + | /// End execution while evaluating a reference initializer. |
|
| 9 | + | fn initialize() { |
|
| 10 | + | let value: &u32 = stop(); |
|
| 11 | + | } |
test/tests/ref.local.never.ril
added
+10 -0
| 1 | + | fn w64 $stop() { |
|
| 2 | + | @entry0 |
|
| 3 | + | unreachable; |
|
| 4 | + | } |
|
| 5 | + | ||
| 6 | + | fn w64 $initialize() { |
|
| 7 | + | @entry0 |
|
| 8 | + | call w64 $stop(); |
|
| 9 | + | unreachable; |
|
| 10 | + | } |
test/tests/ref.local.scopes.rad
added
+61 -0
| 1 | + | //! Local loans end at branch and loop scope boundaries. |
|
| 2 | + | ||
| 3 | + | /// Release each branch borrow before accessing the source. |
|
| 4 | + | fn branches(flag: bool) { |
|
| 5 | + | let mut n: u32 = 0; |
|
| 6 | + | if flag { |
|
| 7 | + | let p = &mut n; |
|
| 8 | + | set *p = 1; |
|
| 9 | + | } else { |
|
| 10 | + | let q = &mut n; |
|
| 11 | + | set *q = 2; |
|
| 12 | + | } |
|
| 13 | + | set n = 3; |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | /// Release the body borrow before the next condition check. |
|
| 17 | + | fn whileLoop() { |
|
| 18 | + | let mut n: u32 = 0; |
|
| 19 | + | while n < 4 { |
|
| 20 | + | let p = &mut n; |
|
| 21 | + | set *p += 1; |
|
| 22 | + | } |
|
| 23 | + | set n = 5; |
|
| 24 | + | } |
|
| 25 | + | ||
| 26 | + | /// Release the body borrow when the loop exits. |
|
| 27 | + | fn breakLoop() { |
|
| 28 | + | let mut n: u32 = 0; |
|
| 29 | + | loop { |
|
| 30 | + | let p = &mut n; |
|
| 31 | + | set *p = 1; |
|
| 32 | + | break; |
|
| 33 | + | } |
|
| 34 | + | set n = 2; |
|
| 35 | + | } |
|
| 36 | + | ||
| 37 | + | /// Two scalar fields. |
|
| 38 | + | record Pair: Copy { |
|
| 39 | + | /// First scalar. |
|
| 40 | + | a: u32, |
|
| 41 | + | /// Second scalar. |
|
| 42 | + | b: u32, |
|
| 43 | + | } |
|
| 44 | + | ||
| 45 | + | /// Nested and independent scalar storage. |
|
| 46 | + | record Nested: Copy { |
|
| 47 | + | /// Nested record. |
|
| 48 | + | r: Pair, |
|
| 49 | + | /// Independent scalar. |
|
| 50 | + | n: u32, |
|
| 51 | + | } |
|
| 52 | + | ||
| 53 | + | /// Borrow nested siblings while mutating a separate outer field. |
|
| 54 | + | fn nestedFields(s: &mut Nested) { |
|
| 55 | + | let r = &mut s.r; |
|
| 56 | + | let a = &mut r.a; |
|
| 57 | + | let b = &mut r.b; |
|
| 58 | + | set *a = 1; |
|
| 59 | + | set *b = 2; |
|
| 60 | + | set s.n = 3; |
|
| 61 | + | } |
test/tests/ref.local.scopes.ril
added
+55 -0
| 1 | + | fn w64 $branches(w8 %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | reserve %1 4 4; |
|
| 4 | + | store w32 0 %1 0; |
|
| 5 | + | br.ne w32 %0 0 @then1 @else2; |
|
| 6 | + | @then1 |
|
| 7 | + | store w32 1 %1 0; |
|
| 8 | + | jmp @merge3; |
|
| 9 | + | @else2 |
|
| 10 | + | store w32 2 %1 0; |
|
| 11 | + | jmp @merge3; |
|
| 12 | + | @merge3 |
|
| 13 | + | store w32 3 %1 0; |
|
| 14 | + | ret; |
|
| 15 | + | } |
|
| 16 | + | ||
| 17 | + | fn w64 $whileLoop() { |
|
| 18 | + | @entry0 |
|
| 19 | + | reserve %0 4 4; |
|
| 20 | + | store w32 0 %0 0; |
|
| 21 | + | jmp @while1(%0); |
|
| 22 | + | @while1(w32 %1) |
|
| 23 | + | load w32 %2 %1 0; |
|
| 24 | + | br.ult w32 %2 4 @body2 @merge3; |
|
| 25 | + | @body2 |
|
| 26 | + | load w32 %3 %1 0; |
|
| 27 | + | add w32 %4 %3 1; |
|
| 28 | + | store w32 %4 %1 0; |
|
| 29 | + | jmp @while1(%1); |
|
| 30 | + | @merge3 |
|
| 31 | + | store w32 5 %1 0; |
|
| 32 | + | ret; |
|
| 33 | + | } |
|
| 34 | + | ||
| 35 | + | fn w64 $breakLoop() { |
|
| 36 | + | @entry0 |
|
| 37 | + | reserve %0 4 4; |
|
| 38 | + | store w32 0 %0 0; |
|
| 39 | + | jmp @loop1(%0); |
|
| 40 | + | @loop1(w32 %1) |
|
| 41 | + | store w32 1 %1 0; |
|
| 42 | + | jmp @merge2; |
|
| 43 | + | @merge2 |
|
| 44 | + | store w32 2 %1 0; |
|
| 45 | + | ret; |
|
| 46 | + | } |
|
| 47 | + | ||
| 48 | + | fn w64 $nestedFields(w64 %0) { |
|
| 49 | + | @entry0 |
|
| 50 | + | add w64 %1 %0 4; |
|
| 51 | + | store w32 1 %0 0; |
|
| 52 | + | store w32 2 %1 0; |
|
| 53 | + | store w32 3 %0 8; |
|
| 54 | + | ret; |
|
| 55 | + | } |
test/tests/ref.local.slice.release.rad
added
+10 -0
| 1 | + | //! Slice mutations must preserve storage held by a local reference. |
|
| 2 | + | ||
| 3 | + | /// Delete an element after its shared reference leaves scope. |
|
| 4 | + | fn deleteAfterBorrow(slice: *mut [u8]) { |
|
| 5 | + | { |
|
| 6 | + | let value: &u8 = &slice[0]; |
|
| 7 | + | *value; |
|
| 8 | + | } |
|
| 9 | + | slice.delete(0); |
|
| 10 | + | } |
test/tests/ref.local.slice.release.ril
added
+36 -0
| 1 | + | fn w64 $deleteAfterBorrow(w64 %0) { |
|
| 2 | + | @entry0 |
|
| 3 | + | load w32 %1 %0 8; |
|
| 4 | + | br.ult w32 0 %1 @guard#pass1 @guard#trap2; |
|
| 5 | + | @guard#pass1 |
|
| 6 | + | load w64 %2 %0 0; |
|
| 7 | + | load w8 %3 %2 0; |
|
| 8 | + | load w32 %4 %0 8; |
|
| 9 | + | br.ult w32 0 %4 @guard#pass3 @guard#trap4; |
|
| 10 | + | @guard#trap2 |
|
| 11 | + | ebreak; |
|
| 12 | + | unreachable; |
|
| 13 | + | @guard#pass3 |
|
| 14 | + | load w64 %5 %0 0; |
|
| 15 | + | add w64 %6 %5 1; |
|
| 16 | + | sub w32 %7 %4 0; |
|
| 17 | + | sub w32 %8 %7 1; |
|
| 18 | + | mul w32 %9 %8 1; |
|
| 19 | + | jmp @delete5(0); |
|
| 20 | + | @guard#trap4 |
|
| 21 | + | ebreak; |
|
| 22 | + | unreachable; |
|
| 23 | + | @delete5(w32 %10) |
|
| 24 | + | br.ult w32 %10 %9 @delete6 @delete7; |
|
| 25 | + | @delete6 |
|
| 26 | + | add w64 %11 %6 %10; |
|
| 27 | + | load w8 %12 %11 0; |
|
| 28 | + | add w64 %13 %5 %10; |
|
| 29 | + | store w8 %12 %13 0; |
|
| 30 | + | add w32 %14 %10 1; |
|
| 31 | + | jmp @delete5(%14); |
|
| 32 | + | @delete7 |
|
| 33 | + | sub w32 %15 %4 1; |
|
| 34 | + | store w32 %15 %0 8; |
|
| 35 | + | ret; |
|
| 36 | + | } |