compiler: Fix initialization of optional constants

9b9efcd0064af631db73a629576a9a3586e7c87a812d28a456799e3f8827b85f
Lower optional constant coercions with their resolved tag, padding, and payload
layout. Handle nil constants and repeated optional elements through the same
payload path. This produces initialized static data for nullable and tagged
optional representations.
Alexis Sellier committed ago 1 parent 7f92c2e1
lib/std/lang/lower.rad +74 -61
1419 1419
    }
1420 1420
    dataSliceHeader(b, dataName, arrInfo.length);
1421 1421
}
1422 1422
1423 1423
/// Lower a constant expression payload into a builder without slot padding.
1424 -
/// Compute the type layout only when undefined data needs a byte count.
1424 +
/// Return the number of appended data entries, not bytes or array elements.
1425 +
/// Compute the type layout when nil or undefined data needs a byte count.
1425 1426
unsafe fn lowerConstDataPayloadInto(
1426 1427
    self: &mut Lowerer,
1427 1428
    node: *ast::Node,
1428 1429
    ty: resolver::Type,
1429 1430
    dataPrefix: *[u8],
1430 1431
    b: &mut DataValueBuilder
1431 -
) throws (LowerError) {
1432 +
) -> u32 throws (LowerError) {
1433 +
    let start = b.values.len;
1434 +
    // Optional coercions define the tag and payload layout of constant values.
1435 +
    if let case resolver::Type::Optional(inner) = ty {
1436 +
        if let coercion = resolver::coercionFor(self.resolver, node) {
1437 +
            if let case resolver::Coercion::OptionalLift(target) = coercion; resolver::typesEqual(target, ty) {
1438 +
                let layout = resolver::getTypeLayout(ty);
1439 +
                let mut offset: u32 = 0;
1440 +
                if resolver::isOptionalAggregate(ty) {
1441 +
                    set offset = resolver::getOptionalValOffset(*inner);
1442 +
                    dataBuilderPush(b, il::DataValue {
1443 +
                        item: il::DataItem::Val { typ: il::Type::W8, val: 1 }, count: 1,
1444 +
                    });
1445 +
                    if offset > 1 {
1446 +
                        dataBuilderPush(b, il::DataValue { item: il::DataItem::Undef, count: offset - 1 });
1447 +
                    }
1448 +
                }
1449 +
                try lowerConstDataInto(self, node, *inner, layout.size - offset, dataPrefix, b);
1450 +
                return b.values.len - start;
1451 +
            }
1452 +
        }
1453 +
    }
1432 1454
    // Function pointer references in constant data.
1433 1455
    if let case resolver::Type::Fn(_) = ty {
1434 1456
        let sym = resolver::nodeData(self.resolver, node).sym
1435 1457
            else throw LowerError::MissingSymbol(node);
1436 1458
        let modId = resolver::moduleIdForSymbol(self.resolver, sym);
1437 1459
        let qualName = qualifyName(self, modId, sym.name);
1438 1460
        dataBuilderPush(b, il::DataValue {
1439 1461
            item: il::DataItem::Fn(qualName), count: 1,
1440 1462
        });
1441 -
        return;
1463 +
        return b.values.len - start;
1442 1464
    }
1443 1465
    // In constant data, a void variant of a mixed union still occupies the
1444 1466
    // full tagged-union slot. Emitting only the tag corrupts following fields.
1445 1467
    if let sym = resolver::nodeData(self.resolver, node).sym {
1446 1468
        if let case resolver::SymbolData::Variant { type: resolver::Type::Void, .. } = sym.data {
1447 1469
            if let case resolver::Type::Nominal(resolver::NominalType::Union(_)) = ty {
1448 1470
                try lowerConstUnionVariantInto(self, node, sym, ty, &mut [], dataPrefix, b);
1449 -
                return;
1471 +
                return b.values.len - start;
1450 1472
            }
1451 1473
        }
1452 1474
    }
1453 1475
    match node.value {
1476 +
        case ast::NodeValue::Nil => {
1477 +
            let case resolver::Type::Optional(_) = ty else throw LowerError::NilInNonOptional;
1478 +
            let layout = resolver::getTypeLayout(ty);
1479 +
            dataBuilderPush(b, il::DataValue {
1480 +
                item: il::DataItem::Val { typ: il::Type::W8, val: 0 }, count: layout.size,
1481 +
            });
1482 +
        },
1454 1483
        case ast::NodeValue::Undef => {
1455 1484
            let layout = resolver::getTypeLayout(ty);
1456 1485
            dataBuilderPush(b, il::DataValue {
1457 1486
                item: il::DataItem::Undef,
1458 1487
                count: layout.size
1500 1529
        else => {
1501 1530
            // Scalar values: integers, bools, strings, void union variants, etc.
1502 1531
            try lowerConstScalarDataInto(self, node, ty, dataPrefix, b);
1503 1532
        }
1504 1533
    }
1534 +
    return b.values.len - start;
1505 1535
}
1506 1536
1507 1537
/// Lower a constant expression into a builder, padding to the given slot size.
1508 1538
unsafe fn lowerConstDataInto(
1509 1539
    self: &mut Lowerer,
1539 1569
        try lowerConstDataPayloadInto(self, elem, elemTy, dataPrefix, b);
1540 1570
    }
1541 1571
}
1542 1572
1543 1573
/// Build data values for a constant array repeat literal `[item; count]`.
1544 -
/// Repeat element payloads without extra slot padding. Undefined data uses
1545 -
/// the element layout to compute the total byte count.
1574 +
/// Repeat element payloads without extra slot padding. Undefined payloads
1575 +
/// already carry their element size in bytes.
1546 1576
unsafe fn lowerConstArrayRepeatInto(
1547 1577
    self: &mut Lowerer,
1548 1578
    repeat: ast::ArrayRepeatLit,
1549 1579
    ty: resolver::Type,
1550 1580
    dataPrefix: *[u8],
1553 1583
    let case resolver::Type::Array(arrInfo) = ty
1554 1584
        else throw LowerError::ExpectedArray;
1555 1585
    let length = arrInfo.length;
1556 1586
    let elemTy = *arrInfo.item;
1557 1587
1558 -
    if let case ast::NodeValue::Undef = repeat.item.value {
1559 -
        let elemLayout = resolver::getTypeLayout(elemTy);
1560 -
        dataBuilderPush(b, il::DataValue {
1561 -
            item: il::DataItem::Undef,
1562 -
            count: elemLayout.size * length
1563 -
        });
1564 -
    } else if let val = resolver::constValueEntry(self.resolver, repeat.item) {
1565 -
        if let case resolver::ConstValue::String(_) = val {
1566 -
            // A string used as a slice is represented by a three-word slice
1567 -
            // header, not by the bytes of the string itself.
1568 -
            for _ in 0..length {
1569 -
                try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b);
1570 -
            }
1571 -
        } else {
1572 -
            dataBuilderPush(b, il::DataValue {
1573 -
                item: constValueToDataItem(self, val, elemTy),
1574 -
                count: length
1575 -
            });
1576 -
        }
1588 +
    if length == 0 {
1589 +
        return;
1590 +
    }
1591 +
    let elementStart = b.values.len;
1592 +
    let entryCount = try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b);
1593 +
    if entryCount == 1 {
1594 +
        set b.values[elementStart].count *= length;
1577 1595
    } else {
1578 -
        for _ in 0..length {
1579 -
            try lowerConstDataPayloadInto(self, repeat.item, elemTy, dataPrefix, b);
1596 +
        // A string used as a slice is represented by a slice header, not by
1597 +
        // the bytes of the string itself. Repeat complete payloads in order.
1598 +
        for _ in 1..length {
1599 +
            for i in 0..entryCount {
1600 +
                let value = b.values[elementStart + i];
1601 +
                dataBuilderPush(b, value);
1602 +
            }
1580 1603
        }
1581 1604
    }
1582 1605
}
1583 1606
1584 1607
/// Build data values for a constant record literal.
4233 4256
        }
4234 4257
    }
4235 4258
    return il::Val::Reg(dst);
4236 4259
}
4237 4260
4238 -
/// Wrap a value in an optional type.
4261 +
/// Build a present or absent optional value.
4239 4262
///
4240 -
/// For optional pointers (`?*T`), the value is returned as-is since pointers
4241 -
/// use zero to represent `nil`. For other optionals, builds a tagged aggregate.
4242 -
/// with the tag set to `1`, and the value as payload.
4243 -
unsafe fn wrapInOptional(self: &mut FnLowerer, val: il::Val, optType: resolver::Type) -> il::Val throws (LowerError) {
4263 +
/// Optional pointers and slices use present values as-is, since valid values
4264 +
/// have non-null data pointers. Absent values use a null pointer or zero slice.
4265 +
/// Other optionals use a tagged aggregate with tag `1` and a payload when
4266 +
/// present, or tag `0` with no payload when absent.
4267 +
unsafe fn buildOptional(self: &mut FnLowerer, val: ?il::Val, optType: resolver::Type) -> il::Val throws (LowerError) {
4244 4268
    let case resolver::Type::Optional(inner) = optType else {
4245 4269
        throw LowerError::ExpectedOptional;
4246 4270
    };
4247 4271
    // Null-pointer-optimized (NPO) types are used as-is -- valid values are never null.
4248 -
    if resolver::isNullableType(*inner) {
4249 -
        return val;
4272 +
    if resolver::isOptionalPointer(optType) {
4273 +
        if let val = val {
4274 +
            return val;
4275 +
        }
4276 +
        if let case resolver::Type::Slice { item, mutable, .. } = *inner {
4277 +
            return try buildSliceValue(
4278 +
                self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0)
4279 +
            );
4280 +
        }
4281 +
        return il::Val::Imm(0);
4250 4282
    }
4251 4283
    let layout = resolver::getTypeLayout(optType);
4252 4284
    let valOffset = resolver::getOptionalValOffset(*inner) as i32;
4253 4285
4254 -
    return try buildTagged(self, layout, 1, val, *inner, 1, valOffset);
4255 -
}
4256 -
4257 -
/// Build a `nil` value for an optional type.
4258 -
///
4259 -
/// For optional pointers (`?*T`), returns an immediate `0` (null pointer).
4260 -
/// For other optionals, builds a tagged aggregate with tag set to `0` (absent).
4261 -
unsafe fn buildNilOptional(self: &mut FnLowerer, optType: resolver::Type) -> il::Val throws (LowerError) {
4262 -
    let case resolver::Type::Optional(inner) = optType
4263 -
        else throw LowerError::ExpectedOptional;
4264 -
    if let case resolver::Type::Pointer { .. } = *inner {
4265 -
        return il::Val::Imm(0);
4266 -
    }
4267 -
    if let case resolver::Type::Slice { item, mutable, .. } = *inner {
4268 -
        return try buildSliceValue(
4269 -
            self, item, mutable, il::Val::Imm(0), il::Val::Imm(0), il::Val::Imm(0)
4270 -
        );
4271 -
    }
4272 -
    let valOffset = resolver::getOptionalValOffset(*inner) as i32;
4273 -
    return try buildTagged(self, resolver::getTypeLayout(optType), 0, nil, *inner, 1, valOffset);
4286 +
    return try buildTagged(self, layout, 1 if val <> nil else 0, val, *inner, 1, valOffset);
4274 4287
}
4275 4288
4276 4289
/// Build a result value for throwing functions.
4277 4290
unsafe fn buildResult(
4278 4291
    self: &mut FnLowerer,
6025 6038
        // Aggregate types require element-wise comparison.
6026 6039
        // When comparing `?T` with `T`, wrap the scalar side.
6027 6040
        if isAggregateType(leftTy) {
6028 6041
            let mut rhs = b;
6029 6042
            if not isAggregateType(rightTy) {
6030 -
                set rhs = try wrapInOptional(self, rhs, leftTy);
6043 +
                set rhs = try buildOptional(self, rhs, leftTy);
6031 6044
            }
6032 6045
            return try emitAggregateEqOp(self, binop.op, leftTy, a, rhs);
6033 6046
        }
6034 6047
        if isAggregateType(rightTy) {
6035 -
            let lhs = try wrapInOptional(self, a, rightTy);
6048 +
            let lhs = try buildOptional(self, a, rightTy);
6036 6049
            return try emitAggregateEqOp(self, binop.op, rightTy, lhs, b);
6037 6050
        }
6038 6051
        set resultTy = scalarComparisonType(leftTy, rightTy);
6039 6052
    }
6040 6053
    return emitScalarBinOp(self, binop.op, ilType(self.low, resultTy), a, b, isUnsignedType(resultTy));
6326 6339
        // type (e.g. `try?` wrapping `T` into `?T`), wrap the value.
6327 6340
        let payloadVal = tvalPayloadVal(self, base, okValueTy, RESULT_VAL_OFFSET);
6328 6341
        let mut okVal = payloadVal;
6329 6342
6330 6343
        if t.returnsOptional and tryExprTy <> okValueTy {
6331 -
            set okVal = try wrapInOptional(self, payloadVal, tryExprTy);
6344 +
            set okVal = try buildOptional(self, payloadVal, tryExprTy);
6332 6345
        }
6333 6346
        try emitStore(self, slot, 0, tryExprTy, okVal);
6334 6347
    }
6335 6348
    // Jump to merge block if unterminated.
6336 6349
    try emitMergeIfUnterminated(self, &mut mergeBlock);
6339 6352
    switchToBlock(self, errBlock);
6340 6353
6341 6354
    if t.returnsOptional {
6342 6355
        // `try?` converts errors to `nil` -- store the `nil` and continue.
6343 6356
        if let slot = resultSlot {
6344 -
            let errVal = try buildNilOptional(self, tryExprTy);
6357 +
            let errVal = try buildOptional(self, nil, tryExprTy);
6345 6358
            try emitStore(self, slot, 0, tryExprTy, errVal);
6346 6359
        }
6347 6360
        try emitMergeIfUnterminated(self, &mut mergeBlock);
6348 6361
    } else if t.catches.len > 0 {
6349 6362
        // `try ... catch` -- handle the error.
7073 7086
        return val;
7074 7087
    };
7075 7088
    match coerce {
7076 7089
        case resolver::Coercion::OptionalLift(optType) => {
7077 7090
            if let case ast::NodeValue::Nil = node.value {
7078 -
                return try buildNilOptional(self, optType);
7091 +
                return try buildOptional(self, nil, optType);
7079 7092
            }
7080 -
            return try wrapInOptional(self, val, optType);
7093 +
            return try buildOptional(self, val, optType);
7081 7094
        }
7082 7095
        case resolver::Coercion::NumericCast { from, to } => {
7083 7096
            return lowerNumericCast(self, val, from, to);
7084 7097
        }
7085 7098
        case resolver::Coercion::ResultWrap => {
7257 7270
            set val = il::Val::Imm(c as i64);
7258 7271
        }
7259 7272
        case ast::NodeValue::Nil => {
7260 7273
            let typ = try typeOf(self, node);
7261 7274
            if let case resolver::Type::Optional(_) = typ {
7262 -
                set val = try buildNilOptional(self, typ);
7275 +
                set val = try buildOptional(self, nil, typ);
7263 7276
            } else if let case resolver::Type::Nil = typ {
7264 7277
                // Standalone `nil` without a concrete optional type. We can't
7265 7278
                // generate a proper value representation.
7266 7279
                throw LowerError::MissingType(node);
7267 7280
            } else {
test/tests/binding.storage.rad added +35 -0
1 +
//! returns: 0
2 +
3 +
/// Aggregate whose fields need initialized storage.
4 +
record Pair: Copy {
5 +
    /// First summand.
6 +
    first: u32,
7 +
    /// Second summand.
8 +
    second: u64,
9 +
}
10 +
11 +
/// Check initialized values and storage layouts.
12 +
@default unsafe fn main() -> u32 {
13 +
    let mut pair: Pair = undefined;
14 +
    set pair.first = 40;
15 +
    set pair.second = 0x1122334455667788;
16 +
    let numbers: [u32; 2] = [40, 2];
17 +
    assert pair.first == numbers[0];
18 +
    assert pair.second == 0x1122334455667788;
19 +
    let wrapped: ?Pair = pair;
20 +
    let value = wrapped else panic;
21 +
    assert value.first == 40;
22 +
    assert value.second == 0x1122334455667788;
23 +
    let scalar: u32 = 42;
24 +
    let mut optional: ?u32 = scalar;
25 +
    assert readOptional(&optional) == 42;
26 +
    set optional = nil;
27 +
    assert optional == nil;
28 +
    return 0;
29 +
}
30 +
31 +
/// Read a coerced optional through its stored representation.
32 +
fn readOptional(value: &?u32) -> u32 {
33 +
    let present = *value else return 0;
34 +
    return present;
35 +
}
test/tests/data.optional.nil.rad added +33 -0
1 +
//! returns: 0
2 +
3 +
/// Payload with alignment padding inside a tagged optional.
4 +
record Payload: Copy {
5 +
    /// Small prefix.
6 +
    prefix: u8,
7 +
    /// Aligned value.
8 +
    value: u64,
9 +
}
10 +
11 +
/// Initialized nullable pointer table.
12 +
unsafe static POINTERS: [?*u32; 3] = [nil; 3];
13 +
/// Initialized nullable slice.
14 +
unsafe static SLICE: ?*[u8] = nil;
15 +
/// Initialized tagged record optionals.
16 +
unsafe static RECORDS: [?Payload; 2] = [nil, nil];
17 +
/// Initialized nested optional.
18 +
unsafe static NESTED: ??u32 = nil;
19 +
20 +
/// Check initialized values and storage layouts.
21 +
@default unsafe fn main() -> u32 {
22 +
    for pointer in &POINTERS[..] {
23 +
        assert pointer == nil;
24 +
    }
25 +
    assert SLICE == nil;
26 +
    assert RECORDS[0] == nil;
27 +
    assert RECORDS[1] == nil;
28 +
    assert NESTED == nil;
29 +
    set RECORDS[1] = Payload { prefix: 2, value: 40 };
30 +
    let result = RECORDS[1] else panic;
31 +
    assert result.prefix as u32 + result.value as u32 == 42;
32 +
    return 0;
33 +
}
test/tests/data.optional.value.rad added +67 -0
1 +
//! returns: 0
2 +
3 +
/// Aligned record payload in a present optional.
4 +
record Payload: Copy {
5 +
    /// Small prefix before the aligned value.
6 +
    prefix: u8,
7 +
    /// Value used to check payload alignment.
8 +
    number: u64,
9 +
}
10 +
11 +
/// Tagged payload with both empty and initialized variants.
12 +
union Value: Copy { Empty, Number(i64), Pair { left: u8, right: u64 } }
13 +
14 +
/// Optional payload in a tagged instruction.
15 +
union Instruction: Copy { Return { value: ?Value } }
16 +
17 +
/// Optional constant reused as a nested optional payload.
18 +
constant PRESENT: ?u32 = 42;
19 +
/// Absent inner payload used in a present outer optional.
20 +
constant ABSENT: ?u32 = nil;
21 +
/// Present aligned record initializer.
22 +
unsafe static RECORD: ?Payload = Payload { prefix: 2, number: 40 };
23 +
/// Present outer optional that contains a present value.
24 +
unsafe static NESTED: ??u32 = PRESENT;
25 +
/// Present outer optional that contains an absent value.
26 +
unsafe static NESTED_NIL: ??u32 = ABSENT;
27 +
/// Repeated present optional values.
28 +
unsafe static REPEATED: [?u32; 2] = [PRESENT; 2];
29 +
/// Present nullable slice initializer.
30 +
unsafe static TEXT: ?*[u8] = "answer";
31 +
32 +
/// Check initialized values and storage layouts.
33 +
@default unsafe fn main() -> u32 {
34 +
    let instructions = &[
35 +
        Instruction::Return { value: Value::Number(42) },
36 +
        Instruction::Return { value: Value::Pair { left: 2, right: 40 } },
37 +
        Instruction::Return { value: Value::Empty }
38 +
    ];
39 +
    let case Instruction::Return { value } = instructions[0] else panic;
40 +
    let present = value else panic;
41 +
    let case Value::Number(number) = present else panic;
42 +
    assert number == 42;
43 +
    let case Instruction::Return { value: second } = instructions[1] else panic;
44 +
    let pair = second else panic;
45 +
    let case Value::Pair { left, right } = pair else panic;
46 +
    assert left as u64 + right == 42;
47 +
    let case Instruction::Return { value: third } = instructions[2] else panic;
48 +
    let empty = third else panic;
49 +
    let case Value::Empty = empty else panic;
50 +
    let rec = RECORD else panic;
51 +
    assert rec.prefix as u64 + rec.number == 42;
52 +
    let inner = NESTED else panic;
53 +
    let answer = inner else panic;
54 +
    assert answer == 42;
55 +
    let missing = NESTED_NIL else panic;
56 +
    assert missing == nil;
57 +
    for item in &REPEATED[..] {
58 +
        let value = item else panic;
59 +
        assert value == 42;
60 +
    }
61 +
    let text = TEXT else panic;
62 +
    assert text.len == 6;
63 +
    for byte, i in "answer" {
64 +
        assert text[i] == byte;
65 +
    }
66 +
    return 0;
67 +
}