@@ -3359,6 +3359,65 @@ op_gc_bif2(
33593359 Arg2Value = Arg2 bsr 4 ,
33603360 Range2 = {Arg2Value , Arg2Value },
33613361 op_gc_bif2_bxor (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest , Range1 , Range2 );
3362+ % mul - both typed integers with range: inline if proven small
3363+ op_gc_bif2 (
3364+ MMod ,
3365+ MSt0 ,
3366+ FailLabel ,
3367+ Live ,
3368+ Bif ,
3369+ erlang ,
3370+ '*' ,
3371+ {typed , Arg1 , {t_integer , Range1 }},
3372+ {typed , Arg2 , {t_integer , Range2 }},
3373+ Dest
3374+ ) ->
3375+ op_gc_bif2_mul (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest , Range1 , Range2 );
3376+ op_gc_bif2 (
3377+ MMod ,
3378+ MSt0 ,
3379+ FailLabel ,
3380+ Live ,
3381+ Bif ,
3382+ erlang ,
3383+ '*' ,
3384+ {typed , Arg1 , {t_integer , Range1 }},
3385+ Arg2 ,
3386+ Dest
3387+ ) when is_integer (Arg2 ), Arg2 band ? TERM_IMMED_TAG_MASK =:= ? TERM_INTEGER_TAG ->
3388+ Arg2Value = Arg2 bsr 4 ,
3389+ Range2 = {Arg2Value , Arg2Value },
3390+ op_gc_bif2_mul (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest , Range1 , Range2 );
3391+ % bsl - typed integer with literal shift amount: inline if result fits
3392+ op_gc_bif2 (
3393+ MMod ,
3394+ MSt0 ,
3395+ FailLabel ,
3396+ Live ,
3397+ Bif ,
3398+ erlang ,
3399+ 'bsl' ,
3400+ {typed , Arg1 , {t_integer , Range1 }},
3401+ Arg2 ,
3402+ Dest
3403+ ) when is_integer (Arg2 ), Arg2 band ? TERM_IMMED_TAG_MASK =:= ? TERM_INTEGER_TAG ->
3404+ Arg2Value = Arg2 bsr 4 ,
3405+ op_gc_bif2_bsl (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest , Range1 , Arg2Value );
3406+ % bsr - typed integer with literal shift amount: inline if non-negative and small
3407+ op_gc_bif2 (
3408+ MMod ,
3409+ MSt0 ,
3410+ FailLabel ,
3411+ Live ,
3412+ Bif ,
3413+ erlang ,
3414+ 'bsr' ,
3415+ {typed , Arg1 , {t_integer , Range1 }},
3416+ Arg2 ,
3417+ Dest
3418+ ) when is_integer (Arg2 ), Arg2 band ? TERM_IMMED_TAG_MASK =:= ? TERM_INTEGER_TAG ->
3419+ Arg2Value = Arg2 bsr 4 ,
3420+ op_gc_bif2_bsr (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest , Range1 , Arg2Value );
33623421% Default case
33633422op_gc_bif2 (
33643423 MMod , MSt0 , FailLabel , Live , Bif , _Module , _Function , {typed , Arg1 , _ }, {typed , Arg2 , _ }, Dest
@@ -3587,6 +3646,160 @@ op_gc_bif2_bxor(MMod, MSt0, FailLabel, Live, Bif, Arg1, Arg2, Dest, Range1, Rang
35873646 op_gc_bif2_default (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest )
35883647 end .
35893648
3649+ % Check if multiplication can be inlined based on type ranges
3650+ % Returns true if the result is guaranteed to fit in a small integer
3651+ can_inline_mul (Range1 , Range2 , MMod ) ->
3652+ {MinSafe , MaxSafe } = small_integer_bounds (MMod ),
3653+ case {Range1 , Range2 } of
3654+ {{Min1 , Max1 }, {Min2 , Max2 }} when
3655+ is_integer (Min1 ),
3656+ is_integer (Max1 ),
3657+ is_integer (Min2 ),
3658+ is_integer (Max2 )
3659+ ->
3660+ % For multiplication, all four corner products must be checked
3661+ Products = [Min1 * Min2 , Min1 * Max2 , Max1 * Min2 , Max1 * Max2 ],
3662+ MinResult = lists :min (Products ),
3663+ MaxResult = lists :max (Products ),
3664+ MinResult >= MinSafe andalso MaxResult =< MaxSafe ;
3665+ _ ->
3666+ false
3667+ end .
3668+
3669+ % Optimized multiplication with compile-time range checking
3670+ op_gc_bif2_mul (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest , Range1 , Range2 ) when
3671+ is_integer (Arg2 )
3672+ ->
3673+ case can_inline_mul (Range1 , Range2 , MMod ) of
3674+ true ->
3675+ Arg2Value = Arg2 bsr 4 ,
3676+ case Arg2Value of
3677+ C when C > 1 ->
3678+ % Strip tag, multiply by constant, re-tag
3679+ {MSt1 , Reg } = MMod :move_to_native_register (MSt0 , Arg1 ),
3680+ {MSt2 , Reg } = MMod :and_ (MSt1 , {free , Reg }, bnot (? TERM_IMMED_TAG_MASK )),
3681+ MSt3 = MMod :mul (MSt2 , Reg , C ),
3682+ MSt4 = MMod :or_ (MSt3 , Reg , ? TERM_INTEGER_TAG ),
3683+ MSt5 = MMod :move_to_vm_register (MSt4 , Reg , Dest ),
3684+ MMod :free_native_registers (MSt5 , [Reg , Dest ]);
3685+ _ ->
3686+ % 0 or 1 would need special handling (0 produces wrong
3687+ % tag, 1 is identity), and negative constants require
3688+ % sign-aware logic. The compiler typically folds these,
3689+ % but fall back defensively.
3690+ op_gc_bif2_default (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest )
3691+ end ;
3692+ false ->
3693+ op_gc_bif2_default (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest )
3694+ end ;
3695+ op_gc_bif2_mul (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest , Range1 , Range2 ) ->
3696+ case can_inline_mul (Range1 , Range2 , MMod ) of
3697+ true ->
3698+ % Both operands in registers: strip tags, extract value, multiply
3699+ {MSt1 , Reg1 } = MMod :move_to_native_register (MSt0 , Arg1 ),
3700+ {MSt2 , Reg2 } = MMod :move_to_native_register (MSt1 , Arg2 ),
3701+ % Strip tag from Reg1: value1 << 4
3702+ {MSt3 , Reg1 } = MMod :and_ (MSt2 , {free , Reg1 }, bnot (? TERM_IMMED_TAG_MASK )),
3703+ % Strip tag from Reg2 and shift right by 4 to get raw value2
3704+ {MSt4 , Reg2 } = MMod :and_ (MSt3 , {free , Reg2 }, bnot (? TERM_IMMED_TAG_MASK )),
3705+ {MSt5 , Reg2 } = MMod :shift_right (MSt4 , {free , Reg2 }, 4 ),
3706+ % Multiply: (value1 << 4) * value2 = (value1 * value2) << 4
3707+ MSt6 = MMod :mul (MSt5 , Reg1 , Reg2 ),
3708+ % Add tag back
3709+ MSt7 = MMod :or_ (MSt6 , Reg1 , ? TERM_INTEGER_TAG ),
3710+ MSt8 = MMod :move_to_vm_register (MSt7 , Reg1 , Dest ),
3711+ MMod :free_native_registers (MSt8 , [Reg1 , Reg2 , Dest ]);
3712+ false ->
3713+ op_gc_bif2_default (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest )
3714+ end .
3715+
3716+ % Check if left shift can be inlined based on type range and shift amount
3717+ can_inline_bsl (Range1 , ShiftAmount , MMod ) ->
3718+ {MinSafe , MaxSafe } = small_integer_bounds (MMod ),
3719+ case Range1 of
3720+ {Min1 , Max1 } when
3721+ is_integer (Min1 ),
3722+ is_integer (Max1 ),
3723+ ShiftAmount >= 0
3724+ ->
3725+ MinResult = Min1 bsl ShiftAmount ,
3726+ MaxResult = Max1 bsl ShiftAmount ,
3727+ MinResult >= MinSafe andalso MaxResult =< MaxSafe ;
3728+ _ ->
3729+ false
3730+ end .
3731+
3732+ % Optimized bsl with compile-time range checking
3733+ op_gc_bif2_bsl (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest , Range1 , ShiftAmount ) ->
3734+ case can_inline_bsl (Range1 , ShiftAmount , MMod ) of
3735+ true ->
3736+ case ShiftAmount of
3737+ 0 ->
3738+ % No shift - just copy
3739+ {MSt1 , Reg } = MMod :move_to_native_register (MSt0 , Arg1 ),
3740+ MSt2 = MMod :move_to_vm_register (MSt1 , Reg , Dest ),
3741+ MMod :free_native_registers (MSt2 , [Reg , Dest ]);
3742+ _ ->
3743+ % Strip tag, shift left, re-tag
3744+ {MSt1 , Reg } = MMod :move_to_native_register (MSt0 , Arg1 ),
3745+ {MSt2 , Reg } = MMod :and_ (MSt1 , {free , Reg }, bnot (? TERM_IMMED_TAG_MASK )),
3746+ MSt3 = MMod :shift_left (MSt2 , Reg , ShiftAmount ),
3747+ MSt4 = MMod :or_ (MSt3 , Reg , ? TERM_INTEGER_TAG ),
3748+ MSt5 = MMod :move_to_vm_register (MSt4 , Reg , Dest ),
3749+ MMod :free_native_registers (MSt5 , [Reg , Dest ])
3750+ end ;
3751+ false ->
3752+ op_gc_bif2_default (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest )
3753+ end .
3754+
3755+ % Check if right shift can be inlined
3756+ % Only safe for non-negative inputs (the generated native code uses logical
3757+ % shift right, which does not preserve sign for negative values)
3758+ can_inline_bsr (Range1 , ShiftAmount , MMod ) ->
3759+ {_MinSafe , MaxSafe } = small_integer_bounds (MMod ),
3760+ % Ensure (ShiftAmount + 4) does not exceed register width
3761+ % (would be undefined behavior in native shift)
3762+ WordBits = MMod :word_size () * 8 ,
3763+ case Range1 of
3764+ {Min1 , Max1 } when
3765+ is_integer (Min1 ),
3766+ is_integer (Max1 ),
3767+ Min1 >= 0 ,
3768+ ShiftAmount >= 0 ,
3769+ ShiftAmount + 4 < WordBits
3770+ ->
3771+ % Non-negative input: right shift can only reduce magnitude
3772+ Max1 =< MaxSafe ;
3773+ _ ->
3774+ false
3775+ end .
3776+
3777+ % Optimized bsr with compile-time range checking
3778+ op_gc_bif2_bsr (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest , Range1 , ShiftAmount ) ->
3779+ case can_inline_bsr (Range1 , ShiftAmount , MMod ) of
3780+ true ->
3781+ case ShiftAmount of
3782+ 0 ->
3783+ % No shift - just copy
3784+ {MSt1 , Reg } = MMod :move_to_native_register (MSt0 , Arg1 ),
3785+ MSt2 = MMod :move_to_vm_register (MSt1 , Reg , Dest ),
3786+ MMod :free_native_registers (MSt2 , [Reg , Dest ]);
3787+ _ ->
3788+ % For non-negative values: shift right by (S+4), shift left by 4, re-tag.
3789+ % This avoids a separate tag-stripping instruction: the combined
3790+ % shift (S+4) removes both the 4 tag bits and applies the S-bit
3791+ % shift in one operation. The tag bits get shifted away since S+4 >= 5.
3792+ {MSt1 , Reg } = MMod :move_to_native_register (MSt0 , Arg1 ),
3793+ {MSt2 , Reg } = MMod :shift_right (MSt1 , {free , Reg }, ShiftAmount + 4 ),
3794+ MSt3 = MMod :shift_left (MSt2 , Reg , 4 ),
3795+ MSt4 = MMod :or_ (MSt3 , Reg , ? TERM_INTEGER_TAG ),
3796+ MSt5 = MMod :move_to_vm_register (MSt4 , Reg , Dest ),
3797+ MMod :free_native_registers (MSt5 , [Reg , Dest ])
3798+ end ;
3799+ false ->
3800+ op_gc_bif2_default (MMod , MSt0 , FailLabel , Live , Bif , Arg1 , Arg2 , Dest )
3801+ end .
3802+
35903803% Helper to unwrap typed arguments
35913804unwrap_typed ({typed , Arg , _Type }) -> Arg ;
35923805unwrap_typed (Arg ) -> Arg .
0 commit comments