From 5564208939793ee4e81a34b79cd8a218daabd351 Mon Sep 17 00:00:00 2001 From: Nelson Vides Date: Mon, 13 Apr 2026 13:25:54 +0200 Subject: [PATCH] beam_bounds: Tighten bounds for band, bor, and bxor Add fallback clauses for bitwise operations when operand ranges exceed NUM_BITS or involve sign combinations not previously handled. Values that exceed NUM_BITS are widened to +inf/-inf via a new cap/1 helper to keep bounds within the representable range. For band: - One non-negative operand: result in [0, cap(B)]. - Both strictly negative: result in [-inf, min(B, D)]. - Both straddling zero: result in [-inf, cap(max(B, D))]. - Both non-negative exceeding NUM_BITS: [0, cap(min(B, D))]. For bor: - Both non-negative exceeding NUM_BITS: [0, +inf]. - Both non-positive exceeding NUM_BITS: [-inf, 0]. For bxor: - Both non-negative exceeding NUM_BITS: [0, +inf]. - One strictly negative, one non-negative: [-inf, -1]. These tighter bounds enable the compiler to eliminate dead branches in code that performs comparisons after bitwise operations. For example, given: %% bxor with one negative, one non-negative: result is always negative. bxor_negative(X, Y) when is_integer(X), X >= 0, is_integer(Y), Y < 0 -> Z = X bxor Y, case Z < 0 of true -> negative; false -> non_negative end. %% band with both negative: result is always negative. band_neg(X, Y) when is_integer(X), X < 0, is_integer(Y), Y < 0 -> Z = X band Y, case Z < 0 of true -> negative; false -> non_negative end. The compiler previously emitted: {function, bxor_negative, 2, 4}. {label,3}. {line,[{location,"test_bounds_example.erl",16}]}. {func_info,{atom,test_bounds_example},{atom,bxor_negative},2}. {label,4}. {test,is_integer,{f,3},[{x,0}]}. {test,is_ge,{f,3},[{tr,{x,0},{t_integer,any}},{integer,0}]}. {test,is_integer,{f,3},[{x,1}]}. {test,is_ge,{f,3},[{integer,-1},{tr,{x,1},{t_integer,any}}]}. {line,[{location,"test_bounds_example.erl",18}]}. {gc_bif,'bxor', {f,0}, 2, [{tr,{x,0},{t_integer,{0,'+inf'}}}, {tr,{x,1},{t_integer,{'-inf',-1}}}], {x,0}}. {test,is_ge,{f,5},[{integer,-1},{tr,{x,0},{t_integer,any}}]}. {move,{atom,negative},{x,0}}. return. {label,5}. {move,{atom,non_negative},{x,0}}. return. {function, band_neg, 2, 7}. {label,6}. {line,[{location,"test_bounds_example.erl",25}]}. {func_info,{atom,test_bounds_example},{atom,band_neg},2}. {label,7}. {test,is_integer,{f,6},[{x,0}]}. {test,is_ge,{f,6},[{integer,-1},{tr,{x,0},{t_integer,any}}]}. {test,is_integer,{f,6},[{x,1}]}. {test,is_ge,{f,6},[{integer,-1},{tr,{x,1},{t_integer,any}}]}. {line,[{location,"test_bounds_example.erl",27}]}. {gc_bif,'band', {f,0}, 2, [{tr,{x,0},{t_integer,{'-inf',-1}}}, {tr,{x,1},{t_integer,{'-inf',-1}}}], {x,0}}. {test,is_ge,{f,8},[{integer,-1},{tr,{x,0},{t_integer,any}}]}. {move,{atom,negative},{x,0}}. return. {label,8}. {move,{atom,non_negative},{x,0}}. return. and it now emits: {function, bxor_negative, 2, 4}. {label,3}. {line,[{location,"test_bounds_example.erl",16}]}. {func_info,{atom,test_bounds_example},{atom,bxor_negative},2}. {label,4}. {test,is_integer,{f,3},[{x,0}]}. {test,is_ge,{f,3},[{tr,{x,0},{t_integer,any}},{integer,0}]}. {test,is_integer,{f,3},[{x,1}]}. {test,is_ge,{f,3},[{integer,-1},{tr,{x,1},{t_integer,any}}]}. {move,{atom,negative},{x,0}}. return. {function, band_neg, 2, 6}. {label,5}. {line,[{location,"test_bounds_example.erl",25}]}. {func_info,{atom,test_bounds_example},{atom,band_neg},2}. {label,6}. {test,is_integer,{f,5},[{x,0}]}. {test,is_ge,{f,5},[{integer,-1},{tr,{x,0},{t_integer,any}}]}. {test,is_integer,{f,5},[{x,1}]}. {test,is_ge,{f,5},[{integer,-1},{tr,{x,1},{t_integer,any}}]}. {move,{atom,negative},{x,0}}. return. --- lib/compiler/src/beam_bounds.erl | 55 +++++++++++++++++-- lib/compiler/test/beam_bounds_SUITE.erl | 73 ++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 6 deletions(-) diff --git a/lib/compiler/src/beam_bounds.erl b/lib/compiler/src/beam_bounds.erl index a85044604c81..ec43bd5b3dc9 100644 --- a/lib/compiler/src/beam_bounds.erl +++ b/lib/compiler/src/beam_bounds.erl @@ -147,16 +147,38 @@ bounds('rem', R1, R2) -> rem_bounds(R1, R2); bounds('band', R1, R2) -> case {R1,R2} of - {{A,B}, {C,D}} when A bsr ?NUM_BITS =:= 0, A >= 0, - C bsr ?NUM_BITS =:= 0, C >= 0, + {{A,B}, {C,D}} when is_integer(A), A >= 0, A bsr ?NUM_BITS =:= 0, + is_integer(C), C >= 0, C bsr ?NUM_BITS =:= 0, is_integer(B), is_integer(D) -> Min = min_band(A, B, C, D), Max = max_band(A, B, C, D), {Min,Max}; - {_, {C,D}} when is_integer(C), C >= 0 -> - {0,D}; + {{A,B}, {C,D}} when is_integer(A), A >= 0, + is_integer(C), C >= 0 -> + %% Both non-negative, but some bounds exceed ?NUM_BITS. + %% x band y =< min(x, y). + {0, cap(inf_min(B, D))}; + {{_,B}, {_,D}} when is_integer(B), B < 0, + is_integer(D), D < 0 -> + %% Both strictly negative. Result is always <= min(B, D). + %% When min(B,D) exceeds NUM_BITS, widen to -1 (least + %% negative), not '-inf'. + Max = case inf_min(B, D) of + M when is_integer(M), abs(M) bsr ?NUM_BITS =/= 0 -> -1; + M -> M + end, + {'-inf', Max}; {{A,B}, _} when is_integer(A), A >= 0 -> - {0,B}; + %% First non-negative. Result is non-negative, bounded by the first operand. + {0, cap(B)}; + {_, {C,D}} when is_integer(C), C >= 0 -> + %% Second non-negative. Result is non-negative, bounded by the second operand. + {0, cap(D)}; + {{_,B}, {_,D}} when is_integer(B), B >= 0, + is_integer(D), D >= 0 -> + %% Both ranges straddle zero with non-negative upper + %% bounds. The upper bound of band cannot exceed max(B, D). + {'-inf', cap(inf_max(B, D))}; {_, _} -> any end; @@ -169,6 +191,14 @@ bounds('bor', R1, R2) -> Min = min_bor(A, B, C, D), Max = max_bor(A, B, C, D), normalize({Min,Max}); + {{A,_}, {C,_}} when is_integer(A), A >= 0, + is_integer(C), C >= 0 -> + %% Both non-negative, but some bounds exceed ?NUM_BITS. + {0, '+inf'}; + {{_,B}, {_,D}} when is_integer(B), B =< 0, + is_integer(D), D =< 0 -> + %% Both non-positive, but some bounds exceed ?NUM_BITS. + {'-inf', 0}; {_, _} -> any end; @@ -179,6 +209,15 @@ bounds('bxor', R1, R2) -> is_integer(B), is_integer(D) -> Max = max_bxor(A, B, C, D), {0,Max}; + {{A,_}, {C,_}} when is_integer(A), A >= 0, + is_integer(C), C >= 0 -> + %% Both non-negative, but some bounds exceed ?NUM_BITS. + {0, '+inf'}; + {{A,B}, {C,D}} when (is_integer(B) andalso B < 0 andalso is_integer(C) andalso C >= 0) orelse + (is_integer(A) andalso A >= 0 andalso is_integer(D) andalso D < 0) -> + %% One strictly negative, other non-negative. + %% XOR of different sign bits always yields negative. + {'-inf', -1}; {_, _} -> any end; @@ -548,6 +587,12 @@ inf_neg(N) -> -N. inf_add(Int, N) when is_integer(Int) -> Int + N; inf_add(Inf, _N) -> Inf. +%% Cap a value to '+inf' or '-inf' if it exceeds ?NUM_BITS. +cap(A) when A =:= '-inf'; A =:= '+inf' -> A; +cap(N) when is_integer(N), N >= 0, N bsr ?NUM_BITS =/= 0 -> '+inf'; +cap(N) when is_integer(N), N < 0, (-N) bsr ?NUM_BITS =/= 0 -> '-inf'; +cap(N) -> N. + inf_bsr('-inf', _S) -> '-inf'; inf_bsr('+inf', _S) -> diff --git a/lib/compiler/test/beam_bounds_SUITE.erl b/lib/compiler/test/beam_bounds_SUITE.erl index b1610f893e55..e57068b2aee9 100644 --- a/lib/compiler/test/beam_bounds_SUITE.erl +++ b/lib/compiler/test/beam_bounds_SUITE.erl @@ -191,8 +191,43 @@ band_bounds(_Config) -> {0,42} = beam_bounds:bounds('band', {0,42}, any), any = beam_bounds:bounds('band', {-1,1}, any), any = beam_bounds:bounds('band', any, {-10,0}), - any = beam_bounds:bounds('band', {-10,0}, {-1,10}), any = beam_bounds:bounds('band', {-20,-10}, {-1,10}), + {'-inf',10} = beam_bounds:bounds('band', {-10,0}, {-1,10}), + + %% band upper bound should be min(B, D), not just D or B. + %% x band y =< min(x, y) when both non-negative. + {0,5} = beam_bounds:bounds('band', {0,5}, {0,'+inf'}), + {0,5} = beam_bounds:bounds('band', {0,'+inf'}, {0,5}), + {0,10} = beam_bounds:bounds('band', {0,10}, {0,20}), + + %% One strictly non-negative, one strictly negative. + %% Result is bounded by [0, PositiveMax]. + {0,10} = beam_bounds:bounds('band', {0,10}, {-20,-5}), + {0,10} = beam_bounds:bounds('band', {-20,-5}, {0,10}), + + %% Both strictly negative. + %% Lower bound drops to '-inf', upper bound is min(B, D). + {'-inf',-10} = beam_bounds:bounds('band', {-20,-10}, {-15,-5}), + {'-inf',-10} = beam_bounds:bounds('band', {-15,-5}, {-20,-10}), + + %% Straddling zero. + %% Lower bound fails open. Upper bound is max of the positive bounds. + {'-inf',10} = beam_bounds:bounds('band', {-5,10}, {-20,5}), + {'-inf',20} = beam_bounds:bounds('band', {-10,20}, {-10,20}), + + %% Large bounds exceeding ?NUM_BITS. + %% min(Big, +inf) = Big, but Big exceeds ?NUM_BITS so widens to +inf. + Big = 1 bsl 512, + {0,'+inf'} = beam_bounds:bounds('band', {0,Big}, {0,'+inf'}), + {0,'+inf'} = beam_bounds:bounds('band', {0,'+inf'}, {0,Big}), + {'-inf',-1} = beam_bounds:bounds('band', {-Big,-1}, {-Big,-1}), + {'-inf',-1} = beam_bounds:bounds('band', {-Big,-Big-1}, {-Big,-Big}), + {0,'+inf'} = beam_bounds:bounds('band', {0, Big}, {-Big,Big}), + {0,'+inf'} = beam_bounds:bounds('band', {-Big, -Big}, {0, Big}), + + %% Large negative operand, small positive operand. + %% The small positive operand perfectly constrains the result. + {0,5} = beam_bounds:bounds('band', {0,5}, {-Big,-1}), ok. @@ -212,6 +247,20 @@ bor_bounds(_Config) -> {16,'+inf'} = beam_bounds:bounds('bor', {0,8}, {16,'+inf'}), {16,'+inf'} = beam_bounds:bounds('bor', {3,'+inf'}, {16,'+inf'}), + %% Both non-negative, some bounds exceeding ?NUM_BITS. + %% Bounds that exceed ?NUM_BITS are widened to +inf/-inf. + Big = 1 bsl 512, + {0,'+inf'} = beam_bounds:bounds('bor', {0,'+inf'}, {Big,Big}), + {0,'+inf'} = beam_bounds:bounds('bor', {0,Big}, {0,'+inf'}), + {0,'+inf'} = beam_bounds:bounds('bor', {0,Big}, {Big,Big}), + {0,'+inf'} = beam_bounds:bounds('bor', {Big,Big}, {Big,Big}), + + %% Both non-positive, some bounds exceeding ?NUM_BITS. + {'-inf',0} = beam_bounds:bounds('bor', {'-inf', 0}, {-Big,-Big}), + {'-inf',0} = beam_bounds:bounds('bor', {-Big,0}, {'-inf',0}), + {'-inf',0} = beam_bounds:bounds('bor', {-Big,0}, {-Big,0}), + {'-inf',0} = beam_bounds:bounds('bor', {-Big,-Big}, {-Big,0}), + ok. bxor_bounds(_Config) -> @@ -220,6 +269,28 @@ bxor_bounds(_Config) -> any = beam_bounds:bounds('bxor', {-10,0}, {-1,10}), any = beam_bounds:bounds('bxor', {-20,-10}, {-1,10}), + %% Both non-negative, at least one infinite upper bound, + %% with finite bounds exceeding ?NUM_BITS. + Big = 1 bsl 512, + {0,'+inf'} = beam_bounds:bounds('bxor', {0,'+inf'}, {Big,Big}), + {0,'+inf'} = beam_bounds:bounds('bxor', {0,Big}, {0,'+inf'}), + + %% Both non-negative, both finite, exceeding ?NUM_BITS. + {0,'+inf'} = beam_bounds:bounds('bxor', {0,Big}, {Big,Big}), + {0,'+inf'} = beam_bounds:bounds('bxor', {Big,Big}, {Big,Big}), + + %% One strictly negative, one strictly non-negative. + %% The result is guaranteed to be negative, so the upper bound is -1. + {'-inf',-1} = beam_bounds:bounds('bxor', {-Big,-1}, {0,Big}), + {'-inf',-1} = beam_bounds:bounds('bxor', {0,Big}, {-Big,-1}), + + %% One strictly negative, one strictly non-negative (with infinite inputs). + {'-inf',-1} = beam_bounds:bounds('bxor', {'-inf',-1}, {0,'+inf'}), + {'-inf',-1} = beam_bounds:bounds('bxor', {0,'+inf'}, {'-inf',-1}), + + %% Both finite constants exceeding ?NUM_BITS, signs mismatched. + {'-inf',-1} = beam_bounds:bounds('bxor', {-Big,-Big}, {Big,Big}), + ok. bnot_bounds(_Config) ->