static Eterm erts_term_to_binary_int()

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
}