From ff6208c33d3bb6107f47b1f8ed5f7de3543f4d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Mon, 27 Oct 2025 05:42:03 +0100 Subject: [PATCH 1/5] beam_ssa_type: Tighten bounds in the signatures pass The signatures pass is run once, doing a type analysis of all functions in the module to compute argument types and return types for all functions. This pass must be conservative regarding the range for certain operators. Consider this example: fact(N) when is_integer(N, 0, 10_000) -> fact(N, 1). fact(0, P) -> P; fact(N, P) -> fact(N - 1, P * N). To ensure convergence, the signatures pass will compute the range of the result of `N - 1` to be '-inf' to 9999, and `P * N` to be 1 to '+inf'. This commit improves the signatures pass by attempting to use a tighter range for the result of the `+`, `-`, `*`, and `bnot` operators. If the value range analysis converges, the tighter range is kept; otherwise, the more conservative range is used. For the example, the range for `N` will now be 0 to 10000. Solves #8157 --- lib/compiler/src/beam_call_types.erl | 8 +- lib/compiler/src/beam_ssa_opt.erl | 2 +- lib/compiler/src/beam_ssa_type.erl | 180 +++++++++++++++++++++++++-- 3 files changed, 176 insertions(+), 14 deletions(-) diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl index c982e2f4015c..f806de84e175 100644 --- a/lib/compiler/src/beam_call_types.erl +++ b/lib/compiler/src/beam_call_types.erl @@ -1280,10 +1280,14 @@ beam_bounds_type(Op, Type, [LHS, RHS]) -> get_range(LHS, RHS, Type) -> get_range(meet(LHS, Type), meet(RHS, Type)). -get_range(#t_float{}=LHS, #t_float{}=RHS) -> - {float, get_range(LHS), get_range(RHS)}; get_range(#t_integer{}=LHS, #t_integer{}=RHS) -> {integer, get_range(LHS), get_range(RHS)}; +get_range(#t_float{}=LHS, #t_float{}=RHS) -> + {float, get_range(LHS), get_range(RHS)}; +get_range(#t_float{}=LHS, RHS) -> + {float, get_range(LHS), get_range(RHS)}; +get_range(LHS, #t_float{}=RHS) -> + {float, get_range(LHS), get_range(RHS)}; get_range(LHS, RHS) -> {number, get_range(LHS), get_range(RHS)}. diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index d50350a10c36..27841f124499 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -272,6 +272,7 @@ module_passes(Opts) -> %% are repeated as required. repeated_passes(Opts) -> Ps = [?PASS(ssa_opt_live), + ?PASS(ssa_opt_sw), ?PASS(ssa_opt_is_between), ?PASS(ssa_opt_ne), ?PASS(ssa_opt_bs_create_bin), @@ -301,7 +302,6 @@ epilogue_module_passes(Opts) -> early_epilogue_passes(Opts) -> Ps = [?PASS(ssa_opt_type_finish), ?PASS(ssa_opt_float), - ?PASS(ssa_opt_sw), ?PASS(ssa_opt_no_reuse), ?PASS(ssa_opt_deoptimize_update_tuple)], passes_1(Ps, Opts). diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 7b1da625398b..2a098ea97b9d 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -133,11 +133,15 @@ opt_start_1([], _CommittedArgs, StMap, FuncDb, _MetaCache) -> %% [1] http://www.it.uu.se/research/group/hipe/papers/succ_types.pdf %% +-type uvs() :: #{beam_ssa:b_var() => {_,non_neg_integer()}}. + -record(sig_st, { wl = wl_new() :: worklist(), committed = #{} :: #{ func_id() => [type()] }, updates = #{} :: #{ func_id() => [type()] }, - meta_cache = #{} :: meta_cache()}). + meta_cache = #{} :: meta_cache(), + unstable = #{} :: #{beam_ssa:label() => uvs()}, + uvs = #{} :: uvs()}). signatures(StMap, FuncDb0) -> State0 = init_sig_st(StMap, FuncDb0), @@ -220,7 +224,15 @@ sig_function_1(Id, StMap, State0, FuncDb) -> Wl0 = State1#sig_st.wl, - {State, SuccTypes} = sig_bs(Linear, Ds, Ls, FuncDb, #{}, [], Meta, State2), + Unstable0 = State1#sig_st.unstable, + Uvs0 = maps:get(Id, Unstable0, #{}), + State3 = State2#sig_st{uvs=Uvs0}, + + {State4, SuccTypes} = sig_bs(Linear, Ds, Ls, FuncDb, #{}, [], Meta, State3), + + Uvs = State4#sig_st.uvs, + Unstable = Unstable0#{Id => Uvs}, + State = State4#sig_st{unstable=Unstable,uvs=#{}}, WlChanged = wl_changed(Wl0, State#sig_st.wl), #{ Id := #func_info{succ_types=SuccTypes0}=Entry0 } = FuncDb, @@ -307,12 +319,16 @@ sig_is([#b_set{op=make_fun,args=Args0,dst=Dst}=I0|Is], Ts = update_types(I, Ts0, Ds0), Ds = Ds0#{ Dst => I }, sig_is(Is, Ts, Ds, Ls, Fdb, Sub0, State); -sig_is([I0 | Is], Ts0, Ds0, Ls, Fdb, Sub0, State) -> - case simplify(I0, Ts0, Ds0, Ls, Sub0) of +sig_is([I0 | Is], Ts0, Ds0, Ls, Fdb, Sub0, State0) -> + Uvs0 = State0#sig_st.uvs, + case simplify(I0, Uvs0, Ts0, Ds0, Ls, Sub0) of {#b_set{}, Ts, Ds} -> + sig_is(Is, Ts, Ds, Ls, Fdb, Sub0, State0); + {#b_set{}, Ts, Ds, Uvs} -> + State = State0#sig_st{uvs=Uvs}, sig_is(Is, Ts, Ds, Ls, Fdb, Sub0, State); Sub when is_map(Sub) -> - sig_is(Is, Ts0, Ds0, Ls, Fdb, Sub, State) + sig_is(Is, Ts0, Ds0, Ls, Fdb, Sub, State0) end; sig_is([], Ts, Ds, _Ls, _Fdb, Sub, State) -> {Ts, Ds, Sub, State}. @@ -579,10 +595,13 @@ opt_is([#b_set{op=make_fun,args=Args0,dst=Dst}=I0|Is], Ds = Ds0#{ Dst => I }, opt_is(Is, Ts, Ds, Ls, Fdb, Sub0, Meta, [I|Acc]); opt_is([I0 | Is], Ts0, Ds0, Ls, Fdb, Sub0, Meta, Acc) -> - case simplify(I0, Ts0, Ds0, Ls, Sub0) of + case simplify(I0, none, Ts0, Ds0, Ls, Sub0) of {#b_set{}=I1, Ts, Ds} -> I = opt_anno_types(I1, Ts), opt_is(Is, Ts, Ds, Ls, Fdb, Sub0, Meta, [I | Acc]); + {#b_set{}=I1, Ts, Ds, _} -> + I = opt_anno_types(I1, Ts), + opt_is(Is, Ts, Ds, Ls, Fdb, Sub0, Meta, [I | Acc]); Sub when is_map(Sub) -> opt_is(Is, Ts0, Ds0, Ls, Fdb, Sub, Meta, Acc) end; @@ -1012,7 +1031,7 @@ simplify_terminator(#b_ret{arg=Arg,anno=Anno0}=Ret0, Ts, Ds, Sub) -> %% was redundant. %% -simplify(#b_set{op=phi,dst=Dst,args=Args0}=I0, Ts0, Ds0, Ls, Sub) -> +simplify(#b_set{op=phi,dst=Dst,args=Args0}=I0, _Uvs, Ts0, Ds0, Ls, Sub) -> %% Simplify the phi node by removing all predecessor blocks that no %% longer exists or no longer branches to this block. {Type, Args} = simplify_phi_args(Args0, Ls, Sub, none, []), @@ -1030,7 +1049,7 @@ simplify(#b_set{op=phi,dst=Dst,args=Args0}=I0, Ts0, Ds0, Ls, Sub) -> {I, Ts, Ds} end; simplify(#b_set{op={succeeded,Kind},args=[Arg],dst=Dst}=I, - Ts0, Ds0, _Ls, Sub) -> + _Uvs, Ts0, Ds0, _Ls, Sub) -> Type = case will_succeed(I, Ts0, Ds0, Sub) of yes -> beam_types:make_atom(true); no -> beam_types:make_atom(false); @@ -1056,7 +1075,7 @@ simplify(#b_set{op={succeeded,Kind},args=[Arg],dst=Dst}=I, Ds = Ds0#{ Dst => I }, {I, Ts, Ds} end; -simplify(#b_set{op=bs_match,dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) -> +simplify(#b_set{op=bs_match,dst=Dst,args=Args0}=I0, _Uvs, Ts0, Ds0, _Ls, Sub) -> Args = simplify_args(Args0, Ts0, Sub), I1 = I0#b_set{args=Args}, I2 = case {Args0,Args} of @@ -1075,7 +1094,7 @@ simplify(#b_set{op=bs_match,dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) -> Ds = Ds0#{ Dst => I }, {I, Ts, Ds}; simplify(#b_set{op=bs_create_bin=Op,dst=Dst,args=Args0,anno=Anno}=I0, - Ts0, Ds0, _Ls, Sub) -> + _Uvs, Ts0, Ds0, _Ls, Sub) -> Args = simplify_args(Args0, Ts0, Sub), case Args of @@ -1099,10 +1118,15 @@ simplify(#b_set{op=bs_create_bin=Op,dst=Dst,args=Args0,anno=Anno}=I0, Ds = Ds0#{ Dst => I }, {I, Ts, Ds} end; -simplify(#b_set{dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) -> +simplify(#b_set{dst=Dst,args=Args0}=I0, Uvs0, Ts0, Ds0, _Ls, Sub) -> Args = simplify_args(Args0, Ts0, Sub), I1 = beam_ssa:normalize(I0#b_set{args=Args}), case simplify(I1, Ts0, Ds0) of + #b_set{op={bif,Op}}=I when Op =:= '+'; Op =:= '-'; + Op =:= '*'; Op =:= 'bnot' -> + {Ts,Uvs} = update_arith_types(I, Ts0, Ds0, Uvs0), + Ds = Ds0#{ Dst => I }, + {I, Ts, Ds, Uvs}; #b_set{}=I -> Ts = update_types(I, Ts0, Ds0), Ds = Ds0#{ Dst => I }, @@ -1113,6 +1137,140 @@ simplify(#b_set{dst=Dst,args=Args0}=I0, Ts0, Ds0, _Ls, Sub) -> Sub#{ Dst => Var } end. +update_arith_types(#b_set{dst=Dst}=I, Ts0, Ds, UnstableVars0) -> + %% "Arith types" can be more exact, but can diverge if used for + %% computing the range for one of `+`, `-`, '*`, or `bnot` in a + %% recursive function. For example: + %% + %% len(L) -> len(L, 0). + %% + %% len([], N) -> N; + %% len([_|T], N) -> len(N + 1). + %% + %% The initial range for `N` will be {0,0}. The range after + %% evaluating `N + 1` when using arith types will be {1,1}, then + %% {2,2}, and so on forever. + %% + %% The conservative range calculation done by update_types/3 will + %% set the range to {1,'+inf'}. + %% + %% Arith types will work when the new range is not fed back to + %% the operation, or if there is some constraint that prevents + %% the range from growing forever. For example: + %% + %% intsum(N) when is_integer(N, 0, 1 bsl 59) -> + %% {sum,intsum(0, N, 0)}. + %% + %% intsum(I, N, Sum) when I < N -> + %% intsum(I + 1, N, Sum + I); + %% intsum(_, _, Sum) -> + %% Sum. + %% + case update_arith_types_1(I, Ts0, UnstableVars0) of + {any,UnstableVars} -> + %% The arithmetic type is either `any` or diverging. + Ts = update_types(I, Ts0, Ds), + {Ts,UnstableVars}; + {Type,UnstableVars} -> + %% The arithmetic type is stable. + Ts = Ts0#{Dst => Type}, + {Ts,UnstableVars} + end. + +update_arith_types_1(#b_set{op={bif,_}=Op,args=BifArgs}=I, + Ts0, UnstableVars0) -> + ArgTypes = concrete_types(BifArgs, Ts0), + case beam_call_types:arith_type(Op, ArgTypes) of + any -> + {any,UnstableVars0}; + #t_float{elements=any} -> + {any,UnstableVars0}; + #t_integer{elements=any} -> + {any,UnstableVars0}; + #t_number{elements=any} -> + {any,UnstableVars0}; + Type -> + case update_arith_types_safe(I, ArgTypes, Type, UnstableVars0) of + {safe,UnstableVars} -> + %% Safe (permanently or temporarily). + {Type,UnstableVars}; + {unsafe,UnstableVars} -> + %% This variable doesn't seem to converge to a + %% stable range. + {update_arith_types_2(I, ArgTypes),UnstableVars} + end + end. + +update_arith_types_2(#b_set{op={bif,'-'}=Op,args=[_,#b_literal{val=1}]}, + [#t_integer{elements={Min,_Max}}=ArgType|_]) + when is_integer(Min), Min > 0 -> + %% We have almost given up on this operation. As a final attempt, + %% subtract a number that will set the minimum value to 0. + Args = [ArgType,#t_integer{elements={Min,Min}}], + beam_call_types:arith_type(Op, Args); +update_arith_types_2(#b_set{}, _) -> + %% Fall back to using more conservative update_types/3 approach + %% (setting one end of the range to infinity). + any. + +update_arith_types_safe(#b_set{}, _ArgTypes, _Type, none) -> + %% Not running the signatures sub pass; it is always safe to + %% propagate types, because they will only be propagated within + %% the current function. + {safe,none}; +update_arith_types_safe(#b_set{dst=Dst}=I, ArgTypes, Type, UnstableVars0) -> + case UnstableVars0 of + #{Dst := {Type,_}} -> + %% No change since last time. + {safe,UnstableVars0}; + #{Dst := {_,0}} -> + %% The counter has run down. Give up on using the more + %% exact arith types. + {unsafe,UnstableVars0}; + #{Dst := {_,Count}} when is_integer(Count) -> + %% Try using this type. + UnstableVars = UnstableVars0#{Dst := {Type,Count-1}}, + {safe,UnstableVars}; + #{} -> + %% We have not seen this variable before. Initialize + %% a counter for the number of times to try. + Counter = init_counter(I, ArgTypes), + UnstableVars = UnstableVars0#{Dst => {Type,Counter}}, + {safe,UnstableVars} + end. + +init_counter(#b_set{op=Op}, ArgTypes) -> + case Op of + {bif,'+'} -> 64; + {bif,'-'} -> + Def = 64, + case ArgTypes of + [#t_integer{elements={_Min,Max}}, + #t_integer{elements={1,1}}] -> + %% Avoid passing zero because it is unlikely to + %% improve the range. + max(1, min(Def, Max)); + _ -> + Def + end; + {bif,'bnot'} -> 60; + {bif,'*'} -> + case ArgTypes of + [_,#t_integer{elements={_,Max}}] when Max > 0 -> + %% Use a conservative number of attempts to + %% avoid overflowing a small. + try floor(32.0 / math:log2(abs(Max))) of + Log -> + max(1, min(32, Log)) + catch + _:_ -> + 1 + end; + _ -> + 1 + end + end. + simplify(#b_set{op={bif,'band'},args=Args}=I, Ts, Ds) -> case normalized_types(Args, Ts) of [#t_integer{elements=R},#t_integer{elements={M,M}}] -> From 7eba72f2420a988289bead87a1f4925ad3b4ace1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Tue, 28 Oct 2025 08:15:51 +0100 Subject: [PATCH 2/5] beam_ssa_opt: Get rid of the opt_ranges subpass The `opt_ranges` subpass is no longer needed after the improvements made to the signatures pass in the previous commit. --- lib/compiler/src/beam_call_types.erl | 4 +- lib/compiler/src/beam_ssa_opt.erl | 6 +- lib/compiler/src/beam_ssa_type.erl | 167 +-------------------------- 3 files changed, 3 insertions(+), 174 deletions(-) diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl index f806de84e175..8bd68cdf6ab8 100644 --- a/lib/compiler/src/beam_call_types.erl +++ b/lib/compiler/src/beam_call_types.erl @@ -492,9 +492,7 @@ types(erlang, 'bnot', [_]) -> %% between the ranges calculated by the type pass and by %% beam_validator. %% - %% Therefore, don't attempt to calculate a range now. Save the - %% range calculation for the opt_ranges pass (arith_type/2), which - %% is only run once. + %% Therefore, don't attempt to calculate a range now. sub_unsafe(#t_integer{}, [#t_integer{}]); %% Fixed-type arithmetic diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 27841f124499..9c3f59862098 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -321,8 +321,7 @@ late_epilogue_passes(Opts) -> ?PASS(ssa_opt_get_tuple_element), ?PASS(ssa_opt_tail_literals), ?PASS(ssa_opt_trim_unreachable), - ?PASS(ssa_opt_unfold_literals), - ?PASS(ssa_opt_ranges)], + ?PASS(ssa_opt_unfold_literals)], passes_1(Ps, Opts). passes_1(Ps, Opts0) -> @@ -461,9 +460,6 @@ ssa_opt_merge_blocks({#opt_st{ssa=Blocks0}=St, FuncDb}) -> Blocks = beam_ssa:merge_blocks(RPO, Blocks0), {St#opt_st{ssa=Blocks}, FuncDb}. -ssa_opt_ranges({#opt_st{ssa=Blocks}=St, FuncDb}) -> - {St#opt_st{ssa=beam_ssa_type:opt_ranges(Blocks)}, FuncDb}. - %%% %%% Merges updates that cannot fail, for example two consecutive updates of the %%% same record. diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 2a098ea97b9d..22fe205d234d 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -30,7 +30,7 @@ -module(beam_ssa_type). -moduledoc false. --export([opt_start/2, opt_continue/4, opt_finish/3, opt_ranges/1]). +-export([opt_start/2, opt_continue/4, opt_finish/3]). -include("beam_ssa_opt.hrl"). -include("beam_types.hrl"). @@ -678,12 +678,6 @@ benefits_from_type_anno(is_tagged_tuple, _Args) -> true; benefits_from_type_anno(call, [#b_var{} | _]) -> true; -benefits_from_type_anno({float,convert}, _Args) -> - %% Note: The {float,convert} instruction does not exist when - %% the main type optimizer pass is run. It is created and - %% annotated by ssa_opt_float1 in beam_ssa_opt, and can also - %% be annotated by opt_ranges/1. - true; benefits_from_type_anno(get_map_element, _Args) -> true; benefits_from_type_anno(has_map_field, _Args) -> @@ -821,165 +815,6 @@ opt_finish_1([Arg | Args], [TypeMap | TypeMaps], Acc0) -> opt_finish_1([], [], Acc) -> Acc. -%%% -%%% This sub pass is run once after the main type sub pass -%%% to annotate more instructions with integer ranges. -%%% -%%% The main type sub pass annotates certain instructions with -%%% their types to help the JIT generate better code. -%%% -%%% Example: -%%% -%%% foo(N0) -> -%%% N1 = N0 band 3, -%%% N = N1 + 1, % N1 is in 0..3 -%%% element(N, -%%% {zero,one,two,three}). -%%% -%%% The main type pass is able to figure out the range for `N1` but -%%% not for `N`. The reason is that the type pass iterates until it -%%% reaches a fixpoint. To guarantee that it will converge, ranges for -%%% results must only be calculated for operations that retain or -%%% shrink the ranges of their arguments. -%%% -%%% Therefore, to ensure convergence, the main type pass can only -%%% safely calculate ranges for results of operations such as `and`, -%%% `bsr`, and `rem`, but not for operations such as `+`, '-', '*', -%%% and `bsl`. -%%% -%%% This sub pass will start from the types found in the annotations -%%% and propagate them forward through arithmetic instructions within -%%% the same function. -%%% -%%% For the example, this sub pass adds a new annotation for `N`: -%%% -%%% foo(N0) -> -%%% N1 = N0 band 3, -%%% N = N1 + 1, % N1 is in 0..3 -%%% element(N, % N is in 1..4 -%%% {zero,one,two,three}). -%%% -%%% With a known range and known tuple size, the JIT is able to remove -%%% all range checks for the `element/2` instruction. -%%% - --spec opt_ranges(Blocks0) -> Blocks when - Blocks0 :: beam_ssa:block_map(), - Blocks :: beam_ssa:block_map(). - -opt_ranges(Blocks) -> - RPO = beam_ssa:rpo(Blocks), - Tss = #{0 => #{}, ?EXCEPTION_BLOCK => #{}}, - ranges(RPO, Tss, Blocks). - -ranges([L|Ls], Tss0, Blocks0) -> - #b_blk{is=Is0} = Blk0 = map_get(L, Blocks0), - Ts0 = map_get(L, Tss0), - {Is,Ts} = ranges_is(Is0, Ts0, []), - Blk = Blk0#b_blk{is=Is}, - Blocks = Blocks0#{L := Blk}, - Tss = ranges_successors(beam_ssa:successors(Blk), Ts, Tss0), - ranges(Ls, Tss, Blocks); -ranges([], _Tss, Blocks) -> Blocks. - -ranges_is([#b_set{op=Op,args=Args}=I0|Is], Ts0, Acc) -> - case benefits_from_type_anno(Op, Args) of - false -> - ranges_is(Is, Ts0, [I0|Acc]); - true -> - I = update_anno_types(I0, Ts0), - Ts = ranges_propagate_types(I, Ts0), - ranges_is(Is, Ts, [I|Acc]) - end; -ranges_is([], Ts, Acc) -> - {reverse(Acc),Ts}. - -ranges_successors([?EXCEPTION_BLOCK|Ls], Ts, Tss) -> - ranges_successors(Ls, Ts, Tss); -ranges_successors([L|Ls], Ts0, Tss0) -> - case Tss0 of - #{L := Ts1} -> - Ts = join_types(Ts0, Ts1), - Tss = Tss0#{L := Ts}, - ranges_successors(Ls, Ts0, Tss); - #{} -> - Tss = Tss0#{L => Ts0}, - ranges_successors(Ls, Ts0, Tss) - end; -ranges_successors([], _, Tss) -> Tss. - -ranges_propagate_types(#b_set{anno=Anno,op={bif,_}=Op,args=Args,dst=Dst}, Ts) -> - case Anno of - #{arg_types := ArgTypes0} -> - ArgTypes = ranges_get_arg_types(Args, 0, ArgTypes0), - case beam_call_types:arith_type(Op, ArgTypes) of - any -> Ts; - T -> Ts#{Dst => T} - end; - #{} -> - Ts - end; -ranges_propagate_types(_, Ts) -> Ts. - -ranges_get_arg_types([#b_var{}|As], Index, ArgTypes) -> - case ArgTypes of - #{Index := Type} -> - [Type|ranges_get_arg_types(As, Index + 1, ArgTypes)]; - #{} -> - [any|ranges_get_arg_types(As, Index + 1, ArgTypes)] - end; -ranges_get_arg_types([#b_literal{val=Value}|As], Index, ArgTypes) -> - Type = beam_types:make_type_from_value(Value), - [Type|ranges_get_arg_types(As, Index + 1, ArgTypes)]; -ranges_get_arg_types([], _, _) -> []. - -update_anno_types(#b_set{anno=Anno,args=Args}=I, Ts) -> - ArgTypes1 = case Anno of - #{arg_types := ArgTypes0} -> ArgTypes0; - #{} -> #{} - end, - ArgTypes = update_anno_types_1(Args, Ts, 0, ArgTypes1), - case Anno of - #{arg_types := ArgTypes} -> - I; - #{} when map_size(ArgTypes) =/= 0 -> - I#b_set{anno=Anno#{arg_types => ArgTypes}}; - #{} -> - I - end. - -update_anno_types_1([#b_var{}=V|As], Ts, Index, ArgTypes) -> - T0 = case ArgTypes of - #{Index := T00} -> T00; - #{} -> any - end, - T1 = case Ts of - #{V := T11} -> T11; - #{} -> any - end, - case beam_types:meet(T0, T1) of - any -> - update_anno_types_1(As, Ts, Index + 1, ArgTypes); - none -> - %% This instruction will never be reached. This happens when - %% compiling code such as the following: - %% - %% f(X) when is_integer(X), 0 =< X, X < 64 -> - %% (X = bnot X) + 1. - %% - %% The main type optimization sub pass will not find out - %% that `(X = bnot X)` will never succeed and that the `+` - %% operator is never executed, but this sub pass will. - %% This happens very rarely; therefore, don't bother removing - %% the unreachable instruction. - update_anno_types_1(As, Ts, Index + 1, ArgTypes); - T -> - update_anno_types_1(As, Ts, Index + 1, ArgTypes#{Index => T}) - end; -update_anno_types_1([_|As], Ts, Index, ArgTypes) -> - update_anno_types_1(As, Ts, Index + 1, ArgTypes); -update_anno_types_1([], _, _, ArgTypes) -> ArgTypes. - %%% %%% Optimization helpers %%% From f7549acc4e54d3cd310be0ba8a11d42ee6aed766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Fri, 13 Mar 2026 15:53:12 +0100 Subject: [PATCH 3/5] beam_bounds: Tighten bounds for logical operations Handle negative bounds for `bor` and `bxor`, and simplify handling of `band`. Also simplify all logical operators by canonicalizing the input ranges: converting `any` to `{'-inf','+inf'} and replacing too large integers with infinity. This reduces the number of special cases. Thanks to Nelson Vides for the idea to replace large integers with infinity. --- lib/compiler/src/beam_bounds.erl | 409 ++++++++++++++++----- lib/compiler/test/beam_bounds_SUITE.erl | 469 ++++++++++++++++++++---- 2 files changed, 712 insertions(+), 166 deletions(-) diff --git a/lib/compiler/src/beam_bounds.erl b/lib/compiler/src/beam_bounds.erl index a85044604c81..91138ff75e53 100644 --- a/lib/compiler/src/beam_bounds.erl +++ b/lib/compiler/src/beam_bounds.erl @@ -48,13 +48,9 @@ -spec bounds(op(), range()) -> range_result(). bounds('bnot', R0) -> - case R0 of - {A,B} -> - R = {inf_add(inf_neg(B), -1), inf_add(inf_neg(A), -1)}, - normalize(R); - _ -> - any - end; + [A,B] = canonical_arg(R0), + R = {inf_add(inf_neg(B), -1), inf_add(inf_neg(A), -1)}, + normalize(R); bounds(abs, R) -> case R of {A,B} when is_integer(A), is_integer(B) -> @@ -146,68 +142,29 @@ bounds('div', R1, R2) -> 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, - 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}, _} when is_integer(A), A >= 0 -> - {0,B}; - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + normalize(min_max_band(A, B, C, D)); bounds('bor', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when A =:= '-inf' orelse abs(A) bsr ?NUM_BITS =:= 0, - C =:= '-inf' orelse abs(C) bsr ?NUM_BITS =:= 0, - B =:= '+inf' orelse abs(B) bsr ?NUM_BITS =:= 0, - D =:= '+inf' orelse abs(D) bsr ?NUM_BITS =:= 0 -> - Min = min_bor(A, B, C, D), - Max = max_bor(A, B, C, D), - normalize({Min,Max}); - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + normalize(min_max_bor(A, B, C, D)); bounds('bxor', 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, - is_integer(B), is_integer(D) -> - Max = max_bxor(A, B, C, D), - {0,Max}; - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + normalize(min_max_bxor(A, B, C, D)); bounds('bsr', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when is_integer(C), C >= 0 -> - Min = inf_min(inf_bsr(A, C), inf_bsr(A, D)), - Max = inf_max(inf_bsr(B, C), inf_bsr(B, D)), - normalize({Min,Max}); - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + Min = inf_min(inf_bsr(A, C), inf_bsr(A, D)), + Max = inf_max(inf_bsr(B, C), inf_bsr(B, D)), + normalize({Min,Max}); bounds('bsl', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when A =:= '-inf' orelse abs(A) bsr ?NUM_BITS =:= 0, - B =:= '+inf' orelse abs(B) bsr ?NUM_BITS =:= 0 -> - Min = inf_min(inf_bsl(A, C), inf_bsl(A, D)), - Max = inf_max(inf_bsl(B, C), inf_bsl(B, D)), - normalize({Min,Max}); - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + Min = inf_min(inf_bsl(A, C), inf_bsl(A, D)), + Max = inf_max(inf_bsl(B, C), inf_bsl(B, D)), + normalize({Min,Max}); bounds(max, R1, R2) -> - {A,B} = expand(R1), - {C,D} = expand(R2), + [A,B,C,D] = canonical_args(R1, R2), normalize({inf_max(A, C),inf_max(B, D)}); bounds(min, R1, R2) -> - {A,B} = expand(R1), - {C,D} = expand(R2), + [A,B,C,D] = canonical_args(R1, R2), normalize({inf_min(A, C),inf_min(B, D)}). -spec relop(relop(), range(), range()) -> bool_result(). @@ -338,6 +295,99 @@ rem_bounds({A,B}, _) -> rem_bounds(_, _) -> any. +-if(false). +min_max_band(A, B, C, D) -> + {Min,Max} = min_max_bor(inf_bnot(B), inf_bnot(A), + inf_bnot(D), inf_bnot(C)), + {inf_bnot(Max),inf_bnot(Min)}. + +-else. +%% For testing, we calculate the bounds both directly and by using the +%% `bor` bounds calculation. It is the intention to remove this code +%% in separate commit after testing (thus keeping it in the git +%% history but not in the compiler code). +min_max_band(A, B, C, D) -> + Res = old_min_max_band(A, B, C, D), + case new_min_max_band(A, B, C, D) of + Res -> + Res; + Other -> + error({A,B,C,D,Res,Other}) + end. + +new_min_max_band(A, B, C, D) -> + {Min,Max} = min_max_bor(inf_bnot(B), inf_bnot(A), + inf_bnot(D), inf_bnot(C)), + {inf_bnot(Max),inf_bnot(Min)}. + +old_min_max_band(A, B, C, D) -> + case inf_le(A, C) of + true -> + do_min_max_band(A, B, C, D); + false -> + do_min_max_band(C, D, A, B) + end. + +do_min_max_band(A, B, C, D) when is_integer(A), + is_integer(B), + is_integer(C), + is_integer(D) -> + case {inf_sign(A),inf_sign(B),inf_sign(C),inf_sign(D)} of + {'-','-','-','-'} -> + {min_band(A, B, C, D), + max_band(A, B, C, D)}; + {'-','-','-','+'} -> + {min_band(A, B, C, D), + max_band(A, B, C, D)}; + {'-','-','+','+'} -> + {min_band(A, B, C, D), + max_band(A, B, C, D)}; + {'-','+','-','-'} -> + {min_band(A, B, C, D), + max_band(0, B, C, D)}; + {'-','+','-','+'} -> + {min_band(A, -1, C, -1), + inf_max(B, D)}; + {'-','+','+','+'} -> + {0, D}; + {'+','+','+','+'} -> + {min_band(A, B, C, D), + max_band(A, B, C, D)} + end; +do_min_max_band(A, B, C, D) -> + case {inf_sign(A),inf_sign(B),inf_sign(C),inf_sign(D)} of + {'-','-','-','-'} -> + {'-inf',max_band(A, B, C, D)}; + {'-','-','-','+'} when is_integer(A), + is_integer(C) -> + {min_band(A, B, C, D),D}; + {'-','-','-','+'} -> + {'-inf',D}; + {'-','-','+','+'} when is_integer(D) -> + {0,max_band(A, B, C, D)}; + {'-','-','+','+'} when is_integer(A) -> + {min_band(A, B, C, D),D}; + {'-','-','+','+'} -> + {0,D}; + {'-','+','-','-'} when is_integer(A), + is_integer(C) -> + {min_band(A, B, C, D),B}; + {'-','+','-','-'} when is_integer(B) -> + {'-inf',max_band(A, B, C, D)}; + {'-','+','-','-'} -> + {'-inf',B}; + {'-','+','-','+'} when is_integer(A), + is_integer(C) -> + {min_band(A, B, C, D),inf_max(B, D)}; + {'-','+','-','+'} -> + {'-inf',inf_max(B, D)}; + {'-','+','+','+'} -> + {0,D}; + {'+','+','+','+'} -> + {min_band(A, B, C, D), + inf_min(B, D)} + end. + min_band(A, B, C, D) -> M = 1 bsl (upper_bit(A bor C) + 1), min_band(A, B, C, D, M). @@ -347,14 +397,16 @@ min_band(A, _B, C, _D, 0) -> min_band(A, B, C, D, M) -> if (bnot A) band (bnot C) band M =/= 0 -> - case (A bor M) band -M of - NewA when NewA =< B -> + NewA = (A bor M) band -M, + case inf_le(NewA, B) of + true -> min_band(NewA, B, C, D, 0); - _ -> - case (C bor M) band -M of - NewC when NewC =< D -> + false -> + NewC = (C bor M) band -M, + case inf_le(NewC, D) of + true -> min_band(A, B, NewC, D, 0); - _ -> + false -> min_band(A, B, C, D, M bsr 1) end end; @@ -371,28 +423,64 @@ max_band(_A, B, _C, D, 0) -> max_band(A, B, C, D, M) -> if B band (bnot D) band M =/= 0 -> - case (B band (bnot M)) bor (M - 1) of - NewB when NewB >= A -> + NewB = (B band (bnot M)) bor (M - 1), + case inf_ge(NewB, A) of + true -> max_band(A, NewB, C, D, 0); - _ -> + false -> max_band(A, B, C, D, M bsr 1) end; (bnot B) band D band M =/= 0 -> - case (D band (bnot M)) bor (M - 1) of - NewD when NewD >= C -> + NewD = (D band (bnot M)) bor (M - 1), + case inf_ge(NewD, C) of + true -> max_band(A, B, C, NewD, 0); - _ -> + false -> max_band(A, B, C, D, M bsr 1) end; true -> max_band(A, B, C, D, M bsr 1) end. +-endif. -min_bor(A, B, C, D) -> - case inf_lt(inf_min(A, C), 0) of +min_max_bor(A, B, C, D) -> + case inf_le(A, C) of true -> - '-inf'; + do_min_max_bor(A, B, C, D); false -> + do_min_max_bor(C, D, A, B) + end. + +do_min_max_bor(A, B, C, D) -> + case {inf_sign(A),inf_sign(B),inf_sign(C),inf_sign(D)} of + {'-','-','-','-'} -> + {min_bor(A, B, C, D), + max_bor(A, B, C, D)}; + {'-','-','-','+'} -> + {A, -1}; + {'-','-','+','+'} -> + {min_bor(A, B, C, D), + max_bor(A, B, C, D)}; + {'-','+','-','-'} -> + {C, -1}; + {'-','+','-','+'} -> + {inf_min(A, C), max_bor(0, B, 0, D)}; + {'-','+','+','+'} -> + {min_bor(A, -1, C, D), + max_bor(A, B, C, D)}; + {'+','+','+','+'} -> + {min_bor(A, B, C, D), + max_bor(A, B, C, D)} + end. + +min_bor('-inf', B, C, D) when is_integer(C), C < 0 -> + M = 1 bsl upper_bit(C), + min_bor(0, B, C, D, M); +min_bor(A, B, C, D) -> + case inf_min(A, C) of + '-inf' -> + '-inf'; + _ -> M = 1 bsl upper_bit(A bxor C), min_bor(A, B, C, D, M) end. @@ -419,20 +507,20 @@ min_bor(A, B, C, D, M) -> min_bor(A, B, C, D, M bsr 1) end. -max_bor(A0, B, C0, D) -> - A = inf_max(A0, 0), - C = inf_max(C0, 0), - case inf_max(B, D) of +max_bor(A, B0, C, D0) -> + {Intersection,B,D} = + case {B0,D0} of + {_,'+inf'} when B0 < 0 -> + {B0,B0,-1}; + {_,_} when is_integer(B0), is_integer(D0) -> + {B0 band D0,B0,D0}; + {_,_} -> + {'+inf',B0,D0} + end, + case Intersection of '+inf' -> '+inf'; - Max when Max < 0 -> - %% Both B and D are negative. The intersection would be - %% infinite. - -1; _ -> - %% At least one of B and D are positive. The intersection - %% has a finite size. - Intersection = B band D, M = 1 bsl upper_bit(Intersection), max_bor(Intersection, A, B, C, D, M) end. @@ -442,14 +530,17 @@ max_bor(_Intersection, _A, B, _C, D, 0) -> max_bor(Intersection, A, B, C, D, M) -> if Intersection band M =/= 0 -> - case (B - M) bor (M - 1) of - NewB when NewB >= A -> + NewM = M - 1, + NewB = (B - M) bor NewM, + case inf_ge(NewB, A) of + true -> max_bor(Intersection, A, NewB, C, D, 0); - _ -> - case (D - M) bor (M - 1) of - NewD when NewD >= C -> + false -> + NewD = (D - M) bor NewM, + case inf_ge(NewD, C) of + true -> max_bor(Intersection, A, B, C, NewD, 0); - _ -> + false -> max_bor(Intersection, A, B, C, D, M bsr 1) end end; @@ -457,6 +548,89 @@ max_bor(Intersection, A, B, C, D, M) -> max_bor(Intersection, A, B, C, D, M bsr 1) end. +min_max_bxor(A, B, C, D) -> + case inf_le(A, C) of + true -> + do_min_max_bxor(A, B, C, D); + false -> + do_min_max_bxor(C, D, A, B) + end. + +do_min_max_bxor(A, B, C, D) when is_integer(A), + is_integer(B), + is_integer(C), + is_integer(D) -> + case {inf_sign(A),inf_sign(B),inf_sign(C),inf_sign(D)} of + {'-','-','-','-'} -> + {min_bxor(A, B, C, D), + max_bxor(A, B, C, D)}; + {'-','-','-','+'} -> + %% Bounds are not tight. + MinMin = min(A, C), + Max = max(bnot MinMin, D), + {min_bxor(MinMin, -1, 0, D), + max_bxor(0, Max, 0, Max)}; + {'-','-','+','+'} -> + {min_bxor(A, B, C, D), + max_bxor(A, B, C, D)}; + {'-','+','-','-'} -> + %% Bounds are not tight. + MinMin = min(A, C), + Max = max(bnot MinMin, B), + {min_bxor(MinMin, -1, 0, B), + max_bxor(0, Max, 0, Max)}; + {'-','+','-','+'} -> + %% Bounds are not tight. + MinMin = min(A, C), + MaxMax = max(B, D), + Max = max(bnot MinMin, MaxMax), + {min_bxor(MinMin, -1, 0, MaxMax), + max_bxor(0, Max, 0, Max)}; + {'-','+','+','+'} -> + {min_bxor(A, -1, C, D), + max_bxor(A, B, C, D)}; + {'+','+','+','+'} -> + {min_bxor(A, B, C, D), + max_bxor(A, B, C, D)} + end; +do_min_max_bxor(A, B, C, D) -> + case {inf_sign(A),inf_sign(B),inf_sign(C),inf_sign(D)} of + {'+','+','+','+'} -> + {min_bxor(A, B, C, D), '+inf'}; + {'-','-','+','+'} when is_integer(D) -> + {'-inf',max_bxor(A, B, C, D)}; + {'-','-','+','+'} -> + {'-inf',-1}; + _ -> + {'-inf','+inf'} + end. + +min_bxor(A, B, C, D) -> + M = 1 bsl upper_bit(A bor C), + min_bxor(A, B, C, D, M). + +min_bxor(A, _B, C, _D, 0) -> + A bxor C; +min_bxor(A, B, C, D, M) -> + if + (bnot A) band C band M =/= 0 -> + case (A bor M) band -M of + NewA when NewA =< B -> + min_bxor(NewA, B, C, D, M bsr 1); + _ -> + min_bxor(A, B, C, D, M bsr 1) + end; + A band (bnot C) band M =/= 0 -> + case (C bor M) band -M of + NewC when NewC =< D -> + min_bxor(A, B, NewC, D, M bsr 1); + _ -> + min_bxor(A, B, C, D, M bsr 1) + end; + true -> + min_bxor(A, B, C, D, M bsr 1) + end. + max_bxor(A, B, C, D) -> M = 1 bsl upper_bit(B band D), max_bxor(A, B, C, D, M). @@ -466,14 +640,16 @@ max_bxor(_A, B, _C, D, 0) -> max_bxor(A, B, C, D, M) -> if B band D band M =/= 0 -> - case (B - M) bor (M - 1) of - NewB when NewB >= A -> + NewB = (B - M) bor (M - 1), + case inf_ge(NewB, A) of + true -> max_bxor(A, NewB, C, D, M bsr 1); - _ -> - case (D - M) bor (M - 1) of - NewD when NewD >= C -> + false -> + NewD = (D - M) bor (M - 1), + case inf_ge(NewD, C) of + true -> max_bxor(A, B, C, NewD, M bsr 1); - _ -> + false -> max_bxor(A, B, C, D, M bsr 1) end end; @@ -481,6 +657,8 @@ max_bxor(A, B, C, D, M) -> max_bxor(A, B, C, D, M bsr 1) end. +upper_bit(Val) when Val < 0 -> + ?NUM_BITS + 1; upper_bit(Val) -> upper_bit_1(Val, 0). @@ -507,6 +685,20 @@ infer_relop_types_1('>', {A,B}, {C,D}) -> Right = normalize({C,clamp(inf_add(B, -1), C, D)}), {Left,Right}. +canonical_args(R1, R2) -> + canonical_arg(R1) ++ canonical_arg(R2). + +canonical_arg(R0) -> + {A,B} = expand(R0), + case [inf_cap(A),inf_cap(B)] of + ['-inf','-inf'] -> + ['-inf',-1]; + ['+inf','+inf'] -> + [0,'+inf']; + R -> + R + end. + %%% %%% Handling of ranges. %%% @@ -581,3 +773,20 @@ inf_ge(A, B) -> A >= B. inf_le(A, B) -> inf_ge(B, A). inf_gt(A, B) -> inf_lt(B, A). + +inf_sign('-inf') -> '-'; +inf_sign('+inf') -> '+'; +inf_sign(N) when N < 0 -> '-'; +inf_sign(_) -> '+'. + +inf_cap(N) when abs(N) bsr ?NUM_BITS =/= 0 -> + case inf_lt(N, 0) of + true -> '-inf'; + false -> '+inf' + end; +inf_cap(N) -> + N. + +inf_bnot('-inf') -> '+inf'; +inf_bnot('+inf') -> '-inf'; +inf_bnot(N) -> bnot N. diff --git a/lib/compiler/test/beam_bounds_SUITE.erl b/lib/compiler/test/beam_bounds_SUITE.erl index 42436f5b8b8a..6c2121935883 100644 --- a/lib/compiler/test/beam_bounds_SUITE.erl +++ b/lib/compiler/test/beam_bounds_SUITE.erl @@ -197,44 +197,223 @@ rem_bounds(_Config) -> ok. band_bounds(_Config) -> - test_commutative('band'), - - %% Coverage. - {0,17} = beam_bounds:bounds('band', any, {7,17}), - {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}), + test_commutative('band', {-15,15}), + + Big = 1 bsl 512, + NegBig = -Big, + + %% -- -- + {'-inf',-7} = do_band({'-inf',-7}, {'-inf',-1}), + {'-inf',-155} = do_band({'-inf',-7}, {-300,-155}), + {-116,'+inf'} = do_band({-99,'+inf'}, {-18,-5}), + {-256,'+inf'} = do_band({-99,'+inf'}, {-179,-5}), + {'-inf',-1} = do_band({NegBig,NegBig}, {-10,-1}), + do_rand_band('-inf', '-', '-inf', '-'), + do_rand_band('-inf', '-', '-', '-'), + + %% -- -+ + {'-inf',25} = do_band({'-inf',-37}, {'-inf',25}), + {'-inf',15} = do_band({'-inf',-10}, {-11,15}), + any = do_band({-8,-8}, any), + do_rand_band('-', '-', '-inf', '+inf'), + do_rand_band('-inf', '-', '-inf', '+'), + do_rand_band('-inf', '-', '-', '+'), + + %% -+ -+ + {'-inf',37} = do_band({'-inf',37}, {'-inf',25}), + + {'-inf',15} = do_band({'-inf',10}, {-11,15}), + {'-inf',66} = do_band({'-inf',66}, {-11,17}), + + {-2,'+inf'} = do_band({-1,1}, {-2,'+inf'}), + {-112,'+inf'} = do_band({-99,10}, {-15,'+inf'}), + {-512,'+inf'} = do_band({-500,7}, {-57,'+inf'}), + {0,'+inf'} = do_band({-10,10}, {27,'+inf'}), + {0,'+inf'} = do_band({-10,27}, {10,'+inf'}), + + any = do_band({'-inf',0}, {-1,'+inf'}), + any = do_band({'-inf',11}, {-17,'+inf'}), + any = do_band({-10,27}, any), + any = do_band(rand_r('-', '+'), any), + + do_rand_band('-inf', '+', '-inf', '+'), + do_rand_band('-inf', '+', '-', '+'), + do_rand_band('-', '+', '-', '+inf'), + any = do_band(rand_r('-inf', '+'), rand_r('-', '+inf')), + + %% -- ++ + {0,'+inf'} = do_band({'-inf',-17}, {1,'+inf'}), + {0,'+inf'} = do_band({'-inf',-17}, {7,'+inf'}), + {0,14} = do_band({'-inf',-17}, {7,14}), + {0,255} = do_band({'-inf',-1}, {255,255}), + {0,'+inf'} = do_band({-77,-10}, {15,'+inf'}), + do_rand_band('-inf', '-', '+', '+'), + do_rand_band('-', '-', '+', '+inf'), + do_rand_band('-inf', '-', '+', '+inf'), + do_rand_band('-', '+', '+', '+inf'), + + %% -+ ++ + {0,13} = do_band({-10,'+inf'}, {7,13}), + {0,'+inf'} = do_band({-10,'+inf'}, {7,'+inf'}), + {0,'+inf'} = do_band(any, {7,'+inf'}), + do_rand_band('-inf', '+', '+', '+'), + do_rand_band('-', '+', '+', '+inf'), + do_rand_band('-', '+inf', '+', '+inf'), + do_rand_band('-inf', '+inf', '+', '+inf'), + + %% ++++ + {0,'+inf'} = do_band({1,'+inf'}, {1,'+inf'}), + {0,10} = do_band({7,'+inf'}, {5,10}), + {0,'+inf'} = do_band({7,'+inf'}, {Big,Big}), + do_rand_band('+', '+inf', '+', '+inf'), + do_rand_band('+', '+inf', '+', '+'), ok. -bor_bounds(_Config) -> - test_commutative('bor'), +do_band(R0, R1) -> + test_commutative_inf('band', R0, R1). - {'-inf',15} = beam_bounds:bounds('bor', {-10,7},{3,10}), - {'-inf',11} = beam_bounds:bounds('bor', {-10,1},{-1,10}), - {'-inf',-1} = beam_bounds:bounds('bor', {-20,-10}, {-2,10}), +do_rand_band(A, B, C, D) -> + test_rand('band', A, B, C, D). - {'-inf',15} = beam_bounds:bounds('bor', {'-inf',10}, {3,5}), - {'-inf',-1} = beam_bounds:bounds('bor', {-20,-10}, {-100,-50}), +bor_bounds(_Config) -> + test_commutative('bor', {-15,15}), + + %% -- -- + {'-inf',-1} = do_bor({'-inf',-1}, {'-inf',-1}), + {'-inf',-1} = do_bor({'-inf',-1}, {'-inf',-10}), + {'-inf',-1} = do_bor({'-inf',-177}, {'-inf',-17}), + do_rand_bor('-inf', '-', '-inf', '-'), + do_rand_bor('-inf', '-', '-', '-'), + + %% -- -+ and -+ -- + {'-inf',-1} = do_bor({'-inf',-1}, {'-inf',0}), + {'-inf',-1} = do_bor({'-inf',-1}, {'-inf',1}), + {'-inf',-1} = do_bor({'-inf',-9}, {'-inf',58}), + {'-inf',-1} = do_bor({'-inf',-177}, {'-inf',19}), + {-500,-1} = do_bor({-500,-100}, {'-inf',10}), + {-20,-1} = do_bor({-20,-10}, {-2,'+inf'}), + {'-inf',-1} = do_bor({'-inf',-177}, {-5,'+inf'}), + do_rand_bor('-inf', '-', '-inf', '+'), + do_rand_bor('-inf', '-', '-', '+'), + + %% -+ -+ + {'-inf',1} = do_bor({'-inf',1}, {'-inf',1}), + {'-inf',7} = do_bor({'-inf',1}, {'-inf',7}), + {'-inf',63} = do_bor({'-inf',63}, {'-inf',21}), + {'-inf',1} = do_bor({'-inf',1}, {-500,1}), + {'-inf',7} = do_bor({'-inf',5}, {-500,2}), + {-12,'+inf'} = do_bor({-1,10}, {-12,'+inf'}), + {-16,'+inf'} = do_bor({-7,'+inf'}, {-16,'+inf'}), + any = do_bor({'-inf',1}, {-1,'+inf'}), + any = do_bor({'-inf',37}, {-8,'+inf'}), + + do_rand_bor('-inf', '+', '-inf', '+'), + do_rand_bor('-inf', '+', '-', '+'), + do_rand_bor('-', '+', '-', '+inf'), + any = do_bor(rand_r('-inf', '+'), rand_r('-', '+inf')), + + %% -- ++ and ++ -- + {-7,-1} = do_bor({-7,-7}, {0,'+inf'}), + {-256,-1} = do_bor({-256,-256}, {200,'+inf'}), + {'-inf',-9} = do_bor({'-inf',-10}, {3,5}), + {-20,-1} = do_bor({-20,-10}, {5,'+inf'}), + {-1,'+inf'} = do_bor({1,10}, {-1,'+inf'}), + {'-inf',-1} = do_bor({'-inf',-97}, {42,'+inf'}), + {'-inf',-1} = do_bor({'-inf',-1}, {1,'+inf'}), + {'-inf',-1} = do_bor({'-inf',-777}, {100,'+inf'}), + do_rand_bor('-inf', '-', '+', '+'), + do_rand_bor('-', '-', '+', '+inf'), + do_rand_bor('-inf', '-', '+', '+inf'), + + %% -+ ++ and ++ -+ + {'-inf',15} = do_bor({'-inf',10}, {3,5}), + {-3,'+inf'} = do_bor({-3,10}, {17,'+inf'}), + {-8,'+inf'} = do_bor({-8,'+inf'}, {17,'+inf'}), + {-32,'+inf'} = do_bor({-32,'+inf'}, {5,'+inf'}), + do_rand_bor('-inf', '+', '+', '+'), + do_rand_bor('-', '+', '+', '+inf'), + do_rand_bor('-', '+inf', '+', '+inf'), + + any = do_bor({'-inf',0}, {0,'+inf'}), + any = do_bor({'-inf',1}, {1,'+inf'}), + any = do_bor({'-inf',47}, {99,'+inf'}), + any = do_bor(rand_r('-inf', '+'), rand_r('+', '+inf')), + + %% ++ ++ + {52,'+inf'} = do_bor({20,25}, {33,'+inf'}), + {16,'+inf'} = do_bor({3,'+inf'}, {16,'+inf'}), + do_rand_bor('+', '+inf', '+', '+inf'), + do_rand_bor('+', '+inf', '+', '+'), - any = beam_bounds:bounds('bor', {-20,-10}, {-2,'+inf'}), - any = beam_bounds:bounds('bor', {-20,'+inf'}, {-7,-3}), + ok. - {16,'+inf'} = beam_bounds:bounds('bor', {0,8}, {16,'+inf'}), - {16,'+inf'} = beam_bounds:bounds('bor', {3,'+inf'}, {16,'+inf'}), +do_bor(R0, R1) -> + test_commutative_inf('bor', R0, R1). - ok. +do_rand_bor(A, B, C, D) -> + test_rand('bor', A, B, C, D). bxor_bounds(_Config) -> - test_commutative('bxor'), - - any = beam_bounds:bounds('bxor', {-10,0}, {-1,10}), - any = beam_bounds:bounds('bxor', {-20,-10}, {-1,10}), + test_commutative('bxor', {-15,15}), + + %% -- -- + any = do_bxor({'-inf',-177}, {'-inf',-17}), + any = do_bxor({'-inf',-103}, {-17,-5}), + any = do_bxor(rand_r('-inf', '-'), rand_r('-inf', '-')), + any = do_bxor(rand_r('-inf', '-'), rand_r('-', '-')), + + %% -- -+ + any = do_bxor({'-inf',-177}, {'-inf',19}), + any = do_bxor({'-inf',-9}, {'-inf',58}), + any = do_bxor({-500,-100}, {'-inf',10}), + any = do_bxor({-20,-10}, {-2,'+inf'}), + any = do_bxor({'-inf',-177}, {-5,'+inf'}), + any = do_bxor(rand_r('-inf', '-'), rand_r('-inf', '+')), + any = do_bxor(rand_r('-inf', '-'), rand_r('-', '+')), + + %% -+ -+ + any = do_bxor({'-inf',63}, {'-inf',21}), + any = do_bxor({'-inf',5}, {-500,2}), + any = do_bxor({-1,10}, {-12,'+inf'}), + any = do_bxor({-7,'+inf'}, {-16,'+inf'}), + any = do_bxor({-8,'+inf'}, {17,'+inf'}), + any = do_bxor({'-inf',37}, {-8,'+inf'}), + any = do_bxor(rand_r('-inf', '+'), rand_r('-inf', '+')), + any = do_bxor(rand_r('-inf', '+'), rand_r('-', '+')), + + %% -- ++ + {'-inf',-9} = do_bxor({'-inf',-10}, {3,5}), + {'-inf',-97} = do_bxor({'-inf',-100}, {10,20}), + {'-inf',-1} = do_bxor({-20,-10}, {5,'+inf'}), + {'-inf',-1} = do_bxor({'-inf',-97}, {42,'+inf'}), + do_rand_bxor('-inf', '+', '-inf', '+'), + do_rand_bxor('-inf', '+', '-', '+'), + + %% -+ ++ + any = do_bxor({'-inf',10}, {3,5}), + any = do_bxor({-3,10}, {17,'+inf'}), + any = do_bxor({-1,'+inf'}, {1,10}), + any = do_bxor({-32,'+inf'}, {5,'+inf'}), + any = do_bxor({'-inf',47}, {99,'+inf'}), + any = do_bxor(rand_r('-inf', '+'), rand_r('+', '+')), + + %% ++ ++ + {32,'+inf'} = do_bxor({20,25}, {33,'+inf'}), + {0,'+inf'} = do_bxor({3,'+inf'}, {16,'+inf'}), + do_rand_bxor('+', '+inf', '+', '+inf'), + do_rand_bxor('+', '+inf', '+', '+'), + + any = do_bxor(any, {1,10}), ok. +do_rand_bxor(A, B, C, D) -> + test_rand('bxor', A, B, C, D). + +do_bxor(R0, R1) -> + test_commutative_inf('bxor', R0, R1). + bnot_bounds(_Config) -> Min = -7, Max = 7, @@ -249,6 +428,14 @@ bnot_bounds(_Config) -> {'-inf',9} = beam_bounds:bounds('bnot', {-10,'+inf'}), {-1114111,'+inf'} = beam_bounds:bounds('bnot', {'-inf', 1114110}), + Big = 1 bsl 512, + {1,'+inf'} = beam_bounds:bounds('bnot', {-Big, -2}), + {'-inf',-11} = beam_bounds:bounds('bnot', {10, Big}), + {'-inf',9} = beam_bounds:bounds('bnot', {-10, Big}), + {'-inf',-1} = beam_bounds:bounds('bnot', {Big, Big}), + + any = beam_bounds:bounds('bnot', any), + -1 = bnot_bounds_2(0), -43 = bnot_bounds_2_coverage(id(42)), ?assertError(badarith, bnot_bounds_2_coverage(id(bad))), @@ -265,7 +452,7 @@ bnot_bounds_1(R) -> {HighestMin,LowestMax} = min_max_unary_op('bnot', R), {Min,Max} = beam_bounds:bounds('bnot', R), if - Min =< HighestMin, LowestMax =< Max -> + Min =:= HighestMin, LowestMax =:= Max -> ok; true -> io:format("bnot(~p) evaluates to ~p; should be ~p\n", @@ -289,51 +476,64 @@ bnot_bounds_4() -> bsr_bounds(_Config) -> - test_noncommutative('bsr', {-12,12}, {0,7}), + test_noncommutative('bsr', {-12,12}, {-7,7}), - {0,10} = beam_bounds:bounds('bsr', {0,10}, {0,'+inf'}), - {0,2} = beam_bounds:bounds('bsr', {0,10}, {2,'+inf'}), + {0,10} = do_bsr({0,10}, {0,'+inf'}), + {0,2} = do_bsr({0,10}, {2,'+inf'}), - {-1,10} = beam_bounds:bounds('bsr', {-1,10}, {0,'+inf'}), - {-100,900} = beam_bounds:bounds('bsr', {-100,900}, {0,'+inf'}), - {-50,450} = beam_bounds:bounds('bsr', {-100,900}, {1,'+inf'}), + {-1,10} = do_bsr({-1,10}, {0,'+inf'}), + {-100,900} = do_bsr({-100,900}, {0,'+inf'}), + {-50,450} = do_bsr({-100,900}, {1,'+inf'}), - {'-inf',16} = beam_bounds:bounds('bsr', {'-inf',32}, {1,10}), - {-5,'+inf'} = beam_bounds:bounds('bsr', {-10,'+inf'}, {1,10}), + {'-inf',16} = do_bsr({'-inf',32}, {1,10}), + {-5,'+inf'} = do_bsr({-10,'+inf'}, {1,10}), + + {0,'+inf'} = do_bsr({17,'+inf'}, any), + {'-inf',-1} = do_bsr({'-inf',-10}, any), ok. +do_bsr(R0, R1) -> + test_noncommutative_inf('bsr', R0, R1). + bsl_bounds(_Config) -> test_noncommutative('bsl', {-12,12}, {-7,7}), - {2,'+inf'} = beam_bounds:bounds('bsl', {1,10}, {1,10_000}), - {0,'+inf'} = beam_bounds:bounds('bsl', {1,10}, {-10,10_000}), - {'-inf',-20} = beam_bounds:bounds('bsl', {-30,-10}, {1,10_000}), - {'-inf',-2} = beam_bounds:bounds('bsl', {-9,-1}, {1,10_000}), - any = beam_bounds:bounds('bsl', {-7,10}, {1,10_000}), + {2,'+inf'} = do_bsl({1,10}, {1,10_000}), + {0,'+inf'} = do_bsl({1,10}, {-10,10_000}), + {'-inf',-20} = do_bsl({-30,-10}, {1,10_000}), + {'-inf',-2} = do_bsl({-9,-1}, {1,10_000}), + any = do_bsl({-7,10}, {1,10_000}), + + {0,'+inf'} = do_bsl({0,'+inf'}, {0,'+inf'}), + {20,'+inf'} = do_bsl({20,30}, {0,'+inf'}), - {0,'+inf'} = beam_bounds:bounds('bsl', {0,'+inf'}, {0,'+inf'}), - {20,'+inf'} = beam_bounds:bounds('bsl', {20,30}, {0,'+inf'}), + any = do_bsl({-10,100}, {0,'+inf'}), + any = do_bsl({-10,100}, {1,'+inf'}), + any = do_bsl({-10,100}, {-1,'+inf'}), - any = beam_bounds:bounds('bsl', {-10,100}, {0,'+inf'}), - any = beam_bounds:bounds('bsl', {-10,100}, {1,'+inf'}), - any = beam_bounds:bounds('bsl', {-10,100}, {-1,'+inf'}), + {0,10} = do_bsl({1,10}, {'-inf',0}), + {0,20} = do_bsl({1,10}, {'-inf',1}), + {-7,10} = do_bsl({-7,10}, {'-inf',0}), + {-28,40} = do_bsl({-7,10}, {'-inf',2}), - {0,10} = beam_bounds:bounds('bsl', {1,10}, {'-inf',0}), - {0,20} = beam_bounds:bounds('bsl', {1,10}, {'-inf',1}), - {-7,10} = beam_bounds:bounds('bsl', {-7,10}, {'-inf',0}), - {-28,40} = beam_bounds:bounds('bsl', {-7,10}, {'-inf',2}), + {'-inf',-1} = do_bsl({-10,-1}, {500,1024}), + {0,'+inf'} = do_bsl({1,10}, {500,1024}), - {'-inf',-1} = beam_bounds:bounds('bsl', {-10,-1}, {500,1024}), - {0,'+inf'} = beam_bounds:bounds('bsl', {1,10}, {500,1024}), + {'-inf',-40} = do_bsl({'-inf',-10}, {2,64}), + {'-inf',224} = do_bsl({'-inf',7}, {3,5}), - {'-inf',-40} = beam_bounds:bounds('bsl', {'-inf',-10}, {2,64}), - {'-inf',224} = beam_bounds:bounds('bsl', {'-inf',7}, {3,5}), + {'-inf',-88} = do_bsl({'-inf',-11}, {3,'+inf'}), + any = do_bsl({'-inf',7}, {3,'+inf'}), - any = beam_bounds:bounds('bsl', {'-inf',7}, {3,'+inf'}), + {0,'+inf'} = do_bsl({17,'+inf'}, any), + {'-inf',-1} = do_bsl({'-inf',-10}, any), ok. +do_bsl(R0, R1) -> + test_noncommutative_inf('bsl', R0, R1). + lt_bounds(_Config) -> test_relop('<'). @@ -364,13 +564,12 @@ min_bounds(_Config) -> {'-inf',10} = min_bounds({1,10}, any), any = min_bounds({1,'+inf'}, any), - {'-inf',777} = min_bounds(any, {'-inf',777}), + {'-inf',777} = min_bounds({'-inf',777}, any), ok. -min_bounds(R1, R2) -> - Result = beam_bounds:bounds(min, R1, R2), - Result = beam_bounds:bounds(min, R2, R1). +min_bounds(R0, R1) -> + test_commutative_inf('min', R0, R1). max_bounds(_Config) -> test_commutative(max, {-12,12}), @@ -394,9 +593,8 @@ max_bounds(_Config) -> ok. -max_bounds(R1, R2) -> - Result = beam_bounds:bounds(max, R1, R2), - Result = beam_bounds:bounds(max, R2, R1). +max_bounds(R0, R1) -> + test_commutative_inf('max', R0, R1). abs_bounds(_Config) -> Min = -7, @@ -515,6 +713,119 @@ do_bench(F) -> %%% Common utilities. %%% +test_rand(Op, A, B, C, D) -> + R0 = rand_r(A, B), + R1 = rand_r(C, D), + Res = beam_bounds:bounds(Op, R0, R1), + Res = beam_bounds:bounds(Op, R1, R0), + test_random_pairs(Op, R0, R1, Res). + +test_commutative_inf(Op, {A0,B0}=R0, {C0,D0}=R1) -> + Res = beam_bounds:bounds(Op, R0, R1), + Res = beam_bounds:bounds(Op, R1, R0), + [A,B,C,D] = [case N of + '-inf' -> -1 bsl 512; + '+inf' -> 1 bsl 512; + _ -> N + end || N <- [A0,B0,C0,D0]], + Res = beam_bounds:bounds(Op, {A,B}, {C,D}), + Res = beam_bounds:bounds(Op, {C,D}, {A,B}), + test_random_pairs(Op, R0, R1, Res), + Res; +test_commutative_inf(Op, {A0,B0}=R0, R1) -> + Res = beam_bounds:bounds(Op, R0, R1), + Res = beam_bounds:bounds(Op, R1, R0), + [A,B] = [case N of + '-inf' -> -1 bsl 512; + '+inf' -> 1 bsl 512; + _ -> N + end || N <- [A0,B0]], + Res = beam_bounds:bounds(Op, {A,B}, R1), + Res = beam_bounds:bounds(Op, R1, {A,B}), + test_random_pairs(Op, R0, R1, Res), + Res; +test_commutative_inf(Op, any, R) -> + test_commutative_inf(Op, R, any). + +test_noncommutative_inf(Op, {A0,B0}=R0, {C0,D0}=R1) -> + Res = beam_bounds:bounds(Op, R0, R1), + [A,B,C,D] = [case N of + '-inf' -> -1 bsl 512; + '+inf' -> 1 bsl 512; + _ -> N + end || N <- [A0,B0,C0,D0]], + Res = beam_bounds:bounds(Op, {A,B}, {C,D}), + test_random_pairs(Op, R0, R1, Res), + Res; +test_noncommutative_inf(Op, {A0,B0}=R0, R1) -> + Res = beam_bounds:bounds(Op, R0, R1), + [A,B] = [case N of + '-inf' -> -1 bsl 512; + '+inf' -> 1 bsl 512; + _ -> N + end || N <- [A0,B0]], + Res = beam_bounds:bounds(Op, {A,B}, R1), + test_random_pairs(Op, R0, R1, Res), + Res; +test_noncommutative_inf(Op, any, R) -> + test_commutative_inf(Op, R, any). + +rand_r(A0, B0) -> + A = case A0 of + '-' -> -rand:uniform(1000); + '+' -> rand:uniform(1000); + _ -> A0 + end, + B = case B0 of + '-' when is_integer(A) -> + -rand:uniform(-A); + '-' -> + -rand:uniform(1000); + '+' when is_integer(A), A >= 0 -> + A + rand:uniform(1000); + '+' -> + rand:uniform(1000); + _ -> + B0 + end, + {A,B}. + +test_random_pairs(_Op, _R0, _R1, any) -> + ok; +test_random_pairs(Op, R0, R1, Range) -> + {A,B} = eliminate_infinity(R0), + {C,D} = eliminate_infinity(R1), + L0 = [{A,C}, {A,D}, {B,C}, {B,D} | + [random_pair(A, B, C, D) || + _ <- lists:seq(1, 10)]], + L1 = lists:usort(L0), + L = [{N0,N1,Result,Range} || + {N0,N1} <- L1, + Result = erlang:Op(N0, N1), + not inf_in_range(Result, Range)], + [] = L, + ok. + +eliminate_infinity({'-inf','+inf'}) -> + A = rand:uniform(100), + B = A + rand:uniform(100), + {A,B}; +eliminate_infinity({'-inf',B}) -> + {B - rand:uniform(100), B}; +eliminate_infinity({A,'+inf'}) -> + {A, A + rand:uniform(100)}; +eliminate_infinity(any) -> + eliminate_infinity({'-inf','+inf'}); +eliminate_infinity(R) -> + R. + +random_pair(A, B, C, D) -> + {random_in_range(A, B), + random_in_range(C, D)}. + +random_in_range(A, B) -> + A + rand:uniform(B - A + 1) - 1. + infer_lt_gt(R1, R2) -> case beam_bounds:infer_relop_types('>', R2, R1) of {Rb,Ra} -> @@ -523,15 +834,13 @@ infer_lt_gt(R1, R2) -> any = beam_bounds:infer_relop_types('<', R1, R2) end. -test_commutative(Op) -> - test_commutative(Op, {0,32}). - test_commutative(Op, {Min,Max}) -> Seq = lists:seq(Min, Max), _ = [test_commutative_1(Op, {A,B}, {C,D}) || A <- Seq, - B <- lists:nthtail(A-Min, Seq), - C <- lists:nthtail(A-Min, Seq), + Reduced = lists:nthtail(A-Min, Seq), + B <- Reduced, + C <- Reduced, D <- lists:nthtail(C-Min, Seq), {A,B} =< {C,D}], ok. @@ -541,8 +850,29 @@ test_commutative_1(Op, R1, R2) -> {Min,Max} = beam_bounds:bounds(Op, R1, R2), {Min,Max} = beam_bounds:bounds(Op, R2, R1), if - Min =< HighestMin, LowestMax =< Max -> + Min =:= HighestMin, LowestMax =:= Max -> + %% The bounds are as tight as possible. ok; + Min =< HighestMin, LowestMax =< Max -> + %% The bounds are correct, but not as tight as possible. + %% + %% We haven't figured out how to compute tight bounds for + %% bxor when one range includes zero and the other range + %% can be negative. + case {Op,R1,R2} of + {'bxor',{A,B},{C,D}} when A < 0, B < 0, C < 0, D >= 0 -> + ok; + {'bxor',{A,B},{C,D}} when A < 0, B >= 0, C < 0, D < 0 -> + ok; + {'bxor',{A,B},{C,D}} when A < 0, B >= 0, C < 0, D >= 0 -> + ok; + _ -> + io:format("~p(~p, ~p): bounds are not tight;\n" + " evaluates to ~p; should be ~p\n", + [Op,R1,R2,{Min,Max}, + {HighestMin,LowestMax}]), + ct:fail(bounds_are_not_tight) + end; true -> io:format("~p(~p, ~p) evaluates to ~p; should be ~p\n", [Op,R1,R2,{Min,Max},{HighestMin,LowestMax}]), @@ -667,6 +997,13 @@ test_infer_relop('maybe', Op, {A0,B0}=R1, {C0,D0}=R2) -> in_range(Int, {A,B}) -> A =< Int andalso Int =< B. +inf_in_range(Int, {'-inf',B}) -> + Int =< B; +inf_in_range(Int, {A,'+inf'}) -> + A =< Int; +inf_in_range(Int, {A,B}) -> + A =< Int andalso Int =< B. + rel_op(Op, {A,B}, {C,D}) -> rel_op_1(Op, A, B, C, D, none). From c492677d006476c00db3b9a663b364e1eeb38a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Wed, 20 May 2026 10:08:04 +0200 Subject: [PATCH 4/5] Remove verification code in min_max_band/4 --- lib/compiler/src/beam_bounds.erl | 143 ------------------------------- 1 file changed, 143 deletions(-) diff --git a/lib/compiler/src/beam_bounds.erl b/lib/compiler/src/beam_bounds.erl index 91138ff75e53..919feb9ab520 100644 --- a/lib/compiler/src/beam_bounds.erl +++ b/lib/compiler/src/beam_bounds.erl @@ -295,154 +295,11 @@ rem_bounds({A,B}, _) -> rem_bounds(_, _) -> any. --if(false). min_max_band(A, B, C, D) -> {Min,Max} = min_max_bor(inf_bnot(B), inf_bnot(A), inf_bnot(D), inf_bnot(C)), {inf_bnot(Max),inf_bnot(Min)}. --else. -%% For testing, we calculate the bounds both directly and by using the -%% `bor` bounds calculation. It is the intention to remove this code -%% in separate commit after testing (thus keeping it in the git -%% history but not in the compiler code). -min_max_band(A, B, C, D) -> - Res = old_min_max_band(A, B, C, D), - case new_min_max_band(A, B, C, D) of - Res -> - Res; - Other -> - error({A,B,C,D,Res,Other}) - end. - -new_min_max_band(A, B, C, D) -> - {Min,Max} = min_max_bor(inf_bnot(B), inf_bnot(A), - inf_bnot(D), inf_bnot(C)), - {inf_bnot(Max),inf_bnot(Min)}. - -old_min_max_band(A, B, C, D) -> - case inf_le(A, C) of - true -> - do_min_max_band(A, B, C, D); - false -> - do_min_max_band(C, D, A, B) - end. - -do_min_max_band(A, B, C, D) when is_integer(A), - is_integer(B), - is_integer(C), - is_integer(D) -> - case {inf_sign(A),inf_sign(B),inf_sign(C),inf_sign(D)} of - {'-','-','-','-'} -> - {min_band(A, B, C, D), - max_band(A, B, C, D)}; - {'-','-','-','+'} -> - {min_band(A, B, C, D), - max_band(A, B, C, D)}; - {'-','-','+','+'} -> - {min_band(A, B, C, D), - max_band(A, B, C, D)}; - {'-','+','-','-'} -> - {min_band(A, B, C, D), - max_band(0, B, C, D)}; - {'-','+','-','+'} -> - {min_band(A, -1, C, -1), - inf_max(B, D)}; - {'-','+','+','+'} -> - {0, D}; - {'+','+','+','+'} -> - {min_band(A, B, C, D), - max_band(A, B, C, D)} - end; -do_min_max_band(A, B, C, D) -> - case {inf_sign(A),inf_sign(B),inf_sign(C),inf_sign(D)} of - {'-','-','-','-'} -> - {'-inf',max_band(A, B, C, D)}; - {'-','-','-','+'} when is_integer(A), - is_integer(C) -> - {min_band(A, B, C, D),D}; - {'-','-','-','+'} -> - {'-inf',D}; - {'-','-','+','+'} when is_integer(D) -> - {0,max_band(A, B, C, D)}; - {'-','-','+','+'} when is_integer(A) -> - {min_band(A, B, C, D),D}; - {'-','-','+','+'} -> - {0,D}; - {'-','+','-','-'} when is_integer(A), - is_integer(C) -> - {min_band(A, B, C, D),B}; - {'-','+','-','-'} when is_integer(B) -> - {'-inf',max_band(A, B, C, D)}; - {'-','+','-','-'} -> - {'-inf',B}; - {'-','+','-','+'} when is_integer(A), - is_integer(C) -> - {min_band(A, B, C, D),inf_max(B, D)}; - {'-','+','-','+'} -> - {'-inf',inf_max(B, D)}; - {'-','+','+','+'} -> - {0,D}; - {'+','+','+','+'} -> - {min_band(A, B, C, D), - inf_min(B, D)} - end. - -min_band(A, B, C, D) -> - M = 1 bsl (upper_bit(A bor C) + 1), - min_band(A, B, C, D, M). - -min_band(A, _B, C, _D, 0) -> - A band C; -min_band(A, B, C, D, M) -> - if - (bnot A) band (bnot C) band M =/= 0 -> - NewA = (A bor M) band -M, - case inf_le(NewA, B) of - true -> - min_band(NewA, B, C, D, 0); - false -> - NewC = (C bor M) band -M, - case inf_le(NewC, D) of - true -> - min_band(A, B, NewC, D, 0); - false -> - min_band(A, B, C, D, M bsr 1) - end - end; - true -> - min_band(A, B, C, D, M bsr 1) - end. - -max_band(A, B, C, D) -> - M = 1 bsl upper_bit(B bxor D), - max_band(A, B, C, D, M). - -max_band(_A, B, _C, D, 0) -> - B band D; -max_band(A, B, C, D, M) -> - if - B band (bnot D) band M =/= 0 -> - NewB = (B band (bnot M)) bor (M - 1), - case inf_ge(NewB, A) of - true -> - max_band(A, NewB, C, D, 0); - false -> - max_band(A, B, C, D, M bsr 1) - end; - (bnot B) band D band M =/= 0 -> - NewD = (D band (bnot M)) bor (M - 1), - case inf_ge(NewD, C) of - true -> - max_band(A, B, C, NewD, 0); - false -> - max_band(A, B, C, D, M bsr 1) - end; - true -> - max_band(A, B, C, D, M bsr 1) - end. --endif. - min_max_bor(A, B, C, D) -> case inf_le(A, C) of true -> From 88e4098111c1bb58f349fc97cc12ec38bfb0eb51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Sat, 18 Apr 2026 08:10:23 +0200 Subject: [PATCH 5/5] beam_bounds: Simplify bounds computation for arithmetic operations --- lib/compiler/src/beam_bounds.erl | 144 ++++++++++-------------- lib/compiler/test/beam_bounds_SUITE.erl | 77 +++++++++++-- 2 files changed, 130 insertions(+), 91 deletions(-) diff --git a/lib/compiler/src/beam_bounds.erl b/lib/compiler/src/beam_bounds.erl index 919feb9ab520..ee6753d19b4d 100644 --- a/lib/compiler/src/beam_bounds.erl +++ b/lib/compiler/src/beam_bounds.erl @@ -42,6 +42,8 @@ -type bool_result() :: 'true' | 'false' | 'maybe'. -type op() :: atom(). +-import(lists, [foldl/3]). + %% Maximum size of integers in bits to keep ranges for. -define(NUM_BITS, 128). @@ -52,95 +54,51 @@ bounds('bnot', R0) -> R = {inf_add(inf_neg(B), -1), inf_add(inf_neg(A), -1)}, normalize(R); bounds(abs, R) -> - case R of - {A,B} when is_integer(A), is_integer(B) -> - Min = 0, - Max = max(abs(A), abs(B)), - {Min,Max}; - _ -> - {0,'+inf'} - end. + [A,B] = canonical_arg(R), + AbsA = inf_abs(A), + AbsB = inf_abs(B), + Min = case {inf_sign(A),inf_sign(B)} of + {'-','+'} -> 0; + {_,_} -> inf_min(AbsA, AbsB) + end, + Max = inf_max(AbsA, AbsB), + normalize({Min,Max}). -spec bounds(op(), range(), range()) -> range_result(). bounds('+', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> - normalize({A+C,B+D}); - {{'-inf',B}, {_C,D}} when abs(B) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> - normalize({'-inf',B+D}); - {{_A,B}, {'-inf',D}} when abs(B) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> - normalize({'-inf',B+D}); - {{A,'+inf'}, {C,_D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0 -> - normalize({A+C,'+inf'}); - {{A,_B}, {C,'+inf'}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0 -> - normalize({A+C,'+inf'}); - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + normalize({inf_add(A, C), inf_add(B, D)}); bounds('-', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> + case canonical_args(R1, R2) of + [A,B,C,D] when is_integer(A), + is_integer(B), + is_integer(C), + is_integer(D) -> normalize({A-D,B-C}); - {{A,'+inf'}, {_C,D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> + [A,'+inf',_C,D] when is_integer(A), is_integer(D) -> normalize({A-D,'+inf'}); - {{_A,B}, {C,'+inf'}} when abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0 -> + [_A,B,C,'+inf'] when is_integer(B), is_integer(C) -> normalize({'-inf',B-C}); - {{'-inf',B}, {C,_D}} when abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0 -> + ['-inf',B,C,_D] when is_integer(B), is_integer(C) -> normalize({'-inf',B-C}); - {{A,_B}, {'-inf',D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> + [A,_B,'-inf',D] when is_integer(A), is_integer(D) -> normalize({A-D,'+inf'}); - {_, _} -> + [_,_,_,_] -> any end; bounds('*', R1, R2) -> - case {R1,R2} of - {{A,B}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0 -> - All = [X * Y || X <- [A,B], Y <- [C,D]], - Min = lists:min(All), - Max = lists:max(All), - normalize({Min,Max}); - {{A,'+inf'}, {C,'+inf'}} when abs(A) bsr ?NUM_BITS =:= 0, A >= 0, - abs(C) bsr ?NUM_BITS =:= 0, C >= 0 -> - {A*C,'+inf'}; - {{A,'+inf'}, {C,D}} when abs(A) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0, - C >= 0 -> - {min(A*C, A*D),'+inf'}; - {{'-inf',B}, {C,D}} when abs(B) bsr ?NUM_BITS =:= 0, - abs(C) bsr ?NUM_BITS =:= 0, - abs(D) bsr ?NUM_BITS =:= 0, - C >= 0 -> - {'-inf',max(B*C, B*D)}; - {{A,B}, {'-inf',_}} when is_integer(A), is_integer(B) -> - bounds('*', R2, R1); - {{A,B}, {_,'+inf'}} when is_integer(A), is_integer(B) -> - bounds('*', R2, R1); - {_, _} -> - any - end; + [A,B,C,D] = canonical_args(R1, R2), + All = [inf_mul(X, Y) || X <- [A,B], Y <- [C,D]], + Min = foldl(fun inf_min/2, '+inf', All), + Max = foldl(fun inf_max/2, '-inf', All), + normalize({Min,Max}); bounds('div', R1, R2) -> - div_bounds(R1, R2); + [A,B,C,D] = canonical_args(R1, R2), + div_bounds({A,B}, {C,D}); bounds('rem', R1, R2) -> - rem_bounds(R1, R2); + [A,B,C,D] = canonical_args(R1, R2), + rem_bounds({A,B}, {C,D}); bounds('band', R1, R2) -> [A,B,C,D] = canonical_args(R1, R2), normalize(min_max_band(A, B, C, D)); @@ -255,13 +213,15 @@ div_bounds({A,B}, {C,D}) when is_integer(A), is_integer(B), Min = lists:min(All), Max = lists:max(All), normalize({Min,Max}); -div_bounds({A,'+inf'}, {C,D}) when is_integer(C), C > 0, is_integer(D) -> - Min = min(A div C, A div D), +div_bounds({A,'+inf'}, {C,D}) when is_integer(A), + is_integer(C), C > 0 -> + Min = min(A div C, inf_div(A, D)), Max = '+inf', normalize({Min,Max}); -div_bounds({'-inf',B}, {C,D}) when is_integer(C), C > 0, is_integer(D) -> +div_bounds({'-inf',B}, {C,D}) when is_integer(B), + is_integer(C), C > 0 -> Min = '-inf', - Max = max(B div C, B div D), + Max = max(B div C, inf_div(B, D)), normalize({Min,Max}); div_bounds({A,B}, _) when is_integer(A), is_integer(B) -> Max = max(abs(A), abs(B)), @@ -291,9 +251,7 @@ rem_bounds({A,B}, _) -> %% include zero. Min = inf_min(0, A), Max = inf_max(0, B), - normalize({Min,Max}); -rem_bounds(_, _) -> - any. + normalize({Min,Max}). min_max_band(A, B, C, D) -> {Min,Max} = min_max_bor(inf_bnot(B), inf_bnot(A), @@ -594,8 +552,28 @@ inf_neg('-inf') -> '+inf'; inf_neg('+inf') -> '-inf'; inf_neg(N) -> -N. -inf_add(Int, N) when is_integer(Int) -> Int + N; -inf_add(Inf, _N) -> Inf. +inf_add(A, B) when is_integer(A), is_integer(B) -> A + B; +inf_add(Inf, Inf) when is_atom(Inf) -> Inf; +inf_add(Inf, _) when is_atom(Inf) -> Inf; +inf_add(_, Inf) when is_atom(Inf) -> Inf. + +inf_mul(A, B) when is_integer(A), is_integer(B) -> + A * B; +inf_mul(A, B) -> + case {inf_sign(A),inf_sign(B)} of + {'-','-'} -> '+inf'; + {'+','+'} -> '+inf'; + {_,_} -> '-inf' + end. + +inf_div(A, B) when is_integer(A), is_integer(B) -> + A div B; +inf_div(A, '+inf') when is_integer(A) -> + 0. + +inf_abs('-inf') -> '+inf'; +inf_abs('+inf') -> '+inf'; +inf_abs(N) when is_integer(N) -> abs(N). inf_bsr('-inf', _S) -> '-inf'; diff --git a/lib/compiler/test/beam_bounds_SUITE.erl b/lib/compiler/test/beam_bounds_SUITE.erl index 6c2121935883..241df9059e03 100644 --- a/lib/compiler/test/beam_bounds_SUITE.erl +++ b/lib/compiler/test/beam_bounds_SUITE.erl @@ -93,17 +93,22 @@ end_per_testcase(Case, Config) when is_atom(Case), is_list(Config) -> addition_bounds(_Config) -> test_commutative('+', {-12,12}), - {'-inf',-15} = beam_bounds:bounds('+', {'-inf',-20}, {2,5}), - {'-inf',55} = beam_bounds:bounds('+', {'-inf',50}, {'-inf',5}), - {'-inf',110} = beam_bounds:bounds('+', {1,10}, {'-inf',100}), - any = beam_bounds:bounds('+', {1,'+inf'}, {'-inf',100}), + {'-inf',-15} = do_add({'-inf',-20}, {2,5}), + {'-inf',55} = do_add({'-inf',50}, {'-inf',5}), + {'-inf',110} = do_add({1,10}, {'-inf',100}), + any = do_add({1,'+inf'}, {'-inf',100}), - {-8,'+inf'} = beam_bounds:bounds('+', {2,'+inf'}, {-10,20}), - {6,'+inf'} = beam_bounds:bounds('+', {1,10}, {5,'+inf'}), - {9,'+inf'} = beam_bounds:bounds('+', {2,'+inf'}, {7,'+inf'}), + {-8,'+inf'} = do_add({2,'+inf'}, {-10,20}), + {6,'+inf'} = do_add({1,10}, {5,'+inf'}), + {9,'+inf'} = do_add({2,'+inf'}, {7,'+inf'}), ok. +do_add(A, B) -> + Res = beam_bounds:bounds('+', A, B), + Res = beam_bounds:bounds('+', B, A), + Res. + subtraction_bounds(_Config) -> test_noncommutative('-', {-12,12}), @@ -155,6 +160,28 @@ division_bounds(_Config) -> any = beam_bounds:bounds('div', {'-inf',10}, any), any = beam_bounds:bounds('div', {0,'+inf'}, any), + any = beam_bounds:bounds('div', any, {'-inf',10}), + any = beam_bounds:bounds('div', any, {0,'+inf'}), + + {-32,32} = beam_bounds:bounds('div', {16,32}, any), + any = beam_bounds:bounds('div', any, {7,13}), + + Big = 1 bsl 512, + {-100,100} = beam_bounds:bounds('div', {0,100}, {1,Big}), + {-100,100} = beam_bounds:bounds('div', {0,100}, {-Big,10}), + + {-100,'+inf'} = beam_bounds:bounds('div', {-100,'+inf'}, {1,Big}), + {-25,'+inf'} = beam_bounds:bounds('div', {-100,'+inf'}, {4,Big}), + {'-inf',0} = beam_bounds:bounds('div', {'-inf',-5}, {1,Big}), + {'-inf',5} = beam_bounds:bounds('div', {'-inf',5}, {1,Big}), + {0,'+inf'} = beam_bounds:bounds('div', {5,'+inf'}, {1,Big}), + + {'-inf',0} = beam_bounds:bounds('div', {-Big,-1}, {5,10}), + {'-inf',-2} = beam_bounds:bounds('div', {-Big,-20}, {5,10}), + {'-inf',0} = beam_bounds:bounds('div', {-Big,-1}, {7,Big}), + {'-inf',8} = beam_bounds:bounds('div', {-Big,17}, {2,Big}), + {2,'+inf'} = beam_bounds:bounds('div', {20,Big}, {5,10}), + {0,'+inf'} = beam_bounds:bounds('div', {1,Big}, {1,Big}), ok. @@ -194,6 +221,19 @@ rem_bounds(_Config) -> {-7,0} = beam_bounds:bounds('rem', {-7,-7}, any), {-6,0} = beam_bounds:bounds('rem', {-6,-4}, any), + {0,'+inf'} = beam_bounds:bounds('rem', {0,'+inf'}, any), + {0,'+inf'} = beam_bounds:bounds('rem', {7,'+inf'}, any), + {'-inf',0} = beam_bounds:bounds('rem', {'-inf',-5}, any), + {'-inf',11} = beam_bounds:bounds('rem', {'-inf',11}, any), + + any = beam_bounds:bounds('rem', any, {'-inf',-7}), + any = beam_bounds:bounds('rem', any, {'-inf',0}), + any = beam_bounds:bounds('rem', any, {'-inf',1}), + any = beam_bounds:bounds('rem', any, {'-inf',5}), + any = beam_bounds:bounds('rem', any, {0,'+inf'}), + any = beam_bounds:bounds('rem', any, {1,'+inf'}), + any = beam_bounds:bounds('rem', any, {100,'+inf'}), + ok. band_bounds(_Config) -> @@ -603,13 +643,34 @@ abs_bounds(_Config) -> _ = [abs_bounds_1({A,B}) || A <- Seq, B <- lists:nthtail(A-Min, Seq)], + + Big = 1 bsl 512, + + {1,'+inf'} = do_abs({'-inf',-1}), + {10,'+inf'} = do_abs({'-inf',-10}), + + {0,'+inf'} = do_abs({'-inf',0}), + {0,'+inf'} = do_abs({'-inf',1}), + {0,'+inf'} = do_abs({'-inf',10}), + {0,'+inf'} = do_abs(any), + {1,'+inf'} = do_abs({-Big,-Big}), + + {0,'+inf'} = do_abs({-5,'+inf'}), + {0,'+inf'} = do_abs({0,'+inf'}), + {1,'+inf'} = do_abs({1,'+inf'}), + {17,'+inf'} = do_abs({17,'+inf'}), + {0,'+inf'} = do_abs({Big,Big}), + ok. +do_abs(R) -> + beam_bounds:bounds('abs', R). + abs_bounds_1(R) -> {HighestMin,LowestMax} = min_max_unary_op('abs', R), {Min,Max} = beam_bounds:bounds(abs, R), if - Min =< HighestMin, LowestMax =< Max -> + Min =:= HighestMin, LowestMax =:= Max -> ok; true -> io:format("~p(~p) evaluates to ~p; should be ~p\n",