diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab index 95fcb0589f11..e03f312443c1 100644 --- a/erts/emulator/beam/bif.tab +++ b/erts/emulator/beam/bif.tab @@ -834,3 +834,8 @@ bif erl_debugger:peek_xreg/3 # bif erts_debug:unaligned_bitstring/2 bif re:import/1 + +# +# New in 29. +# +ubif erlang:is_integer/3 diff --git a/erts/emulator/beam/emu/ops.tab b/erts/emulator/beam/emu/ops.tab index 68eebde0f24c..265ca44b55d6 100644 --- a/erts/emulator/beam/emu/ops.tab +++ b/erts/emulator/beam/emu/ops.tab @@ -1001,6 +1001,8 @@ bif1 Fail=f Bif S1 Dst => i_bif1 S1 Fail Bif Dst bif2 p Bif S1 S2 Dst => i_bif2_body S2 S1 Bif Dst bif2 Fail=f Bif S1 S2 Dst => i_bif2 S2 S1 Fail Bif Dst +bif3 p Bif S1 S2 S3 Dst => i_bif3_body S3 S2 S1 Bif Dst + i_get_hash c W d i_get s d diff --git a/erts/emulator/beam/erl_bif_guard.c b/erts/emulator/beam/erl_bif_guard.c index 258e48a51e8f..5fc801179f54 100644 --- a/erts/emulator/beam/erl_bif_guard.c +++ b/erts/emulator/beam/erl_bif_guard.c @@ -334,6 +334,20 @@ BIF_RETTYPE size_1(BIF_ALIST_1) BIF_ERROR(BIF_P, BADARG); } +BIF_RETTYPE is_integer_3(BIF_ALIST_3) +{ + if(is_not_integer(BIF_ARG_2) || + is_not_integer(BIF_ARG_3)) { + BIF_ERROR(BIF_P, BADARG); + } + if(is_not_integer(BIF_ARG_1)) { + BIF_RET(am_false); + } + + BIF_RET((CMP_LE(BIF_ARG_2, BIF_ARG_1) && CMP_LE(BIF_ARG_1, BIF_ARG_3)) ? + am_true : am_false); +} + /**********************************************************************/ /* returns the bitsize of a bitstring */ diff --git a/erts/emulator/beam/erl_db_util.c b/erts/emulator/beam/erl_db_util.c index de67d804c0ab..1ffe59aa5e55 100644 --- a/erts/emulator/beam/erl_db_util.c +++ b/erts/emulator/beam/erl_db_util.c @@ -588,6 +588,12 @@ static DMCGuardBif guard_tab[] = 1, DBIF_ALL }, + { + am_is_integer, + &is_integer_3, + 3, + DBIF_ALL + }, { am_is_list, &is_list_1, diff --git a/erts/emulator/beam/jit/arm/ops.tab b/erts/emulator/beam/jit/arm/ops.tab index 6c0d1a172578..180c6925a5aa 100644 --- a/erts/emulator/beam/jit/arm/ops.tab +++ b/erts/emulator/beam/jit/arm/ops.tab @@ -814,6 +814,7 @@ bif2 _Fail Bif S1 S2 Dst | never_fails(Bif) => nofail_bif2 S1 S2 Bif Dst bif1 Fail Bif S1 Dst => i_bif1 S1 Fail Bif Dst bif2 Fail Bif S1 S2 Dst => i_bif2 S1 S2 Fail Bif Dst +bif3 Fail Bif S1 S2 S3 Dst => i_bif3 S1 S2 S3 Fail Bif Dst nofail_bif2 S1=d S2 Bif Dst | is_eq_exact_bif(Bif) => bif_is_eq_exact S1 S2 Dst nofail_bif2 S1=d S2 Bif Dst | is_ne_exact_bif(Bif) => bif_is_ne_exact S1 S2 Dst diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab index 2b0dfb2e4a9f..5bbbaf747eac 100644 --- a/erts/emulator/beam/jit/x86/ops.tab +++ b/erts/emulator/beam/jit/x86/ops.tab @@ -745,6 +745,7 @@ bif2 _Fail Bif S1 S2 Dst | never_fails(Bif) => nofail_bif2 S1 S2 Bif Dst bif1 Fail Bif S1 Dst => i_bif1 S1 Fail Bif Dst bif2 Fail Bif S1 S2 Dst => i_bif2 S1 S2 Fail Bif Dst +bif3 Fail Bif S1 S2 S3 Dst => i_bif3 S1 S2 S3 Fail Bif Dst nofail_bif2 S1=d S2 Bif Dst | is_eq_exact_bif(Bif) => bif_is_eq_exact S1 S2 Dst nofail_bif2 S1=d S2 Bif Dst | is_ne_exact_bif(Bif) => bif_is_ne_exact S1 S2 Dst diff --git a/erts/emulator/test/bif_SUITE.erl b/erts/emulator/test/bif_SUITE.erl index 5b286b7f6424..3d8782d9dac2 100644 --- a/erts/emulator/test/bif_SUITE.erl +++ b/erts/emulator/test/bif_SUITE.erl @@ -24,6 +24,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/file.hrl"). +-include_lib("stdlib/include/assert.hrl"). -export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2]). @@ -46,7 +47,7 @@ test_length/1, fixed_apply_badarg/1, external_fun_apply3/1, - node_1/1,doctests/1]). + node_1/1,doctests/1,is_integer_3_test/1]). suite() -> [{ct_hooks,[ts_install_cth]}, @@ -64,7 +65,7 @@ all() -> is_process_alive, is_process_alive_signal_from, process_info_blast, os_env_case_sensitivity, verify_middle_queue_save, test_length,fixed_apply_badarg, - external_fun_apply3, node_1, doctests]. + external_fun_apply3, node_1, doctests, is_integer_3_test]. init_per_testcase(guard_bifs_in_erl_bif_types, Config) when is_list(Config) -> skip_missing_erl_bif_types(Config); @@ -1780,6 +1781,72 @@ node_error(E0) -> doctests(_Config) -> shell_docs:test(erlang, []). +is_integer_3_test(_Config) -> + _ = [is_between_ten(X) || X <- lists:seq(-2, 12)], + + false = is_between_ten(0), + true = is_between_ten(1), + true = is_between_ten(10), + false = is_between_ten(11), + + false = is_between_ten(a), + false = is_between_ten(5.0), + false = is_between_ten(-7.0), + false = is_between_ten([1]), + + _ = [begin + is_between_negative(X), + false = is_between_negative(-X) + end || X <- lists:seq(-100, -70)], + + _ = [is_between_mixed(X) || X <- lists:seq(-10, 10)], + + _ = [begin + is_between_bignum(X), + false = is_between_bignum(-X), + false = is_between_bignum(X - (1 bsl 64)) + end || X <- lists:seq((1 bsl 64) - 3, (1 bsl 64) + 10)], + + is_between_badarg(2, 1.5, 10.0), + is_between_badarg(2, 10.0, 1.5), + is_between_badarg(2, 1.5, 10), + is_between_badarg(2, 1, 10.0), + is_between_badarg(2, lower, upper), + + ok. + +-define(IS_BETWEEN_TEST(Name, LB, UB), +Name(X0) -> + F = id(is_integer), + Lower0 = LB, + Upper0 = UB, + Lower = id(Lower0), + Upper = id(Upper0), + + X1 = id(X0), + Result = is_integer(X1, Lower0, Upper0), + Result = is_integer(X1, Lower, Upper), + Result = apply(erlang, F, id([X1, Lower, Upper])), + Result = erlang:F(X1, Lower, Upper), + + false = is_integer(id(X1), Upper, Lower), + + X = id(X1), + Result = is_integer(X) andalso Lower =< X andalso X =< Upper, + Result). + +?IS_BETWEEN_TEST(is_between_ten, 1, 10). +?IS_BETWEEN_TEST(is_between_negative, -89, -77). +?IS_BETWEEN_TEST(is_between_mixed, -7, 7). +?IS_BETWEEN_TEST(is_between_bignum, 1 bsl 64, (1 bsl 64) + 7). + +is_between_badarg(X, A, B) -> + F = id(is_integer), + + ?assertError(badarg, is_integer(id(X), id(A), id(B))), + ?assertError(badarg, erlang:F(X, A, B)), + ?assertError(badarg, apply(erlang, F, id([X, A, B]))). + %% helpers wait_until(Fun) -> diff --git a/erts/emulator/test/exception_SUITE.erl b/erts/emulator/test/exception_SUITE.erl index 6f88b0105b57..db45d942cc7a 100644 --- a/erts/emulator/test/exception_SUITE.erl +++ b/erts/emulator/test/exception_SUITE.erl @@ -940,6 +940,7 @@ error_info(_Config) -> {is_builtin, [1, 2, a]}, {is_function, [abc, bad_arity]}, {is_function, [abc, -1]}, + {is_integer, [5, a, b]}, {is_map_key, [key, not_map]}, {is_process_alive, [abc]}, {is_record, [not_tuple,42]}, diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam index 8bab7acd94da..f229bb4b043f 100644 Binary files a/erts/preloaded/ebin/erlang.beam and b/erts/preloaded/ebin/erlang.beam differ diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam index a64afe58c654..db659c843ca6 100644 Binary files a/erts/preloaded/ebin/init.beam and b/erts/preloaded/ebin/init.beam differ diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 8acce0585289..bd76664c801d 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -484,7 +484,7 @@ A list of binaries. This datatype is useful to use together with -export([abs/1, append/2, element/2, get_module_info/2, hd/1, is_atom/1, is_binary/1, is_bitstring/1, is_boolean/1, - is_float/1, is_function/1, is_function/2, is_integer/1, + is_float/1, is_function/1, is_function/2, is_integer/1, is_integer/3, is_list/1, is_map/1, is_number/1, is_pid/1, is_port/1, is_record/2, is_record/3, is_reference/1, is_tuple/1, load_module/2, load_nif/2, localtime_to_universaltime/2, make_fun/3, @@ -7302,6 +7302,31 @@ false is_tuple(_Term) -> erlang:nif_error(undefined). +%% Shadowed by erl_bif_types: erlang:is_integer/3 +-doc """ +Returns `true` if `Term`, `LB`, and `UB` all evaluate to integers, and `Term` +is between `LB` and `UB` inclusive; otherwise, returns `false`. + +## Examples + +```erlang +1> is_integer(15, 0, 1024). +true +2> is_integer(-1, 0, 1). +false +``` + +Failure: `badarg` if `LB` or `UB` does not evaluate to an integer. +""". +-doc #{ category => terms }. +-spec is_integer(Term, LB, UB) -> boolean() when + Term :: integer(), + LB :: integer(), + UB :: integer(). +is_integer(_Term, _LB, _UB) -> + erlang:nif_error(undefined). + + -doc """ Loads `Module` described by the object code contained within `Binary`. diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl index 3c2b364c5b79..d830870603c8 100644 --- a/lib/compiler/src/beam_asm.erl +++ b/lib/compiler/src/beam_asm.erl @@ -508,7 +508,8 @@ bif_type(fsub, 2) -> {op,fsub}; bif_type(fmul, 2) -> {op,fmul}; bif_type(fdiv, 2) -> {op,fdiv}; bif_type(_, 1) -> bif1; -bif_type(_, 2) -> bif2. +bif_type(_, 2) -> bif2; +bif_type(_, 3) -> bif3. make_op({'%',_}, Dict) -> {[],Dict}; diff --git a/lib/compiler/src/beam_call_types.erl b/lib/compiler/src/beam_call_types.erl index 332be5ca063a..0e375f438567 100644 --- a/lib/compiler/src/beam_call_types.erl +++ b/lib/compiler/src/beam_call_types.erl @@ -100,14 +100,15 @@ will_succeed(erlang, 'bsl'=Op, [LHS, RHS]=Args) -> will_succeed(erlang, '++', [LHS, _RHS]) -> succeeds_if_type(LHS, proper_list()); will_succeed(erlang, '--', [_, _] = Args) -> - succeeds_if_types(Args, proper_list()); + succeeds_if_types(Args, [proper_list(), proper_list()]); will_succeed(erlang, BoolOp, [_, _] = Args) when BoolOp =:= 'and'; BoolOp =:= 'or' -> - succeeds_if_types(Args, beam_types:make_boolean()); + Bool = beam_types:make_boolean(), + succeeds_if_types(Args, [Bool, Bool]); will_succeed(erlang, Op, [_, _] = Args) when Op =:= 'band'; Op =:= 'bor'; Op =:= 'bxor' -> - succeeds_if_types(Args, #t_integer{}); + succeeds_if_types(Args, [#t_integer{}, #t_integer{}]); will_succeed(erlang, bit_size, [Arg]) -> succeeds_if_type(Arg, #t_bs_matchable{}); will_succeed(erlang, byte_size, [Arg]) -> @@ -144,13 +145,18 @@ will_succeed(erlang, map_size, [Arg]) -> will_succeed(erlang, node, [Arg]) -> succeeds_if_type(Arg, identifier); will_succeed(erlang, 'and', [_, _]=Args) -> - succeeds_if_types(Args, beam_types:make_boolean()); + Bool = beam_types:make_boolean(), + succeeds_if_types(Args, [Bool, Bool]); will_succeed(erlang, 'not', [Arg]) -> succeeds_if_type(Arg, beam_types:make_boolean()); will_succeed(erlang, 'or', [_, _]=Args) -> - succeeds_if_types(Args, beam_types:make_boolean()); + Bool = beam_types:make_boolean(), + succeeds_if_types(Args, [Bool, Bool]); will_succeed(erlang, 'xor', [_, _]=Args) -> - succeeds_if_types(Args, beam_types:make_boolean()); + Bool = beam_types:make_boolean(), + succeeds_if_types(Args, [Bool, Bool]); +will_succeed(erlang, 'is_integer', [_, _, _]=Args) -> + succeeds_if_types(Args, [any, #t_integer{}, #t_integer{}]); will_succeed(erlang, setelement, [Pos, Tuple0, _Value]=Args) -> PosRange = #t_integer{elements={1,?MAX_TUPLE_SIZE}}, case {meet(Pos, PosRange), meet(Tuple0, #t_tuple{size=1})} of @@ -231,14 +237,20 @@ fails_on_conflict_1([ArgType | Args], [Required | Types]) -> fails_on_conflict_1([], []) -> 'maybe'. -succeeds_if_types([LHS, RHS], Required) -> - case {succeeds_if_type(LHS, Required), - succeeds_if_type(RHS, Required)} of - {yes, yes} -> yes; - {no, _} -> no; - {_, no} -> no; - {_, _} -> 'maybe' - end. +succeeds_if_types(Ts, Rs) -> + succeeds_if_types_1(Ts, Rs, yes). + +succeeds_if_types_1([T | Ts], [R | Rs], Acc) -> + case succeeds_if_type(T, R) of + yes when Acc =:= yes -> + succeeds_if_types_1(Ts, Rs, Acc); + no -> + no; + _ -> + succeeds_if_types_1(Ts, Rs, 'maybe') + end; +succeeds_if_types_1([], [], Acc) -> + Acc. succeeds_if_type(ArgType, Required) -> case meet(ArgType, Required) of @@ -396,6 +408,8 @@ types(erlang, is_function, [Type]) -> sub_unsafe_type_test(Type, #t_fun{}); types(erlang, is_integer, [Type]) -> sub_unsafe_type_test(Type, #t_integer{}); +types(erlang, is_integer, [_Term, _LB, _UB]) -> + sub_unsafe(beam_types:make_boolean(), [any, #t_integer{}, #t_integer{}]); types(erlang, is_list, [Type]) -> sub_unsafe_type_test(Type, #t_list{}); types(erlang, is_map, [Type]) -> diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl index e507e95db21d..da37f0487e32 100644 --- a/lib/compiler/src/beam_disasm.erl +++ b/lib/compiler/src/beam_disasm.erl @@ -820,6 +820,10 @@ resolve_inst({bif2,Args},Imports,_,_) -> [F,Bif,A1,A2,Reg] = resolve_args(Args), {extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports), {bif,BifName,F,[A1,A2],Reg}; +resolve_inst({bif3,Args},Imports,_,_) -> + [F,Bif,A1,A2,A3,Reg] = resolve_args(Args), + {extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports), + {bif,BifName,F,[A1,A2,A3],Reg}; resolve_inst({allocate,[{u,X0},{u,X1}]},_,_,_) -> {allocate,X0,X1}; resolve_inst({allocate_heap,[{u,X0},{u,X1},{u,X2}]},_,_,_) -> diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl index 5d7beee0e3a5..55b12e618700 100644 --- a/lib/compiler/src/beam_ssa_codegen.erl +++ b/lib/compiler/src/beam_ssa_codegen.erl @@ -2792,6 +2792,7 @@ is_gc_bif(node, [_]) -> false; is_gc_bif(element, [_,_]) -> false; is_gc_bif(get, [_]) -> false; is_gc_bif(is_map_key, [_,_]) -> false; +is_gc_bif(is_integer, [_,_,_]) -> false; is_gc_bif(map_get, [_,_]) -> false; is_gc_bif(tuple_size, [_]) -> false; is_gc_bif(Bif, Args) -> diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index bb55a5deb89e..dbf4ec96b20d 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -269,6 +269,7 @@ module_passes(Opts) -> %% are repeated as required. repeated_passes(Opts) -> Ps = [?PASS(ssa_opt_live), + ?PASS(ssa_opt_is_between), ?PASS(ssa_opt_ne), ?PASS(ssa_opt_bs_create_bin), ?PASS(ssa_opt_dead), @@ -549,7 +550,8 @@ merge_tuple_update_1([], Tuple) -> %%% ssa_opt_split_blocks({#opt_st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) -> - P = fun(#b_set{op={bif,element}}) -> true; + P = fun(#b_set{op={bif,is_integer},args=[_,_,_]}) -> true; + (#b_set{op={bif,element}}) -> true; (#b_set{op=call}) -> true; (#b_set{op=bs_init_writable}) -> true; (#b_set{op=make_fun}) -> true; @@ -559,6 +561,53 @@ ssa_opt_split_blocks({#opt_st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) -> {Blocks,Count} = beam_ssa:split_blocks_before(RPO, P, Blocks0, Count0), {St#opt_st{ssa=Blocks,cnt=Count}, FuncDb}. +%%% +%%% BIF is_integer/3 tests whether a number is between a given range. +%%% When the range is constant, rewrite it into 3 BIFs: is_integer/1 and two +%%% =<'s to enable later optimization. +%%% +ssa_opt_is_between({#opt_st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) -> + {Blocks1, Count1} = ssa_opt_is_between_1(Blocks0, Count0), + {St#opt_st{ssa=Blocks1,cnt=Count1}, FuncDb}. + +ssa_opt_is_between_1([{L,#b_blk{}=B}=Blk0|Ls0], Count0) -> + case B of + #b_blk{is=[#b_set{op={bif,is_integer},dst=Bool1, + args=[_,#b_literal{val=Min}, + #b_literal{val=Max}]}], + last=#b_br{bool=Bool1}}=Blk when is_integer(Min), + is_integer(Max), + Min =< Max -> + {Blk1, Count1} = is_between_rewrite(Count0, L, Blk), + {Ls1, Count2} = ssa_opt_is_between_1(Ls0, Count1), + {Blk1++Ls1, Count2}; + #b_blk{} -> + {Ls1, Count1} = ssa_opt_is_between_1(Ls0, Count0), + {[Blk0|Ls1], Count1} + end; +ssa_opt_is_between_1([], Count0) -> + {[], Count0}. + +is_between_rewrite(Count0, L, Blk0) -> + LowerL = Count0, + UpperL = Count0 + 1, + LowerBool = #b_var{name=Count0}, + UpperBool = #b_var{name=Count0 + 1}, + Count = Count0 + 2, + #b_blk{is=[#b_set{dst=Bool1,args=[Term,LB,UB]}|_], + last=#b_br{fail=Fail}=Br0} = Blk0, + Blk1 = Blk0#b_blk{is=[#b_set{op={bif,is_integer},dst=Bool1, + args=[Term]}], + last=#b_br{bool=Bool1,succ=LowerL,fail=Fail}}, + BlkLower = #b_blk{is=[#b_set{op={bif,'=<'},dst=LowerBool, + args=[LB,Term]}], + last=#b_br{bool=LowerBool,succ=UpperL,fail=Fail}}, + BlkUpper = #b_blk{is=[#b_set{op={bif,'=<'},dst=UpperBool, + args=[Term,UB]}], + last=Br0#b_br{bool=UpperBool}}, + Blocks = [{L, Blk1}, {LowerL, BlkLower}, {UpperL, BlkUpper}], + {Blocks, Count}. + %%% %%% Coalesce phi nodes. %%% diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl index 0ed15783be9f..23f5df622298 100644 --- a/lib/compiler/src/beam_ssa_pre_codegen.erl +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -2822,6 +2822,7 @@ use_zreg(wait_timeout) -> yes; %% avoid using a z register if their result is used directly in a branch. use_zreg(call) -> no; use_zreg({bif,element}) -> no; +use_zreg({bif,is_integer}) -> no; use_zreg({bif,is_map_key}) -> no; use_zreg({bif,is_record}) -> no; use_zreg({bif,map_get}) -> no; diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 03f97565b628..4d40651df8c7 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -2721,6 +2721,15 @@ make_number({'-inf','+inf'}) -> make_number({_,_}=R) -> #t_number{elements=R}. +make_integer({'-inf','+inf'}) -> + #t_integer{}; +make_integer({'-inf',_}=R) -> + #t_integer{elements=R}; +make_integer({Min,Max}=R) when is_integer(Min), Min =< Max -> + #t_integer{elements=R}; +make_integer(_) -> + #t_integer{}. + inv_relop({bif,Op}) -> inv_relop_1(Op); inv_relop(_) -> none. @@ -2736,6 +2745,14 @@ infer_get_range(#t_integer{elements=R}) -> R; infer_get_range(#t_number{elements=R}) -> R; infer_get_range(_) -> unknown. +infer_integer_get_range(Arg, Ts) -> + case concrete_type(Arg, Ts) of + #t_integer{elements={_,_}=R} -> + R; + _ -> + {'-inf','+inf'} + end. + infer_br_value(_V, _Bool, none) -> none; infer_br_value(V, Bool, NewTs) -> @@ -2814,6 +2831,18 @@ infer_type({bif,is_function}, [#b_var{}=Arg, Arity], _Ts, _Ds) -> infer_type({bif,is_integer}, [#b_var{}=Arg], _Ts, _Ds) -> T = {Arg, #t_integer{}}, {[T], [T]}; +infer_type({bif,is_integer}, [#b_var{}=Arg, + #b_literal{val=Min}, + #b_literal{val=Max}], _Ts, _Ds) when Min =< Max -> + T = {Arg, beam_types:make_integer(Min, Max)}, + {[T], [T]}; +infer_type({bif,is_integer}, [#b_var{}=Arg,Min0,Max0], Ts, _Ds) -> + {Min,_} = infer_integer_get_range(Min0, Ts), + {_,Max} = infer_integer_get_range(Max0, Ts), + T = {Arg, make_integer({Min,Max})}, + %% Conservatively never attempt to subtract the type; subtraction + %% will most likely be incorrect or useless. + {[T], []}; infer_type({bif,is_list}, [#b_var{}=Arg], _Ts, _Ds) -> T = {Arg, #t_list{}}, {[T], [T]}; diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index c44091a408d5..0c5aec9b07fe 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -2288,6 +2288,22 @@ infer_types_1(#value{op={bif,is_function},args=[Src]}, Val, Op, Vst) -> infer_type_test_bif(#t_fun{}, Src, Val, Op, Vst); infer_types_1(#value{op={bif,is_integer},args=[Src]}, Val, Op, Vst) -> infer_type_test_bif(#t_integer{}, Src, Val, Op, Vst); +infer_types_1(#value{op={bif,is_integer},args=[Src, + {integer, Min}, + {integer, Max}]}, Val, Op, Vst) -> + infer_type_test_bif(beam_types:make_integer(Min, Max), Src, Val, Op, Vst); +infer_types_1(#value{op={bif,is_integer},args=[Src,Min0,Max0]}, Val, Op, Vst) -> + %% If there is at least one unknown bound, we cannot subtract + %% when 'false'. + {Min,_} = infer_integer_get_range(Min0, Vst), + {_,Max} = infer_integer_get_range(Max0, Vst), + Type = make_integer({Min, Max}), + case Val of + {atom, Bool} when Op =:= eq_exact, Bool; Op =:= ne_exact, not Bool -> + update_type(fun meet/2, Type, Src, Vst); + _ -> + Vst + end; infer_types_1(#value{op={bif,is_list},args=[Src]}, Val, Op, Vst) -> infer_type_test_bif(#t_list{}, Src, Val, Op, Vst); infer_types_1(#value{op={bif,is_map},args=[Src]}, Val, Op, Vst) -> @@ -2354,6 +2370,23 @@ invert_relop('=<') -> '>'; invert_relop('>=') -> '<'; invert_relop('>') -> '=<'. +infer_integer_get_range(Arg, Vst) -> + case get_term_type(Arg, Vst) of + #t_integer{elements={_,_}=R} -> + R; + _ -> + {'-inf','+inf'} + end. + +make_integer({'-inf','+inf'}) -> + #t_integer{}; +make_integer({'-inf',_}=R) -> + #t_integer{elements=R}; +make_integer({Min,Max}=R) when is_integer(Min), Min =< Max -> + #t_integer{elements=R}; +make_integer(_) -> + #t_integer{}. + %%% %%% Keeping track of types. %%% diff --git a/lib/compiler/src/erl_bifs.erl b/lib/compiler/src/erl_bifs.erl index ee81d218a42f..e3d137909d12 100644 --- a/lib/compiler/src/erl_bifs.erl +++ b/lib/compiler/src/erl_bifs.erl @@ -114,6 +114,7 @@ is_pure(erlang, is_float, 1) -> true; is_pure(erlang, is_function, 1) -> true; is_pure(erlang, is_function, 2) -> true; is_pure(erlang, is_integer, 1) -> true; +is_pure(erlang, is_integer, 3) -> true; is_pure(erlang, is_list, 1) -> true; is_pure(erlang, is_map, 1) -> true; is_pure(erlang, is_map_key, 2) -> true; diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab index 588ec5210ee1..d54edd0fef26 100755 --- a/lib/compiler/src/genop.tab +++ b/lib/compiler/src/genop.tab @@ -710,3 +710,11 @@ BEAM_FORMAT_NUMBER=0 ## * {atom,entry} - Function entry. ## * {atom,line} - Any other line in the function. 184: debug_line/4 + +# OTP 29 + +## @spec bif3 Lbl Bif Arg1 Arg2 Arg3 Reg +## @doc Call the bif Bif with the arguments Arg1, Arg2, and Arg3, +## and store the result in Reg. +## On failure jump to Lbl. +185: bif3/6 diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl index 1d6ee3232b24..3656672e36ec 100644 --- a/lib/compiler/test/guard_SUITE.erl +++ b/lib/compiler/test/guard_SUITE.erl @@ -26,6 +26,7 @@ -compile([nowarn_obsolete_guard]). -include_lib("syntax_tools/include/merl.hrl"). +-include_lib("stdlib/include/assert.hrl"). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, @@ -44,7 +45,7 @@ bad_constants/1,bad_guards/1, guard_in_catch/1,beam_bool_SUITE/1, repeated_type_tests/1,use_after_branch/1, - body_in_guard/1]). + body_in_guard/1,is_integer_3_guard/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -63,12 +64,13 @@ groups() -> basic_andalso_orelse,traverse_dcd, check_qlc_hrl,andalso_semi,t_tuple_size,binary_part, bad_constants,bad_guards,guard_in_catch,beam_bool_SUITE, - repeated_type_tests,use_after_branch,body_in_guard]}, + repeated_type_tests,use_after_branch,body_in_guard, + is_integer_3_guard]}, {slow,[],[literal_type_tests,generated_combinations]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), - Config. + id(Config). end_per_suite(_Config) -> ok. @@ -233,6 +235,26 @@ basic_not(Config) when is_list(Config) -> check(fun() -> if not (False =:= true) -> ok; true -> error end end, ok), check(fun() -> if not (Glurf =:= true) -> ok; true -> error end end, ok), + check(fun() -> if + not is_integer(7, Glurf, Glurf) -> ok; + true -> error + end + end, error), + + check(fun() -> if + not is_integer(10, C, D) -> ok; + true -> error + end + end, ok), + + check(fun() -> + X = id(20), + if + not is_integer(X, 1, 10) -> ok; + true -> error + end + end, ok), + ok. complex_not(Config) when is_list(Config) -> @@ -783,6 +805,24 @@ more_or_guards(Config) when is_list(Config) -> element(19, ATuple) -> ok; true -> error end end, error), + + check(fun() -> + Lower = id(a), + Upper = id(b), + if + true or is_integer(1, Lower, Upper) -> ok; + true -> error + end + end, error), + + check(fun() -> + Lower = id(1), + Upper = id(10), + if + false or is_integer(1, Lower, Upper) -> ok; + true -> error + end + end, ok), ok. complex_or_guards(Config) when is_list(Config) -> @@ -1561,7 +1601,8 @@ is_digit(N) -> Bool = is_digit_8(N), Bool = is_digit_9(42, N), Bool = is_digit_10(N, 0), - Bool = is_digit_11(N, 0). + Bool = is_digit_11(N, 0), + Bool = is_digit_12(N). is_digit_1(X) when 16#0660 =< X, X =< 16#0669 -> true; is_digit_1(X) when 16#0030 =< X, X =< 16#0039 -> true; @@ -1622,6 +1663,12 @@ is_digit_11(X, _) when 16#0030 =< X, X =< 16#0039 -> true; is_digit_11(X, _) when 16#06F0 =< X, X =< 16#06F9 -> true; is_digit_11(_, _) -> false. +is_digit_12(X) when is_integer(X, 16#0030, 16#0039); + is_integer(X, 16#06F0, 16#06F9); + is_integer(X, 16#0660, 16#0669) -> true; +is_digit_12(16#0670) -> false; +is_digit_12(_) -> false. + rel_op_combinations_2(0, _) -> ok; rel_op_combinations_2(N, Range) -> @@ -1642,7 +1689,8 @@ broken_range(N) -> Bool = broken_range_10(N), Bool = broken_range_11(N), Bool = broken_range_12(N), - Bool = broken_range_13(N). + Bool = broken_range_13(N), + Bool = broken_range_14(N). broken_range_1(X) when X >= 10, X =< 20, X =/= 13 -> true; broken_range_1(X) when X >= 3, X =< 5 -> true; @@ -1707,6 +1755,10 @@ broken_range_13(X) when X >= 10, X =< 20, 13 =/= X -> true; broken_range_13(X) when X >= 3, X =< 5 -> true; broken_range_13(_) -> false. +broken_range_14(X) when is_integer(X, 10, 20), 13 =/= X -> true; +broken_range_14(X) when is_integer(X, 3, 5) -> true; +broken_range_14(_) -> false. + rel_op_combinations_3(0, _) -> ok; rel_op_combinations_3(N, Red) -> @@ -3363,6 +3415,99 @@ body_in_guard(_Config) -> demonitor(Mon) end. +is_integer_3_guard(_Config) -> + Lower = id(1), + Upper = id(10), + _ = [begin + Expected = Lower =< X andalso X =< Upper, + Expected = is_integer_3_guard_1(X, Lower, Upper), + false = is_integer_3_guard_1(float(X), Lower, Upper) + end || X <- lists:seq(-7, 17)], + + ?assertError(badarg, is_integer_3_guard_1(2, 1.5, 10)), + ?assertError(badarg, is_integer_3_guard_1(2, true, 10)), + ?assertError(badarg, is_integer_3_guard_1(2, 10, b)), + + false = is_integer_3_guard_2(id(0)), + true = is_integer_3_guard_2(id(1)), + true = is_integer_3_guard_2(id(32)), + true = is_integer_3_guard_2(id(1024)), + false = is_integer_3_guard_2(id(1025)), + + true = is_integer_3_guard_3(id(0)), + false = is_integer_3_guard_3(id(1)), + false = is_integer_3_guard_3(id(32)), + false = is_integer_3_guard_3(id(1024)), + true = is_integer_3_guard_3(id(1025)), + + 1 = is_integer_3_guard_4(id(1), id(0), id(5)), + false = is_integer_3_guard_4(id(1), id(-1), id(0)), + + 2 = is_integer_3_guard_5(id(2), id(0), id(9)), + false = is_integer_3_guard_5(id(1024), id(0), id(9)), + + false = is_integer_3_guard_6(id(0), id(1), id(-1)), + true = is_integer_3_guard_6(id(0), id(0), id(1)), + + false = is_integer_3_guard_7(id(0), id(1), id(9)), + true = is_integer_3_guard_7(id(1), id(1), id(9)), + + true = is_integer_3_guard_8(id(17), id(12), id(20)), + false = is_integer_3_guard_8(id(0), id(12), id(20)), + true = is_integer_3_guard_8(id(5), id(1), id(20)), + + ok. + +is_integer_3_guard_1(X, LB, UB) when is_integer(X, LB, UB) -> + true = is_integer(X, LB, UB); +is_integer_3_guard_1(X, LB, UB) -> + is_integer(X, LB, UB). + +is_integer_3_guard_2(X) when is_integer(X, 1, 1024) -> + true = is_integer(X, 1, 1024); +is_integer_3_guard_2(X) -> + is_integer(X, 1, 1024). + +is_integer_3_guard_3(X) when not is_integer(X, 1, 1024) -> + true = not is_integer(X, 1, 1024); +is_integer_3_guard_3(X) -> + not is_integer(X, 1, 1024). + +is_integer_3_guard_4(X, LB, UB) when 0 =< LB, UB < 10, + is_integer(X, LB, UB) -> + is_integer_3_guard_4_id(X); +is_integer_3_guard_4(X, LB, UB) -> + is_integer(X, LB, UB). + +is_integer_3_guard_4_id(I) -> I. + +is_integer_3_guard_5(X, LB, UB) when 0 =< LB, is_integer(UB), + UB < 10, is_integer(X, LB, UB) -> + is_integer_3_guard_5_id(X); +is_integer_3_guard_5(X, LB, UB) -> + is_integer(X, LB, UB). + +is_integer_3_guard_5_id(I) -> I. + +%% Test incorrect order of bounds. +is_integer_3_guard_6(X, LB, UB) when 10 =< LB, UB < 0, is_integer(X, LB, UB) -> + is_integer(X, LB, UB); +is_integer_3_guard_6(X, LB, UB) -> + is_integer(X, LB, UB). + +is_integer_3_guard_7(X, LB, UB) when is_number(UB), UB < 10, is_integer(X, LB, UB) -> + is_integer(X, LB, UB); +is_integer_3_guard_7(X, LB, UB) -> + is_integer(X, LB, UB). + +is_integer_3_guard_8(X, LB, UB) when is_number(LB), LB > 10, is_integer(X, LB, UB) -> + is_integer_3_guard_8_id(X), + is_integer(X, LB, UB); +is_integer_3_guard_8(X, LB, UB) -> + is_integer(X, LB, UB). + +is_integer_3_guard_8_id(I) -> I. + %% Call this function to turn off constant propagation. id(I) -> I. diff --git a/lib/dialyzer/src/erl_bif_types.erl b/lib/dialyzer/src/erl_bif_types.erl index 311b4cfc9e05..b54116ed4d77 100644 --- a/lib/dialyzer/src/erl_bif_types.erl +++ b/lib/dialyzer/src/erl_bif_types.erl @@ -2243,6 +2243,8 @@ arg_types(erlang, is_function, 2) -> [t_any(), t_arity()]; arg_types(erlang, is_integer, 1) -> [t_any()]; +arg_types(erlang, is_integer, 3) -> + [t_any(),t_integer(), t_integer()]; arg_types(erlang, is_list, 1) -> [t_any()]; arg_types(erlang, is_map, 1) -> diff --git a/lib/kernel/src/erl_erts_errors.erl b/lib/kernel/src/erl_erts_errors.erl index c4b852987472..c4e8283b7cda 100644 --- a/lib/kernel/src/erl_erts_errors.erl +++ b/lib/kernel/src/erl_erts_errors.erl @@ -477,6 +477,8 @@ format_erlang_error(is_function, [_,Arity], _) -> is_integer(Arity) -> range; true -> not_integer end]; +format_erlang_error(is_integer, [_,_,_], _) -> + [not_integer]; format_erlang_error(is_map_key, [_,_], _) -> [[],not_map]; format_erlang_error(is_process_alive, [Arg], _) -> diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl index 34d3b5c5c06a..6ac0ccbee3e2 100644 --- a/lib/stdlib/src/erl_internal.erl +++ b/lib/stdlib/src/erl_internal.erl @@ -86,6 +86,7 @@ guard_bif(element, 2) -> true; guard_bif(float, 1) -> true; guard_bif(floor, 1) -> true; guard_bif(hd, 1) -> true; +guard_bif(is_integer, 3) -> true; guard_bif(is_map_key, 2) -> true; guard_bif(length, 1) -> true; guard_bif(map_size, 1) -> true; @@ -351,6 +352,7 @@ bif(is_float, 1) -> true; bif(is_function, 1) -> true; bif(is_function, 2) -> true; bif(is_integer, 1) -> true; +bif(is_integer, 3) -> true; bif(is_list, 1) -> true; bif(is_map, 1) -> true; bif(is_map_key, 2) -> true;