@@ -459,8 +459,6 @@ def coerce_int_to_fixed_width(self, src: Value, target_type: RType, line: int) -
459459 assert is_fixed_width_rtype (target_type ), target_type
460460 assert isinstance (target_type , RPrimitive ), target_type
461461
462- res = Register (target_type )
463-
464462 fast , slow , end = BasicBlock (), BasicBlock (), BasicBlock ()
465463
466464 check = self .check_tagged_short_int (src , line )
@@ -471,37 +469,20 @@ def coerce_int_to_fixed_width(self, src: Value, target_type: RType, line: int) -
471469 size = target_type .size
472470 if size < int_rprimitive .size :
473471 # Add a range check when the target type is smaller than the source type
474- fast2 , fast3 = BasicBlock (), BasicBlock ()
475- upper_bound = 1 << (size * 8 - 1 )
476- if not target_type .is_signed :
477- upper_bound *= 2
478- check2 = self .add (ComparisonOp (src , Integer (upper_bound , src .type ), ComparisonOp .SLT ))
479- self .add (Branch (check2 , fast2 , slow , Branch .BOOL ))
480- self .activate_block (fast2 )
481- if target_type .is_signed :
482- lower_bound = - upper_bound
483- else :
484- lower_bound = 0
485- check3 = self .add (ComparisonOp (src , Integer (lower_bound , src .type ), ComparisonOp .SGE ))
486- self .add (Branch (check3 , fast3 , slow , Branch .BOOL ))
487- self .activate_block (fast3 )
488- tmp = self .int_op (
489- c_pyssize_t_rprimitive ,
490- src ,
491- Integer (1 , c_pyssize_t_rprimitive ),
492- IntOp .RIGHT_SHIFT ,
493- line ,
472+ # Use helper method to generate range checking and conversion
473+ res = self .coerce_tagged_to_fixed_width_with_range_check (
474+ src , target_type , int_rprimitive .size , slow , end , line
494475 )
495- tmp = self .add (Truncate (tmp , target_type ))
496476 else :
477+ # No range check needed when target is same size or larger
497478 if size > int_rprimitive .size :
498479 tmp = self .add (Extend (src , target_type , signed = True ))
499480 else :
500481 tmp = src
501482 tmp = self .int_op (target_type , tmp , Integer (1 , target_type ), IntOp .RIGHT_SHIFT , line )
502-
503- self .add (Assign (res , tmp ))
504- self .goto (end )
483+ res = Register ( target_type )
484+ self .add (Assign (res , tmp ))
485+ self .goto (end )
505486
506487 self .activate_block (slow )
507488 if is_int64_rprimitive (target_type ) or (
@@ -521,31 +502,122 @@ def coerce_int_to_fixed_width(self, src: Value, target_type: RType, line: int) -
521502 self .add (Assign (res , tmp ))
522503 self .add (KeepAlive ([src ]))
523504 self .goto (end )
524- elif is_int32_rprimitive ( target_type ) :
505+ else :
525506 # Slow path just always generates an OverflowError
507+ self .emit_fixed_width_overflow_error (target_type , line )
508+
509+ self .activate_block (end )
510+ return res
511+
512+ def coerce_tagged_to_fixed_width_with_range_check (
513+ self ,
514+ src : Value ,
515+ target_type : RType ,
516+ source_size : int ,
517+ overflow_block : BasicBlock ,
518+ success_block : BasicBlock ,
519+ line : int ,
520+ ) -> Register :
521+ """Helper to convert a tagged value to a smaller fixed-width type with range checking.
522+
523+ This method generates IR for converting a tagged integer (like short_int or the fast
524+ path of int) to a smaller fixed-width type (i32, i16, uint8) with overflow detection.
525+
526+ The method performs range checks and branches to overflow_block on failure, or
527+ success_block on success (after assigning the result to a register).
528+
529+ Args:
530+ src: Tagged source value (with tag bit set)
531+ target_type: Target fixed-width type (must be smaller than source_size)
532+ source_size: Size in bytes of the source type
533+ overflow_block: Block to branch to on overflow
534+ success_block: Block to goto after successful conversion
535+ line: Line number
536+
537+ Returns:
538+ Result register containing the converted value (valid in success_block)
539+ """
540+ assert is_fixed_width_rtype (target_type ), target_type
541+ assert isinstance (target_type , RPrimitive ), target_type
542+ size = target_type .size
543+ assert size < source_size , (target_type , size , source_size )
544+
545+ res = Register (target_type )
546+ in_range , in_range2 = BasicBlock (), BasicBlock ()
547+
548+ # Calculate bounds for the target type (in tagged representation)
549+ upper_bound = 1 << (size * 8 - 1 )
550+ if not target_type .is_signed :
551+ upper_bound *= 2
552+
553+ # Check if value < upper_bound
554+ check_upper = self .add (ComparisonOp (src , Integer (upper_bound , src .type ), ComparisonOp .SLT ))
555+ self .add (Branch (check_upper , in_range , overflow_block , Branch .BOOL ))
556+
557+ self .activate_block (in_range )
558+
559+ # Check if value >= lower_bound
560+ if target_type .is_signed :
561+ lower_bound = - upper_bound
562+ else :
563+ lower_bound = 0
564+ check_lower = self .add (ComparisonOp (src , Integer (lower_bound , src .type ), ComparisonOp .SGE ))
565+ self .add (Branch (check_lower , in_range2 , overflow_block , Branch .BOOL ))
566+
567+ self .activate_block (in_range2 )
568+
569+ # Value is in range - shift right to remove tag, then truncate
570+ shifted = self .int_op (
571+ c_pyssize_t_rprimitive ,
572+ src ,
573+ Integer (1 , c_pyssize_t_rprimitive ),
574+ IntOp .RIGHT_SHIFT ,
575+ line ,
576+ )
577+ tmp = self .add (Truncate (shifted , target_type ))
578+ self .add (Assign (res , tmp ))
579+ self .goto (success_block )
580+
581+ return res
582+
583+ def emit_fixed_width_overflow_error (self , target_type : RType , line : int ) -> None :
584+ """Emit overflow error for fixed-width type conversion."""
585+ if is_int32_rprimitive (target_type ):
526586 self .call_c (int32_overflow , [], line )
527- self .add (Unreachable ())
528587 elif is_int16_rprimitive (target_type ):
529- # Slow path just always generates an OverflowError
530588 self .call_c (int16_overflow , [], line )
531- self .add (Unreachable ())
532589 elif is_uint8_rprimitive (target_type ):
533- # Slow path just always generates an OverflowError
534590 self .call_c (uint8_overflow , [], line )
535- self .add (Unreachable ())
536591 else :
537592 assert False , target_type
538-
539- self .activate_block (end )
540- return res
593+ self .add (Unreachable ())
541594
542595 def coerce_short_int_to_fixed_width (self , src : Value , target_type : RType , line : int ) -> Value :
596+ # short_int (CPyTagged) is guaranteed to be a tagged value, never a pointer,
597+ # so we don't need the fast/slow path split like coerce_int_to_fixed_width.
598+ # However, we still need range checking when target type is smaller than source.
599+ assert is_fixed_width_rtype (target_type ), target_type
600+ assert isinstance (target_type , RPrimitive ), target_type
601+
543602 if is_int64_rprimitive (target_type ) or (
544603 PLATFORM_SIZE == 4 and is_int32_rprimitive (target_type )
545604 ):
605+ # No range check needed - target is same size or larger than source
546606 return self .int_op (target_type , src , Integer (1 , target_type ), IntOp .RIGHT_SHIFT , line )
547- # TODO: i32 on 64-bit platform
548- assert False , (src .type , target_type , PLATFORM_SIZE )
607+
608+ # Target is smaller than source - need range checking
609+ # Use helper method to generate range checking and conversion
610+ overflow , end = BasicBlock (), BasicBlock ()
611+ res = self .coerce_tagged_to_fixed_width_with_range_check (
612+ src , target_type , short_int_rprimitive .size , overflow , end , line
613+ )
614+
615+ # Handle overflow case
616+ self .activate_block (overflow )
617+ self .emit_fixed_width_overflow_error (target_type , line )
618+
619+ self .activate_block (end )
620+ return res
549621
550622 def coerce_fixed_width_to_int (self , src : Value , line : int ) -> Value :
551623 if (
0 commit comments