/* * %CopyrightBegin% * * Copyright Ericsson AB 1996-2011. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * %CopyrightEnd% */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "sys.h" #include "erl_vm.h" #include "global.h" #include "erl_process.h" #include "error.h" #include "bif.h" #include "big.h" #include "erl_binary.h" #include "erl_bits.h" #ifdef DEBUG static int list_to_bitstr_buf(Eterm obj, char* buf, Uint len); #else static int list_to_bitstr_buf(Eterm obj, char* buf); #endif static int bitstr_list_len(Eterm obj, Uint* num_bytes); void erts_init_binary(void) { /* Verify Binary alignment... */ if ((((UWord) &((Binary *) 0)->orig_bytes[0]) % ((UWord) 8)) != 0) { /* I assume that any compiler should be able to optimize this away. If not, this test is not very expensive... */ erl_exit(ERTS_ABORT_EXIT, "Internal error: Address of orig_bytes[0] of a Binary" "is *not* 8-byte aligned\n"); } } /* * Create a brand new binary from scratch. */ Eterm new_binary(Process *p, byte *buf, Uint len) { ProcBin* pb; Binary* bptr; if (len <= ERL_ONHEAP_BIN_LIMIT) { ErlHeapBin* hb = (ErlHeapBin *) HAlloc(p, heap_bin_size(len)); hb->thing_word = header_heap_bin(len); hb->size = len; if (buf != NULL) { sys_memcpy(hb->data, buf, len); } return make_binary(hb); } /* * Allocate the binary struct itself. */ bptr = erts_bin_nrml_alloc(len); bptr->flags = 0; bptr->orig_size = len; erts_refc_init(&bptr->refc, 1); if (buf != NULL) { sys_memcpy(bptr->orig_bytes, buf, len); } /* * Now allocate the ProcBin on the heap. */ pb = (ProcBin *) HAlloc(p, PROC_BIN_SIZE); pb->thing_word = HEADER_PROC_BIN; pb->size = len; pb->next = MSO(p).first; MSO(p).first = (struct erl_off_heap_header*)pb; pb->val = bptr; pb->bytes = (byte*) bptr->orig_bytes; pb->flags = 0; /* * Miscellanous updates. Return the tagged binary. */ OH_OVERHEAD(&(MSO(p)), pb->size / sizeof(Eterm)); return make_binary(pb); } /* * When heap binary is not desired... */ Eterm erts_new_mso_binary(Process *p, byte *buf, int len) { ProcBin* pb; Binary* bptr; /* * Allocate the binary struct itself. */ bptr = erts_bin_nrml_alloc(len); bptr->flags = 0; bptr->orig_size = len; erts_refc_init(&bptr->refc, 1); if (buf != NULL) { sys_memcpy(bptr->orig_bytes, buf, len); } /* * Now allocate the ProcBin on the heap. */ pb = (ProcBin *) HAlloc(p, PROC_BIN_SIZE); pb->thing_word = HEADER_PROC_BIN; pb->size = len; pb->next = MSO(p).first; MSO(p).first = (struct erl_off_heap_header*)pb; pb->val = bptr; pb->bytes = (byte*) bptr->orig_bytes; pb->flags = 0; /* * Miscellanous updates. Return the tagged binary. */ OH_OVERHEAD(&(MSO(p)), pb->size / sizeof(Eterm)); return make_binary(pb); } /* * Create a brand new binary from scratch on the heap. */ Eterm erts_new_heap_binary(Process *p, byte *buf, int len, byte** datap) { ErlHeapBin* hb = (ErlHeapBin *) HAlloc(p, heap_bin_size(len)); hb->thing_word = header_heap_bin(len); hb->size = len; if (buf != NULL) { sys_memcpy(hb->data, buf, len); } *datap = (byte*) hb->data; return make_binary(hb); } Eterm erts_realloc_binary(Eterm bin, size_t size) { Eterm* bval = binary_val(bin); if (thing_subtag(*bval) == HEAP_BINARY_SUBTAG) { ASSERT(size <= binary_size(bin)); binary_size(bin) = size; } else { /* REFC */ ProcBin* pb = (ProcBin *) bval; Binary* newbin = erts_bin_realloc(pb->val, size); newbin->orig_size = size; pb->val = newbin; pb->size = size; pb->bytes = (byte*) newbin->orig_bytes; pb->flags = 0; bin = make_binary(pb); } return bin; } byte* erts_get_aligned_binary_bytes_extra(Eterm bin, byte** base_ptr, ErtsAlcType_t allocator, unsigned extra) { byte* bytes; Eterm* real_bin; Uint byte_size; Uint offs = 0; Uint bit_offs = 0; if (is_not_binary(bin)) { return NULL; } byte_size = binary_size(bin); real_bin = binary_val(bin); if (*real_bin == HEADER_SUB_BIN) { ErlSubBin* sb = (ErlSubBin *) real_bin; if (sb->bitsize) { return NULL; } offs = sb->offs; bit_offs = sb->bitoffs; real_bin = binary_val(sb->orig); } if (*real_bin == HEADER_PROC_BIN) { bytes = ((ProcBin *) real_bin)->bytes + offs; } else { bytes = (byte *)(&(((ErlHeapBin *) real_bin)->data)) + offs; } if (bit_offs) { byte* buf = (byte *) erts_alloc(allocator, byte_size + extra); *base_ptr = buf; buf += extra; erts_copy_bits(bytes, bit_offs, 1, buf, 0, 1, byte_size*8); bytes = buf; } return bytes; } Eterm erts_bin_bytes_to_list(Eterm previous, Eterm* hp, byte* bytes, Uint size, Uint bitoffs) { if (bitoffs == 0) { while (size) { previous = CONS(hp, make_small(bytes[--size]), previous); hp += 2; } } else { byte present; byte next; next = bytes[size]; while (size) { present = next; next = bytes[--size]; previous = CONS(hp, make_small(((present >> (8-bitoffs)) | (next << bitoffs)) & 255), previous); hp += 2; } } return previous; } BIF_RETTYPE binary_to_list_1(BIF_ALIST_1) { Eterm real_bin; Uint offset; Uint size; Uint bitsize; Uint bitoffs; if (is_not_binary(BIF_ARG_1)) { goto error; } size = binary_size(BIF_ARG_1); ERTS_GET_REAL_BIN(BIF_ARG_1, real_bin, offset, bitoffs, bitsize); if (bitsize != 0) { goto error; } if (size == 0) { BIF_RET(NIL); } else { Eterm* hp = HAlloc(BIF_P, 2 * size); byte* bytes = binary_bytes(real_bin)+offset; BIF_RET(erts_bin_bytes_to_list(NIL, hp, bytes, size, bitoffs)); } error: BIF_ERROR(BIF_P, BADARG); } BIF_RETTYPE binary_to_list_3(BIF_ALIST_3) { byte* bytes; Uint size; Uint bitoffs; Uint bitsize; Uint i; Uint start; Uint stop; Eterm* hp; if (is_not_binary(BIF_ARG_1)) { goto error; } if (!term_to_Uint(BIF_ARG_2, &start) || !term_to_Uint(BIF_ARG_3, &stop)) { goto error; } size = binary_size(BIF_ARG_1); ERTS_GET_BINARY_BYTES(BIF_ARG_1, bytes, bitoffs, bitsize); if (start < 1 || start > size || stop < 1 || stop > size || stop < start ) { goto error; } i = stop-start+1; hp = HAlloc(BIF_P, 2*i); BIF_RET(erts_bin_bytes_to_list(NIL, hp, bytes+start-1, i, bitoffs)); error: BIF_ERROR(BIF_P, BADARG); } BIF_RETTYPE bitstring_to_list_1(BIF_ALIST_1) { Eterm real_bin; Uint offset; Uint size; Uint bitsize; Uint bitoffs; byte* bytes; Eterm previous = NIL; Eterm* hp; if (is_not_binary(BIF_ARG_1)) { BIF_ERROR(BIF_P, BADARG); } size = binary_size(BIF_ARG_1); ERTS_GET_REAL_BIN(BIF_ARG_1, real_bin, offset, bitoffs, bitsize); bytes = binary_bytes(real_bin)+offset; if (bitsize == 0) { hp = HAlloc(BIF_P, 2 * size); } else if (size == 0) { hp = HAlloc(BIF_P, 2); BIF_RET(CONS(hp,BIF_ARG_1,NIL)); } else { ErlSubBin* last; hp = HAlloc(BIF_P, ERL_SUB_BIN_SIZE+2+2*size); last = (ErlSubBin *) hp; last->thing_word = HEADER_SUB_BIN; last->size = 0; last->bitsize = bitsize; last->offs = offset+size; last->bitoffs = bitoffs; last->orig = real_bin; last->is_writable = 0; hp += ERL_SUB_BIN_SIZE; previous = CONS(hp, make_binary(last), previous); hp += 2; } BIF_RET(erts_bin_bytes_to_list(previous, hp, bytes, size, bitoffs)); } /* Turn a possibly deep list of ints (and binaries) into */ /* One large binary object */ /* * This bif also exists in the binary module, under the name * binary:list_to_bin/1, why it's divided into interface and * implementation. Also the backend for iolist_to_binary_1. */ BIF_RETTYPE erts_list_to_binary_bif(Process *p, Eterm arg) { Eterm bin; Uint size; int offset; byte* bytes; if (is_nil(arg)) { BIF_RET(new_binary(p,(byte*)"",0)); } if (is_not_list(arg)) { goto error; } switch (erts_iolist_size(arg, &size)) { case ERTS_IOLIST_OVERFLOW: BIF_ERROR(p, SYSTEM_LIMIT); case ERTS_IOLIST_TYPE: goto error; default: ; } bin = new_binary(p, (byte *)NULL, size); bytes = binary_bytes(bin); offset = io_list_to_buf(arg, (char*) bytes, size); ASSERT(offset == 0); BIF_RET(bin); error: BIF_ERROR(p, BADARG); } BIF_RETTYPE list_to_binary_1(BIF_ALIST_1) { return erts_list_to_binary_bif(BIF_P, BIF_ARG_1); } /* Turn a possibly deep list of ints (and binaries) into */ /* One large binary object */ BIF_RETTYPE iolist_to_binary_1(BIF_ALIST_1) { if (is_binary(BIF_ARG_1)) { BIF_RET(BIF_ARG_1); } return erts_list_to_binary_bif(BIF_P, BIF_ARG_1); } BIF_RETTYPE list_to_bitstring_1(BIF_ALIST_1) { Eterm bin; Uint sz; int offset; byte* bytes; ErlSubBin* sb1; Eterm* hp; if (is_nil(BIF_ARG_1)) { BIF_RET(new_binary(BIF_P,(byte*)"",0)); } if (is_not_list(BIF_ARG_1)) { error: BIF_ERROR(BIF_P, BADARG); } switch (bitstr_list_len(BIF_ARG_1, &sz)) { case ERTS_IOLIST_TYPE: goto error; case ERTS_IOLIST_OVERFLOW: BIF_ERROR(BIF_P, SYSTEM_LIMIT); } bin = new_binary(BIF_P, (byte *)NULL, sz); bytes = binary_bytes(bin); #ifdef DEBUG offset = list_to_bitstr_buf(BIF_ARG_1, (char*) bytes, sz); #else offset = list_to_bitstr_buf(BIF_ARG_1, (char*) bytes); #endif ASSERT(offset >= 0); if (offset > 0) { hp = HAlloc(BIF_P, ERL_SUB_BIN_SIZE); sb1 = (ErlSubBin *) hp; sb1->thing_word = HEADER_SUB_BIN; sb1->size = sz-1; sb1->offs = 0; sb1->orig = bin; sb1->bitoffs = 0; sb1->bitsize = offset; sb1->is_writable = 0; bin = make_binary(sb1); } BIF_RET(bin); } BIF_RETTYPE split_binary_2(BIF_ALIST_2) { Uint pos; ErlSubBin* sb1; ErlSubBin* sb2; size_t orig_size; Eterm orig; Uint offset; Uint bit_offset; Uint bit_size; Eterm* hp; if (is_not_binary(BIF_ARG_1)) { goto error; } if (!term_to_Uint(BIF_ARG_2, &pos)) { goto error; } if ((orig_size = binary_size(BIF_ARG_1)) < pos) { goto error; } hp = HAlloc(BIF_P, 2*ERL_SUB_BIN_SIZE+3); ERTS_GET_REAL_BIN(BIF_ARG_1, orig, offset, bit_offset, bit_size); sb1 = (ErlSubBin *) hp; sb1->thing_word = HEADER_SUB_BIN; sb1->size = pos; sb1->offs = offset; sb1->orig = orig; sb1->bitoffs = bit_offset; sb1->bitsize = 0; sb1->is_writable = 0; hp += ERL_SUB_BIN_SIZE; sb2 = (ErlSubBin *) hp; sb2->thing_word = HEADER_SUB_BIN; sb2->size = orig_size - pos; sb2->offs = offset + pos; sb2->orig = orig; sb2->bitoffs = bit_offset; sb2->bitsize = bit_size; /* The extra bits go into the second binary. */ sb2->is_writable = 0; hp += ERL_SUB_BIN_SIZE; return TUPLE2(hp, make_binary(sb1), make_binary(sb2)); error: BIF_ERROR(BIF_P, BADARG); } /* * Local functions. */ /* * The input list is assumed to be type-correct and the buffer is * assumed to be of sufficient size. Those assumptions are verified in * the DEBUG-built emulator. */ static int #ifdef DEBUG list_to_bitstr_buf(Eterm obj, char* buf, Uint len) #else list_to_bitstr_buf(Eterm obj, char* buf) #endif { Eterm* objp; int offset = 0; DECLARE_ESTACK(s); goto L_again; while (!ESTACK_ISEMPTY(s)) { obj = ESTACK_POP(s); L_again: if (is_list(obj)) { L_iter_list: objp = list_val(obj); obj = CAR(objp); if (is_byte(obj)) { ASSERT(len > 0); if (offset == 0) { *buf++ = unsigned_val(obj); } else { *buf = (char)((unsigned_val(obj) >> offset) | ((*buf >> (8-offset)) << (8-offset))); buf++; *buf = (unsigned_val(obj) << (8-offset)); } #ifdef DEBUG len--; #endif } else if (is_binary(obj)) { byte* bptr; size_t size = binary_size(obj); Uint bitsize; Uint bitoffs; Uint num_bits; ASSERT(size <= len); ERTS_GET_BINARY_BYTES(obj, bptr, bitoffs, bitsize); num_bits = 8*size+bitsize; copy_binary_to_buffer(buf, offset, bptr, bitoffs, num_bits); offset += bitsize; buf += size + (offset>7); #ifdef DEBUG len -= size + (offset>7); #endif offset = offset & 7; } else if (is_list(obj)) { ESTACK_PUSH(s, CDR(objp)); goto L_iter_list; /* on head */ } else { ASSERT(is_nil(obj)); } obj = CDR(objp); if (is_list(obj)) { goto L_iter_list; /* on tail */ } else if (is_binary(obj)) { byte* bptr; size_t size = binary_size(obj); Uint bitsize; Uint bitoffs; Uint num_bits; ASSERT(size <= len); ERTS_GET_BINARY_BYTES(obj, bptr, bitoffs, bitsize); num_bits = 8*size+bitsize; copy_binary_to_buffer(buf, offset, bptr, bitoffs, num_bits); offset += bitsize; buf += size+(offset>7); #ifdef DEBUG len -= size+(offset>7); #endif offset = offset & 7; } else { ASSERT(is_nil(obj)); } } else if (is_binary(obj)) { byte* bptr; size_t size = binary_size(obj); Uint bitsize; Uint bitoffs; Uint num_bits; ASSERT(size <= len); ERTS_GET_BINARY_BYTES(obj, bptr, bitoffs, bitsize); num_bits = 8*size+bitsize; copy_binary_to_buffer(buf, offset, bptr, bitoffs, num_bits); offset += bitsize; buf += size + (offset>7); #ifdef DEBUG len -= size + (offset>7); #endif offset = offset & 7; } else { ASSERT(is_nil(obj)); } } DESTROY_ESTACK(s); return offset; } static int bitstr_list_len(Eterm obj, Uint* num_bytes) { Eterm* objp; Uint len = 0; Uint offs = 0; DECLARE_ESTACK(s); goto L_again; #define SAFE_ADD(Var, Val) \ do { \ Uint valvar = (Val); \ Var += valvar; \ if (Var < valvar) { \ goto L_overflow_error; \ } \ } while (0) #define SAFE_ADD_BITSIZE(Var, Bin) \ do { \ if (*binary_val(Bin) == HEADER_SUB_BIN) { \ Uint valvar = ((ErlSubBin *) binary_val(Bin))->bitsize; \ Var += valvar; \ if (Var < valvar) { \ goto L_overflow_error; \ } \ } \ } while (0) while (!ESTACK_ISEMPTY(s)) { obj = ESTACK_POP(s); L_again: if (is_list(obj)) { L_iter_list: objp = list_val(obj); /* Head */ obj = CAR(objp); if (is_byte(obj)) { len++; if (len == 0) { goto L_overflow_error; } } else if (is_binary(obj)) { SAFE_ADD(len, binary_size(obj)); SAFE_ADD_BITSIZE(offs, obj); } else if (is_list(obj)) { ESTACK_PUSH(s, CDR(objp)); goto L_iter_list; /* on head */ } else if (is_not_nil(obj)) { goto L_type_error; } /* Tail */ obj = CDR(objp); if (is_list(obj)) goto L_iter_list; /* on tail */ else if (is_binary(obj)) { SAFE_ADD(len, binary_size(obj)); SAFE_ADD_BITSIZE(offs, obj); } else if (is_not_nil(obj)) { goto L_type_error; } } else if (is_binary(obj)) { SAFE_ADD(len, binary_size(obj)); SAFE_ADD_BITSIZE(offs, obj); } else if (is_not_nil(obj)) { goto L_type_error; } } #undef SAFE_ADD #undef SAFE_ADD_BITSIZE DESTROY_ESTACK(s); /* * Make sure that the number of bits in the bitstring will fit * in an Uint to ensure that the binary can be matched using * the binary syntax. */ if (len << 3 < len) { goto L_overflow_error; } len += (offs >> 3) + ((offs & 7) != 0); if (len << 3 < len) { goto L_overflow_error; } *num_bytes = len; return ERTS_IOLIST_OK; L_type_error: DESTROY_ESTACK(s); return ERTS_IOLIST_TYPE; L_overflow_error: DESTROY_ESTACK(s); return ERTS_IOLIST_OVERFLOW; }