@@ -433,28 +433,64 @@ unsafe fn string_content_for_bigint(value: f64) -> String {
433433 String :: from_utf8_lossy ( bytes) . into_owned ( )
434434}
435435
436+ /// Both operands already numeric (plain IEEE double or int32-tagged)?
437+ ///
438+ /// `abstract_relational` opens a `RuntimeHandleScope`, roots both operands and
439+ /// runs `ToPrimitive` on each — necessary only because a *heap* operand can run
440+ /// user `valueOf`/`toString`. For a number `ToPrimitive` is the identity and
441+ /// there is no pointer to root, so the whole apparatus is dead weight. This is
442+ /// the same predicate and the same reasoning `dynamic_arith`'s binary operators
443+ /// already use; the relational operators were simply never given it.
444+ ///
445+ /// NaN must stay `false` for all four operators, which Rust's `<`/`>`/`<=`/`>=`
446+ /// on `f64` already deliver.
447+ #[ inline( always) ]
448+ fn rel_numeric_operand ( v : f64 ) -> Option < f64 > {
449+ const TAG_BAND_FLOOR : u64 = 0x7FF9_0000_0000_0000 ;
450+ if ( v. to_bits ( ) & 0x7FFF_0000_0000_0000 ) < TAG_BAND_FLOOR {
451+ return Some ( v) ;
452+ }
453+ let jv = crate :: value:: JSValue :: from_bits ( v. to_bits ( ) ) ;
454+ if jv. is_int32 ( ) {
455+ return Some ( jv. as_int32 ( ) as f64 ) ;
456+ }
457+ None
458+ }
459+
436460/// `x < y` — codegen routes here for any relational `<` whose operands are not
437461/// both statically numeric. Returns a NaN-boxed boolean (`f64`).
438462#[ no_mangle]
439463pub extern "C" fn js_rel_lt ( x : f64 , y : f64 ) -> f64 {
464+ if let ( Some ( a) , Some ( b) ) = ( rel_numeric_operand ( x) , rel_numeric_operand ( y) ) {
465+ return rel_bool_f64 ( a < b) ;
466+ }
440467 rel_bool_f64 ( unsafe { abstract_relational ( x, y, true ) } == REL_TRUE )
441468}
442469
443470/// `x > y` ⇔ `IsLessThan(y, x, false)` is true (right operand `ToPrimitive`'d first).
444471#[ no_mangle]
445472pub extern "C" fn js_rel_gt ( x : f64 , y : f64 ) -> f64 {
473+ if let ( Some ( a) , Some ( b) ) = ( rel_numeric_operand ( x) , rel_numeric_operand ( y) ) {
474+ return rel_bool_f64 ( a > b) ;
475+ }
446476 rel_bool_f64 ( unsafe { abstract_relational ( y, x, false ) } == REL_TRUE )
447477}
448478
449479/// `x <= y` ⇔ `IsLessThan(y, x, false)` is `false` (not `true`, not `undefined`).
450480#[ no_mangle]
451481pub extern "C" fn js_rel_le ( x : f64 , y : f64 ) -> f64 {
482+ if let ( Some ( a) , Some ( b) ) = ( rel_numeric_operand ( x) , rel_numeric_operand ( y) ) {
483+ return rel_bool_f64 ( a <= b) ;
484+ }
452485 rel_bool_f64 ( unsafe { abstract_relational ( y, x, false ) } == REL_FALSE )
453486}
454487
455488/// `x >= y` ⇔ `IsLessThan(x, y, true)` is `false` (not `true`, not `undefined`).
456489#[ no_mangle]
457490pub extern "C" fn js_rel_ge ( x : f64 , y : f64 ) -> f64 {
491+ if let ( Some ( a) , Some ( b) ) = ( rel_numeric_operand ( x) , rel_numeric_operand ( y) ) {
492+ return rel_bool_f64 ( a >= b) ;
493+ }
458494 rel_bool_f64 ( unsafe { abstract_relational ( x, y, true ) } == REL_FALSE )
459495}
460496
@@ -773,3 +809,85 @@ pub extern "C" fn js_value_typeof(value: f64) -> *mut StringHeader {
773809 get_cached ( & TYPEOF_NUMBER , "number" )
774810 }
775811}
812+
813+
814+ #[ cfg( test) ]
815+ mod rel_numeric_fastpath_tests {
816+ use super :: * ;
817+
818+ const INT32 : u64 = 0x7FFE_0000_0000_0000 ;
819+ const UNDEF : u64 = 0x7FFC_0000_0000_0001 ;
820+ const NULLV : u64 = 0x7FFC_0000_0000_0002 ;
821+ const FALSEV : u64 = 0x7FFC_0000_0000_0003 ;
822+ const TRUEV : u64 = 0x7FFC_0000_0000_0004 ;
823+
824+ fn i32v ( n : i32 ) -> f64 {
825+ f64:: from_bits ( INT32 | ( n as u32 as u64 ) )
826+ }
827+ fn is_true ( v : f64 ) -> bool {
828+ v. to_bits ( ) == TAG_TRUE_BITS
829+ }
830+
831+ /// The early-out accepts exactly the operands for which `ToPrimitive` is
832+ /// the identity and there is nothing to root; everything else must fall
833+ /// through to the full abstract relational comparison.
834+ #[ test]
835+ fn fast_path_accepts_only_numbers ( ) {
836+ assert ! ( rel_numeric_operand( 1.5 ) . is_some( ) ) ;
837+ assert ! ( rel_numeric_operand( -0.0 ) . is_some( ) ) ;
838+ assert ! ( rel_numeric_operand( f64 :: INFINITY ) . is_some( ) ) ;
839+ assert ! ( rel_numeric_operand( f64 :: NEG_INFINITY ) . is_some( ) ) ;
840+ assert ! ( rel_numeric_operand( f64 :: NAN ) . is_some( ) ) ;
841+ assert_eq ! ( rel_numeric_operand( i32v( 7 ) ) , Some ( 7.0 ) ) ;
842+ assert_eq ! ( rel_numeric_operand( i32v( -7 ) ) , Some ( -7.0 ) ) ;
843+ for tag in [ UNDEF , NULLV , FALSEV , TRUEV ] {
844+ assert ! (
845+ rel_numeric_operand( f64 :: from_bits( tag) ) . is_none( ) ,
846+ "tag {tag:#x} must not take the numeric fast path"
847+ ) ;
848+ }
849+ }
850+
851+ /// NaN makes all four operators false. This is the one way a naive `fcmp`
852+ /// early-out silently diverges from the spec, so pin it in both operand
853+ /// positions.
854+ #[ test]
855+ fn nan_is_false_for_every_operator ( ) {
856+ let n = f64:: NAN ;
857+ for ( name, got) in [
858+ ( "NaN < x" , js_rel_lt ( n, 1.0 ) ) ,
859+ ( "NaN > x" , js_rel_gt ( n, 1.0 ) ) ,
860+ ( "NaN <= x" , js_rel_le ( n, 1.0 ) ) ,
861+ ( "NaN >= x" , js_rel_ge ( n, 1.0 ) ) ,
862+ ( "x < NaN" , js_rel_lt ( 1.0 , n) ) ,
863+ ( "x > NaN" , js_rel_gt ( 1.0 , n) ) ,
864+ ( "x <= NaN" , js_rel_le ( 1.0 , n) ) ,
865+ ( "x >= NaN" , js_rel_ge ( 1.0 , n) ) ,
866+ ] {
867+ assert ! ( !is_true( got) , "{name} must be false" ) ;
868+ }
869+ }
870+
871+ /// `-0 < 0` is false while `-0 <= 0` is true.
872+ #[ test]
873+ fn signed_zero_matches_the_spec ( ) {
874+ assert ! ( !is_true( js_rel_lt( -0.0 , 0.0 ) ) ) ;
875+ assert ! ( !is_true( js_rel_gt( -0.0 , 0.0 ) ) ) ;
876+ assert ! ( is_true( js_rel_le( -0.0 , 0.0 ) ) ) ;
877+ assert ! ( is_true( js_rel_ge( -0.0 , 0.0 ) ) ) ;
878+ }
879+
880+ #[ test]
881+ fn ordinary_numeric_comparisons_are_unchanged ( ) {
882+ assert ! ( is_true( js_rel_lt( 1.0 , 2.0 ) ) ) ;
883+ assert ! ( !is_true( js_rel_lt( 2.0 , 1.0 ) ) ) ;
884+ assert ! ( is_true( js_rel_ge( 2.0 , 2.0 ) ) ) ;
885+ assert ! ( is_true( js_rel_le( 2.0 , 2.0 ) ) ) ;
886+ assert ! ( is_true( js_rel_gt( f64 :: INFINITY , 1e308 ) ) ) ;
887+ assert ! ( is_true( js_rel_lt( f64 :: NEG_INFINITY , 0.0 ) ) ) ;
888+ assert ! ( is_true( js_rel_lt( i32v( 3 ) , i32v( 9 ) ) ) ) ;
889+ assert ! ( !is_true( js_rel_gt( i32v( 3 ) , i32v( 9 ) ) ) ) ;
890+ assert ! ( is_true( js_rel_lt( i32v( 3 ) , 3.5 ) ) ) ;
891+ assert ! ( is_true( js_rel_gt( 3.5 , i32v( 3 ) ) ) ) ;
892+ }
893+ }
0 commit comments