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

openSUSE Build Service is sponsored by