// -*- c -*-
//
// %CopyrightBegin%
//
// Copyright Ericsson AB 2017-2018. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// %CopyrightEnd%
//
%if ARCH_64
BS_SAFE_MUL(A, B, Fail, Dst) {
Uint a = $A;
Uint b = $B;
Uint res;
#ifdef HAVE_OVERFLOW_CHECK_BUILTINS
if (__builtin_mul_overflow(a, b, &res)) {
$Fail;
}
#else
res = a * b;
if (res / b != a) {
$Fail;
}
#endif
$Dst = res;
}
BS_GET_FIELD_SIZE(Bits, Unit, Fail, Dst) {
if (is_small($Bits)) {
Uint uint_size;
Sint signed_size = signed_val($Bits);
if (signed_size < 0) {
$Fail;
}
uint_size = (Uint) signed_size;
$BS_SAFE_MUL(uint_size, $Unit, $Fail, $Dst);
} else {
/*
* On a 64-bit architecture, the size of any binary
* that would fit in the memory fits in a small.
*/
$Fail;
}
}
BS_GET_UNCHECKED_FIELD_SIZE(Bits, Unit, Fail, Dst) {
if (is_small($Bits)) {
Uint uint_size;
Sint signed_size = signed_val($Bits);
if (signed_size < 0) {
$Fail;
}
uint_size = (Uint) signed_size;
$Dst = uint_size * $Unit;
} else {
/*
* On a 64-bit architecture, the size of any binary
* that would fit in the memory fits in a small.
*/
$Fail;
}
}
%else
BS_SAFE_MUL(A, B, Fail, Dst) {
Uint64 res = (Uint64)($A) * (Uint64)($B);
if ((res >> (8*sizeof(Uint))) != 0) {
$Fail;
}
$Dst = res;
}
BS_GET_FIELD_SIZE(Bits, Unit, Fail, Dst) {
Sint signed_size;
Uint uint_size;
Uint temp_bits;
if (is_small($Bits)) {
signed_size = signed_val($Bits);
if (signed_size < 0) {
$Fail;
}
uint_size = (Uint) signed_size;
} else {
if (!term_to_Uint($Bits, &temp_bits)) {
$Fail;
}
uint_size = temp_bits;
}
$BS_SAFE_MUL(uint_size, $Unit, $Fail, $Dst);
}
BS_GET_UNCHECKED_FIELD_SIZE(Bits, Unit, Fail, Dst) {
Sint signed_size;
Uint uint_size;
Uint temp_bits;
if (is_small($Bits)) {
signed_size = signed_val($Bits);
if (signed_size < 0) {
$Fail;
}
uint_size = (Uint) signed_size;
} else {
if (!term_to_Uint($Bits, &temp_bits)) {
$Fail;
}
uint_size = temp_bits;
}
$Dst = uint_size * $Unit;
}
%endif
TEST_BIN_VHEAP(VNh, Nh, Live) {
Uint need = $Nh;
if (E - HTOP < need || MSO(c_p).overhead + $VNh >= BIN_VHEAP_SZ(c_p)) {
SWAPOUT;
PROCESS_MAIN_CHK_LOCKS(c_p);
FCALLS -= erts_garbage_collect_nobump(c_p, need, reg, $Live, FCALLS);
ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
PROCESS_MAIN_CHK_LOCKS(c_p);
SWAPIN;
}
HEAP_SPACE_VERIFIED(need);
}
i_bs_get_binary_all2 := i_bs_get_binary_all2.fetch.execute;
i_bs_get_binary_all2.head() {
Eterm context;
}
i_bs_get_binary_all2.fetch(Ctx) {
context = $Ctx;
}
i_bs_get_binary_all2.execute(Fail, Live, Unit, Dst) {
ErlBinMatchBuffer *_mb;
Eterm _result;
$GC_TEST_PRESERVE(ERL_SUB_BIN_SIZE, $Live, context);
_mb = ms_matchbuffer(context);
if (((_mb->size - _mb->offset) % $Unit) == 0) {
LIGHT_SWAPOUT;
_result = erts_bs_get_binary_all_2(c_p, _mb);
LIGHT_SWAPIN;
HEAP_SPACE_VERIFIED(0);
ASSERT(is_value(_result));
$REFRESH_GEN_DEST();
$Dst = _result;
} else {
HEAP_SPACE_VERIFIED(0);
$FAIL($Fail);
}
}
i_bs_get_binary2 := i_bs_get_binary2.fetch.execute;
i_bs_get_binary2.head() {
Eterm context;
}
i_bs_get_binary2.fetch(Ctx) {
context = $Ctx;
}
i_bs_get_binary2.execute(Fail, Live, Sz, Flags, Dst) {
ErlBinMatchBuffer *_mb;
Eterm _result;
Uint _size;
$BS_GET_FIELD_SIZE($Sz, (($Flags) >> 3), $FAIL($Fail), _size);
$GC_TEST_PRESERVE(ERL_SUB_BIN_SIZE, $Live, context);
_mb = ms_matchbuffer(context);
LIGHT_SWAPOUT;
_result = erts_bs_get_binary_2(c_p, _size, $Flags, _mb);
LIGHT_SWAPIN;
HEAP_SPACE_VERIFIED(0);
if (is_non_value(_result)) {
$FAIL($Fail);
} else {
$REFRESH_GEN_DEST();
$Dst = _result;
}
}
i_bs_get_binary_imm2 := i_bs_get_binary_imm2.fetch.execute;
i_bs_get_binary_imm2.head() {
Eterm context;
}
i_bs_get_binary_imm2.fetch(Ctx) {
context = $Ctx;
}
i_bs_get_binary_imm2.execute(Fail, Live, Sz, Flags, Dst) {
ErlBinMatchBuffer *_mb;
Eterm _result;
$GC_TEST_PRESERVE(heap_bin_size(ERL_ONHEAP_BIN_LIMIT),
$Live, context);
_mb = ms_matchbuffer(context);
LIGHT_SWAPOUT;
_result = erts_bs_get_binary_2(c_p, $Sz, $Flags, _mb);
LIGHT_SWAPIN;
HEAP_SPACE_VERIFIED(0);
if (is_non_value(_result)) {
$FAIL($Fail);
} else {
$REFRESH_GEN_DEST();
$Dst = _result;
}
}
i_bs_get_float2 := i_bs_get_float2.fetch.execute;
i_bs_get_float2.head() {
Eterm context;
}
i_bs_get_float2.fetch(Ctx) {
context = $Ctx;
}
i_bs_get_float2.execute(Fail, Live, Sz, Flags, Dst) {
ErlBinMatchBuffer *_mb;
Eterm _result;
Sint _size;
if (!is_small($Sz) || (_size = unsigned_val($Sz)) > 64) {
$FAIL($Fail);
}
_size *= (($Flags) >> 3);
$GC_TEST_PRESERVE(FLOAT_SIZE_OBJECT, $Live, context);
_mb = ms_matchbuffer(context);
LIGHT_SWAPOUT;
_result = erts_bs_get_float_2(c_p, _size, ($Flags), _mb);
LIGHT_SWAPIN;
HEAP_SPACE_VERIFIED(0);
if (is_non_value(_result)) {
$FAIL($Fail);
} else {
$REFRESH_GEN_DEST();
$Dst = _result;
}
}
i_bs_skip_bits2 := i_bs_skip_bits2.fetch.execute;
i_bs_skip_bits2.head() {
Eterm context, bits;
}
i_bs_skip_bits2.fetch(Ctx, Bits) {
context = $Ctx;
bits = $Bits;
}
i_bs_skip_bits2.execute(Fail, Unit) {
ErlBinMatchBuffer *_mb;
size_t new_offset;
Uint _size;
_mb = ms_matchbuffer(context);
$BS_GET_FIELD_SIZE(bits, $Unit, $FAIL($Fail), _size);
new_offset = _mb->offset + _size;
if (new_offset <= _mb->size) {
_mb->offset = new_offset;
} else {
$FAIL($Fail);
}
}
i_bs_skip_bits_imm2(Fail, Ms, Bits) {
ErlBinMatchBuffer *_mb;
size_t new_offset;
_mb = ms_matchbuffer($Ms);
new_offset = _mb->offset + ($Bits);
if (new_offset <= _mb->size) {
_mb->offset = new_offset;
} else {
$FAIL($Fail);
}
}
i_new_bs_put_binary(Fail, Sz, Flags, Src) {
Eterm sz = $Sz;
Sint _size;
$BS_GET_UNCHECKED_FIELD_SIZE(sz, (($Flags) >> 3), $BADARG($Fail), _size);
if (!erts_new_bs_put_binary(ERL_BITS_ARGS_2(($Src), _size))) {
$BADARG($Fail);
}
}
i_new_bs_put_binary_all := i_new_bs_put_binary_all.fetch.execute;
i_new_bs_put_binary_all.head() {
Eterm src;
}
i_new_bs_put_binary_all.fetch(Src) {
src = $Src;
}
i_new_bs_put_binary_all.execute(Fail, Unit) {
if (!erts_new_bs_put_binary_all(ERL_BITS_ARGS_2(src, ($Unit)))) {
$BADARG($Fail);
}
}
i_new_bs_put_binary_imm(Fail, Sz, Src) {
if (!erts_new_bs_put_binary(ERL_BITS_ARGS_2(($Src), ($Sz)))) {
$BADARG($Fail);
}
}
i_new_bs_put_float(Fail, Sz, Flags, Src) {
Eterm sz = $Sz;
Eterm flags = $Flags;
Sint _size;
$BS_GET_UNCHECKED_FIELD_SIZE(sz, (flags >> 3), $BADARG($Fail), _size);
if (!erts_new_bs_put_float(c_p, ($Src), _size, flags)) {
$BADARG($Fail);
}
}
i_new_bs_put_float_imm(Fail, Sz, Flags, Src) {
if (!erts_new_bs_put_float(c_p, ($Src), ($Sz), ($Flags))) {
$BADARG($Fail);
}
}
i_new_bs_put_integer(Fail, Sz, Flags, Src) {
Eterm sz = $Sz;
Eterm flags = $Flags;
Sint _size;
$BS_GET_UNCHECKED_FIELD_SIZE(sz, (flags >> 3), $BADARG($Fail), _size);
if (!erts_new_bs_put_integer(ERL_BITS_ARGS_3(($Src), _size, flags))) {
$BADARG($Fail);
}
}
i_new_bs_put_integer_imm := i_new_bs_put_integer_imm.fetch.execute;
i_new_bs_put_integer_imm.head() {
Eterm src;
}
i_new_bs_put_integer_imm.fetch(Src) {
src = $Src;
}
i_new_bs_put_integer_imm.execute(Fail, Sz, Flags) {
if (!erts_new_bs_put_integer(ERL_BITS_ARGS_3(src, ($Sz), ($Flags)))) {
$BADARG($Fail);
}
}
#
# i_bs_init*
#
i_bs_init_fail_heap := bs_init.fail_heap.verify.execute;
i_bs_init_fail := bs_init.fail.verify.execute;
i_bs_init := bs_init.plain.execute;
i_bs_init_heap := bs_init.heap.execute;
bs_init.head() {
Eterm BsOp1;
Eterm BsOp2;
}
bs_init.fail_heap(Size, HeapAlloc) {
BsOp1 = $Size;
BsOp2 = $HeapAlloc;
}
bs_init.fail(Size) {
BsOp1 = $Size;
BsOp2 = 0;
}
bs_init.plain(Size) {
BsOp1 = $Size;
BsOp2 = 0;
}
bs_init.heap(Size, HeapAlloc) {
BsOp1 = $Size;
BsOp2 = $HeapAlloc;
}
bs_init.verify(Fail) {
if (is_small(BsOp1)) {
Sint size = signed_val(BsOp1);
if (size < 0) {
$BADARG($Fail);
}
BsOp1 = (Eterm) size;
} else {
Uint bytes;
if (!term_to_Uint(BsOp1, &bytes)) {
c_p->freason = bytes;
$FAIL_HEAD_OR_BODY($Fail);
}
if ((bytes >> (8*sizeof(Uint)-3)) != 0) {
$SYSTEM_LIMIT($Fail);
}
BsOp1 = (Eterm) bytes;
}
}
bs_init.execute(Live, Dst) {
if (BsOp1 <= ERL_ONHEAP_BIN_LIMIT) {
ErlHeapBin* hb;
Uint bin_need;
bin_need = heap_bin_size(BsOp1);
erts_bin_offset = 0;
erts_writable_bin = 0;
$GC_TEST(0, bin_need+BsOp2+ERL_SUB_BIN_SIZE, $Live);
hb = (ErlHeapBin *) HTOP;
HTOP += bin_need;
hb->thing_word = header_heap_bin(BsOp1);
hb->size = BsOp1;
erts_current_bin = (byte *) hb->data;
$Dst = make_binary(hb);
} else {
Binary* bptr;
ProcBin* pb;
erts_bin_offset = 0;
erts_writable_bin = 0;
$TEST_BIN_VHEAP(BsOp1 / sizeof(Eterm),
BsOp2 + PROC_BIN_SIZE + ERL_SUB_BIN_SIZE, $Live);
/*
* Allocate the binary struct itself.
*/
bptr = erts_bin_nrml_alloc(BsOp1);
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 = BsOp1;
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)), BsOp1 / sizeof(Eterm));
$Dst = make_binary(pb);
}
}
#
# i_bs_init_bits*
#
i_bs_init_bits := bs_init_bits.plain.execute;
i_bs_init_bits_heap := bs_init_bits.heap.execute;
i_bs_init_bits_fail := bs_init_bits.fail.verify.execute;
i_bs_init_bits_fail_heap := bs_init_bits.fail_heap.verify.execute;
bs_init_bits.head() {
Eterm num_bits_term;
Uint num_bits;
Uint alloc;
}
bs_init_bits.plain(NumBits) {
num_bits = $NumBits;
alloc = 0;
}
bs_init_bits.heap(NumBits, Alloc) {
num_bits = $NumBits;
alloc = $Alloc;
}
bs_init_bits.fail(NumBitsTerm) {
num_bits_term = $NumBitsTerm;
alloc = 0;
}
bs_init_bits.fail_heap(NumBitsTerm, Alloc) {
num_bits_term = $NumBitsTerm;
alloc = $Alloc;
}
bs_init_bits.verify(Fail) {
if (is_small(num_bits_term)) {
Sint size = signed_val(num_bits_term);
if (size < 0) {
$BADARG($Fail);
}
num_bits = (Uint) size;
} else {
Uint bits;
if (!term_to_Uint(num_bits_term, &bits)) {
c_p->freason = bits;
$FAIL_HEAD_OR_BODY($Fail);
}
num_bits = (Uint) bits;
}
}
bs_init_bits.execute(Live, Dst) {
Eterm new_binary;
Uint 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;
}
$test_heap(alloc, $Live);
/* 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
* Operands: NotUsed NotUsed Dst
*/
if (num_bytes <= ERL_ONHEAP_BIN_LIMIT) {
ErlHeapBin* hb;
erts_bin_offset = 0;
erts_writable_bin = 0;
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);
do_bits_sub_bin:
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);
$Dst = new_binary;
} else {
Binary* bptr;
ProcBin* pb;
erts_bin_offset = 0;
erts_writable_bin = 0;
/*
* 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);
goto do_bits_sub_bin;
}
}
bs_add(Fail, Src1, Src2, Unit, Dst) {
Eterm Op1 = $Src1;
Eterm Op2 = $Src2;
Uint unit = $Unit;
if (is_both_small(Op1, Op2)) {
Sint Arg1 = signed_val(Op1);
Sint Arg2 = signed_val(Op2);
if (Arg1 >= 0 && Arg2 >= 0) {
$BS_SAFE_MUL(Arg2, unit, $SYSTEM_LIMIT($Fail), Op1);
Op1 += Arg1;
store_bs_add_result:
if (Op1 <= MAX_SMALL) {
Op1 = make_small(Op1);
} else {
/*
* May generate a heap fragment, but in this
* particular case it is OK, since the value will be
* stored into an x register (the GC will scan x
* registers for references to heap fragments) and
* there is no risk that value can be stored into a
* location that is not scanned for heap-fragment
* references (such as the heap).
*/
SWAPOUT;
Op1 = erts_make_integer(Op1, c_p);
HTOP = HEAP_TOP(c_p);
}
$Dst = Op1;
$NEXT0();
}
$BADARG($Fail);
} else {
Uint a;
Uint b;
Uint c;
/*
* Now we know that one of the arguments is
* not a small. We must convert both arguments
* to Uints and check for errors at the same time.
*
* Error checking is tricky.
*
* If one of the arguments is not numeric or
* not positive, the error reason is BADARG.
*
* Otherwise if both arguments are numeric,
* but at least one argument does not fit in
* an Uint, the reason is SYSTEM_LIMIT.
*/
if (!term_to_Uint(Op1, &a)) {
if (a == BADARG) {
$BADARG($Fail);
}
if (!term_to_Uint(Op2, &b)) {
c_p->freason = b;
$FAIL_HEAD_OR_BODY($Fail);
}
$SYSTEM_LIMIT($Fail);
} else if (!term_to_Uint(Op2, &b)) {
c_p->freason = b;
$FAIL_HEAD_OR_BODY($Fail);
}
/*
* The arguments are now correct and stored in a and b.
*/
$BS_SAFE_MUL(b, unit, $SYSTEM_LIMIT($Fail), c);
Op1 = a + c;
if (Op1 < a) {
/*
* If the result is less than one of the
* arguments, there must have been an overflow.
*/
$SYSTEM_LIMIT($Fail);
}
goto store_bs_add_result;
}
/* No fallthrough */
ASSERT(0);
}
bs_put_string(Len, Ptr) {
erts_new_bs_put_string(ERL_BITS_ARGS_2((byte *) $Ptr, $Len));
}
i_bs_append(Fail, ExtraHeap, Live, Unit, Size, Dst) {
Uint live = $Live;
Uint res;
HEAVY_SWAPOUT;
reg[live] = x(SCRATCH_X_REG);
res = erts_bs_append(c_p, reg, live, $Size, $ExtraHeap, $Unit);
HEAVY_SWAPIN;
if (is_non_value(res)) {
/* c_p->freason is already set (to BADARG or SYSTEM_LIMIT). */
$FAIL_HEAD_OR_BODY($Fail);
}
$Dst = res;
}
i_bs_private_append(Fail, Unit, Size, Src, Dst) {
Eterm res;
res = erts_bs_private_append(c_p, $Src, $Size, $Unit);
if (is_non_value(res)) {
/* c_p->freason is already set (to BADARG or SYSTEM_LIMIT). */
$FAIL_HEAD_OR_BODY($Fail);
}
$Dst = res;
}
bs_init_writable() {
HEAVY_SWAPOUT;
r(0) = erts_bs_init_writable(c_p, r(0));
HEAVY_SWAPIN;
}
i_bs_utf8_size(Src, Dst) {
Eterm arg = $Src;
Eterm result;
/*
* Calculate the number of bytes 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 (0 or 4). We
* can get away with that because we KNOW that bs_put_utf8 will do
* full error checking.
*/
if (arg < make_small(0x80UL)) {
result = make_small(1);
} else if (arg < make_small(0x800UL)) {
result = make_small(2);
} else if (arg < make_small(0x10000UL)) {
result = make_small(3);
} else {
result = make_small(4);
}
$Dst = result;
}
i_bs_put_utf8(Fail, Src) {
if (!erts_bs_put_utf8(ERL_BITS_ARGS_1($Src))) {
$BADARG($Fail);
}
}
i_bs_utf16_size(Src, Dst) {
Eterm arg = $Src;
Eterm result = make_small(2);
/*
* Calculate the number of bytes 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 (2 or 4). We
* can get away with that because we KNOW that bs_put_utf16 will do
* full error checking.
*/
if (arg >= make_small(0x10000UL)) {
result = make_small(4);
}
$Dst = result;
}
bs_put_utf16(Fail, Flags, Src) {
if (!erts_bs_put_utf16(ERL_BITS_ARGS_2($Src, $Flags))) {
$BADARG($Fail);
}
}
// Validate a value about to be stored in a binary.
i_bs_validate_unicode(Fail, Src) {
Eterm val = $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(val) || val > make_small(0x10FFFFUL) ||
(make_small(0xD800UL) <= val && val <= make_small(0xDFFFUL))) {
$BADARG($Fail);
}
}
// Validate a value that has been matched out.
i_bs_validate_unicode_retract(Fail, Src, Ms) {
/*
* There is no need to untag the integer, but it IS necessary
* to make sure it is small (a bignum pointer could fall in
* the valid range).
*/
Eterm i = $Src;
if (is_not_small(i) || i > make_small(0x10FFFFUL) ||
(make_small(0xD800UL) <= i && i <= make_small(0xDFFFUL))) {
Eterm ms = $Ms; /* Match context */
ErlBinMatchBuffer* mb;
/* Invalid value. Retract the position in the binary. */
mb = ms_matchbuffer(ms);
mb->offset -= 32;
$BADARG($Fail);
}
}
//
// Matching of binaries.
//
i_bs_start_match2 := bs_start_match.fetch.execute;
bs_start_match.head() {
Eterm context;
}
bs_start_match.fetch(Src) {
context = $Src;
}
bs_start_match.execute(Fail, Live, Slots, Dst) {
Uint slots;
Uint live;
Eterm header;
if (!is_boxed(context)) {
$FAIL($Fail);
}
header = *boxed_val(context);
/* Reserve a slot for the start position. */
slots = $Slots + 1;
live = $Live;
if (header_is_bin_matchstate(header)) {
ErlBinMatchState* ms = (ErlBinMatchState *) boxed_val(context);
Uint actual_slots = HEADER_NUM_SLOTS(header);
/* We're not compatible with contexts created by bs_start_match3. */
ASSERT(actual_slots >= 1);
ms->save_offset[0] = ms->mb.offset;
if (ERTS_UNLIKELY(actual_slots < slots)) {
ErlBinMatchState* expanded;
Uint live = $Live;
Uint wordsneeded = ERL_BIN_MATCHSTATE_SIZE(slots);
$GC_TEST_PRESERVE(wordsneeded, live, context);
ms = (ErlBinMatchState *) boxed_val(context);
expanded = (ErlBinMatchState *) HTOP;
*expanded = *ms;
*HTOP = HEADER_BIN_MATCHSTATE(slots);
HTOP += wordsneeded;
HEAP_SPACE_VERIFIED(0);
context = make_matchstate(expanded);
$REFRESH_GEN_DEST();
}
$Dst = context;
} else if (is_binary_header(header)) {
Eterm result;
Uint wordsneeded = ERL_BIN_MATCHSTATE_SIZE(slots);
$GC_TEST_PRESERVE(wordsneeded, live, context);
HEAP_TOP(c_p) = HTOP;
#ifdef DEBUG
c_p->stop = E; /* Needed for checking in HeapOnlyAlloc(). */
#endif
result = erts_bs_start_match_2(c_p, context, slots);
HTOP = HEAP_TOP(c_p);
HEAP_SPACE_VERIFIED(0);
if (is_non_value(result)) {
$FAIL($Fail);
}
$REFRESH_GEN_DEST();
$Dst = result;
} else {
$FAIL($Fail);
}
}
bs_test_zero_tail2(Fail, Ctx) {
ErlBinMatchBuffer *_mb;
_mb = (ErlBinMatchBuffer*) ms_matchbuffer($Ctx);
if (_mb->size != _mb->offset) {
$FAIL($Fail);
}
}
bs_test_tail_imm2(Fail, Ctx, Offset) {
ErlBinMatchBuffer *_mb;
_mb = ms_matchbuffer($Ctx);
if (_mb->size - _mb->offset != $Offset) {
$FAIL($Fail);
}
}
bs_test_unit(Fail, Ctx, Unit) {
ErlBinMatchBuffer *_mb;
_mb = ms_matchbuffer($Ctx);
if ((_mb->size - _mb->offset) % $Unit) {
$FAIL($Fail);
}
}
bs_test_unit8(Fail, Ctx) {
ErlBinMatchBuffer *_mb;
_mb = ms_matchbuffer($Ctx);
if ((_mb->size - _mb->offset) & 7) {
$FAIL($Fail);
}
}
i_bs_get_integer_8 := i_bs_get_integer_8.fetch.execute;
i_bs_get_integer_8.head() {
Eterm context;
}
i_bs_get_integer_8.fetch(Ctx) {
context = $Ctx;
}
i_bs_get_integer_8.execute(Fail, Dst) {
Eterm _result;
ErlBinMatchBuffer* _mb = ms_matchbuffer(context);
if (_mb->size - _mb->offset < 8) {
$FAIL($Fail);
}
if (BIT_OFFSET(_mb->offset) != 0) {
_result = erts_bs_get_integer_2(c_p, 8, 0, _mb);
} else {
_result = make_small(_mb->base[BYTE_OFFSET(_mb->offset)]);
_mb->offset += 8;
}
$Dst = _result;
}
i_bs_get_integer_16 := i_bs_get_integer_16.fetch.execute;
i_bs_get_integer_16.head() {
Eterm context;
}
i_bs_get_integer_16.fetch(Ctx) {
context = $Ctx;
}
i_bs_get_integer_16.execute(Fail, Dst) {
Eterm _result;
ErlBinMatchBuffer* _mb = ms_matchbuffer(context);
if (_mb->size - _mb->offset < 16) {
$FAIL($Fail);
}
if (BIT_OFFSET(_mb->offset) != 0) {
_result = erts_bs_get_integer_2(c_p, 16, 0, _mb);
} else {
_result = make_small(get_int16(_mb->base+BYTE_OFFSET(_mb->offset)));
_mb->offset += 16;
}
$Dst = _result;
}
%if ARCH_64
i_bs_get_integer_32 := i_bs_get_integer_32.fetch.execute;
i_bs_get_integer_32.head() {
Eterm context;
}
i_bs_get_integer_32.fetch(Ctx) {
context = $Ctx;
}
i_bs_get_integer_32.execute(Fail, Dst) {
Uint32 _integer;
ErlBinMatchBuffer* _mb = ms_matchbuffer(context);
if (_mb->size - _mb->offset < 32) {
$FAIL($Fail);
}
if (BIT_OFFSET(_mb->offset) != 0) {
_integer = erts_bs_get_unaligned_uint32(_mb);
} else {
_integer = get_int32(_mb->base + _mb->offset/8);
}
_mb->offset += 32;
$Dst = make_small(_integer);
}
%endif
i_bs_get_integer_imm := bs_get_integer.fetch.execute;
i_bs_get_integer_small_imm := bs_get_integer.fetch_small.execute;
bs_get_integer.head() {
Eterm Ms, Sz;
}
bs_get_integer.fetch(Ctx, Size, Live) {
Uint wordsneeded;
Ms = $Ctx;
Sz = $Size;
wordsneeded = 1+WSIZE(NBYTES(Sz));
$GC_TEST_PRESERVE(wordsneeded, $Live, Ms);
}
bs_get_integer.fetch_small(Ctx, Size) {
Ms = $Ctx;
Sz = $Size;
}
bs_get_integer.execute(Fail, Flags, Dst) {
ErlBinMatchBuffer* mb;
Eterm result;
mb = ms_matchbuffer(Ms);
LIGHT_SWAPOUT;
result = erts_bs_get_integer_2(c_p, Sz, $Flags, mb);
LIGHT_SWAPIN;
HEAP_SPACE_VERIFIED(0);
if (is_non_value(result)) {
$FAIL($Fail);
}
$Dst = result;
}
i_bs_get_integer := i_bs_get_integer.fetch.execute;
i_bs_get_integer.head() {
Eterm context;
}
i_bs_get_integer.fetch(Ctx) {
context = $Ctx;
}
i_bs_get_integer.execute(Fail, Live, FlagsAndUnit, Sz, Dst) {
Uint flags;
Uint size;
ErlBinMatchBuffer* mb;
Eterm result;
flags = $FlagsAndUnit;
$BS_GET_FIELD_SIZE($Sz, (flags >> 3), $FAIL($Fail), size);
if (size >= SMALL_BITS) {
Uint wordsneeded;
/* Check bits size before potential gc.
* We do not want a gc and then realize we don't need
* the allocated space (i.e. if the op fails).
*
* Remember to re-acquire the matchbuffer after gc.
*/
mb = ms_matchbuffer(context);
if (mb->size - mb->offset < size) {
$FAIL($Fail);
}
wordsneeded = 1+WSIZE(NBYTES((Uint) size));
$GC_TEST_PRESERVE(wordsneeded, $Live, context);
$REFRESH_GEN_DEST();
}
mb = ms_matchbuffer(context);
LIGHT_SWAPOUT;
result = erts_bs_get_integer_2(c_p, size, flags, mb);
LIGHT_SWAPIN;
HEAP_SPACE_VERIFIED(0);
if (is_non_value(result)) {
$FAIL($Fail);
}
$Dst = result;
}
i_bs_get_utf8 := i_bs_get_utf8.fetch.execute;
i_bs_get_utf8.head() {
Eterm context;
}
i_bs_get_utf8.fetch(Ctx) {
context = $Ctx;
}
i_bs_get_utf8.execute(Fail, Dst) {
Eterm result;
ErlBinMatchBuffer* mb = ms_matchbuffer(context);
if (mb->size - mb->offset < 8) {
$FAIL($Fail);
}
if (BIT_OFFSET(mb->offset) != 0) {
result = erts_bs_get_utf8(mb);
} else {
byte b = mb->base[BYTE_OFFSET(mb->offset)];
if (b < 128) {
result = make_small(b);
mb->offset += 8;
} else {
result = erts_bs_get_utf8(mb);
}
}
if (is_non_value(result)) {
$FAIL($Fail);
}
$REFRESH_GEN_DEST();
$Dst = result;
}
i_bs_get_utf16 := i_bs_get_utf16.fetch.execute;
i_bs_get_utf16.head() {
Eterm context;
}
i_bs_get_utf16.fetch(Ctx) {
context = $Ctx;
}
i_bs_get_utf16.execute(Fail, Flags, Dst) {
ErlBinMatchBuffer* mb = ms_matchbuffer(context);
Eterm result = erts_bs_get_utf16(mb, $Flags);
if (is_non_value(result)) {
$FAIL($Fail);
}
$REFRESH_GEN_DEST();
$Dst = result;
}
bs_context_to_binary := ctx_to_bin.fetch.execute;
i_bs_get_binary_all_reuse := ctx_to_bin.fetch_bin.execute;
ctx_to_bin.head() {
Eterm context;
ErlBinMatchBuffer* mb;
Uint size;
Uint offs;
}
ctx_to_bin.fetch(Src) {
context = $Src;
if (is_boxed(context) &&
header_is_bin_matchstate(*boxed_val(context))) {
ErlBinMatchState* ms;
ms = (ErlBinMatchState *) boxed_val(context);
mb = &ms->mb;
offs = ms->save_offset[0];
size = mb->size - offs;
} else {
$NEXT0();
}
}
ctx_to_bin.fetch_bin(Src, Fail, Unit) {
context = $Src;
mb = ms_matchbuffer(context);
size = mb->size - mb->offset;
if (size % $Unit != 0) {
$FAIL($Fail);
}
offs = mb->offset;
}
ctx_to_bin.execute() {
Uint hole_size;
Uint orig = mb->orig;
ErlSubBin* sb = (ErlSubBin *) boxed_val(context);
/* Since we're going to overwrite the match state with the result, an
* ErlBinMatchState must be at least as large as an ErlSubBin. */
ERTS_CT_ASSERT(sizeof(ErlSubBin) <= sizeof(ErlBinMatchState));
hole_size = 1 + header_arity(sb->thing_word) - ERL_SUB_BIN_SIZE;
sb->thing_word = HEADER_SUB_BIN;
sb->size = BYTE_OFFSET(size);
sb->bitsize = BIT_OFFSET(size);
sb->offs = BYTE_OFFSET(offs);
sb->bitoffs = BIT_OFFSET(offs);
sb->is_writable = 0;
sb->orig = orig;
if (hole_size) {
sb[1].thing_word = make_pos_bignum_header(hole_size-1);
}
}
i_bs_match_string(Ctx, Fail, Bits, Ptr) {
byte* bytes = (byte *) $Ptr;
Uint bits = $Bits;
ErlBinMatchBuffer* mb;
Uint offs;
mb = ms_matchbuffer($Ctx);
if (mb->size - mb->offset < bits) {
$FAIL($Fail);
}
offs = mb->offset & 7;
if (offs == 0 && (bits & 7) == 0) {
if (sys_memcmp(bytes, mb->base+(mb->offset>>3), bits>>3)) {
$FAIL($Fail);
}
} else if (erts_cmp_bits(bytes, 0, mb->base+(mb->offset>>3), mb->offset & 7, bits)) {
$FAIL($Fail);
}
mb->offset += bits;
}
i_bs_save2(Src, Slot) {
ErlBinMatchState* _ms = (ErlBinMatchState*) boxed_val((Eterm) $Src);
ASSERT(HEADER_NUM_SLOTS(_ms->thing_word) > $Slot);
_ms->save_offset[$Slot] = _ms->mb.offset;
}
i_bs_restore2(Src, Slot) {
ErlBinMatchState* _ms = (ErlBinMatchState*) boxed_val((Eterm) $Src);
ASSERT(HEADER_NUM_SLOTS(_ms->thing_word) > $Slot);
_ms->mb.offset = _ms->save_offset[$Slot];
}
bs_get_tail := bs_get_tail.fetch.execute;
bs_get_tail.head() {
Eterm context;
}
bs_get_tail.fetch(Src) {
context = $Src;
}
bs_get_tail.execute(Dst, Live) {
ErlBinMatchBuffer* mb;
Uint size, offs;
ErlSubBin* sb;
ASSERT(header_is_bin_matchstate(*boxed_val(context)));
$GC_TEST_PRESERVE(ERL_SUB_BIN_SIZE, $Live, context);
mb = ms_matchbuffer(context);
offs = mb->offset;
size = mb->size - offs;
sb = (ErlSubBin *) HTOP;
HTOP += ERL_SUB_BIN_SIZE;
sb->thing_word = HEADER_SUB_BIN;
sb->size = BYTE_OFFSET(size);
sb->bitsize = BIT_OFFSET(size);
sb->offs = BYTE_OFFSET(offs);
sb->bitoffs = BIT_OFFSET(offs);
sb->is_writable = 0;
sb->orig = mb->orig;
$REFRESH_GEN_DEST();
$Dst = make_binary(sb);
}
%if ARCH_64
i_bs_start_match3_gp := i_bs_start_match3_gp.fetch.execute;
i_bs_start_match3_gp.head() {
Eterm context;
}
i_bs_start_match3_gp.fetch(Src) {
context = $Src;
}
i_bs_start_match3_gp.execute(Live, Fail, Dst, Pos) {
Eterm header;
Uint position, live;
live = $Live;
if (!is_boxed(context)) {
$FAIL($Fail);
}
header = *boxed_val(context);
if (header_is_bin_matchstate(header)) {
ErlBinMatchBuffer *mb;
ASSERT(HEADER_NUM_SLOTS(header) == 0);
mb = ms_matchbuffer(context);
position = mb->offset;
$Dst = context;
} else if (is_binary_header(header)) {
ErlBinMatchState *ms;
$GC_TEST_PRESERVE(ERL_BIN_MATCHSTATE_SIZE(0), live, context);
HEAP_TOP(c_p) = HTOP;
#ifdef DEBUG
c_p->stop = E; /* Needed for checking in HeapOnlyAlloc(). */
#endif
ms = erts_bs_start_match_3(c_p, context);
HTOP = HEAP_TOP(c_p);
HEAP_SPACE_VERIFIED(0);
if (ms == NULL) {
$FAIL($Fail);
}
$REFRESH_GEN_DEST();
$Dst = make_matchstate(ms);
position = ms->mb.offset;
} else {
$FAIL($Fail);
}
ASSERT(IS_USMALL(0, position));
$Pos = make_small(position);
}
i_bs_start_match3 := i_bs_start_match3.fetch.execute;
i_bs_start_match3.head() {
Eterm context;
}
i_bs_start_match3.fetch(Src) {
context = $Src;
}
i_bs_start_match3.execute(Live, Fail, Dst) {
Eterm header;
Uint live;
live = $Live;
if (!is_boxed(context)) {
$FAIL($Fail);
}
header = *boxed_val(context);
if (header_is_bin_matchstate(header)) {
ASSERT(HEADER_NUM_SLOTS(header) == 0);
$Dst = context;
} else if (is_binary_header(header)) {
ErlBinMatchState *ms;
$GC_TEST_PRESERVE(ERL_BIN_MATCHSTATE_SIZE(0), live, context);
HEAP_TOP(c_p) = HTOP;
#ifdef DEBUG
c_p->stop = E; /* Needed for checking in HeapOnlyAlloc(). */
#endif
ms = erts_bs_start_match_3(c_p, context);
HTOP = HEAP_TOP(c_p);
HEAP_SPACE_VERIFIED(0);
if (ms == NULL) {
$FAIL($Fail);
}
$REFRESH_GEN_DEST();
$Dst = make_matchstate(ms);
} else {
$FAIL($Fail);
}
}
bs_set_position(Ctx, Pos) {
ErlBinMatchBuffer* mb;
Eterm context;
context = $Ctx;
ASSERT(header_is_bin_matchstate(*boxed_val(context)));
mb = ms_matchbuffer(context);
mb->offset = unsigned_val($Pos);
}
i_bs_get_position(Ctx, Dst) {
ErlBinMatchBuffer* mb;
Eterm context;
context = $Ctx;
ASSERT(header_is_bin_matchstate(*boxed_val(context)));
mb = ms_matchbuffer(context);
$Dst = make_small(mb->offset);
}
%else
#
# Unlike their 64-bit counterparts, the 32-bit position instructions operate on
# an offset from the "base position" of the context because storing raw
# positions would lead to the creation of far too many bigints.
#
# When a match context is reused we check whether its position fits into an
# immediate, and create a new match context if it does not. This means we only
# have to allocate stuff roughly once every 16MB rather than every time we
# match at a position beyond 16MB.
#
bs_set_position := bs_set_position.fetch.execute;
bs_set_position.head() {
Eterm context, position;
}
bs_set_position.fetch(Ctx, Pos) {
context = $Ctx;
position = $Pos;
}
bs_set_position.execute() {
ErlBinMatchState *ms;
ASSERT(header_is_bin_matchstate(*boxed_val(context)));
ms = (ErlBinMatchState*)boxed_val(context);
if (ERTS_LIKELY(is_small(position))) {
ms->mb.offset = ms->save_offset[0] + unsigned_val(position);
} else {
ASSERT(is_big(position));
ms->mb.offset = ms->save_offset[0] + *BIG_V(big_val(position));
}
}
bs_get_position := bs_get_position.fetch.execute;
bs_get_position.head() {
Eterm context;
}
bs_get_position.fetch(Ctx) {
context = $Ctx;
}
bs_get_position.execute(Dst, Live) {
ErlBinMatchState *ms;
Uint position;
ASSERT(header_is_bin_matchstate(*boxed_val(context)));
ms = (ErlBinMatchState*)boxed_val(context);
position = ms->mb.offset - ms->save_offset[0];
if (ERTS_LIKELY(IS_USMALL(0, position))) {
$Dst = make_small(position);
} else {
Eterm *hp;
$GC_TEST_PRESERVE(BIG_UINT_HEAP_SIZE, $Live, context);
hp = HTOP;
HTOP += BIG_UINT_HEAP_SIZE;
*hp = make_pos_bignum_header(1);
BIG_DIGIT(hp, 0) = position;
$REFRESH_GEN_DEST();
$Dst = make_big(hp);
}
}
i_bs_start_match3 := i_bs_start_match3.fetch.execute;
i_bs_start_match3.head() {
Eterm context;
}
i_bs_start_match3.fetch(Src) {
context = $Src;
}
i_bs_start_match3.execute(Live, Fail, Dst) {
Eterm header;
Uint live;
live = $Live;
if (!is_boxed(context)) {
$FAIL($Fail);
}
header = *boxed_val(context);
if (header_is_bin_matchstate(header)) {
ErlBinMatchState *current_ms;
Uint position;
ASSERT(HEADER_NUM_SLOTS(header) == 1);
current_ms = (ErlBinMatchState*)boxed_val(context);
position = current_ms->mb.offset - current_ms->save_offset[0];
if (ERTS_LIKELY(IS_USMALL(0, position))) {
$Dst = context;
} else {
ErlBinMatchState *new_ms;
$GC_TEST_PRESERVE(ERL_BIN_MATCHSTATE_SIZE(1), live, context);
current_ms = (ErlBinMatchState*)boxed_val(context);
new_ms = (ErlBinMatchState*)HTOP;
HTOP += ERL_BIN_MATCHSTATE_SIZE(1);
new_ms->thing_word = HEADER_BIN_MATCHSTATE(1);
new_ms->save_offset[0] = current_ms->mb.offset;
new_ms->mb = current_ms->mb;
$REFRESH_GEN_DEST();
$Dst = make_matchstate(new_ms);
}
} else if (is_binary_header(header)) {
Eterm result;
$GC_TEST_PRESERVE(ERL_BIN_MATCHSTATE_SIZE(1), live, context);
HEAP_TOP(c_p) = HTOP;
#ifdef DEBUG
c_p->stop = E; /* Needed for checking in HeapOnlyAlloc(). */
#endif
/* We intentionally use erts_bs_start_match_2 so that we can use
* save_offset as a base for all saved positions on this context,
* allowing us to avoid bigints for much longer. */
result = erts_bs_start_match_2(c_p, context, 1);
HTOP = HEAP_TOP(c_p);
HEAP_SPACE_VERIFIED(0);
if (is_non_value(result)) {
$FAIL($Fail);
}
$REFRESH_GEN_DEST();
$Dst = result;
} else {
$FAIL($Fail);
}
}
%endif