From 57552fb2f641c34e81537dfbca90aab238a535b2 Mon Sep 17 00:00:00 2001 From: David Mak Date: Mon, 3 Feb 2025 17:04:37 +0800 Subject: [PATCH] [core] codegen: Add Option{Type,Value} --- nac3core/src/codegen/expr.rs | 66 +++------ nac3core/src/codegen/mod.rs | 12 +- nac3core/src/codegen/types/mod.rs | 2 + nac3core/src/codegen/types/option.rs | 188 ++++++++++++++++++++++++++ nac3core/src/codegen/values/mod.rs | 2 + nac3core/src/codegen/values/option.rs | 75 ++++++++++ 6 files changed, 296 insertions(+), 49 deletions(-) create mode 100644 nac3core/src/codegen/types/option.rs create mode 100644 nac3core/src/codegen/values/option.rs diff --git a/nac3core/src/codegen/expr.rs b/nac3core/src/codegen/expr.rs index c398ed97..3f48154c 100644 --- a/nac3core/src/codegen/expr.rs +++ b/nac3core/src/codegen/expr.rs @@ -32,7 +32,7 @@ use super::{ gen_for_callback_incrementing, gen_if_callback, gen_if_else_expr_callback, gen_raise, gen_var, }, - types::{ndarray::NDArrayType, ListType, RangeType, StringType, TupleType}, + types::{ndarray::NDArrayType, ListType, OptionType, RangeType, StringType, TupleType}, values::{ ndarray::{NDArrayOut, RustNDIndex, ScalarOrNDArray}, ArrayLikeIndexer, ArrayLikeValue, ListValue, ProxyValue, RangeValue, @@ -179,34 +179,16 @@ impl<'ctx> CodeGenContext<'ctx, '_> { .into() } SymbolValue::OptionSome(v) => { - let ty = match self.unifier.get_ty_immutable(ty).as_ref() { - TypeEnum::TObj { obj_id, params, .. } - if *obj_id == self.primitives.option.obj_id(&self.unifier).unwrap() => - { - *params.iter().next().unwrap().1 - } - _ => codegen_unreachable!(self, "must be option type"), - }; let val = self.gen_symbol_val(generator, v, ty); - let ptr = generator - .gen_var_alloc(self, val.get_type(), Some("default_opt_some")) - .unwrap(); - self.builder.build_store(ptr, val).unwrap(); - ptr.into() - } - SymbolValue::OptionNone => { - let ty = match self.unifier.get_ty_immutable(ty).as_ref() { - TypeEnum::TObj { obj_id, params, .. } - if *obj_id == self.primitives.option.obj_id(&self.unifier).unwrap() => - { - *params.iter().next().unwrap().1 - } - _ => codegen_unreachable!(self, "must be option type"), - }; - let actual_ptr_type = - self.get_llvm_type(generator, ty).ptr_type(AddressSpace::default()); - actual_ptr_type.const_null().into() + OptionType::from_unifier_type(generator, self, ty) + .construct_some_value(generator, self, &val, None) + .as_abi_value(self) + .into() } + SymbolValue::OptionNone => OptionType::from_unifier_type(generator, self, ty) + .construct_empty(generator, self, None) + .as_abi_value(self) + .into(), } } @@ -2333,16 +2315,13 @@ pub fn gen_expr<'ctx, G: CodeGenerator>( const_val.into() } ExprKind::Name { id, .. } if id == &"none".into() => { - match ( - ctx.unifier.get_ty(expr.custom.unwrap()).as_ref(), - ctx.unifier.get_ty(ctx.primitives.option).as_ref(), - ) { - (TypeEnum::TObj { obj_id, params, .. }, TypeEnum::TObj { obj_id: opt_id, .. }) - if *obj_id == *opt_id => + match &*ctx.unifier.get_ty(expr.custom.unwrap()) { + TypeEnum::TObj { obj_id, .. } + if *obj_id == ctx.primitives.option.obj_id(&ctx.unifier).unwrap() => { - ctx.get_llvm_type(generator, *params.iter().next().unwrap().1) - .ptr_type(AddressSpace::default()) - .const_null() + OptionType::from_unifier_type(generator, ctx, expr.custom.unwrap()) + .construct_empty(generator, ctx, None) + .as_abi_value(ctx) .into() } _ => codegen_unreachable!(ctx, "must be option type"), @@ -2827,8 +2806,12 @@ pub fn gen_expr<'ctx, G: CodeGenerator>( }; } ValueEnum::Dynamic(BasicValueEnum::PointerValue(ptr)) => { - let not_null = - ctx.builder.build_is_not_null(ptr, "unwrap_not_null").unwrap(); + let option = OptionType::from_pointer_type( + ptr.get_type(), + ctx.get_size_type(), + ) + .map_pointer_value(ptr, None); + let not_null = option.is_some(ctx); ctx.make_assert( generator, not_null, @@ -2837,12 +2820,7 @@ pub fn gen_expr<'ctx, G: CodeGenerator>( [None, None, None], expr.location, ); - return Ok(Some( - ctx.builder - .build_load(ptr, "unwrap_some_load") - .map(Into::into) - .unwrap(), - )); + return Ok(Some(unsafe { option.load(ctx).into() })); } ValueEnum::Dynamic(_) => { codegen_unreachable!(ctx, "option must be static or ptr") diff --git a/nac3core/src/codegen/mod.rs b/nac3core/src/codegen/mod.rs index 5b6fa215..b9c743c7 100644 --- a/nac3core/src/codegen/mod.rs +++ b/nac3core/src/codegen/mod.rs @@ -43,7 +43,9 @@ use crate::{ }; use concrete_type::{ConcreteType, ConcreteTypeEnum, ConcreteTypeStore}; pub use generator::{CodeGenerator, DefaultCodeGenerator}; -use types::{ndarray::NDArrayType, ListType, ProxyType, RangeType, StringType, TupleType}; +use types::{ + ndarray::NDArrayType, ListType, OptionType, ProxyType, RangeType, StringType, TupleType, +}; pub mod builtin_fns; pub mod concrete_type; @@ -538,7 +540,7 @@ fn get_llvm_type<'ctx, G: CodeGenerator + ?Sized>( if PrimDef::contains_id(*obj_id) { return match &*unifier.get_ty_immutable(ty) { TObj { obj_id, params, .. } if *obj_id == PrimDef::Option.id() => { - get_llvm_type( + let element_type = get_llvm_type( ctx, module, generator, @@ -546,9 +548,9 @@ fn get_llvm_type<'ctx, G: CodeGenerator + ?Sized>( top_level, type_cache, *params.iter().next().unwrap().1, - ) - .ptr_type(AddressSpace::default()) - .into() + ); + + OptionType::new_with_generator(generator, ctx, &element_type).as_abi_type().into() } TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { diff --git a/nac3core/src/codegen/types/mod.rs b/nac3core/src/codegen/types/mod.rs index bceb8040..cbab600b 100644 --- a/nac3core/src/codegen/types/mod.rs +++ b/nac3core/src/codegen/types/mod.rs @@ -26,12 +26,14 @@ use super::{ {CodeGenContext, CodeGenerator}, }; pub use list::*; +pub use option::*; pub use range::*; pub use string::*; pub use tuple::*; mod list; pub mod ndarray; +mod option; mod range; mod string; pub mod structure; diff --git a/nac3core/src/codegen/types/option.rs b/nac3core/src/codegen/types/option.rs new file mode 100644 index 00000000..6347e5ab --- /dev/null +++ b/nac3core/src/codegen/types/option.rs @@ -0,0 +1,188 @@ +use inkwell::{ + context::Context, + types::{BasicType, BasicTypeEnum, IntType, PointerType}, + values::{BasicValue, BasicValueEnum, PointerValue}, + AddressSpace, +}; + +use super::ProxyType; +use crate::{ + codegen::{values::OptionValue, CodeGenContext, CodeGenerator}, + typecheck::typedef::{iter_type_vars, Type, TypeEnum}, +}; + +/// Proxy type for an `Option` type in LLVM. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct OptionType<'ctx> { + ty: PointerType<'ctx>, + llvm_usize: IntType<'ctx>, +} + +impl<'ctx> OptionType<'ctx> { + /// Creates an LLVM type corresponding to the expected structure of an `Option`. + #[must_use] + fn llvm_type(element_type: &impl BasicType<'ctx>) -> PointerType<'ctx> { + element_type.ptr_type(AddressSpace::default()) + } + + fn new_impl(element_type: &impl BasicType<'ctx>, llvm_usize: IntType<'ctx>) -> Self { + let llvm_option = Self::llvm_type(element_type); + + Self { ty: llvm_option, llvm_usize } + } + + /// Creates an instance of [`OptionType`]. + #[must_use] + pub fn new(ctx: &CodeGenContext<'ctx, '_>, element_type: &impl BasicType<'ctx>) -> Self { + Self::new_impl(element_type, ctx.get_size_type()) + } + + /// Creates an instance of [`OptionType`]. + #[must_use] + pub fn new_with_generator( + generator: &G, + ctx: &'ctx Context, + element_type: &impl BasicType<'ctx>, + ) -> Self { + Self::new_impl(element_type, generator.get_size_type(ctx)) + } + + /// Creates an [`OptionType`] from a [unifier type][Type]. + #[must_use] + pub fn from_unifier_type( + generator: &G, + ctx: &mut CodeGenContext<'ctx, '_>, + ty: Type, + ) -> Self { + // Check unifier type and extract `element_type` + let elem_type = match &*ctx.unifier.get_ty_immutable(ty) { + TypeEnum::TObj { obj_id, params, .. } + if *obj_id == ctx.primitives.option.obj_id(&ctx.unifier).unwrap() => + { + iter_type_vars(params).next().unwrap().ty + } + + _ => panic!("Expected `option` type, but got {}", ctx.unifier.stringify(ty)), + }; + + let llvm_usize = ctx.get_size_type(); + let llvm_elem_type = ctx.get_llvm_type(generator, elem_type); + + Self::new_impl(&llvm_elem_type, llvm_usize) + } + + /// Creates an [`OptionType`] from a [`PointerType`]. + #[must_use] + pub fn from_pointer_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self { + debug_assert!(Self::has_same_repr(ptr_ty, llvm_usize).is_ok()); + + Self { ty: ptr_ty, llvm_usize } + } + + /// Returns the element type of this `Option` type. + #[must_use] + pub fn element_type(&self) -> BasicTypeEnum<'ctx> { + BasicTypeEnum::try_from(self.ty.get_element_type()).unwrap() + } + + /// Allocates an [`OptionValue`] on the stack. + /// + /// The returned value will be `Some(v)` if [`value` contains a value][Option::is_some], + /// otherwise `none` will be returned. + #[must_use] + pub fn construct( + &self, + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + value: Option>, + name: Option<&'ctx str>, + ) -> >::Value { + let ptr = if let Some(v) = value { + let pvar = self.raw_alloca_var(generator, ctx, name); + ctx.builder.build_store(pvar, v).unwrap(); + pvar + } else { + self.ty.const_null() + }; + + self.map_pointer_value(ptr, name) + } + /// Allocates an [`OptionValue`] on the stack. + /// + /// The returned value will always be `none`. + #[must_use] + pub fn construct_empty( + &self, + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + name: Option<&'ctx str>, + ) -> >::Value { + self.construct(generator, ctx, None, name) + } + + /// Allocates an [`OptionValue`] on the stack. + /// + /// The returned value will be set to `Some(value)`. + #[must_use] + pub fn construct_some_value( + &self, + generator: &mut G, + ctx: &mut CodeGenContext<'ctx, '_>, + value: &impl BasicValue<'ctx>, + name: Option<&'ctx str>, + ) -> >::Value { + self.construct(generator, ctx, Some(value.as_basic_value_enum()), name) + } + + /// Converts an existing value into a [`OptionValue`]. + #[must_use] + pub fn map_pointer_value( + &self, + value: PointerValue<'ctx>, + name: Option<&'ctx str>, + ) -> >::Value { + >::Value::from_pointer_value(value, self.llvm_usize, name) + } +} + +impl<'ctx> ProxyType<'ctx> for OptionType<'ctx> { + type ABI = PointerType<'ctx>; + type Base = PointerType<'ctx>; + type Value = OptionValue<'ctx>; + + fn is_representable( + llvm_ty: impl BasicType<'ctx>, + llvm_usize: IntType<'ctx>, + ) -> Result<(), String> { + if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() { + Self::has_same_repr(ty, llvm_usize) + } else { + Err(format!("Expected pointer type, got {llvm_ty:?}")) + } + } + + fn has_same_repr(ty: Self::Base, _: IntType<'ctx>) -> Result<(), String> { + BasicTypeEnum::try_from(ty.get_element_type()) + .map_err(|()| format!("Expected `ty` to be a BasicTypeEnum, got {ty}"))?; + + Ok(()) + } + + fn alloca_type(&self) -> impl BasicType<'ctx> { + self.element_type() + } + + fn as_base_type(&self) -> Self::Base { + self.ty + } + + fn as_abi_type(&self) -> Self::ABI { + self.as_base_type() + } +} + +impl<'ctx> From> for PointerType<'ctx> { + fn from(value: OptionType<'ctx>) -> Self { + value.as_base_type() + } +} diff --git a/nac3core/src/codegen/values/mod.rs b/nac3core/src/codegen/values/mod.rs index cf125fee..7a43ba41 100644 --- a/nac3core/src/codegen/values/mod.rs +++ b/nac3core/src/codegen/values/mod.rs @@ -3,6 +3,7 @@ use inkwell::{types::IntType, values::BasicValue}; use super::{types::ProxyType, CodeGenContext}; pub use array::*; pub use list::*; +pub use option::*; pub use range::*; pub use string::*; pub use tuple::*; @@ -10,6 +11,7 @@ pub use tuple::*; mod array; mod list; pub mod ndarray; +mod option; mod range; mod string; pub mod structure; diff --git a/nac3core/src/codegen/values/option.rs b/nac3core/src/codegen/values/option.rs new file mode 100644 index 00000000..7fca60f8 --- /dev/null +++ b/nac3core/src/codegen/values/option.rs @@ -0,0 +1,75 @@ +use inkwell::{ + types::IntType, + values::{BasicValueEnum, IntValue, PointerValue}, +}; + +use super::ProxyValue; +use crate::codegen::{types::OptionType, CodeGenContext}; + +/// Proxy type for accessing a `Option` value in LLVM. +#[derive(Copy, Clone)] +pub struct OptionValue<'ctx> { + value: PointerValue<'ctx>, + llvm_usize: IntType<'ctx>, + name: Option<&'ctx str>, +} + +impl<'ctx> OptionValue<'ctx> { + /// Creates an [`OptionValue`] from a [`PointerValue`]. + #[must_use] + pub fn from_pointer_value( + ptr: PointerValue<'ctx>, + llvm_usize: IntType<'ctx>, + name: Option<&'ctx str>, + ) -> Self { + debug_assert!(Self::is_instance(ptr, llvm_usize).is_ok()); + + Self { value: ptr, llvm_usize, name } + } + + /// Returns an `i1` indicating if this `Option` instance does not hold a value. + #[must_use] + pub fn is_none(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { + ctx.builder.build_is_null(self.value, "").unwrap() + } + + /// Returns an `i1` indicating if this `Option` instance contains a value. + #[must_use] + pub fn is_some(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { + ctx.builder.build_is_not_null(self.value, "").unwrap() + } + + /// Loads the value present in this `Option` instance. + /// + /// # Safety + /// + /// The caller must ensure that this `option` value [contains a value][Self::is_some]. + #[must_use] + pub unsafe fn load(&self, ctx: &CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx> { + ctx.builder.build_load(self.value, "").unwrap() + } +} + +impl<'ctx> ProxyValue<'ctx> for OptionValue<'ctx> { + type ABI = PointerValue<'ctx>; + type Base = PointerValue<'ctx>; + type Type = OptionType<'ctx>; + + fn get_type(&self) -> Self::Type { + Self::Type::from_pointer_type(self.value.get_type(), self.llvm_usize) + } + + fn as_base_value(&self) -> Self::Base { + self.value + } + + fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI { + self.as_base_value() + } +} + +impl<'ctx> From> for PointerValue<'ctx> { + fn from(value: OptionValue<'ctx>) -> Self { + value.as_base_value() + } +}