@@ -27,8 +27,8 @@ use rustc_public::mir::{
2727} ;
2828use rustc_public:: rustc_internal;
2929use rustc_public:: ty:: {
30- Binder , ClosureKind , ExistentialPredicate , IntTy , RigidTy , Size , Ty , TyConst , TyKind , UintTy ,
31- VariantIdx ,
30+ Binder , ClosureKind , ExistentialPredicate , FloatTy , IntTy , RigidTy , Size , Ty , TyConst , TyKind ,
31+ UintTy , VariantIdx ,
3232} ;
3333use std:: collections:: BTreeMap ;
3434use tracing:: { debug, trace, warn} ;
@@ -1086,6 +1086,15 @@ impl GotocCtx<'_, '_> {
10861086 let src_ty_kind = src_ty. kind ( ) ;
10871087 let dst_ty_kind = dst_ty. kind ( ) ;
10881088
1089+ // Float to integer casting requires special handling for saturating semantics.
1090+ // Since Rust 1.45, the `as` keyword performs a saturating cast when casting
1091+ // from float to int. See https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast
1092+ if let TyKind :: RigidTy ( RigidTy :: Float ( _) ) = src_ty_kind
1093+ && dst_ty_kind. is_integral ( )
1094+ {
1095+ return self . codegen_float_to_int_saturating_cast ( src, dst_ty) ;
1096+ }
1097+
10891098 // number casting
10901099 if src_ty_kind. is_numeric ( ) && dst_ty_kind. is_numeric ( ) {
10911100 return self . codegen_operand_stable ( src) . cast_to ( self . codegen_ty_stable ( dst_ty) ) ;
@@ -1162,6 +1171,143 @@ impl GotocCtx<'_, '_> {
11621171 }
11631172 }
11641173
1174+ /// Codegen a float-to-int cast with Rust's saturating semantics.
1175+ ///
1176+ /// Since Rust 1.45, the `as` keyword performs a *saturating cast* when casting from float to int:
1177+ /// - If the value is NaN, the result is 0
1178+ /// - If the value exceeds the upper bound, the result is MAX
1179+ /// - If the value is below the lower bound, the result is MIN (or 0 for unsigned types)
1180+ /// - If the value is positive infinity, the result is MAX
1181+ /// - If the value is negative infinity, the result is MIN (or 0 for unsigned types)
1182+ /// - Otherwise, the value is truncated toward zero
1183+ ///
1184+ /// See: https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast
1185+ fn codegen_float_to_int_saturating_cast ( & mut self , src : & Operand , dst_ty : Ty ) -> Expr {
1186+ let src_expr = self . codegen_operand_stable ( src) ;
1187+ let dst_goto_ty = self . codegen_ty_stable ( dst_ty) ;
1188+ let mm = self . symbol_table . machine_model ( ) ;
1189+
1190+ // Get the integer type bounds
1191+ let int_min = dst_goto_ty. min_int_expr ( mm) ;
1192+ let int_max = dst_goto_ty. max_int_expr ( mm) ;
1193+
1194+ // Get the source float type for creating bound constants
1195+ let src_ty = self . operand_ty_stable ( src) ;
1196+
1197+ // Get the integer bounds as float constants for comparison.
1198+ // Note: We convert the integer bounds to float for comparison.
1199+ // For very large integer types (i128, u128), the float conversion may lose precision,
1200+ // but this is acceptable because:
1201+ // 1. Any float value that's truly larger than MAX_INT will still compare as > MAX_INT
1202+ // 2. Any float value that's truly smaller than MIN_INT will still compare as < MIN_INT
1203+ let int_min_as_float = self . int_bounds_as_float ( dst_ty, false , src_ty) ;
1204+ let int_max_as_float = self . int_bounds_as_float ( dst_ty, true , src_ty) ;
1205+
1206+ // Determine if the destination type is signed
1207+ let dst_is_signed = matches ! ( dst_ty. kind( ) , TyKind :: RigidTy ( RigidTy :: Int ( _) ) ) ;
1208+
1209+ // Check for special cases:
1210+ // 1. isnan(src) -> result is 0
1211+ // 2. src > int_max -> result is MAX
1212+ // 3. src < int_min -> result is MIN (or 0 for unsigned)
1213+ // 4. Otherwise -> truncate toward zero
1214+ let is_nan = src_expr. clone ( ) . is_nan ( ) ;
1215+ let above_max = src_expr. clone ( ) . gt ( int_max_as_float. clone ( ) ) ;
1216+ let below_min = src_expr. clone ( ) . lt ( int_min_as_float) ;
1217+
1218+ // The truncated value (normal cast behavior for values in range)
1219+ let truncated = src_expr. cast_to ( dst_goto_ty. clone ( ) ) ;
1220+
1221+ // Build the nested ternary expression:
1222+ // isnan ? 0 : (above_max ? MAX : (below_min ? MIN : truncated))
1223+ let int_zero = dst_goto_ty. zero ( ) ;
1224+ let min_value = if dst_is_signed { int_min } else { int_zero. clone ( ) } ;
1225+
1226+ // Note: For positive infinity, above_max will be true.
1227+ // For negative infinity, below_min will be true.
1228+ // So infinity cases are handled by the bounds checks.
1229+ below_min
1230+ . ternary ( min_value, above_max. ternary ( int_max, is_nan. ternary ( int_zero, truncated) ) )
1231+ }
1232+
1233+ /// Convert an integer bound (min or max) to a float constant for the given float type.
1234+ fn int_bounds_as_float ( & self , int_ty : Ty , is_max : bool , float_ty : Ty ) -> Expr {
1235+ let mm = self . symbol_table . machine_model ( ) ;
1236+
1237+ // Get the integer bound value
1238+ let bound_value: f64 = match int_ty. kind ( ) {
1239+ TyKind :: RigidTy ( RigidTy :: Int ( int_kind) ) => {
1240+ if is_max {
1241+ match int_kind {
1242+ IntTy :: I8 => i8:: MAX as f64 ,
1243+ IntTy :: I16 => i16:: MAX as f64 ,
1244+ IntTy :: I32 => i32:: MAX as f64 ,
1245+ IntTy :: I64 => i64:: MAX as f64 ,
1246+ IntTy :: I128 => i128:: MAX as f64 ,
1247+ IntTy :: Isize => {
1248+ if mm. pointer_width == 32 {
1249+ i32:: MAX as f64
1250+ } else {
1251+ i64:: MAX as f64
1252+ }
1253+ }
1254+ }
1255+ } else {
1256+ match int_kind {
1257+ IntTy :: I8 => i8:: MIN as f64 ,
1258+ IntTy :: I16 => i16:: MIN as f64 ,
1259+ IntTy :: I32 => i32:: MIN as f64 ,
1260+ IntTy :: I64 => i64:: MIN as f64 ,
1261+ IntTy :: I128 => i128:: MIN as f64 ,
1262+ IntTy :: Isize => {
1263+ if mm. pointer_width == 32 {
1264+ i32:: MIN as f64
1265+ } else {
1266+ i64:: MIN as f64
1267+ }
1268+ }
1269+ }
1270+ }
1271+ }
1272+ TyKind :: RigidTy ( RigidTy :: Uint ( uint_kind) ) => {
1273+ if is_max {
1274+ match uint_kind {
1275+ UintTy :: U8 => u8:: MAX as f64 ,
1276+ UintTy :: U16 => u16:: MAX as f64 ,
1277+ UintTy :: U32 => u32:: MAX as f64 ,
1278+ UintTy :: U64 => u64:: MAX as f64 ,
1279+ UintTy :: U128 => u128:: MAX as f64 ,
1280+ UintTy :: Usize => {
1281+ if mm. pointer_width == 32 {
1282+ u32:: MAX as f64
1283+ } else {
1284+ u64:: MAX as f64
1285+ }
1286+ }
1287+ }
1288+ } else {
1289+ 0.0 // MIN for unsigned is always 0
1290+ }
1291+ }
1292+ _ => unreachable ! ( "Expected integer type" ) ,
1293+ } ;
1294+
1295+ // Create the float constant in the appropriate float type
1296+ match float_ty. kind ( ) {
1297+ TyKind :: RigidTy ( RigidTy :: Float ( FloatTy :: F16 ) ) => {
1298+ Expr :: float16_constant ( bound_value as f16 )
1299+ }
1300+ TyKind :: RigidTy ( RigidTy :: Float ( FloatTy :: F32 ) ) => {
1301+ Expr :: float_constant ( bound_value as f32 )
1302+ }
1303+ TyKind :: RigidTy ( RigidTy :: Float ( FloatTy :: F64 ) ) => Expr :: double_constant ( bound_value) ,
1304+ TyKind :: RigidTy ( RigidTy :: Float ( FloatTy :: F128 ) ) => {
1305+ Expr :: float128_constant ( bound_value as f128 )
1306+ }
1307+ _ => unreachable ! ( "Expected float type" ) ,
1308+ }
1309+ }
1310+
11651311 /// "Pointer casts" are particular kinds of pointer-to-pointer casts.
11661312 /// See the [`PointerCoercion`] type for specifics.
11671313 /// Note that this does not include all casts involving pointers,
0 commit comments