From 4fe2900a02b2b84fec0e9a1e260c1da4ed2df9a7 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 12 Sep 2025 23:02:22 +0000 Subject: [PATCH] Use MaybeUnformed in Optional. Add builtins to form and detect null `MaybeUnformed(T*)` values, and use those to represent `Optional(T*)`. --- core/prelude/types/optional.carbon | 85 ++++++++++++++++++---- toolchain/check/eval.cpp | 4 +- toolchain/lower/handle_call.cpp | 20 +++++ toolchain/sem_ir/builtin_function_kind.cpp | 28 +++++++ toolchain/sem_ir/builtin_function_kind.def | 4 + toolchain/sem_ir/type_iterator.h | 4 +- 6 files changed, 127 insertions(+), 18 deletions(-) diff --git a/core/prelude/types/optional.carbon b/core/prelude/types/optional.carbon index 0f0804efaeaa3..bfb3752b742b4 100644 --- a/core/prelude/types/optional.carbon +++ b/core/prelude/types/optional.carbon @@ -6,30 +6,83 @@ package Core library "prelude/types/optional"; import library "prelude/copy"; import library "prelude/destroy"; +import library "prelude/operators/as"; import library "prelude/types/bool"; +import library "prelude/types/maybe_unformed"; + +// TODO: Decide how to expose this in the public API. +private interface OptionalStorage { + let Type:! type; + fn None() -> Type; + fn Some[self: Self]() -> Type; + fn Has(value: Type) -> bool; + fn Get(value: Type) -> Self; +} -// For now, an `Optional(T)` is stored as a pair of a `bool` and a `T`, with -// the `T` left uninitialized if the `bool` is `false`. This isn't a viable -// approach in the longer term, but is the best we can do for now. -// -// TODO: Revisit this once we have choice types implemented in the toolchain. -// // TODO: We don't have an approved design for an `Optional` type yet, but it's // used by the design for `Iterate`. The API here is a placeholder. -class Optional(T:! Copy) { +class Optional(T:! OptionalStorage) { fn None() -> Self { - returned var me: Self; - me.has_value = false; - return var; + return {.value = T.None()}; } - fn Some(value: T) -> Self { - return {.has_value = true, .value = value}; + return {.value = value.Some()}; + } + fn HasValue[self: Self]() -> bool { + return T.Has(self.value); + } + fn Get[self: Self]() -> T { + return T.Get(self.value); } - fn HasValue[self: Self]() -> bool { return self.has_value; } - fn Get[self: Self]() -> T { return self.value; } + private var value: T.Type; +} - private var has_value: bool; - private var value: T; +// By default, an `Optional(T)` is stored as a pair of a `bool` and a +// `MaybeUnformed(T)`, with the `MaybeUnformed(T)` left uninitialized if the +// `bool` is `false`. +// +// TODO: Revisit this once we have choice types implemented in the toolchain. +private class DefaultOptionalStorage(T:! Copy) { + var value: MaybeUnformed(T); + var has_value: bool; +} + +impl forall [T:! Copy] T as OptionalStorage + where .Type = DefaultOptionalStorage(T) { + fn None() -> DefaultOptionalStorage(T) { + returned var me: DefaultOptionalStorage(T); + me.has_value = false; + return var; + } + fn Some[self: Self]() -> DefaultOptionalStorage(T) { + returned var me: DefaultOptionalStorage(T); + // TODO: Should be: + // me.value = self as MaybeUnformed(T); + me.value unsafe as T = self; + me.has_value = true; + return var; + } + fn Has(value: DefaultOptionalStorage(T)) -> bool { + return value.has_value; + } + fn Get(value: DefaultOptionalStorage(T)) -> T { + return value.value unsafe as T; + } +} + +// For pointers, we use a null pointer value as the "None" value. This allows +// `Optional(T*)` to be ABI-compatible with a C++ nullable pointer. +final impl forall [T:! type] T* as OptionalStorage + where .Type = MaybeUnformed(T*) { + fn None() -> MaybeUnformed(T*) = "pointer.make_null"; + fn Some[self: Self]() -> MaybeUnformed(T*) { + returned var result: MaybeUnformed(T*); + result unsafe as T* = self; + return var; + } + fn Has(value: MaybeUnformed(T*)) -> bool = "pointer.is_null"; + fn Get(value: MaybeUnformed(T*)) -> T* { + return value unsafe as T*; + } } diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 467c83a282523..69e0d2d0d26a9 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -1693,7 +1693,9 @@ static auto MakeConstantForBuiltinCall(EvalContext& eval_context, case SemIR::BuiltinFunctionKind::IntOrAssign: case SemIR::BuiltinFunctionKind::IntXorAssign: case SemIR::BuiltinFunctionKind::IntLeftShiftAssign: - case SemIR::BuiltinFunctionKind::IntRightShiftAssign: { + case SemIR::BuiltinFunctionKind::IntRightShiftAssign: + case SemIR::BuiltinFunctionKind::PointerMakeNull: + case SemIR::BuiltinFunctionKind::PointerIsNull: { // These are runtime-only builtins. // TODO: Consider tracking this on the `BuiltinFunctionKind`. return SemIR::ConstantId::NotConstant; diff --git a/toolchain/lower/handle_call.cpp b/toolchain/lower/handle_call.cpp index dd9ef828d4c4c..3dfde5041b50e 100644 --- a/toolchain/lower/handle_call.cpp +++ b/toolchain/lower/handle_call.cpp @@ -469,6 +469,26 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id, CARBON_FATAL("Missing constant value for call to comptime-only function"); } + case SemIR::BuiltinFunctionKind::PointerMakeNull: { + // MaybeUnformed(T*) has an in-place initializing representation, so an + // out parameter will be passed. + context.builder().CreateStore( + llvm::ConstantPointerNull::get( + llvm::PointerType::get(context.llvm_context(), + /*AddressSpace=*/0)), + context.GetValue(arg_ids[1])); + context.SetLocal(inst_id, + llvm::PoisonValue::get(context.GetTypeOfInst(inst_id))); + return; + } + + case SemIR::BuiltinFunctionKind::PointerIsNull: { + auto* ptr = context.builder().CreateLoad( + context.GetTypeOfInst(arg_ids[0]), context.GetValue(arg_ids[0])); + context.SetLocal(inst_id, context.builder().CreateIsNull(ptr)); + return; + } + case SemIR::BuiltinFunctionKind::TypeAggregateDestroy: // TODO: Destroy aggregate members. return; diff --git a/toolchain/sem_ir/builtin_function_kind.cpp b/toolchain/sem_ir/builtin_function_kind.cpp index 74ec22e2a1f46..a0bae17576191 100644 --- a/toolchain/sem_ir/builtin_function_kind.cpp +++ b/toolchain/sem_ir/builtin_function_kind.cpp @@ -80,6 +80,22 @@ struct PointerTo { } }; +// Constraint that a type is MaybeUnformed. +template +struct MaybeUnformed { + static auto Check(const File& sem_ir, ValidateState& state, TypeId type_id) + -> bool { + auto maybe_unformed = + sem_ir.types().TryGetAs(type_id); + if (!maybe_unformed) { + return false; + } + return Check( + sem_ir, state, + sem_ir.types().GetTypeIdForTypeInstId(maybe_unformed->inner_id)); + } +}; + // Constraint that a type is `()`, used as the return type of builtin functions // with no return value. struct NoReturn { @@ -605,6 +621,18 @@ constexpr BuiltinInfo BoolEq = {"bool.eq", constexpr BuiltinInfo BoolNeq = {"bool.neq", ValidateSignatureBool>}; +// "pointer.make_null": returns the representation of a null pointer value. This +// is an unformed state for the pointer type. +constexpr BuiltinInfo PointerMakeNull = { + "pointer.make_null", + ValidateSignatureMaybeUnformed>>}; + +// "pointer.is_null": determines whether the given pointer representation is a +// null pointer value. +constexpr BuiltinInfo PointerIsNull = { + "pointer.is_null", + ValidateSignature>)->Bool>}; + // "type.and": facet type combination. constexpr BuiltinInfo TypeAnd = {"type.and", ValidateSignatureType>}; diff --git a/toolchain/sem_ir/builtin_function_kind.def b/toolchain/sem_ir/builtin_function_kind.def index e8080b37b740e..81860be7eb734 100644 --- a/toolchain/sem_ir/builtin_function_kind.def +++ b/toolchain/sem_ir/builtin_function_kind.def @@ -123,6 +123,10 @@ CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(FloatGreaterEq) CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(BoolEq) CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(BoolNeq) +// Pointers. +CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PointerMakeNull) +CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(PointerIsNull) + // Facet type combination. CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(TypeAnd) diff --git a/toolchain/sem_ir/type_iterator.h b/toolchain/sem_ir/type_iterator.h index 936d52820ffe7..6faec820db968 100644 --- a/toolchain/sem_ir/type_iterator.h +++ b/toolchain/sem_ir/type_iterator.h @@ -40,7 +40,9 @@ class TypeIterator { // The iterator will visit things in the reverse order that they are added. auto Add(InstId inst_id) -> void { auto type_id = sem_ir_->insts().Get(inst_id).type_id(); - CARBON_CHECK(sem_ir_->types().IsFacetType(type_id)); + CARBON_CHECK(sem_ir_->types().IsFacetType(type_id), + "Type {0} of type inst is not a facet type", + sem_ir_->types().GetAsInst(type_id).kind()); PushInstId(inst_id); }