cairo_lang_sierra/extensions/modules/
casts.rs

1use num_traits::Zero;
2use starknet_types_core::felt::Felt as Felt252;
3
4use super::range_check::RangeCheckType;
5use super::utils::{Range, reinterpret_cast_signature};
6use crate::define_libfunc_hierarchy;
7use crate::extensions::lib_func::{
8    BranchSignature, LibfuncSignature, OutputVarInfo, ParamSignature, SierraApChange,
9    SignatureOnlyGenericLibfunc, SignatureSpecializationContext, SpecializationContext,
10};
11use crate::extensions::{
12    NamedLibfunc, NamedType, OutputVarReferenceInfo, SignatureBasedConcreteLibfunc,
13    SpecializationError, args_as_two_types,
14};
15use crate::ids::ConcreteTypeId;
16use crate::program::GenericArg;
17
18define_libfunc_hierarchy! {
19    pub enum CastLibfunc {
20        Downcast(DowncastLibfunc),
21        Upcast(UpcastLibfunc),
22    }, CastConcreteLibfunc
23}
24
25/// The type of casting between two integer types.
26#[derive(PartialEq, Eq)]
27pub struct CastType {
28    /// Does the source type have values above the destination type possible values.
29    pub overflow_above: bool,
30    /// Does the source type have values below the destination type possible values.
31    pub overflow_below: bool,
32}
33
34/// Libfunc for casting from one type to another where any input value can fit into the destination
35/// type. For example, from u8 to u64.
36#[derive(Default)]
37pub struct UpcastLibfunc {}
38impl SignatureOnlyGenericLibfunc for UpcastLibfunc {
39    const STR_ID: &'static str = "upcast";
40
41    fn specialize_signature(
42        &self,
43        context: &dyn SignatureSpecializationContext,
44        args: &[GenericArg],
45    ) -> Result<LibfuncSignature, SpecializationError> {
46        let (from_ty, to_ty) = args_as_two_types(args)?;
47        let from_range = Range::from_type(context, from_ty.clone())?;
48        let to_range: Range = Range::from_type(context, to_ty.clone())?;
49        let is_upcast = to_range.lower <= from_range.lower && from_range.upper <= to_range.upper;
50        if !is_upcast {
51            return Err(SpecializationError::UnsupportedGenericArg);
52        }
53        Ok(reinterpret_cast_signature(from_ty, to_ty))
54    }
55}
56
57/// A concrete version of the `downcast` libfunc. See [DowncastLibfunc].
58pub struct DowncastConcreteLibfunc {
59    pub signature: LibfuncSignature,
60    pub from_ty: ConcreteTypeId,
61    pub from_range: Range,
62    pub to_ty: ConcreteTypeId,
63    pub to_range: Range,
64}
65impl DowncastConcreteLibfunc {
66    /// Returns the cast type.
67    pub fn cast_type(&self) -> CastType {
68        if self.from_ty == self.to_ty && self.from_range.lower.is_zero() {
69            // Backwards compatibility for the case of casting an unsigned type to itself.
70            CastType { overflow_above: true, overflow_below: false }
71        } else {
72            CastType {
73                overflow_above: self.to_range.upper < self.from_range.upper,
74                overflow_below: self.to_range.lower > self.from_range.lower,
75            }
76        }
77    }
78}
79
80impl SignatureBasedConcreteLibfunc for DowncastConcreteLibfunc {
81    fn signature(&self) -> &LibfuncSignature {
82        &self.signature
83    }
84}
85
86/// Libfunc for casting from one type to another where the input value may not fit into the
87/// destination type. For example, from u64 to u8.
88#[derive(Default)]
89pub struct DowncastLibfunc {}
90impl NamedLibfunc for DowncastLibfunc {
91    type Concrete = DowncastConcreteLibfunc;
92    const STR_ID: &'static str = "downcast";
93
94    fn specialize_signature(
95        &self,
96        context: &dyn SignatureSpecializationContext,
97        args: &[GenericArg],
98    ) -> Result<LibfuncSignature, SpecializationError> {
99        let (from_ty, to_ty) = args_as_two_types(args)?;
100        let to_range = Range::from_type(context, to_ty.clone())?;
101        let from_range = Range::from_type(context, from_ty.clone())?;
102        // Shrinking the range of the destination type by the range of the source type.
103        // This is necessary for example in the case `[0, PRIME) -> i8`.
104        // In this case `PRIME - 1` is a valid value in `from_range` and it is equivalent
105        // to `-1` in the field. Yet, we must make sure `PRIME - 1` is not downcasted to `-1`.
106        // By reducing `to_range`, we get a cast `[0, PRIME) -> [0, 128)` where `-1` is not
107        // in the output range.
108        //
109        // Note that the call to `intersection` additionally disallows disjoint ranges.
110        let to_range =
111            to_range.intersection(&from_range).ok_or(SpecializationError::UnsupportedGenericArg)?;
112
113        // Currently, we only support downcasting into `RangeCheck` sized types.
114        if !to_range.is_small_range() {
115            return Err(SpecializationError::UnsupportedGenericArg);
116        }
117        let is_small_values_downcast = from_range.is_small_range();
118        // Only allow `size < prime % u128::MAX` so that we can safely use `K=2` in
119        // `validate_under_limit`.
120        let is_felt252_valid_downcast = from_range.is_full_felt252_range()
121            && to_range.size() < (Felt252::prime() % u128::MAX).into();
122        if !(is_small_values_downcast || is_felt252_valid_downcast) {
123            return Err(SpecializationError::UnsupportedGenericArg);
124        }
125
126        let range_check_type = context.get_concrete_type(RangeCheckType::id(), &[])?;
127        let rc_output_info = OutputVarInfo::new_builtin(range_check_type.clone(), 0);
128        Ok(LibfuncSignature {
129            param_signatures: vec![
130                ParamSignature::new(range_check_type).with_allow_add_const(),
131                ParamSignature::new(from_ty),
132            ],
133            branch_signatures: vec![
134                // Success.
135                BranchSignature {
136                    vars: vec![rc_output_info.clone(), OutputVarInfo {
137                        ty: to_ty,
138                        ref_info: OutputVarReferenceInfo::SameAsParam { param_idx: 1 },
139                    }],
140                    ap_change: SierraApChange::Known { new_vars_only: false },
141                },
142                // Failure.
143                BranchSignature {
144                    vars: vec![rc_output_info],
145                    ap_change: SierraApChange::Known { new_vars_only: false },
146                },
147            ],
148            fallthrough: Some(0),
149        })
150    }
151
152    fn specialize(
153        &self,
154        context: &dyn SpecializationContext,
155        args: &[GenericArg],
156    ) -> Result<Self::Concrete, SpecializationError> {
157        let (from_ty, to_ty) = args_as_two_types(args)?;
158        let from_range = Range::from_type(context.upcast(), from_ty.clone())?;
159        // Shrinking the range of the destination type by the range of the source type.
160        let to_range: Range = Range::from_type(context.upcast(), to_ty.clone())?
161            .intersection(&from_range)
162            .ok_or(SpecializationError::UnsupportedGenericArg)?;
163        Ok(DowncastConcreteLibfunc {
164            signature: self.specialize_signature(context.upcast(), args)?,
165            from_range,
166            from_ty,
167            to_range,
168            to_ty,
169        })
170    }
171}