Implement slice range assignment

1c550ebed871e6323fd0ec77d4ff76b0001e4c7b6160dd0517c9d09eb1649899
Eg. `to[a..b] = from[c..d]`
Alexis Sellier committed ago 1 parent 9be0a920
lib/std/arch/rv64/tests/slice.assign.mismatch.rad added +9 -0
1 +
//! returns: 133
2 +
//! Test that slice copy with mismatched lengths traps.
3 +
4 +
@default fn main() -> i32 {
5 +
    let mut dst: [i32; 4] = [0, 0, 0, 0];
6 +
    let src: [i32; 2] = [1, 2];
7 +
    dst[..] = &src[..];
8 +
    return 0;
9 +
}
lib/std/arch/rv64/tests/slice.assign.rad added +55 -0
1 +
//! returns: 0
2 +
3 +
@default fn main() -> i32 {
4 +
    // Fill entire array.
5 +
    let mut a: [i32; 4] = [1, 2, 3, 4];
6 +
    a[..] = 0;
7 +
    assert a == [0, 0, 0, 0];
8 +
9 +
    // Fill sub-range of array.
10 +
    let mut b: [i32; 4] = [1, 2, 3, 4];
11 +
    b[1..3] = 99;
12 +
    assert b == [1, 99, 99, 4];
13 +
14 +
    // Fill mutable slice.
15 +
    let mut c: [i32; 4] = [10, 20, 30, 40];
16 +
    let cs: *mut [i32] = &mut c[..];
17 +
    cs[..] = 7;
18 +
    assert c == [7, 7, 7, 7];
19 +
20 +
    // Fill sub-range of mutable slice.
21 +
    let mut d: [i32; 5] = [1, 2, 3, 4, 5];
22 +
    let ds: *mut [i32] = &mut d[..];
23 +
    ds[1..4] = 0;
24 +
    assert d == [1, 0, 0, 0, 5];
25 +
26 +
    // Copy slice into array.
27 +
    let mut e: [i32; 3] = [0, 0, 0];
28 +
    let src: [i32; 3] = [10, 20, 30];
29 +
    e[..] = &src[..];
30 +
    assert e == [10, 20, 30];
31 +
32 +
    // Copy into sub-range.
33 +
    let mut f: [i32; 5] = [1, 2, 3, 4, 5];
34 +
    let src2: [i32; 2] = [88, 99];
35 +
    f[1..3] = &src2[..];
36 +
    assert f == [1, 88, 99, 4, 5];
37 +
38 +
    // Empty range is a no-op.
39 +
    let mut g: [i32; 3] = [1, 2, 3];
40 +
    g[1..1] = 99;
41 +
    assert g == [1, 2, 3];
42 +
43 +
    // Copy between sub-ranges.
44 +
    let mut i: [i32; 10] = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
45 +
    let s: *mut [i32] = &mut i[..];
46 +
    s[1..4] = &s[7..10];
47 +
    assert i[1] == 80 and i[2] == 90 and i[3] == 100;
48 +
49 +
    // Fill u8 array.
50 +
    let mut h: [u8; 4] = [65, 66, 67, 68];
51 +
    h[..] = 0;
52 +
    assert h == [0, 0, 0, 0];
53 +
54 +
    return 0;
55 +
}
lib/std/lang/lower.rad +111 -15
598 598
    elemReg: il::Reg,
599 599
    /// Source-level type of the element.
600 600
    elemType: resolver::Type,
601 601
}
602 602
603 +
/// Result of resolving a slice range to a data pointer and element count.
604 +
record SliceRangeResult {
605 +
    dataReg: il::Reg,
606 +
    count: il::Val,
607 +
}
608 +
603 609
/////////////////////////////
604 610
// Function Lowering State //
605 611
/////////////////////////////
606 612
607 613
/// Per-function lowering state. Created fresh for each function and contains
3713 3719
        }
3714 3720
        case ast::NodeValue::IfLet(i) => {
3715 3721
            try lowerIfLet(self, i);
3716 3722
        }
3717 3723
        case ast::NodeValue::Assign(a) => {
3718 -
            try lowerAssign(self, a);
3724 +
            try lowerAssign(self, node, a);
3719 3725
        }
3720 3726
        case ast::NodeValue::Loop { body } => {
3721 3727
            try lowerLoop(self, body);
3722 3728
        }
3723 3729
        case ast::NodeValue::While(w) => {
4700 4706
fn lowerFieldAccess(self: *mut FnLowerer, access: ast::Access) -> il::Val throws (LowerError) {
4701 4707
    let fieldRef = try lowerFieldRef(self, access);
4702 4708
    return emitRead(self, fieldRef.base, fieldRef.offset, fieldRef.fieldType);
4703 4709
}
4704 4710
4705 -
/// Lower a slice range expression into a slice header value.
4706 -
fn lowerSliceRange(
4711 +
/// Compute data pointer and element count for a range into a container.
4712 +
/// Used by both slice range expressions (`&a[start..end]`) and slice
4713 +
/// assignments (`a[start..end] = value`).
4714 +
fn resolveSliceRangePtr(
4707 4715
    self: *mut FnLowerer,
4708 4716
    container: *ast::Node,
4709 4717
    range: ast::Range,
4710 -
    sliceNode: *ast::Node
4711 -
) -> il::Val throws (LowerError) {
4712 -
    let info = resolver::sliceRangeInfoFor(self.low.resolver, sliceNode) else {
4713 -
        throw LowerError::MissingMetadata;
4714 -
    };
4718 +
    info: resolver::SliceRangeInfo
4719 +
) -> SliceRangeResult throws (LowerError) {
4715 4720
    let baseVal = try lowerExpr(self, container);
4716 4721
    let baseReg = emitValToReg(self, baseVal);
4717 4722
4718 4723
    // Extract data pointer and container length.
4719 4724
    let mut dataReg = baseReg;
4732 4737
    }
4733 4738
    let mut endVal = containerLen;
4734 4739
    if let end = range.end {
4735 4740
        endVal = try lowerExpr(self, end);
4736 4741
    }
4737 -
    // If the start value is known to be zero, the slice length is just the end
4742 +
4743 +
    // If the start value is known to be zero, the count is just the end
4738 4744
    // value. Otherwise, we have to compute it.
4739 -
    let mut sliceLen = endVal;
4745 +
    let mut count = endVal;
4740 4746
4741 -
    // Only compute range offset and length if the start value is not
4747 +
    // Only compute range offset and count if the start value is not
4742 4748
    // statically known to be zero.
4743 4749
    if startVal != il::Val::Imm(0) {
4744 4750
        // Offset the data pointer by the start value.
4745 4751
        dataReg = emitElem(
4746 4752
            self, resolver::getTypeLayout(*info.itemType).size, dataReg, startVal
4747 4753
        );
4748 -
        // Compute the length as `end - start`.
4754 +
        // Compute the count as `end - start`.
4749 4755
        let lenReg = nextReg(self);
4750 4756
        emit(self, il::Instr::BinOp {
4751 4757
            op: il::BinOp::Sub,
4752 4758
            typ: il::Type::W32,
4753 4759
            dst: lenReg,
4754 4760
            a: endVal,
4755 4761
            b: startVal,
4756 4762
        });
4757 -
        sliceLen = il::Val::Reg(lenReg);
4763 +
        count = il::Val::Reg(lenReg);
4758 4764
    }
4765 +
    return SliceRangeResult { dataReg, count };
4766 +
}
4767 +
4768 +
/// Lower a slice range expression into a slice header value.
4769 +
fn lowerSliceRange(
4770 +
    self: *mut FnLowerer,
4771 +
    container: *ast::Node,
4772 +
    range: ast::Range,
4773 +
    sliceNode: *ast::Node
4774 +
) -> il::Val throws (LowerError) {
4775 +
    let info = resolver::sliceRangeInfoFor(self.low.resolver, sliceNode) else {
4776 +
        throw LowerError::MissingMetadata;
4777 +
    };
4778 +
    let r = try resolveSliceRangePtr(self, container, range, info);
4759 4779
    return try buildSliceValue(
4760 -
        self, info.itemType, info.mutable, il::Val::Reg(dataReg), sliceLen, sliceLen
4780 +
        self, info.itemType, info.mutable, il::Val::Reg(r.dataReg), r.count, r.count
4761 4781
    );
4762 4782
}
4763 4783
4764 4784
/// Lower an address-of (`&x`) expression.
4765 4785
fn lowerAddressOf(self: *mut FnLowerer, node: *ast::Node, addr: ast::AddressOf) -> il::Val throws (LowerError) {
5091 5111
        try switchToAndSeal(self, endBlock);
5092 5112
    }
5093 5113
}
5094 5114
5095 5115
/// Lower an assignment statement.
5096 -
fn lowerAssign(self: *mut FnLowerer, a: ast::Assign) throws (LowerError) {
5116 +
fn lowerAssign(self: *mut FnLowerer, node: *ast::Node, a: ast::Assign) throws (LowerError) {
5117 +
    // Slice assignment: `slice[range] = value`.
5118 +
    if let info = resolver::sliceRangeInfoFor(self.low.resolver, node) {
5119 +
        let case ast::NodeValue::Subscript { container, index } = a.left.value
5120 +
            else panic "lowerAssign: slice assign without subscript";
5121 +
        let case ast::NodeValue::Range(range) = index.value
5122 +
            else panic "lowerAssign: slice assign without range";
5123 +
        try lowerSliceAssign(self, a.right, container, range, info);
5124 +
5125 +
        return;
5126 +
    }
5097 5127
    // Evaluate assignment value.
5098 5128
    let rhs = try lowerExpr(self, a.right);
5099 5129
5100 5130
    match a.left.value {
5101 5131
        case ast::NodeValue::Ident(_) => {
5144 5174
        }
5145 5175
        else => throw LowerError::UnexpectedNodeValue(a.left),
5146 5176
    }
5147 5177
}
5148 5178
5179 +
/// Lower `slice[range] = value`.
5180 +
fn lowerSliceAssign(
5181 +
    self: *mut FnLowerer,
5182 +
    rhs: *ast::Node,
5183 +
    container: *ast::Node,
5184 +
    range: ast::Range,
5185 +
    info: resolver::SliceRangeInfo
5186 +
) throws (LowerError) {
5187 +
    let r = try resolveSliceRangePtr(self, container, range, info);
5188 +
    let elemSize = resolver::getTypeLayout(*info.itemType).size;
5189 +
    let rhsTy = resolver::typeFor(self.low.resolver, rhs) else {
5190 +
        throw LowerError::MissingType(rhs);
5191 +
    };
5192 +
5193 +
    if let case resolver::Type::Slice { .. } = rhsTy {
5194 +
        // Copy from source slice.
5195 +
        let srcReg = emitValToReg(self, try lowerExpr(self, rhs));
5196 +
        let srcData = loadSlicePtr(self, srcReg);
5197 +
        let srcLen = loadSliceLen(self, srcReg);
5198 +
5199 +
        // Trap if source and destination lengths differ.
5200 +
        try emitTrapUnlessCmp(self, il::CmpOp::Eq, il::Type::W32, r.count, srcLen);
5201 +
5202 +
        let bytes = emitTypedBinOp(
5203 +
            self, il::BinOp::Mul, il::Type::W32, r.count, il::Val::Imm(elemSize as i64)
5204 +
        );
5205 +
        try emitByteCopyLoop(self, r.dataReg, srcData, bytes, "copy");
5206 +
    } else {
5207 +
        // Fill with scalar value.
5208 +
        let fillVal = try lowerExpr(self, rhs);
5209 +
        try emitFillLoop(self, r.dataReg, fillVal, r.count, *info.itemType, elemSize);
5210 +
    }
5211 +
}
5212 +
5213 +
/// Emit a typed fill loop: `for i in 0..count { dst[i * stride] = value; }`.
5214 +
fn emitFillLoop(
5215 +
    self: *mut FnLowerer,
5216 +
    dst: il::Reg,
5217 +
    value: il::Val,
5218 +
    count: il::Val,
5219 +
    elemType: resolver::Type,
5220 +
    elemSize: u32
5221 +
) throws (LowerError) {
5222 +
    let iReg = nextReg(self);
5223 +
    let header = try createBlockWithParam(
5224 +
        self, "fill", il::Param { value: iReg, type: il::Type::W32 }
5225 +
    );
5226 +
    let body = try createBlock(self, "fill");
5227 +
    let done = try createBlock(self, "fill");
5228 +
5229 +
    try emitJmpWithArg(self, header, il::Val::Imm(0));
5230 +
    switchToBlock(self, header);
5231 +
    try emitBrCmp(self, il::CmpOp::Ult, il::Type::W32, il::Val::Reg(iReg), count, body, done);
5232 +
5233 +
    try switchToAndSeal(self, body);
5234 +
    let dstElem = emitElem(self, elemSize, dst, il::Val::Reg(iReg));
5235 +
    try emitStore(self, dstElem, 0, elemType, value);
5236 +
5237 +
    let nextI = emitTypedBinOp(
5238 +
        self, il::BinOp::Add, il::Type::W32, il::Val::Reg(iReg), il::Val::Imm(1)
5239 +
    );
5240 +
    try emitJmpWithArg(self, header, nextI);
5241 +
    try sealBlock(self, header);
5242 +
    try switchToAndSeal(self, done);
5243 +
}
5244 +
5149 5245
///////////////////
5150 5246
// Loop Lowering //
5151 5247
///////////////////
5152 5248
5153 5249
// All loop forms are lowered to a common structure:
lib/std/lang/resolver.rad +41 -0
4917 4917
4918 4918
/// Analyze an assignment expression.
4919 4919
fn resolveAssign(self: *mut Resolver, node: *ast::Node, assign: ast::Assign) -> Type
4920 4920
    throws (ResolveError)
4921 4921
{
4922 +
    // Slice assignment: `slice[range] = value`.
4923 +
    if let case ast::NodeValue::Subscript { container, index } = assign.left.value {
4924 +
        if let case ast::NodeValue::Range(range) = index.value {
4925 +
            try infer(self, index);
4926 +
            let containerTy = try infer(self, container);
4927 +
            if not try canBorrowMutFrom(self, container) {
4928 +
                throw emitError(self, container, ErrorKind::ImmutableBinding);
4929 +
            }
4930 +
            let subjectTy = autoDeref(containerTy);
4931 +
            try checkSliceRangeIndices(self, range);
4932 +
4933 +
            let mut item: *Type = undefined;
4934 +
            let mut capacity: ?u32 = nil;
4935 +
4936 +
            match subjectTy {
4937 +
                case Type::Array(a) => {
4938 +
                    try validateArraySliceBounds(self, range, a.length, node);
4939 +
                    item = a.item;
4940 +
                    capacity = a.length;
4941 +
                }
4942 +
                case Type::Slice { item: i, mutable } => {
4943 +
                    if not mutable { throw emitError(self, container, ErrorKind::ImmutableBinding); }
4944 +
                    item = i;
4945 +
                }
4946 +
                else => throw emitError(self, container, ErrorKind::ExpectedIndexable),
4947 +
            }
4948 +
            // RHS is either a fill value or a source slice.
4949 +
            let rhsTy = try infer(self, assign.right);
4950 +
            if let case Type::Slice { item: srcItem, .. } = rhsTy {
4951 +
                if *srcItem != *item {
4952 +
                    throw emitTypeMismatch(self, assign.right, TypeMismatch { expected: *item, actual: *srcItem });
4953 +
                }
4954 +
            } else {
4955 +
                try checkAssignable(self, assign.right, *item);
4956 +
            }
4957 +
            setSliceRangeInfo(self, node, SliceRangeInfo { itemType: item, mutable: true, capacity });
4958 +
            setNodeType(self, assign.left, *item);
4959 +
4960 +
            return setNodeType(self, node, Type::Void);
4961 +
        }
4962 +
    }
4922 4963
    let leftTy = try infer(self, assign.left);
4923 4964
4924 4965
    // Check if the left-hand side can be assigned to by checking if it's a mutable location.
4925 4966
    if not try canBorrowMutFrom(self, assign.left) {
4926 4967
        throw emitError(self, assign.left, ErrorKind::ImmutableBinding);