Implement slice range assignment
1c550ebed871e6323fd0ec77d4ff76b0001e4c7b6160dd0517c9d09eb1649899
Eg. `to[a..b] = from[c..d]`
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); |