compiler: Calculate optional payload offsets from resolved layouts

560d04a37c182be151ab65d24eaecd213db4bb9a0aad151d29e35c44d3617ef2
Alexis Sellier committed ago 1 parent 415fdcf8
lib/std/lang/lower.rad +5 -5
1511 1511
                if resolver::typesEqual(target, ty) {
1512 1512
                    if resolver::isNullableType(*inner) {
1513 1513
                        try lowerConstDataPayloadInto(self, node, *inner, dataPrefix, b);
1514 1514
                    } else {
1515 1515
                        let layout = resolver::getTypeLayout(ty);
1516 -
                        let offset = resolver::getOptionalValOffset(*inner);
1516 +
                        let offset = resolver::getOptionalValOffset(resolver::getTypeLayout(*inner));
1517 1517
                        dataBuilderPush(b, il::DataValue {
1518 1518
                            item: il::DataItem::Val { typ: il::Type::W8, val: 1 }, count: 1,
1519 1519
                        });
1520 1520
                        if offset > 1 {
1521 1521
                            dataBuilderPush(b, il::DataValue { item: il::DataItem::Undef, count: offset - 1 });
3391 3391
        return nil;
3392 3392
    };
3393 3393
    // For optional aggregates, extract the payload from the tagged value.
3394 3394
    // The tag check already passed, so we know the payload is valid.
3395 3395
    if let case MatchSubjectKind::OptionalAggregate = subject.kind {
3396 -
        let valOffset = resolver::getOptionalValOffset(subject.bindType) as i32;
3396 +
        let valOffset = resolver::getOptionalValOffset(resolver::getTypeLayout(subject.bindType)) as i32;
3397 3397
        return try bindPayloadVariable(self, name, subject.val, subject.bindType, subject.by, valOffset, mutable);
3398 3398
    }
3399 3399
    // Declare the variable in the current block's scope.
3400 3400
    return newVar(self, name, ilType(self.low, subject.bindType), mutable, subject.val);
3401 3401
}
4393 4393
    // Null-pointer-optimized (NPO) types are used as-is -- valid values are never null.
4394 4394
    if resolver::isNullableType(*inner) {
4395 4395
        return val;
4396 4396
    }
4397 4397
    let layout = resolver::getTypeLayout(optType);
4398 -
    let valOffset = resolver::getOptionalValOffset(*inner) as i32;
4398 +
    let valOffset = resolver::getOptionalValOffset(resolver::getTypeLayout(*inner)) as i32;
4399 4399
4400 4400
    return try buildTagged(self, layout, 1, val, *inner, 1, valOffset);
4401 4401
}
4402 4402
4403 4403
/// Build a `nil` value for an optional type.
4413 4413
    if let case resolver::Type::Slice { item, mutable, .. } = *inner {
4414 4414
        return try buildSliceValue(
4415 4415
            self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0)
4416 4416
        );
4417 4417
    }
4418 -
    let valOffset = resolver::getOptionalValOffset(*inner) as i32;
4418 +
    let valOffset = resolver::getOptionalValOffset(resolver::getTypeLayout(*inner)) as i32;
4419 4419
    return try buildTagged(self, resolver::getTypeLayout(optType), 0, nil, *inner, 1, valOffset);
4420 4420
}
4421 4421
4422 4422
/// Build a result value for throwing functions.
4423 4423
unsafe fn buildResult 'arena 'phase 'function (
4667 4667
    inner: resolver::Type,
4668 4668
    a: il::Reg,
4669 4669
    b: il::Reg,
4670 4670
    offset: i32
4671 4671
) -> il::Val throws (LowerError) where 'arena: 'phase, 'phase: 'function {
4672 -
    let valOffset = resolver::getOptionalValOffset(inner) as i32;
4672 +
    let valOffset = resolver::getOptionalValOffset(resolver::getTypeLayout(inner)) as i32;
4673 4673
4674 4674
    // Load tags.
4675 4675
    let tagA = loadTag(self, a, offset + TVAL_TAG_OFFSET, il::Type::W8);
4676 4676
    let tagB = loadTag(self, b, offset + TVAL_TAG_OFFSET, il::Type::W8);
4677 4677
lib/std/lang/resolver.rad +3 -5
2059 2059
    // Nullable types use null pointer optimization -- no tag byte needed.
2060 2060
    if isNullableType(inner) {
2061 2061
        return getTypeLayout(inner);
2062 2062
    }
2063 2063
    let innerLayout = getTypeLayout(inner);
2064 -
    let tagSize: u32 = 1;
2065 -
    let valOffset = mem::alignUp(tagSize, innerLayout.alignment);
2064 +
    let valOffset = getOptionalValOffset(innerLayout);
2066 2065
    let alignment = max(innerLayout.alignment, 1);
2067 2066
2068 2067
    return Layout {
2069 2068
        size: mem::alignUp(valOffset + innerLayout.size, alignment),
2070 2069
        alignment,
2071 2070
    };
2072 2071
}
2073 2072
2074 2073
/// Get the payload offset within an optional aggregate.
2075 -
export unsafe fn getOptionalValOffset(inner: Type) -> u32 {
2076 -
    let innerLayout = getTypeLayout(inner);
2077 -
    return mem::alignUp(1, innerLayout.alignment);
2074 +
export fn getOptionalValOffset(inner: Layout) -> u32 {
2075 +
    return mem::alignUp(1, inner.alignment);
2078 2076
}
2079 2077
2080 2078
/// Check if a type is optional.
2081 2079
export fn isOptionalType(ty: Type) -> bool {
2082 2080
    match ty {
lib/std/lang/resolver/tests/regions.rad +8 -0
4 4
use std::testing;
5 5
use std::lang::types;
6 6
use std::lang::resolver;
7 7
use std::lang::ast;
8 8
9 +
/// Optional aggregate payloads start at their required alignment after the tag.
10 +
@test fn testOptionalPayloadOffsets() throws (testing::TestError) {
11 +
    for alignment in [1 as u32, 2, 4, 8, 16] {
12 +
        let layout = resolver::Layout { size: 32, alignment };
13 +
        assert resolver::getOptionalValOffset(layout) == alignment;
14 +
    }
15 +
}
16 +
9 17
/// Each declaration has a distinct identity within its resolver.
10 18
@test unsafe fn testSymbolIdentities() throws (testing::TestError) {
11 19
    let mut arena = super::testArena();
12 20
    let storage: 'test = &mut arena in {
13 21
        let mut res = super::testResolver(storage);