compiler: Retain and check conditional call argument loans

7f92c2e1ce7245fcb8b37e4114564678614c5bed21cd9632e2036ff3833b2901
Track explicit reference arguments until the call starts and compare each
possible conditional argument place with earlier arguments. This prevents a
later argument from mutating or aliasing storage borrowed by an earlier one.
The checker releases these temporary loans after the call.
Alexis Sellier committed ago 1 parent d5fdaec0
lib/std/lang/resolver.rad +108 -61
839 839
    len: u32,
840 840
    /// Whether further projections can identify distinct storage.
841 841
    precise: bool,
842 842
}
843 843
844 -
/// A reference binding that protects its source for one lexical scope.
844 +
/// Storage protected by a reference binding or a pending call argument.
845 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,
846 +
    /// Local symbol that provides access, or nil for a pending call argument.
847 +
    binding: ?*unsafe mut Symbol,
848 +
    /// Expression that supplies the borrowed storage.
849 +
    source: *ast::Node,
850 850
    /// Whether other reads of the source are excluded.
851 851
    exclusive: bool,
852 852
}
853 853
854 854
/// Function-local exact-use checker state.
7964 7964
7965 7965
/// Reject accesses that conflict with a reference in an active lexical scope.
7966 7966
unsafe fn checkLocalLoans(checker: &mut LinearChecker, node: *ast::Node, exclusive: bool)
7967 7967
    throws (ResolveError)
7968 7968
{
7969 -
    let place = borrowPlace(checker.resolver, node);
7969 +
    try checkLoans(checker.resolver, &checker.locals[..checker.localLen], node, exclusive);
7970 +
}
7971 +
7972 +
/// Reject accesses that conflict with any of the supplied loans.
7973 +
unsafe fn checkLoans(
7974 +
    self: &mut Resolver, loans: &[LocalLoan], node: *ast::Node, exclusive: bool
7975 +
) throws (ResolveError) {
7976 +
    match node.value {
7977 +
        case ast::NodeValue::CondExpr(cond) => {
7978 +
            try checkLoans(self, loans, cond.thenExpr, exclusive);
7979 +
            try checkLoans(self, loans, cond.elseExpr, exclusive);
7980 +
            return;
7981 +
        }
7982 +
        case ast::NodeValue::As(cast) => {
7983 +
            try checkLoans(self, loans, cast.value, exclusive);
7984 +
            return;
7985 +
        }
7986 +
        else => {}
7987 +
    }
7988 +
    let place = borrowPlace(self, node);
7970 7989
    let root = place.root else return;
7971 -
    for i in 0..checker.localLen {
7972 -
        let loan = checker.locals[i];
7973 -
        if (exclusive or loan.exclusive) and placesOverlap(&place, &loan.place)
7974 -
            and not usesLocalLoan(checker.resolver, node, loan.binding)
7975 -
        {
7976 -
            throw emitError(checker.resolver, node, ErrorKind::BorrowConflict(root.name));
7990 +
    for loan in loans {
7991 +
        if not (exclusive or loan.exclusive) {
7992 +
            continue;
7993 +
        }
7994 +
        if let binding = loan.binding; usesLocalLoan(self, node, binding) {
7995 +
            continue;
7996 +
        }
7997 +
        if borrowOverlaps(self, &place, loan.source) {
7998 +
            throw emitError(self, node, ErrorKind::BorrowConflict(root.name));
7977 7999
        }
7978 8000
    }
7979 8001
}
7980 8002
7981 8003
/// Retain existing storage until its immutable reference binding leaves scope.
7994 8016
        throw emitError(checker.resolver, node, ErrorKind::Internal);
7995 8017
    }
7996 8018
    let sym = symbolFor(checker.resolver, node) else panic "reference without binding";
7997 8019
    let exclusive = isExclusiveArgument(ty);
7998 8020
    try checkLocalLoans(checker, binding.value, exclusive);
7999 -
    set checker.locals[checker.localLen] = LocalLoan { binding: sym, place, exclusive };
8021 +
    set checker.locals[checker.localLen] = LocalLoan {
8022 +
        binding: sym, source: binding.value, exclusive,
8023 +
    };
8000 8024
    set checker.localLen += 1;
8001 8025
}
8002 8026
8003 8027
/// Protect a pattern source until its reference bindings leave scope.
8004 8028
unsafe fn addPatternLoan(checker: &mut LinearChecker, subject: *ast::Node)
8293 8317
    checker: &mut LinearChecker,
8294 8318
    env: &mut LinearEnv,
8295 8319
    node: *ast::Node,
8296 8320
    call: ast::Call,
8297 8321
) throws (ResolveError) {
8322 +
    let localStart = checker.localLen;
8298 8323
    match checker.resolver.nodeData.entries[node.id].extra {
8299 8324
        case NodeExtra::SliceAppend { .. }, NodeExtra::SliceDelete { .. } => {
8300 8325
            let case ast::NodeValue::FieldAccess(access) = call.callee.value
8301 8326
                else panic "slice mutation without receiver";
8302 8327
            try checkPatternLoan(checker, access.parent);
8304 8329
        }
8305 8330
        else => {}
8306 8331
    }
8307 8332
    try checkLinearNode(checker, env, call.callee, LinearUse::Observe);
8308 8333
    let mut fnInfo: ?*FnType = nil;
8334 +
    let mut receiverClass: ?types::PointerClass = nil;
8335 +
    let mut receiverMutable = false;
8309 8336
    match checker.resolver.nodeData.entries[node.id].extra {
8310 -
        case NodeExtra::TraitMethodCall { traitInfo, methodIndex } =>
8311 -
            set fnInfo = traitInfo.methods[methodIndex].fnType,
8312 -
        case NodeExtra::MethodCall { method } => set fnInfo = method.fnType,
8337 +
        case NodeExtra::TraitMethodCall { traitInfo, methodIndex } => {
8338 +
            let method = &traitInfo.methods[methodIndex];
8339 +
            set fnInfo = method.fnType;
8340 +
            set receiverClass = method.receiverClass;
8341 +
            set receiverMutable = method.mutable;
8342 +
        }
8343 +
        case NodeExtra::MethodCall { method } => {
8344 +
            set fnInfo = method.fnType;
8345 +
            set receiverClass = method.receiverClass;
8346 +
            set receiverMutable = method.mutable;
8347 +
        }
8313 8348
        else => {
8314 8349
            if let calleeTy = typeFor(checker.resolver, call.callee) {
8315 8350
                if let case Type::Fn(info) = calleeTy {
8316 8351
                    set fnInfo = info;
8317 8352
                }
8322 8357
        for arg in call.args {
8323 8358
            try checkLinearNode(checker, env, arg, LinearUse::Consume);
8324 8359
        }
8325 8360
        return;
8326 8361
    };
8327 -
    let mut places: [BorrowPlace; MAX_FN_PARAMS + 1] = undefined;
8328 -
    let mut exclusive: [bool; MAX_FN_PARAMS + 1] = undefined;
8329 -
    let mut placesLen: u32 = 0;
8362 +
    let mut arguments: [LocalLoan; MAX_FN_PARAMS + 1] = undefined;
8363 +
    let mut argumentsLen: u32 = 0;
8330 8364
8331 8365
    // Method function types exclude their implicit receiver. Account for it
8332 8366
    // explicitly so owning receivers are consumed and reference receivers
8333 8367
    // participate in call-scoped loan conflict checks.
8334 8368
    if let case ast::NodeValue::FieldAccess(access) = call.callee.value {
8335 -
        let mut receiverClass = types::PointerClass::Unsafe;
8336 -
        let mut receiverMutable = false;
8337 -
        let mut haveReceiver = false;
8338 -
        match checker.resolver.nodeData.entries[node.id].extra {
8339 -
            case NodeExtra::TraitMethodCall { traitInfo, methodIndex } => {
8340 -
                let method = &traitInfo.methods[methodIndex];
8341 -
                set receiverClass = method.receiverClass;
8342 -
                set receiverMutable = method.mutable;
8343 -
                set haveReceiver = true;
8344 -
            }
8345 -
            case NodeExtra::MethodCall { method } => {
8346 -
                set receiverClass = method.receiverClass;
8347 -
                set receiverMutable = method.mutable;
8348 -
                set haveReceiver = true;
8349 -
            }
8350 -
            else => {}
8351 -
        }
8352 -
        if haveReceiver {
8353 -
            try checkLocalLoans(checker, access.parent,
8354 -
                receiverMutable or receiverClass == types::PointerClass::Owned);
8355 -
            if receiverMutable or receiverClass == types::PointerClass::Owned {
8369 +
        if let receiverClass = receiverClass {
8370 +
            let loan = LocalLoan {
8371 +
                binding: nil, source: access.parent,
8372 +
                exclusive: receiverClass == types::PointerClass::Owned or receiverMutable,
8373 +
            };
8374 +
            try checkLocalLoans(checker, access.parent, loan.exclusive);
8375 +
            if loan.exclusive {
8356 8376
                try checkPatternLoan(checker, access.parent);
8357 8377
            }
8358 8378
            if receiverClass <> types::PointerClass::Unsafe {
8359 -
                let place = borrowPlace(checker.resolver, access.parent);
8360 -
                if place.root <> nil {
8361 -
                    set places[placesLen] = place;
8362 -
                    set exclusive[placesLen] =
8363 -
                        receiverClass == types::PointerClass::Owned or receiverMutable;
8364 -
                    set placesLen += 1;
8365 -
                }
8379 +
                set arguments[argumentsLen] = loan;
8380 +
                set argumentsLen += 1;
8366 8381
            }
8367 8382
            if receiverClass == types::PointerClass::Ref {
8368 8383
                try checkLinearNode(checker, env, access.parent, LinearUse::Borrow);
8384 +
                try retainCallLoan(checker, loan);
8369 8385
            } else if receiverClass == types::PointerClass::Owned {
8370 8386
                try checkLinearNode(checker, env, access.parent, LinearUse::Consume);
8371 8387
            }
8372 8388
        }
8373 8389
    }
8374 8390
8375 8391
    for arg, i in call.args {
8376 8392
        let expected = *info.paramTypes[i];
8377 -
        let argExclusive = isExclusiveArgument(expected);
8378 -
        if argExclusive {
8393 +
        let loan = LocalLoan { binding: nil, source: arg, exclusive: isExclusiveArgument(expected) };
8394 +
        if loan.exclusive {
8379 8395
            try checkPatternLoan(checker, arg);
8380 8396
        }
8381 -
        let place = borrowPlace(checker.resolver, arg);
8382 8397
        if not isUnsafePointerType(expected) {
8383 -
            if let rootSym = place.root {
8384 -
                for j in 0..placesLen {
8385 -
                    if (exclusive[j] or argExclusive) and placesOverlap(&places[j], &place) {
8386 -
                        throw emitError(checker.resolver, arg, ErrorKind::BorrowConflict(rootSym.name));
8387 -
                    }
8388 -
                }
8389 -
                set places[placesLen] = place;
8390 -
                set exclusive[placesLen] = argExclusive;
8391 -
                set placesLen += 1;
8392 -
            }
8398 +
            try checkLoans(checker.resolver, &arguments[..argumentsLen], arg, loan.exclusive);
8399 +
            set arguments[argumentsLen] = loan;
8400 +
            set argumentsLen += 1;
8393 8401
        }
8394 -
        try checkLocalLoans(checker, arg, argExclusive);
8402 +
        try checkLocalLoans(checker, arg, loan.exclusive);
8395 8403
        if isRefType(expected) {
8396 8404
            try checkLinearNode(checker, env, arg, LinearUse::Borrow);
8405 +
            try retainCallLoan(checker, loan);
8397 8406
        } else {
8398 8407
            try checkLinearNode(checker, env, arg, LinearUse::Consume);
8399 8408
        }
8400 8409
    }
8410 +
    set checker.localLen = localStart;
8401 8411
    if *info.returnType == Type::Never and info.throwList.len == 0 {
8402 8412
        set env.terminated = true;
8403 8413
    }
8404 8414
}
8405 8415
8416 +
/// Return whether an expression can address the supplied storage.
8417 +
unsafe fn borrowOverlaps(self: &mut Resolver, place: &BorrowPlace, node: *ast::Node) -> bool {
8418 +
    match node.value {
8419 +
        case ast::NodeValue::CondExpr(cond) =>
8420 +
            return borrowOverlaps(self, place, cond.thenExpr)
8421 +
                or borrowOverlaps(self, place, cond.elseExpr),
8422 +
        case ast::NodeValue::As(cast) => return borrowOverlaps(self, place, cast.value),
8423 +
        else => {
8424 +
            let other = borrowPlace(self, node);
8425 +
            return placesOverlap(place, &other);
8426 +
        }
8427 +
    }
8428 +
}
8429 +
8430 +
/// Return whether evaluating an argument creates an explicit address borrow.
8431 +
fn createsExplicitBorrow(node: *ast::Node) -> bool {
8432 +
    match node.value {
8433 +
        case ast::NodeValue::AddressOf(_) => return true,
8434 +
        case ast::NodeValue::As(cast) => return createsExplicitBorrow(cast.value),
8435 +
        case ast::NodeValue::CondExpr(cond) =>
8436 +
            return createsExplicitBorrow(cond.thenExpr) or createsExplicitBorrow(cond.elseExpr),
8437 +
        else => return false,
8438 +
    }
8439 +
}
8440 +
8441 +
/// Retain an explicit address argument until its call begins.
8442 +
unsafe fn retainCallLoan(checker: &mut LinearChecker, loan: LocalLoan) throws (ResolveError) {
8443 +
    if not createsExplicitBorrow(loan.source) {
8444 +
        return;
8445 +
    }
8446 +
    if checker.localLen >= MAX_LINEAR_BINDINGS {
8447 +
        throw emitError(checker.resolver, loan.source, ErrorKind::Internal);
8448 +
    }
8449 +
    set checker.locals[checker.localLen] = loan;
8450 +
    set checker.localLen += 1;
8451 +
}
8452 +
8406 8453
/// Check a pattern conditional. Linear scrutinees require an exhaustive match.
8407 8454
unsafe fn checkLinearIfLet(
8408 8455
    checker: &mut LinearChecker,
8409 8456
    env: &mut LinearEnv,
8410 8457
    node: *ast::Node,
lib/std/lang/resolver/tests.rad +87 -0
6445 6445
        let mut res = testResolver();
6446 6446
        let result = try resolveProgramStr(&mut res, program);
6447 6447
        try expectNoErrors(&result);
6448 6448
    }
6449 6449
}
6450 +
6451 +
/// Earlier reference arguments protect their storage during later arguments.
6452 +
@test unsafe fn testCallArgumentLoans() throws (testing::TestError) {
6453 +
    for program in [
6454 +
        "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &mut u32, n: u32) {} fn f (p: &mut u32) { outer(&mut *p, inner(p)); }",
6455 +
        "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32) { outer(&*p, inner(p)); }",
6456 +
        "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &mut u32, n: u32) {} unsafe fn f (p: &mut u32) { outer(&mut *p, inner(p)); }",
6457 +
        "record R { n: u32 } fn (r: &mut R) call(n: u32) {} fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn f (r: &mut R) { (&mut *r).call(inner(&mut r.n)); }",
6458 +
    ] {
6459 +
        let mut res = testResolver();
6460 +
        let result = try resolveProgramStr(&mut res, program);
6461 +
        let error = try expectError(&result);
6462 +
        let case super::ErrorKind::BorrowConflict(_) = error.kind
6463 +
            else throw testing::TestError::Failed;
6464 +
    }
6465 +
}
6466 +
6467 +
/// Conditional explicit arguments protect every possible borrowed place.
6468 +
@test unsafe fn testConditionalCallArgumentLoans() throws (testing::TestError) {
6469 +
    for program in [
6470 +
        "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&*p if flag else &*q, inner(p)); }",
6471 +
        "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&*p if flag else &*q, inner(q)); }",
6472 +
        "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&mut *p if flag else &mut *q, &mut *p); }",
6473 +
        "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &u32, n: u32) {} unsafe fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&*p if flag else &*q, inner(q)); }",
6474 +
        "fn outer(p: &mut u32, q: &mut u32) {} unsafe fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&mut *p if flag else &mut *q, &mut *q); }",
6475 +
        "fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer((&*p if flag else &*q) as &u32, inner(p)); }",
6476 +
        "record R { n: u32 } fn (r: &R) call(n: u32) {} fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn f (p: &mut R, q: &mut R, flag: bool) { (&*p if flag else &*q).call(inner(&mut q.n)); }",
6477 +
    ] {
6478 +
        let mut res = testResolver();
6479 +
        let result = try resolveProgramStr(&mut res, program);
6480 +
        let error = try expectError(&result);
6481 +
        let case super::ErrorKind::BorrowConflict(_) = error.kind
6482 +
            else throw testing::TestError::Failed;
6483 +
    }
6484 +
}
6485 +
6486 +
/// Conditional argument alternatives cannot overlap another exclusive argument.
6487 +
@test unsafe fn testConditionalCallArgumentOverlap() throws (testing::TestError) {
6488 +
    for program in [
6489 +
        "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p if flag else q, p); }",
6490 +
        "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p if flag else q, q); }",
6491 +
        "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p, p if flag else q); }",
6492 +
        "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(q, p if flag else q); }",
6493 +
        "fn outer(p: &mut u32, q: &mut u32) {} unsafe fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p if flag else q, p); }",
6494 +
        "fn outer(p: &u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p if flag else q, q); }",
6495 +
        "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(p if flag else q, q if flag else p); }",
6496 +
        "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, r: &mut u32, a: bool, b: bool) { outer((p if a else q) if b else r, q); }",
6497 +
        "fn outer(p: &u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer((p if flag else q) as &u32, p); }",
6498 +
        "record R { n: u32 } fn (r: &mut R) call(p: &R) {} fn f (p: &mut R, q: &mut R, flag: bool) { (p if flag else q).call(p); }",
6499 +
        "trait R { fn (&mut R) call(p: &opaque R); } fn f (p: &mut opaque R, q: &mut opaque R, flag: bool) { (p if flag else q).call(q); }",
6500 +
    ] {
6501 +
        let mut res = testResolver();
6502 +
        let result = try resolveProgramStr(&mut res, program);
6503 +
        let error = try expectError(&result);
6504 +
        let case super::ErrorKind::BorrowConflict(_) = error.kind
6505 +
            else throw testing::TestError::Failed;
6506 +
    }
6507 +
}
6508 +
6509 +
/// Conditional argument alternatives permit shared access and disjoint places.
6510 +
@test unsafe fn testConditionalCallArgumentSeparation() throws (testing::TestError) {
6511 +
    for program in [
6512 +
        "fn outer(p: &u32, q: &u32) {} fn f (p: &u32, q: &u32, flag: bool) { outer(p if flag else q, p); }",
6513 +
        "fn outer(p: &mut u32, q: &mut u32) {} fn f (p: &mut u32, q: &mut u32, r: &mut u32, flag: bool) { outer(p if flag else q, r); }",
6514 +
        "record R { a: u32, b: u32, c: u32 } fn outer(p: &mut u32, q: &mut u32) {} fn f (r: &mut R, flag: bool) { outer(&mut r.a if flag else &mut r.b, &mut r.c); }",
6515 +
        "record R { n: u32 } fn (r: &mut R) call(p: &R) {} fn f (p: &mut R, q: &mut R, r: &R, flag: bool) { (p if flag else q).call(r); }",
6516 +
    ] {
6517 +
        let mut res = testResolver();
6518 +
        let result = try resolveProgramStr(&mut res, program);
6519 +
        try expectNoErrors(&result);
6520 +
    }
6521 +
}
6522 +
6523 +
/// Call loans allow shared reads, separate fields, and access after the call.
6524 +
@test unsafe fn testCallArgumentLoanScopes() throws (testing::TestError) {
6525 +
    for program in [
6526 +
        "fn read(p: &u32) -> u32 { return *p; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32, q: &mut u32, flag: bool) { outer(&*p if flag else &*q, read(p)); set *p = 3; set *q = 4; }",
6527 +
        "record R { a: u32, b: u32, c: u32 } fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &mut u32, n: u32) {} fn f (r: &mut R, flag: bool) { outer(&mut r.a if flag else &mut r.b, inner(&mut r.c)); set r.a = 3; }",
6528 +
        "fn outer(p: &mut u32, n: u32) {} fn f (p: &mut u32, flag: bool) { outer(&mut *p if flag else &mut *p, 0); set *p = 3; }",
6529 +
        "fn read(p: &u32) -> u32 { return *p; } fn outer(p: &u32, n: u32) {} fn f (p: &mut u32) { outer(&*p, read(p)); set *p = 3; }",
6530 +
        "record R { a: u32, b: u32 } fn inner(p: &mut u32) -> u32 { set *p = 2; return 0; } fn outer(p: &mut u32, n: u32) {} fn f (r: &mut R) { outer(&mut r.a, inner(&mut r.b)); set r.a = 3; }",
6531 +
    ] {
6532 +
        let mut res = testResolver();
6533 +
        let result = try resolveProgramStr(&mut res, program);
6534 +
        try expectNoErrors(&result);
6535 +
    }
6536 +
}
lib/std/vec.rad +6 -4
65 65
/// Returns false if the vector is at capacity.
66 66
export unsafe fn push(vec: &mut RawVec, elem: &opaque) -> bool {
67 67
    if vec.len >= capacity(vec) {
68 68
        return false;
69 69
    }
70 -
    let off: u32 = vec.len * vec.stride;
71 -
    copyBytes(&mut vec.data[off..off + vec.stride], @sliceOf(elem as &u8, vec.stride));
70 +
    let stride = vec.stride;
71 +
    let off: u32 = vec.len * stride;
72 +
    copyBytes(&mut vec.data[off..off + stride], @sliceOf(elem as &u8, stride));
72 73
    set vec.len += 1;
73 74
74 75
    return true;
75 76
}
76 77
95 96
/// Returns false if index is out of bounds.
96 97
export unsafe fn put(vec: &mut RawVec, index: u32, elem: &opaque) -> bool {
97 98
    if index >= vec.len {
98 99
        return false;
99 100
    }
100 -
    let off: u32 = index * vec.stride;
101 -
    copyBytes(&mut vec.data[off..off + vec.stride], @sliceOf(elem as &u8, vec.stride));
101 +
    let stride = vec.stride;
102 +
    let off: u32 = index * stride;
103 +
    copyBytes(&mut vec.data[off..off + stride], @sliceOf(elem as &u8, stride));
102 104
103 105
    return true;
104 106
}
105 107
106 108
/// Copy bytes from source to destination.
test/tests/mutref.loop.bug.rad +8 -4
19 19
/// merge tries to `load` through it as if it were a pointer.
20 20
fn testZeroIter(n: u32) -> u32 {
21 21
    let mut val: u32 = 42;
22 22
    let mut i: u32 = 0;
23 23
    while i < n {
24 -
        store(&mut val, val + 1);
24 +
        let next = val + 1;
25 +
        store(&mut val, next);
25 26
        set i += 1;
26 27
    }
27 28
    return val;
28 29
}
29 30
30 31
/// Multiple iterations: accumulate via &mut pointer in a loop.
31 32
fn testMultiIter() -> u32 {
32 33
    let mut acc: u32 = 0;
33 34
    let mut i: u32 = 0;
34 35
    while i < 5 {
35 -
        store(&mut acc, acc + i);
36 +
        let next = acc + i;
37 +
        store(&mut acc, next);
36 38
        set i += 1;
37 39
    }
38 40
    return acc;
39 41
}
40 42
42 44
fn testMultipleVars() -> u32 {
43 45
    let mut a: u32 = 0;
44 46
    let mut b: u32 = 100;
45 47
    let mut i: u32 = 0;
46 48
    while i < 3 {
47 -
        store(&mut a, a + 1);
48 -
        store(&mut b, b - 1);
49 +
        let nextA = a + 1;
50 +
        store(&mut a, nextA);
51 +
        let nextB = b - 1;
52 +
        store(&mut b, nextB);
49 53
        set i += 1;
50 54
    }
51 55
    return a + b;
52 56
}
53 57