From 4510ef190d337da0daa1b91423405352af455432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?= Date: Fri, 24 Sep 2021 07:36:43 +0200 Subject: Implement the bs_create_bin instruction Implement the bs_create_bin instruction for the JITs and the emulator. Consider this example module: -module(foo). -export([bin/2, int/2, float/2]). bin(A, B) -> <>. int(A, B) -> <>. float(A, B) -> <>. var_int(A, Size) -> <>. With the extended error information introduced in this commit, failures to build binaries will be reported like this: 1> c(foo). {ok,foo} 2> foo:bin(2, <<>>). ** exception error: bad argument in function foo:bin/2 (foo.erl, line 4) *** failed constructing binary: segment 1, type 'binary': not a binary 3> foo:bin(<<>>, 42). ** exception error: bad argument in function foo:bin/2 (foo.erl, line 4) *** failed constructing binary: segment 2, type 'binary': not a binary 4> foo:bin(<<>>, <<1:1>>). ** exception error: bad argument in function foo:bin/2 (foo.erl, line 4) *** failed constructing binary: segment 2, type 'binary': the size of the given binary/bitstring is not a multiple of the unit for the segment 5> foo:int(a, 42). ** exception error: bad argument in function foo:int/2 (foo.erl, line 5) *** failed constructing binary: segment 1, type 'integer': not an integer 6> foo:float(<<>>, <<>>). ** exception error: bad argument in function foo:float/2 (foo.erl, line 6) *** failed constructing binary: segment 1, type 'float': not a float or an integer 7> foo:var_int(42, -1). ** exception error: bad argument in function foo:var_int/2 (foo.erl, line 6) *** failed constructing binary: segment 1, type 'integer': the size is negative 8> foo:var_int(42, a). ** exception error: bad argument in function foo:var_int/2 (foo.erl, line 6) *** failed constructing binary: segment 1, type 'integer': the size is not an integer 9> foo:var_int(42, 1 bsl 64). ** exception error: a system limit has been reached in function foo:var_int/2 (foo.erl, line 6) *** failed constructing binary: segment 1, type 'integer': the size is too large Closes #4971. --- erts/emulator/beam/atom.names | 8 +- erts/emulator/beam/beam_common.c | 5 +- erts/emulator/beam/beam_debug.c | 49 +++ erts/emulator/beam/emu/beam_emu.c | 54 +++ erts/emulator/beam/emu/bs_instrs.tab | 434 +++++++++++++++++++++ erts/emulator/beam/emu/emu_load.c | 15 + erts/emulator/beam/emu/generators.tab | 160 ++++++++ erts/emulator/beam/emu/ops.tab | 16 +- erts/emulator/beam/erl_bits.c | 82 +++- erts/emulator/beam/erl_bits.h | 25 +- erts/emulator/beam/global.h | 1 + erts/emulator/beam/jit/arm/beam_asm.hpp | 2 + erts/emulator/beam/jit/arm/generators.tab | 56 +++ erts/emulator/beam/jit/arm/instr_bs.cpp | 590 +++++++++++++++++++++++++++++ erts/emulator/beam/jit/arm/ops.tab | 16 +- erts/emulator/beam/jit/beam_jit_common.cpp | 111 +++++- erts/emulator/beam/jit/beam_jit_common.hpp | 32 ++ erts/emulator/beam/jit/x86/beam_asm.hpp | 1 + erts/emulator/beam/jit/x86/generators.tab | 56 +++ erts/emulator/beam/jit/x86/instr_bs.cpp | 534 ++++++++++++++++++++++++++ erts/emulator/beam/jit/x86/ops.tab | 16 +- erts/emulator/test/bs_construct_SUITE.erl | 115 +++++- erts/emulator/test/exception_SUITE.erl | 11 +- erts/emulator/utils/beam_makeops | 1 + lib/compiler/src/beam_asm.erl | 11 +- lib/compiler/src/beam_clean.erl | 4 +- lib/compiler/src/beam_disasm.erl | 21 + lib/compiler/src/genop.tab | 6 + lib/kernel/src/erl_erts_errors.erl | 54 ++- 29 files changed, 2425 insertions(+), 61 deletions(-) diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index 55b662abc9..49b9fd0e9f 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -297,9 +297,11 @@ atom flags atom flush atom flush_monitor_messages atom force +atom format_bs_fail atom format_cpu_topology atom free atom fullsweep_after +atom function atom functions atom function_clause atom garbage_collect @@ -358,6 +360,7 @@ atom inherit atom init atom initial_call atom input +atom integer atom internal atom internal_error atom instruction_counts @@ -451,6 +454,7 @@ atom namelist atom native atom native_addresses atom need_gc +atom negative_size atom Neq='=/=' atom Neqeq='/=' atom net_kernel @@ -546,6 +550,7 @@ atom prepare_on_load atom print atom priority atom private +atom private_append atom process atom processes atom processes_used @@ -711,10 +716,11 @@ atom unregister atom urun atom use_stdio atom used -atom utf8 +atom utf8 utf16 utf32 atom unblock atom unblock_normal atom uniq +atom unit atom unless_suspending atom unloaded atom unloaded_only diff --git a/erts/emulator/beam/beam_common.c b/erts/emulator/beam/beam_common.c index 0ef3922301..49f55b1378 100644 --- a/erts/emulator/beam/beam_common.c +++ b/erts/emulator/beam/beam_common.c @@ -996,10 +996,13 @@ save_stacktrace(Process* c_p, ErtsCodePtr pc, Eterm* reg, args = make_arglist(c_p, reg, bif_mfa->arity); } else { + if (c_p->freason & EXF_HAS_EXT_INFO && is_map(c_p->fvalue)) { + error_info = c_p->fvalue; + } non_bif_stacktrace: - s->current = c_p->current; + /* * For a function_clause error, the arguments are in the beam * registers and c_p->current is set. diff --git a/erts/emulator/beam/beam_debug.c b/erts/emulator/beam/beam_debug.c index e995ca28db..0f8ae9fdea 100644 --- a/erts/emulator/beam/beam_debug.c +++ b/erts/emulator/beam/beam_debug.c @@ -898,6 +898,55 @@ print_op(fmtfn_t to, void *to_arg, int op, int size, BeamInstr* addr) } } break; + case op_i_bs_create_bin_jItdW: + { + int n = unpacked[-1]; + int i = 0; + Eterm type = 0; + + while (n > 0) { + switch (i % 5) { + case 0: /* Type */ + type = ap[i]; + erts_print(to, to_arg, " `%d`", type); + break; + case 1: /* Unit */ + case 2: /* Flags */ + erts_print(to, to_arg, " `%d`", (Eterm) ap[i]); + break; + case 4: /* Size */ + if (type == BSC_BINARY_FIXED_SIZE || + type == BSC_FLOAT_FIXED_SIZE || + type == BSC_INTEGER_FIXED_SIZE || + type == BSC_STRING || + type == BSC_UTF32) { + erts_print(to, to_arg, " `%d`", ap[i]); + break; + } + + /*FALLTHROUGH*/ + case 3: /* Src */ + if (type == BSC_STRING) { + erts_print(to, to_arg, " "); + print_byte_string(to, to_arg, (byte *) ap[i], ap[i+1]); + break; + } + switch (loader_tag(ap[i])) { + case LOADER_X_REG: + erts_print(to, to_arg, " x(%d)", loader_x_reg_index(ap[i])); + break; + case LOADER_Y_REG: + erts_print(to, to_arg, " y(%d)", loader_y_reg_index(ap[i]) - CP_SIZE); + break; + default: + erts_print(to, to_arg, " `%T`", (Eterm) ap[i]); + break; + } + } + i++, size++, n--; + } + } + break; } erts_print(to, to_arg, "\n"); diff --git a/erts/emulator/beam/emu/beam_emu.c b/erts/emulator/beam/emu/beam_emu.c index b16e23c61e..ec71dd2088 100644 --- a/erts/emulator/beam/emu/beam_emu.c +++ b/erts/emulator/beam/emu/beam_emu.c @@ -713,3 +713,57 @@ erts_beam_jump_table(void) return 1; #endif } + +void +erts_prepare_bs_construct_fail_info(Process* c_p, const BeamInstr* p, Eterm reason, Eterm Info) +{ + Eterm* hp = HeapFragOnlyAlloc(c_p, MAP3_SZ+3+1); + Eterm cause_tuple; + Eterm op; + Eterm error_info; + Uint segment; + + segment = p[2] >> 3; + + switch (p[0]) { + case BSC_APPEND: + case BSC_PRIVATE_APPEND: + case BSC_BINARY: + case BSC_BINARY_FIXED_SIZE: + case BSC_BINARY_ALL: + op = am_binary; + break; + case BSC_FLOAT: + case BSC_FLOAT_FIXED_SIZE: + op = am_float; + break; + case BSC_INTEGER: + case BSC_INTEGER_FIXED_SIZE: + op = am_integer; + break; + case BSC_STRING: + op = am_string; + break; + case BSC_UTF8: + op = am_utf8; + break; + case BSC_UTF16: + op = am_utf16; + break; + case BSC_UTF32: + op = am_utf32; + break; + default: + op = am_none; + break; + } + + cause_tuple = TUPLE3(hp, make_small(segment), op, Info); + hp += 4; + error_info = MAP3(hp, + am_cause, cause_tuple, + am_function, am_format_bs_fail, + am_module, am_erl_erts_errors); + c_p->fvalue = error_info; + c_p->freason = reason | EXF_HAS_EXT_INFO; +} diff --git a/erts/emulator/beam/emu/bs_instrs.tab b/erts/emulator/beam/emu/bs_instrs.tab index a5e6bb46de..e143331e51 100644 --- a/erts/emulator/beam/emu/bs_instrs.tab +++ b/erts/emulator/beam/emu/bs_instrs.tab @@ -835,6 +835,440 @@ i_bs_validate_unicode_retract(Fail, Src, Ms) { } } +BS_GET_TERM(Term, Dst) { + $Dst = $Term; + switch (loader_tag($Dst)) { + case LOADER_X_REG: + $Dst = x(loader_x_reg_index($Dst)); + break; + case LOADER_Y_REG: + $Dst = y(loader_y_reg_index($Dst)); + break; + } +} + +BS_LOAD_UNIT(Ptr, Dst) { + $Dst = $Ptr[1]; +} + +BS_LOAD_FLAGS(Ptr, Dst) { + $Dst = $Ptr[2]; +} + +BS_LOAD_SRC(Ptr, Dst) { + $BS_GET_TERM($Ptr[3], $Dst); +} + +BS_LOAD_STRING_SRC(Ptr, Dst) { + $Dst = (byte *) $Ptr[3]; +} + +BS_LOAD_SIZE(Ptr, Dst) { + $BS_GET_TERM($Ptr[4], $Dst); +} + +BS_LOAD_FIXED_SIZE(Ptr, Dst) { + $Dst = $Ptr[4]; +} + +BS_FAIL_INFO(Fail, Reason, ErrorType) { + erts_prepare_bs_construct_fail_info(c_p, p, $Reason, $ErrorType); + $FAIL_HEAD_OR_BODY($Fail); +} + +BS_FAIL_INFO_SYSTEM_LIMIT(Fail) { + $BS_FAIL_INFO($Fail, SYSTEM_LIMIT, am_size); +} + +i_bs_create_bin(Fail, Alloc, Live, Dst, N) { + //| -no_prefetch + int n = $N; + const BeamInstr* p_start = $NEXT_INSTRUCTION; + const BeamInstr* p_end = p_start + n; + const BeamInstr* p; + Uint num_bytes; + Uint alloc = $Alloc; + Eterm new_binary; + + /* We count the total number of bits in an unsigned integer. To avoid + * having to check for overflow when adding to `num_bits`, we ensure that the + * signed size of each segment fits in a word. */ + Uint num_bits = 0; + + /* Calculate size of binary in bits. */ + for (p = p_start; p < p_end; p += BSC_NUM_ARGS) { + Eterm Src; + Eterm Size; + Uint unit; + Uint fixed_size; + + switch (p[0]) { + case BSC_APPEND: + case BSC_PRIVATE_APPEND: + break; + case BSC_BINARY_ALL: + { + Uint low_bits; + Uint byte_size; + Uint high_bits; + + $BS_LOAD_SRC(p, Src); + if (is_not_binary(Src)) { + $BS_FAIL_INFO($Fail, BADARG, am_type); + } + byte_size = binary_size(Src); + high_bits = byte_size >> ((sizeof(Uint) * 8)-3); + low_bits = (byte_size << 3) + binary_bitsize(Src); + if (high_bits == 0) { + num_bits += low_bits; + } else { + $BS_FAIL_INFO($Fail, SYSTEM_LIMIT, am_size); + } + } + break; + case BSC_BINARY_FIXED_SIZE: + case BSC_FLOAT_FIXED_SIZE: + case BSC_INTEGER_FIXED_SIZE: + $BS_LOAD_FIXED_SIZE(p, fixed_size); + num_bits += fixed_size; + break; + case BSC_STRING: + $BS_LOAD_FIXED_SIZE(p, fixed_size); + num_bits += fixed_size * 8; + break; + case BSC_BINARY: + case BSC_FLOAT: + case BSC_INTEGER: + $BS_LOAD_UNIT(p, unit); + $BS_LOAD_SIZE(p, Size); + if (is_small(Size)) { + Sint signed_size = signed_val(Size); + Uint size; + if (signed_size >= 0) { + $BS_SAFE_MUL(signed_size, unit, $BS_FAIL_INFO_SYSTEM_LIMIT($Fail), size); + if (size >> (sizeof(Uint) * 8 - 1) != 0) { + /* The signed size does not fit in a word. */ + $BS_FAIL_INFO($Fail, SYSTEM_LIMIT, am_size); + } + num_bits += size; + } else { + $BS_FAIL_INFO($Fail, BADARG, am_negative_size); + } + } else { +#ifdef ARCH_64 + /* The size must fit in a small on 64-bit platforms. */ + if (is_big(Size)) { + if (!big_sign(Size)) { + $BS_FAIL_INFO($Fail, SYSTEM_LIMIT, am_size); + } else { + $BS_FAIL_INFO($Fail, BADARG, am_negative_size); + } + } else { + /* Not an integer. */ + $BS_FAIL_INFO($Fail, BADARG, am_size); + } +#else + Uint size; + + if (!term_to_Uint(Size, &size)) { + if (size == BADARG) { + /* Not an integer or a negative integer. Determine which. */ + if (is_big(Size)) { + /* Negative integer. */ + $BS_FAIL_INFO($Fail, BADARG, am_negative_size); + } + /* Not an integer. */ + $BS_FAIL_INFO($Fail, BADARG, am_size); + } + /* Huge positive integer. */ + $BS_FAIL_INFO_SYSTEM_LIMIT($Fail); + } + $BS_SAFE_MUL(size, unit, $BS_FAIL_INFO_SYSTEM_LIMIT($Fail), size); + if ((size >> 31) != 0) { + $BS_FAIL_INFO_SYSTEM_LIMIT($Fail); + } else { + num_bits += size; + } +#endif + } + break; + case BSC_UTF8: + { + int num_bytes; + + /* + * Calculate the number of bits needed to encode the + * source operand to UTF-8. If the source operand is + * invalid (e.g. wrong type or range) we return a + * nonsense integer result (32). We can get away + * with that because we KNOW that full error checking + * will be done in the construction phase. + */ + + $BS_LOAD_SRC(p, Src); + if (Src < make_small(0x80UL)) { + num_bytes = 1; + } else if (Src < make_small(0x800UL)) { + num_bytes = 2; + } else if (Src < make_small(0x10000UL)) { + num_bytes = 3; + } else { + num_bytes = 4; + } + num_bits += num_bytes * 8; + } + break; + case BSC_UTF16: + { + int num_bytes = 2; + + /* + * Calculate the number of bits needed to encode the + * source operarand to UTF-16. If the source operand + * is invalid (e.g. wrong type or range) we return a + * nonsense integer result (16 or 32). We can get away + * with that because we KNOW that full error checking + * will be done in the construction phase. + */ + + $BS_LOAD_SRC(p, Src); + if (Src >= make_small(0x10000UL)) { + num_bytes = 4; + } + num_bits += num_bytes * 8; + } + break; + case BSC_UTF32: + $BS_LOAD_SRC(p, Src); + + /* + * There is no need to untag the integer, but it IS + * necessary to make sure it is small (if the term is a + * bignum, it could slip through the test, and there is no + * further test that would catch it, since bit syntax + * construction silently masks too big numbers). + */ + if (is_not_small(Src) || Src > make_small(0x10FFFFUL) || + (make_small(0xD800UL) <= Src && Src <= make_small(0xDFFFUL))) { + $BS_FAIL_INFO($Fail, BADARG, am_type); + } + num_bits += 4 * 8; + break; + default: + ASSERT(0); + break; + } + } + + c_p->fcalls = FCALLS; + + /* Allocate binary. */ + p = p_start; + if (p[0] == BSC_APPEND) { + Uint live = $Live; + Uint unit; + Eterm Src; + + $BS_LOAD_UNIT(p, unit); + $BS_LOAD_SRC(p, Src); + HEAVY_SWAPOUT; + reg[live] = Src; + new_binary = erts_bs_append_checked(c_p, reg, live, num_bits, alloc, unit); + HEAVY_SWAPIN; + if (is_non_value(new_binary)) { + $BS_FAIL_INFO($Fail, c_p->freason, c_p->fvalue); + } + p_start += BSC_NUM_ARGS; + } else if (p[0] == BSC_PRIVATE_APPEND) { + Uint unit; + Eterm Src; + + if (alloc) { + $test_heap(alloc, $Live); + } + + $BS_LOAD_UNIT(p, unit); + $BS_LOAD_SRC(p, Src); + new_binary = erts_bs_private_append_checked(c_p, Src, num_bits, unit); + if (is_non_value(new_binary)) { + $BS_FAIL_INFO($Fail, c_p->freason, c_p->fvalue); + } + p_start += BSC_NUM_ARGS; + } else { + num_bytes = ((Uint64)num_bits+(Uint64)7) >> 3; + if (num_bits & 7) { + alloc += ERL_SUB_BIN_SIZE; + } + if (num_bytes <= ERL_ONHEAP_BIN_LIMIT) { + alloc += heap_bin_size(num_bytes); + } else { + alloc += PROC_BIN_SIZE; + } + + /* num_bits = Number of bits to build + * num_bytes = Number of bytes to allocate in the binary + * alloc = Total number of words to allocate on heap + */ + erts_bin_offset = 0; + erts_writable_bin = 0; + if (num_bytes <= ERL_ONHEAP_BIN_LIMIT) { + ErlHeapBin* hb; + + $test_heap(alloc, $Live); + hb = (ErlHeapBin *) HTOP; + HTOP += heap_bin_size(num_bytes); + hb->thing_word = header_heap_bin(num_bytes); + hb->size = num_bytes; + erts_current_bin = (byte *) hb->data; + new_binary = make_binary(hb); + } else { + Binary* bptr; + ProcBin* pb; + + $TEST_BIN_VHEAP(num_bytes / sizeof(Eterm), alloc, $Live); + + /* + * Allocate the binary struct itself. + */ + bptr = erts_bin_nrml_alloc(num_bytes); + erts_current_bin = (byte *) bptr->orig_bytes; + + /* + * Now allocate the ProcBin on the heap. + */ + pb = (ProcBin *) HTOP; + HTOP += PROC_BIN_SIZE; + pb->thing_word = HEADER_PROC_BIN; + pb->size = num_bytes; + pb->next = MSO(c_p).first; + MSO(c_p).first = (struct erl_off_heap_header*) pb; + pb->val = bptr; + pb->bytes = (byte*) bptr->orig_bytes; + pb->flags = 0; + OH_OVERHEAD(&(MSO(c_p)), pb->size / sizeof(Eterm)); + new_binary = make_binary(pb); + } + + if (num_bits & 7) { + ErlSubBin* sb; + + sb = (ErlSubBin *) HTOP; + HTOP += ERL_SUB_BIN_SIZE; + sb->thing_word = HEADER_SUB_BIN; + sb->size = num_bytes - 1; + sb->bitsize = num_bits & 7; + sb->offs = 0; + sb->bitoffs = 0; + sb->is_writable = 0; + sb->orig = new_binary; + new_binary = make_binary(sb); + } + HEAP_SPACE_VERIFIED(0); + } + + /* Construct the segments. */ + for (p = p_start; p < p_end; p += BSC_NUM_ARGS) { + Eterm Src; + Eterm Size; + Eterm flags; + Eterm unit; + Sint _size; + + if(p[0] == BSC_STRING) { + byte* string; + $BS_LOAD_STRING_SRC(p, string); + $BS_LOAD_FIXED_SIZE(p, Size); + erts_new_bs_put_string(ERL_BITS_ARGS_2(string, Size)); + continue; + } + + $BS_LOAD_SRC(p, Src); + + switch (p[0]) { + case BSC_BINARY_ALL: + $BS_LOAD_UNIT(p, unit); + if (!erts_new_bs_put_binary_all(c_p, Src, unit)) { + $BS_FAIL_INFO($Fail, BADARG, am_type); + } + break; + case BSC_BINARY: + $BS_LOAD_UNIT(p, unit); + $BS_LOAD_FLAGS(p, flags); + $BS_LOAD_SIZE(p, Size); + $BS_GET_UNCHECKED_FIELD_SIZE(Size, unit, $BADARG($Fail), _size); + if (!erts_new_bs_put_binary(c_p, Src, _size)) { + $BS_FAIL_INFO($Fail, BADARG, c_p->fvalue); + } + break; + case BSC_BINARY_FIXED_SIZE: + $BS_LOAD_FIXED_SIZE(p, Size); + if (!erts_new_bs_put_binary(c_p, Src, Size)) { + $BS_FAIL_INFO($Fail, BADARG, c_p->fvalue); + } + break; + case BSC_FLOAT: + $BS_LOAD_UNIT(p, unit); + $BS_LOAD_FLAGS(p, flags); + $BS_LOAD_SIZE(p, Size); + $BS_GET_UNCHECKED_FIELD_SIZE(Size, unit, $BADARG($Fail), _size); + if (!erts_new_bs_put_float(c_p, Src, _size, flags)) { + $BS_FAIL_INFO($Fail, BADARG, c_p->fvalue); + } + break; + case BSC_FLOAT_FIXED_SIZE: + $BS_LOAD_FLAGS(p, flags); + $BS_LOAD_FIXED_SIZE(p, Size); + if (!erts_new_bs_put_float(c_p, Src, Size, flags)) { + $BS_FAIL_INFO($Fail, BADARG, c_p->fvalue); + } + break; + case BSC_INTEGER: + { + Sint _size; + + $BS_LOAD_UNIT(p, unit); + $BS_LOAD_FLAGS(p, flags); + $BS_LOAD_SIZE(p, Size); + $BS_GET_UNCHECKED_FIELD_SIZE(Size, unit, $BADARG($Fail), _size); + if (!erts_new_bs_put_integer(ERL_BITS_ARGS_3(Src, _size, flags))) { + $BS_FAIL_INFO($Fail, BADARG, am_type); + } + } + break; + case BSC_INTEGER_FIXED_SIZE: + case BSC_UTF32: + $BS_LOAD_FLAGS(p, flags); + $BS_LOAD_FIXED_SIZE(p, Size); + if (!erts_new_bs_put_integer(ERL_BITS_ARGS_3(Src, Size, flags))) { + $BS_FAIL_INFO($Fail, BADARG, am_type); + } + break; + case BSC_UTF8: + if (!erts_bs_put_utf8(ERL_BITS_ARGS_1(Src))) { + $BS_FAIL_INFO($Fail, BADARG, am_type); + } + break; + case BSC_UTF16: + $BS_LOAD_FLAGS(p, flags); + $BS_LOAD_SRC(p, Src); + if (!erts_bs_put_utf16(ERL_BITS_ARGS_2(Src, flags))) { + $BS_FAIL_INFO($Fail, BADARG, am_type); + } + break; + default: + ASSERT(0); + break; + } + } + + FCALLS = c_p->fcalls; + + /* Return the resulting binary. */ + $REFRESH_GEN_DEST(); + $Dst = new_binary; + I += n; +} + // // Matching of binaries. diff --git a/erts/emulator/beam/emu/emu_load.c b/erts/emulator/beam/emu/emu_load.c index fa0372be87..41d5aac41d 100644 --- a/erts/emulator/beam/emu/emu_load.c +++ b/erts/emulator/beam/emu/emu_load.c @@ -1376,6 +1376,21 @@ int beam_load_emit_op(LoaderState *stp, BeamOp *tmp_op) { case op_i_bs_match_string_yfWW: new_string_patch(stp, ci-1); break; + case op_i_bs_create_bin_jItdW: + { + int n = tmp_op->arity; + BeamInstr* p = &code[ci-n]; + BeamInstr* end_p = &code[ci]; + while (p < end_p) { + switch (*p) { + case BSC_STRING: + new_string_patch(stp, p+3-code); + break; + } + p += 5; + } + } + break; case op_catch_yf: /* code[ci-3] &&lb_catch_yf * code[ci-2] y-register offset in E diff --git a/erts/emulator/beam/emu/generators.tab b/erts/emulator/beam/emu/generators.tab index f9988f2399..397ddb97e7 100644 --- a/erts/emulator/beam/emu/generators.tab +++ b/erts/emulator/beam/emu/generators.tab @@ -831,3 +831,163 @@ gen.init_yregs(N, Yregs) { $INIT_YREGS(S, N.val); return first; } + +gen.create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) { + BeamOp* op; + int fixed_args; + BeamOpArg* src; + BeamOpArg* dst; + BeamOpArg* endp; + + endp = Segments + N.val; + N.val = 5*N.val/6; + + $NewBeamOp(S, op); + $BeamOpNameArity(op, i_bs_create_bin, 5); + fixed_args = op->arity; + $BeamOpArity(op, (N.val + fixed_args)); + + op->a[0] = Fail; + op->a[1] = Alloc; + op->a[2] = Live; + op->a[3] = Dst; + op->a[4] = N; + + for (src = Segments, dst = op->a+fixed_args; src < endp; src += 6, dst += 5) { + UWord unit; + BeamOpArg Flags; + Uint flags = 0; + BeamOpArg Size; + Uint type; + Uint segment; + + ASSERT(src[0].type = TAG_a); + ASSERT(src[1].type == TAG_u); + ASSERT(src[2].type == TAG_u); + segment = src[1].val; + + /* Get unit. */ + dst[1] = src[2]; + unit = dst[1].val; + + /* Translate flags. */ + Flags = src[3]; /* Flags */ + if (Flags.type != TAG_n) { + 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; + } + term = CDR(consp); + } + ASSERT(is_nil(term)); + } + } + Flags.type = TAG_u; + Flags.val = flags; + $NativeEndian(Flags); + Flags.val = (segment << 3) | Flags.val; + dst[2] = Flags; + + /* Store source. */ + dst[3] = src[4]; /* Src */ + + /* Get size */ + Size = src[5]; /* Size */ + + /* Translate type. */ + switch (src[0].val) { + case am_append: + type = BSC_APPEND; + break; + case am_private_append: + type = BSC_PRIVATE_APPEND; + break; + case am_binary: + { + UWord bits; + type = BSC_BINARY; + if (Size.type == TAG_a && Size.val == am_all) { + type = BSC_BINARY_ALL; + } else if (Size.type == TAG_i && + (Sint) Size.val >= 0 && + beam_load_safe_mul(Size.val, unit, &bits) && + (bits >> (sizeof(Uint)-1)*8) == 0) { + type = BSC_BINARY_FIXED_SIZE; + Size.type = TAG_u; + Size.val = bits; + unit = 0; + } + } + break; + case am_float: + { + UWord bits; + type = BSC_FLOAT; + if (Size.type == TAG_i && + (Sint) Size.val >= 0 && + beam_load_safe_mul(Size.val, unit, &bits) && + (bits >> (sizeof(Uint)-1)*8) == 0) { + type = BSC_FLOAT_FIXED_SIZE; + Size.type = TAG_u; + Size.val = bits; + unit = 0; + } + } + break; + case am_integer: + { + UWord bits; + type = BSC_INTEGER; + if (Size.type == TAG_i && + (Sint) Size.val >= 0 && + beam_load_safe_mul(Size.val, unit, &bits) && + (bits >> (sizeof(Uint)-1)*8) == 0) { + type = BSC_INTEGER_FIXED_SIZE; + Size.type = TAG_u; + Size.val = bits; + unit = 0; + } + } + break; + case am_string: + type = BSC_STRING; + ASSERT(Size.type == TAG_i); + ASSERT(unit == 8); + Size.type = TAG_u; + Size.val = Size.val; /* Size of string in bytes. */ + unit = 0; + break; + case am_utf8: + type = BSC_UTF8; + break; + case am_utf16: + type = BSC_UTF16; + break; + case am_utf32: + type = BSC_UTF32; + Size.type = TAG_u; + Size.val = 32; + break; + default: + abort(); + } + dst[0].type = TAG_u; + dst[0].val = type; + + /* Store value of unit. */ + dst[1].val = unit; + + /* Store size. */ + dst[4] = Size; + } + return op; +} diff --git a/erts/emulator/beam/emu/ops.tab b/erts/emulator/beam/emu/ops.tab index c50b12513d..0148ebb112 100644 --- a/erts/emulator/beam/emu/ops.tab +++ b/erts/emulator/beam/emu/ops.tab @@ -1177,9 +1177,19 @@ bs_skip_utf32 Fail=f Ms=xy Live=u Flags=u => \ i_bs_validate_unicode_retract j s S %hot -# -# Constructing binaries -# +# ================================================================ +# New binary construction (OTP 25). +# ================================================================ + +bs_create_bin Fail Alloc=u Live=u Unit=u Dst=xy N=u Segments=* => \ + create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) + +i_bs_create_bin j I t d W * + +# ================================================================ +# Old instruction for constructing binaries (up to OTP 24). +# ================================================================ + %warm bs_init2 Fail Sz Words Regs Flags Dst | binary_too_big(Sz) => system_limit Fail diff --git a/erts/emulator/beam/erl_bits.c b/erts/emulator/beam/erl_bits.c index e4dcb729f8..2fb0d26059 100644 --- a/erts/emulator/beam/erl_bits.c +++ b/erts/emulator/beam/erl_bits.c @@ -1021,10 +1021,12 @@ erts_new_bs_put_binary(Process *c_p, Eterm arg, Uint num_bits) ERL_BITS_DEFINE_STATEP(c_p); if (!is_binary(arg)) { - return 0; + c_p->fvalue = am_type; + return 0; } ERTS_GET_BINARY_BYTES(arg, bptr, bitoffs, bitsize); if (num_bits > 8*binary_size(arg)+bitsize) { + c_p->fvalue = am_short; return 0; } copy_binary_to_buffer(erts_current_bin, erts_bin_offset, bptr, bitoffs, num_bits); @@ -1098,6 +1100,7 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) #endif } else if (is_big(arg)) { if (big_to_double(arg, &u.f64) < 0) { + c_p->fvalue = am_no_float; return 0; } #ifdef DOUBLE_MIDDLE_ENDIAN @@ -1108,6 +1111,7 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) b = u.i32[1]; #endif } else { + c_p->fvalue = am_type; return 0; } } else if (num_bits == 32) { @@ -1130,6 +1134,7 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) } else if (is_big(arg)) { double f64; if (big_to_double(arg, &f64) < 0) { + c_p->fvalue = am_no_float; return 0; } ERTS_FP_CHECK_INIT(c_p); @@ -1137,6 +1142,7 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) ERTS_FP_ERROR(c_p,u.f32,;); a = u.i32; } else { + c_p->fvalue = am_type; return 0; } } else if (num_bits == 16) { @@ -1159,6 +1165,7 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) } else if (is_big(arg)) { double f64; if (big_to_double(arg, &f64) < 0) { + c_p->fvalue = am_no_float; return 0; } ERTS_FP_CHECK_INIT(c_p); @@ -1166,9 +1173,11 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) u.f16 = FP16_FROM_FP64(f64); a = u.i16; } else { + c_p->fvalue = am_type; return 0; } } else { + c_p->fvalue = am_invalid; return 0; } @@ -1265,6 +1274,7 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) #endif } else if (is_big(arg)) { if (big_to_double(arg, &f64) < 0) { + c_p->fvalue = am_no_float; return 0; } #ifdef DOUBLE_MIDDLE_ENDIAN @@ -1273,6 +1283,7 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) bptr = (byte *) &f64; #endif } else { + c_p->fvalue = am_type; return 0; } #ifdef DOUBLE_MIDDLE_ENDIAN @@ -1293,6 +1304,7 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) bptr = (byte *) &f32; } else if (is_big(arg)) { if (big_to_double(arg, &f64) < 0) { + c_p->fvalue = am_no_float; return 0; } ERTS_FP_CHECK_INIT(c_p); @@ -1300,6 +1312,7 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) ERTS_FP_ERROR(c_p,f32,;); bptr = (byte *) &f32; } else { + c_p->fvalue = am_type; return 0; } } else if (num_bits == 16) { @@ -1315,6 +1328,7 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) bptr = (byte *) &f16; } else if (is_big(arg)) { if (big_to_double(arg, &f64) < 0) { + c_p->fvalue = am_no_float; return 0; } ERTS_FP_CHECK_INIT(c_p); @@ -1322,9 +1336,11 @@ erts_new_bs_put_float(Process *c_p, Eterm arg, Uint num_bits, int flags) f16 = FP16_FROM_FP64(f64); bptr = (byte *) &f16; } else { + c_p->fvalue = am_type; return 0; } } else { + c_p->fvalue = am_invalid; return 0; } if (BIT_IS_MACHINE_ENDIAN(flags)) { @@ -1369,19 +1385,10 @@ void increase_proc_bin_sz(Process* p, ProcBin* pb, Uint new_size) Eterm erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term, - Uint extra_words, Uint unit) + Uint extra_words, Uint unit) { - Eterm bin; /* Given binary */ - Eterm* ptr; - Eterm hdr; - ErlSubBin* sb; - ProcBin* pb; - Binary* binp; - Uint heap_need; - Uint build_size_in_bits; - Uint used_size_in_bits; Uint unsigned_bits; - ERL_BITS_DEFINE_STATEP(c_p); + Uint build_size_in_bits; /* * Check and untag the requested build size. @@ -1389,7 +1396,8 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term, if (is_small(build_size_term)) { Sint signed_bits = signed_val(build_size_term); if (signed_bits < 0) { - goto badarg; + c_p->freason = BADARG; + return THE_NON_VALUE; } build_size_in_bits = (Uint) signed_bits; } else if (term_to_Uint(build_size_term, &unsigned_bits)) { @@ -1398,12 +1406,33 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term, c_p->freason = unsigned_bits; return THE_NON_VALUE; } + return erts_bs_append_checked(c_p, reg, live, build_size_in_bits, + extra_words, unit); +} + +Eterm +erts_bs_append_checked(Process* c_p, Eterm* reg, Uint live, + Uint build_size_in_bits, Uint extra_words, + Uint unit) +{ + Eterm bin; /* Given binary */ + Eterm* ptr; + Eterm hdr; + ErlSubBin* sb; + ProcBin* pb; + Binary* binp; + Uint heap_need; + Uint used_size_in_bits; + ERL_BITS_DEFINE_STATEP(c_p); /* * Check the binary argument. */ bin = reg[live]; if (!is_boxed(bin)) { + type_error: + c_p->fvalue = am_type; + badarg: c_p->freason = BADARG; return THE_NON_VALUE; @@ -1411,7 +1440,7 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term, ptr = boxed_val(bin); hdr = *ptr; if (!is_binary_header(hdr)) { - goto badarg; + goto type_error; } if (hdr != HEADER_SUB_BIN) { goto not_writable; @@ -1434,6 +1463,7 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term, if (unit > 1) { if ((unit == 8 && (erts_bin_offset & 7) != 0) || (erts_bin_offset % unit) != 0) { + c_p->fvalue = am_unit; goto badarg; } } @@ -1447,6 +1477,7 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term, } if((ERTS_UINT_MAX - build_size_in_bits) < erts_bin_offset) { + c_p->fvalue = am_size; c_p->freason = SYSTEM_LIMIT; return THE_NON_VALUE; } @@ -1526,6 +1557,7 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term, if (unit > 1) { if ((unit == 8 && (erts_bin_offset & 7) != 0) || (erts_bin_offset % unit) != 0) { + c_p->fvalue = am_unit; goto badarg; } } @@ -1535,6 +1567,7 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term, } if((ERTS_UINT_MAX - build_size_in_bits) < erts_bin_offset) { + c_p->fvalue = am_size; c_p->freason = SYSTEM_LIMIT; return THE_NON_VALUE; } @@ -1599,14 +1632,8 @@ erts_bs_append(Process* c_p, Eterm* reg, Uint live, Eterm build_size_term, Eterm erts_bs_private_append(Process* p, Eterm bin, Eterm build_size_term, Uint unit) { - Eterm* ptr; - ErlSubBin* sb; - ProcBin* pb; - Binary* binp; - Uint build_size_in_bits; - Uint pos_in_bits_after_build; Uint unsigned_bits; - ERL_BITS_DEFINE_STATEP(p); + Uint build_size_in_bits; /* * Check and untag the requested build size. @@ -1624,6 +1651,18 @@ erts_bs_private_append(Process* p, Eterm bin, Eterm build_size_term, Uint unit) p->freason = unsigned_bits; return THE_NON_VALUE; } + return erts_bs_private_append_checked(p, bin, build_size_in_bits, unit); +} + +Eterm +erts_bs_private_append_checked(Process* p, Eterm bin, Uint build_size_in_bits, Uint unit) +{ + Eterm* ptr; + ErlSubBin* sb; + ProcBin* pb; + Binary* binp; + Uint pos_in_bits_after_build; + ERL_BITS_DEFINE_STATEP(p); ptr = boxed_val(bin); ASSERT(*ptr == HEADER_SUB_BIN); @@ -1640,6 +1679,7 @@ erts_bs_private_append(Process* p, Eterm bin, Eterm build_size_term, Uint unit) erts_bin_offset = 8*sb->size + sb->bitsize; if((ERTS_UINT_MAX - build_size_in_bits) < erts_bin_offset) { + p->fvalue = am_size; p->freason = SYSTEM_LIMIT; return THE_NON_VALUE; } diff --git a/erts/emulator/beam/erl_bits.h b/erts/emulator/beam/erl_bits.h index 25aff39047..5ac6dd9ca7 100644 --- a/erts/emulator/beam/erl_bits.h +++ b/erts/emulator/beam/erl_bits.h @@ -182,7 +182,10 @@ Eterm erts_bs_get_utf8(ErlBinMatchBuffer* mb); Eterm erts_bs_get_utf16(ErlBinMatchBuffer* mb, Uint flags); Eterm erts_bs_append(Process* p, Eterm* reg, Uint live, Eterm build_size_term, Uint extra_words, Uint unit); +Eterm erts_bs_append_checked(Process* p, Eterm* reg, Uint live, Uint size, + Uint extra_words, Uint unit); Eterm erts_bs_private_append(Process* p, Eterm bin, Eterm sz, Uint unit); +Eterm erts_bs_private_append_checked(Process* p, Eterm bin, Uint size, Uint unit); Eterm erts_bs_init_writable(Process* p, Eterm sz); /* @@ -204,7 +207,7 @@ Eterm erts_extract_sub_binary(Eterm **hp, Eterm base_bin, byte *base_data, #define EXTRACT_SUB_BIN_HEAP_NEED (heap_bin_size(ERL_ONHEAP_BIN_LIMIT)) /* - * Flags for bs_get_* / bs_put_* / bs_init* instructions. + * Flags for bs_create_bin / bs_get_* / bs_put_* / bs_init* instructions. */ #define BSF_ALIGNED 1 /* Field is guaranteed to be byte-aligned. */ @@ -213,4 +216,24 @@ Eterm erts_extract_sub_binary(Eterm **hp, Eterm base_bin, byte *base_data, #define BSF_EXACT 8 /* Size in bs_init is exact. */ #define BSF_NATIVE 16 /* Native endian. */ +/* + * Binary construction operations. + */ + +#define BSC_APPEND 0 +#define BSC_PRIVATE_APPEND 1 +#define BSC_BINARY 2 +#define BSC_BINARY_FIXED_SIZE 3 +#define BSC_BINARY_ALL 4 +#define BSC_FLOAT 5 +#define BSC_FLOAT_FIXED_SIZE 6 +#define BSC_INTEGER 7 +#define BSC_INTEGER_FIXED_SIZE 8 +#define BSC_STRING 9 +#define BSC_UTF8 10 +#define BSC_UTF16 11 +#define BSC_UTF32 12 + +#define BSC_NUM_ARGS 5 + #endif /* __ERL_BITS_H__ */ diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h index 904d394fca..2d5fced7c8 100644 --- a/erts/emulator/beam/global.h +++ b/erts/emulator/beam/global.h @@ -1236,6 +1236,7 @@ void print_pass_through(int, byte*, int); int catchlevel(Process*); void init_emulator(void); void process_main(ErtsSchedulerData *); +void erts_prepare_bs_construct_fail_info(Process* c_p, const BeamInstr* p, Eterm reason, Eterm Info); void erts_dirty_process_main(ErtsSchedulerData *); Eterm build_stacktrace(Process* c_p, Eterm exc); Eterm expand_error_value(Process* c_p, Uint freason, Eterm Value); diff --git a/erts/emulator/beam/jit/arm/beam_asm.hpp b/erts/emulator/beam/jit/arm/beam_asm.hpp index fbd42ba962..bc873991a1 100644 --- a/erts/emulator/beam/jit/arm/beam_asm.hpp +++ b/erts/emulator/beam/jit/arm/beam_asm.hpp @@ -800,6 +800,8 @@ class BeamGlobalAssembler : public BeamAssembler { _(bif_tuple_size_guard) \ _(bs_add_guard_shared) \ _(bs_add_body_shared) \ + _(bs_bit_size_shared) \ + _(bs_create_bin_error_shared) \ _(bs_get_tail_shared) \ _(bs_size_check_shared) \ _(call_bif_shared) \ diff --git a/erts/emulator/beam/jit/arm/generators.tab b/erts/emulator/beam/jit/arm/generators.tab index abe030f51f..684649f47d 100644 --- a/erts/emulator/beam/jit/arm/generators.tab +++ b/erts/emulator/beam/jit/arm/generators.tab @@ -495,3 +495,59 @@ gen.func_end(Func_Label, Entry_Label) { return op; } + +gen.create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) { + BeamOp* op; + int fixed_args; + int i; + + $NewBeamOp(S, op); + $BeamOpNameArity(op, i_bs_create_bin, 4); + fixed_args = op->arity; + $BeamOpArity(op, (N.val + fixed_args)); + + op->a[0] = Fail; + op->a[1] = Alloc; + op->a[2] = Live; + op->a[3] = Dst; + + for (i = 0; i < N.val; i += 6) { + BeamOpArg Flags; + Uint flags = 0; + + /* Copy all but flags. */ + op->a[i+fixed_args+0] = Segments[i+0]; + op->a[i+fixed_args+1] = Segments[i+1]; + op->a[i+fixed_args+2] = Segments[i+2]; + op->a[i+fixed_args+4] = Segments[i+4]; + op->a[i+fixed_args+5] = Segments[i+5]; + + /* Translate flags. */ + Flags = Segments[i+3]; /* Flags */ + if (Flags.type != TAG_n) { + 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; + } + term = CDR(consp); + } + ASSERT(is_nil(term)); + } + } + Flags.type = TAG_u; + Flags.val = flags; + $NativeEndian(Flags); + op->a[i+fixed_args+3] = Flags; + } + + return op; +} diff --git a/erts/emulator/beam/jit/arm/instr_bs.cpp b/erts/emulator/beam/jit/arm/instr_bs.cpp index 93a65f613a..3a8d6652f5 100644 --- a/erts/emulator/beam/jit/arm/instr_bs.cpp +++ b/erts/emulator/beam/jit/arm/instr_bs.cpp @@ -1355,3 +1355,593 @@ void BeamModuleAssembler::emit_bs_init_writable() { a.mov(XREG0, ARG1); } + +void BeamGlobalAssembler::emit_bs_create_bin_error_shared() { + a.mov(XREG0, a64::x30); + + emit_enter_runtime(0); + + a.mov(ARG3, ARG1); + a.mov(ARG2, ARG4); + a.mov(ARG1, c_p); + runtime_call<3>(beam_jit_bs_construct_fail_info); + + emit_leave_runtime(0); + + a.mov(ARG4, ZERO); + a.mov(ARG2, XREG0); + a.b(labels[raise_exception_shared]); +} + +/* + * ARG1 = term + * + * If the term in ARG1 is a binary on enty, on return + * ARG1 will contain the size of the binary in bits and + * sign flag will be cleared. + * + * If the term is not a binary, the sign flag will be set. + */ +void BeamGlobalAssembler::emit_bs_bit_size_shared() { + Label not_sub_bin = a.newLabel(); + Label fail = a.newLabel(); + + arm::Gp boxed_ptr = emit_ptr_val(ARG1, ARG1); + + emit_is_boxed(fail, boxed_ptr); + + a.ldur(TMP1, emit_boxed_val(boxed_ptr)); + a.and_(TMP1, TMP1, imm(_TAG_HEADER_MASK)); + a.cmp(TMP1, imm(_TAG_HEADER_SUB_BIN)); + a.cond_ne().b(not_sub_bin); + + a.ldur(TMP1, emit_boxed_val(boxed_ptr, sizeof(Eterm))); + a.ldurb(TMP2.w(), emit_boxed_val(boxed_ptr, offsetof(ErlSubBin, bitsize))); + + a.adds(ARG1, TMP2, TMP1, arm::lsl(3)); + a.ret(a64::x30); + + a.bind(not_sub_bin); + ERTS_CT_ASSERT(_TAG_HEADER_REFC_BIN + 4 == _TAG_HEADER_HEAP_BIN); + a.and_(TMP1, TMP1, imm(~4)); + a.cmp(TMP1, imm(_TAG_HEADER_REFC_BIN)); + a.cond_ne().b(fail); + + a.ldur(TMP1, emit_boxed_val(boxed_ptr, sizeof(Eterm))); + a.lsl(ARG1, TMP1, imm(3)); + a.tst(ARG1, ARG1); + + a.ret(a64::x30); + + a.bind(fail); + mov_imm(ARG1, -1); + a.tst(ARG1, ARG1); + + a.ret(a64::x30); +} + +struct BscSegment { + BscSegment() + : src(ArgVal(ArgVal::Immediate, NIL)), + size(ArgVal(ArgVal::Immediate, NIL)), effectiveSize(-1){}; + + Eterm type; + Uint unit; + Uint flags; + ArgVal src; + ArgVal size; + + Uint error_info; + Sint effectiveSize; +}; + +void BeamModuleAssembler::emit_i_bs_create_bin(const ArgVal &Fail, + const ArgVal &Alloc, + const ArgVal &Live0, + const ArgVal &Dst, + const Span &args) { + Uint num_bits = 0; + std::size_t n = args.size(); + std::vector segments; + Label error = a.newLabel(); + ArgVal Live = Live0; + arm::Gp sizeReg; + + /* + * Collect information about each segment and calculate sizes of + * fixed segments. + */ + for (std::size_t i = 0; i < n; i += 6) { + BscSegment seg; + Uint bsc_op; + Uint bsc_segment; + + ASSERT(args[i].isImmed()); + ASSERT(args[i + 1].getType() == TAG_u); + ASSERT(args[i + 2].getType() == TAG_u); + ASSERT(args[i + 3].getType() == TAG_u); + seg.type = args[i].getValue(); + bsc_segment = args[i + 1].getValue(); + seg.unit = args[i + 2].getValue(); + seg.flags = args[i + 3].getValue(); + seg.src = args[i + 4]; + seg.size = args[i + 5]; + + switch (seg.type) { + case am_float: + bsc_op = BSC_OP_FLOAT; + break; + case am_integer: + bsc_op = BSC_OP_INTEGER; + break; + case am_utf8: + bsc_op = BSC_OP_UTF8; + break; + case am_utf16: + bsc_op = BSC_OP_UTF16; + break; + case am_utf32: + bsc_op = BSC_OP_UTF32; + break; + default: + bsc_op = BSC_OP_BINARY; + break; + } + + /* + * Save segment number and operation for use in extended + * error information. + */ + seg.error_info = BSC_SET_SEGMENT_OP(bsc_segment, bsc_op); + + /* + * Attempt to calculate the effective size of this segment. + * Give up is variable or invalid. + */ + if (seg.size.isImmed() && seg.unit != 0) { + Eterm size = seg.size.getValue(); + if (is_small(size)) { + Uint unsigned_size = unsigned_val(size); + if ((unsigned_size >> (sizeof(Eterm) - 1) * 8) == 0) { + /* This multiplication cannot overflow. */ + Uint seg_size = seg.unit * unsigned_size; + seg.effectiveSize = seg_size; + num_bits += seg_size; + } + } + } + + if (seg.effectiveSize < 0 && seg.type != am_append && + seg.type != am_private_append) { + /* At least one segment will need a dynamic size + * calculation. */ + sizeReg = ARG8; + } + + segments.insert(segments.end(), seg); + } + + if (Fail.getValue() != 0) { + error = resolve_beam_label(Fail, dispUnknown); + } else { + Label past_error = a.newLabel(); + + a.b(past_error); + + a.bind(error); + { + comment("handle error"); + + /* + * ARG4 = packed error information + * ARG1 = optional bad size value; only valid if + * BSC_REASON_DEPENDS is set in ARG4 + */ + fragment_call(ga->get_bs_create_bin_error_shared()); + last_error_offset = getOffset() & -8; + } + + a.bind(past_error); + } + + /* We count the total number of bits in an unsigned integer. To + * avoid having to check for overflow when adding to the counter, + * we ensure that the signed size of each segment fits in a + * word. */ + if (sizeReg.isValid()) { + comment("calculate sizes"); + mov_imm(sizeReg, num_bits); + } + + /* Generate code for calculating the size of the binary to be + * created. */ + for (auto seg : segments) { + if (seg.effectiveSize >= 0) { + continue; + } + + if (seg.type == am_append || seg.type == am_private_append) { + continue; + } + + if (seg.size.isImmed() && seg.size.getValue() == am_all && + seg.type == am_binary) { + comment("size of an entire binary"); + mov_arg(ARG1, seg.src); + fragment_call(ga->get_bs_bit_size_shared()); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + a.cond_mi().b(resolve_label(error, disp1MB)); + a.add(sizeReg, sizeReg, ARG1); + } else if (seg.unit != 0) { + comment("size binary/integer/float/string"); + mov_arg(ARG1, seg.size); + a.and_(TMP2, ARG1, imm(_TAG_IMMED1_MASK)); + a.cmp(TMP2, imm(_TAG_IMMED1_SMALL)); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_DEPENDS, + BSC_INFO_DEPENDS)); + } + a.cond_ne().b(resolve_label(error, disp1MB)); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | + BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_NEGATIVE)); + } + a.tbnz(ARG1, 63, resolve_label(error, disp32K)); + a.asr(TMP1, ARG1, imm(_TAG_IMMED1_SIZE)); + if (seg.unit == 1) { + a.add(sizeReg, sizeReg, TMP1); + } else { + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | + BSC_SET_REASON_INFO(BSC_REASON_SYSTEM_LIMIT, + BSC_INFO_SIZE)); + } + a.tst(TMP1, imm(0xffful << 52)); + a.cond_ne().b(resolve_label(error, disp1MB)); + mov_imm(TMP2, seg.unit); + a.madd(sizeReg, TMP1, TMP2, sizeReg); + } + } else { + switch (seg.type) { + case am_utf8: { + comment("size utf8"); + Label next = a.newLabel(); + auto src_reg = load_source(seg.src, TMP1); + + a.lsr(TMP1, src_reg.reg, imm(_TAG_IMMED1_SIZE)); + mov_imm(TMP2, 1 * 8); + a.cmp(TMP1, imm(0x7F)); + a.cond_ls().b(next); + + mov_imm(TMP2, 2 * 8); + a.cmp(TMP1, imm(0x7FFUL)); + a.cond_ls().b(next); + + a.cmp(TMP1, imm(0x10000UL)); + mov_imm(TMP2, 3 * 8); + mov_imm(TMP3, 4 * 8); + a.csel(TMP2, TMP2, TMP3, arm::Cond::kLO); + + a.bind(next); + a.add(sizeReg, sizeReg, TMP2); + break; + } + case am_utf16: { + /* erts_bs_put_utf16 errors out whenever something's + * fishy, so we can return garbage (16 or 32) if our + * input is not a small. */ + comment("size utf16"); + auto src_reg = load_source(seg.src, TMP1); + + a.asr(TMP1, src_reg.reg, imm(_TAG_IMMED1_SIZE)); + a.cmp(TMP1, imm(0x10000UL)); + mov_imm(TMP1, 2 * 8); + mov_imm(TMP2, 4 * 8); + a.csel(TMP1, TMP1, TMP2, arm::Cond::kLO); + a.add(sizeReg, sizeReg, TMP1); + break; + } + case am_utf32: { + Label next = a.newLabel(); + + comment("size utf32"); + auto src_reg = load_source(seg.src, TMP1); + + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | + BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + + a.add(sizeReg, sizeReg, imm(4 * 8)); + + a.and_(TMP2, src_reg.reg, imm(_TAG_IMMED1_MASK)); + a.cmp(TMP2, imm(_TAG_IMMED1_SMALL)); + a.cond_ne().b(error); + + mov_imm(TMP2, make_small(0xD800UL)); + a.cmp(src_reg.reg, TMP2); + a.cond_lo().b(next); + + mov_imm(TMP2, make_small(0xDFFFUL)); + a.cmp(src_reg.reg, TMP2); + a.cond_ls().b(error); + + mov_imm(TMP2, make_small(0x10FFFFUL)); + a.cmp(src_reg.reg, TMP2); + a.cond_hi().b(error); + + a.bind(next); + break; + } + default: + ASSERT(0); + break; + } + } + } + + /* Allocate the binary. */ + if (segments[0].type == am_append) { + BscSegment seg = segments[0]; + comment("append to binary"); + mov_arg(ARG3, Live); + if (sizeReg.isValid()) { + a.mov(ARG4, sizeReg); + } else { + mov_imm(ARG4, num_bits); + } + mov_arg(ARG5, Alloc); + mov_imm(ARG6, seg.unit); + mov_arg(ArgVal(ArgVal::XReg, Live.getValue()), seg.src); + a.mov(ARG1, c_p); + load_x_reg_array(ARG2); + + emit_enter_runtime(Live.getValue() + 1); + runtime_call<6>(erts_bs_append_checked); + emit_leave_runtime(Live.getValue() + 1); + + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_FVALUE)); + } + emit_branch_if_not_value(ARG1, resolve_label(error, dispUnknown)); + } else if (segments[0].type == am_private_append) { + BscSegment seg = segments[0]; + comment("private append to binary"); + if (Alloc.getValue() != 0) { + emit_gc_test(ArgVal(ArgVal::Word, 0), Alloc, Live); + } + mov_arg(ARG2, seg.src); + if (sizeReg.isValid()) { + a.mov(ARG3, sizeReg); + } else { + mov_imm(ARG3, num_bits); + } + a.mov(ARG4, seg.unit); + a.mov(ARG1, c_p); + emit_enter_runtime(Live.getValue()); + runtime_call<4>(erts_bs_private_append_checked); + emit_leave_runtime(Live.getValue()); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_FREASON, + BSC_INFO_FVALUE)); + } + emit_branch_if_not_value(ARG1, resolve_label(error, dispUnknown)); + } else { + comment("allocate binary"); + mov_arg(ARG5, Alloc); + mov_arg(ARG6, Live); + load_erl_bits_state(ARG3); + load_x_reg_array(ARG2); + a.mov(ARG1, c_p); + emit_enter_runtime(Live.getValue()); + if (sizeReg.isValid()) { + comment("(size in bits)"); + a.mov(ARG4, sizeReg); + runtime_call<6>(beam_jit_bs_init_bits); + } else if (num_bits % 8 == 0) { + comment("(size in bytes)"); + mov_imm(ARG4, num_bits / 8); + runtime_call<6>(beam_jit_bs_init); + } else { + mov_imm(ARG4, num_bits); + runtime_call<6>(beam_jit_bs_init_bits); + } + emit_leave_runtime(Live.getValue()); + } + a.str(ARG1, TMP_MEM1q); + + /* Build each segment of the binary. */ + for (auto seg : segments) { + switch (seg.type) { + case am_append: + case am_private_append: + break; + case am_binary: { + Uint error_info; + + comment("construct a binary segment"); + emit_enter_runtime(Live.getValue()); + if (seg.effectiveSize >= 0) { + /* The segment has a literal size. */ + mov_imm(ARG3, seg.effectiveSize); + mov_arg(ARG2, seg.src); + a.mov(ARG1, c_p); + runtime_call<3>(erts_new_bs_put_binary); + error_info = + BSC_SET_REASON_INFO(BSC_REASON_BADARG, BSC_INFO_FVALUE); + } else if (seg.size.isImmed() && seg.size.getValue() == am_all) { + /* Include the entire binary/bitstring in the + * resulting binary. */ + a.mov(ARG3, seg.unit); + mov_arg(ARG2, seg.src); + a.mov(ARG1, c_p); + runtime_call<3>(erts_new_bs_put_binary_all); + error_info = + BSC_SET_REASON_INFO(BSC_REASON_BADARG, BSC_INFO_UNIT); + } else { + /* The size is a variable. We have verified that + * the value is a non-negative small in the + * appropriate range. Multiply the size with the + * unit. */ + mov_arg(ARG3, seg.size); + a.asr(ARG3, ARG3, imm(_TAG_IMMED1_SIZE)); + if (seg.unit != 1) { + mov_imm(TMP1, seg.unit); + a.mul(ARG3, ARG3, TMP1); + } + mov_arg(ARG2, seg.src); + a.mov(ARG1, c_p); + runtime_call<3>(erts_new_bs_put_binary); + error_info = + BSC_SET_REASON_INFO(BSC_REASON_BADARG, BSC_INFO_FVALUE); + } + emit_leave_runtime(Live.getValue()); + if (Fail.getValue() == 0) { + mov_imm(ARG4, seg.error_info | error_info); + } + a.cbz(ARG1, resolve_label(error, disp1MB)); + break; + } + case am_float: + comment("construct float segment"); + if (seg.effectiveSize >= 0) { + mov_imm(ARG3, seg.effectiveSize); + } else { + mov_arg(ARG3, seg.size); + a.asr(ARG3, ARG3, imm(_TAG_IMMED1_SIZE)); + if (seg.unit != 1) { + mov_imm(TMP1, seg.unit); + a.mul(ARG3, ARG3, TMP1); + } + } + mov_arg(ARG2, seg.src); + mov_imm(ARG4, seg.flags); + a.mov(ARG1, c_p); + + emit_enter_runtime(Live.getValue()); + runtime_call<4>(erts_new_bs_put_float); + emit_leave_runtime(Live.getValue()); + + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_FVALUE)); + } + a.cbz(ARG1, resolve_label(error, disp1MB)); + break; + case am_integer: + comment("construct integer segment"); + if (seg.effectiveSize >= 0) { + mov_imm(ARG3, seg.effectiveSize); + } else { + mov_arg(ARG3, seg.size); + a.asr(ARG3, ARG3, imm(_TAG_IMMED1_SIZE)); + if (seg.unit != 1) { + mov_imm(TMP1, seg.unit); + a.mul(ARG3, ARG3, TMP1); + } + } + mov_arg(ARG2, seg.src); + mov_imm(ARG4, seg.flags); + load_erl_bits_state(ARG1); + + emit_enter_runtime(Live.getValue()); + runtime_call<4>(erts_new_bs_put_integer); + emit_leave_runtime(Live.getValue()); + + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + a.cbz(ARG1, resolve_label(error, disp1MB)); + break; + case am_string: { + ArgVal string_ptr = ArgVal(ArgVal::BytePtr, seg.src.getValue()); + comment("insert string"); + ASSERT(seg.effectiveSize >= 0); + mov_imm(ARG3, seg.effectiveSize / 8); + mov_arg(ARG2, string_ptr); + load_erl_bits_state(ARG1); + + emit_enter_runtime(Live.getValue()); + runtime_call<3>(erts_new_bs_put_string); + emit_leave_runtime(Live.getValue()); + break; + } + case am_utf8: + comment("construct utf8 segment"); + mov_arg(ARG2, seg.src); + load_erl_bits_state(ARG1); + + emit_enter_runtime(Live.getValue()); + runtime_call<2>(erts_bs_put_utf8); + + emit_leave_runtime(Live.getValue()); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + a.cbz(ARG1, resolve_label(error, disp1MB)); + break; + case am_utf16: + comment("construct utf8 segment"); + mov_arg(ARG2, seg.src); + a.mov(ARG3, seg.flags); + load_erl_bits_state(ARG1); + + emit_enter_runtime(Live.getValue()); + runtime_call<3>(erts_bs_put_utf16); + emit_leave_runtime(Live.getValue()); + + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + a.cbz(ARG1, resolve_label(error, disp1MB)); + break; + case am_utf32: + mov_arg(ARG2, seg.src); + mov_imm(ARG3, 4 * 8); + a.mov(ARG4, seg.flags); + load_erl_bits_state(ARG1); + + emit_enter_runtime(Live.getValue()); + runtime_call<4>(erts_new_bs_put_integer); + emit_leave_runtime(Live.getValue()); + + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + a.cbz(ARG1, resolve_label(error, disp1MB)); + break; + default: + ASSERT(0); + break; + } + } + + comment("done"); + mov_arg(Dst, TMP_MEM1q); +} diff --git a/erts/emulator/beam/jit/arm/ops.tab b/erts/emulator/beam/jit/arm/ops.tab index d6f0c283f6..845549f256 100644 --- a/erts/emulator/beam/jit/arm/ops.tab +++ b/erts/emulator/beam/jit/arm/ops.tab @@ -893,9 +893,19 @@ bs_skip_utf32 Fail=f Ms=xy Live=u Flags=u => \ i_bs_validate_unicode_retract j s S %hot -# -# Constructing binaries -# +# ================================================================ +# New binary construction (OTP 25). +# ================================================================ + +bs_create_bin Fail=j Alloc=u Live=u Unit=u Dst=xy N=u Segments=* => \ + create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) + +i_bs_create_bin j I t d * + +# ================================================================ +# Old instruction for constructing binaries (up to OTP 24). +# ================================================================ + %warm bs_init2 Fail Sz Words Regs Flags Dst | binary_too_big(Sz) => system_limit Fail diff --git a/erts/emulator/beam/jit/beam_jit_common.cpp b/erts/emulator/beam/jit/beam_jit_common.cpp index b1a43206d8..d03ca7fcfd 100644 --- a/erts/emulator/beam/jit/beam_jit_common.cpp +++ b/erts/emulator/beam/jit/beam_jit_common.cpp @@ -664,11 +664,7 @@ Eterm beam_jit_bs_init_bits(Process *c_p, Binary *bptr; ProcBin *pb; - test_bin_vheap(c_p, - reg, - num_bytes / sizeof(Eterm), - alloc, - Live); + test_bin_vheap(c_p, reg, num_bytes / sizeof(Eterm), alloc, Live); /* * Allocate the binary struct itself. @@ -742,6 +738,111 @@ Eterm beam_jit_bs_get_integer(Process *c_p, return erts_bs_get_integer_2(c_p, size, flags, mb); } +void beam_jit_bs_construct_fail_info(Process *c_p, + Uint packed_error_info, + Eterm bad_value) { + Eterm *hp = HAlloc(c_p, Sint(MAP3_SZ + 3 + 1)); + Eterm cause_tuple; + Eterm error_info; + Uint segment = BSC_GET_SEGMENT(packed_error_info); + Uint op = BSC_GET_OP(packed_error_info); + Uint info = BSC_GET_INFO(packed_error_info); + Uint reason = BSC_GET_REASON(packed_error_info); + Eterm Op = am_none; + Eterm Info = am_none; + + switch (op) { + case BSC_OP_BINARY: + Op = am_binary; + break; + case BSC_OP_FLOAT: + Op = am_float; + break; + case BSC_OP_INTEGER: + Op = am_integer; + break; + case BSC_OP_UTF8: + Op = am_utf8; + break; + case BSC_OP_UTF16: + Op = am_utf16; + break; + case BSC_OP_UTF32: + Op = am_utf32; + break; + } + + switch (reason) { + case BSC_REASON_FREASON: + reason = c_p->freason; + break; + case BSC_REASON_BADARG: + reason = BADARG; + break; + case BSC_REASON_SYSTEM_LIMIT: + reason = SYSTEM_LIMIT; + break; + case BSC_REASON_DEPENDS: + if ((is_small(bad_value) && signed_val(bad_value) >= 0) || + (is_big(bad_value) && !big_sign(bad_value))) { + reason = SYSTEM_LIMIT; + } else { + reason = BADARG; + } + break; + } + + switch (info) { + case BSC_INFO_FVALUE: + Info = c_p->fvalue; + break; + case BSC_INFO_TYPE: + Info = am_type; + break; + case BSC_INFO_SIZE: + Info = am_size; + break; + case BSC_INFO_NEGATIVE: + Info = am_negative_size; + break; + case BSC_INFO_UNIT: + Info = am_unit; + break; + case BSC_INFO_DEPENDS: + if (reason == SYSTEM_LIMIT) { + Info = am_size; + } else if (is_small(bad_value) || is_big(bad_value)) { + Info = am_negative_size; + } else { + Info = am_size; + } + break; + } + + cause_tuple = TUPLE3(hp, make_small(segment), Op, Info); + hp += 4; + error_info = MAP3(hp, + am_cause, + cause_tuple, + am_function, + am_format_bs_fail, + am_module, + am_erl_erts_errors); + c_p->fvalue = error_info; + c_p->freason = reason | EXF_HAS_EXT_INFO; +} + +Sint beam_jit_bs_bit_size(Eterm term) { + if (is_binary(term)) { + ASSERT(sizeof(Uint) == 8); /* Only support 64-bit machines. */ + Uint byte_size = binary_size(term); + return (Sint)((byte_size << 3) + binary_bitsize(term)); + } + + /* Signal error */ + return (Sint)-1; +} + ErtsMessage *beam_jit_decode_dist(Process *c_p, ErtsMessage *msgp) { if (!erts_proc_sig_decode_dist(c_p, ERTS_PROC_LOCK_MAIN, msgp, 0)) { /* diff --git a/erts/emulator/beam/jit/beam_jit_common.hpp b/erts/emulator/beam/jit/beam_jit_common.hpp index 8db8c95eac..7d32f71dcd 100644 --- a/erts/emulator/beam/jit/beam_jit_common.hpp +++ b/erts/emulator/beam/jit/beam_jit_common.hpp @@ -235,6 +235,33 @@ public: } }; +static const int BSC_OP_BINARY = 0; +static const int BSC_OP_FLOAT = 1; +static const int BSC_OP_INTEGER = 2; +static const int BSC_OP_UTF8 = 3; +static const int BSC_OP_UTF16 = 4; +static const int BSC_OP_UTF32 = 5; + +static const int BSC_INFO_FVALUE = 0; +static const int BSC_INFO_TYPE = 1; +static const int BSC_INFO_SIZE = 2; +static const int BSC_INFO_NEGATIVE = 3; +static const int BSC_INFO_UNIT = 4; +static const int BSC_INFO_DEPENDS = 5; + +static const int BSC_REASON_FREASON = 0; +static const int BSC_REASON_BADARG = 1; +static const int BSC_REASON_SYSTEM_LIMIT = 2; +static const int BSC_REASON_DEPENDS = 3; + +#define BSC_SET_SEGMENT_OP(Seg, Op) (((Seg) << 8) | ((Op) << 5)) +#define BSC_SET_REASON_INFO(Reason, Info) (((Info) << 2) | Reason) + +#define BSC_GET_SEGMENT(PackedInfo) ((PackedInfo) >> 8) +#define BSC_GET_OP(PackedInfo) (((PackedInfo) >> 5) & 0x007) +#define BSC_GET_INFO(PackedInfo) (((PackedInfo) >> 2) & 0x07); +#define BSC_GET_REASON(PackedInfo) ((PackedInfo)&0x03); + /* ** */ #if defined(DEBUG) && defined(JIT_HARD_DEBUG) @@ -292,6 +319,11 @@ Sint beam_jit_remove_message(Process *c_p, Eterm *E, Uint32 active_code_ix); +void beam_jit_bs_construct_fail_info(Process *c_p, + Uint packed_error_info, + Eterm value); +Sint beam_jit_bs_bit_size(Eterm term); + void beam_jit_take_receive_lock(Process *c_p); void beam_jit_wait_locked(Process *c_p, ErtsCodePtr cp); void beam_jit_wait_unlocked(Process *c_p, ErtsCodePtr cp); diff --git a/erts/emulator/beam/jit/x86/beam_asm.hpp b/erts/emulator/beam/jit/x86/beam_asm.hpp index 5e83c7e83c..44a41ca6d5 100644 --- a/erts/emulator/beam/jit/x86/beam_asm.hpp +++ b/erts/emulator/beam/jit/x86/beam_asm.hpp @@ -874,6 +874,7 @@ class BeamGlobalAssembler : public BeamAssembler { _(bif_element_shared) \ _(bif_export_trap) \ _(bs_add_shared) \ + _(bs_create_bin_error_shared) \ _(bs_size_check_shared) \ _(bs_fixed_integer_shared) \ _(bs_get_tail_shared) \ diff --git a/erts/emulator/beam/jit/x86/generators.tab b/erts/emulator/beam/jit/x86/generators.tab index 8763e34a47..85384ad5bc 100644 --- a/erts/emulator/beam/jit/x86/generators.tab +++ b/erts/emulator/beam/jit/x86/generators.tab @@ -563,3 +563,59 @@ gen.func_end(Func_Label, Entry_Label) { return op; } + +gen.create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) { + BeamOp* op; + int fixed_args; + int i; + + $NewBeamOp(S, op); + $BeamOpNameArity(op, i_bs_create_bin, 4); + fixed_args = op->arity; + $BeamOpArity(op, (N.val + fixed_args)); + + op->a[0] = Fail; + op->a[1] = Alloc; + op->a[2] = Live; + op->a[3] = Dst; + + for (i = 0; i < N.val; i += 6) { + BeamOpArg Flags; + Uint flags = 0; + + /* Copy all but flags. */ + op->a[i+fixed_args+0] = Segments[i+0]; + op->a[i+fixed_args+1] = Segments[i+1]; + op->a[i+fixed_args+2] = Segments[i+2]; + op->a[i+fixed_args+4] = Segments[i+4]; + op->a[i+fixed_args+5] = Segments[i+5]; + + /* Translate flags. */ + Flags = Segments[i+3]; /* Flags */ + if (Flags.type != TAG_n) { + 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; + } + term = CDR(consp); + } + ASSERT(is_nil(term)); + } + } + Flags.type = TAG_u; + Flags.val = flags; + $NativeEndian(Flags); + op->a[i+fixed_args+3] = Flags; + } + + return op; +} diff --git a/erts/emulator/beam/jit/x86/instr_bs.cpp b/erts/emulator/beam/jit/x86/instr_bs.cpp index 2d68db3aae..845e92fd2d 100644 --- a/erts/emulator/beam/jit/x86/instr_bs.cpp +++ b/erts/emulator/beam/jit/x86/instr_bs.cpp @@ -1559,3 +1559,537 @@ void BeamModuleAssembler::emit_bs_init_writable() { emit_leave_runtime(); } + +void BeamGlobalAssembler::emit_bs_create_bin_error_shared() { + emit_enter_runtime(); + + a.mov(ARG3, ARG1); + a.mov(ARG2, ARG4); + a.mov(ARG1, c_p); + runtime_call<3>(beam_jit_bs_construct_fail_info); + + emit_leave_runtime(); + + /* We must align the return address to make it a proper tagged CP, in case + * we were called with `safe_fragment_call`. This is safe because we will + * never actually return to the return address. */ + a.pop(ARG2); + a.and_(ARG2, imm(-8)); + +#ifdef NATIVE_ERLANG_STACK + a.push(ARG2); + + if (erts_frame_layout == ERTS_FRAME_LAYOUT_FP_RA) { +# ifdef ERLANG_FRAME_POINTERS + a.push(frame_pointer); +# endif + } else { + ASSERT(erts_frame_layout == ERTS_FRAME_LAYOUT_RA); + } +#endif + + mov_imm(ARG4, nullptr); + a.jmp(labels[raise_exception_shared]); +} + +struct BscSegment { + BscSegment() + : src(ArgVal(ArgVal::Immediate, NIL)), + size(ArgVal(ArgVal::Immediate, NIL)), effectiveSize(-1){}; + + Eterm type; + Uint unit; + Uint flags; + ArgVal src; + ArgVal size; + + Uint error_info; + Sint effectiveSize; +}; + +void BeamModuleAssembler::emit_i_bs_create_bin(const ArgVal &Fail, + const ArgVal &Alloc, + const ArgVal &Live0, + const ArgVal &Dst, + const Span &args) { + Uint num_bits = 0; + std::size_t n = args.size(); + std::vector segments; + Label error = a.newLabel(); + Label past_error = a.newLabel(); + ArgVal Live = Live0; + x86::Gp sizeReg; + + /* + * Collect information about each segment and calculate sizes of + * fixed segments. + */ + for (std::size_t i = 0; i < n; i += 6) { + BscSegment seg; + Uint bsc_op; + Uint bsc_segment; + + ASSERT(args[i].isImmed()); + ASSERT(args[i + 1].getType() == TAG_u); + ASSERT(args[i + 2].getType() == TAG_u); + ASSERT(args[i + 3].getType() == TAG_u); + seg.type = args[i].getValue(); + bsc_segment = args[i + 1].getValue(); + seg.unit = args[i + 2].getValue(); + seg.flags = args[i + 3].getValue(); + seg.src = args[i + 4]; + seg.size = args[i + 5]; + + switch (seg.type) { + case am_float: + bsc_op = BSC_OP_FLOAT; + break; + case am_integer: + bsc_op = BSC_OP_INTEGER; + break; + case am_utf8: + bsc_op = BSC_OP_UTF8; + break; + case am_utf16: + bsc_op = BSC_OP_UTF16; + break; + case am_utf32: + bsc_op = BSC_OP_UTF32; + break; + default: + bsc_op = BSC_OP_BINARY; + break; + } + + /* + * Save segment number and operation for use in extended + * error information. + */ + seg.error_info = BSC_SET_SEGMENT_OP(bsc_segment, bsc_op); + + /* + * As soon as we have entered runtime mode, Y registers can no + * longer be accessed in the usual way. Therefore, if the source + * and/or size are in Y register, copy them to X registers. + */ + if (seg.src.getType() == TAG_y) { + ArgVal reg = ArgVal(TAG_x, Live.getValue()); + mov_arg(reg, seg.src); + Live.val = Live.getValue() + 1; + seg.src = reg; + } + + if (seg.size.getType() == TAG_y) { + ArgVal reg = ArgVal(TAG_x, Live.getValue()); + mov_arg(reg, seg.size); + Live.val = Live.getValue() + 1; + seg.size = reg; + } + + if (seg.size.isImmed() && seg.unit != 0) { + Eterm size = seg.size.getValue(); + if (is_small(size)) { + Uint unsigned_size = unsigned_val(size); + if ((unsigned_size >> (sizeof(Eterm) - 1) * 8) == 0) { + /* This multiplication cannot overflow. */ + Uint seg_size = seg.unit * unsigned_size; + seg.effectiveSize = seg_size; + num_bits += seg_size; + } + } + } + + if (seg.effectiveSize < 0 && seg.type != am_append && + seg.type != am_private_append) { + sizeReg = FCALLS; + } + + segments.insert(segments.end(), seg); + } + + emit_enter_runtime(); + + a.short_().jmp(past_error); + a.bind(error); + { + /* + * ARG4 = packed error information + * ARG1 = optional bad size value; only valid if BSC_REASON_DEPENDS is + * set in ARG4 + */ + comment("handle error"); + emit_leave_runtime(); + if (Fail.getValue() != 0) { + a.jmp(labels[Fail.getValue()]); + } else { + safe_fragment_call(ga->get_bs_create_bin_error_shared()); + } + } + + a.bind(past_error); + + /* We count the total number of bits in an unsigned integer. To + * avoid having to check for overflow when adding to the counter, + * we ensure that the signed size of each segment fits in a + * word. */ + if (sizeReg.isValid()) { + comment("calculate sizes"); + mov_imm(sizeReg, num_bits); + } + + /* Generate code for calculating the size of the binary to be + * created. */ + for (auto seg : segments) { + if (seg.effectiveSize >= 0) { + continue; + } + + if (seg.type == am_append || seg.type == am_private_append) { + continue; + } + + if (seg.size.isImmed() && seg.size.getValue() == am_all && + seg.type == am_binary) { + comment("size of an entire binary"); + mov_arg(ARG1, seg.src); + runtime_call<1>(beam_jit_bs_bit_size); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + a.test(RET, RET); + a.js(error); + a.add(sizeReg, RET); + } else if (seg.unit != 0) { + comment("size binary/integer/float/string"); + mov_arg(ARG1, seg.size); + a.mov(RETd, ARG1d); + a.and_(RETb, imm(_TAG_IMMED1_MASK)); + a.cmp(RETb, imm(_TAG_IMMED1_SMALL)); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_DEPENDS, + BSC_INFO_DEPENDS)); + } + a.jne(error); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | + BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_NEGATIVE)); + } + a.sar(ARG1, imm(_TAG_IMMED1_SIZE)); + a.js(error); + if (seg.unit == 1) { + a.mov(RET, ARG1); + } else { + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | + BSC_SET_REASON_INFO(BSC_REASON_SYSTEM_LIMIT, + BSC_INFO_SIZE)); + } + a.imul(RET, ARG1, imm(seg.unit)); + a.jo(error); + } + a.add(sizeReg, RET); + } else { + switch (seg.type) { + case am_utf8: { + Label next = a.newLabel(); + + comment("size utf8"); + mov_arg(ARG1, seg.src); + + mov_imm(RET, 0); + a.mov(RETb, imm(1 * 8)); + a.cmp(ARG1, imm(make_small(0x80UL))); + a.short_().jl(next); + + a.mov(RETb, imm(2 * 8)); + a.cmp(ARG1, imm(make_small(0x800UL))); + a.short_().jl(next); + + a.mov(RETb, imm(3 * 8)); + a.cmp(ARG1, imm(make_small(0x10000UL))); + a.short_().jl(next); + + a.mov(RETb, imm(4 * 8)); + + a.bind(next); + a.add(sizeReg, RET); + break; + } + case am_utf16: { + mov_arg(ARG1, seg.src); + mov_imm(RET, 2 * 8); + mov_imm(ARG2, 4 * 8); + a.cmp(ARG1, imm(make_small(0x10000UL))); + a.cmovae(RET, ARG2); + a.add(sizeReg, RET); + break; + } + case am_utf32: { + Label next = a.newLabel(); + + mov_arg(ARG1, seg.src); + + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | + BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + + a.add(sizeReg, imm(4 * 8)); + + a.mov(RETd, ARG1d); + a.and_(RETb, imm(_TAG_IMMED1_MASK)); + a.cmp(RETb, imm(_TAG_IMMED1_SMALL)); + a.jne(error); + + a.cmp(ARG1, imm(make_small(0xD800UL))); + a.short_().jb(next); + a.cmp(ARG1, imm(make_small(0xDFFFUL))); + a.jbe(error); + a.cmp(ARG1, imm(make_small(0x10FFFFUL))); + a.ja(error); + + a.bind(next); + break; + } + default: + ASSERT(0); + } + } + } + + /* Allocate the binary. */ + if (segments[0].type == am_append) { + BscSegment seg = segments[0]; + comment("append to binary"); + mov_arg(ARG3, Live); + if (sizeReg.isValid()) { + a.mov(ARG4, sizeReg); + } else { + mov_imm(ARG4, num_bits); + } + mov_arg(ARG5, Alloc); + mov_imm(ARG6, seg.unit); + mov_arg(ArgVal(ArgVal::XReg, Live.getValue()), seg.src); + a.mov(ARG1, c_p); + load_x_reg_array(ARG2); + runtime_call<6>(erts_bs_append_checked); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_FVALUE)); + } + emit_test_the_non_value(RET); + a.je(error); + } else if (segments[0].type == am_private_append) { + BscSegment seg = segments[0]; + comment("private append to binary"); + if (Alloc.getValue() != 0) { + emit_gc_test(ArgVal(ArgVal::Word, 0), Alloc, Live); + } + mov_arg(ARG2, seg.src); + if (sizeReg.isValid()) { + a.mov(ARG3, sizeReg); + } else { + mov_imm(ARG3, num_bits); + } + a.mov(ARG4, seg.unit); + a.mov(ARG1, c_p); + runtime_call<4>(erts_bs_private_append_checked); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_FREASON, + BSC_INFO_FVALUE)); + } + emit_test_the_non_value(RET); + a.je(error); + } else { + comment("allocate binary"); + mov_arg(ARG5, Alloc); + mov_arg(ARG6, Live); + load_erl_bits_state(ARG3); + load_x_reg_array(ARG2); + a.mov(ARG1, c_p); + if (sizeReg.isValid()) { + comment("(size in bits)"); + a.mov(ARG4, sizeReg); + runtime_call<6>(beam_jit_bs_init_bits); + } else if (num_bits % 8 == 0) { + comment("(size in bytes)"); + mov_imm(ARG4, num_bits / 8); + runtime_call<6>(beam_jit_bs_init); + } else { + mov_imm(ARG4, num_bits); + runtime_call<6>(beam_jit_bs_init_bits); + } + } + a.mov(TMP_MEM1q, RET); + + /* Build each segment of the binary. */ + for (auto seg : segments) { + switch (seg.type) { + case am_append: + case am_private_append: + break; + case am_binary: { + Uint error_info; + + comment("construct a binary segment"); + if (seg.effectiveSize >= 0) { + /* The segment has a literal size. */ + mov_imm(ARG3, seg.effectiveSize); + mov_arg(ARG2, seg.src); + a.mov(ARG1, c_p); + runtime_call<3>(erts_new_bs_put_binary); + error_info = + BSC_SET_REASON_INFO(BSC_REASON_BADARG, BSC_INFO_FVALUE); + } else if (seg.size.isImmed() && seg.size.getValue() == am_all) { + /* Include the entire binary/bitstring in the + * resulting binary. */ + a.mov(ARG3, seg.unit); + mov_arg(ARG2, seg.src); + a.mov(ARG1, c_p); + runtime_call<3>(erts_new_bs_put_binary_all); + error_info = + BSC_SET_REASON_INFO(BSC_REASON_BADARG, BSC_INFO_UNIT); + } else { + /* The size is a variable. We have verified that + * the value is a non-negative small in the + * appropriate range. Multiply the size with the + * unit. */ + mov_arg(ARG3, seg.size); + a.sar(ARG3, imm(_TAG_IMMED1_SIZE)); + if (seg.unit != 1) { + mov_imm(RET, seg.unit); + a.mul(ARG3); /* CLOBBERS RDX = ARG3! */ + a.mov(ARG3, RET); + } + mov_arg(ARG2, seg.src); + a.mov(ARG1, c_p); + runtime_call<3>(erts_new_bs_put_binary); + error_info = + BSC_SET_REASON_INFO(BSC_REASON_BADARG, BSC_INFO_FVALUE); + } + a.test(RET, RET); + if (Fail.getValue() == 0) { + mov_imm(ARG4, seg.error_info | error_info); + } + a.je(error); + break; + } + case am_float: + comment("construct float segment"); + if (seg.effectiveSize >= 0) { + mov_imm(ARG3, seg.effectiveSize); + } else { + mov_arg(ARG3, seg.size); + a.sar(ARG3, imm(_TAG_IMMED1_SIZE)); + if (seg.unit != 1) { + mov_imm(RET, seg.unit); + a.mul(ARG3); /* CLOBBERS RDX = ARG3! */ + a.mov(ARG3, RET); + } + } + mov_arg(ARG2, seg.src); + mov_imm(ARG4, seg.flags); + a.mov(ARG1, c_p); + runtime_call<4>(erts_new_bs_put_float); + a.test(RET, RET); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_FVALUE)); + } + a.je(error); + break; + case am_integer: + comment("construct integer segment"); + if (seg.effectiveSize >= 0) { + mov_imm(ARG3, seg.effectiveSize); + } else { + mov_arg(ARG3, seg.size); + a.sar(ARG3, imm(_TAG_IMMED1_SIZE)); + if (seg.unit != 1) { + mov_imm(RET, seg.unit); + a.mul(ARG3); /* CLOBBERS RDX = ARG3! */ + a.mov(ARG3, RET); + } + } + mov_arg(ARG2, seg.src); + mov_imm(ARG4, seg.flags); + load_erl_bits_state(ARG1); + runtime_call<4>(erts_new_bs_put_integer); + a.test(RET, RET); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + a.je(error); + break; + case am_string: + comment("insert string"); + ASSERT(seg.effectiveSize >= 0); + mov_imm(ARG3, seg.effectiveSize / 8); + make_move_patch(ARG2, strings, seg.src.getValue()); + load_erl_bits_state(ARG1); + runtime_call<3>(erts_new_bs_put_string); + break; + case am_utf8: + mov_arg(ARG2, seg.src); + load_erl_bits_state(ARG1); + runtime_call<2>(erts_bs_put_utf8); + a.test(RET, RET); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + a.je(error); + break; + case am_utf16: + mov_arg(ARG2, seg.src); + a.mov(ARG3, seg.flags); + load_erl_bits_state(ARG1); + runtime_call<3>(erts_bs_put_utf16); + a.test(RET, RET); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + a.je(error); + break; + case am_utf32: + mov_arg(ARG2, seg.src); + mov_imm(ARG3, 4 * 8); + a.mov(ARG4, seg.flags); + load_erl_bits_state(ARG1); + runtime_call<4>(erts_new_bs_put_integer); + a.test(RET, RET); + if (Fail.getValue() == 0) { + mov_imm(ARG4, + seg.error_info | BSC_SET_REASON_INFO(BSC_REASON_BADARG, + BSC_INFO_TYPE)); + } + a.je(error); + break; + default: + ASSERT(0); + break; + } + } + + comment("done"); + emit_leave_runtime(); + a.mov(RET, TMP_MEM1q); + mov_arg(Dst, RET); +} diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab index 8109821a1b..00f56856ab 100644 --- a/erts/emulator/beam/jit/x86/ops.tab +++ b/erts/emulator/beam/jit/x86/ops.tab @@ -878,9 +878,19 @@ bs_skip_utf32 Fail=f Ms=xy Live=u Flags=u => \ i_bs_validate_unicode_retract j s S %hot -# -# Constructing binaries -# +# ================================================================ +# New binary construction (OTP 25). +# ================================================================ + +bs_create_bin Fail=j Alloc=u Live=u Unit=u Dst=xy N=u Segments=* => \ + create_bin(Fail, Alloc, Live, Unit, Dst, N, Segments) + +i_bs_create_bin j I t d * + +# ================================================================ +# Old instruction for constructing binaries (up to OTP 24). +# ================================================================ + %warm bs_init2 Fail Sz Words Regs Flags Dst | binary_too_big(Sz) => system_limit Fail diff --git a/erts/emulator/test/bs_construct_SUITE.erl b/erts/emulator/test/bs_construct_SUITE.erl index 81107c5644..5f3cea1bcb 100644 --- a/erts/emulator/test/bs_construct_SUITE.erl +++ b/erts/emulator/test/bs_construct_SUITE.erl @@ -28,7 +28,7 @@ huge_float_field/1, system_limit/1, badarg/1, copy_writable_binary/1, kostis/1, dynamic/1, bs_add/1, otp_7422/1, zero_width/1, bad_append/1, bs_append_overflow/1, - reductions/1, fp16/1]). + reductions/1, fp16/1, error_info/1]). -include_lib("common_test/include/ct.hrl"). @@ -41,7 +41,7 @@ all() -> in_guard, mem_leak, coerce_to_float, bjorn, append_empty_is_same, huge_float_field, system_limit, badarg, copy_writable_binary, kostis, dynamic, bs_add, otp_7422, zero_width, - bad_append, bs_append_overflow, reductions, fp16]. + bad_append, bs_append_overflow, reductions, fp16, error_info]. init_per_suite(Config) -> Config. @@ -935,9 +935,6 @@ reds_at_least(N, Fun) -> Diff -> ct:fail({expected,N,got,Diff}) end. - -id(I) -> I. - memsize() -> application:ensure_all_started(os_mon), {Tot,_Used,_} = memsup:get_memory_data(), @@ -984,3 +981,111 @@ fp16(_Config) -> ?FP16(16#4000, 2), ?FP16(16#4000, 2.0), ok. + +-define(ERROR_INFO(Expr), + fun() -> + try Expr of + _ -> + error(should_fail) + catch + error:Reason:Stk -> + error_info_verify(Reason, Stk, ??Expr) + end + end()). + +error_info(_Config) -> + Atom = id(atom), + NegativeSize = id(-1), + HugeNegativeSize = id(-1 bsl 64), + Binary = id(<<"abc">>), + HugeBig = id(1 bsl 1500), + + {badarg, {1,binary,type}} = ?ERROR_INFO(<>), + {badarg, {2,binary,type}} = ?ERROR_INFO(<>), + {badarg, {3,binary,type}} = ?ERROR_INFO(<<1:32, Binary/binary, Atom/binary>>), + {badarg, {4,binary,type}} = ?ERROR_INFO(<<1:32, "xyz", Binary/binary, Atom/binary>>), + + {badarg, {1,integer,type}} = ?ERROR_INFO(<>), + {badarg, {1,integer,size}} = ?ERROR_INFO(<<42:Atom>>), + {badarg, {1,integer,negative_size}} = ?ERROR_INFO(<<42:NegativeSize>>), + {badarg, {1,integer,negative_size}} = ?ERROR_INFO(<<42:HugeNegativeSize>>), + {system_limit, {1,integer,size}} = ?ERROR_INFO(<<42:(1 bsl 58)/unit:255>>), + {system_limit, {1,integer,size}} = ?ERROR_INFO(<<42:(1 bsl 60)/unit:8>>), + {system_limit, {1,integer,size}} = ?ERROR_INFO(<<42:(1 bsl 64)>>), + + {badarg, {1,binary,type}} = ?ERROR_INFO(<>), + {badarg, {1,binary,size}} = ?ERROR_INFO(<>), + {badarg, {1,binary,negative_size}} = ?ERROR_INFO(<>), + {badarg, {1,binary,negative_size}} = ?ERROR_INFO(<>), + {system_limit, {1,binary,size}} = ?ERROR_INFO(<>), + {badarg, {1,binary,short}} = ?ERROR_INFO(<>), + {badarg, {1,binary,type}} = ?ERROR_INFO(<>), + + {badarg, {1,float,type}} = ?ERROR_INFO(<>), + {badarg, {1,float,size}} = ?ERROR_INFO(<>), + {badarg, {1,float,negative_size}} = ?ERROR_INFO(<<42.0:NegativeSize/float>>), + {badarg, {1,float,negative_size}} = ?ERROR_INFO(<<42.0:HugeNegativeSize/float>>), + {badarg, {1,float,invalid}} = ?ERROR_INFO(<<42.0:(id(1))/float>>), + {badarg, {1,float,no_float}} = ?ERROR_INFO(<>), + {badarg, {1,float,no_float}} = ?ERROR_INFO(<>), + {system_limit, {1,float,size}} = ?ERROR_INFO(<<42.0:(id(1 bsl 64))/float>>), + + {badarg, {1,utf8,type}} = ?ERROR_INFO(<>), + {badarg, {1,utf16,type}} = ?ERROR_INFO(<>), + {badarg, {1,utf32,type}} = ?ERROR_INFO(<>), + + Bin = id(<<>>), + Float = id(42.0), + + %% Attempt constructing a binary with total size 1^64 + 32. + + {system_limit, {1,integer,size}} = ?ERROR_INFO(<<0:((1 bsl 59)-1)/unit:32,0:64>>), + {system_limit, {1,integer,size}} = ?ERROR_INFO(<<0:((1 bsl 59)-1)/unit:32,(id(0)):64>>), + {system_limit, {1,integer,size}} = ?ERROR_INFO(<<0:(id((1 bsl 59)-1))/unit:32,0:64>>), + {system_limit, {1,integer,size}} = ?ERROR_INFO(<<0:(id((1 bsl 59)-1))/unit:32,(id(0)):64>>), + + {system_limit, {1,binary,size}} = ?ERROR_INFO(<>), + {system_limit, {1,binary,size}} = ?ERROR_INFO(<>), + {system_limit, {1,binary,size}} = ?ERROR_INFO(<>), + {system_limit, {1,binary,size}} = ?ERROR_INFO(<>), + + {system_limit, {1,float,size}} = ?ERROR_INFO(<>), + {system_limit, {1,float,size}} = ?ERROR_INFO(<>), + {system_limit, {1,float,size}} = ?ERROR_INFO(<>), + {system_limit, {1,float,size}} = ?ERROR_INFO(<>), + + %% Test a size exceeding 1^64, where the sign bit (bit 63) is not set. + 0 = ((((1 bsl 59)-1) * 33) bsr 63) band 1, %Assertion: The sign bit is not set. + + {system_limit, {1,integer,size}} = ?ERROR_INFO(<<0:((1 bsl 59)-1)/unit:33>>), + {system_limit, {1,integer,size}} = ?ERROR_INFO(<<0:((1 bsl 59)-1)/unit:33>>), + {system_limit, {1,integer,size}} = ?ERROR_INFO(<<0:(id((1 bsl 59)-1))/unit:33>>), + {system_limit, {1,integer,size}} = ?ERROR_INFO(<<0:(id((1 bsl 59)-1))/unit:33>>), + + {system_limit, {1,binary,size}} = ?ERROR_INFO(<>), + {system_limit, {1,binary,size}} = ?ERROR_INFO(<>), + {system_limit, {1,binary,size}} = ?ERROR_INFO(<>), + {system_limit, {1,binary,size}} = ?ERROR_INFO(<>), + + {system_limit, {1,float,size}} = ?ERROR_INFO(<>), + {system_limit, {1,float,size}} = ?ERROR_INFO(<>), + {system_limit, {1,float,size}} = ?ERROR_INFO(<>), + {system_limit, {1,float,size}} = ?ERROR_INFO(<>), + + ok. + +error_info_verify(Reason, Stk, Expr) -> + [{?MODULE, _, _, Info}|_] = Stk, + {error_info, ErrorInfo} = lists:keyfind(error_info, 1, Info), + #{cause := Cause, module := Module, function := Function} = ErrorInfo, + Result = Module:Function(Reason, Stk), + #{general := String} = Result, + true = is_binary(String), + io:format("~ts: ~ts\n", [Expr,String]), + {Reason, Cause}. + +%%% +%%% Common utilities. +%%% + +id(I) -> I. diff --git a/erts/emulator/test/exception_SUITE.erl b/erts/emulator/test/exception_SUITE.erl index 6744ee895b..89f34b0a47 100644 --- a/erts/emulator/test/exception_SUITE.erl +++ b/erts/emulator/test/exception_SUITE.erl @@ -1485,7 +1485,7 @@ line_numbers(Config) when is_list(Config) -> <<0,0>> = build_binary1(16), {'EXIT',{badarg, [{?MODULE,build_binary1,1, - [{file,"bit_syntax.erl"},{line,72503}]}, + [{file,"bit_syntax.erl"},{line,72503},{error_info,_}]}, {?MODULE,line_numbers,1, [{file,ModFile},{line,_}]}|_]}} = (catch build_binary1(bad_size)), @@ -1493,21 +1493,20 @@ line_numbers(Config) when is_list(Config) -> <<7,1,2,3>> = build_binary2(8, <<1,2,3>>), {'EXIT',{badarg, [{?MODULE,build_binary2,2, - [{file,"bit_syntax.erl"},{line,72507}]}, + [{file,"bit_syntax.erl"},{line,72507},{error_info,_}]}, {?MODULE,line_numbers,1, [{file,ModFile},{line,_}]}|_]}} = (catch build_binary2(bad_size, <<>>)), {'EXIT',{badarg, - [{erlang,bit_size,[bad_binary],[{error_info,_}]}, - {?MODULE,build_binary2,2, - [{file,"bit_syntax.erl"},{line,72507}]}, + [{?MODULE,build_binary2,2, + [{file,"bit_syntax.erl"},{line,72507},{error_info,_}]}, {?MODULE,line_numbers,1, [{file,ModFile},{line,_}]}|_]}} = (catch build_binary2(8, bad_binary)), <<"abc",357:16>> = build_binary3(<<"abc">>), {'EXIT',{badarg,[{?MODULE,build_binary3,1, - [{file,"bit_syntax.erl"},{line,72511}]}, + [{file,"bit_syntax.erl"},{line,72511},{error_info,_}]}, {?MODULE,line_numbers,1, [{file,ModFile},{line,_}]}|_]}} = (catch build_binary3(no_binary)), diff --git a/erts/emulator/utils/beam_makeops b/erts/emulator/utils/beam_makeops index 7d2992044d..3f98aba6aa 100755 --- a/erts/emulator/utils/beam_makeops +++ b/erts/emulator/utils/beam_makeops @@ -1042,6 +1042,7 @@ sub emulator_output { print '#include "erl_term.h"', "\n"; print '#include "erl_map.h"', "\n"; print '#include "big.h"', "\n"; + print '#include "erl_bits.h"', "\n"; print '#include "beam_transform_helpers.h"', "\n"; print "\n"; gen_tr_code('pred.'); diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl index 7d1cf90c8e..c5d26146e8 100644 --- a/lib/compiler/src/beam_asm.erl +++ b/lib/compiler/src/beam_asm.erl @@ -391,14 +391,9 @@ make_op({Name,Arg1,Arg2,Arg3}, Dict) -> encode_op(Name, [Arg1,Arg2,Arg3], Dict); make_op({Name,Arg1,Arg2,Arg3,Arg4}, Dict) -> encode_op(Name, [Arg1,Arg2,Arg3,Arg4], Dict); -make_op({Name,Arg1,Arg2,Arg3,Arg4,Arg5}, Dict) -> - encode_op(Name, [Arg1,Arg2,Arg3,Arg4,Arg5], Dict); -make_op({Name,Arg1,Arg2,Arg3,Arg4,Arg5,Arg6}, Dict) -> - encode_op(Name, [Arg1,Arg2,Arg3,Arg4,Arg5,Arg6], Dict); -%% make_op({Name,Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7}, Dict) -> -%% encode_op(Name, [Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7], Dict); -make_op({Name,Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7,Arg8}, Dict) -> - encode_op(Name, [Arg1,Arg2,Arg3,Arg4,Arg5,Arg6,Arg7,Arg8], Dict); +make_op(Instr, Dict) when tuple_size(Instr) >= 6 -> + [Name|Args] = tuple_to_list(Instr), + encode_op(Name, Args, Dict); make_op(Op, Dict) when is_atom(Op) -> encode_op(Op, [], Dict). diff --git a/lib/compiler/src/beam_clean.erl b/lib/compiler/src/beam_clean.erl index 30d6670bf6..fb36f2375f 100644 --- a/lib/compiler/src/beam_clean.erl +++ b/lib/compiler/src/beam_clean.erl @@ -186,9 +186,7 @@ remove_lines_block([]) -> []. %%% fix_bs_create_bin(Fs, Opts) -> - %% Force translation to old instructions before we have actually - %% implemented the `bs_create_bin` instruction. - case true orelse proplists:get_bool(no_bs_create_bin, Opts) of + case proplists:get_bool(no_bs_create_bin, Opts) of false -> Fs; true -> fold_functions(fun fix_bs_create_bin/1, Fs) end. diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl index 40359c1c54..d9675713ae 100644 --- a/lib/compiler/src/beam_disasm.erl +++ b/lib/compiler/src/beam_disasm.erl @@ -379,6 +379,8 @@ disasm_instr(B, Bs, Atoms, Literals) -> disasm_make_fun3(Bs, Atoms, Literals); init_yregs -> disasm_init_yregs(Bs, Atoms, Literals); + bs_create_bin -> + disasm_bs_create_bin(Bs, Atoms, Literals); _ -> try decode_n_args(Arity, Bs, Atoms, Literals) of {Args, RestBs} -> @@ -443,6 +445,18 @@ disasm_init_yregs(Bs1, Atoms, Literals) -> {List, RestBs} = decode_n_args(Len, Bs3, Atoms, Literals), {{init_yregs, [{Z,U,List}]}, RestBs}. +disasm_bs_create_bin(Bs0, Atoms, Literals) -> + {A1, Bs1} = decode_arg(Bs0, Atoms, Literals), + {A2, Bs2} = decode_arg(Bs1, Atoms, Literals), + {A3, Bs3} = decode_arg(Bs2, Atoms, Literals), + {A4, Bs4} = decode_arg(Bs3, Atoms, Literals), + {A5, Bs5} = decode_arg(Bs4, Atoms, Literals), + {Z, Bs6} = decode_arg(Bs5, Atoms, Literals), + {U, Bs7} = decode_arg(Bs6, Atoms, Literals), + {u, Len} = U, + {List, RestBs} = decode_n_args(Len, Bs7, Atoms, Literals), + {{bs_create_bin, [{A1,A2,A3,A4,A5,Z,U,List}]}, RestBs}. + %%----------------------------------------------------------------------- %% decode_arg([Byte]) -> {Arg, [Byte]} %% @@ -1173,6 +1187,13 @@ resolve_inst({recv_marker_reserve,[Reg]},_,_,_) -> resolve_inst({recv_marker_use,[Reg]},_,_,_) -> {recv_marker_use,Reg}; +%% +%% OTP 25. +%% + +resolve_inst({bs_create_bin,Args},_,_,_) -> + io:format("~p\n", [Args]), + {bs_create_bin,Args}; %% %% Catches instructions that are not yet handled. diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab index 9c0fb13c84..a4b10acff2 100755 --- a/lib/compiler/src/genop.tab +++ b/lib/compiler/src/genop.tab @@ -647,3 +647,9 @@ BEAM_FORMAT_NUMBER=0 ## @doc Sets the current receive cursor to the marker associated with ## the given Reference. 176: recv_marker_use/1 + +# OTP 25 + +## @spec bs_create_bin Fail Alloc Live Unit Dst OpList +## @doc Builda a new binary using the binary syntax. +177: bs_create_bin/6 diff --git a/lib/kernel/src/erl_erts_errors.erl b/lib/kernel/src/erl_erts_errors.erl index 8b0768ac80..66fcd1bb0b 100644 --- a/lib/kernel/src/erl_erts_errors.erl +++ b/lib/kernel/src/erl_erts_errors.erl @@ -19,7 +19,7 @@ %% -module(erl_erts_errors). --export([format_error/2]). +-export([format_error/2, format_bs_fail/2]). -spec format_error(Reason, StackTrace) -> ErrorMap when Reason :: term(), @@ -43,6 +43,24 @@ format_error(Reason, [{M,F,As,Info}|_]) -> end, format_error_map(Res, 1, #{}). +-spec format_bs_fail(Reason, StackTrace) -> ErrorMap when + Reason :: term(), + StackTrace :: erlang:stacktrace(), + ErrorMap :: #{'general' => unicode:chardata()}. + +format_bs_fail(Reason, [{_,_,_,Info}|_]) -> + ErrorInfoMap = proplists:get_value(error_info, Info, #{}), + case ErrorInfoMap of + #{cause := {Segment,Type,Error}} -> + Str0 = do_format_bs_fail( Reason, Type, Error), + Str1 = io_lib:format("failed constructing binary: segment ~p, type '~ts': ~ts", + [Segment,Type,Str0]), + Str = iolist_to_binary(Str1), + #{general => Str}; + #{} -> + #{} + end. + format_atomics_error(new, [Size,Options], Reason, Cause) -> case Reason of system_limit -> @@ -1027,6 +1045,40 @@ format_erlang_error(whereis, [_], _) -> format_erlang_error(_, _, _) -> []. +do_format_bs_fail(system_limit, _Type, size) -> + <<"the size is too large">>; +do_format_bs_fail(badarg, Type, Info) -> + do_format_bs_fail(Type, Info). + +do_format_bs_fail(float, invalid) -> + <<"the size is not one of the supported sizes (16, 32, or 64)">>; +do_format_bs_fail(float, no_float) -> + <<"the integer is outside the range expressible as a float of the given size">>; +do_format_bs_fail(binary, unit) -> + <<"the size of the given binary/bitstring is not a multiple of the unit for the segment">>; +do_format_bs_fail(_Type, short) -> + <<"the given binary/bitstring is shorter than the size of the segment">>; +do_format_bs_fail(_Type, negative_size) -> + <<"the size is negative">>; +do_format_bs_fail(_Type, size) -> + <<"the size is not an integer">>; +do_format_bs_fail(Type, type) -> + <<"not a", + (case Type of + binary -> + <<" binary">>; + float -> + <<" float or an integer">>; + integer -> + <<"n integer">>; + _ -> + <<"n integer encodable as ", (atom_to_binary(Type))/binary>> + end)/binary>>. + +%%% +%%% Utility functions. +%%% + list_to_something(List, Error) -> try length(List) of _ -> -- cgit v1.2.1