Support checked local references and disjoint field borrows

56a00753efd746a24c163f94ba7770c8d90cda638c0f2a0ef28c6b456b2367bb
Alexis Sellier committed ago 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 +
}