File 2861-erts-Add-guard-BIF-erlang-is_integer-3.patch of Package erlang
From 64b2fde4e58e0f743cb910afff656e06fdd2312d Mon Sep 17 00:00:00 2001
From: Isabell Huang <isabell@erlang.org>
Date: Mon, 9 Jun 2025 10:51:57 +0200
Subject: [PATCH] erts: Add guard BIF `erlang:is_integer/3`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add guard BIF `erlang:is_integer/3`, following the design of the
original EEP-16, only changing the name from `is_between` to
`is_integer`. This BIF takes in 3 parameters, `Term`, `LowerBound`,
and `UpperBound`.
It returns `true` if `Term`, `LowerBound`, and `UpperBound` are
all integers, and `LowerBound =< Term =< UpperBound`; otherwise,
it returns false.
Failure: `badarg` if `LowerBound` or `UpperBound` does not evaluate to
an integer.
Example:
````
1> is_integer(2, 1, 10).
true
2> is_integer(11, 1, 10).
false
3> is_integer(1, 1.0, 10.0).
** exception error: bad argument
in function is_integer/3
called as is_integer(1,1.0,10.0)
````
Co-authored-by: Björn Gustavsson <bjorn@erlang.org>
Co-authored-by: John Högberg <john@erlang.org>
---
erts/emulator/beam/bif.tab | 5 +
erts/emulator/beam/emu/ops.tab | 2 +
erts/emulator/beam/erl_bif_guard.c | 14 ++
erts/emulator/beam/erl_db_util.c | 6 +
erts/emulator/beam/jit/arm/ops.tab | 1 +
erts/emulator/beam/jit/x86/ops.tab | 1 +
erts/emulator/test/bif_SUITE.erl | 71 +++++++++-
erts/emulator/test/exception_SUITE.erl | 1 +
erts/preloaded/ebin/erlang.beam | Bin 40316 -> 40356 bytes
erts/preloaded/ebin/init.beam | Bin 27912 -> 27924 bytes
erts/preloaded/src/erlang.erl | 27 +++-
lib/compiler/src/beam_asm.erl | 3 +-
lib/compiler/src/beam_call_types.erl | 42 ++++--
lib/compiler/src/beam_disasm.erl | 4 +
lib/compiler/src/beam_ssa_codegen.erl | 1 +
lib/compiler/src/beam_ssa_opt.erl | 51 ++++++-
lib/compiler/src/beam_ssa_pre_codegen.erl | 1 +
lib/compiler/src/beam_ssa_type.erl | 29 ++++
lib/compiler/src/beam_validator.erl | 33 +++++
lib/compiler/src/erl_bifs.erl | 1 +
lib/compiler/src/genop.tab | 8 ++
lib/compiler/test/guard_SUITE.erl | 155 +++++++++++++++++++++-
lib/dialyzer/src/erl_bif_types.erl | 2 +
lib/kernel/src/erl_erts_errors.erl | 2 +
lib/stdlib/src/erl_internal.erl | 2 +
25 files changed, 438 insertions(+), 24 deletions(-)
diff --git a/erts/emulator/beam/bif.tab b/erts/emulator/beam/bif.tab
index 95fcb0589f..e03f312443 100644
--- a/erts/emulator/beam/bif.tab
+++ b/erts/emulator/beam/bif.tab
@@ -813,3 +813,8 @@ bif erl_debugger:peek_xreg/3
# New in 28.1.
#
bif erts_debug:unaligned_bitstring/2
+
+#
+# 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 68eebde0f2..265ca44b55 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 258e48a51e..5fc801179f 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 de67d804c0..1ffe59aa5e 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 6c0d1a1725..180c6925a5 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 2b0dfb2e4a..5bbbaf747e 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 5b286b7f64..3d8782d9da 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]).
+ node_1/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].
+ external_fun_apply3, node_1, 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) ->
ok
end.
+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 6f88b0105b..db45d942cc 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/src/erlang.erl b/erts/preloaded/src/erlang.erl
index 8acce05852..bd76664c80 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 3c2b364c5b..d830870603 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 332be5ca06..0e375f4385 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 e507e95db2..da37f0487e 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 5d7beee0e3..55b12e6187 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 bb55a5deb8..dbf4ec96b2 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(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 0ed15783be..23f5df6222 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 03f97565b6..4d40651df8 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 c44091a408..0c5aec9b07 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 ee81d218a4..e3d137909d 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 588ec5210e..d54edd0fef 100755
--- a/lib/compiler/src/genop.tab
+++ b/lib/compiler/src/genop.tab
@@ -695,3 +695,11 @@
## @spec executable_line Location Index
## @doc Provide location for an executable line.
183: executable_line/2
+
+# 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 1d6ee3232b..3656672e36 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,
@@ -41,7 +41,8 @@
check_qlc_hrl/1,andalso_semi/1,t_tuple_size/1,binary_part/1,
bad_constants/1,bad_guards/1,
guard_in_catch/1,beam_bool_SUITE/1,
- repeated_type_tests/1,use_after_branch/1]).
+ repeated_type_tests/1,use_after_branch/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]},
+ repeated_type_tests,use_after_branch,
+ 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 @@ use_after_branch_1(A) ->
false -> {id(Boolean), gaffel}
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 311b4cfc9e..b54116ed4d 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 c4b8529874..c4e8283b7c 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 34d3b5c5c0..6ac0ccbee3 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;
--
2.51.0