Add slice capacity field

d1aed3369d270e931023f93b1cf9abfd7630f335dae55624d73870aa02470b5e
Change slice layout from { ptr, len, <pad> } to { ptr, len, cap },
reusing the existing 4 bytes of padding so the size stays at 16 bytes.

This is the first part of changes to introduce growable slices.
Alexis Sellier committed ago 1 parent 7fa02c34
lib/std/arch/rv64/tests/slice.cap.rad added +52 -0
1 +
//! Test slice capacity field and @sliceOf builtin.
2 +
3 +
@default fn main() -> i32 {
4 +
    // Test that regular slices have cap == len.
5 +
    let mut arr: [i32; 4] = [10, 20, 30, 40];
6 +
    let s = &mut arr[..];
7 +
8 +
    if s.cap != 4 {
9 +
        return 1;
10 +
    }
11 +
    if s.len != s.cap {
12 +
        return 2;
13 +
    }
14 +
15 +
    // Test @sliceOf with explicit capacity.
16 +
    let ptr = &mut arr[0];
17 +
    let s2 = @sliceOf(ptr, 2, 4);
18 +
19 +
    if s2.len != 2 {
20 +
        return 3;
21 +
    }
22 +
    if s2.cap != 4 {
23 +
        return 4;
24 +
    }
25 +
    if s2[0] != 10 {
26 +
        return 5;
27 +
    }
28 +
    if s2[1] != 20 {
29 +
        return 6;
30 +
    }
31 +
32 +
    // Test @sliceOf with zero length.
33 +
    let s3 = @sliceOf(ptr, 0, 4);
34 +
35 +
    if s3.len != 0 {
36 +
        return 7;
37 +
    }
38 +
    if s3.cap != 4 {
39 +
        return 8;
40 +
    }
41 +
42 +
    // Test @sliceOf still works and has cap == len.
43 +
    let s4 = @sliceOf(ptr, 3);
44 +
45 +
    if s4.len != 3 {
46 +
        return 9;
47 +
    }
48 +
    if s4.cap != 3 {
49 +
        return 10;
50 +
    }
51 +
    return 0;
52 +
}
lib/std/lang/ast.rad +1 -1
218 218
pub union Builtin {
219 219
    /// Size of type in bytes (`@sizeOf`).
220 220
    SizeOf,
221 221
    /// Alignment requirement of type (`@alignOf`).
222 222
    AlignOf,
223 -
    /// Construct a slice from pointer and length (`@sliceOf`).
223 +
    /// Construct a slice from pointer, length, and optional capacity (`@sliceOf`).
224 224
    SliceOf,
225 225
}
226 226
227 227
/// Source extent for a node measured in bytes.
228 228
pub record Span {
lib/std/lang/lower.rad +31 -14
226 226
227 227
/// Slice data pointer offset.
228 228
const SLICE_PTR_OFFSET: i32 = 0;
229 229
/// Offset of slice length in slice data structure.
230 230
const SLICE_LEN_OFFSET: i32 = 8;
231 +
/// Offset of slice capacity in slice data structure.
232 +
const SLICE_CAP_OFFSET: i32 = 12;
231 233
232 234
// Trait Object Layout
233 235
//
234 236
// A trait object is a fat pointer consisting of a data pointer and a
235 237
// v-table pointer. `{ data: *T, vtable: *VTable }`.
1307 1309
        isUndefined: result.isUndefined,
1308 1310
        values: result.values,
1309 1311
    });
1310 1312
}
1311 1313
1312 -
/// Emit the in-memory representation of a slice header: `{ ptr, len, padding }`.
1314 +
/// Emit the in-memory representation of a slice header: `{ ptr, len, cap }`.
1313 1315
fn dataSliceHeader(b: *mut DataValueBuilder, dataSym: *[u8], len: u32) {
1314 1316
    dataBuilderPush(b, il::DataValue {
1315 1317
        item: il::DataItem::Sym(dataSym),
1316 1318
        count: 1
1317 1319
    });
1321 1323
            val: len as i64
1322 1324
        },
1323 1325
        count: 1
1324 1326
    });
1325 1327
    dataBuilderPush(b, il::DataValue {
1326 -
        item: il::DataItem::Undef,
1327 -
        count: 4 // 4 byte padding.
1328 +
        item: il::DataItem::Val {
1329 +
            typ: il::Type::W32,
1330 +
            val: len as i64
1331 +
        },
1332 +
        count: 1
1328 1333
    });
1329 1334
}
1330 1335
1331 1336
/// Lower a compile-time `&[...]` expression to a concrete slice header.
1332 1337
fn lowerConstAddressSliceInto(
1826 1831
    // Get data address.
1827 1832
    let ptrReg = nextReg(self);
1828 1833
    emit(self, il::Instr::Copy { dst: ptrReg, val: il::Val::DataSym(dataName) });
1829 1834
1830 1835
    return try buildSliceValue(
1831 -
        self, elemTy, mutable, il::Val::Reg(ptrReg), il::Val::Imm(length as i64)
1836 +
        self, elemTy, mutable, il::Val::Reg(ptrReg), il::Val::Imm(length as i64), il::Val::Imm(length as i64)
1832 1837
    );
1833 1838
}
1834 1839
1835 1840
/// Generate a unique data name for inline literals, eg. `fnName/N`
1836 1841
fn nextDataName(self: *mut FnLowerer) -> *[u8] throws (LowerError) {
2292 2297
    let lenReg = nextReg(self);
2293 2298
    emitLoadW32At(self, lenReg, sliceReg, SLICE_LEN_OFFSET);
2294 2299
    return il::Val::Reg(lenReg);
2295 2300
}
2296 2301
2302 +
/// Load the capacity from a slice value.
2303 +
fn loadSliceCap(self: *mut FnLowerer, sliceReg: il::Reg) -> il::Val {
2304 +
    let capReg = nextReg(self);
2305 +
    emitLoadW32At(self, capReg, sliceReg, SLICE_CAP_OFFSET);
2306 +
    return il::Val::Reg(capReg);
2307 +
}
2308 +
2297 2309
/// Emit a load instruction for a scalar value at `src` plus `offset`.
2298 2310
/// For reading values that may be aggregates, use `emitRead` instead.
2299 2311
fn emitLoad(self: *mut FnLowerer, src: il::Reg, offset: i32, typ: resolver::Type) -> il::Val {
2300 2312
    let dst = nextReg(self);
2301 2313
    let ilTyp = ilType(self.low, typ);
4055 4067
    };
4056 4068
    if let case resolver::Type::Pointer { .. } = *inner {
4057 4069
        return il::Val::Imm(0);
4058 4070
    }
4059 4071
    if let case resolver::Type::Slice { item, mutable } = *inner {
4060 -
        return try buildSliceValue(self, item, mutable, il::Val::Imm(0), il::Val::Imm(0));
4072 +
        return try buildSliceValue(self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0));
4061 4073
    }
4062 4074
    let valOffset = resolver::getOptionalValOffset(*inner) as i32;
4063 4075
    return try buildTagged(self, resolver::getTypeLayout(optType), 0, nil, *inner, 1, valOffset);
4064 4076
}
4065 4077
4075 4087
        successType, &self.fnType.throwList[..self.fnType.throwListLen]
4076 4088
    );
4077 4089
    return try buildTagged(self, layout, tag, payload, payloadType, 8, RESULT_VAL_OFFSET);
4078 4090
}
4079 4091
4080 -
/// Build a slice aggregate from a data pointer and length.
4092 +
/// Build a slice aggregate from a data pointer, length and capacity.
4081 4093
fn buildSliceValue(
4082 4094
    self: *mut FnLowerer,
4083 4095
    elemTy: *resolver::Type,
4084 4096
    mutable: bool,
4085 4097
    ptrVal: il::Val,
4086 -
    lenVal: il::Val
4098 +
    lenVal: il::Val,
4099 +
    capVal: il::Val
4087 4100
) -> il::Val throws (LowerError) {
4088 4101
    let sliceType = resolver::Type::Slice { item: elemTy, mutable };
4089 4102
    let dst = try emitReserve(self, sliceType);
4090 4103
    let ptrTy = resolver::Type::Pointer { target: elemTy, mutable };
4091 4104
4092 4105
    try emitStore(self, dst, SLICE_PTR_OFFSET, ptrTy, ptrVal);
4093 4106
    try emitStore(self, dst, SLICE_LEN_OFFSET, resolver::Type::U32, lenVal);
4107 +
    try emitStore(self, dst, SLICE_CAP_OFFSET, resolver::Type::U32, capVal);
4094 4108
4095 4109
    return il::Val::Reg(dst);
4096 4110
}
4097 4111
4098 4112
/// Build a trait object fat pointer from a data pointer and a v-table.
4806 4820
            b: startVal,
4807 4821
        });
4808 4822
        sliceLen = il::Val::Reg(lenReg);
4809 4823
    }
4810 4824
    return try buildSliceValue(
4811 -
        self, info.itemType, info.mutable, il::Val::Reg(dataReg), sliceLen
4825 +
        self, info.itemType, info.mutable, il::Val::Reg(dataReg), sliceLen, sliceLen
4812 4826
    );
4813 4827
}
4814 4828
4815 4829
/// Lower an address-of (`&x`) expression.
4816 4830
fn lowerAddressOf(self: *mut FnLowerer, node: *ast::Node, addr: ast::AddressOf) -> il::Val throws (LowerError) {
4892 4906
    };
4893 4907
    let case resolver::Type::Slice { item, mutable } = sliceTy else {
4894 4908
        throw LowerError::UnexpectedType(&sliceTy);
4895 4909
    };
4896 4910
    if elements.len == 0 { // Empty slices don't need to be stored as data.
4897 -
        return try buildSliceValue(self, item, mutable, il::Val::Imm(0), il::Val::Imm(0));
4911 +
        return try buildSliceValue(self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0));
4898 4912
    }
4899 4913
    if resolver::isConstExpr(self.low.resolver, arrayNode) {
4900 4914
        let elemLayout = resolver::getTypeLayout(*item);
4901 4915
        return try lowerConstSliceLiteral(self, item, mutable, elements, elemLayout);
4902 4916
    } else {
4949 4963
4950 4964
        try emitStore(self, arrayReg, offset as i32, *elemTy, elemVal);
4951 4965
    }
4952 4966
    let lenVal = il::Val::Imm(elements.len as i64);
4953 4967
4954 -
    return try buildSliceValue(self, elemTy, mutable, il::Val::Reg(arrayReg), lenVal);
4968 +
    return try buildSliceValue(self, elemTy, mutable, il::Val::Reg(arrayReg), lenVal, lenVal);
4955 4969
}
4956 4970
4957 4971
/// Lower the common element pointer computation for subscript operations.
4958 4972
/// Handles both arrays and slices by resolving the container type, extracting
4959 4973
/// the data pointer (for slices), and emitting an [`il::Instr::Elem`] to compute
5918 5932
            return try constValueToVal(self, constVal, node);
5919 5933
        }
5920 5934
    }
5921 5935
}
5922 5936
5923 -
/// Lower a `@sliceOf(ptr, len)` builtin call.
5937 +
/// Lower a `@sliceOf(ptr, len)` or `@sliceOf(ptr, len, cap)` builtin call.
5924 5938
fn lowerSliceOf(self: *mut FnLowerer, node: *ast::Node, args: ast::NodeList) -> il::Val throws (LowerError) {
5925 -
    if args.len != 2 {
5939 +
    if args.len != 2 and args.len != 3 {
5926 5940
        throw LowerError::InvalidArgCount;
5927 5941
    }
5928 5942
    let sliceTy = resolver::typeFor(self.low.resolver, node) else {
5929 5943
        throw LowerError::MissingType(node);
5930 5944
    };
5931 5945
    let case resolver::Type::Slice { item, mutable } = sliceTy else {
5932 5946
        throw LowerError::ExpectedSliceOrArray;
5933 5947
    };
5934 5948
    let ptrVal = try lowerExpr(self, args.list[0]);
5935 5949
    let lenVal = try lowerExpr(self, args.list[1]);
5936 -
5937 -
    return try buildSliceValue(self, item, mutable, ptrVal, lenVal);
5950 +
    let mut capVal = lenVal;
5951 +
    if args.len == 3 {
5952 +
        capVal = try lowerExpr(self, args.list[2]);
5953 +
    }
5954 +
    return try buildSliceValue(self, item, mutable, ptrVal, lenVal, capVal);
5938 5955
}
5939 5956
5940 5957
/// Lower a `try` expression.
5941 5958
fn lowerTry(self: *mut FnLowerer, node: *ast::Node, t: ast::Try) -> il::Val throws (LowerError) {
5942 5959
    let case ast::NodeValue::Call(callExpr) = t.expr.value else {
lib/std/lang/lower/tests/array.slice.full.ril +1 -0
1 1
fn w64 $sliceFull(w64 %0, w64 %1) {
2 2
  @entry0
3 3
    reserve %2 16 8;
4 4
    store w64 %1 %2 0;
5 5
    store w32 4 %2 8;
6 +
    store w32 4 %2 12;
6 7
    blit %0 %2 16;
7 8
    ret %0;
8 9
}
lib/std/lang/lower/tests/array.slice.openend.ril +1 -0
4 4
    add w64 %4 %1 %3;
5 5
    sub w32 %5 4 %2;
6 6
    reserve %6 16 8;
7 7
    store w64 %4 %6 0;
8 8
    store w32 %5 %6 8;
9 +
    store w32 %5 %6 12;
9 10
    blit %0 %6 16;
10 11
    ret %0;
11 12
}
lib/std/lang/lower/tests/array.slice.openstart.ril +1 -0
1 1
fn w64 $sliceOpenStart(w64 %0, w64 %1, w32 %2) {
2 2
  @entry0
3 3
    reserve %3 16 8;
4 4
    store w64 %1 %3 0;
5 5
    store w32 %2 %3 8;
6 +
    store w32 %2 %3 12;
6 7
    blit %0 %3 16;
7 8
    ret %0;
8 9
}
lib/std/lang/lower/tests/const.array.strings.slice.ril +3 -3
7 7
}
8 8
9 9
data $WORDS align 8 {
10 10
    sym $WORDS$0;
11 11
    w32 5;
12 -
    undef * 4;
12 +
    w32 5;
13 13
    sym $WORDS$1;
14 14
    w32 6;
15 -
    undef * 4;
15 +
    w32 6;
16 16
    sym $WORDS$0;
17 17
    w32 5;
18 -
    undef * 4;
18 +
    w32 5;
19 19
}
20 20
21 21
fn w32 $totalLen() {
22 22
  @entry0
23 23
    copy %0 $WORDS;
lib/std/lang/lower/tests/const.record.union.ril +3 -3
11 11
}
12 12
13 13
data $KEYWORDS align 8 {
14 14
    sym $KEYWORDS$0;
15 15
    w32 2;
16 -
    undef * 4;
16 +
    w32 2;
17 17
    w8 0;
18 18
    undef * 7;
19 19
    sym $KEYWORDS$1;
20 20
    w32 3;
21 -
    undef * 4;
21 +
    w32 3;
22 22
    w8 1;
23 23
    undef * 7;
24 24
    sym $KEYWORDS$2;
25 25
    w32 2;
26 -
    undef * 4;
26 +
    w32 2;
27 27
    w8 2;
28 28
    undef * 7;
29 29
}
30 30
31 31
fn w32 $getFirstTag() {
lib/std/lang/lower/tests/const.slice.of.slices.ril +5 -5
7 7
}
8 8
9 9
data $GROUPS$2 align 8 {
10 10
    sym $GROUPS$0;
11 11
    w32 2;
12 -
    undef * 4;
12 +
    w32 2;
13 13
    sym $GROUPS$1;
14 14
    w32 2;
15 -
    undef * 4;
15 +
    w32 2;
16 16
}
17 17
18 18
data $GROUPS$3 align 1 {
19 19
    str "efg";
20 20
}
21 21
22 22
data $GROUPS$4 align 8 {
23 23
    sym $GROUPS$3;
24 24
    w32 3;
25 -
    undef * 4;
25 +
    w32 3;
26 26
}
27 27
28 28
data $GROUPS align 8 {
29 29
    sym $GROUPS$2;
30 30
    w32 2;
31 -
    undef * 4;
31 +
    w32 2;
32 32
    sym $GROUPS$4;
33 33
    w32 1;
34 -
    undef * 4;
34 +
    w32 1;
35 35
}
36 36
37 37
fn w32 $totalLen() {
38 38
  @entry0
39 39
    copy %0 $GROUPS;
lib/std/lang/lower/tests/const.string.ril +2 -1
3 3
}
4 4
5 5
data $HELLO align 8 {
6 6
    sym $HELLO$0;
7 7
    w32 5;
8 -
    undef * 4;
8 +
    w32 5;
9 9
}
10 10
11 11
fn w32 $getLen() {
12 12
  @entry0
13 13
    copy %0 $HELLO$0;
14 14
    reserve %1 16 8;
15 15
    store w64 %0 %1 0;
16 16
    store w32 5 %1 8;
17 +
    store w32 5 %1 12;
17 18
    load w32 %2 %1 8;
18 19
    ret %2;
19 20
}
lib/std/lang/lower/tests/const.string.scoped.names.ril +6 -4
3 3
}
4 4
5 5
data $HELLO1 align 8 {
6 6
    sym $HELLO1$0;
7 7
    w32 5;
8 -
    undef * 4;
8 +
    w32 5;
9 9
}
10 10
11 11
data $HELLO2 align 8 {
12 12
    sym $HELLO1$0;
13 13
    w32 5;
14 -
    undef * 4;
14 +
    w32 5;
15 15
}
16 16
17 17
data $MSGS$3 align 1 {
18 18
    str "world";
19 19
}
20 20
21 21
data $MSGS align 8 {
22 22
    sym $HELLO1$0;
23 23
    w32 5;
24 -
    undef * 4;
24 +
    w32 5;
25 25
    sym $MSGS$3;
26 26
    w32 5;
27 -
    undef * 4;
27 +
    w32 5;
28 28
}
29 29
30 30
fn w32 $totalLen() {
31 31
  @entry0
32 32
    copy %0 $HELLO1$0;
33 33
    reserve %1 16 8;
34 34
    store w64 %0 %1 0;
35 35
    store w32 5 %1 8;
36 +
    store w32 5 %1 12;
36 37
    load w32 %2 %1 8;
37 38
    copy %3 $HELLO1$0;
38 39
    reserve %4 16 8;
39 40
    store w64 %3 %4 0;
40 41
    store w32 5 %4 8;
42 +
    store w32 5 %4 12;
41 43
    load w32 %5 %4 8;
42 44
    add w32 %6 %2 %5;
43 45
    copy %7 $MSGS;
44 46
    load w32 %8 %7 8;
45 47
    add w32 %9 %6 %8;
lib/std/lang/lower/tests/field.aggregate.ril +1 -0
33 33
    store w32 3 %0 8;
34 34
    reserve %1 24 8;
35 35
    reserve %2 16 8;
36 36
    store w64 %0 %2 0;
37 37
    store w32 3 %2 8;
38 +
    store w32 3 %2 12;
38 39
    blit %1 %2 16;
39 40
    store w32 77 %1 16;
40 41
    load w32 %3 %1 8;
41 42
    ret %3;
42 43
}
lib/std/lang/lower/tests/literal.slice.bytes.ril +1 -0
8 8
  @entry0
9 9
    copy %0 $byteSlice0;
10 10
    reserve %1 16 8;
11 11
    store w64 %0 %1 0;
12 12
    store w32 3 %1 8;
13 +
    store w32 3 %1 12;
13 14
    load w32 %2 %1 8;
14 15
    ret %2;
15 16
}
lib/std/lang/lower/tests/literal.slice.dedup.ril +2 -0
8 8
  @entry0
9 9
    copy %0 $dedupSlice0;
10 10
    reserve %1 16 8;
11 11
    store w64 %0 %1 0;
12 12
    store w32 3 %1 8;
13 +
    store w32 3 %1 12;
13 14
    copy %2 $dedupSlice0;
14 15
    reserve %3 16 8;
15 16
    store w64 %2 %3 0;
16 17
    store w32 3 %3 8;
18 +
    store w32 3 %3 12;
17 19
    load w32 %4 %1 8;
18 20
    load w32 %5 %3 8;
19 21
    add w32 %6 %4 %5;
20 22
    ret %6;
21 23
}
lib/std/lang/lower/tests/literal.slice.empty.ril +1 -0
1 1
fn w32 $emptySlice() {
2 2
  @entry0
3 3
    reserve %0 16 8;
4 4
    store w64 0 %0 0;
5 5
    store w32 0 %0 8;
6 +
    store w32 0 %0 12;
6 7
    load w32 %1 %0 8;
7 8
    ret %1;
8 9
}
lib/std/lang/lower/tests/literal.slice.multi.ril +2 -0
13 13
  @entry0
14 14
    copy %0 $multiSlice0;
15 15
    reserve %1 16 8;
16 16
    store w64 %0 %1 0;
17 17
    store w32 2 %1 8;
18 +
    store w32 2 %1 12;
18 19
    copy %2 $multiSlice1;
19 20
    reserve %3 16 8;
20 21
    store w64 %2 %3 0;
21 22
    store w32 3 %3 8;
23 +
    store w32 3 %3 12;
22 24
    load w32 %4 %1 8;
23 25
    load w32 %5 %3 8;
24 26
    add w32 %6 %4 %5;
25 27
    ret %6;
26 28
}
lib/std/lang/lower/tests/literal.slice.record.ril +1 -0
9 9
  @entry0
10 10
    copy %0 $localSliceOfRecords0;
11 11
    reserve %1 16 8;
12 12
    store w64 %0 %1 0;
13 13
    store w32 2 %1 8;
14 +
    store w32 2 %1 12;
14 15
    load w32 %2 %1 8;
15 16
    br.ult w32 0 %2 @guard#pass1 @guard#trap2;
16 17
  @guard#pass1
17 18
    load w64 %3 %1 0;
18 19
    sload w32 %4 %3 0;
lib/std/lang/lower/tests/literal.slice.ril +1 -0
8 8
  @entry0
9 9
    copy %0 $sliceLiteral0;
10 10
    reserve %1 16 8;
11 11
    store w64 %0 %1 0;
12 12
    store w32 3 %1 8;
13 +
    store w32 3 %1 12;
13 14
    load w32 %2 %1 8;
14 15
    ret %2;
15 16
}
lib/std/lang/lower/tests/literal.string.dedup.ril +2 -0
6 6
  @entry0
7 7
    copy %0 $dedupString0;
8 8
    reserve %1 16 8;
9 9
    store w64 %0 %1 0;
10 10
    store w32 5 %1 8;
11 +
    store w32 5 %1 12;
11 12
    copy %2 $dedupString0;
12 13
    reserve %3 16 8;
13 14
    store w64 %2 %3 0;
14 15
    store w32 5 %3 8;
16 +
    store w32 5 %3 12;
15 17
    load w32 %4 %1 8;
16 18
    load w32 %5 %3 8;
17 19
    add w32 %6 %4 %5;
18 20
    ret %6;
19 21
}
lib/std/lang/lower/tests/literal.string.empty.ril +1 -0
6 6
  @entry0
7 7
    copy %0 $emptyString0;
8 8
    reserve %1 16 8;
9 9
    store w64 %0 %1 0;
10 10
    store w32 0 %1 8;
11 +
    store w32 0 %1 12;
11 12
    load w32 %2 %1 8;
12 13
    ret %2;
13 14
}
lib/std/lang/lower/tests/literal.string.fns.ril +2 -0
10 10
  @entry0
11 11
    copy %0 $first0;
12 12
    reserve %1 16 8;
13 13
    store w64 %0 %1 0;
14 14
    store w32 3 %1 8;
15 +
    store w32 3 %1 12;
15 16
    load w32 %2 %1 8;
16 17
    ret %2;
17 18
}
18 19
19 20
fn w32 $second() {
20 21
  @entry0
21 22
    copy %0 $second0;
22 23
    reserve %1 16 8;
23 24
    store w64 %0 %1 0;
24 25
    store w32 3 %1 8;
26 +
    store w32 3 %1 12;
25 27
    load w32 %2 %1 8;
26 28
    ret %2;
27 29
}
lib/std/lang/lower/tests/literal.string.multi.ril +2 -0
10 10
  @entry0
11 11
    copy %0 $multiString0;
12 12
    reserve %1 16 8;
13 13
    store w64 %0 %1 0;
14 14
    store w32 5 %1 8;
15 +
    store w32 5 %1 12;
15 16
    copy %2 $multiString1;
16 17
    reserve %3 16 8;
17 18
    store w64 %2 %3 0;
18 19
    store w32 5 %3 8;
20 +
    store w32 5 %3 12;
19 21
    load w32 %4 %1 8;
20 22
    load w32 %5 %3 8;
21 23
    add w32 %6 %4 %5;
22 24
    ret %6;
23 25
}
lib/std/lang/lower/tests/literal.string.ril +1 -0
6 6
  @entry0
7 7
    copy %0 $stringLen0;
8 8
    reserve %1 16 8;
9 9
    store w64 %0 %1 0;
10 10
    store w32 5 %1 8;
11 +
    store w32 5 %1 12;
11 12
    load w32 %2 %1 8;
12 13
    ret %2;
13 14
}
lib/std/lang/lower/tests/opt.slice.npo.ril +1 -0
7 7
fn w64 $nilSlice(w64 %0) {
8 8
  @entry0
9 9
    reserve %1 16 8;
10 10
    store w64 0 %1 0;
11 11
    store w32 0 %1 8;
12 +
    store w32 0 %1 12;
12 13
    blit %0 %1 16;
13 14
    ret %0;
14 15
}
15 16
16 17
fn w8 $nilCheck(w64 %0) {
lib/std/lang/lower/tests/slice.basic.rad +10 -0
1 1
/// Returns the length field from a slice header.
2 2
fn sliceLen(s: *[i32]) -> u32 {
3 3
    return s.len;
4 4
}
5 5
6 +
/// Returns the capacity field from a slice header.
7 +
fn sliceCap(s: *[i32]) -> u32 {
8 +
    return s.cap;
9 +
}
10 +
6 11
/// Returns the pointer field from a slice header.
7 12
fn slicePtr(s: *[i32]) -> *i32 {
8 13
    return s.ptr;
9 14
}
10 15
16 21
17 22
/// Builds a slice header from a pointer and length.
18 23
fn sliceOf(ptr: *i32, len: u32) -> *[i32] {
19 24
    return @sliceOf(ptr, len);
20 25
}
26 +
27 +
/// Builds a slice header from pointer, length, and capacity.
28 +
fn sliceOfWithCap(ptr: *i32, len: u32, cap: u32) -> *[i32] {
29 +
    return @sliceOf(ptr, len, cap);
30 +
}
lib/std/lang/lower/tests/slice.basic.ril +17 -0
2 2
  @entry0
3 3
    load w32 %1 %0 8;
4 4
    ret %1;
5 5
}
6 6
7 +
fn w32 $sliceCap(w64 %0) {
8 +
  @entry0
9 +
    load w32 %1 %0 12;
10 +
    ret %1;
11 +
}
12 +
7 13
fn w64 $slicePtr(w64 %0) {
8 14
  @entry0
9 15
    load w64 %1 %0 0;
10 16
    ret %1;
11 17
}
21 27
fn w64 $sliceOf(w64 %0, w64 %1, w32 %2) {
22 28
  @entry0
23 29
    reserve %3 16 8;
24 30
    store w64 %1 %3 0;
25 31
    store w32 %2 %3 8;
32 +
    store w32 %2 %3 12;
26 33
    blit %0 %3 16;
27 34
    ret %0;
28 35
}
36 +
37 +
fn w64 $sliceOfWithCap(w64 %0, w64 %1, w32 %2, w32 %3) {
38 +
  @entry0
39 +
    reserve %4 16 8;
40 +
    store w64 %1 %4 0;
41 +
    store w32 %2 %4 8;
42 +
    store w32 %3 %4 12;
43 +
    blit %0 %4 16;
44 +
    ret %0;
45 +
}
lib/std/lang/lower/tests/slice.range.ril +5 -0
6 6
    add w64 %7 %4 %6;
7 7
    sub w32 %8 %3 %2;
8 8
    reserve %9 16 8;
9 9
    store w64 %7 %9 0;
10 10
    store w32 %8 %9 8;
11 +
    store w32 %8 %9 12;
11 12
    blit %0 %9 16;
12 13
    ret %0;
13 14
}
14 15
15 16
fn w64 $sliceRangeOpenEnd(w64 %0, w64 %1, w32 %2) {
20 21
    add w64 %6 %3 %5;
21 22
    sub w32 %7 %4 %2;
22 23
    reserve %8 16 8;
23 24
    store w64 %6 %8 0;
24 25
    store w32 %7 %8 8;
26 +
    store w32 %7 %8 12;
25 27
    blit %0 %8 16;
26 28
    ret %0;
27 29
}
28 30
29 31
fn w64 $sliceRangeOpenStart(w64 %0, w64 %1, w32 %2) {
31 33
    load w64 %3 %1 0;
32 34
    load w32 %4 %1 8;
33 35
    reserve %5 16 8;
34 36
    store w64 %3 %5 0;
35 37
    store w32 %2 %5 8;
38 +
    store w32 %2 %5 12;
36 39
    blit %0 %5 16;
37 40
    ret %0;
38 41
}
39 42
40 43
fn w64 $sliceRangeFull(w64 %0, w64 %1) {
42 45
    load w64 %2 %1 0;
43 46
    load w32 %3 %1 8;
44 47
    reserve %4 16 8;
45 48
    store w64 %2 %4 0;
46 49
    store w32 %3 %4 8;
50 +
    store w32 %3 %4 12;
47 51
    blit %0 %4 16;
48 52
    ret %0;
49 53
}
50 54
51 55
fn w64 $sliceArray(w64 %0, w64 %1) {
54 58
    add w64 %3 %1 %2;
55 59
    sub w32 %4 3 1;
56 60
    reserve %5 16 8;
57 61
    store w64 %3 %5 0;
58 62
    store w32 %4 %5 8;
63 +
    store w32 %4 %5 12;
59 64
    blit %0 %5 16;
60 65
    ret %0;
61 66
}
lib/std/lang/lower/tests/slice.runtime.i32.ril +3 -0
3 3
    reserve %2 4 4;
4 4
    store w32 %1 %2 0;
5 5
    reserve %3 16 8;
6 6
    store w64 %2 %3 0;
7 7
    store w32 1 %3 8;
8 +
    store w32 1 %3 12;
8 9
    blit %0 %3 16;
9 10
    ret %0;
10 11
}
11 12
12 13
fn w64 $sliceWithVars(w64 %0, w32 %1, w32 %2) {
15 16
    store w32 %1 %3 0;
16 17
    store w32 %2 %3 4;
17 18
    reserve %4 16 8;
18 19
    store w64 %3 %4 0;
19 20
    store w32 2 %4 8;
21 +
    store w32 2 %4 12;
20 22
    blit %0 %4 16;
21 23
    ret %0;
22 24
}
23 25
24 26
fn w64 $sliceWithMixed(w64 %0, w32 %1) {
28 30
    store w32 42 %2 4;
29 31
    store w32 %1 %2 8;
30 32
    reserve %3 16 8;
31 33
    store w64 %2 %3 0;
32 34
    store w32 3 %3 8;
35 +
    store w32 3 %3 12;
33 36
    blit %0 %3 16;
34 37
    ret %0;
35 38
}
lib/std/lang/lower/tests/slice.runtime.literal.ril +2 -0
3 3
    reserve %2 1 1;
4 4
    store w8 %1 %2 0;
5 5
    reserve %3 16 8;
6 6
    store w64 %2 %3 0;
7 7
    store w32 1 %3 8;
8 +
    store w32 1 %3 12;
8 9
    blit %0 %3 16;
9 10
    ret %0;
10 11
}
11 12
12 13
fn w64 $sliceWithVars(w64 %0, w8 %1, w8 %2) {
15 16
    store w8 %1 %3 0;
16 17
    store w8 %2 %3 1;
17 18
    reserve %4 16 8;
18 19
    store w64 %3 %4 0;
19 20
    store w32 2 %4 8;
21 +
    store w32 2 %4 12;
20 22
    blit %0 %4 16;
21 23
    ret %0;
22 24
}
lib/std/lang/resolver.rad +17 -8
89 89
90 90
/// Identifier for the synthetic `len` field.
91 91
pub const LEN_FIELD: *[u8] = "len";
92 92
/// Identifier for the synthetic `ptr` field.
93 93
pub const PTR_FIELD: *[u8] = "ptr";
94 +
/// Identifier for the synthetic `cap` field.
95 +
pub const CAP_FIELD: *[u8] = "cap";
94 96
95 97
/// Maximum `u16` value.
96 98
const U16_MAX: u16 = 0xFFFF;
97 99
/// Maximum `u8` value.
98 100
const U8_MAX: u16 = 0xFF;
1929 1931
                case 1 => return RecordField {
1930 1932
                    name: LEN_FIELD,
1931 1933
                    fieldType: Type::U32,
1932 1934
                    offset: PTR_SIZE as i32,
1933 1935
                },
1936 +
                case 2 => return RecordField {
1937 +
                    name: CAP_FIELD,
1938 +
                    fieldType: Type::U32,
1939 +
                    offset: PTR_SIZE as i32 + 4,
1940 +
                },
1934 1941
                else => return nil,
1935 1942
            }
1936 1943
        }
1937 1944
        else => return nil,
1938 1945
    }
4649 4656
    self: *mut Resolver,
4650 4657
    node: *ast::Node,
4651 4658
    kind: ast::Builtin,
4652 4659
    args: *ast::NodeList
4653 4660
) -> Type throws (ResolveError) {
4654 -
    // Handle `@sliceOf` separately since it takes two runtime arguments.
4661 +
    // Handle `@sliceOf(ptr, len)` and `@sliceOf(ptr, len, cap)`.
4655 4662
    if kind == ast::Builtin::SliceOf {
4656 -
        if args.len != 2 {
4663 +
        if args.len != 2 and args.len != 3 {
4657 4664
            throw emitError(self, node, ErrorKind::BuiltinArgCountMismatch(CountMismatch {
4658 4665
                expected: 2,
4659 4666
                actual: args.len as u32,
4660 4667
            }));
4661 4668
        }
4662 -
        // Analyze pointer expression.
4663 4669
        let ptrType = try visit(self, args.list[0], Type::Unknown);
4664 4670
        let case Type::Pointer { target, mutable } = ptrType else {
4665 4671
            throw emitError(self, node, ErrorKind::ExpectedPointer);
4666 4672
        };
4667 -
        // Analyze length expression.
4668 4673
        let _ = try checkAssignable(self, args.list[1], Type::U32);
4669 -
        // Result is a slice of the pointer's element type.
4670 -
        let sliceType = Type::Slice { item: target, mutable };
4671 -
4672 -
        return try setNodeType(self, node, sliceType);
4674 +
        if args.len == 3 {
4675 +
            let _ = try checkAssignable(self, args.list[2], Type::U32);
4676 +
        }
4677 +
        return try setNodeType(self, node, Type::Slice { item: target, mutable });
4673 4678
    }
4674 4679
    if args.len != 1 {
4675 4680
        throw emitError(self, node, ErrorKind::BuiltinArgCountMismatch(CountMismatch {
4676 4681
            expected: 1,
4677 4682
            actual: args.len as u32,
5263 5268
            }
5264 5269
            if mem::eq(fieldName, LEN_FIELD) {
5265 5270
                try setRecordFieldIndex(self, fieldNode, 1);
5266 5271
                return try setNodeType(self, node, Type::U32);
5267 5272
            }
5273 +
            if mem::eq(fieldName, CAP_FIELD) {
5274 +
                try setRecordFieldIndex(self, fieldNode, 2);
5275 +
                return try setNodeType(self, node, Type::U32);
5276 +
            }
5268 5277
            throw emitError(self, node, ErrorKind::SliceFieldUnknown(fieldName));
5269 5278
        }
5270 5279
        case Type::TraitObject { traitInfo, .. } => {
5271 5280
            let fieldName = try nodeName(self, access.child);
5272 5281
            let method = findTraitMethod(traitInfo, fieldName)
lib/std/lang/resolver/tests.rad +37 -2
4055 4055
        try testing::expect(mismatch.actual == 1);
4056 4056
    }
4057 4057
    // Too many arguments.
4058 4058
    {
4059 4059
        let mut a = testResolver();
4060 -
        let program = "fn f(ptr: *u8, len: u32, extra: u32) -> *[u8] { return @sliceOf(ptr, len, extra); }";
4060 +
        let program = "fn f(ptr: *u8, len: u32, cap: u32, extra: u32) -> *[u8] { return @sliceOf(ptr, len, cap, extra); }";
4061 4061
        let result = try resolveProgramStr(&mut a, program);
4062 4062
        let err = try expectError(&result);
4063 4063
        let case super::ErrorKind::BuiltinArgCountMismatch(mismatch) = err.kind
4064 4064
            else throw testing::TestError::Failed;
4065 4065
        try testing::expect(mismatch.expected == 2);
4066 -
        try testing::expect(mismatch.actual == 3);
4066 +
        try testing::expect(mismatch.actual == 4);
4067 4067
    }
4068 4068
}
4069 4069
4070 4070
/// Test @sliceOf with wrong argument types produces errors.
4071 4071
@test fn testResolveSliceOfWrongArgTypes() throws (testing::TestError) {
4105 4105
        let case super::ErrorKind::TypeMismatch(_) = err.kind
4106 4106
            else throw testing::TestError::Failed;
4107 4107
    }
4108 4108
}
4109 4109
4110 +
/// Test @sliceOf with 3 arguments (ptr, len, cap) succeeds.
4111 +
@test fn testResolveSliceOfWithCap() throws (testing::TestError) {
4112 +
    {
4113 +
        let mut a = testResolver();
4114 +
        let program = "fn f(ptr: *u8, len: u32, cap: u32) -> *[u8] { return @sliceOf(ptr, len, cap); }";
4115 +
        let result = try resolveProgramStr(&mut a, program);
4116 +
        try expectNoErrors(&result);
4117 +
    }
4118 +
    // Mutable pointer produces mutable slice.
4119 +
    {
4120 +
        let mut a = testResolver();
4121 +
        let program = "fn f(ptr: *mut u8, len: u32, cap: u32) -> *mut [u8] { return @sliceOf(ptr, len, cap); }";
4122 +
        let result = try resolveProgramStr(&mut a, program);
4123 +
        try expectNoErrors(&result);
4124 +
    }
4125 +
}
4126 +
4127 +
/// Test @sliceOf with 3 arguments but wrong cap type.
4128 +
@test fn testResolveSliceOfCapWrongType() throws (testing::TestError) {
4129 +
    let mut a = testResolver();
4130 +
    let program = "fn f(ptr: *u8, len: u32, cap: bool) -> *[u8] { return @sliceOf(ptr, len, cap); }";
4131 +
    let result = try resolveProgramStr(&mut a, program);
4132 +
    let err = try expectError(&result);
4133 +
    let case super::ErrorKind::TypeMismatch(_) = err.kind
4134 +
        else throw testing::TestError::Failed;
4135 +
}
4136 +
4137 +
/// Test .cap field access on slices resolves to u32.
4138 +
@test fn testResolveSliceCapField() throws (testing::TestError) {
4139 +
    let mut a = testResolver();
4140 +
    let program = "fn f(s: *[u8]) -> u32 { return s.cap; }";
4141 +
    let result = try resolveProgramStr(&mut a, program);
4142 +
    try expectNoErrors(&result);
4143 +
}
4144 +
4110 4145
/// Test `match &opt` produces immutable pointer bindings.
4111 4146
@test fn testResolveMatchRefUnionBinding() throws (testing::TestError) {
4112 4147
    let mut a = testResolver();
4113 4148
    let program = "union Opt { Some(i32), None } fn f() { let opt = Opt::Some(42); match &opt { case Opt::Some(x) => { *x; } else => {} } }";
4114 4149
    let result = try resolveProgramStr(&mut a, program);