Skip to content

Commit 383c09a

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 71ad10a commit 383c09a

File tree

6 files changed

+135
-18
lines changed

6 files changed

+135
-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
@@ -1681,7 +1681,9 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context,
16811681
case SemIR::BuiltinFunctionKind::IntOrAssign:
16821682
case SemIR::BuiltinFunctionKind::IntXorAssign:
16831683
case SemIR::BuiltinFunctionKind::IntLeftShiftAssign:
1684-
case SemIR::BuiltinFunctionKind::IntRightShiftAssign: {
1684+
case SemIR::BuiltinFunctionKind::IntRightShiftAssign:
1685+
case SemIR::BuiltinFunctionKind::PointerMakeNull:
1686+
case SemIR::BuiltinFunctionKind::PointerIsNull: {
16851687
// These are runtime-only builtins.
16861688
// TODO: Consider tracking this on the `BuiltinFunctionKind`.
16871689
return SemIR::ConstantId::NotConstant;

toolchain/lower/handle_call.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,26 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
466466
context.sem_ir().insts().Get(inst_id).type_id()));
467467
CARBON_FATAL("Missing constant value for call to comptime-only function");
468468
}
469+
470+
case SemIR::BuiltinFunctionKind::PointerMakeNull: {
471+
// MaybeUnformed(T*) has an in-place initializing representation, so an
472+
// out parameter will be passed.
473+
context.builder().CreateStore(
474+
llvm::ConstantPointerNull::get(
475+
llvm::PointerType::get(context.llvm_context(),
476+
/*AddressSpace=*/0)),
477+
context.GetValue(arg_ids[1]));
478+
context.SetLocal(inst_id,
479+
llvm::PoisonValue::get(context.GetTypeOfInst(inst_id)));
480+
return;
481+
}
482+
483+
case SemIR::BuiltinFunctionKind::PointerIsNull: {
484+
auto* ptr = context.builder().CreateLoad(
485+
context.GetTypeOfInst(arg_ids[0]), context.GetValue(arg_ids[0]));
486+
context.SetLocal(inst_id, context.builder().CreateIsNull(ptr));
487+
return;
488+
}
469489
}
470490

471491
CARBON_FATAL("Unsupported builtin call.");

toolchain/sem_ir/builtin_function_kind.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ struct ValidateState {
3838
template <typename TypeConstraint>
3939
auto Check(const File& sem_ir, ValidateState& state, TypeId type_id) -> bool;
4040

41+
// Constraint that is satisfied by any type. See ValidateSignature for details.
42+
struct AnyType {
43+
static auto Check(const File& /*sem_ir*/, ValidateState& /*state*/,
44+
TypeId /*type_id*/) -> bool {
45+
return true;
46+
}
47+
};
48+
4149
// Constraint that a type is generic type parameter `I` of the builtin,
4250
// satisfying `TypeConstraint`. See ValidateSignature for details.
4351
template <int I, typename TypeConstraint>
@@ -80,6 +88,22 @@ struct PointerTo {
8088
}
8189
};
8290

91+
// Constraint that a type is MaybeUnformed<T>.
92+
template <typename T>
93+
struct MaybeUnformed {
94+
static auto Check(const File& sem_ir, ValidateState& state, TypeId type_id)
95+
-> bool {
96+
auto maybe_unformed =
97+
sem_ir.types().TryGetAs<SemIR::MaybeUnformedType>(type_id);
98+
if (!maybe_unformed) {
99+
return false;
100+
}
101+
return Check<T>(
102+
sem_ir, state,
103+
sem_ir.types().GetTypeIdForTypeInstId(maybe_unformed->inner_id));
104+
}
105+
};
106+
83107
// Constraint that a type is `()`, used as the return type of builtin functions
84108
// with no return value.
85109
struct NoReturn {
@@ -597,6 +621,18 @@ constexpr BuiltinInfo BoolEq = {"bool.eq",
597621
constexpr BuiltinInfo BoolNeq = {"bool.neq",
598622
ValidateSignature<auto(Bool, Bool)->Bool>};
599623

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+
600636
// "type.and": facet type combination.
601637
constexpr BuiltinInfo TypeAnd = {"type.and",
602638
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(SemIR::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)