Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 69 additions & 16 deletions core/prelude/types/optional.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -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*;
}
}
4 changes: 3 additions & 1 deletion toolchain/check/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 20 additions & 0 deletions toolchain/lower/handle_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 28 additions & 0 deletions toolchain/sem_ir/builtin_function_kind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ struct PointerTo {
}
};

// Constraint that a type is MaybeUnformed<T>.
template <typename T>
struct MaybeUnformed {
static auto Check(const File& sem_ir, ValidateState& state, TypeId type_id)
-> bool {
auto maybe_unformed =
sem_ir.types().TryGetAs<SemIR::MaybeUnformedType>(type_id);
if (!maybe_unformed) {
return false;
}
return Check<T>(
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 {
Expand Down Expand Up @@ -605,6 +621,18 @@ constexpr BuiltinInfo BoolEq = {"bool.eq",
constexpr BuiltinInfo BoolNeq = {"bool.neq",
ValidateSignature<auto(Bool, Bool)->Bool>};

// "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",
ValidateSignature<auto()->MaybeUnformed<PointerTo<AnyType>>>};

// "pointer.is_null": determines whether the given pointer representation is a
// null pointer value.
constexpr BuiltinInfo PointerIsNull = {
"pointer.is_null",
ValidateSignature<auto(MaybeUnformed<PointerTo<AnyType>>)->Bool>};

// "type.and": facet type combination.
constexpr BuiltinInfo TypeAnd = {"type.and",
ValidateSignature<auto(Type, Type)->Type>};
Expand Down
4 changes: 4 additions & 0 deletions toolchain/sem_ir/builtin_function_kind.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 3 additions & 1 deletion toolchain/sem_ir/type_iterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Loading