in erts/emulator/beam/external.c [2380:2736]
static Eterm erts_term_to_binary_int(Process* p, Sint bif_ix, Eterm Term, Eterm opts,
int level, Uint64 dflags, Binary *context_b,
int iovec, Uint fragment_size)
{
Eterm *hp;
Eterm res;
Eterm c_term;
#ifndef EXTREME_TTB_TRAPPING
Sint reds = (Sint) (ERTS_BIF_REDS_LEFT(p) * TERM_TO_BINARY_LOOP_FACTOR);
#else
Sint reds = 20; /* For testing */
#endif
Sint initial_reds = reds;
TTBContext c_buff;
TTBContext *context = &c_buff;
ASSERT(bif_ix > 0 && IS_USMALL(!0, bif_ix));
ASSERT(bif_ix == BIF_term_to_binary_1 || bif_ix == BIF_term_to_binary_2
|| bif_ix == BIF_term_to_iovec_1 || bif_ix == BIF_term_to_iovec_2);
#define EXPORT_CONTEXT() \
do { \
if (context_b == NULL) { \
context_b = erts_create_magic_binary(sizeof(TTBContext), \
ttb_context_destructor);\
context = ERTS_MAGIC_BIN_DATA(context_b); \
sys_memcpy(context,&c_buff,sizeof(TTBContext)); \
} \
} while (0)
#define RETURN_STATE() \
do { \
hp = HAlloc(p, ERTS_MAGIC_REF_THING_SIZE + 1 + 4); \
c_term = erts_mk_magic_ref(&hp, &MSO(p), context_b); \
res = TUPLE4(hp, Term, opts, c_term, make_small(bif_ix)); \
BUMP_ALL_REDS(p); \
return res; \
} while (0);
if (context_b == NULL) {
/* Setup enough to get started */
context->state = TTBSize;
context->alive = 1;
ERTS_INIT_TTBSizeContext(&context->s.sc, dflags);
context->s.sc.level = level;
context->s.sc.fragment_size = fragment_size;
if (!level) {
context->s.sc.vlen = iovec ? 0 : -1;
context->s.sc.iovec = iovec;
}
else {
context->s.sc.vlen = -1;
context->s.sc.iovec = 0;
}
} else {
context = ERTS_MAGIC_BIN_DATA(context_b);
}
/* Initialization done, now we will go through the states */
for (;;) {
switch (context->state) {
case TTBSize:
{
Uint size, fragments = 1;
Binary *result_bin;
int level = context->s.sc.level;
Sint vlen;
iovec = context->s.sc.iovec;
fragment_size = context->s.sc.fragment_size;
size = 1; /* VERSION_MAGIC */
switch (encode_size_struct_int(&context->s.sc, NULL, Term,
context->s.sc.dflags, &reds,
&size)) {
case ERTS_EXT_SZ_SYSTEM_LIMIT:
BUMP_REDS(p, (initial_reds - reds) / TERM_TO_BINARY_LOOP_FACTOR);
return THE_NON_VALUE;
case ERTS_EXT_SZ_YIELD:
EXPORT_CONTEXT();
/* Same state */
RETURN_STATE();
case ERTS_EXT_SZ_OK:
break;
}
/* Move these to next state */
dflags = context->s.sc.dflags;
vlen = context->s.sc.vlen;
if (vlen >= 0) {
Uint total_size = size + context->s.sc.extra_size;
fragments = (total_size - 1)/fragment_size + 1;
vlen += 3*fragments;
ASSERT(vlen);
}
else if (size <= ERL_ONHEAP_BIN_LIMIT) {
/* Finish in one go */
res = erts_term_to_binary_simple(p, Term, size,
level, dflags);
if (iovec) {
Eterm *hp = HAlloc(p, 2);
res = CONS(hp, res, NIL);
}
BUMP_REDS(p, 1);
return res;
}
result_bin = erts_bin_nrml_alloc(size);
result_bin->orig_bytes[0] = (byte)VERSION_MAGIC;
/* Next state immediately, no need to export context */
context->state = TTBEncode;
ERTS_INIT_TTBEncodeContext(&context->s.ec, dflags);
context->s.ec.level = level;
context->s.ec.result_bin = result_bin;
context->s.ec.iovec = iovec;
if (vlen >= 0) {
Uint sz = erts_ttb_iov_size(!0, vlen, fragments);
char *ptr = (char *) erts_alloc(ERTS_ALC_T_T2B_VEC, sz);
erts_ttb_iov_init(&context->s.ec, !0, ptr, vlen,
fragments, fragment_size);
context->s.ec.cptr = (byte *) &result_bin->orig_bytes[0];
}
break;
}
case TTBEncode:
{
byte *endp, *tmp;
byte *bytes = (byte *) context->s.ec.result_bin->orig_bytes;
size_t real_size;
Binary *result_bin;
Sint realloc_offset;
Uint fragments;
dflags = context->s.ec.dflags;
if (enc_term_int(&context->s.ec, NULL,Term, bytes+1, dflags,
NULL, &reds, &endp) < 0) {
EXPORT_CONTEXT();
RETURN_STATE();
}
real_size = endp - bytes;
tmp = (byte *) &context->s.ec.result_bin->orig_bytes[0];
result_bin = erts_bin_realloc(context->s.ec.result_bin,real_size);
realloc_offset = (byte *) &result_bin->orig_bytes[0] - tmp;
level = context->s.ec.level;
BUMP_REDS(p, (initial_reds - reds) / TERM_TO_BINARY_LOOP_FACTOR);
if (level == 0 || real_size < 6) { /* We are done */
Sint cbin_refc_diff;
Eterm result, rb_term, *hp, *hp_end;
Uint hsz;
int ix;
SysIOVec *iov;
Eterm *termv;
return_normal:
fragments = context->s.ec.frag_ix + 1;
context->s.ec.result_bin = NULL;
context->alive = 0;
if (context_b && erts_refc_read(&context_b->intern.refc,0) == 0) {
erts_bin_free(context_b);
}
if (!context->s.ec.iov) {
hsz = PROC_BIN_SIZE + (iovec ? 2 : 0);
hp = HAlloc(p, hsz);
result = erts_build_proc_bin(&MSO(p), hp, result_bin);
if (iovec) {
hp += PROC_BIN_SIZE;
result = CONS(hp, result, NIL);
}
return result;
}
iovec = context->s.ec.iovec;
ASSERT(iovec);
iov = context->s.ec.iov;
termv = context->s.ec.termv;
ASSERT(context->s.ec.vlen <= context->s.ec.debug_vlen);
ASSERT(fragments <= context->s.ec.debug_fragments);
/* first two elements should be unused */
ASSERT(context->s.ec.vlen >= 3*fragments);
ASSERT(!iov[0].iov_base && !iov[0].iov_len);
ASSERT(!iov[1].iov_base && !iov[1].iov_len);
hsz = (2 /* cons */
+ (PROC_BIN_SIZE > ERL_SUB_BIN_SIZE
? PROC_BIN_SIZE
: ERL_SUB_BIN_SIZE)); /* max size per vec */
hsz *= context->s.ec.vlen - 2*fragments; /* number of vecs */
hp = HAlloc(p, hsz);
hp_end = hp + hsz;
rb_term = THE_NON_VALUE;
result = NIL;
ASSERT(erts_refc_read(&result_bin->intern.refc, 1) == 1);
cbin_refc_diff = -1;
for (ix = context->s.ec.vlen - 1; ix > 1; ix--) {
Eterm bin_term, pb_term;
Uint pb_size;
ProcBin *pb;
SysIOVec *iovp = &iov[ix];
if (!iovp->iov_base)
continue; /* empty slot for header */
pb_term = termv[ix];
if (is_value(pb_term)) {
pb_size = binary_size(pb_term);
pb = (ProcBin *) binary_val(pb_term);
}
else {
iovp->iov_base = (void *) (((byte *) iovp->iov_base)
+ realloc_offset);
pb_size = result_bin->orig_size;
if (is_non_value(rb_term))
pb = NULL;
else {
pb = (ProcBin *) binary_val(rb_term);
pb_term = rb_term;
}
}
/*
* We intentionally avoid using sub binaries
* since the GC might convert those to heap
* binaries and by this ruin the nice preparation
* for usage of this data as I/O vector in
* nifs/drivers.
*/
if (is_value(pb_term) && iovp->iov_len == pb_size)
bin_term = pb_term;
else {
Binary *bin;
if (is_value(pb_term)) {
bin = ((ProcBin *) binary_val(pb_term))->val;
erts_refc_inc(&bin->intern.refc, 2);
}
else {
bin = result_bin;
cbin_refc_diff++;
}
pb = (ProcBin *) (char *) hp;
hp += PROC_BIN_SIZE;
pb->thing_word = HEADER_PROC_BIN;
pb->size = (Uint) iovp->iov_len;
pb->next = MSO(p).first;
MSO(p).first = (struct erl_off_heap_header*) pb;
pb->val = bin;
pb->bytes = (byte*) iovp->iov_base;
pb->flags = 0;
OH_OVERHEAD(&MSO(p), pb->size / sizeof(Eterm));
bin_term = make_binary(pb);
}
result = CONS(hp, bin_term, result);
hp += 2;
}
ASSERT(hp <= hp_end);
HRelease(p, hp_end, hp);
context->s.ec.iov = NULL;
erts_free(ERTS_ALC_T_T2B_VEC, iov);
if (cbin_refc_diff) {
ASSERT(cbin_refc_diff >= -1);
if (cbin_refc_diff > 0)
erts_refc_add(&result_bin->intern.refc,
cbin_refc_diff, 1);
else
erts_bin_free(result_bin);
}
return result;
}
/* Continue with compression... */
/* To make absolutely sure that zlib does not barf on a reallocated context,
we make sure it's "exported" before doing anything compession-like */
EXPORT_CONTEXT();
bytes = (byte *) result_bin->orig_bytes; /* result_bin is reallocated */
if (erl_zlib_deflate_start(&(context->s.cc.stream),bytes+1,real_size-1,level)
!= Z_OK) {
goto return_normal;
}
context->state = TTBCompress;
context->s.cc.real_size = real_size;
context->s.cc.result_bin = result_bin;
result_bin = erts_bin_nrml_alloc(real_size);
result_bin->orig_bytes[0] = (byte) VERSION_MAGIC;
context->s.cc.destination_bin = result_bin;
context->s.cc.dest_len = 0;
context->s.cc.dbytes = (byte *) result_bin->orig_bytes+6;
break;
}
case TTBCompress:
{
uLongf tot_dest_len = context->s.cc.real_size - 6;
uLongf left = (tot_dest_len - context->s.cc.dest_len);
uLongf this_time = (left > TERM_TO_BINARY_COMPRESS_CHUNK) ?
TERM_TO_BINARY_COMPRESS_CHUNK :
left;
Binary *result_bin;
ProcBin *pb;
Uint max = (ERTS_BIF_REDS_LEFT(p) * TERM_TO_BINARY_COMPRESS_CHUNK) / CONTEXT_REDS;
if (max < this_time) {
this_time = max + 1; /* do not set this_time to 0 */
}
res = erl_zlib_deflate_chunk(&(context->s.cc.stream), context->s.cc.dbytes, &this_time);
context->s.cc.dbytes += this_time;
context->s.cc.dest_len += this_time;
switch (res) {
case Z_OK:
if (context->s.cc.dest_len >= tot_dest_len) {
goto no_use_compressing;
}
RETURN_STATE();
case Z_STREAM_END:
{
byte *dbytes = (byte *) context->s.cc.destination_bin->orig_bytes + 1;
dbytes[0] = COMPRESSED;
put_int32(context->s.cc.real_size-1,dbytes+1);
erl_zlib_deflate_finish(&(context->s.cc.stream));
result_bin = erts_bin_realloc(context->s.cc.destination_bin,
context->s.cc.dest_len+6);
context->s.cc.destination_bin = NULL;
ASSERT(erts_refc_read(&result_bin->intern.refc, 1));
erts_bin_free(context->s.cc.result_bin);
context->s.cc.result_bin = NULL;
context->alive = 0;
BUMP_REDS(p, (this_time * CONTEXT_REDS) / TERM_TO_BINARY_COMPRESS_CHUNK);
if (context_b && erts_refc_read(&context_b->intern.refc,0) == 0) {
erts_bin_free(context_b);
}
return erts_build_proc_bin(&MSO(p),
HAlloc(p, PROC_BIN_SIZE),
result_bin);
}
default: /* Compression error, revert to uncompressed binary (still in
context) */
no_use_compressing:
result_bin = context->s.cc.result_bin;
context->s.cc.result_bin = NULL;
pb = (ProcBin *) HAlloc(p, PROC_BIN_SIZE);
pb->thing_word = HEADER_PROC_BIN;
pb->size = context->s.cc.real_size;
pb->next = MSO(p).first;
MSO(p).first = (struct erl_off_heap_header*)pb;
pb->val = result_bin;
pb->bytes = (byte*) result_bin->orig_bytes;
pb->flags = 0;
OH_OVERHEAD(&(MSO(p)), pb->size / sizeof(Eterm));
ASSERT(erts_refc_read(&result_bin->intern.refc, 1));
erl_zlib_deflate_finish(&(context->s.cc.stream));
erts_bin_free(context->s.cc.destination_bin);
context->s.cc.destination_bin = NULL;
context->alive = 0;
BUMP_REDS(p, (this_time * CONTEXT_REDS) / TERM_TO_BINARY_COMPRESS_CHUNK);
if (context_b && erts_refc_read(&context_b->intern.refc,0) == 0) {
erts_bin_free(context_b);
}
return make_binary(pb);
}
}
}
}
#undef EXPORT_CONTEXT
#undef RETURN_STATE
}