Skip to content

Commit 4fe2900

Browse files
committed
Use MaybeUnformed in Optional.
Add builtins to form and detect null `MaybeUnformed(T*)` values, and use those to represent `Optional(T*)`.
1 parent 3b6d202 commit 4fe2900

File tree

6 files changed

+127
-18
lines changed

6 files changed

+127
-18
lines changed

core/prelude/types/optional.carbon

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,83 @@ package Core library "prelude/types/optional";
66

77
import library "prelude/copy";
88
import library "prelude/destroy";
9+
import library "prelude/operators/as";
910
import library "prelude/types/bool";
11+
import library "prelude/types/maybe_unformed";
12+
13+
// TODO: Decide how to expose this in the public API.
14+
private interface OptionalStorage {
15+
let Type:! type;
16+
fn None() -> Type;
17+
fn Some[self: Self]() -> Type;
18+
fn Has(value: Type) -> bool;
19+
fn Get(value: Type) -> Self;
20+
}
1021

11-
// For now, an `Optional(T)` is stored as a pair of a `bool` and a `T`, with
12-
// the `T` left uninitialized if the `bool` is `false`. This isn't a viable
13-
// approach in the longer term, but is the best we can do for now.
14-
//
15-
// TODO: Revisit this once we have choice types implemented in the toolchain.
16-
//
1722
// TODO: We don't have an approved design for an `Optional` type yet, but it's
1823
// used by the design for `Iterate`. The API here is a placeholder.
19-
class Optional(T:! Copy) {
24+
class Optional(T:! OptionalStorage) {
2025
fn None() -> Self {
21-
returned var me: Self;
22-
me.has_value = false;
23-
return var;
26+
return {.value = T.None()};
2427
}
25-
2628
fn Some(value: T) -> Self {
27-
return {.has_value = true, .value = value};
29+
return {.value = value.Some()};
30+
}
31+
fn HasValue[self: Self]() -> bool {
32+
return T.Has(self.value);
33+
}
34+
fn Get[self: Self]() -> T {
35+
return T.Get(self.value);
2836
}
2937

30-
fn HasValue[self: Self]() -> bool { return self.has_value; }
31-
fn Get[self: Self]() -> T { return self.value; }
38+
private var value: T.Type;
39+
}
3240

33-
private var has_value: bool;
34-
private var value: T;
41+
// By default, an `Optional(T)` is stored as a pair of a `bool` and a
42+
// `MaybeUnformed(T)`, with the `MaybeUnformed(T)` left uninitialized if the
43+
// `bool` is `false`.
44+
//
45+
// TODO: Revisit this once we have choice types implemented in the toolchain.
46+
private class DefaultOptionalStorage(T:! Copy) {
47+
var value: MaybeUnformed(T);
48+
var has_value: bool;
49+
}
50+
51+
impl forall [T:! Copy] T as OptionalStorage
52+
where .Type = DefaultOptionalStorage(T) {
53+
fn None() -> DefaultOptionalStorage(T) {
54+
returned var me: DefaultOptionalStorage(T);
55+
me.has_value = false;
56+
return var;
57+
}
58+
fn Some[self: Self]() -> DefaultOptionalStorage(T) {
59+
returned var me: DefaultOptionalStorage(T);
60+
// TODO: Should be:
61+
// me.value = self as MaybeUnformed(T);
62+
me.value unsafe as T = self;
63+
me.has_value = true;
64+
return var;
65+
}
66+
fn Has(value: DefaultOptionalStorage(T)) -> bool {
67+
return value.has_value;
68+
}
69+
fn Get(value: DefaultOptionalStorage(T)) -> T {
70+
return value.value unsafe as T;
71+
}
72+
}
73+
74+
// For pointers, we use a null pointer value as the "None" value. This allows
75+
// `Optional(T*)` to be ABI-compatible with a C++ nullable pointer.
76+
final impl forall [T:! type] T* as OptionalStorage
77+
where .Type = MaybeUnformed(T*) {
78+
fn None() -> MaybeUnformed(T*) = "pointer.make_null";
79+
fn Some[self: Self]() -> MaybeUnformed(T*) {
80+
returned var result: MaybeUnformed(T*);
81+
result unsafe as T* = self;
82+
return var;
83+
}
84+
fn Has(value: MaybeUnformed(T*)) -> bool = "pointer.is_null";
85+
fn Get(value: MaybeUnformed(T*)) -> T* {
86+
return value unsafe as T*;
87+
}
3588
}

toolchain/check/eval.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1693,7 +1693,9 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context,
16931693
case SemIR::BuiltinFunctionKind::IntOrAssign:
16941694
case SemIR::BuiltinFunctionKind::IntXorAssign:
16951695
case SemIR::BuiltinFunctionKind::IntLeftShiftAssign:
1696-
case SemIR::BuiltinFunctionKind::IntRightShiftAssign: {
1696+
case SemIR::BuiltinFunctionKind::IntRightShiftAssign:
1697+
case SemIR::BuiltinFunctionKind::PointerMakeNull:
1698+
case SemIR::BuiltinFunctionKind::PointerIsNull: {
16971699
// These are runtime-only builtins.
16981700
// TODO: Consider tracking this on the `BuiltinFunctionKind`.
16991701
return SemIR::ConstantId::NotConstant;

toolchain/lower/handle_call.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,26 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
469469
CARBON_FATAL("Missing constant value for call to comptime-only function");
470470
}
471471

472+
case SemIR::BuiltinFunctionKind::PointerMakeNull: {
473+
// MaybeUnformed(T*) has an in-place initializing representation, so an
474+
// out parameter will be passed.
475+
context.builder().CreateStore(
476+
llvm::ConstantPointerNull::get(
477+
llvm::PointerType::get(context.llvm_context(),
478+
/*AddressSpace=*/0)),
479+
context.GetValue(arg_ids[1]));
480+
context.SetLocal(inst_id,
481+
llvm::PoisonValue::get(context.GetTypeOfInst(inst_id)));
482+
return;
483+
}
484+
485+
case SemIR::BuiltinFunctionKind::PointerIsNull: {
486+
auto* ptr = context.builder().CreateLoad(
487+
context.GetTypeOfInst(arg_ids[0]), context.GetValue(arg_ids[0]));
488+
context.SetLocal(inst_id, context.builder().CreateIsNull(ptr));
489+
return;
490+
}
491+
472492
case SemIR::BuiltinFunctionKind::TypeAggregateDestroy:
473493
// TODO: Destroy aggregate members.
474494
return;

toolchain/sem_ir/builtin_function_kind.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@ struct PointerTo {
8080
}
8181
};
8282

83+
// Constraint that a type is MaybeUnformed<T>.
84+
template <typename T>
85+
struct MaybeUnformed {
86+
static auto Check(const File& sem_ir, ValidateState& state, TypeId type_id)
87+
-> bool {
88+
auto maybe_unformed =
89+
sem_ir.types().TryGetAs<SemIR::MaybeUnformedType>(type_id);
90+
if (!maybe_unformed) {
91+
return false;
92+
}
93+
return Check<T>(
94+
sem_ir, state,
95+
sem_ir.types().GetTypeIdForTypeInstId(maybe_unformed->inner_id));
96+
}
97+
};
98+
8399
// Constraint that a type is `()`, used as the return type of builtin functions
84100
// with no return value.
85101
struct NoReturn {
@@ -605,6 +621,18 @@ constexpr BuiltinInfo BoolEq = {"bool.eq",
605621
constexpr BuiltinInfo BoolNeq = {"bool.neq",
606622
ValidateSignature<auto(Bool, Bool)->Bool>};
607623

624+
// "pointer.make_null": returns the representation of a null pointer value. This
625+
// is an unformed state for the pointer type.
626+
constexpr BuiltinInfo PointerMakeNull = {
627+
"pointer.make_null",
628+
ValidateSignature<auto()->MaybeUnformed<PointerTo<AnyType>>>};
629+
630+
// "pointer.is_null": determines whether the given pointer representation is a
631+
// null pointer value.
632+
constexpr BuiltinInfo PointerIsNull = {
633+
"pointer.is_null",
634+
ValidateSignature<auto(MaybeUnformed<PointerTo<AnyType>>)->Bool>};
635+
608636
// "type.and": facet type combination.
609637
constexpr BuiltinInfo TypeAnd = {"type.and",
610638
ValidateSignature<auto(Type, Type)->Type>};

toolchain/sem_ir/builtin_function_kind.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(FloatGreaterEq)
123123
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(BoolEq)
124124
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(BoolNeq)
125125

126+
// Pointers.
127+
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PointerMakeNull)
128+
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PointerIsNull)
129+
126130
// Facet type combination.
127131
CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(TypeAnd)
128132

toolchain/sem_ir/type_iterator.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ class TypeIterator {
4040
// The iterator will visit things in the reverse order that they are added.
4141
auto Add(InstId inst_id) -> void {
4242
auto type_id = sem_ir_->insts().Get(inst_id).type_id();
43-
CARBON_CHECK(sem_ir_->types().IsFacetType(type_id));
43+
CARBON_CHECK(sem_ir_->types().IsFacetType(type_id),
44+
"Type {0} of type inst is not a facet type",
45+
sem_ir_->types().GetAsInst(type_id).kind());
4446
PushInstId(inst_id);
4547
}
4648

0 commit comments

Comments
 (0)