diff options
author | Björn Gustavsson <bjorn@erlang.org> | 2022-08-02 09:58:05 +0200 |
---|---|---|
committer | Björn Gustavsson <bjorn@erlang.org> | 2022-09-02 05:52:15 +0200 |
commit | 4f0ec73674b5c042084b528642185f968f7d9981 (patch) | |
tree | f4ab47cc625dc7fc47a4e7dfd798dbd4a04693ab | |
parent | f962bc3636d4f8bd25d77ace27294c274c49a6ca (diff) | |
download | erlang-4f0ec73674b5c042084b528642185f968f7d9981.tar.gz |
Optimize binary matching for fixed-width segments
Consider this function:
foo(<<A:6, B:6, C:6, D:6>>) ->
{A, B, C, D}.
The compiler in Erlang/OTP 25 and earlier would generate the following
code for doing the binary matching:
{test,bs_start_match3,{f,1},1,[{x,0}],{x,1}}.
{bs_get_position,{x,1},{x,0},2}.
{test,bs_get_integer2,
{f,3},
2,
[{x,1},
{integer,6},
1,
{field_flags,[{anno,[4,{file,"t.erl"}]},unsigned,big]}],
{x,2}}.
{test,bs_get_integer2,
{f,3},
3,
[{x,1},
{integer,6},
1,
{field_flags,[{anno,[4,{file,"t.erl"}]},unsigned,big]}],
{x,3}}.
{test,bs_get_integer2,
{f,3},
4,
[{x,1},
{integer,6},
1,
{field_flags,[{anno,[4,{file,"t.erl"}]},unsigned,big]}],
{x,4}}.
{test,bs_get_integer2,
{f,3},
5,
[{x,1},
{integer,6},
1,
{field_flags,[{anno,[4,{file,"t.erl"}]},unsigned,big]}],
{x,5}}.
{test,bs_test_tail2,{f,3},[{x,1},0]}.
That is, there would be one instruction for each segment being
matched. Having separate match instructions for each segment makes it
difficult for the JIT to do any serious optimization. Currently, when
matching a segment with a size that is not a multiple of 8, the JIT
will generate code that calls a helper function. Common sizes such as
8, 16, and 32 are specially optimized with inline code in the x86 JIT
and in the non-JIT BEAM VM.
This commit introduces a new `bs_match` instruction for matching of
integer and binary segments of fixed size. Here is the generated code
for the example:
{test,bs_start_match3,{f,1},1,[{x,0}],{x,1}}.
{bs_get_position,{x,1},{x,0},2}.
{bs_match,{f,3},
{x,1},
{commands,[{ensure_exactly,24},
{integer,2,{literal,[]},6,1,{x,2}},
{integer,3,{literal,[]},6,1,{x,3}},
{integer,4,{literal,[]},6,1,{x,4}},
{integer,5,{literal,[]},6,1,{x,5}}]}}.
Having only one instruction for the matching allows the JIT to
generate faster code. The generated code will do the following:
* Test that the size of the binary being matched is exactly 24 bits.
* Read 24 bits from the binary into a temporary CPU register.
* For each segment, extract the integer from the temporary register
by shifting and masking.
Because of the before-mentioned optimization for certain common
segment sizes, the main part of the Base64 encoding in the `base64`
module is currently implemented in the following non-intuitive way:
encode_binary(<<B1:8, B2:8, B3:8, Ls/bits>>, A) ->
BB = (B1 bsl 16) bor (B2 bsl 8) bor B3,
encode_binary(Ls,
<<A/bits,(b64e(BB bsr 18)):8,
(b64e((BB bsr 12) band 63)):8,
(b64e((BB bsr 6) band 63)):8,
(b64e(BB band 63)):8>>)
With the new optimization, it is now possible to express the Base64
encoding in a more natural way, which is also faster than before:
encode_binary(<<B1:6, B2:6, B3:6, B4:6, Ls/bits>>, A) ->
encode_binary(Ls,
<<A/bits,
(b64e(B1)):8,
(b64e(B2)):8,
(b64e(B3)):8,
(b64e(B4)):8>>)
40 files changed, 3838 insertions, 451 deletions
diff --git a/.gitignore b/.gitignore index c88056e46e..772a0c2258 100644 --- a/.gitignore +++ b/.gitignore @@ -240,6 +240,7 @@ JAVADOC-GENERATED /lib/compiler/test/*_inline_SUITE.erl /lib/compiler/test/*_r23_SUITE.erl /lib/compiler/test/*_r24_SUITE.erl +/lib/compiler/test/*_r25_SUITE.erl /lib/compiler/test/*_no_module_opt_SUITE.erl /lib/compiler/test/*_no_type_opt_SUITE.erl /lib/compiler/test/*_dialyzer_SUITE.erl diff --git a/bootstrap/lib/compiler/ebin/compiler.appup b/bootstrap/lib/compiler/ebin/compiler.appup index aa537986a5..2585c4a26d 100644 --- a/bootstrap/lib/compiler/ebin/compiler.appup +++ b/bootstrap/lib/compiler/ebin/compiler.appup @@ -16,7 +16,7 @@ %% limitations under the License. %% %% %CopyrightEnd% -{"8.1", +{"8.2", [{<<".*">>,[{restart_application, compiler}]}], [{<<".*">>,[{restart_application, compiler}]}] }. diff --git a/erts/.gitignore b/erts/.gitignore index 954e922492..e26eca8917 100644 --- a/erts/.gitignore +++ b/erts/.gitignore @@ -18,6 +18,7 @@ /emulator/test/Emakefile /emulator/test/*.beam /emulator/test/*_no_opt_SUITE.erl +/emulator/test/*_r25_SUITE.erl /emulator/pcre/pcre_exec_loop_break_cases.inc /emulator/beam/erl_db_insert_list.ycf.h diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index b04a5f6052..1332f3a0d9 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -256,6 +256,7 @@ atom enable_trace atom enabled atom endian atom env +atom ensure_at_least ensure_exactly atom eof atom eol atom Eq='=:=' @@ -324,6 +325,7 @@ atom get_all_trap atom get_internal_state_blocked atom get_seq_token atom get_size +atom get_tail atom get_tcw atom gather_gc_info_result atom gather_io_bytes @@ -645,6 +647,7 @@ atom set_tcw_fake atom short atom shutdown atom sighup +atom signed atom sigterm atom sigusr1 atom sigusr2 @@ -659,6 +662,7 @@ atom sigtstp atom sigquit atom silent atom size +atom skip atom spawn_executable atom spawn_driver atom spawn_init diff --git a/erts/emulator/beam/emu/beam_emu.c b/erts/emulator/beam/emu/beam_emu.c index dff6a274dc..5130eed5a2 100644 --- a/erts/emulator/beam/emu/beam_emu.c +++ b/erts/emulator/beam/emu/beam_emu.c @@ -313,6 +313,8 @@ void process_main(ErtsSchedulerData *esdp) #endif #endif + Uint bitdata = 0; + Uint64 start_time = 0; /* Monitor long schedule */ ErtsCodePtr start_time_i = NULL; diff --git a/erts/emulator/beam/emu/bs_instrs.tab b/erts/emulator/beam/emu/bs_instrs.tab index fe37b6fe2c..f84cb29805 100644 --- a/erts/emulator/beam/emu/bs_instrs.tab +++ b/erts/emulator/beam/emu/bs_instrs.tab @@ -1853,3 +1853,274 @@ i_bs_start_match3.execute(Live, Fail, Dst) { } %endif + +// +// New instructions introduced in OTP 26 for matching of integers and +// binaries of fixed sizes follow. +// + +// +// i_bs_ensure_bits Ctx Size Fail +// + +i_bs_ensure_bits := i_bs_ensure_bits.fetch.execute; + +i_bs_ensure_bits.head() { + Eterm context; +} + +i_bs_ensure_bits.fetch(Src) { + context = $Src; +} + +i_bs_ensure_bits.execute(NumBits, Fail) { + ErlBinMatchBuffer* mb = ms_matchbuffer(context); + Uint size = $NumBits; + if (mb->size - mb->offset < size) { + $FAIL($Fail); + } +} + +// +// i_bs_ensure_bits_unit Ctx Size Unit Fail +// + +i_bs_ensure_bits_unit := i_bs_ensure_bits_unit.fetch.execute; + +i_bs_ensure_bits_unit.head() { + Eterm context; +} + +i_bs_ensure_bits_unit.fetch(Src) { + context = $Src; +} + +i_bs_ensure_bits_unit.execute(NumBits, Unit, Fail) { + ErlBinMatchBuffer* mb = ms_matchbuffer(context); + Uint size = $NumBits; + Uint diff; + + if ((diff = mb->size - mb->offset) < size) { + $FAIL($Fail); + } + if ((diff - size) % $Unit != 0) { + $FAIL($Fail); + } +} + +// +// i_bs_read_bits Ctx Size +// i_bs_ensure_bits_read Ctx Size Fail +// + +i_bs_read_bits := i_bs_read_bits.fetch.execute; +i_bs_ensure_bits_read := i_bs_read_bits.fetch.ensure_bits.execute; + +i_bs_read_bits.head() { + ErlBinMatchBuffer* mb; + Uint size; +} + +i_bs_read_bits.fetch(Src, NumBits) { + mb = ms_matchbuffer($Src); + size = $NumBits; +} + +i_bs_read_bits.ensure_bits(Fail) { + if (mb->size - mb->offset < size) { + $FAIL($Fail); + } +} + +i_bs_read_bits.execute() { + byte *byte_ptr; + Uint bit_offset = mb->offset % 8; + Uint num_bytes_to_read = (size + 7) / 8; + Uint num_partial = size % 8; + + if ((num_partial == 0 && bit_offset != 0) || + (num_partial != 0 && bit_offset > 8 - num_partial)) { + num_bytes_to_read++; + } + + bitdata = 0; + byte_ptr = mb->base + (mb->offset >> 3); + mb->offset += size; + switch (num_bytes_to_read) { +#ifdef ARCH_64 + case 9: + case 8: + bitdata = bitdata << 8 | *byte_ptr++; + case 7: + bitdata = bitdata << 8 | *byte_ptr++; + case 6: + bitdata = bitdata << 8 | *byte_ptr++; + case 5: + bitdata = bitdata << 8 | *byte_ptr++; +#else + case 5: +#endif + case 4: + bitdata = bitdata << 8 | *byte_ptr++; + case 3: + bitdata = bitdata << 8 | *byte_ptr++; + case 2: + bitdata = bitdata << 8 | *byte_ptr++; + case 1: + bitdata = bitdata << 8 | *byte_ptr++; + } + + if (num_bytes_to_read <= sizeof(Uint)) { + bitdata <<= 8 * (sizeof(Uint) - num_bytes_to_read) + bit_offset; + } else { + bitdata <<= bit_offset; + bitdata = bitdata | ((*byte_ptr << bit_offset) >> 8); + } +} + +// i_bs_extract_integer Size Dst +i_bs_extract_integer(NumBits, Dst) { + Uint size = $NumBits; + Eterm result; + + result = bitdata >> (8 * sizeof(Uint) - size); + result = make_small(result); + bitdata <<= size; + $Dst = result; +} + +// i_bs_read_integer_8 Ctx Dst +i_bs_read_integer_8(Ctx, Dst) { + ErlBinMatchBuffer* mb = ms_matchbuffer($Ctx); + byte *byte_ptr; + Uint bit_offset = mb->offset % 8; + Eterm result; + + byte_ptr = mb->base + (mb->offset >> 3); + mb->offset += 8; + result = byte_ptr[0]; + if (bit_offset != 0) { + result = result << 8 | byte_ptr[1]; + result = ((result << bit_offset) >> 8) & 0xff; + } + result = make_small(result); + $Dst = result; +} + +// +// i_bs_get_fixed_integer Ctx Size Flags Dst +// + +i_bs_get_fixed_integer := i_bs_get_fixed_integer.fetch.execute; + +i_bs_get_fixed_integer.head() { + Eterm context; +} + +i_bs_get_fixed_integer.fetch(Src) { + context = $Src; +} + +i_bs_get_fixed_integer.execute(Size, Flags, Dst) { + ErlBinMatchBuffer* mb; + Uint size = $Size; + Eterm result; + + mb = ms_matchbuffer(context); + LIGHT_SWAPOUT; + result = erts_bs_get_integer_2(c_p, size, $Flags, mb); + LIGHT_SWAPIN; + HEAP_SPACE_VERIFIED(0); + $Dst = result; +} + +// +// i_get_fixed_binary Ctx Size Dst +// + +i_bs_get_fixed_binary := i_bs_get_fixed_binary.fetch.execute; + +i_bs_get_fixed_binary.head() { + Eterm context; +} + +i_bs_get_fixed_binary.fetch(Src) { + context = $Src; +} + +i_bs_get_fixed_binary.execute(Size, Dst) { + ErlBinMatchBuffer* mb; + Uint size = $Size; + Eterm* htop; + Eterm result; + + ASSERT(header_is_bin_matchstate(*boxed_val(context))); + + htop = HTOP; + mb = ms_matchbuffer(context); + result = erts_extract_sub_binary(&htop, mb->orig, mb->base, + mb->offset, size); + HTOP = htop; + + mb->offset += size; + + $Dst = result; +} + +// +// i_get_tail Ctx Dst +// + +i_bs_get_tail := i_bs_get_tail.fetch.execute; + +i_bs_get_tail.head() { + Eterm context; +} + +i_bs_get_tail.fetch(Src) { + context = $Src; +} + +i_bs_get_tail.execute(Dst) { + ErlBinMatchBuffer* mb; + Eterm* htop; + Eterm result; + + ASSERT(header_is_bin_matchstate(*boxed_val(context))); + + htop = HTOP; + mb = ms_matchbuffer(context); + result = erts_extract_sub_binary(&htop, mb->orig, mb->base, + mb->offset, mb->size - mb->offset); + HTOP = htop; + + $Dst = result; +} + +// +// i_bs_skip Ctx Size +// + +i_bs_skip := i_bs_skip.fetch.execute; + +i_bs_skip.head() { + Eterm context; +} + +i_bs_skip.fetch(Src) { + context = $Src; +} + +i_bs_skip.execute(Size) { + ErlBinMatchBuffer* mb; + Uint size = $Size; + + ASSERT(header_is_bin_matchstate(*boxed_val(context))); + mb = ms_matchbuffer(context); + mb->offset += size; +} + +// i_bs_drop Size +i_bs_drop(Size) { + bitdata <<= $Size; +} diff --git a/erts/emulator/beam/emu/generators.tab b/erts/emulator/beam/emu/generators.tab index 13ae1b30fa..535e5479c6 100644 --- a/erts/emulator/beam/emu/generators.tab +++ b/erts/emulator/beam/emu/generators.tab @@ -1074,3 +1074,259 @@ gen.update_record(Size, Src, Dst, N, Updates) { return begin; } + +gen.bs_match(Fail, Ctx, N, List) { + BeamOp* first_op = 0; + BeamOp** next_ptr = &first_op; + BeamOp* test_heap_op = 0; + BeamOp* read_op = 0; + int src; + + src = 0; + while (src < N.val) { + Uint unit; + Uint size; + Uint words_needed; + BeamOp* op; + + /* Calculate the number of heap words needed for this + * instruction. */ + words_needed = 0; + switch (List[src].val) { + case am_binary: + ASSERT(List[src+3].type == TAG_u); + ASSERT(List[src+4].type == TAG_u); + size = List[src+3].val * List[src+4].val; + words_needed = heap_bin_size((size + 7) / 8); + break; + case am_integer: + ASSERT(List[src+3].type == TAG_u); + ASSERT(List[src+4].type == TAG_u); + size = List[src+3].val * List[src+4].val; + if (size >= SMALL_BITS) { + words_needed = BIG_NEED_FOR_BITS(size); + } + break; + case am_get_tail: + words_needed = EXTRACT_SUB_BIN_HEAP_NEED; + break; + } + + /* Emit a test_heap instrution if needed and there is + * no previous one. */ + if (words_needed && test_heap_op == 0) { + $NewBeamOp(S, test_heap_op); + $BeamOpNameArity(test_heap_op, test_heap, 2); + + test_heap_op->a[0].type = TAG_u; + test_heap_op->a[0].val = 0; /* Number of heap words */ + + ASSERT(List[src+1].type == TAG_u); + test_heap_op->a[1] = List[src+1]; /* Live */ + + *next_ptr = test_heap_op; + next_ptr = &test_heap_op->next; + } + + if (words_needed) { + test_heap_op->a[0].val += words_needed; + } + + /* Translate this sub-instruction to a BEAM instruction. */ + op = 0; + switch (List[src].val) { + case am_ensure_at_least: { + Uint size = List[src+1].val; + unit = List[src+2].val; + if (size != 0 && unit == 1) { + $NewBeamOp(S, op); + $BeamOpNameArity(op, i_bs_ensure_bits, 3); + op->a[0] = Ctx; + op->a[1].type = TAG_u; + op->a[1].val = size; + op->a[2] = Fail; + } else if (size != 0 && unit != 1) { + $NewBeamOp(S, op); + $BeamOpNameArity(op, i_bs_ensure_bits_unit, 4); + + op->a[0] = Ctx; + op->a[1].type = TAG_u; + op->a[1].val = size; /* Size */ + op->a[2].type = TAG_u; + op->a[2].val = unit; /* Unit */ + op->a[3] = Fail; + } else if (size == 0 && unit != 1) { + $NewBeamOp(S, op); + $BeamOpNameArity(op, bs_test_unit, 3); + + op->a[0] = Fail; + op->a[1] = Ctx; + op->a[2].type = TAG_u; + op->a[2].val = unit; + } else if (size == 0 && unit == 1) { + /* This test is redundant because it always + * succeeds. This should only happen for unoptimized + * code. Generate a dummy instruction to ensure that + * we don't trigger the sanity check at the end of + * this generator. */ + $NewBeamOp(S, op); + $BeamOpNameArity(op, delete_me, 0); + } + src += 3; + break; + } + case am_ensure_exactly: { + $NewBeamOp(S, op); + $BeamOpNameArity(op, bs_test_tail2, 3); + + op->a[0] = Fail; + op->a[1] = Ctx; + op->a[2]= List[src+1]; /* Size */ + + src += 2; + break; + } + case am_binary: { + ASSERT(List[src+3].type == TAG_u); + ASSERT(List[src+4].type == TAG_u); + size = List[src+3].val; + unit = List[src+4].val; + + $NewBeamOp(S, op); + $BeamOpNameArity(op, i_bs_get_fixed_binary, 3); + + op->a[0] = Ctx; + op->a[1].type = TAG_u; + op->a[1].val = size * unit; /* Size */ + op->a[2] = List[src+5]; /* Dst */ + + read_op = 0; + src += 6; + break; + } + case am_integer: { + Uint flags = 0; + BeamOpArg Flags; + + /* Translate flags. */ + Flags = List[src+2]; + if (Flags.type == TAG_n) { + Flags.type = TAG_u; + Flags.val = 0; + } else if (Flags.type == TAG_q) { + Eterm term = beamfile_get_literal(&S->beam, Flags.val); + while (is_list(term)) { + Eterm* consp = list_val(term); + Eterm elem = CAR(consp); + switch (elem) { + case am_little: + flags |= BSF_LITTLE; + break; + case am_native: + flags |= BSF_NATIVE; + break; + case am_signed: + flags |= BSF_SIGNED; + break; + } + term = CDR(consp); + } + ASSERT(is_nil(term)); + Flags.type = TAG_u; + Flags.val = flags; + $NativeEndian(Flags); + } + + ASSERT(List[src+3].type == TAG_u); + ASSERT(List[src+4].type == TAG_u); + size = List[src+3].val * List[src+4].val; + +#define READ_OP_SIZE 1 + if (size < SMALL_BITS && flags == 0) { + /* This is a suitable segment -- an unsigned big + * endian integer that fits in a small. */ + if (read_op == 0 || read_op->a[READ_OP_SIZE].val + size > 8*sizeof(Uint)) { + /* There is either no previous i_bs_read_bits instruction or + * size of this segment don't fit into it. */ + $NewBeamOp(S, read_op); + $BeamOpNameArity(read_op, i_bs_read_bits, 2); + + read_op->a[0] = Ctx; + read_op->a[1].type = TAG_u; + read_op->a[1].val = 0; + + *next_ptr = read_op; + next_ptr = &read_op->next; + } + + read_op->a[READ_OP_SIZE].val += size; + + $NewBeamOp(S, op); + $BeamOpNameArity(op, i_bs_extract_integer, 2); + op->a[0].type = TAG_u; + op->a[0].val = size; + op->a[1] = List[src+5]; /* Dst */ + } else { + /* Little endian, signed, or might not fit in a small. */ + $NewBeamOp(S, op); + $BeamOpNameArity(op, i_bs_get_fixed_integer, 4); + + op->a[0] = Ctx; + op->a[1].type = TAG_u; + op->a[1].val = size; /* Size */ + op->a[2] = Flags; /* Flags */ + op->a[3] = List[src+5]; /* Dst */ + + read_op = 0; + } + + src += 6; + break; + } + case am_get_tail: + $NewBeamOp(S, op); + $BeamOpNameArity(op, i_bs_get_tail, 2); + + op->a[0] = Ctx; + op->a[1] = List[src+3]; /* Dst */ + + read_op = 0; + src += 4; + break; + case am_skip: + ASSERT(List[src+1].type == TAG_u); + size = List[src+1].val; + + $NewBeamOp(S, op); + + if (read_op && read_op->a[READ_OP_SIZE].val + size <= 8*sizeof(Uint)) { + read_op->a[READ_OP_SIZE].val += size; + $BeamOpNameArity(op, i_bs_drop, 1); + op->a[0] = List[src+1]; /* Size */ + } else { + $BeamOpNameArity(op, i_bs_skip, 2); + op->a[0] = Ctx; + op->a[1] = List[src+1]; /* Size */ + read_op = 0; + } + + src += 2; + break; + default: + abort(); + } + + if (op) { + *next_ptr = op; + next_ptr = &op->next; + } + } + + if (first_op == 0) { + erts_exit(ERTS_ERROR_EXIT, "loading bs_match in %T:%T/%d: no instructions loaded", + S->module, S->function, S->arity); + } + + ASSERT(first_op); + return first_op; +} diff --git a/erts/emulator/beam/emu/ops.tab b/erts/emulator/beam/emu/ops.tab index 2d670cbb80..90ff4af92f 100644 --- a/erts/emulator/beam/emu/ops.tab +++ b/erts/emulator/beam/emu/ops.tab @@ -1106,11 +1106,65 @@ is_function Fail=f c => jump Fail func_info M F A => i_func_info u M F A # ================================================================ -# New bit syntax matching (R11B). +# New bit syntax matching for fixed sizes (from OTP 26). # ================================================================ %warm +bs_match Fail Ctx Size Rest=* => bs_match(Fail, Ctx, Size, Rest) + +# The bs_match generator breaks the bs_match instruction into +# the instructions that follow. + +i_bs_ensure_bits xy I f +i_bs_ensure_bits_unit xy I t f + +i_bs_read_bits xy t + +i_bs_extract_integer t d + +i_bs_read_bits Ctx=x u==8 | i_bs_extract_integer u==8 Dst=x => + i_bs_read_integer_8 Ctx Dst + +i_bs_read_integer_8 x x + +i_bs_get_fixed_integer xy I t d + +i_bs_get_fixed_binary xy I d + +i_bs_get_tail xy d + +i_bs_skip xy I + +i_bs_drop I + +i_bs_ensure_bits Ctx1 Size1 Fail | i_bs_read_bits Ctx2 Size2 | + equal(Ctx1, Ctx2) | equal(Size1, Size2) => + i_bs_ensure_bits_read Ctx1 Size1 Fail + +i_bs_ensure_bits_read xy t f + +# Optimize extraction of a single segment for some popular sizes. + +i_bs_ensure_bits Ctx1 u==8 Fail | i_bs_read_bits Ctx2 u==8 | + i_bs_extract_integer u==8 Dst=x | equal(Ctx1, Ctx2) => + i_bs_get_integer_8 Ctx1 Fail Dst + +i_bs_ensure_bits Ctx1 u==16 Fail | i_bs_read_bits Ctx2 u==16 | + i_bs_extract_integer u==16 Dst=x | equal(Ctx1, Ctx2) => + i_bs_get_integer_16 Ctx1 Fail Dst + +%if ARCH_64 +i_bs_ensure_bits Ctx1 u==32 Fail | i_bs_read_bits Ctx2 u==32 | + i_bs_extract_integer u==32 Dst=x | equal(Ctx1, Ctx2) => + i_bs_get_integer_32 Ctx1 Fail Dst +%endif + + +# ================================================================ +# Bit syntax matching (from R11B). +# ================================================================ + # Matching integers bs_match_string Fail Ms Bits Val => i_bs_match_string Ms Fail Bits Val diff --git a/erts/emulator/beam/jit/arm/beam_asm.hpp b/erts/emulator/beam/jit/arm/beam_asm.hpp index b76b92368c..e2946fdbc7 100644 --- a/erts/emulator/beam/jit/arm/beam_asm.hpp +++ b/erts/emulator/beam/jit/arm/beam_asm.hpp @@ -1323,6 +1323,20 @@ protected: arm::Gp size_reg); void set_zero(Sint effectiveSize); + void emit_read_bits(Uint bits, + const arm::Gp bin_offset, + const arm::Gp bin_base, + const arm::Gp bitdata); + + void emit_extract_integer(const arm::Gp bitdata, + Uint flags, + Uint bits, + const ArgRegister &Dst); + + void emit_extract_binary(const arm::Gp bitdata, + Uint bits, + const ArgRegister &Dst); + void emit_raise_exception(); void emit_raise_exception(const ErtsCodeMFA *exp); void emit_raise_exception(Label I, const ErtsCodeMFA *exp); diff --git a/erts/emulator/beam/jit/arm/generators.tab b/erts/emulator/beam/jit/arm/generators.tab index 3df71180db..63916ce130 100644 --- a/erts/emulator/beam/jit/arm/generators.tab +++ b/erts/emulator/beam/jit/arm/generators.tab @@ -606,3 +606,59 @@ gen.create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) { return op; } + +gen.bs_match(Fail, Ctx, N, List) { + BeamOp* op; + int fixed_args; + int i; + + $NewBeamOp(S, op); + $BeamOpNameArity(op, i_bs_match, 2); + fixed_args = op->arity; + $BeamOpArity(op, (N.val + fixed_args)); + + op->a[0] = Fail; + op->a[1] = Ctx; + + for (i = 0; i < N.val; i++) { + BeamOpArg current; + Uint flags = 0; + + current = List[i]; + if (current.type == TAG_n) { + current.type = TAG_u; + current.val = 0; + } else if (current.type == TAG_q) { + Eterm term = beamfile_get_literal(&S->beam, current.val); + while (is_list(term)) { + Eterm* consp = list_val(term); + Eterm elem = CAR(consp); + switch (elem) { + case am_little: + flags |= BSF_LITTLE; + break; + case am_native: + flags |= BSF_NATIVE; + break; + case am_signed: + flags |= BSF_SIGNED; + break; + } + term = CDR(consp); + } + ASSERT(is_nil(term)); + current.type = TAG_u; + current.val = flags; + $NativeEndian(current); + } else if (current.type == TAG_o) { + /* An overflow tag (in ensure_at_least or ensure_exactly) + * means that the match will always fail. */ + $BeamOpNameArity(op, jump, 1); + op->a[0] = Fail; + return op; + } + op->a[i+fixed_args] = current; + } + + return op; +} diff --git a/erts/emulator/beam/jit/arm/instr_bs.cpp b/erts/emulator/beam/jit/arm/instr_bs.cpp index ac57063b4f..e23b09fa80 100644 --- a/erts/emulator/beam/jit/arm/instr_bs.cpp +++ b/erts/emulator/beam/jit/arm/instr_bs.cpp @@ -2611,3 +2611,761 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail, comment("done"); mov_arg(Dst, TMP_MEM1q); } + +/* + * Here follows the bs_match instruction and friends. + */ + +struct BsmSegment { + BsmSegment() + : action(action::TEST_HEAP), live(ArgNil()), size(0), unit(1), + flags(0), dst(ArgXRegister(0)){}; + + enum class action { + TEST_HEAP, + ENSURE_AT_LEAST, + ENSURE_EXACTLY, + READ, + EXTRACT_BINARY, + EXTRACT_INTEGER, + GET_INTEGER, + GET_BINARY, + SKIP, + DROP, + GET_TAIL + } action; + ArgVal live; + Uint size; + Uint unit; + Uint flags; + ArgRegister dst; +}; + +void BeamModuleAssembler::emit_read_bits(Uint bits, + const arm::Gp bin_base, + const arm::Gp bin_offset, + const arm::Gp bitdata) { + Label handle_partial = a.newLabel(); + Label shift = a.newLabel(); + Label read_done = a.newLabel(); + + const arm::Gp bin_byte_ptr = TMP2; + const arm::Gp bit_offset = TMP4; + const arm::Gp tmp = TMP5; + + auto num_partial = bits % 8; + + ASSERT(1 <= bits && bits <= 64); + + a.add(bin_byte_ptr, bin_base, bin_offset, arm::lsr(3)); + + if (bits == 1) { + a.and_(bit_offset, bin_offset, imm(7)); + a.ldrb(bitdata.w(), arm::Mem(bin_byte_ptr)); + a.rev64(bitdata, bitdata); + + a.bind(handle_partial); /* Not used, but must bind. */ + } else if (bits <= 8) { + a.ands(bit_offset, bin_offset, imm(7)); + + if (num_partial == 0) { + /* Byte-sized segment. If bit_offset is not byte-aligned, + * this segment always spans two bytes. */ + a.b_ne(handle_partial); + } else { + /* Segment smaller than one byte. Test whether the segment + * fits within the current byte. */ + a.cmp(bit_offset, imm(8 - num_partial)); + a.b_gt(handle_partial); + } + + /* The segment fits in the current byte. */ + a.ldrb(bitdata.w(), arm::Mem(bin_byte_ptr)); + a.rev64(bitdata, bitdata); + a.b(num_partial ? shift : read_done); + + /* The segment is unaligned and spans two bytes. */ + a.bind(handle_partial); + a.ldrh(bitdata.w(), arm::Mem(bin_byte_ptr)); + a.rev64(bitdata, bitdata); + } else if (bits <= 16) { + a.ands(bit_offset, bin_offset, imm(7)); + + /* We always need to read at least two bytes. */ + a.ldrh(bitdata.w(), arm::Mem(bin_byte_ptr)); + a.rev64(bitdata, bitdata); + a.b_eq(read_done); /* Done if segment is byte-aligned. */ + + /* The segment is unaligned. */ + a.bind(handle_partial); + if (num_partial != 0) { + /* If segment size is less than 15 bits or less, it is + * possible that it fits into two bytes. */ + a.cmp(bit_offset, imm(8 - num_partial)); + a.b_le(shift); + } + + /* The segment spans three bytes. Read an additional byte and + * shift into place (right below the already read two bytes a + * the top of the word). */ + a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 2)); + a.orr(bitdata, bitdata, tmp, arm::lsl(40)); + } else if (bits <= 24) { + a.ands(bit_offset, bin_offset, imm(7)); + + if (num_partial == 0) { + /* Byte-sized segment. If bit_offset is not byte-aligned, + * this segment always spans four bytes. */ + a.b_ne(handle_partial); + } else { + /* The segment is smaller than three bytes. Test whether + * it spans three or four bytes. */ + a.cmp(bit_offset, imm(8 - num_partial)); + a.b_gt(handle_partial); + } + + /* This segment spans three bytes. */ + a.ldrh(bitdata.w(), arm::Mem(bin_byte_ptr)); + a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 2)); + a.orr(bitdata, bitdata, tmp, arm::lsl(16)); + a.rev64(bitdata, bitdata); + a.b(num_partial ? shift : read_done); + + /* This segment spans four bytes. */ + a.bind(handle_partial); + a.ldr(bitdata.w(), arm::Mem(bin_byte_ptr)); + a.rev64(bitdata, bitdata); + } else if (bits <= 32) { + a.ands(bit_offset, bin_offset, imm(7)); + + /* We always need to read at least four bytes. */ + a.ldr(bitdata.w(), arm::Mem(bin_byte_ptr)); + a.rev64(bitdata, bitdata); + a.b_eq(read_done); + + a.bind(handle_partial); + if (num_partial != 0) { + a.cmp(bit_offset, imm(8 - num_partial)); + a.b_le(shift); + } + a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 4)); + a.orr(bitdata, bitdata, tmp, arm::lsl(24)); + } else if (bits <= 40) { + a.ands(bit_offset, bin_offset, imm(7)); + + /* We always need to read four bytes. */ + a.ldr(bitdata.w(), arm::Mem(bin_byte_ptr)); + a.rev64(bitdata, bitdata); + + if (num_partial == 0) { + /* Byte-sized segment. If bit_offset is not byte-aligned, + * this segment always spans six bytes. */ + a.b_ne(handle_partial); + } else { + /* The segment is smaller than five bytes. Test whether it + * spans five or six bytes. */ + a.cmp(bit_offset, imm(8 - num_partial)); + a.b_gt(handle_partial); + } + + /* This segment spans five bytes. Read an additional byte. */ + a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 4)); + a.orr(bitdata, bitdata, tmp, arm::lsl(24)); + a.b(num_partial ? shift : read_done); + + /* This segment spans six bytes. Read two additional bytes. */ + a.bind(handle_partial); + a.ldrh(tmp.w(), arm::Mem(bin_byte_ptr, 4)); + a.rev16(tmp.w(), tmp.w()); + a.orr(bitdata, bitdata, tmp, arm::lsl(16)); + } else if (bits <= 48) { + a.ands(bit_offset, bin_offset, imm(7)); + a.ldr(bitdata.w(), arm::Mem(bin_byte_ptr)); + a.ldrh(tmp.w(), arm::Mem(bin_byte_ptr, 4)); + a.orr(bitdata, bitdata, tmp, arm::lsl(32)); + a.rev64(bitdata, bitdata); + a.b_eq(read_done); + + a.bind(handle_partial); + if (num_partial != 0) { + a.cmp(bit_offset, imm(8 - num_partial)); + a.b_le(shift); + } + a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 6)); + a.orr(bitdata, bitdata, tmp, arm::lsl(8)); + } else if (bits <= 56) { + a.ands(bit_offset, bin_offset, imm(7)); + + if (num_partial == 0) { + /* Byte-sized segment. If bit_offset is not byte-aligned, + * this segment always spans 8 bytes. */ + a.b_ne(handle_partial); + } else { + /* The segment is smaller than 8 bytes. Test whether it + * spans 7 or 8 bytes. */ + a.cmp(bit_offset, imm(8 - num_partial)); + a.b_gt(handle_partial); + } + + /* This segment spans 7 bytes. */ + a.ldr(bitdata, arm::Mem(bin_byte_ptr, -1)); + a.lsr(bitdata, bitdata, imm(8)); + a.rev64(bitdata, bitdata); + a.b(shift); + + /* This segment spans 8 bytes. */ + a.bind(handle_partial); + a.ldr(bitdata, arm::Mem(bin_byte_ptr)); + a.rev64(bitdata, bitdata); + } else if (bits <= 64) { + a.ands(bit_offset, bin_offset, imm(7)); + a.ldr(bitdata, arm::Mem(bin_byte_ptr)); + a.rev64(bitdata, bitdata); + + if (num_partial == 0) { + /* Byte-sized segment. If bit_offset is not byte-aligned, + * this segment always spans 8 bytes. */ + a.b_eq(read_done); + } else { + /* The segment is smaller than 8 bytes. Test whether it + * spans 8 or 9 bytes. */ + a.cmp(bit_offset, imm(8 - num_partial)); + a.b_le(shift); + } + + /* This segments spans 9 bytes. Read an additional byte. */ + a.bind(handle_partial); + a.ldrb(tmp.w(), arm::Mem(bin_byte_ptr, 8)); + a.lsl(bitdata, bitdata, bit_offset); + a.lsl(tmp, tmp, bit_offset); + a.orr(bitdata, bitdata, tmp, arm::lsr(8)); + a.b(read_done); + } + + /* Shift the read data into the most significant bits of the + * word. */ + a.bind(shift); + a.lsl(bitdata, bitdata, bit_offset); + + a.bind(read_done); +} + +void BeamModuleAssembler::emit_extract_integer(const arm::Gp bitdata, + Uint flags, + Uint bits, + const ArgRegister &Dst) { + Label big = a.newLabel(); + Label done = a.newLabel(); + arm::Gp data_reg; + auto dst = init_destination(Dst, TMP1); + Uint num_partial = bits % 8; + Uint num_complete = 8 * (bits / 8); + + if (bits <= 8) { + /* Endian does not matter for values that fit in a byte. */ + flags &= ~BSF_LITTLE; + } + + /* If this segment is little-endian, reverse endianness. */ + if ((flags & BSF_LITTLE) != 0) { + comment("reverse endian for a little-endian segment"); + } + data_reg = TMP2; + if ((flags & BSF_LITTLE) == 0) { + data_reg = bitdata; + } else if (bits == 16) { + a.rev16(TMP2, bitdata); + } else if (bits == 32) { + a.rev32(TMP2, bitdata); + } else if (num_partial == 0) { + a.rev64(TMP2, bitdata); + a.lsr(TMP2, TMP2, arm::lsr(64 - bits)); + } else { + a.ubfiz(TMP3, bitdata, imm(num_complete), imm(num_partial)); + a.ubfx(TMP2, bitdata, imm(num_partial), imm(num_complete)); + a.rev64(TMP2, TMP2); + a.orr(TMP2, TMP3, TMP2, arm::lsr(64 - num_complete)); + } + + /* Sign-extend the number if the segment is signed. */ + if ((flags & BSF_SIGNED) != 0) { + if (bits < 64) { + comment("sign extend extracted value"); + a.lsl(TMP2, data_reg, imm(64 - bits)); + a.asr(TMP2, TMP2, imm(64 - bits)); + data_reg = TMP2; + } + } + + /* Handle segments whose values might not fit in a small integer. */ + if (bits >= SMALL_BITS) { + comment("test whether it fits in a small"); + if (bits < 64 && (flags & BSF_SIGNED) == 0) { + a.and_(TMP2, data_reg, imm((1ull << bits) - 1)); + data_reg = TMP2; + } + if ((flags & BSF_SIGNED) != 0) { + /* Signed segment. */ + a.adds(TMP3, ZERO, data_reg, arm::lsr(SMALL_BITS - 1)); + a.ccmp(TMP3, + imm(_TAG_IMMED1_MASK << 1 | 1), + imm(NZCV::kEqual), + imm(arm::CondCode::kNE)); + a.b_ne(big); + } else { + /* Unsigned segment. */ + a.lsr(TMP3, data_reg, imm(SMALL_BITS - 1)); + a.cbnz(TMP3, big); + } + } + + /* Tag and store the extracted small integer. */ + comment("store extracted integer as a small"); + mov_imm(dst.reg, _TAG_IMMED1_SMALL); + if ((flags & BSF_SIGNED) != 0) { + a.orr(dst.reg, dst.reg, data_reg, arm::lsl(_TAG_IMMED1_SIZE)); + } else { + if (bits >= SMALL_BITS) { + a.bfi(dst.reg, + data_reg, + arm::lsl(_TAG_IMMED1_SIZE), + imm(SMALL_BITS)); + } else { + a.bfi(dst.reg, data_reg, arm::lsl(_TAG_IMMED1_SIZE), imm(bits)); + } + } + + if (bits >= SMALL_BITS) { + a.b(done); + } + + /* Handle a bignum (up to 64 bits). */ + a.bind(big); + if (bits >= SMALL_BITS) { + comment("store extracted integer as a bignum"); + a.add(dst.reg, HTOP, imm(TAG_PRIMARY_BOXED)); + mov_imm(TMP3, make_pos_bignum_header(1)); + if ((flags & BSF_SIGNED) == 0) { + /* Unsigned. */ + a.stp(TMP3, data_reg, arm::Mem(HTOP).post(sizeof(Eterm[2]))); + } else { + /* Signed. */ + Label store = a.newLabel(); + a.adds(TMP2, data_reg, ZERO); + a.b_pl(store); + + mov_imm(TMP3, make_neg_bignum_header(1)); + a.neg(TMP2, TMP2); + + a.bind(store); + a.stp(TMP3, TMP2, arm::Mem(HTOP).post(sizeof(Eterm[2]))); + } + } + + a.bind(done); + flush_var(dst); +} + +void BeamModuleAssembler::emit_extract_binary(const arm::Gp bitdata, + Uint bits, + const ArgRegister &Dst) { + auto dst = init_destination(Dst, TMP1); + Uint num_bytes = bits / 8; + + a.add(dst.reg, HTOP, imm(TAG_PRIMARY_BOXED)); + mov_imm(TMP2, header_heap_bin(num_bytes)); + mov_imm(TMP3, num_bytes); + a.rev64(TMP4, bitdata); + a.stp(TMP2, TMP3, arm::Mem(HTOP).post(sizeof(Eterm[2]))); + a.str(TMP4, arm::Mem(HTOP).post(sizeof(Eterm[1]))); + flush_var(dst); +} + +static std::vector<BsmSegment> opt_bsm_segments( + const std::vector<BsmSegment> segments, + const ArgWord &Need, + const ArgWord &Live) { + std::vector<BsmSegment> segs; + + Uint heap_need = Need.get(); + + /* + * First calculate the total number of heap words needed for + * bignums and binaries. + */ + for (auto seg : segments) { + switch (seg.action) { + case BsmSegment::action::GET_INTEGER: + if (seg.size >= SMALL_BITS) { + heap_need += BIG_NEED_FOR_BITS(seg.size); + } + break; + case BsmSegment::action::GET_BINARY: + heap_need += heap_bin_size((seg.size + 7) / 8); + break; + case BsmSegment::action::GET_TAIL: + heap_need += EXTRACT_SUB_BIN_HEAP_NEED; + break; + default: + break; + } + } + + int index = 0; + int read_action_pos = -1; + + index = 0; + for (auto seg : segments) { + if (heap_need != 0 && seg.live.isWord()) { + BsmSegment s = seg; + + s.action = BsmSegment::action::TEST_HEAP; + s.size = heap_need; + segs.push_back(s); + index++; + heap_need = 0; + } + + switch (seg.action) { + case BsmSegment::action::GET_INTEGER: + case BsmSegment::action::GET_BINARY: + if (seg.size > 64) { + read_action_pos = -1; + } else if (seg.action == BsmSegment::action::GET_BINARY && + seg.size % 8 != 0) { + read_action_pos = -1; + } else { + if ((seg.flags & BSF_LITTLE) != 0 || read_action_pos < 0 || + seg.size + segs.at(read_action_pos).size > 64) { + BsmSegment s; + + /* Create a new READ action. */ + read_action_pos = index; + s.action = BsmSegment::action::READ; + s.size = seg.size; + segs.push_back(s); + index++; + } else { + /* Reuse previous READ action. */ + segs.at(read_action_pos).size += seg.size; + } + switch (seg.action) { + case BsmSegment::action::GET_INTEGER: + seg.action = BsmSegment::action::EXTRACT_INTEGER; + break; + case BsmSegment::action::GET_BINARY: + seg.action = BsmSegment::action::EXTRACT_BINARY; + break; + default: + break; + } + } + segs.push_back(seg); + break; + case BsmSegment::action::SKIP: + if (read_action_pos >= 0 && + seg.size + segs.at(read_action_pos).size <= 64) { + segs.at(read_action_pos).size += seg.size; + seg.action = BsmSegment::action::DROP; + } else { + read_action_pos = -1; + } + segs.push_back(seg); + break; + default: + read_action_pos = -1; + segs.push_back(seg); + break; + } + index++; + } + + /* Handle a trailing test_heap instruction (for the + * i_bs_match_test_heap instruction). */ + if (heap_need) { + BsmSegment seg; + + seg.action = BsmSegment::action::TEST_HEAP; + seg.size = heap_need; + seg.live = Live; + segs.push_back(seg); + } + return segs; +} + +void BeamModuleAssembler::emit_i_bs_match(ArgLabel const &Fail, + ArgRegister const &Ctx, + Span<ArgVal> const &List) { + emit_i_bs_match_test_heap(Fail, Ctx, ArgWord(0), ArgWord(0), List); +} + +void BeamModuleAssembler::emit_i_bs_match_test_heap(ArgLabel const &Fail, + ArgRegister const &Ctx, + ArgWord const &Need, + ArgWord const &Live, + Span<ArgVal> const &List) { + const int orig_offset = offsetof(ErlBinMatchState, mb.orig); + const int base_offset = offsetof(ErlBinMatchState, mb.base); + const int position_offset = offsetof(ErlBinMatchState, mb.offset); + const int size_offset = offsetof(ErlBinMatchState, mb.size); + + std::vector<BsmSegment> segments; + + auto current = List.begin(); + auto end = List.begin() + List.size(); + + while (current < end) { + auto cmd = current++->as<ArgImmed>().get(); + BsmSegment seg; + + switch (cmd) { + case am_ensure_at_least: { + seg.action = BsmSegment::action::ENSURE_AT_LEAST; + seg.size = current[0].as<ArgWord>().get(); + seg.unit = current[1].as<ArgWord>().get(); + current += 2; + break; + } + case am_ensure_exactly: { + seg.action = BsmSegment::action::ENSURE_EXACTLY; + seg.size = current[0].as<ArgWord>().get(); + current += 1; + break; + } + case am_binary: + case am_integer: { + auto size = current[2].as<ArgWord>().get(); + auto unit = current[3].as<ArgWord>().get(); + + switch (cmd) { + case am_integer: + seg.action = BsmSegment::action::GET_INTEGER; + break; + case am_binary: + seg.action = BsmSegment::action::GET_BINARY; + break; + } + + seg.live = current[0]; + seg.size = size * unit; + seg.unit = unit; + seg.flags = current[1].as<ArgWord>().get(); + seg.dst = current[4].as<ArgRegister>(); + current += 5; + break; + } + case am_get_tail: { + seg.action = BsmSegment::action::GET_TAIL; + seg.live = current[0].as<ArgWord>(); + seg.dst = current[2].as<ArgRegister>(); + current += 3; + break; + } + case am_skip: { + seg.action = BsmSegment::action::SKIP; + seg.size = current[0].as<ArgWord>().get(); + seg.flags = 0; + current += 1; + break; + } + default: + abort(); + break; + } + segments.push_back(seg); + } + + segments = opt_bsm_segments(segments, Need, Live); + + const arm::Gp bin_base = ARG2; + const arm::Gp bin_position = ARG3; + const arm::Gp bin_size = ARG4; + const arm::Gp bitdata = ARG8; + bool position_is_valid = false; + + for (auto seg : segments) { + switch (seg.action) { + case BsmSegment::action::ENSURE_AT_LEAST: { + comment("ensure_at_least %ld %ld", seg.size, seg.unit); + auto ctx_reg = load_source(Ctx, TMP1); + auto stride = seg.size; + auto unit = seg.unit; + + a.ldur(bin_position, emit_boxed_val(ctx_reg.reg, position_offset)); + a.ldur(bin_size, emit_boxed_val(ctx_reg.reg, size_offset)); + a.sub(TMP5, bin_size, bin_position); + cmp(TMP5, stride); + a.b_lo(resolve_beam_label(Fail, disp1MB)); + + if (unit != 1) { + if (stride % unit != 0) { + sub(TMP5, TMP5, stride); + } + + if ((unit & (unit - 1)) != 0) { + mov_imm(TMP4, unit); + + a.udiv(TMP3, TMP5, TMP4); + a.msub(TMP5, TMP3, TMP4, TMP5); + + a.cbnz(TMP5, resolve_beam_label(Fail, disp1MB)); + } else { + a.tst(TMP5, imm(unit - 1)); + a.b_ne(resolve_beam_label(Fail, disp1MB)); + } + } + + position_is_valid = true; + break; + } + case BsmSegment::action::ENSURE_EXACTLY: { + comment("ensure_exactly %ld", seg.size); + auto ctx_reg = load_source(Ctx, TMP1); + auto size = seg.size; + + a.ldur(bin_position, emit_boxed_val(ctx_reg.reg, position_offset)); + a.ldur(TMP3, emit_boxed_val(ctx_reg.reg, size_offset)); + if (size != 0) { + a.sub(TMP1, TMP3, bin_position); + cmp(TMP1, size); + } else { + a.subs(TMP1, TMP3, bin_position); + } + a.b_ne(resolve_beam_label(Fail, disp1MB)); + position_is_valid = true; + break; + } + case BsmSegment::action::TEST_HEAP: { + comment("test_heap %ld", seg.size); + emit_gc_test(ArgWord(0), ArgWord(seg.size), seg.live); + position_is_valid = false; + break; + } + case BsmSegment::action::READ: { + comment("read %ld", seg.size); + if (seg.size == 0) { + comment("(nothing to do)"); + } else { + auto ctx = load_source(Ctx, ARG1); + + if (!position_is_valid) { + a.ldur(bin_position, + emit_boxed_val(ctx.reg, position_offset)); + position_is_valid = true; + } + a.ldur(bin_base, emit_boxed_val(ctx.reg, base_offset)); + + emit_read_bits(seg.size, bin_base, bin_position, bitdata); + + a.add(bin_position, bin_position, imm(seg.size)); + a.stur(bin_position, emit_boxed_val(ctx.reg, position_offset)); + } + break; + } + case BsmSegment::action::EXTRACT_BINARY: { + auto bits = seg.size; + auto Dst = seg.dst; + + comment("extract binary %ld", bits); + emit_extract_binary(bitdata, bits, Dst); + if (bits != 0 && bits != 64) { + a.ror(bitdata, bitdata, imm(64 - bits)); + } + break; + } + case BsmSegment::action::EXTRACT_INTEGER: { + auto bits = seg.size; + auto flags = seg.flags; + auto Dst = seg.dst; + + comment("extract integer %ld", bits); + if (bits != 0 && bits != 64) { + a.ror(bitdata, bitdata, imm(64 - bits)); + } + emit_extract_integer(bitdata, flags, bits, Dst); + break; + } + case BsmSegment::action::GET_INTEGER: { + Uint live = seg.live.as<ArgWord>().get(); + Uint flags = seg.flags; + auto bits = seg.size; + auto Dst = seg.dst; + + comment("get integer %ld", bits); + auto ctx = load_source(Ctx, TMP1); + + if (bits >= SMALL_BITS) { + emit_enter_runtime<Update::eHeap>(live); + } else { + emit_enter_runtime(live); + } + + a.mov(ARG1, c_p); + a.mov(ARG2, bits); + a.mov(ARG3, flags); + lea(ARG4, emit_boxed_val(ctx.reg, offsetof(ErlBinMatchState, mb))); + runtime_call<4>(erts_bs_get_integer_2); + + if (bits >= SMALL_BITS) { + emit_leave_runtime<Update::eHeap>(live); + } else { + emit_leave_runtime(live); + } + + mov_arg(Dst, ARG1); + + position_is_valid = false; + break; + } + case BsmSegment::action::GET_BINARY: { + auto Live = seg.live; + comment("get binary %ld", seg.size); + auto ctx = load_source(Ctx, TMP1); + + emit_enter_runtime<Update::eHeap>(Live.as<ArgWord>().get()); + + lea(ARG1, arm::Mem(c_p, offsetof(Process, htop))); + a.ldur(ARG2, emit_boxed_val(ctx.reg, orig_offset)); + a.ldur(ARG3, emit_boxed_val(ctx.reg, base_offset)); + a.ldur(ARG4, emit_boxed_val(ctx.reg, position_offset)); + mov_imm(ARG5, seg.size); + a.add(TMP2, ARG4, ARG5); + a.stur(TMP2, emit_boxed_val(ctx.reg, position_offset)); + runtime_call<5>(erts_extract_sub_binary); + + emit_leave_runtime<Update::eHeap>(Live.as<ArgWord>().get()); + + mov_arg(seg.dst, ARG1); + position_is_valid = false; + break; + } + case BsmSegment::action::GET_TAIL: { + comment("get_tail"); + + mov_arg(ARG1, Ctx); + fragment_call(ga->get_bs_get_tail_shared()); + mov_arg(seg.dst, ARG1); + position_is_valid = false; + break; + } + case BsmSegment::action::SKIP: { + comment("skip %ld", seg.size); + auto ctx = load_source(Ctx, TMP1); + if (!position_is_valid) { + a.ldur(bin_position, emit_boxed_val(ctx.reg, position_offset)); + position_is_valid = true; + } + add(bin_position, bin_position, seg.size); + a.stur(bin_position, emit_boxed_val(ctx.reg, position_offset)); + break; + } + case BsmSegment::action::DROP: + auto bits = seg.size; + comment("drop %ld", bits); + if (bits != 0 && bits != 64) { + a.ror(bitdata, bitdata, imm(64 - bits)); + } + break; + } + } +} diff --git a/erts/emulator/beam/jit/arm/ops.tab b/erts/emulator/beam/jit/arm/ops.tab index e915ece301..c1c33ed309 100644 --- a/erts/emulator/beam/jit/arm/ops.tab +++ b/erts/emulator/beam/jit/arm/ops.tab @@ -275,11 +275,12 @@ load_tuple_ptr s # If positions are in consecutive memory, fetch and store two words at # once. +## FIXME: Fix this bug in maint, too. i_get_tuple_element Tuple Pos1 Dst1 | current_tuple Tuple2 | get_tuple_element Tuple3 Pos2 Dst2 | equal(Tuple, Tuple2) | equal(Tuple, Tuple3) | - consecutive_words(Pos1, Pos2) => + consecutive_words(Pos1, Pos2) | distinct(Dst1, Dst2) => get_two_tuple_elements Tuple Pos1 Dst1 Dst2 | current_tuple Tuple Dst2 @@ -881,7 +882,19 @@ i_flush_stubs i_breakpoint_trampoline # ================================================================ -# New bit syntax matching (R11B). +# New bit syntax matching for fixed sizes (from OTP 26). +# ================================================================ + +bs_match Fail Ctx Size Rest=* => bs_match(Fail, Ctx, Size, Rest) + +i_bs_match Fail Ctx Rest=* | test_heap Need Live => + i_bs_match_test_heap Fail Ctx Need Live Rest + +i_bs_match f S * +i_bs_match_test_heap f S I t * + +# ================================================================ +# Bit syntax matching (from R11B). # ================================================================ %warm diff --git a/erts/emulator/beam/jit/x86/beam_asm.hpp b/erts/emulator/beam/jit/x86/beam_asm.hpp index 71294190b9..9f2040da00 100644 --- a/erts/emulator/beam/jit/x86/beam_asm.hpp +++ b/erts/emulator/beam/jit/x86/beam_asm.hpp @@ -1371,10 +1371,12 @@ protected: void emit_error(int code); - x86::Mem emit_bs_get_integer_prologue(Label next, - Label fail, - int flags, - int size); + void emit_bs_get_integer(const ArgRegister &Ctx, + const ArgLabel &Fail, + const ArgWord &Live, + const ArgWord Flags, + int bits, + const ArgRegister &Dst); int emit_bs_get_field_size(const ArgSource &Size, int unit, @@ -1396,6 +1398,25 @@ protected: bool bs_maybe_enter_runtime(bool entered); void bs_maybe_leave_runtime(bool entered); + void emit_read_bits(Uint bits, + const x86::Gp bin_base, + const x86::Gp bin_offset, + const x86::Gp bitdata); + void emit_extract_integer(const x86::Gp bitdata, + const x86::Gp tmp, + Uint flags, + Uint bits, + const ArgRegister &Dst); + void emit_extract_binary(const x86::Gp bitdata, + Uint bits, + const ArgRegister &Dst); + void emit_read_integer(const x86::Gp bin_base, + const x86::Gp bin_position, + const x86::Gp tmp, + Uint flags, + Uint bits, + const ArgRegister &Dst); + void emit_raise_exception(); void emit_raise_exception(const ErtsCodeMFA *exp); void emit_raise_exception(Label I, const ErtsCodeMFA *exp); diff --git a/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl b/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl index 2fa14f3ad9..bd4e6f9f43 100755 --- a/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl +++ b/erts/emulator/beam/jit/x86/beam_asm_global.hpp.pl @@ -31,7 +31,6 @@ my @beam_global_funcs = qw( bs_add_shared bs_create_bin_error_shared bs_size_check_shared - bs_fixed_integer_shared bs_get_tail_shared call_bif_shared call_light_bif_shared diff --git a/erts/emulator/beam/jit/x86/generators.tab b/erts/emulator/beam/jit/x86/generators.tab index 143e91dab7..ba896f9c0b 100644 --- a/erts/emulator/beam/jit/x86/generators.tab +++ b/erts/emulator/beam/jit/x86/generators.tab @@ -674,3 +674,59 @@ gen.create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) { return op; } + +gen.bs_match(Fail, Ctx, N, List) { + BeamOp* op; + int fixed_args; + int i; + + $NewBeamOp(S, op); + $BeamOpNameArity(op, i_bs_match, 2); + fixed_args = op->arity; + $BeamOpArity(op, (N.val + fixed_args)); + + op->a[0] = Fail; + op->a[1] = Ctx; + + for (i = 0; i < N.val; i++) { + BeamOpArg current; + Uint flags = 0; + + current = List[i]; + if (current.type == TAG_n) { + current.type = TAG_u; + current.val = 0; + } else if (current.type == TAG_q) { + Eterm term = beamfile_get_literal(&S->beam, current.val); + while (is_list(term)) { + Eterm* consp = list_val(term); + Eterm elem = CAR(consp); + switch (elem) { + case am_little: + flags |= BSF_LITTLE; + break; + case am_native: + flags |= BSF_NATIVE; + break; + case am_signed: + flags |= BSF_SIGNED; + break; + } + term = CDR(consp); + } + ASSERT(is_nil(term)); + current.type = TAG_u; + current.val = flags; + $NativeEndian(current); + } else if (current.type == TAG_o) { + /* An overflow tag (in ensure_at_least or ensure_exactly) + * means that the match will always fail. */ + $BeamOpNameArity(op, jump, 1); + op->a[0] = Fail; + return op; + } + op->a[i+fixed_args] = current; + } + + return op; +} diff --git a/erts/emulator/beam/jit/x86/instr_bs.cpp b/erts/emulator/beam/jit/x86/instr_bs.cpp index 2b9445a75a..e97678e4a6 100644 --- a/erts/emulator/beam/jit/x86/instr_bs.cpp +++ b/erts/emulator/beam/jit/x86/instr_bs.cpp @@ -652,63 +652,49 @@ void BeamModuleAssembler::emit_i_bs_get_position(const ArgRegister &Ctx, mov_arg(Dst, ARG1); } -/* ARG3 = flags | (size << 3), - * ARG4 = tagged match context */ -void BeamGlobalAssembler::emit_bs_fixed_integer_shared() { - emit_enter_runtime<Update::eStack | Update::eHeap>(); - - a.mov(ARG1, c_p); - /* Unpack size ... */ - a.mov(ARG2, ARG3); - a.shr(ARG2, imm(3)); - /* ... flags. */ - a.and_(ARG3, imm(BSF_ALIGNED | BSF_LITTLE | BSF_SIGNED)); - a.lea(ARG4, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb))); - runtime_call<4>(erts_bs_get_integer_2); - - emit_leave_runtime<Update::eStack | Update::eHeap>(); - - a.ret(); -} - -x86::Mem BeamModuleAssembler::emit_bs_get_integer_prologue(Label next, - Label fail, - int flags, - int size) { - Label aligned = a.newLabel(); - - a.mov(ARG2, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb.offset))); - a.lea(ARG3, x86::qword_ptr(ARG2, size)); - a.cmp(ARG3, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb.size))); - a.ja(fail); - - a.test(ARG2.r8(), imm(CHAR_BIT - 1)); - a.short_().je(aligned); - - /* Actually unaligned reads are quite rare, so we handle everything in a - * shared fragment. */ - mov_imm(ARG3, flags | (size << 3)); - safe_fragment_call(ga->get_bs_fixed_integer_shared()); +void BeamModuleAssembler::emit_bs_get_integer(const ArgRegister &Ctx, + const ArgLabel &Fail, + const ArgWord &Live, + const ArgWord Flags, + int bits, + const ArgRegister &Dst) { + const int base_offset = offsetof(ErlBinMatchState, mb.base); + const int position_offset = offsetof(ErlBinMatchState, mb.offset); + const int size_offset = offsetof(ErlBinMatchState, mb.size); - /* The above call can't fail since we work on small numbers and - * bounds-tested above. */ -#ifdef JIT_HARD_DEBUG - a.jmp(next); +#ifdef WIN32 + const x86::Gp bin_position = ARG1; + const x86::Gp bitdata = ARG4; #else - a.short_().jmp(next); + const x86::Gp bin_position = ARG4; + const x86::Gp bitdata = ARG1; #endif + ASSERT(bin_position == x86::rcx); + const x86::Gp bin_base = ARG2; + const x86::Gp ctx = ARG3; + const x86::Gp tmp = ARG5; - a.bind(aligned); - { - /* Read base address and convert offset to bytes. */ - a.mov(ARG1, emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb.base))); - a.shr(ARG2, imm(3)); + mov_arg(ctx, Ctx); - /* We cannot fail from here on; bump the match context's position. */ - a.mov(emit_boxed_val(ARG4, offsetof(ErlBinMatchState, mb.offset)), - ARG3); + if (bits == 64) { + a.mov(ARG1, ctx); + emit_gc_test_preserve(ArgWord(BIG_UINT_HEAP_SIZE), Live, Ctx, ARG1); + a.mov(ARG3, ARG1); + } + + a.mov(bin_position, emit_boxed_val(ctx, position_offset)); + a.lea(RET, qword_ptr(bin_position, bits)); + a.cmp(RET, emit_boxed_val(ctx, size_offset)); + a.ja(resolve_beam_label(Fail)); - return x86::Mem(ARG1, ARG2, 0, 0, size / 8); + a.mov(bin_base, emit_boxed_val(ctx, base_offset)); + a.mov(emit_boxed_val(ctx, position_offset), RET); + + if (bits == 64) { + emit_read_bits(bits, bin_base, bin_position, bitdata); + emit_extract_integer(bitdata, tmp, Flags.get(), bits, Dst); + } else { + emit_read_integer(bin_base, bin_position, tmp, Flags.get(), bits, Dst); } } @@ -716,113 +702,21 @@ void BeamModuleAssembler::emit_i_bs_get_integer_8(const ArgRegister &Ctx, const ArgWord &Flags, const ArgLabel &Fail, const ArgRegister &Dst) { - int flags = Flags.get(); - Label next = a.newLabel(); - x86::Mem address; - - mov_arg(ARG4, Ctx); - - address = emit_bs_get_integer_prologue(next, - resolve_beam_label(Fail), - flags, - 8); - - if (flags & BSF_SIGNED) { - a.movsx(RET, address); - } else { - a.movzx(RET, address); - } - - a.shl(RET, imm(_TAG_IMMED1_SIZE)); - a.or_(RET, imm(_TAG_IMMED1_SMALL)); - - a.bind(next); - mov_arg(Dst, RET); + emit_bs_get_integer(Ctx, Fail, ArgWord(0), Flags, 8, Dst); } void BeamModuleAssembler::emit_i_bs_get_integer_16(const ArgRegister &Ctx, const ArgWord &Flags, const ArgLabel &Fail, const ArgRegister &Dst) { - int flags = Flags.get(); - Label next = a.newLabel(); - x86::Mem address; - - mov_arg(ARG4, Ctx); - - address = emit_bs_get_integer_prologue(next, - resolve_beam_label(Fail), - flags, - 16); - - if (flags & BSF_LITTLE) { - if (flags & BSF_SIGNED) { - a.movsx(RET, address); - } else { - a.movzx(RET, address); - } - } else { - if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) { - a.movbe(x86::ax, address); - } else { - a.mov(x86::ax, address); - a.xchg(x86::al, x86::ah); - } - - if (flags & BSF_SIGNED) { - a.movsx(RET, x86::ax); - } else { - a.movzx(RET, x86::ax); - } - } - - a.shl(RET, imm(_TAG_IMMED1_SIZE)); - a.or_(RET, imm(_TAG_IMMED1_SMALL)); - - a.bind(next); - mov_arg(Dst, RET); + emit_bs_get_integer(Ctx, Fail, ArgWord(0), Flags, 16, Dst); } void BeamModuleAssembler::emit_i_bs_get_integer_32(const ArgRegister &Ctx, const ArgWord &Flags, const ArgLabel &Fail, const ArgRegister &Dst) { - int flags = Flags.get(); - Label next = a.newLabel(); - x86::Mem address; - - mov_arg(ARG4, Ctx); - - address = emit_bs_get_integer_prologue(next, - resolve_beam_label(Fail), - flags, - 32); - - if (flags & BSF_LITTLE) { - if (flags & BSF_SIGNED) { - a.movsxd(RET, address); - } else { - /* Implicitly zero-extends to 64 bits */ - a.mov(RETd, address); - } - } else { - if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) { - a.movbe(RETd, address); - } else { - a.mov(RETd, address); - a.bswap(RETd); - } - - if (flags & BSF_SIGNED) { - a.movsxd(RET, RETd); - } - } - - a.shl(RET, imm(_TAG_IMMED1_SIZE)); - a.or_(RET, imm(_TAG_IMMED1_SMALL)); - - a.bind(next); - mov_arg(Dst, RET); + emit_bs_get_integer(Ctx, Fail, ArgWord(0), Flags, 32, Dst); } void BeamModuleAssembler::emit_i_bs_get_integer_64(const ArgRegister &Ctx, @@ -830,62 +724,7 @@ void BeamModuleAssembler::emit_i_bs_get_integer_64(const ArgRegister &Ctx, const ArgLabel &Fail, const ArgWord &Live, const ArgRegister &Dst) { - int flags = Flags.get(); - Label next = a.newLabel(); - x86::Mem address; - - mov_arg(ARG4, Ctx); - - emit_gc_test_preserve(ArgWord(BIG_UINT_HEAP_SIZE), Live, Ctx, ARG4); - - address = emit_bs_get_integer_prologue(next, - resolve_beam_label(Fail), - flags, - 64); - - if (flags & BSF_LITTLE) { - a.mov(RET, address); - } else { - if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) { - a.movbe(RET, address); - } else { - a.mov(RET, address); - a.bswap(RET); - } - } - - a.mov(ARG1, RET); - a.mov(ARG2, RET); - - /* Speculatively make a small out of the result even though it might not - * be one, and jump to the next instruction if it is. */ - a.shl(RET, imm(_TAG_IMMED1_SIZE)); - a.or_(RET, imm(_TAG_IMMED1_SMALL)); - - if (flags & BSF_SIGNED) { - a.sar(ARG2, imm(SMALL_BITS - 1)); - a.add(ARG2, imm(1)); - a.cmp(ARG2, imm(1)); - a.jbe(next); - } else { - a.shr(ARG2, imm(SMALL_BITS - 1)); - a.jz(next); - } - - emit_enter_runtime(); - - a.mov(ARG2, HTOP); - if (flags & BSF_SIGNED) { - runtime_call<2>(small_to_big); - } else { - runtime_call<2>(uword_to_big); - } - a.add(HTOP, imm(sizeof(Eterm) * BIG_UINT_HEAP_SIZE)); - - emit_leave_runtime(); - - a.bind(next); - mov_arg(Dst, RET); + emit_bs_get_integer(Ctx, Fail, Live, Flags, 64, Dst); } void BeamModuleAssembler::emit_i_bs_get_integer(const ArgRegister &Ctx, @@ -1040,7 +879,6 @@ void BeamModuleAssembler::emit_i_bs_skip_bits2(const ArgRegister &Ctx, Label fail; fail = resolve_beam_label(Fail); - if (emit_bs_get_field_size(Bits, Unit.get(), fail, RET) >= 0) { emit_bs_skip_bits(Fail, Ctx); } @@ -2799,3 +2637,921 @@ void BeamModuleAssembler::emit_i_bs_create_bin(const ArgLabel &Fail, a.mov(RET, TMP_MEM1q); mov_arg(Dst, RET); } + +/* + * Here follows the bs_match instruction and friends. + */ + +struct BsmSegment { + BsmSegment() + : action(action::TEST_HEAP), live(ArgNil()), size(0), unit(1), + flags(0), dst(ArgXRegister(0)){}; + + enum class action { + TEST_HEAP, + ENSURE_AT_LEAST, + ENSURE_EXACTLY, + READ, + EXTRACT_BINARY, + EXTRACT_INTEGER, + READ_INTEGER, + GET_INTEGER, + GET_BINARY, + SKIP, + DROP, + GET_TAIL + } action; + ArgVal live; + Uint size; + Uint unit; + Uint flags; + ArgRegister dst; +}; + +void BeamModuleAssembler::emit_read_bits(Uint bits, + const x86::Gp bin_base, + const x86::Gp bin_offset, + const x86::Gp bitdata) { + Label handle_partial = a.newLabel(); + Label shift = a.newLabel(); + Label read_done = a.newLabel(); + auto num_partial = bits % 8; + auto num_bytes_to_read = (bits + 7) / 8; + + a.mov(RET, bin_offset); + a.shr(RET, imm(3)); + a.and_(bin_offset.r32(), imm(7)); + + if (num_partial == 0) { + /* Byte-sized segment. If bit_offset is not byte-aligned, this + * segment always needs an additional byte. */ + a.jnz(handle_partial); + } else { + /* Non-byte-sized segment. Test whether we will need an + * additional byte. */ + a.cmp(bin_offset.r32(), imm(8 - num_partial)); + a.jg(handle_partial); + } + + /* We don't need an extra byte. */ + if (num_bytes_to_read == 1) { + a.movzx(bitdata.r32(), x86::byte_ptr(bin_base, RET)); + if (num_partial == 0) { + a.bswap(bitdata); + a.short_().jmp(read_done); + } else { + a.add(bin_offset.r32(), imm(56)); + a.short_().jmp(shift); + } + } else if (num_bytes_to_read <= 4) { + a.add(bin_base, RET); + if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) { + a.movbe(bitdata.r32(), + x86::dword_ptr(bin_base, num_bytes_to_read - 4)); + } else { + a.mov(bitdata.r32(), + x86::dword_ptr(bin_base, num_bytes_to_read - 4)); + a.bswap(bitdata.r32()); + } + a.add(bin_offset.r32(), imm(64 - 8 * num_bytes_to_read)); + a.short_().jmp(shift); + } else { + a.add(bin_base, RET); + if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) { + a.movbe(bitdata, x86::qword_ptr(bin_base, num_bytes_to_read - 8)); + } else { + a.mov(bitdata, x86::qword_ptr(bin_base, num_bytes_to_read - 8)); + a.bswap(bitdata); + } + if (num_bytes_to_read < 8) { + a.add(bin_offset.r32(), imm(64 - 8 * num_bytes_to_read)); + } + a.short_().jmp(shift); + } + + /* We'll need an extra byte and we will need to shift. */ + a.bind(handle_partial); + + if (num_bytes_to_read == 1) { + a.mov(bitdata.r16(), x86::word_ptr(bin_base, RET)); + a.bswap(bitdata); + } else if (num_bytes_to_read < 8) { + a.add(bin_base, RET); + a.mov(bitdata, x86::qword_ptr(bin_base, num_bytes_to_read - 7)); + a.shr(bitdata, imm(64 - 8 * (num_bytes_to_read + 1))); + a.bswap(bitdata); + } else { + a.add(bin_base, RET); + if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) { + a.movbe(bitdata, x86::qword_ptr(bin_base)); + } else { + a.mov(bitdata, x86::qword_ptr(bin_base)); + a.bswap(bitdata); + } + ASSERT(bitdata != x86::rcx); + if (bin_offset != x86::rcx) { + a.mov(x86::cl, bin_offset.r8()); + } + a.shl(bitdata, x86::cl); + a.mov(RET.r8(), x86::byte_ptr(bin_base, 8)); + a.movzx(RET.r32(), RET.r8()); + a.shl(RET.r32(), x86::cl); + a.shr(RET.r32(), imm(8)); + a.or_(bitdata, RET); + a.short_().jmp(read_done); + } + + /* Shift the read data into the most significant bits of the + * word. */ + a.bind(shift); + ASSERT(bitdata != x86::rcx); + if (bin_offset != x86::rcx) { + a.mov(x86::cl, bin_offset.r8()); + } + a.shl(bitdata, x86::cl); + + a.bind(read_done); +} + +/* + * Read an integer and store as a term. This function only handles + * integers of certain common sizes. This is a special optimization + * when only one integer is to be extracted from a binary. + * + * Input: bin_base, bin_offset + * + * Clobbers: bin_base, bin_offset, tmp, RET + */ +void BeamModuleAssembler::emit_read_integer(const x86::Gp bin_base, + const x86::Gp bin_offset, + const x86::Gp tmp, + Uint flags, + Uint bits, + const ArgRegister &Dst) { + Label handle_unaligned = a.newLabel(); + Label store = a.newLabel(); + x86::Mem address; + + a.mov(tmp, bin_offset); + a.shr(tmp, imm(3)); + a.and_(bin_offset.r32(), imm(7)); + + switch (bits) { + case 8: + address = x86::Mem(bin_base, tmp, 0, 0, 1); + if ((flags & BSF_SIGNED) == 0) { + a.movzx(RETd, address); + } else { + a.movsx(RET, address); + } + + a.short_().jz(store); + + a.bind(handle_unaligned); + address = x86::Mem(bin_base, tmp, 0, 0, 2); + if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) { + a.movbe(RET.r16(), address); + } else { + a.mov(RET.r16(), address); + a.xchg(x86::al, x86::ah); + } + ASSERT(bin_offset == x86::rcx); + a.shl(RETd, bin_offset.r8()); + a.shr(RETd, imm(8)); + if ((flags & BSF_SIGNED) == 0) { + a.movzx(RETd, RETb); + } else { + a.movsx(RET, RETb); + } + break; + case 16: + address = x86::Mem(bin_base, tmp, 0, 0, 2); + if ((flags & BSF_LITTLE) != 0) { + if ((flags & BSF_SIGNED) == 0) { + a.movzx(RETd, address); + } else { + a.movsx(RET, address); + } + } else { + /* Big-endian segment. */ + if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) { + a.movbe(RET.r16(), address); + } else { + a.mov(RET.r16(), address); + a.xchg(x86::al, x86::ah); + } + + if ((flags & BSF_SIGNED) != 0) { + a.movsx(RET, RET.r16()); + } else { + a.movzx(RET, RET.r16()); + } + } + + a.short_().jz(store); + + a.bind(handle_unaligned); + a.add(bin_base, tmp); + address = x86::Mem(bin_base, -1, 4); + if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) { + a.movbe(RETd, address); + } else { + a.mov(RETd, address); + a.bswap(RETd); + } + ASSERT(bin_offset == x86::rcx); + a.shl(RETd, bin_offset.r8()); + a.shr(RETd, imm(8)); + + if ((flags & BSF_LITTLE) != 0) { + a.xchg(x86::al, x86::ah); + } + + if ((flags & BSF_SIGNED) == 0) { + a.movzx(RETd, RET.r16()); + } else { + a.movsx(RET, RET.r16()); + } + break; + case 32: + address = x86::Mem(bin_base, tmp, 0, 0, 4); + if ((flags & BSF_LITTLE) != 0) { + /* Little-endian segment. */ + if ((flags & BSF_SIGNED) == 0) { + a.mov(RETd, address); + } else { + a.movsxd(RET, address); + } + } else { + /* Big-endian segment. */ + if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) { + a.movbe(RETd, address); + } else { + a.mov(RETd, address); + a.bswap(RETd); + } + + if ((flags & BSF_SIGNED) != 0) { + a.movsxd(RET, RETd); + } + } + + a.short_().jz(store); + + a.bind(handle_unaligned); + a.add(bin_base, tmp); + address = x86::Mem(bin_base, -3, 8); + if (hasCpuFeature(CpuFeatures::X86::kMOVBE)) { + a.movbe(RET, address); + } else { + a.mov(RET, address); + a.bswap(RET); + } + ASSERT(bin_offset == x86::rcx); + a.shl(RET, bin_offset.r8()); + a.shr(RET, imm(8)); + + if ((flags & BSF_LITTLE) != 0) { + a.bswap(RETd); + } + + if ((flags & BSF_SIGNED) == 0) { + a.mov(RETd, RETd); + } else { + a.movsxd(RET, RETd); + } + break; + default: + ASSERT(0); + break; + } + + a.bind(store); + a.shl(RET, imm(_TAG_IMMED1_SIZE)); + a.or_(RET, imm(_TAG_IMMED1_SMALL)); + mov_arg(Dst, RET); +} + +void BeamModuleAssembler::emit_extract_integer(const x86::Gp bitdata, + const x86::Gp tmp, + Uint flags, + Uint bits, + const ArgRegister &Dst) { + if (bits == 0) { + /* Necessary for correctness when matching a zero-size + * signed segment. + */ + mov_arg(Dst, make_small(0)); + return; + } + + Label big = a.newLabel(); + Label done = a.newLabel(); + Uint num_partial = bits % 8; + Uint num_complete = 8 * (bits / 8); + + if (bits <= 8) { + /* Endian does not matter for values that fit in a byte. */ + flags &= ~BSF_LITTLE; + } + + if ((flags & BSF_LITTLE) == 0) { + /* Big-endian segment. */ + a.mov(RET, bitdata); + } else if ((flags & BSF_LITTLE) != 0) { + /* Reverse endianness for this little-endian segment. */ + if (num_partial == 0) { + a.mov(RET, bitdata); + a.bswap(RET); + if (bits < 64) { + a.shl(RET, imm(64 - num_complete)); + } + } else { + Uint shifted_mask = ((1 << num_partial) - 1) << (8 - num_partial); + a.mov(tmp, bitdata); + a.shr(tmp, imm(64 - num_complete)); + a.bswap(tmp); + a.shr(tmp, imm(num_partial)); + + a.mov(RET, bitdata); + a.rol(RET, imm(num_complete + 8)); + a.and_(RETd, imm(shifted_mask)); + a.ror(RET, imm(8)); + a.or_(RET, tmp); + } + } + + /* Now the extracted data is in RET. */ + if (bits >= SMALL_BITS) { + /* Handle segments whose values might not fit in a small + * integer. */ + Label small = a.newLabel(); + comment("test whether this integer is a small"); + if (bits < 64) { + if ((flags & BSF_SIGNED) == 0) { + /* Unsigned segment. */ + a.shr(RET, imm(64 - bits)); + } else { + /* Signed segment. */ + a.sar(RET, imm(64 - bits)); + } + } + a.mov(tmp, RET); + a.shr(tmp, imm(SMALL_BITS - 1)); + if ((flags & BSF_SIGNED) == 0) { + /* Unsigned segment. */ + a.jnz(big); + } else { + /* Signed segment. */ + a.jz(small); + a.cmp(tmp.r32(), imm(_TAG_IMMED1_MASK << 1 | 1)); + a.jnz(big); + } + + comment("store extracted integer as a small"); + a.bind(small); + a.shl(RET, imm(_TAG_IMMED1_SIZE)); + a.or_(RET, imm(_TAG_IMMED1_SMALL)); + a.short_().jmp(done); + } else { + /* This segment always fits in a small. */ + comment("store extracted integer as a small"); + if ((flags & BSF_SIGNED) == 0) { + /* Unsigned segment. */ + a.shr(RET, imm(64 - bits - _TAG_IMMED1_SIZE)); + } else { + /* Signed segment. */ + a.sar(RET, imm(64 - bits - _TAG_IMMED1_SIZE)); + } + ERTS_CT_ASSERT(_TAG_IMMED1_SMALL == (1 << _TAG_IMMED1_SIZE) - 1); + a.or_(RET, imm(_TAG_IMMED1_SMALL)); + } + + a.bind(big); + if (bits >= SMALL_BITS) { + comment("store extracted integer as a bignum"); + if ((flags & BSF_SIGNED) == 0) { + /* Unsigned segment. */ + a.mov(x86::qword_ptr(HTOP), make_pos_bignum_header(1)); + a.mov(x86::qword_ptr(HTOP, sizeof(Eterm)), RET); + } else { + Label negative = a.newLabel(); + Label sign_done = a.newLabel(); + + /* Signed segment. */ + a.test(RET, RET); + a.short_().jl(negative); + + a.mov(x86::qword_ptr(HTOP), make_pos_bignum_header(1)); + a.short_().jmp(sign_done); + + a.bind(negative); + a.mov(x86::qword_ptr(HTOP), make_neg_bignum_header(1)); + a.neg(RET); + + a.bind(sign_done); + a.mov(x86::qword_ptr(HTOP, sizeof(Eterm)), RET); + } + a.lea(RET, x86::qword_ptr(HTOP, TAG_PRIMARY_BOXED)); + a.add(HTOP, imm(sizeof(Eterm[2]))); + } + + a.bind(done); + mov_arg(Dst, RET); +} + +/* + * Clobbers: RET + */ +void BeamModuleAssembler::emit_extract_binary(const x86::Gp bitdata, + Uint bits, + const ArgRegister &Dst) { + Uint num_bytes = bits / 8; + + a.lea(RET, x86::qword_ptr(HTOP, TAG_PRIMARY_BOXED)); + mov_arg(Dst, RET); + a.mov(x86::qword_ptr(HTOP), header_heap_bin(num_bytes)); + a.mov(x86::qword_ptr(HTOP, sizeof(Eterm)), imm(num_bytes)); + a.mov(RET, bitdata); + a.bswap(RET); + a.mov(x86::qword_ptr(HTOP, 2 * sizeof(Eterm)), RET); + a.add(HTOP, imm(sizeof(Eterm[3]))); +} + +static std::vector<BsmSegment> opt_bsm_segments( + const std::vector<BsmSegment> segments, + const ArgWord &Need, + const ArgWord &Live) { + std::vector<BsmSegment> segs; + + Uint heap_need = Need.get(); + + /* + * First calculate the total number of heap words needed for + * bignums and binaries. + */ + for (auto seg : segments) { + switch (seg.action) { + case BsmSegment::action::GET_INTEGER: + if (seg.size >= SMALL_BITS) { + heap_need += BIG_NEED_FOR_BITS(seg.size); + } + break; + case BsmSegment::action::GET_BINARY: + heap_need += heap_bin_size((seg.size + 7) / 8); + break; + case BsmSegment::action::GET_TAIL: + heap_need += EXTRACT_SUB_BIN_HEAP_NEED; + break; + default: + break; + } + } + + int read_action_pos = -1; + int seg_index = 0; + int count = segments.size(); + + for (int i = 0; i < count; i++) { + auto seg = segments[i]; + if (heap_need != 0 && seg.live.isWord()) { + BsmSegment s = seg; + + s.action = BsmSegment::action::TEST_HEAP; + s.size = heap_need; + segs.push_back(s); + heap_need = 0; + seg_index++; + } + + switch (seg.action) { + case BsmSegment::action::GET_INTEGER: + case BsmSegment::action::GET_BINARY: { + bool is_common_size; + switch (seg.size) { + case 8: + case 16: + case 32: + is_common_size = true; + break; + default: + is_common_size = false; + break; + } + + if (seg.size > 64) { + read_action_pos = -1; + } else if (seg.action == BsmSegment::action::GET_BINARY && + seg.size % 8 != 0) { + read_action_pos = -1; + } else if ((seg.flags & BSF_LITTLE) != 0 && is_common_size) { + seg.action = BsmSegment::action::READ_INTEGER; + read_action_pos = -1; + } else if (read_action_pos < 0 && + seg.action == BsmSegment::action::GET_INTEGER && + is_common_size && i + 1 == count) { + seg.action = BsmSegment::action::READ_INTEGER; + read_action_pos = -1; + } else { + if ((seg.flags & BSF_LITTLE) != 0 || read_action_pos < 0 || + seg.size + segs.at(read_action_pos).size > 64) { + BsmSegment s; + + /* Create a new READ action. */ + read_action_pos = seg_index; + s.action = BsmSegment::action::READ; + s.size = seg.size; + segs.push_back(s); + seg_index++; + } else { + /* Reuse previous READ action. */ + segs.at(read_action_pos).size += seg.size; + } + switch (seg.action) { + case BsmSegment::action::GET_INTEGER: + seg.action = BsmSegment::action::EXTRACT_INTEGER; + break; + case BsmSegment::action::GET_BINARY: + seg.action = BsmSegment::action::EXTRACT_BINARY; + break; + default: + break; + } + } + segs.push_back(seg); + break; + } + case BsmSegment::action::SKIP: + if (read_action_pos >= 0 && + seg.size + segs.at(read_action_pos).size <= 64) { + segs.at(read_action_pos).size += seg.size; + seg.action = BsmSegment::action::DROP; + } else { + read_action_pos = -1; + } + segs.push_back(seg); + break; + default: + read_action_pos = -1; + segs.push_back(seg); + break; + } + seg_index++; + } + + /* Handle a trailing test_heap instruction (for the + * i_bs_match_test_heap instruction). */ + if (heap_need) { + BsmSegment seg; + + seg.action = BsmSegment::action::TEST_HEAP; + seg.size = heap_need; + seg.live = Live; + segs.push_back(seg); + } + return segs; +} + +void BeamModuleAssembler::emit_i_bs_match(ArgLabel const &Fail, + ArgRegister const &Ctx, + Span<ArgVal> const &List) { + emit_i_bs_match_test_heap(Fail, Ctx, ArgWord(0), ArgWord(0), List); +} + +void BeamModuleAssembler::emit_i_bs_match_test_heap(ArgLabel const &Fail, + ArgRegister const &Ctx, + ArgWord const &Need, + ArgWord const &Live, + Span<ArgVal> const &List) { + const int orig_offset = offsetof(ErlBinMatchState, mb.orig); + const int base_offset = offsetof(ErlBinMatchState, mb.base); + const int position_offset = offsetof(ErlBinMatchState, mb.offset); + const int size_offset = offsetof(ErlBinMatchState, mb.size); + + std::vector<BsmSegment> segments; + + auto current = List.begin(); + auto end = List.begin() + List.size(); + + while (current < end) { + auto cmd = current++->as<ArgImmed>().get(); + BsmSegment seg; + + switch (cmd) { + case am_ensure_at_least: { + seg.action = BsmSegment::action::ENSURE_AT_LEAST; + seg.size = current[0].as<ArgWord>().get(); + seg.unit = current[1].as<ArgWord>().get(); + current += 2; + break; + } + case am_ensure_exactly: { + seg.action = BsmSegment::action::ENSURE_EXACTLY; + seg.size = current[0].as<ArgWord>().get(); + current += 1; + break; + } + case am_binary: + case am_integer: { + auto size = current[2].as<ArgWord>().get(); + auto unit = current[3].as<ArgWord>().get(); + + switch (cmd) { + case am_integer: + seg.action = BsmSegment::action::GET_INTEGER; + break; + case am_binary: + seg.action = BsmSegment::action::GET_BINARY; + break; + } + + seg.live = current[0]; + seg.size = size * unit; + seg.unit = unit; + seg.flags = current[1].as<ArgWord>().get(); + seg.dst = current[4].as<ArgRegister>(); + current += 5; + break; + } + case am_get_tail: { + seg.action = BsmSegment::action::GET_TAIL; + seg.live = current[0].as<ArgWord>(); + seg.dst = current[2].as<ArgRegister>(); + current += 3; + break; + } + case am_skip: { + seg.action = BsmSegment::action::SKIP; + seg.size = current[0].as<ArgWord>().get(); + seg.flags = 0; + current += 1; + break; + } + default: + abort(); + break; + } + segments.push_back(seg); + } + + segments = opt_bsm_segments(segments, Need, Live); + +#ifdef WIN32 + const x86::Gp bin_position = ARG1; + const x86::Gp bitdata = ARG4; +#else + const x86::Gp bin_position = ARG4; + const x86::Gp bitdata = ARG1; +#endif + ASSERT(bin_position == x86::rcx); + const x86::Gp bin_base = ARG2; + const x86::Gp ctx = ARG3; + const x86::Gp tmp = ARG5; + + bool is_ctx_valid = false; + bool is_position_valid = false; + bool is_last = false; + int count = segments.size(); + + for (int i = 0; i < count; i++) { + auto seg = segments[i]; + is_last = i == count - 1; + switch (seg.action) { + case BsmSegment::action::ENSURE_AT_LEAST: { + auto size = seg.size; + auto unit = seg.unit; + comment("ensure_at_least %ld %ld", size, seg.unit); + mov_arg(ctx, Ctx); + if (unit == 1) { + a.mov(bin_position, emit_boxed_val(ctx, position_offset)); + a.lea(RET, qword_ptr(bin_position, size)); + a.cmp(RET, emit_boxed_val(ctx, size_offset)); + a.ja(resolve_beam_label(Fail)); + } else { + a.mov(RET, emit_boxed_val(ctx, size_offset)); + a.mov(bin_position, emit_boxed_val(ctx, position_offset)); + a.sub(RET, bin_position); + if (size != 0) { + cmp(RET, size, tmp); + } + a.jl(resolve_beam_label(Fail)); + } + + is_ctx_valid = is_position_valid = true; + + if (unit != 1) { + if (size % unit != 0) { + sub(RET, size, ARG1); + } + + if ((unit & (unit - 1))) { + /* Clobbers ARG3 */ + a.cqo(); + mov_imm(tmp, unit); + a.div(tmp); + a.test(x86::rdx, x86::rdx); + is_ctx_valid = is_position_valid = false; + } else { + a.test(RETb, imm(unit - 1)); + } + a.jnz(resolve_beam_label(Fail)); + } + break; + } + case BsmSegment::action::ENSURE_EXACTLY: { + auto size = seg.size; + comment("ensure_exactly %ld", size); + + mov_arg(ctx, Ctx); + a.mov(RET, emit_boxed_val(ctx, size_offset)); + if (is_last) { + a.sub(RET, emit_boxed_val(ctx, position_offset)); + is_ctx_valid = is_position_valid = false; + } else { + a.mov(bin_position, emit_boxed_val(ctx, position_offset)); + a.sub(RET, bin_position); + is_ctx_valid = is_position_valid = true; + } + if (size != 0) { + cmp(RET, size, tmp); + } + a.jne(resolve_beam_label(Fail)); + break; + } + case BsmSegment::action::TEST_HEAP: { + comment("test_heap %ld", seg.size); + emit_gc_test(ArgWord(0), ArgWord(seg.size), seg.live); + is_ctx_valid = is_position_valid = false; + break; + } + case BsmSegment::action::READ: { + comment("read %ld", seg.size); + if (seg.size == 0) { + comment("(nothing to do)"); + } else { + if (!is_ctx_valid) { + mov_arg(ctx, Ctx); + is_ctx_valid = true; + } + if (!is_position_valid) { + a.mov(bin_position, emit_boxed_val(ctx, position_offset)); + is_position_valid = true; + } + a.mov(bin_base, emit_boxed_val(ctx, base_offset)); + a.add(emit_boxed_val(ctx, position_offset), imm(seg.size)); + + emit_read_bits(seg.size, bin_base, bin_position, bitdata); + } + + is_position_valid = false; + break; + } + case BsmSegment::action::EXTRACT_BINARY: { + auto bits = seg.size; + auto Dst = seg.dst; + + comment("extract binary %ld", bits); + emit_extract_binary(bitdata, bits, Dst); + if (!is_last && bits != 0 && bits != 64) { + a.shl(bitdata, imm(bits)); + } + break; + } + case BsmSegment::action::EXTRACT_INTEGER: { + auto bits = seg.size; + auto flags = seg.flags; + auto Dst = seg.dst; + + comment("extract integer %ld", bits); + if (is_last && flags == 0 && bits < SMALL_BITS) { + a.shr(bitdata, imm(64 - bits - _TAG_IMMED1_SIZE)); + a.or_(bitdata, imm(_TAG_IMMED1_SMALL)); + mov_arg(Dst, bitdata); + } else { + emit_extract_integer(bitdata, tmp, flags, bits, Dst); + if (!is_last && bits != 0 && bits != 64) { + a.shl(bitdata, imm(bits)); + } + } + + /* bin_position is clobbered. */ + is_position_valid = false; + break; + } + case BsmSegment::action::READ_INTEGER: { + auto bits = seg.size; + auto flags = seg.flags; + auto Dst = seg.dst; + + comment("read integer %ld", bits); + if (!is_ctx_valid) { + mov_arg(ctx, Ctx); + is_ctx_valid = true; + } + if (!is_position_valid) { + a.mov(bin_position, emit_boxed_val(ctx, position_offset)); + is_position_valid = true; + } + + a.mov(bin_base, emit_boxed_val(ctx, base_offset)); + a.add(emit_boxed_val(ctx, position_offset), imm(seg.size)); + emit_read_integer(bin_base, bin_position, tmp, flags, bits, Dst); + + is_position_valid = false; + break; + } + case BsmSegment::action::GET_INTEGER: { + Uint flags = seg.flags; + auto bits = seg.size; + auto Dst = seg.dst; + + comment("get integer %ld", bits); + if (!is_ctx_valid) { + mov_arg(ctx, Ctx); + } + + a.lea(ARG4, emit_boxed_val(ctx, offsetof(ErlBinMatchState, mb))); + + if (bits >= SMALL_BITS) { + emit_enter_runtime<Update::eReductions | Update::eStack | + Update::eHeap>(); + } else { + emit_enter_runtime(); + } + + a.mov(ARG1, c_p); + a.mov(ARG2, bits); + a.mov(ARG3, flags); + /* ARG4 set above */ + runtime_call<4>(erts_bs_get_integer_2); + + if (bits >= SMALL_BITS) { + emit_leave_runtime<Update::eReductions | Update::eStack | + Update::eHeap>(); + } else { + emit_leave_runtime(); + } + + mov_arg(Dst, RET); + + is_ctx_valid = is_position_valid = false; + break; + } + case BsmSegment::action::GET_BINARY: { + comment("get binary %ld", seg.size); + if (is_ctx_valid) { + a.mov(RET, ctx); + } else { + mov_arg(RET, Ctx); + } + emit_enter_runtime<Update::eHeap>(); + a.lea(ARG1, x86::qword_ptr(c_p, offsetof(Process, htop))); + a.mov(ARG2, emit_boxed_val(RET, orig_offset)); + a.mov(ARG3, emit_boxed_val(RET, base_offset)); + a.mov(ARG4, emit_boxed_val(RET, position_offset)); + mov_imm(ARG5, seg.size); + a.add(emit_boxed_val(RET, position_offset), ARG5); + + runtime_call<5>(erts_extract_sub_binary); + + emit_leave_runtime<Update::eHeap>(); + mov_arg(seg.dst, RET); + + is_ctx_valid = is_position_valid = false; + break; + } + case BsmSegment::action::GET_TAIL: { + comment("get_tail"); + if (is_ctx_valid) { + a.mov(ARG1, ctx); + } else { + mov_arg(ARG1, Ctx); + } + safe_fragment_call(ga->get_bs_get_tail_shared()); + mov_arg(seg.dst, RET); + is_ctx_valid = is_position_valid = false; + break; + } + case BsmSegment::action::SKIP: { + comment("skip %ld", seg.size); + if (!is_ctx_valid) { + mov_arg(ctx, Ctx); + is_ctx_valid = true; + } + /* The compiler limits the size of any segment in a bs_match + * instruction to 24 bits. */ + ASSERT((seg.size >> 24) == 0); + a.add(emit_boxed_val(ctx, position_offset), imm(seg.size)); + is_position_valid = false; + break; + } + case BsmSegment::action::DROP: + auto bits = seg.size; + comment("drop %ld", bits); + if (bits != 0 && bits != 64) { + a.shl(bitdata, imm(bits)); + } + break; + } + } +} diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab index 1da4f80e6d..37914d7363 100644 --- a/erts/emulator/beam/jit/x86/ops.tab +++ b/erts/emulator/beam/jit/x86/ops.tab @@ -849,7 +849,19 @@ i_lambda_trampoline F f W W i_breakpoint_trampoline # ================================================================ -# New bit syntax matching (R11B). +# New bit syntax matching for fixed sizes (from OTP 26). +# ================================================================ + +bs_match Fail Ctx Size Rest=* => bs_match(Fail, Ctx, Size, Rest) + +i_bs_match Fail Ctx Rest=* | test_heap Need Live => + i_bs_match_test_heap Fail Ctx Need Live Rest + +i_bs_match f S * +i_bs_match_test_heap f S I t * + +# ================================================================ +# Bit syntax matching (from R11B). # ================================================================ %warm diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index 33a80f2e53..40e699e8ab 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -149,6 +149,16 @@ NO_OPT= bs_bincomp \ guard \ map +R25= \ + bs_bincomp \ + bs_construct \ + bs_match_bin \ + bs_match_int \ + bs_match_tail \ + bs_match_misc \ + bs_utf + + NATIVE= hibernate NO_OPT_MODULES= $(NO_OPT:%=%_no_opt_SUITE) @@ -157,6 +167,9 @@ NO_OPT_ERL_FILES= $(NO_OPT_MODULES:%=%.erl) NATIVE_MODULES= $(NATIVE:%=%_native_SUITE) NATIVE_ERL_FILES= $(NATIVE_MODULES:%=%.erl) +R25_MODULES= $(R25:%=%_r25_SUITE) +R25_ERL_FILES= $(R25_MODULES:%=%.erl) + ERL_FILES= $(MODULES:%=%.erl) TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) @@ -188,13 +201,17 @@ ERL_COMPILE_FLAGS := $(filter-out +deterministic,$($(ERL_COMPILE_FLAGS))) # Targets # ---------------------------------------------------- -make_emakefile: $(NO_OPT_ERL_FILES) $(NATIVE_ERL_FILES) $(KERNEL_ERL_FILES) +make_emakefile: $(NO_OPT_ERL_FILES) $(NATIVE_ERL_FILES) \ + $(KERNEL_ERL_FILES) $(R25_ERL_FILES) $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) +compressed -o$(EBIN) \ $(MODULES) $(KERNEL_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +no_copt +no_postopt +no_ssa_opt +no_bsm_opt \ $(ERL_COMPILE_FLAGS) -o$(EBIN) $(NO_OPT_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) \ -o$(EBIN) $(NATIVE_MODULES) >> $(EMAKEFILE) + $(ERL_TOP)/make/make_emakefile +r25 \ + $(ERL_COMPILE_FLAGS) -o$(EBIN) $(R25_MODULES) >> $(EMAKEFILE) + tests debug opt: make_emakefile erl $(ERL_MAKE_FLAGS) -make @@ -218,6 +235,9 @@ targets: $(TARGET_FILES) %_native_SUITE.erl: %_SUITE.erl sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ +%_r25_SUITE.erl: %_SUITE.erl + sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ + # ---------------------------------------------------- # Release Target # ---------------------------------------------------- @@ -232,6 +252,7 @@ release_tests_spec: make_emakefile $(INSTALL_DATA) $(NO_OPT_ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(NATIVE_ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(KERNEL_ERL_FILES) "$(RELSYSDIR)" + $(INSTALL_DATA) $(R25_ERL_FILES) "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" tar cf - *_SUITE_data property_test | (cd "$(RELSYSDIR)"; tar xf -) diff --git a/erts/emulator/test/bs_construct_SUITE.erl b/erts/emulator/test/bs_construct_SUITE.erl index 099bae1cee..b8e2ec976b 100644 --- a/erts/emulator/test/bs_construct_SUITE.erl +++ b/erts/emulator/test/bs_construct_SUITE.erl @@ -829,7 +829,8 @@ dynamic_little(Bef, N, Int, Lpad, Rpad) -> %% Test that the bs_add/5 instruction handles big numbers correctly. bs_add(Config) when is_list(Config) -> - Mod = bs_construct_bs_add, + Mod = list_to_atom(atom_to_list(?MODULE) ++ "_" ++ + atom_to_list(?FUNCTION_NAME)), N = 2000, Code = [{module, Mod}, {exports, [{bs_add,2}]}, @@ -880,9 +881,12 @@ bs_add(Config) when is_list(Config) -> %% Clean up. ok = file:delete(AsmFile), ok = file:delete(code:which(Mod)), + _ = code:delete(Mod), + _ = code:purge(Mod), + ok. - + smallest_big() -> smallest_big_1(1 bsl 24). diff --git a/erts/emulator/test/bs_match_bin_SUITE.erl b/erts/emulator/test/bs_match_bin_SUITE.erl index a7f5ad2d6b..68119b138c 100644 --- a/erts/emulator/test/bs_match_bin_SUITE.erl +++ b/erts/emulator/test/bs_match_bin_SUITE.erl @@ -23,17 +23,17 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, byte_split_binary/1,bit_split_binary/1,match_huge_bin/1, - bs_match_string_edge_case/1]). + bs_match_string_edge_case/1,contexts/1]). -include_lib("common_test/include/ct.hrl"). suite() -> [{ct_hooks,[ts_install_cth]}]. -all() -> +all() -> [byte_split_binary, bit_split_binary, match_huge_bin, - bs_match_string_edge_case]. + bs_match_string_edge_case, contexts]. -groups() -> +groups() -> []. init_per_suite(Config) -> @@ -235,3 +235,38 @@ bs_match_string_edge_case(_Config) -> <<?MATCH512, " ", Tail1/binary>> = Bin, <<" ", Tail1/binary>> = id(Tail0), ok. + +contexts(_Config) -> + Bytes = rand:bytes(12), + _ = [begin + <<B:N/binary,_/binary>> = Bytes, + B = id(get_binary(B)) + end || N <- lists:seq(0, 12)], + ok. + +get_binary(Bin) -> + [A,B,C,D,E,F] = id([1,2,3,4,5,6]), + {Res,_} = get_binary_memory_ctx(A, B, C, D, E, F, Bin), + Res. + + +get_binary_memory_ctx(A, B, C, D, E, F, Bin) -> + %% The match context will be in {x,6}, which is not + %% a X register backed by a CPU register on any platform. + Res = case Bin of + <<Res0:0/binary>> -> Res0; + <<Res0:1/binary>> -> Res0; + <<Res0:2/binary>> -> Res0; + <<Res0:3/binary>> -> Res0; + <<Res0:4/binary>> -> Res0; + <<Res0:5/binary>> -> Res0; + <<Res0:6/binary>> -> Res0; + <<Res0:7/binary>> -> Res0; + <<Res0:8/binary>> -> Res0; + <<Res0:9/binary>> -> Res0; + <<Res0:10/binary>> -> Res0; + <<Res0:11/binary>> -> Res0; + <<Res0:12/binary>> -> Res0 + end, + {Res,{A,B,C,D,E,F}}. + diff --git a/erts/emulator/test/bs_match_int_SUITE.erl b/erts/emulator/test/bs_match_int_SUITE.erl index 0268ba18c8..48775135dc 100644 --- a/erts/emulator/test/bs_match_int_SUITE.erl +++ b/erts/emulator/test/bs_match_int_SUITE.erl @@ -19,10 +19,10 @@ -module(bs_match_int_SUITE). --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, - integer/1,signed_integer/1,dynamic/1,more_dynamic/1,mml/1, - match_huge_int/1,bignum/1,unaligned_32_bit/1]). + integer/1,mixed_sizes/1,signed_integer/1,dynamic/1,more_dynamic/1, + mml/1,match_huge_int/1,bignum/1,unaligned_32_bit/1]). -include_lib("common_test/include/ct.hrl"). @@ -30,11 +30,11 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. -all() -> - [integer, signed_integer, dynamic, more_dynamic, mml, +all() -> + [integer, mixed_sizes, signed_integer, dynamic, more_dynamic, mml, match_huge_int, bignum, unaligned_32_bit]. -groups() -> +groups() -> []. init_per_suite(Config) -> @@ -51,6 +51,9 @@ end_per_group(_GroupName, Config) -> integer(Config) when is_list(Config) -> + _ = rand:uniform(), %Seed generator + io:format("Seed: ~p", [rand:export_seed()]), + 0 = get_int(mkbin([])), 0 = get_int(mkbin([0])), 42 = get_int(mkbin([42])), @@ -58,36 +61,628 @@ integer(Config) when is_list(Config) -> 256 = get_int(mkbin([1,0])), 257 = get_int(mkbin([1,1])), 258 = get_int(mkbin([1,2])), - 258 = get_int(mkbin([1,2])), 65534 = get_int(mkbin([255,254])), 16776455 = get_int(mkbin([255,253,7])), 4245492555 = get_int(mkbin([253,13,19,75])), 4294967294 = get_int(mkbin([255,255,255,254])), 4294967295 = get_int(mkbin([255,255,255,255])), + + 16#cafebeef = get_int(<<16#cafebeef:32>>), + 16#cafebeef42 = get_int(<<16#cafebeef42:40>>), + 16#cafebeeffeed = get_int(<<16#cafebeeffeed:48>>), + 16#cafebeeffeed42 = get_int(<<16#cafebeeffeed42:56>>), + 16#1cafebeeffeed42 = get_int(<<16#1cafebeeffeed42:57>>), + 16#2cafebeeffeed42 = get_int(<<16#2cafebeeffeed42:58>>), + 16#7cafebeeffeed42 = get_int(<<16#7cafebeeffeed42:59>>), + + 16#beefcafefeed = get_int(<<16#beefcafefeed:60>>), + 16#beefcafefeed = get_int(<<16#beefcafefeed:61>>), + 16#beefcafefeed = get_int(<<16#beefcafefeed:62>>), + 16#beefcafefeed = get_int(<<16#beefcafefeed:63>>), + + 16#cafebeeffeed42 = get_int(<<16#cafebeeffeed42:60>>), + 16#cafebeeffeed42 = get_int(<<16#cafebeeffeed42:61>>), + 16#cafebeeffeed42 = get_int(<<16#cafebeeffeed42:62>>), + 16#cafebeeffeed42 = get_int(<<16#cafebeeffeed42:63>>), + + 16#acafebeeffeed42 = get_int(<<16#acafebeeffeed42:60>>), + 16#acafebeeffeed42 = get_int(<<16#acafebeeffeed42:61>>), + 16#acafebeeffeed42 = get_int(<<16#acafebeeffeed42:62>>), + 16#acafebeeffeed42 = get_int(<<16#acafebeeffeed42:63>>), + + 16#cafebeeffeed = get_int(<<16#cafebeeffeed:64>>), + 16#cafebeeffeedface = get_int(<<16#cafebeeffeedface:64>>), + + get_int_roundtrip(rand:bytes(12), 0), + get_int_roundtrip(rand:bytes(12), 0), + Eight = [200,1,19,128,222,42,97,111], cmp128(Eight, uint(Eight)), - fun_clause(catch get_int(mkbin(seq(1,5)))), + fun_clause(catch get_int(mkbin(seq(1, 20)))), + ok. + +get_int_roundtrip(Bin0, Size) when Size =< 8*byte_size(Bin0) -> + <<Bin:Size/bits,_/bits>> = Bin0, + <<I:Size>> = Bin, + I = get_int(Bin), + get_int_roundtrip(Bin0, Size+1); +get_int_roundtrip(_, _) -> ok. + +get_int(Bin0) -> + %% Note that it has become impossible to create a byte-sized sub + %% binary (see erts_extract_sub_binary() in erl_bits.c) of size 64 + %% or less. Therefore, to be able to create an unaligned binary, + %% we'll need to base it on on a binary with more than 64 bytes. + Size = bit_size(Bin0), + Filler = rand:bytes(65), + UnsignedBigBin = id(<<Filler/binary,Bin0/bits>>), + I = get_unsigned_big(UnsignedBigBin), + + %% io:format("~p ~p\n", [Size,I]), + if + Size =< 10*8 -> + OversizedUnsignedBig = id(<<Filler/binary,0:16,Bin0/bits>>), + I = get_unsigned_big(OversizedUnsignedBig); + true -> + ok + end, + + test_unaligned(UnsignedBigBin, I, fun get_unsigned_big/1), + + %% Test unsigned little-endian integers. + + UnsignedLittleBin = id(<<Filler/binary,I:Size/little>>), + I = get_unsigned_little(UnsignedLittleBin), + + test_unaligned(UnsignedLittleBin, I, fun get_unsigned_little/1), + + %% Test signed big-endian integers. + + SignedBigBin1 = id(<<Filler/binary,(-I):(Size+1)/big>>), + I = -get_signed_big(SignedBigBin1), + + SignedBigBin2 = id(<<Filler/binary,I:(Size+1)/big>>), + I = get_signed_big(SignedBigBin2), + + %% test_unaligned(SignedBigBin1, -I, fun get_signed_big/1), + test_unaligned(SignedBigBin2, I, fun get_signed_big/1), + + %% Test signed little-endian integers. + + SignedLittleBin1 = id(<<Filler/binary,(-I):(Size+1)/little>>), + I = -get_signed_little(SignedLittleBin1), + + SignedLittleBin2 = id(<<Filler/binary,I:(Size+1)/little>>), + I = get_signed_little(SignedLittleBin2), + + test_unaligned(SignedLittleBin1, -I, fun get_signed_little/1), + test_unaligned(SignedLittleBin2, I, fun get_signed_little/1), + + I. + +test_unaligned(Bin, I, Matcher) -> + <<RandBytes1:8/binary,RandBytes2:8/binary>> = rand:bytes(16), + Size = bit_size(Bin), + _ = [begin + <<_:(Offset+32),UnalignedBin:Size/bits,_/bits>> = + id(<<RandBytes1:(Offset+32)/bits, + Bin:Size/bits, + RandBytes2/binary>>), + I = Matcher(UnalignedBin) + end || Offset <- [1,2,3,4,5,6,7]], ok. -get_int(Bin) -> - I = get_int1(Bin), - get_int(Bin, I). -get_int(Bin0, I) when size(Bin0) < 4 -> - Bin = <<0,Bin0/binary>>, - I = get_int1(Bin), - get_int(Bin, I); -get_int(_, I) -> I. -get_int1(<<I:0>>) -> I; -get_int1(<<I:8>>) -> I; -get_int1(<<I:16>>) -> I; -get_int1(<<I:24>>) -> I; -get_int1(<<I:32>>) -> I. +get_unsigned_big(Bin) -> + Res = get_unsigned_big_plain(Bin), + [A,B,C,D,E] = id([1,2,3,4,5]), + {Res,_} = get_unsigned_big_memory_ctx(A, B, C, D, E, Res, Bin), + Res. + +get_unsigned_big_memory_ctx(A, B, C, D, E, Res0, Bin) -> + %% The match context will be in {x,6}, which is not + %% a X register backed by a CPU register on any platform. + Res = case Bin of + <<_:65/unit:8,I:7>> -> I; + <<_:65/unit:8,I:8>> -> I; + <<_:65/unit:8,I:36>> -> I; + <<_:65/unit:8,I:59>> -> I; + <<_:65/unit:8,I:60>> -> I; + <<_:65/unit:8,I:61>> -> I; + <<_:65/unit:8,I:62>> -> I; + <<_:65/unit:8,I:63>> -> I; + <<_:65/unit:8,I:64>> -> I; + <<_:65/unit:8,I:65>> -> I; + <<_:65/unit:8,I:70>> -> I; + <<_:65/unit:8,I:80>> -> I; + <<_:65/unit:8,I:95>> -> I; + _ -> Res0 + end, + {Res,{A,B,C,D,E}}. + +get_unsigned_big_plain(<<_:65/unit:8,I:0>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:1>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:2>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:3>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:4>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:5>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:6>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:7>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:8>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:9>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:10>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:11>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:12>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:13>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:14>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:15>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:16>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:17>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:18>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:19>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:20>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:21>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:22>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:23>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:24>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:25>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:26>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:27>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:28>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:29>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:30>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:31>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:32>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:33>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:34>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:35>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:36>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:37>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:38>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:39>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:40>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:41>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:42>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:43>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:44>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:45>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:46>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:47>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:48>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:49>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:50>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:51>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:52>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:53>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:54>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:55>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:56>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:57>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:58>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:59>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:60>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:61>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:62>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:63>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:64>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:65>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:66>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:67>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:68>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:69>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:70>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:71>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:72>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:73>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:74>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:75>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:76>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:77>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:78>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:79>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:80>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:81>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:82>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:83>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:84>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:85>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:86>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:87>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:88>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:89>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:90>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:91>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:92>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:93>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:94>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:95>>) -> I; +get_unsigned_big_plain(<<_:65/unit:8,I:96>>) -> I. + +get_unsigned_little(<<_:65/unit:8,I:0/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:1/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:2/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:3/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:4/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:5/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:6/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:7/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:8/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:9/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:10/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:11/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:12/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:13/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:14/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:15/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:16/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:17/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:18/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:19/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:20/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:21/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:22/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:23/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:24/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:25/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:26/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:27/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:28/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:29/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:30/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:31/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:32/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:33/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:34/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:35/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:36/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:37/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:38/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:39/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:40/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:41/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:42/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:43/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:44/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:45/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:46/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:47/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:48/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:49/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:50/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:51/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:52/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:53/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:54/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:55/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:56/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:57/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:58/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:59/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:60/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:61/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:62/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:63/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:64/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:65/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:66/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:67/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:68/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:69/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:70/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:71/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:72/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:73/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:74/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:75/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:76/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:77/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:78/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:79/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:80/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:81/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:82/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:83/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:84/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:85/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:86/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:87/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:88/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:89/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:90/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:91/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:92/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:93/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:94/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:95/little>>) -> I; +get_unsigned_little(<<_:65/unit:8,I:96/little>>) -> I. + +get_signed_big(<<_:65/unit:8,I:0/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:1/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:2/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:3/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:4/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:5/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:6/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:7/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:8/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:9/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:10/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:11/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:12/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:13/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:14/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:15/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:16/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:17/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:18/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:19/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:20/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:21/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:22/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:23/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:24/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:25/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:26/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:27/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:28/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:29/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:30/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:31/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:32/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:33/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:34/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:35/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:36/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:37/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:38/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:39/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:40/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:41/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:42/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:43/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:44/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:45/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:46/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:47/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:48/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:49/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:50/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:51/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:52/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:53/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:54/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:55/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:56/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:57/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:58/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:59/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:60/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:61/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:62/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:63/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:64/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:65/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:66/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:67/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:68/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:69/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:70/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:71/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:72/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:73/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:74/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:75/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:76/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:77/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:78/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:79/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:80/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:81/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:82/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:83/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:84/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:85/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:86/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:87/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:88/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:89/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:90/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:91/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:92/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:93/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:94/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:95/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:96/signed-big>>) -> I; +get_signed_big(<<_:65/unit:8,I:97/signed-big>>) -> I. + +get_signed_little(<<_:65/unit:8,I:0/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:1/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:2/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:3/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:4/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:5/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:6/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:7/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:8/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:9/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:10/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:11/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:12/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:13/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:14/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:15/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:16/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:17/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:18/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:19/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:20/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:21/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:22/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:23/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:24/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:25/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:26/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:27/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:28/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:29/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:30/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:31/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:32/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:33/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:34/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:35/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:36/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:37/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:38/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:39/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:40/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:41/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:42/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:43/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:44/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:45/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:46/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:47/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:48/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:49/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:50/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:51/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:52/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:53/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:54/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:55/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:56/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:57/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:58/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:59/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:60/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:61/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:62/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:63/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:64/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:65/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:66/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:67/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:68/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:69/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:70/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:71/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:72/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:73/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:74/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:75/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:76/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:77/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:78/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:79/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:80/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:81/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:82/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:83/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:84/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:85/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:86/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:87/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:88/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:89/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:90/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:91/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:92/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:93/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:94/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:95/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:96/signed-little>>) -> I; +get_signed_little(<<_:65/unit:8,I:97/signed-little>>) -> I. cmp128(<<I:128>>, I) -> equal; cmp128(_, _) -> not_equal. - + +mixed_sizes(_Config) -> + mixed({345,42}, + fun({A,B}) -> + <<A:9,1:1,B:6>>; + (<<A:9,_:1,B:6>>) -> + {A,B} + end), + + mixed({27033,59991,16#c001cafe,12345,2}, + fun({A,B,C,D,E}) -> + <<A:16,B:16,C:32,D:22,E:2>>; + (<<A:16,B:16,C:32,D:22,E:2>>) -> + {A,B,C,D,E} + end), + + mixed({79,153,17555,50_000,777_000,36#hugebignumber,2222}, + fun({A,B,C,D,E,F,G}) -> + <<A:7,B:8,C:15,0:3,D:17,E:23,F:88,G:13>>; + (<<A:7,B:8,C:15,_:3,D:17,E:23,F:88,G:13>>) -> + {A,B,C,D,E,F,G} + end), + + mixed({16#123456789ABCDEF,13,36#hugenum,979}, + fun({A,B,C,D}) -> + <<A:60,B:4,C:50,D:10>>; + (<<A:60,B:4,C:50,D:10>>) -> + {A,B,C,D} + end), + + mixed({16#123456789ABCDEF,13,36#hugenum,979}, + fun({A,B,C,D}) -> + <<A:60/little,B:4/little,C:50/little,D:10/little>>; + (<<A:60/little,B:4/little,C:50/little,D:10/little>>) -> + {A,B,C,D} + end), + + mixed({15692284513449131826, 17798, 33798}, + fun({A,B,C}) -> + <<A:64,B:15/little,C:16/little>>; + (<<A:64,B:15/little,C:16/little>>) -> + {A,B,C} + end), + + mixed({15692344284519131826, 1779863, 13556268}, + fun({A,B,C}) -> + <<A:64,B:23/little,C:24/little>>; + (<<A:64,B:23/little,C:24/little>>) -> + {A,B,C} + end), + + mixed({15519169234428431825, 194086885, 2813274043}, + fun({A,B,C}) -> + <<A:64,B:29/little,C:32/little>>; + (<<A:64,B:29/little,C:32/little>>) -> + {A,B,C} + end), + + mixed({5,9,38759385,93}, + fun({A,B,C,D}) -> + <<1:3,A:4,B:5,C:47,D:7>>; + (<<1:3,A:4,B:5,C:47,D:7>>) -> + {A,B,C,D} + end), + + mixed({2022,8,22}, + fun({A,B,C}) -> + <<A:16/little,B,C>>; + (<<A:16/little,B,C>>) -> + {A,B,C} + end), + + mixed({2022,8,22}, + fun({A,B,C}) -> + <<A:16/little,B,C>>; + (<<A:16/little,B,C>>) -> + _ = id(0), + {A,B,C} + end), + ok. + +mixed(Data, F) -> + Bin = F(Data), + Data = F(Bin), + true = is_bitstring(Bin). + signed_integer(Config) when is_list(Config) -> {no_match,_} = sint(mkbin([])), {no_match,_} = sint(mkbin([1,2,3])), @@ -133,7 +728,7 @@ dynamic(Bin, S1, S2, A, B) -> %% Extract integers at different alignments and of different sizes. more_dynamic(Config) when is_list(Config) -> - % Unsigned big-endian numbers. + %% Unsigned big-endian numbers. Unsigned = fun(Bin, List, SkipBef, N) -> SkipAft = 8*size(Bin) - N - SkipBef, <<_:SkipBef,Int:N,_:SkipAft>> = Bin, @@ -249,35 +844,40 @@ match_huge_int(Config) when is_list(Config) -> %% because of insufficient memory. {skip, "unoptimized code would use too much memory"}; bs_match_int_SUITE -> - Sz = 1 bsl 27, - Bin = <<0:Sz,13:8>>, - skip_huge_int_1(Sz, Bin), - 0 = match_huge_int_1(Sz, Bin), - - %% Test overflowing the size of an integer field. - nomatch = overflow_huge_int_skip_32(Bin), - case erlang:system_info(wordsize) of - 4 -> - nomatch = overflow_huge_int_32(Bin); - 8 -> - %% An attempt will be made to allocate heap space for - %% the bignum (which will probably fail); only if the - %% allocation succeeds will the matching fail because - %% the binary is too small. - ok - end, - nomatch = overflow_huge_int_skip_64(Bin), - nomatch = overflow_huge_int_64(Bin), - - %% Test overflowing the size of an integer field using - %% variables as sizes. - Sizes = case erlang:system_info(wordsize) of - 4 -> lists:seq(25, 32); - 8 -> [] - end ++ lists:seq(50, 64), - ok = overflow_huge_int_unit128(Bin, Sizes) + do_match_huge_int(); + bs_match_int_r25_SUITE -> + do_match_huge_int() end. +do_match_huge_int() -> + Sz = 1 bsl 27, + Bin = <<0:Sz,13:8>>, + skip_huge_int_1(Sz, Bin), + 0 = match_huge_int_1(Sz, Bin), + + %% Test overflowing the size of an integer field. + nomatch = overflow_huge_int_skip_32(Bin), + case erlang:system_info(wordsize) of + 4 -> + nomatch = overflow_huge_int_32(Bin); + 8 -> + %% An attempt will be made to allocate heap space for + %% the bignum (which will probably fail); only if the + %% allocation succeeds will the matching fail because + %% the binary is too small. + ok + end, + nomatch = overflow_huge_int_skip_64(Bin), + nomatch = overflow_huge_int_64(Bin), + + %% Test overflowing the size of an integer field using + %% variables as sizes. + Sizes = case erlang:system_info(wordsize) of + 4 -> lists:seq(25, 32); + 8 -> [] + end ++ lists:seq(50, 64), + ok = overflow_huge_int_unit128(Bin, Sizes). + overflow_huge_int_unit128(Bin, [Sz0|Sizes]) -> Sz = id(1 bsl Sz0), case Bin of @@ -375,5 +975,9 @@ unaligned_32_bit_1(_) -> unaligned_32_bit_verify([], 0) -> ok; unaligned_32_bit_verify([4294967295|T], N) when N > 0 -> unaligned_32_bit_verify(T, N-1). - + +%%% +%%% Common utilities. +%%% + id(I) -> I. diff --git a/erts/emulator/utils/beam_makeops b/erts/emulator/utils/beam_makeops index da850ed7cb..c7ea032026 100755 --- a/erts/emulator/utils/beam_makeops +++ b/erts/emulator/utils/beam_makeops @@ -1141,6 +1141,7 @@ sub emulator_output { print '#include "erl_map.h"', "\n"; print '#include "big.h"', "\n"; print '#include "erl_bits.h"', "\n"; + print '#include "erl_binary.h"', "\n"; print '#include "beam_transform_helpers.h"', "\n"; print '#include "erl_global_literals.h"', "\n"; print "\n"; diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl index bbbc844576..d959e21ea1 100644 --- a/lib/compiler/src/beam_asm.erl +++ b/lib/compiler/src/beam_asm.erl @@ -26,7 +26,7 @@ -export_type([fail/0,label/0,src/0,module_code/0,function_name/0]). --import(lists, [map/2,member/2,keymember/3,duplicate/2,splitwith/2]). +-import(lists, [append/1,duplicate/2,map/2,member/2,keymember/3,splitwith/2]). -include("beam_opcodes.hrl"). -include("beam_asm.hrl"). @@ -481,6 +481,13 @@ encode_arg({extfunc, M, F, A}, Dict0) -> encode_arg({list, List}, Dict0) -> {L, Dict} = encode_list(List, Dict0, []), {[encode(?tag_z, 1), encode(?tag_u, length(List))|L], Dict}; +encode_arg({commands, List0}, Dict) -> + List1 = [begin + [H|T] = tuple_to_list(Tuple), + [{atom,H}|T] + end || Tuple <- List0], + List = append(List1), + encode_arg({list, List}, Dict); encode_arg({float, Float}, Dict) when is_float(Float) -> encode_literal(Float, Dict); encode_arg({fr,Fr}, Dict) -> diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl index 0f3e7956f9..5f1ec19d59 100644 --- a/lib/compiler/src/beam_disasm.erl +++ b/lib/compiler/src/beam_disasm.erl @@ -417,6 +417,8 @@ disasm_instr(B, Bs, Atoms, Literals, Types) -> disasm_init_yregs(Bs, Atoms, Literals, Types); bs_create_bin -> disasm_bs_create_bin(Bs, Atoms, Literals, Types); + bs_match -> + disasm_bs_match(Bs, Atoms, Literals, Types); update_record -> disasm_update_record(Bs, Atoms, Literals, Types); _ -> @@ -495,6 +497,16 @@ disasm_bs_create_bin(Bs0, Atoms, Literals, Types) -> {List, RestBs} = decode_n_args(Len, Bs7, Atoms, Literals, Types), {{bs_create_bin, [{A1,A2,A3,A4,A5,Z,U,List}]}, RestBs}. +disasm_bs_match(Bs0, Atoms, Literals, Types) -> + {A1, Bs1} = decode_arg(Bs0, Atoms, Literals, Types), + {A2, Bs2} = decode_arg(Bs1, Atoms, Literals, Types), + Bs5 = Bs2, + {Z, Bs6} = decode_arg(Bs5, Atoms, Literals, Types), + {U, Bs7} = decode_arg(Bs6, Atoms, Literals, Types), + {u, Len} = U, + {List, RestBs} = decode_n_args(Len, Bs7, Atoms, Literals, Types), + {{bs_match, [{A1,A2,Z,U,List}]}, RestBs}. + disasm_update_record(Bs1, Atoms, Literals, Types) -> {Hint, Bs2} = decode_arg(Bs1, Atoms, Literals, Types), {Size, Bs3} = decode_arg(Bs2, Atoms, Literals, Types), @@ -1264,6 +1276,8 @@ resolve_inst({badrecord,[Arg]},_,_,_) -> resolve_inst({update_record,[Hint,Size,Src,Dst,List]},_,_,_) -> {update_record,Hint,Size,Src,Dst,List}; +resolve_inst({bs_match,[{Fail,Ctx,{z,1},{u,_},Args}]},_,_,_) -> + {bs_match,Fail,Ctx,{list,Args}}; %% %% Catches instructions that are not yet handled. diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl index 6c46060d4c..6d3a04b3b3 100644 --- a/lib/compiler/src/beam_jump.erl +++ b/lib/compiler/src/beam_jump.erl @@ -925,6 +925,8 @@ instr_labels({bs_start_match4,Fail,_,_,_}) -> {f,L} -> [L]; {atom,_} -> [] end; +instr_labels({bs_match,{f,Fail},_Ctx,_List}) -> + [Fail]; instr_labels(_) -> []. diff --git a/lib/compiler/src/beam_ssa.erl b/lib/compiler/src/beam_ssa.erl index 0648bc4028..c3775ba1e8 100644 --- a/lib/compiler/src/beam_ssa.erl +++ b/lib/compiler/src/beam_ssa.erl @@ -106,7 +106,7 @@ %% file erl_types.erl in the dialyzer application. -type prim_op() :: 'bs_create_bin' | - 'bs_extract' | 'bs_get_tail' | 'bs_init_writable' | + 'bs_extract' | 'bs_ensure' | 'bs_get_tail' | 'bs_init_writable' | 'bs_match' | 'bs_start_match' | 'bs_test_tail' | 'build_stacktrace' | 'call' | 'catch_end' | @@ -132,7 +132,8 @@ '+' | '-' | '*' | '/'. %% Primops only used internally during code generation. --type cg_prim_op() :: 'bs_get' | 'bs_get_position' | 'bs_match_string' | +-type cg_prim_op() :: 'bs_checked_get' | 'bs_checked_skip' | + 'bs_get' | 'bs_get_position' | 'bs_match_string' | 'bs_restore' | 'bs_save' | 'bs_set_position' | 'bs_skip' | 'copy' | 'match_fail' | 'put_tuple_arity' | 'set_tuple_element' | 'succeeded' | diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl index e89d8f0b57..d8efad4257 100644 --- a/lib/compiler/src/beam_ssa_codegen.erl +++ b/lib/compiler/src/beam_ssa_codegen.erl @@ -46,8 +46,9 @@ -spec module(beam_ssa:b_module(), [compile:option()]) -> {'ok',beam_asm:module_code()}. -module(#b_module{name=Mod,exports=Es,attributes=Attrs,body=Fs}, _Opts) -> - {Asm,St} = functions(Fs, {atom,Mod}), +module(#b_module{name=Mod,exports=Es,attributes=Attrs,body=Fs}, Opts) -> + NoBsMatch = member(no_bs_match, Opts), + {Asm,St} = functions(Fs, NoBsMatch, {atom,Mod}), {ok,{Mod,Es,Attrs,Asm,St#cg.lcount}}. -record(need, {h=0 :: non_neg_integer(), % heap words @@ -107,11 +108,11 @@ module(#b_module{name=Mod,exports=Es,attributes=Attrs,body=Fs}, _Opts) -> -type ssa_register() :: xreg() | yreg() | freg() | zreg(). -functions(Forms, AtomMod) -> - mapfoldl(fun (F, St) -> function(F, AtomMod, St) end, +functions(Forms, NoBsMatch, AtomMod) -> + mapfoldl(fun (F, St) -> function(F, NoBsMatch, AtomMod, St) end, #cg{lcount=1}, Forms). -function(#b_function{anno=Anno,bs=Blocks}, AtomMod, St0) -> +function(#b_function{anno=Anno,bs=Blocks}, NoBsMatch, AtomMod, St0) -> #{func_info:={_,Name,Arity}} = Anno, try assert_exception_block(Blocks), %Assertion. @@ -124,7 +125,7 @@ function(#b_function{anno=Anno,bs=Blocks}, AtomMod, St0) -> Labels = (St4#cg.labels)#{0=>Entry,?EXCEPTION_BLOCK=>0}, St5 = St4#cg{labels=Labels,used_labels=gb_sets:singleton(Entry), ultimate_fail=Ult}, - {Body,St} = cg_fun(Blocks, St5#cg{fc_label=Fi}), + {Body,St} = cg_fun(Blocks, NoBsMatch, St5#cg{fc_label=Fi}), Asm = [{label,Fi},line(Anno), {func_info,AtomMod,{atom,Name},Arity}] ++ add_parameter_annos(Body, Anno) ++ @@ -165,16 +166,20 @@ add_parameter_annos([{label, _}=Entry | Body], Anno) -> [Entry | sort(Annos)] ++ Body. -cg_fun(Blocks, St0) -> +cg_fun(Blocks, NoBsMatch, St0) -> Linear0 = linearize(Blocks), - St = collect_catch_labels(Linear0, St0), + St1 = collect_catch_labels(Linear0, St0), Linear1 = need_heap(Linear0), - Linear2 = prefer_xregs(Linear1, St), - Linear3 = liveness(Linear2, St), - Linear4 = defined(Linear3, St), - Linear5 = opt_allocate(Linear4, St), + Linear2 = prefer_xregs(Linear1, St1), + Linear3 = liveness(Linear2, St1), + Linear4 = defined(Linear3, St1), + Linear5 = opt_allocate(Linear4, St1), Linear = fix_wait_timeout(Linear5), - cg_linear(Linear, St). + {Asm,St} = cg_linear(Linear, St1), + case NoBsMatch of + true -> {Asm,St}; + false -> {bs_translate(Asm),St} + end. %% collect_catch_labels(Linear, St) -> St. %% Collect all catch labels (labels for blocks that begin @@ -368,6 +373,9 @@ classify_heap_need(Name, _Args) -> %% Note: Only handle operations in this function that are not handled %% by classify_heap_need/2. +classify_heap_need(bs_ensure) -> gc; +classify_heap_need(bs_checked_get) -> gc; +classify_heap_need(bs_checked_skip) -> gc; classify_heap_need(bs_get) -> gc; classify_heap_need(bs_get_tail) -> gc; classify_heap_need(bs_init_writable) -> gc; @@ -672,6 +680,7 @@ need_live_anno(Op) -> case Op of {bif,_} -> true; bs_create_bin -> true; + bs_checked_get -> true; bs_get -> true; bs_get_position -> true; bs_get_tail -> true; @@ -1136,6 +1145,13 @@ cg_block([#cg_set{op=bs_start_match, Live = get_live(I), Is = Pre ++ [{test,bs_start_match3,Fail,Live,[Bin],Dst}], {Is,St}; +cg_block([#cg_set{op=bs_ensure,args=Ss0}, + #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) -> + %% Temporary instruction that will be incorporated into a bs_match + %% instruction by the bs_translate sub pass. + [Ctx,{integer,Size},{integer,Unit}] = beam_args(Ss0, St), + Is = [{test,bs_ensure,Fail,[Ctx,Size,Unit]}], + {Is,St}; cg_block([#cg_set{op=bs_get}=Set, #cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) -> {cg_bs_get(Fail, Set, St),St}; @@ -1718,6 +1734,17 @@ cg_instr(bs_start_match, [{atom,new}, Src0], Dst, Set) -> {Src, Pre} = force_reg(Src0, Dst), Live = get_live(Set), Pre ++ [{bs_start_match4,{atom,no_fail},Live,Src,Dst}]; +cg_instr(bs_checked_get, [Kind,Ctx,{literal,Flags},{integer,Size},{integer,Unit}], Dst, Set) -> + %% Temporary instruction that will be incorporated into a bs_match + %% instruction by the bs_translate sub pass. + Live = get_live(Set), + [{bs_checked_get,Live,Kind,Ctx,field_flags(Flags, Set),Size,Unit,Dst}]; +cg_instr(bs_checked_get, [{atom,binary},Ctx,{literal,_Flags}, + {atom,all},{integer,Unit}], Dst, Set) -> + %% Temporary instruction that will be incorporated into a bs_match + %% instruction by the bs_translate sub pass. + Live = get_live(Set), + [{bs_checked_get_tail,Live,Ctx,Unit,Dst}]; cg_instr(bs_get_tail, [Src], Dst, Set) -> Live = get_live(Set), [{bs_get_tail,Src,Dst,Live}]; @@ -1738,6 +1765,13 @@ cg_instr(is_nonempty_list, Ss, Dst, Set) -> cg_instr(Op, Args, Dst, _Set) -> cg_instr(Op, Args, Dst). +cg_instr(bs_checked_skip, [_Type,Ctx,_Flags,{integer,Sz},{integer,U}], {z,_}) + when is_integer(Sz) -> + %% Temporary instruction that will be incorporated into a bs_match + %% instruction by the bs_translate sub pass. + [{bs_checked_skip,Ctx,Sz*U}]; +cg_instr(bs_checked_skip, [_Type,_Ctx,_Flags,{atom,all},{integer,_U}], {z,_}) -> + []; cg_instr(bs_init_writable, Args, Dst) -> setup_args(Args) ++ [bs_init_writable|copy({x,0}, Dst)]; cg_instr(bs_set_position, [Ctx,Pos], _Dst) -> @@ -2134,6 +2168,80 @@ break_up_cycle_1(Dst, [{move,S,D}|Path], Acc) -> break_up_cycle_1(Dst, Path, [{swap,S,D}|Acc]). %%% +%%% Collect and translate binary match instructions, producing a +%%% bs_match instruction. +%%% + +bs_translate([{bs_get_tail,_,_,_}=I|Is]) -> + %% A lone bs_get_tail. There is no advantage to incorporating it into + %% a bs_match instruction. + [I|bs_translate(Is)]; +bs_translate([I|Is0]) -> + case bs_translate_instr(I) of + none -> + [I|bs_translate(Is0)]; + {Ctx,Fail0,First} -> + {Instrs,Fail,Is} = bs_translate_collect(Is0, Ctx, Fail0, [First]), + [{bs_match,Fail,Ctx,{commands,Instrs}}|bs_translate(Is)] + end; +bs_translate([]) -> []. + +bs_translate_collect([I|Is]=Is0, Ctx, Fail, Acc) -> + case bs_translate_instr(I) of + {Ctx,Fail,Instr} -> + bs_translate_collect(Is, Ctx, Fail, [Instr|Acc]); + {Ctx,{f,0},Instr} -> + bs_translate_collect(Is, Ctx, Fail, [Instr|Acc]); + {_,_,_} -> + {bs_translate_fixup(Acc),Fail,Is0}; + none -> + {bs_translate_fixup(Acc),Fail,Is0} + end. + +bs_translate_fixup([{get_tail,_,_,_}=GT,{test_tail,Bits}|Is0]) -> + Is = reverse(Is0), + bs_translate_fixup_tail(Is, Bits) ++ [GT]; +bs_translate_fixup([{test_tail,Bits}|Is0]) -> + Is = reverse(Is0), + bs_translate_fixup_tail(Is, Bits); +bs_translate_fixup(Is) -> + reverse(Is). + +bs_translate_fixup_tail([{ensure_at_least,Bits0,_}|Is], Bits) -> + [{ensure_exactly,Bits0+Bits}|Is]; +bs_translate_fixup_tail([I|Is], Bits) -> + [I|bs_translate_fixup_tail(Is, Bits)]; +bs_translate_fixup_tail([], Bits) -> + [{ensure_exactly,Bits}]. + +bs_translate_instr({test,bs_ensure,Fail,[Ctx,Size,Unit]}) -> + {Ctx,Fail,{ensure_at_least,Size,Unit}}; +bs_translate_instr({bs_checked_get,Live,{atom,Type},Ctx,{field_flags,Flags0}, + Size,Unit,Dst}) -> + %% Only keep flags that have a meaning for binary matching and are + %% distinct from the default value. + Flags = [Flag || Flag <- Flags0, + case Flag of + little -> true; + native -> true; + big -> false; + signed -> true; + unsigned -> false; + {anno,_} -> false + end], + {Ctx,{f,0},{Type,Live,{literal,Flags},Size,Unit,Dst}}; +bs_translate_instr({bs_checked_skip,Ctx,Stride}) -> + {Ctx,{f,0},{skip,Stride}}; +bs_translate_instr({bs_checked_get_tail,Live,Ctx,Unit,Dst}) -> + {Ctx,{f,0},{get_tail,Live,Unit,Dst}}; +bs_translate_instr({bs_get_tail,Ctx,Dst,Live}) -> + {Ctx,{f,0},{get_tail,Live,1,Dst}}; +bs_translate_instr({test,bs_test_tail2,Fail,[Ctx,Bits]}) -> + {Ctx,Fail,{test_tail,Bits}}; +bs_translate_instr(_) -> none. + + +%%% %%% General utility functions. %%% diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index d77a4b2bdd..e746e821d2 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -289,6 +289,7 @@ epilogue_passes(Opts) -> ?PASS(ssa_opt_sink), ?PASS(ssa_opt_blockify), ?PASS(ssa_opt_redundant_br), + ?PASS(ssa_opt_bs_ensure), ?PASS(ssa_opt_merge_blocks), ?PASS(ssa_opt_get_tuple_element), ?PASS(ssa_opt_tail_literals), @@ -300,14 +301,16 @@ epilogue_passes(Opts) -> passes_1(Ps, Opts0) -> Negations = [{list_to_atom("no_"++atom_to_list(N)),N} || {N,_} <- Ps], - Opts = proplists:substitute_negations(Negations, Opts0), + Expansions = [{no_bs_match,[no_ssa_opt_bs_ensure,no_bs_match]}], + Opts = proplists:normalize(Opts0, [{expand,Expansions}, + {negations,Negations}]), [case proplists:get_value(Name, Opts, true) of true -> P; false -> {NoName,Name} = keyfind(Name, 2, Negations), {NoName,fun(S) -> S end} - end || {Name,_}=P <- Ps]. + end || {Name,_}=P <- Ps]. %% Builds a function information map with basic information about incoming and %% outgoing local calls, as well as whether the function is exported. @@ -2688,6 +2691,7 @@ unsuitable(Linear, Blocks, Predecessors) -> unsuitable_1([{L,#b_blk{is=[#b_set{op=Op}=I|_]}}|Bs]) -> Unsuitable = case Op of bs_extract -> true; + bs_match -> true; {float,_} -> true; landingpad -> true; _ -> beam_ssa:is_loop_header(I) @@ -3251,6 +3255,147 @@ redundant_br_safe_bool(Is, Bool) -> end. %%% +%%% Add the bs_ensure instruction before a sequence of `bs_match` +%%% (SSA) instructions, each having a literal size and the +%%% same failure label. +%%% +%%% This is the first part of building the `bs_match` (BEAM) +%%% instruction that can match multiple segments having the same +%%% failure label. +%%% + +ssa_opt_bs_ensure({#opt_st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) -> + RPO = beam_ssa:rpo(Blocks0), + Seen = sets:new([{version,2}]), + {Blocks,Count} = ssa_opt_bs_ensure(RPO, Seen, Count0, Blocks0), + {St#opt_st{ssa=Blocks,cnt=Count}, FuncDb}. + +ssa_opt_bs_ensure([L|Ls], Seen0, Count0, Blocks0) -> + case sets:is_element(L, Seen0) of + true -> + %% This block is already covered by a `bs_ensure` + %% instruction. + ssa_opt_bs_ensure(Ls, Seen0, Count0, Blocks0); + false -> + case is_bs_match_blk(L, Blocks0) of + no -> + ssa_opt_bs_ensure(Ls, Seen0, Count0, Blocks0); + {yes,Size0,#b_br{succ=Succ,fail=Fail}} -> + {Size,Blocks1,Seen} = + ssa_opt_bs_ensure_collect(Succ, Fail, + Blocks0, Seen0, Size0), + Blocks2 = annotate_match(L, Blocks1), + {Blocks,Count} = build_bs_ensure_match(L, Size, Count0, Blocks2), + ssa_opt_bs_ensure(Ls, Seen, Count, Blocks) + end + end; +ssa_opt_bs_ensure([], _Seen, Count, Blocks) -> + {Blocks,Count}. + +ssa_opt_bs_ensure_collect(L, Fail, Blocks0, Seen0, Acc) -> + case is_bs_match_blk(L, Blocks0) of + no -> + {Acc,Blocks0,Seen0}; + {yes,Size,#b_br{succ=Succ,fail=Fail}} -> + Seen = sets:add_element(L, Seen0), + Blocks = annotate_match(L, Blocks0), + ssa_opt_bs_ensure_collect(Succ, Fail, Blocks, Seen, update_size(Size, Acc)); + {yes,_,_} -> + {Acc,Blocks0,Seen0} + end. + +annotate_match(L, Blocks) -> + #b_blk{is=Is0} = Blk0 = map_get(L, Blocks), + Is = [case I of + #b_set{op=bs_match} -> + beam_ssa:add_anno(ensured, true, I); + #b_set{} -> + I + end || I <- Is0], + Blk = Blk0#b_blk{is=Is}, + Blocks#{L := Blk}. + +update_size({Size,Unit}, {Sum,Unit0}) -> + {Sum + Size,max(Unit, Unit0)}. + +is_bs_match_blk(L, Blocks) -> + Blk = map_get(L, Blocks), + case Blk of + #b_blk{is=Is,last=#b_br{bool=#b_var{}}=Last} -> + case is_bs_match_is(Is) of + no -> + no; + {yes,SizeUnit} -> + {yes,SizeUnit,Last} + end; + #b_blk{} -> + no + end. + +is_bs_match_is([#b_set{op=bs_match,dst=Dst}=I, + #b_set{op={succeeded,guard},args=[Dst]}]) -> + case is_viable_match(I) of + no -> + no; + {yes,{Size,_}=SizeUnit} when Size bsr 24 =:= 0 -> + %% Only include matches of reasonable size. + {yes,SizeUnit}; + {yes,_} -> + %% Too large size. + no + end; +is_bs_match_is([_|Is]) -> + is_bs_match_is(Is); +is_bs_match_is([]) -> no. + +is_viable_match(#b_set{op=bs_match,args=Args}) -> + case Args of + [#b_literal{val=binary},_,_,#b_literal{val=all},#b_literal{val=U}] + when is_integer(U), 1 =< U, U =< 256 -> + {yes,{0,U}}; + [#b_literal{val=binary},_,_,#b_literal{val=Size},#b_literal{val=U}] + when is_integer(Size) -> + {yes,{Size*U,1}}; + [#b_literal{val=integer},_,_,#b_literal{val=Size},#b_literal{val=U}] + when is_integer(Size) -> + {yes,{Size*U,1}}; + [#b_literal{val=skip},_,_,_,#b_literal{val=all},#b_literal{val=U}] -> + {yes,{0,U}}; + [#b_literal{val=skip},_,_,_,#b_literal{val=Size},#b_literal{val=U}] + when is_integer(Size) -> + {yes,{Size*U,1}}; + _ -> + no + end. + +build_bs_ensure_match(L, {Size,Unit}, Count0, Blocks0) -> + BsMatchL = Count0, + Count1 = Count0 + 1, + {NewCtx,Count2} = new_var('@context', Count1), + {SuccBool,Count} = new_var('@ssa_bool', Count2), + + BsMatchBlk0 = map_get(L, Blocks0), + + #b_blk{is=MatchIs,last=#b_br{fail=Fail}} = BsMatchBlk0, + {Prefix,Suffix0} = splitwith(fun(#b_set{op=Op}) -> Op =/= bs_match end, MatchIs), + [BsMatch0|Suffix1] = Suffix0, + #b_set{args=[Type,_Ctx|Args]} = BsMatch0, + BsMatch = BsMatch0#b_set{args=[Type,NewCtx|Args]}, + Suffix = [BsMatch|Suffix1], + BsMatchBlk = BsMatchBlk0#b_blk{is=Suffix}, + + #b_set{args=[_,Ctx|_]} = keyfind(bs_match, #b_set.op, MatchIs), + Is = Prefix ++ [#b_set{op=bs_ensure, + dst=NewCtx, + args=[Ctx,#b_literal{val=Size},#b_literal{val=Unit}]}, + #b_set{op={succeeded,guard},dst=SuccBool,args=[NewCtx]}], + Blk = #b_blk{is=Is,last=#b_br{bool=SuccBool,succ=BsMatchL,fail=Fail}}, + + Blocks = Blocks0#{L := Blk, BsMatchL => BsMatchBlk}, + + {Blocks,Count}. + +%%% %%% Common utilities. %%% diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl index c5b428759c..ea17f376e5 100644 --- a/lib/compiler/src/beam_ssa_pre_codegen.erl +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -203,20 +203,23 @@ assert_no_ces(_, _, Blocks) -> Blocks. fix_bs(#st{ssa=Blocks,cnt=Count0}=St) -> F = fun(#b_set{op=bs_start_match,dst=Dst}, A) -> %% Mark the root of the match context list. - [{Dst,{context,Dst}}|A]; + A#{Dst => {context,Dst}}; + (#b_set{op=bs_ensure,dst=Dst,args=[ParentCtx|_]}, A) -> + %% Link this match context to the previous match context. + A#{Dst => ParentCtx}; (#b_set{op=bs_match,dst=Dst,args=[_,ParentCtx|_]}, A) -> - %% Link this match context the previous match context. - [{Dst,ParentCtx}|A]; + %% Link this match context to the previous match context. + A#{Dst => ParentCtx}; (_, A) -> A end, RPO = beam_ssa:rpo(Blocks), - case beam_ssa:fold_instrs(F, RPO, [], Blocks) of - [] -> + CtxChain = beam_ssa:fold_instrs(F, RPO, #{}, Blocks), + case map_size(CtxChain) of + 0 -> %% No binary matching in this function. St; - [_|_]=M -> - CtxChain = maps:from_list(M), + _ -> Linear0 = beam_ssa:linearize(Blocks), %% Insert position instructions where needed. @@ -347,6 +350,33 @@ bs_restores_is([#b_set{op=bs_start_match,dst=Start}|Is], FPos = SPos0, SPos = SPos0#{Start=>Start}, bs_restores_is(Is, CtxChain, SPos, FPos, Rs); +bs_restores_is([#b_set{op=bs_ensure,dst=NewPos,args=Args}|Is], + CtxChain, SPos0, _FPos, Rs0) -> + Start = bs_subst_ctx(NewPos, CtxChain), + [FromPos|_] = Args, + case SPos0 of + #{Start := FromPos} -> + %% Same position, no restore needed. + SPos = SPos0#{Start := NewPos}, + FPos = SPos0, + bs_restores_is(Is, CtxChain, SPos, FPos, Rs0); + #{} -> + SPos = SPos0#{Start := NewPos}, + FPos = SPos0#{Start := FromPos}, + Rs = Rs0#{NewPos=>{Start,FromPos}}, + bs_restores_is(Is, CtxChain, SPos, FPos, Rs) + end; +bs_restores_is([#b_set{anno=#{ensured := _}, + op=bs_match,dst=NewPos,args=Args}|Is], + CtxChain, SPos0, _FPos, Rs) -> + %% This match instruction will be a part of a `bs_match` BEAM + %% instruction, so there will never be a restore to this + %% position. + Start = bs_subst_ctx(NewPos, CtxChain), + [_,FromPos|_] = Args, + SPos = SPos0#{Start := NewPos}, + FPos = SPos0#{Start := FromPos}, + bs_restores_is(Is, CtxChain, SPos, FPos, Rs); bs_restores_is([#b_set{op=bs_match,dst=NewPos,args=Args}=I|Is], CtxChain, SPos0, _FPos, Rs0) -> Start = bs_subst_ctx(NewPos, CtxChain), @@ -483,13 +513,13 @@ bs_restore_args([], Pos, _CtxChain, _Dst, Rs) -> %% Insert all bs_save and bs_restore instructions. bs_insert_bsm3(Blocks, Saves, Restores) -> - bs_insert_1(Blocks, [], Saves, Restores, fun(I) -> I end). + bs_insert_1(Blocks, [], Saves, Restores). -bs_insert_1([{L,#b_blk{is=Is0}=Blk} | Bs], Deferred0, Saves, Restores, XFrm) -> +bs_insert_1([{L,#b_blk{is=Is0}=Blk} | Bs], Deferred0, Saves, Restores) -> Is1 = bs_insert_deferred(Is0, Deferred0), - {Is, Deferred} = bs_insert_is(Is1, Saves, Restores, XFrm, []), - [{L,Blk#b_blk{is=Is}} | bs_insert_1(Bs, Deferred, Saves, Restores, XFrm)]; -bs_insert_1([], [], _, _, _) -> + {Is, Deferred} = bs_insert_is(Is1, Saves, Restores, []), + [{L,Blk#b_blk{is=Is}} | bs_insert_1(Bs, Deferred, Saves, Restores)]; +bs_insert_1([], [], _, _) -> []. bs_insert_deferred([#b_set{op=bs_extract}=I | Is], Deferred) -> @@ -497,8 +527,7 @@ bs_insert_deferred([#b_set{op=bs_extract}=I | Is], Deferred) -> bs_insert_deferred(Is, Deferred) -> Deferred ++ Is. -bs_insert_is([#b_set{dst=Dst}=I0|Is], Saves, Restores, XFrm, Acc0) -> - I = XFrm(I0), +bs_insert_is([#b_set{dst=Dst}=I|Is], Saves, Restores, Acc0) -> Pre = case Restores of #{Dst:=R} -> [R]; #{} -> [] @@ -513,9 +542,9 @@ bs_insert_is([#b_set{dst=Dst}=I0|Is], Saves, Restores, XFrm, Acc0) -> %% Defer the save sequence to the success block. {reverse(Acc, Is), Post}; _ -> - bs_insert_is(Is, Saves, Restores, XFrm, Post ++ Acc) + bs_insert_is(Is, Saves, Restores, Post ++ Acc) end; -bs_insert_is([], _, _, _, Acc) -> +bs_insert_is([], _, _, Acc) -> {reverse(Acc), []}. %% Translate bs_match instructions to bs_get, bs_match_string, @@ -534,7 +563,28 @@ bs_instrs([{L,#b_blk{is=Is0}=Blk}|Bs], CtxChain, Acc0) -> bs_instrs(Bs, CtxChain, [{L,Blk#b_blk{is=Is}}|Acc0]) end; bs_instrs([], _, Acc) -> - reverse(Acc). + bs_rewrite_skip(Acc). + +bs_rewrite_skip([{L,#b_blk{is=Is0,last=Last0}=Blk}|Bs]) -> + case bs_rewrite_skip_is(Is0, []) of + no -> + [{L,Blk}|bs_rewrite_skip(Bs)]; + {yes,Is} -> + #b_br{succ=Succ} = Last0, + Last = beam_ssa:normalize(Last0#b_br{fail=Succ}), + [{L,Blk#b_blk{is=Is,last=Last}}|bs_rewrite_skip(Bs)] + end; +bs_rewrite_skip([]) -> + []. + +bs_rewrite_skip_is([#b_set{anno=#{ensured := true},op=bs_skip}=I0, + #b_set{op={succeeded,guard}}], Acc) -> + I = I0#b_set{op=bs_checked_skip}, + {yes,reverse(Acc, [I])}; +bs_rewrite_skip_is([I|Is], Acc) -> + bs_rewrite_skip_is(Is, [I|Acc]); +bs_rewrite_skip_is([], _Acc) -> + no. bs_instrs_is([#b_set{op={succeeded,_}}=I|Is], CtxChain, Acc) -> %% This instruction refers to a specific operation, so we must not @@ -560,10 +610,19 @@ bs_instrs_is([], _, Acc) -> bs_combine(Dst, Ctx, [{L,#b_blk{is=Is0}=Blk}|Acc]) -> [#b_set{}=Succeeded, - #b_set{op=bs_match,args=[Type,_|As]}=BsMatch|Is1] = reverse(Is0), - Is = reverse(Is1, [BsMatch#b_set{op=bs_get,dst=Dst,args=[Type,Ctx|As]}, - Succeeded#b_set{args=[Dst]}]), - [{L,Blk#b_blk{is=Is}}|Acc]. + #b_set{anno=Anno,op=bs_match,args=[Type,_|As]}=BsMatch|Is1] = reverse(Is0), + if + is_map_key(ensured, Anno) -> + Is = reverse(Is1, [BsMatch#b_set{op=bs_checked_get,dst=Dst, + args=[Type,Ctx|As]}]), + #b_blk{last=#b_br{succ=Succ}=Br0} = Blk, + Br = beam_ssa:normalize(Br0#b_br{fail=Succ}), + [{L,Blk#b_blk{is=Is,last=Br}}|Acc]; + true -> + Is = reverse(Is1, [BsMatch#b_set{op=bs_get,dst=Dst,args=[Type,Ctx|As]}, + Succeeded#b_set{args=[Dst]}]), + [{L,Blk#b_blk{is=Is}}|Acc] + end. bs_subst_ctx(#b_var{}=Var, CtxChain) -> case CtxChain of @@ -2440,6 +2499,7 @@ reserve_zreg([#b_set{op=Op,dst=Dst} | Is], Last, ShortLived, A) -> end; reserve_zreg([], _, _, A) -> A. +use_zreg(bs_checked_skip) -> yes; use_zreg(bs_match_string) -> yes; use_zreg(bs_set_position) -> yes; use_zreg(kill_try_tag) -> yes; diff --git a/lib/compiler/src/beam_utils.erl b/lib/compiler/src/beam_utils.erl index 904e1f3e82..d407b6cb3e 100644 --- a/lib/compiler/src/beam_utils.erl +++ b/lib/compiler/src/beam_utils.erl @@ -115,6 +115,9 @@ replace_labels_1([{get_map_elements=I,{f,Lbl},Src,List}|Is], Acc, D, Fb) when Lb replace_labels_1([{bs_start_match4,{f,Lbl},Live,Src,Dst}|Is], Acc, D, Fb) -> I = {bs_start_match4,{f,label(Lbl, D, Fb)},Live,Src,Dst}, replace_labels_1(Is, [I | Acc], D, Fb); +replace_labels_1([{bs_match,{f,Lbl},Ctx,List}|Is], Acc, D, Fb) -> + I = {bs_match,{f,label(Lbl, D, Fb)},Ctx,List}, + replace_labels_1(Is, [I | Acc], D, Fb); replace_labels_1([I|Is], Acc, D, Fb) -> replace_labels_1(Is, [I|Acc], D, Fb); replace_labels_1([], Acc, _, _) -> Acc. diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 0323fd384c..d07943b45d 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -920,6 +920,20 @@ vi({put_map_exact=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) -> %% Bit syntax matching %% +vi({bs_match,{f,Fail},Ctx0,{commands,List}}, Vst) -> + Ctx = unpack_typed_arg(Ctx0), + + assert_no_exception(Fail), + assert_type(#t_bs_context{}, Ctx, Vst), + verify_y_init(Vst), + + branch(Fail, Vst, + fun(FailVst) -> + validate_failed_bs_match(List, Ctx, FailVst) + end, + fun(SuccVst) -> + validate_bs_match(List, Ctx, 1, SuccVst) + end); vi({bs_get_tail,Ctx,Dst,Live}, Vst0) -> assert_type(#t_bs_context{}, Ctx, Vst0), verify_live(Live, Vst0), @@ -978,25 +992,9 @@ vi({test,bs_get_binary2=Op,{f,Fail},Live,[Ctx,Size,Unit,_],Dst}, Vst) -> validate_bs_get(Op, Fail, Ctx, Live, Stride, Type, Dst, Vst); vi({test,bs_get_integer2=Op,{f,Fail},Live, [Ctx,{integer,Sz},Unit,{field_flags,Flags}],Dst},Vst) -> - NumBits = Unit * Sz, Stride = NumBits, - - Type = if - 0 =< NumBits, NumBits =< 64 -> - Max = (1 bsl NumBits) - 1, - case member(unsigned, Flags) of - true -> - beam_types:make_integer(0, Max); - false -> - Min = -(Max + 1), - beam_types:make_integer(Min, Max) - end; - true -> - %% Way too large or negative size. - #t_integer{} - end, - + Type = bs_integer_type(NumBits, Flags), validate_bs_get(Op, Fail, Ctx, Live, Stride, Type, Dst, Vst); vi({test,bs_get_integer2=Op,{f,Fail},Live,[Ctx,_Sz,Unit,_Flags],Dst},Vst) -> validate_bs_get(Op, Fail, Ctx, Live, Unit, #t_integer{}, Dst, Vst); @@ -1721,8 +1719,79 @@ validate_bs_start_match({f,Fail}, Live, Src, Dst, Vst) -> end). %% +%% Validate the bs_match instruction. +%% + +validate_bs_match([{get_tail,Live,_,Dst}], Ctx, _, Vst0) -> + validate_ctx_live(Ctx, Live), + verify_live(Live, Vst0), + Vst = prune_x_regs(Live, Vst0), + #t_bs_context{tail_unit=Unit} = get_concrete_type(Ctx, Vst0), + Type = #t_bitstring{size_unit=Unit}, + extract_term(Type, get_tail, [Ctx], Dst, Vst, Vst0); +validate_bs_match([I|Is], Ctx, Unit0, Vst0) -> + case I of + {ensure_at_least,_Size,Unit} -> + Type = #t_bs_context{tail_unit=Unit}, + Vst1 = update_bs_unit(Ctx, Unit, Vst0), + Vst = update_type(fun meet/2, Type, Ctx, Vst1), + validate_bs_match(Is, Ctx, Unit, Vst); + {ensure_exactly,Stride} -> + Vst = advance_bs_context(Ctx, Stride, Vst0), + validate_bs_match(Is, Ctx, Unit0, Vst); + {Type0,Live,{literal,Flags},Size,Unit,Dst} when Type0 =:= binary; + Type0 =:= integer -> + validate_ctx_live(Ctx, Live), + verify_live(Live, Vst0), + Vst1 = prune_x_regs(Live, Vst0), + Stride = Size * Unit, + Type = case Type0 of + integer -> + bs_integer_type(Stride, Flags); + binary -> + #t_bitstring{size_unit=bsm_size_unit({integer,Size}, Unit)} + end, + Vst = extract_term(Type, bs_match, [Ctx], Dst, Vst1, Vst0), + validate_bs_match(Is, Ctx, Unit0, Vst); + {skip,_Stride} -> + validate_bs_match(Is, Ctx, Unit0, Vst0) + end; +validate_bs_match([], _Ctx, _Unit, Vst) -> + Vst. + +validate_ctx_live({x,X}=Ctx, Live) when X >= Live -> + error({live_does_not_preserve_context,Live,Ctx}); +validate_ctx_live(_, _) -> + ok. + +validate_failed_bs_match([{ensure_at_least,_Size,Unit}|_], Ctx, Vst) -> + Type = #t_bs_context{tail_unit=Unit}, + update_type(fun subtract/2, Type, Ctx, Vst); +validate_failed_bs_match([_|Is], Ctx, Vst) -> + validate_failed_bs_match(Is, Ctx, Vst); +validate_failed_bs_match([], _Ctx, Vst) -> + Vst. + +bs_integer_type(NumBits, Flags) -> + if + 0 =< NumBits, NumBits =< 64 -> + Max = (1 bsl NumBits) - 1, + case member(signed, Flags) of + false -> + beam_types:make_integer(0, Max); + true -> + Min = -(Max + 1), + beam_types:make_integer(Min, Max) + end; + true -> + %% Way too large or negative size. + #t_integer{} + end. + +%% %% Common code for validating bs_get* instructions. %% + validate_bs_get(Op, Fail, Ctx0, Live, Stride, Type, Dst, Vst) -> Ctx = unpack_typed_arg(Ctx0), diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 771be26765..dc530d0e4a 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -277,7 +277,7 @@ expand_opt(r24, Os) -> expand_opt(no_type_opt, [no_bs_create_bin, no_ssa_opt_ranges | expand_opt(r25, Os)]); expand_opt(r25, Os) -> - [no_ssa_opt_update_tuple | Os]; + [no_ssa_opt_update_tuple, no_bs_match | Os]; expand_opt(no_make_fun3, Os) -> [no_make_fun3, no_fun_opt | Os]; expand_opt({debug_info_key,_}=O, Os) -> diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab index b972230e63..ef46f702a5 100755 --- a/lib/compiler/src/genop.tab +++ b/lib/compiler/src/genop.tab @@ -671,6 +671,8 @@ BEAM_FORMAT_NUMBER=0 ## @doc Raises a {badrecord,Value} error exception. 180: badrecord/1 +# OTP 26 + ## @spec update_record Hint Size Src Dst Updates=[Index, Value] ## @doc Sets Dst to a copy of Src with the update list applied. Hint can be ## one of: @@ -683,3 +685,15 @@ BEAM_FORMAT_NUMBER=0 ## Note that these are just hints and the implementation is free to ## ignore them. More hints may be added in the future. 181: update_record/5 + +## @spec bs_match Fail Ctx {commdands,Commands} +## @doc Match one or more binary segments of fixed size. Commands +## can be one of the following: +## +## * {ensure_at_least,Stride,Unit} +## * {ensure_exactly,Stride} +## * {binary,Live,Flags,Size,Unit,Dst} +## * {integer,Live,Flags,Size,Unit,Dst} +## * {skip,Stride} +## * {get_tail,Live,Unit,Dst} +182: bs_match/3 diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile index 8e145ae136..d184fe78a3 100644 --- a/lib/compiler/test/Makefile +++ b/lib/compiler/test/Makefile @@ -115,6 +115,12 @@ R24= \ bs_utf \ bs_bincomp +R25= \ + bs_construct \ + bs_match \ + bs_utf \ + bs_bincomp + DIALYZER = bs_match CORE_MODULES = \ @@ -139,6 +145,8 @@ R23_MODULES= $(R23:%=%_r23_SUITE) R23_ERL_FILES= $(R23_MODULES:%=%.erl) R24_MODULES= $(R24:%=%_r24_SUITE) R24_ERL_FILES= $(R24_MODULES:%=%.erl) +R25_MODULES= $(R25:%=%_r25_SUITE) +R25_ERL_FILES= $(R25_MODULES:%=%.erl) NO_MOD_OPT_MODULES= $(NO_MOD_OPT:%=%_no_module_opt_SUITE) NO_MOD_OPT_ERL_FILES= $(NO_MOD_OPT_MODULES:%=%.erl) NO_SSA_OPT_MODULES= $(NO_SSA_OPT:%=%_no_ssa_opt_SUITE) @@ -181,7 +189,7 @@ DISABLE_SSA_OPT = +no_bool_opt +no_share_opt +no_bsm_opt +no_fun_opt +no_ssa_opt make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES) \ $(NO_CORE_OPT_ERL_FILES) $(INLINE_ERL_FILES) $(R23_ERL_FILES) \ $(NO_MOD_OPT_ERL_FILES) $(NO_TYPE_OPT_ERL_FILES) \ - $(DIALYZER_ERL_FILES) $(R24_ERL_FILES) + $(DIALYZER_ERL_FILES) $(R24_ERL_FILES) $(R25_ERL_FILES) $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \ > $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +no_copt $(DISABLE_SSA_OPT) +no_postopt \ @@ -198,6 +206,8 @@ make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) $(NO_SSA_OPT_ERL_FILES -o$(EBIN) $(R23_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +r24 $(ERL_COMPILE_FLAGS) \ -o$(EBIN) $(R24_MODULES) >> $(EMAKEFILE) + $(ERL_TOP)/make/make_emakefile +r25 $(ERL_COMPILE_FLAGS) \ + -o$(EBIN) $(R25_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +no_module_opt $(ERL_COMPILE_FLAGS) \ -o$(EBIN) $(NO_MOD_OPT_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +from_core $(ERL_COMPILE_FLAGS) \ @@ -242,6 +252,9 @@ docs: %_r24_SUITE.erl: %_SUITE.erl sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ +%_r25_SUITE.erl: %_SUITE.erl + sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ + %_no_module_opt_SUITE.erl: %_SUITE.erl sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ @@ -266,6 +279,7 @@ release_tests_spec: make_emakefile $(INLINE_ERL_FILES) \ $(R23_ERL_FILES) \ $(R24_ERL_FILES) \ + $(R25_ERL_FILES) \ $(NO_CORE_OPT_ERL_FILES) \ $(NO_MOD_OPT_ERL_FILES) \ $(NO_SSA_OPT_ERL_FILES) \ diff --git a/lib/compiler/test/bs_bincomp_SUITE.erl b/lib/compiler/test/bs_bincomp_SUITE.erl index c3324b64dc..80041855a9 100644 --- a/lib/compiler/test/bs_bincomp_SUITE.erl +++ b/lib/compiler/test/bs_bincomp_SUITE.erl @@ -24,6 +24,7 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, + verify_highest_opcode/1, byte_aligned/1,bit_aligned/1,extended_byte_aligned/1, extended_bit_aligned/1,mixed/1,filters/1,trim_coverage/1, nomatch/1,sizes/1,general_expressions/1, @@ -33,13 +34,14 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. -all() -> - [byte_aligned, bit_aligned, extended_byte_aligned, +all() -> + [verify_highest_opcode, + byte_aligned, bit_aligned, extended_byte_aligned, extended_bit_aligned, mixed, filters, trim_coverage, nomatch, sizes, general_expressions, no_generator, zero_pattern, multiple_segments]. -groups() -> +groups() -> []. init_per_suite(Config) -> @@ -55,6 +57,28 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. +verify_highest_opcode(_Config) -> + case ?MODULE of + bs_construct_r24_SUITE -> + {ok,Beam} = file:read_file(code:which(?MODULE)), + case test_lib:highest_opcode(Beam) of + Highest when Highest =< 176 -> + ok; + TooHigh -> + ct:fail({too_high_opcode,TooHigh}) + end; + bs_construct_r25_SUITE -> + {ok,Beam} = file:read_file(code:which(?MODULE)), + case test_lib:highest_opcode(Beam) of + Highest when Highest =< 180 -> + ok; + TooHigh -> + ct:fail({too_high_opcode,TooHigh}) + end; + _ -> + ok + end. + byte_aligned(Config) when is_list(Config) -> cs_init(), <<"abcdefg">> = cs(<< <<(X+32)>> || <<X>> <= <<"ABCDEFG">> >>), diff --git a/lib/compiler/test/bs_construct_SUITE.erl b/lib/compiler/test/bs_construct_SUITE.erl index 201b411656..cecc3660d4 100644 --- a/lib/compiler/test/bs_construct_SUITE.erl +++ b/lib/compiler/test/bs_construct_SUITE.erl @@ -78,7 +78,15 @@ verify_highest_opcode(_Config) -> Highest when Highest =< 176 -> ok; TooHigh -> - ct:fail({too_high_opcode_for_21,TooHigh}) + ct:fail({too_high_opcode,TooHigh}) + end; + bs_construct_r25_SUITE -> + {ok,Beam} = file:read_file(code:which(?MODULE)), + case test_lib:highest_opcode(Beam) of + Highest when Highest =< 180 -> + ok; + TooHigh -> + ct:fail({too_high_opcode,TooHigh}) end; _ -> ok diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl index f5e33113ee..770b9e0afb 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -113,10 +113,10 @@ end_per_testcase(Case, Config) when is_atom(Case), is_list(Config) -> verify_highest_opcode(_Config) -> case ?MODULE of - bs_match_r21_SUITE -> + bs_match_r25_SUITE -> {ok,Beam} = file:read_file(code:which(?MODULE)), case test_lib:highest_opcode(Beam) of - Highest when Highest =< 163 -> + Highest when Highest =< 180 -> ok; TooHigh -> ct:fail({too_high_opcode_for_21,TooHigh}) @@ -2486,139 +2486,93 @@ id(I) -> I. expand_and_squeeze(Config) when is_list(Config) -> %% UTF8 literals are expanded and then squeezed into integer16 - [ - {test,bs_get_integer2,_,_,[_,{integer,16}|_],_} - | _ - ] = binary_match_to_asm([ - ?Q("<<$á/utf8,_/binary>>"), - ?Q("<<$é/utf8,_/binary>>") - ]), + ensure_squeezed(16, [?Q("<<$á/utf8,_/binary>>"), + ?Q("<<$é/utf8,_/binary>>")]), %% Sized integers are expanded and then squeezed into integer16 - [ - {test,bs_get_integer2,_,_,[_,{integer,16}|_],_} - | _ - ] = binary_match_to_asm([ - ?Q("<<0:32,_/binary>>"), - ?Q("<<\"bbbb\",_/binary>>") - ]), + ensure_squeezed(16, [?Q("<<0:32,_/binary>>"), + ?Q("<<\"bbbb\",_/binary>>")]), %% Groups of 8 bits are squeezed into integer16 - [ - {test,bs_get_integer2,_,_,[_,{integer,16}|_],_} - | _ - ] = binary_match_to_asm([ - ?Q("<<\"aaaa\",_/binary>>"), - ?Q("<<\"bbbb\",_/binary>>") - ]), + ensure_squeezed(16, [?Q("<<\"aaaa\",_/binary>>"), + ?Q("<<\"bbbb\",_/binary>>")]), %% Groups of 8 bits with empty binary are also squeezed - [ - {test,bs_get_integer2,_,_,[_,{integer,16}|_],_} - | _ - ] = binary_match_to_asm([ - ?Q("<<\"aaaa\",_/binary>>"), - ?Q("<<\"bbbb\",_/binary>>"), - ?Q("<<>>") - ]), + ensure_squeezed(16, [?Q("<<\"aaaa\",_/binary>>"), + ?Q("<<\"bbbb\",_/binary>>"), + ?Q("<<>>")]), %% Groups of 8 bits with float lookup are not squeezed - [ - {test,bs_get_integer2,_,_,[_,{integer,8}|_],_} - | _ - ] = binary_match_to_asm([ - ?Q("<<\"aaaa\",_/binary>>"), - ?Q("<<\"bbbb\",_/binary>>"), - ?Q("<<_/float>>") - ]), + ensure_squeezed(8, [?Q("<<\"aaaa\",_/binary>>"), + ?Q("<<\"bbbb\",_/binary>>"), + ?Q("<<_/float>>")]), %% Groups of diverse bits go with minimum possible - [ - {test,bs_get_integer2,_,_,[_,{integer,8}|_],_} - | _ - ] = binary_match_to_asm([ - ?Q("<<\"aa\",_/binary>>"), - ?Q("<<\"bb\",_/binary>>"), - ?Q("<<\"c\",_/binary>>") - ]), + ensure_squeezed(8, [?Q("<<\"aa\",_/binary>>"), + ?Q("<<\"bb\",_/binary>>"), + ?Q("<<\"c\",_/binary>>")]), %% Groups of diverse bits go with minimum possible but are recursive... - [ - {test,bs_get_integer2,_,_,[_,{integer,8}|_],_} - | RestDiverse - ] = binary_match_to_asm([ - ?Q("<<\"aaa\",_/binary>>"), - ?Q("<<\"abb\",_/binary>>"), - ?Q("<<\"c\",_/binary>>") - ]), - - %% so we still perform a 16 bits lookup for the remaining - true = lists:any(fun({test,bs_get_integer2,_,_,[_,{integer,16}|_],_}) -> true; - (_) -> false end, RestDiverse), + [{bs_match,{f,_},_Ctx, + {commands,[{ensure_at_least,Size,1}, + {integer,_Live,_Flags,Size,1,_Dst}]}} | RestDiverse] = + binary_match_to_asm([?Q("<<\"aaa\",_/binary>>"), + ?Q("<<\"abb\",_/binary>>"), + ?Q("<<\"c\",_/binary>>")]), + + %% ... so we still perform a 16 bits lookup for the remaining + F = fun({bs_match,{f,_},_, + {commands,[{ensure_at_least,16,1}, + {integer,_Live,_Flags,16,1,_Dst}]}}) -> + true; + (_) -> false + end, + true = lists:any(F, RestDiverse), %% Large match is kept as is if there is a sized match later - [ - {test,bs_get_integer2,_,_,[_,{integer,64}|_],_} - | _ - ] = binary_match_to_asm([ - ?Q("<<255,255,255,255,255,255,255,255>>"), - ?Q("<<_:64>>") - ]), + ensure_squeezed(64, [?Q("<<255,255,255,255,255,255,255,255>>"), + ?Q("<<_:64>>")]), %% Large match is kept as is with large matches before and after - [ - {test,bs_get_integer2,_,_,[_,{integer,32}|_],_} - | _ - ] = binary_match_to_asm([ - ?Q("<<A:32,_:A>>"), - ?Q("<<0:32>>"), - ?Q("<<_:32>>") - ]), + ensure_squeezed(32, [?Q("<<A:32,_:A>>"), + ?Q("<<0:32>>"), + ?Q("<<_:32>>")]), %% Large match is kept as is with large matches before and after - [ - {test,bs_get_integer2,_,_,[_,{integer,32}|_],_} - | _ - ] = binary_match_to_asm([ - ?Q("<<A:32,_:A>>"), - ?Q("<<0,0,0,0>>"), - ?Q("<<_:32>>") - ]), + ensure_squeezed(32, [?Q("<<A:32,_:A>>"), + ?Q("<<0,0,0,0>>"), + ?Q("<<_:32>>")]), %% Large match is kept as is with smaller but still large matches before and after - [ - {test,bs_get_integer2,_,_,[_,{integer,32}|_],_} - | _ - ] = binary_match_to_asm([ - ?Q("<<A:32, _:A>>"), - ?Q("<<0:64>>"), - ?Q("<<_:32>>") - ]), + ensure_squeezed(32, [?Q("<<A:32, _:A>>"), + ?Q("<<0:64>>"), + ?Q("<<_:32>>")]), %% There is no squeezing for groups with more than 16 matches - [ - {test,bs_get_integer2,_,_,[_,{integer,8}|_],_} - | _ - ] = binary_match_to_asm([ - ?Q("<<\"aa\", _/binary>>"), - ?Q("<<\"bb\", _/binary>>"), - ?Q("<<\"cc\", _/binary>>"), - ?Q("<<\"dd\", _/binary>>"), - ?Q("<<\"ee\", _/binary>>"), - ?Q("<<\"ff\", _/binary>>"), - ?Q("<<\"gg\", _/binary>>"), - ?Q("<<\"hh\", _/binary>>"), - ?Q("<<\"ii\", _/binary>>"), - ?Q("<<\"jj\", _/binary>>"), - ?Q("<<\"kk\", _/binary>>"), - ?Q("<<\"ll\", _/binary>>"), - ?Q("<<\"mm\", _/binary>>"), - ?Q("<<\"nn\", _/binary>>"), - ?Q("<<\"oo\", _/binary>>"), - ?Q("<<\"pp\", _/binary>>") - ]), - - ok. + ensure_squeezed(8, [?Q("<<\"aa\", _/binary>>"), + ?Q("<<\"bb\", _/binary>>"), + ?Q("<<\"cc\", _/binary>>"), + ?Q("<<\"dd\", _/binary>>"), + ?Q("<<\"ee\", _/binary>>"), + ?Q("<<\"ff\", _/binary>>"), + ?Q("<<\"gg\", _/binary>>"), + ?Q("<<\"hh\", _/binary>>"), + ?Q("<<\"ii\", _/binary>>"), + ?Q("<<\"jj\", _/binary>>"), + ?Q("<<\"kk\", _/binary>>"), + ?Q("<<\"ll\", _/binary>>"), + ?Q("<<\"mm\", _/binary>>"), + ?Q("<<\"nn\", _/binary>>"), + ?Q("<<\"oo\", _/binary>>"), + ?Q("<<\"pp\", _/binary>>")]), + + ok. + +ensure_squeezed(ExpectedSize, Fields) -> + [{bs_match,{f,_},_, + {commands,[{ensure_at_least,ExpectedSize,1}, + {integer,_Live,_Flags,ExpectedSize,1,_Dst}]}} | _] = + binary_match_to_asm(Fields). binary_match_to_asm(Matches) -> Clauses = [ diff --git a/lib/compiler/test/bs_utf_SUITE.erl b/lib/compiler/test/bs_utf_SUITE.erl index 10954dd833..427499b995 100644 --- a/lib/compiler/test/bs_utf_SUITE.erl +++ b/lib/compiler/test/bs_utf_SUITE.erl @@ -20,8 +20,9 @@ -module(bs_utf_SUITE). --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, + verify_highest_opcode/1, utf8_roundtrip/1,unused_utf_char/1,utf16_roundtrip/1, utf32_roundtrip/1,guard/1,extreme_tripping/1, literals/1,coverage/1]). @@ -30,12 +31,13 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. -all() -> - [utf8_roundtrip, unused_utf_char, utf16_roundtrip, +all() -> + [verify_highest_opcode, + utf8_roundtrip, unused_utf_char, utf16_roundtrip, utf32_roundtrip, guard, extreme_tripping, literals, coverage]. -groups() -> +groups() -> []. init_per_suite(Config) -> @@ -51,6 +53,27 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. +verify_highest_opcode(_Config) -> + case ?MODULE of + bs_construct_r24_SUITE -> + {ok,Beam} = file:read_file(code:which(?MODULE)), + case test_lib:highest_opcode(Beam) of + Highest when Highest =< 176 -> + ok; + TooHigh -> + ct:fail({too_high_opcode,TooHigh}) + end; + bs_construct_r25_SUITE -> + {ok,Beam} = file:read_file(code:which(?MODULE)), + case test_lib:highest_opcode(Beam) of + Highest when Highest =< 180 -> + ok; + TooHigh -> + ct:fail({too_high_opcode,TooHigh}) + end; + _ -> + ok + end. utf8_roundtrip(Config) when is_list(Config) -> [utf8_roundtrip_1(P) || P <- utf_data()], diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index b2b3f65d6a..2c55f61b54 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -1691,7 +1691,8 @@ bc_options(Config) -> {168, small, [r22]}, {168, small, [no_init_yregs,no_shared_fun_wrappers, no_ssa_opt_record,no_make_fun3, - no_ssa_opt_float,no_line_info,no_type_opt]}, + no_ssa_opt_float,no_line_info,no_type_opt, + no_bs_match]}, {169, small, [r23]}, {169, big, [no_init_yregs,no_shared_fun_wrappers, diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl index 7182dd6555..fb5bbb5edc 100644 --- a/lib/compiler/test/test_lib.erl +++ b/lib/compiler/test/test_lib.erl @@ -92,6 +92,7 @@ opt_opts(Mod) -> (inline) -> true; (no_bs_create_bin) -> true; (no_bsm_opt) -> true; + (no_bs_match) -> true; (no_copt) -> true; (no_fun_opt) -> true; (no_init_yregs) -> true; |