static int subtract_get_length(Process *p, Eterm *iterator_p, Uint *count_p) { static const Sint ELEMENTS_PER_RED = 32; Sint budget, count; Eterm iterator; budget = ELEMENTS_PER_RED * ERTS_BIF_REDS_LEFT(p); iterator = *iterator_p; #ifdef DEBUG budget = budget / 10 + 1; #endif for (count = 0; count < budget && is_list(iterator); count++) { iterator = CDR(list_val(iterator)); } if (!is_list(iterator) && !is_nil(iterator)) { return -1; } BUMP_REDS(p, count / ELEMENTS_PER_RED); *iterator_p = iterator; *count_p += count; if (is_nil(iterator)) { return 1; } return 0; }
static BIF_RETTYPE lists_reverse_onheap(Process *c_p, Eterm list_in, Eterm tail_in) { static const Uint CELLS_PER_RED = 60; Eterm *alloc_start, *alloc_top, *alloc_end; Uint cells_left, max_cells; Eterm list, tail; list = list_in; tail = tail_in; cells_left = max_cells = CELLS_PER_RED * (1 + ERTS_BIF_REDS_LEFT(c_p)); ASSERT(HEAP_LIMIT(c_p) >= HEAP_TOP(c_p) + 2); alloc_start = HEAP_TOP(c_p); alloc_end = HEAP_LIMIT(c_p) - 2; alloc_top = alloc_start; /* Don't process more cells than we have reductions for. */ alloc_end = MIN(alloc_top + (cells_left * 2), alloc_end); while (alloc_top < alloc_end && is_list(list)) { Eterm *pair = list_val(list); tail = CONS(alloc_top, CAR(pair), tail); list = CDR(pair); alloc_top += 2; } cells_left -= (alloc_top - alloc_start) / 2; HEAP_TOP(c_p) = alloc_top; ASSERT(cells_left >= 0 && cells_left <= max_cells); BUMP_REDS(c_p, (max_cells - cells_left) / CELLS_PER_RED); if (is_nil(list)) { BIF_RET(tail); } else if (is_list(list)) { ASSERT(is_list(tail)); if (cells_left > CELLS_PER_RED) { return lists_reverse_alloc(c_p, list, tail); } BUMP_ALL_REDS(c_p); BIF_TRAP2(bif_export[BIF_lists_reverse_2], c_p, list, tail); } BIF_ERROR(c_p, BADARG); }
static Eterm do_info(Process* c_p, TrapData* trap_data) { HashTable* hash_table; Uint remaining; Uint idx; Uint max_iter; hash_table = trap_data->table; idx = trap_data->idx; #if defined(DEBUG) || defined(VALGRIND) max_iter = 50; #else max_iter = ERTS_BIF_REDS_LEFT(c_p); #endif remaining = trap_data->remaining < max_iter ? trap_data->remaining : max_iter; trap_data->remaining -= remaining; while (remaining != 0) { if (is_boxed(hash_table->term[idx])) { ErtsLiteralArea* area; area = term_to_area(hash_table->term[idx]); trap_data->memory += sizeof(ErtsLiteralArea) + sizeof(Eterm) * (area->end - area->start - 1); remaining--; } idx++; } trap_data->idx = idx; if (trap_data->remaining > 0) { return am_ok; /* Dummy return value */ } else { Eterm* hp; Eterm count_term; Eterm memory_term; Eterm res; Uint memory; Uint hsz = MAP_SZ(2); memory = sizeof(HashTable) + (trap_data->table->allocated-1) * sizeof(Eterm) + trap_data->memory; (void) erts_bld_uint(NULL, &hsz, hash_table->num_entries); (void) erts_bld_uint(NULL, &hsz, memory); hp = HAlloc(c_p, hsz); count_term = erts_bld_uint(&hp, NULL, hash_table->num_entries); memory_term = erts_bld_uint(&hp, NULL, memory); res = MAP2(hp, am_count, count_term, am_memory, memory_term); return res; } }
static BIF_RETTYPE lists_reverse_alloc(Process *c_p, Eterm list_in, Eterm tail_in) { static const Uint CELLS_PER_RED = 40; Eterm *alloc_top, *alloc_end; Uint cells_left, max_cells; Eterm list, tail; Eterm lookahead; list = list_in; tail = tail_in; cells_left = max_cells = CELLS_PER_RED * (1 + ERTS_BIF_REDS_LEFT(c_p)); lookahead = list; while (cells_left != 0 && is_list(lookahead)) { lookahead = CDR(list_val(lookahead)); cells_left--; } BUMP_REDS(c_p, (max_cells - cells_left) / CELLS_PER_RED); if (is_not_list(lookahead) && is_not_nil(lookahead)) { BIF_ERROR(c_p, BADARG); } alloc_top = HAlloc(c_p, 2 * (max_cells - cells_left)); alloc_end = alloc_top + 2 * (max_cells - cells_left); while (alloc_top < alloc_end) { Eterm *pair = list_val(list); tail = CONS(alloc_top, CAR(pair), tail); list = CDR(pair); ASSERT(is_list(list) || is_nil(list)); alloc_top += 2; } if (is_nil(list)) { BIF_RET(tail); } ASSERT(is_list(tail) && cells_left == 0); BIF_TRAP2(bif_export[BIF_lists_reverse_2], c_p, list, tail); }
static int subtract_set_build(Process *p, ErtsSubtractContext *context) { const static Sint INSERTIONS_PER_RED = 16; Sint budget, insertions; budget = INSERTIONS_PER_RED * ERTS_BIF_REDS_LEFT(p); insertions = 0; #ifdef DEBUG budget = budget / 10 + 1; #endif while (insertions < budget && is_list(context->iterator)) { subtract_tree_t *existing_node, *new_node; const Eterm *cell; Eterm value, next; cell = list_val(context->iterator); value = CAR(cell); next = CDR(cell); new_node = context->u.rhs_set.alloc; new_node->key = value; new_node->count = 1; existing_node = subtract_rbt_lookup_insert(&context->u.rhs_set.tree, new_node); if (existing_node != NULL) { existing_node->count++; } else { context->u.rhs_set.alloc++; } context->iterator = next; insertions++; } BUMP_REDS(p, insertions / INSERTIONS_PER_RED); ASSERT(is_list(context->iterator) || is_nil(context->iterator)); ASSERT(context->u.rhs_set.tree != NULL); return is_nil(context->iterator); }
static BIF_RETTYPE iol2v_continue(iol2v_state_t *state) { Eterm iterator; DECLARE_ESTACK(s); ESTACK_CHANGE_ALLOCATOR(s, ERTS_ALC_T_SAVED_ESTACK); state->bytereds_available = ERTS_BIF_REDS_LEFT(state->process) * IOL2V_SMALL_BIN_LIMIT; state->bytereds_spent = 0; if (state->estack.start) { ESTACK_RESTORE(s, &state->estack); } iterator = state->input_list; for(;;) { if (state->bytereds_spent >= state->bytereds_available) { ESTACK_SAVE(s, &state->estack); state->input_list = iterator; return iol2v_yield(state); } while (is_list(iterator)) { Eterm *cell; Eterm head; cell = list_val(iterator); head = CAR(cell); if (is_binary(head)) { if (!iol2v_append_binary(state, head)) { goto l_badarg; } iterator = CDR(cell); } else if (is_small(head)) { Eterm seq_end; if (!iol2v_append_byte_seq(state, iterator, &seq_end)) { goto l_badarg; } iterator = seq_end; } else if (is_list(head) || is_nil(head)) { Eterm tail = CDR(cell); if (!is_nil(tail)) { ESTACK_PUSH(s, tail); } state->bytereds_spent += 1; iterator = head; } else { goto l_badarg; } if (state->bytereds_spent >= state->bytereds_available) { ESTACK_SAVE(s, &state->estack); state->input_list = iterator; return iol2v_yield(state); } } if (is_binary(iterator)) { if (!iol2v_append_binary(state, iterator)) { goto l_badarg; } } else if (!is_nil(iterator)) { goto l_badarg; } if(ESTACK_ISEMPTY(s)) { break; } iterator = ESTACK_POP(s); } if (state->acc_size != 0) { iol2v_enqueue_result(state, iol2v_promote_acc(state)); } BUMP_REDS(state->process, state->bytereds_spent / IOL2V_SMALL_BIN_LIMIT); CLEAR_SAVED_ESTACK(&state->estack); DESTROY_ESTACK(s); BIF_RET(state->result_head); l_badarg: CLEAR_SAVED_ESTACK(&state->estack); DESTROY_ESTACK(s); if (state->acc != NULL) { erts_bin_free(state->acc); state->acc = NULL; } BIF_ERROR(state->process, BADARG); }
static Eterm do_get_all(Process* c_p, TrapData* trap_data, Eterm res) { HashTable* hash_table; Uint remaining; Uint idx; Uint max_iter; Uint i; Eterm* hp; Uint heap_size; struct copy_term { Uint key_size; Eterm* tuple_ptr; } *copy_data; hash_table = trap_data->table; idx = trap_data->idx; #if defined(DEBUG) || defined(VALGRIND) max_iter = 50; #else max_iter = ERTS_BIF_REDS_LEFT(c_p); #endif remaining = trap_data->remaining < max_iter ? trap_data->remaining : max_iter; trap_data->remaining -= remaining; copy_data = (struct copy_term *) erts_alloc(ERTS_ALC_T_TMP, remaining * sizeof(struct copy_term)); i = 0; heap_size = (2 + 3) * remaining; while (remaining != 0) { Eterm term = hash_table->term[idx]; if (is_tuple(term)) { Uint key_size; Eterm* tup_val; ASSERT(is_tuple_arity(term, 2)); tup_val = tuple_val(term); key_size = size_object(tup_val[1]); copy_data[i].key_size = key_size; copy_data[i].tuple_ptr = tup_val; heap_size += key_size; i++; remaining--; } idx++; } trap_data->idx = idx; hp = HAlloc(c_p, heap_size); remaining = i; for (i = 0; i < remaining; i++) { Eterm* tuple_ptr; Uint key_size; Eterm key; Eterm tup; tuple_ptr = copy_data[i].tuple_ptr; key_size = copy_data[i].key_size; key = copy_struct(tuple_ptr[1], key_size, &hp, &c_p->off_heap); tup = TUPLE2(hp, key, tuple_ptr[2]); hp += 3; res = CONS(hp, tup, res); hp += 2; } erts_free(ERTS_ALC_T_TMP, copy_data); return res; }
BIF_RETTYPE persistent_term_erase_1(BIF_ALIST_1) { static const Uint ITERATIONS_PER_RED = 32; ErtsPersistentTermErase1Context* ctx; Eterm state_mref = THE_NON_VALUE; long iterations_until_trap; long max_iterations; #ifdef DEBUG (void)ITERATIONS_PER_RED; iterations_until_trap = max_iterations = GET_SMALL_RANDOM_INT(ERTS_BIF_REDS_LEFT(BIF_P) + (Uint)&ctx); #else iterations_until_trap = max_iterations = ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(BIF_P); #endif #define ERASE_TRAP_CODE \ BIF_TRAP1(bif_export[BIF_persistent_term_erase_1], BIF_P, state_mref); #define TRAPPING_COPY_TABLE_ERASE(TABLE_DEST, OLD_TABLE, NEW_SIZE, REHASH, LOC_NAME) \ TRAPPING_COPY_TABLE(TABLE_DEST, OLD_TABLE, NEW_SIZE, REHASH, LOC_NAME, ERASE_TRAP_CODE) if (is_internal_magic_ref(BIF_ARG_1) && (ERTS_MAGIC_BIN_DESTRUCTOR(erts_magic_ref2bin(BIF_ARG_1)) == persistent_term_erase_1_ctx_bin_dtor)) { /* Restore the state after a trap */ Binary* state_bin; state_mref = BIF_ARG_1; state_bin = erts_magic_ref2bin(state_mref); ctx = ERTS_MAGIC_BIN_DATA(state_bin); ASSERT(BIF_P->flags & F_DISABLE_GC); erts_set_gc_state(BIF_P, 1); switch (ctx->trap_location) { case ERASE1_TRAP_LOCATION_TMP_COPY: goto L_ERASE1_TRAP_LOCATION_TMP_COPY; case ERASE1_TRAP_LOCATION_FINAL_COPY: goto L_ERASE1_TRAP_LOCATION_FINAL_COPY; } } else { /* Save state in magic bin in case trapping is necessary */ Eterm* hp; Binary* state_bin = erts_create_magic_binary(sizeof(ErtsPersistentTermErase1Context), persistent_term_erase_1_ctx_bin_dtor); hp = HAlloc(BIF_P, ERTS_MAGIC_REF_THING_SIZE); state_mref = erts_mk_magic_ref(&hp, &MSO(BIF_P), state_bin); ctx = ERTS_MAGIC_BIN_DATA(state_bin); /* * IMPORTANT: The following two fields are used to detect if * persistent_term_erase_1_ctx_bin_dtor needs to free memory */ ctx->cpy_ctx.new_table = NULL; ctx->tmp_table = NULL; } if (!try_seize_update_permission(BIF_P)) { ERTS_BIF_YIELD1(bif_export[BIF_persistent_term_erase_1], BIF_P, BIF_ARG_1); } ctx->key = BIF_ARG_1; ctx->old_table = (HashTable *) erts_atomic_read_nob(&the_hash_table); ctx->entry_index = lookup(ctx->old_table, ctx->key); ctx->old_term = ctx->old_table->term[ctx->entry_index]; if (is_boxed(ctx->old_term)) { Uint new_size; /* * Since we don't use any delete markers, we must rehash * the table when deleting terms to ensure that all terms * can still be reached if there are hash collisions. * We can't rehash in place and it would not be safe to modify * the old table yet, so we will first need a new * temporary table copy of the same size as the old one. */ ASSERT(is_tuple_arity(ctx->old_term, 2)); TRAPPING_COPY_TABLE_ERASE(ctx->tmp_table, ctx->old_table, ctx->old_table->allocated, ERTS_PERSISTENT_TERM_CPY_TEMP, ERASE1_TRAP_LOCATION_TMP_COPY); /* * Delete the term from the temporary table. Then copy the * temporary table to a new table, rehashing the entries * while copying. */ ctx->tmp_table->term[ctx->entry_index] = NIL; ctx->tmp_table->num_entries--; new_size = ctx->tmp_table->allocated; if (MUST_SHRINK(ctx->tmp_table)) { new_size /= 2; } TRAPPING_COPY_TABLE_ERASE(ctx->new_table, ctx->tmp_table, new_size, ERTS_PERSISTENT_TERM_CPY_REHASH, ERASE1_TRAP_LOCATION_FINAL_COPY); erts_free(ERTS_ALC_T_PERSISTENT_TERM_TMP, ctx->tmp_table); /* * IMPORTANT: Memory management depends on that ctx->tmp_table * is set to NULL on the line below */ ctx->tmp_table = NULL; mark_for_deletion(ctx->old_table, ctx->entry_index); erts_schedule_thr_prgr_later_op(table_updater, ctx->new_table, &thr_prog_op); suspend_updater(BIF_P); BUMP_REDS(BIF_P, (max_iterations - iterations_until_trap) / ITERATIONS_PER_RED); ERTS_BIF_YIELD_RETURN(BIF_P, am_true); } /* * Key is not present. Nothing to do. */ ASSERT(is_nil(ctx->old_term)); release_update_permission(0); BIF_RET(am_false); }
BIF_RETTYPE persistent_term_put_2(BIF_ALIST_2) { static const Uint ITERATIONS_PER_RED = 32; ErtsPersistentTermPut2Context* ctx; Eterm state_mref = THE_NON_VALUE; long iterations_until_trap; long max_iterations; #define PUT_TRAP_CODE \ BIF_TRAP2(bif_export[BIF_persistent_term_put_2], BIF_P, state_mref, BIF_ARG_2) #define TRAPPING_COPY_TABLE_PUT(TABLE_DEST, OLD_TABLE, NEW_SIZE, COPY_TYPE, LOC_NAME) \ TRAPPING_COPY_TABLE(TABLE_DEST, OLD_TABLE, NEW_SIZE, COPY_TYPE, LOC_NAME, PUT_TRAP_CODE) #ifdef DEBUG (void)ITERATIONS_PER_RED; iterations_until_trap = max_iterations = GET_SMALL_RANDOM_INT(ERTS_BIF_REDS_LEFT(BIF_P) + (Uint)&ctx); #else iterations_until_trap = max_iterations = ITERATIONS_PER_RED * ERTS_BIF_REDS_LEFT(BIF_P); #endif if (is_internal_magic_ref(BIF_ARG_1) && (ERTS_MAGIC_BIN_DESTRUCTOR(erts_magic_ref2bin(BIF_ARG_1)) == persistent_term_put_2_ctx_bin_dtor)) { /* Restore state after a trap */ Binary* state_bin; state_mref = BIF_ARG_1; state_bin = erts_magic_ref2bin(state_mref); ctx = ERTS_MAGIC_BIN_DATA(state_bin); ASSERT(BIF_P->flags & F_DISABLE_GC); erts_set_gc_state(BIF_P, 1); switch (ctx->trap_location) { case PUT2_TRAP_LOCATION_NEW_KEY: goto L_PUT2_TRAP_LOCATION_NEW_KEY; case PUT2_TRAP_LOCATION_REPLACE_VALUE: goto L_PUT2_TRAP_LOCATION_REPLACE_VALUE; } } else { /* Save state in magic bin in case trapping is necessary */ Eterm* hp; Binary* state_bin = erts_create_magic_binary(sizeof(ErtsPersistentTermPut2Context), persistent_term_put_2_ctx_bin_dtor); hp = HAlloc(BIF_P, ERTS_MAGIC_REF_THING_SIZE); state_mref = erts_mk_magic_ref(&hp, &MSO(BIF_P), state_bin); ctx = ERTS_MAGIC_BIN_DATA(state_bin); /* * IMPORTANT: The following field is used to detect if * persistent_term_put_2_ctx_bin_dtor needs to free memory */ ctx->cpy_ctx.new_table = NULL; } if (!try_seize_update_permission(BIF_P)) { ERTS_BIF_YIELD2(bif_export[BIF_persistent_term_put_2], BIF_P, BIF_ARG_1, BIF_ARG_2); } ctx->hash_table = (HashTable *) erts_atomic_read_nob(&the_hash_table); ctx->key = BIF_ARG_1; ctx->term = BIF_ARG_2; ctx->entry_index = lookup(ctx->hash_table, ctx->key); ctx->heap[0] = make_arityval(2); ctx->heap[1] = ctx->key; ctx->heap[2] = ctx->term; ctx->tuple = make_tuple(ctx->heap); if (is_nil(ctx->hash_table->term[ctx->entry_index])) { Uint new_size = ctx->hash_table->allocated; if (MUST_GROW(ctx->hash_table)) { new_size *= 2; } TRAPPING_COPY_TABLE_PUT(ctx->hash_table, ctx->hash_table, new_size, ERTS_PERSISTENT_TERM_CPY_NO_REHASH, PUT2_TRAP_LOCATION_NEW_KEY); ctx->entry_index = lookup(ctx->hash_table, ctx->key); ctx->hash_table->num_entries++; } else { Eterm tuple = ctx->hash_table->term[ctx->entry_index]; Eterm old_term; ASSERT(is_tuple_arity(tuple, 2)); old_term = boxed_val(tuple)[2]; if (EQ(ctx->term, old_term)) { /* Same value. No need to update anything. */ release_update_permission(0); BIF_RET(am_ok); } else { /* Mark the old term for deletion. */ mark_for_deletion(ctx->hash_table, ctx->entry_index); TRAPPING_COPY_TABLE_PUT(ctx->hash_table, ctx->hash_table, ctx->hash_table->allocated, ERTS_PERSISTENT_TERM_CPY_NO_REHASH, PUT2_TRAP_LOCATION_REPLACE_VALUE); } } { Uint term_size; Uint lit_area_size; ErlOffHeap code_off_heap; ErtsLiteralArea* literal_area; erts_shcopy_t info; Eterm* ptr; /* * Preserve internal sharing in the term by using the * sharing-preserving functions. However, literals must * be copied in case the module holding them are unloaded. */ INITIALIZE_SHCOPY(info); info.copy_literals = 1; term_size = copy_shared_calculate(ctx->tuple, &info); ERTS_INIT_OFF_HEAP(&code_off_heap); lit_area_size = ERTS_LITERAL_AREA_ALLOC_SIZE(term_size); literal_area = erts_alloc(ERTS_ALC_T_LITERAL, lit_area_size); ptr = &literal_area->start[0]; literal_area->end = ptr + term_size; ctx->tuple = copy_shared_perform(ctx->tuple, term_size, &info, &ptr, &code_off_heap); ASSERT(tuple_val(ctx->tuple) == literal_area->start); literal_area->off_heap = code_off_heap.first; DESTROY_SHCOPY(info); erts_set_literal_tag(&ctx->tuple, literal_area->start, term_size); ctx->hash_table->term[ctx->entry_index] = ctx->tuple; erts_schedule_thr_prgr_later_op(table_updater, ctx->hash_table, &thr_prog_op); suspend_updater(BIF_P); } BUMP_REDS(BIF_P, (max_iterations - iterations_until_trap) / ITERATIONS_PER_RED); ERTS_BIF_YIELD_RETURN(BIF_P, am_ok); }
static int subtract_set_finish(Process *p, ErtsSubtractContext *context) { const Sint CHECKS_PER_RED = 8; Sint checks, budget; budget = CHECKS_PER_RED * ERTS_BIF_REDS_LEFT(p); checks = 0; #ifdef DEBUG budget = budget / 10 + 1; #endif while (checks < budget && is_list(context->iterator)) { subtract_tree_t *node; const Eterm *cell; Eterm value, next; cell = list_val(context->iterator); value = CAR(cell); next = CDR(cell); ASSERT(context->rhs_remaining > 0); node = subtract_rbt_lookup(context->u.rhs_set.tree, value); if (node == NULL) { Eterm *hp = HAllocX(p, 2, context->lhs_remaining * 2); *context->result_cdr = make_list(hp); context->result_cdr = &CDR(hp); CAR(hp) = value; } else { if (context->rhs_remaining-- == 1) { *context->result_cdr = next; BUMP_REDS(p, checks / CHECKS_PER_RED); return 1; } if (node->count-- == 1) { subtract_rbt_delete(&context->u.rhs_set.tree, node); } } context->iterator = next; context->lhs_remaining--; checks++; } *context->result_cdr = NIL; BUMP_REDS(p, checks / CHECKS_PER_RED); if (is_list(context->iterator)) { ASSERT(context->lhs_remaining > 0 && context->rhs_remaining > 0); return 0; } return 1; }
static int subtract_naive_rhs(Process *p, ErtsSubtractContext *context) { const Sint CHECKS_PER_RED = 16; Sint checks, budget; budget = CHECKS_PER_RED * ERTS_BIF_REDS_LEFT(p); checks = 0; #ifdef DEBUG budget = budget / 10 + 1; #endif while (checks < budget && is_list(context->iterator)) { const Eterm *cell; Eterm value, next; int found_at; cell = list_val(context->iterator); value = CAR(cell); next = CDR(cell); for (found_at = context->rhs_remaining - 1; found_at >= 0; found_at--) { if (EQ(value, context->u.rhs_elements[found_at])) { break; } } if (found_at < 0) { /* Destructively add the value to the result. This is safe * since the GC is disabled and the unfinished term is never * leaked to the outside world. */ Eterm *hp = HAllocX(p, 2, context->lhs_remaining * 2); *context->result_cdr = make_list(hp); context->result_cdr = &CDR(hp); CAR(hp) = value; } else if (found_at >= 0) { Eterm swap; if (context->rhs_remaining-- == 1) { /* We've run out of items to remove, so the rest of the * result will be equal to the remainder of the input. We know * that LHS is well-formed as any errors would've been reported * during length determination. */ *context->result_cdr = next; BUMP_REDS(p, MIN(budget, checks) / CHECKS_PER_RED); return 1; } swap = context->u.rhs_elements[context->rhs_remaining]; context->u.rhs_elements[found_at] = swap; } checks += context->rhs_remaining; context->iterator = next; context->lhs_remaining--; } /* The result only has to be terminated when returning it to the user, but * we're doing it when trapping as well to prevent headaches when * debugging. */ *context->result_cdr = NIL; BUMP_REDS(p, MIN(budget, checks) / CHECKS_PER_RED); if (is_list(context->iterator)) { ASSERT(context->lhs_remaining > 0 && context->rhs_remaining > 0); return 0; } return 1; }
static int subtract_naive_lhs(Process *p, ErtsSubtractContext *context) { const Sint CHECKS_PER_RED = 16; Sint checks, budget; budget = CHECKS_PER_RED * ERTS_BIF_REDS_LEFT(p); checks = 0; while (checks < budget && is_list(context->iterator)) { const Eterm *cell; Eterm value, next; int found_at; cell = list_val(context->iterator); value = CAR(cell); next = CDR(cell); for (found_at = 0; found_at < context->lhs_remaining; found_at++) { if (EQ(value, context->u.lhs_elements[found_at])) { /* We shift the array one step down as we have to preserve * order. * * Note that we can't exit early as that would suppress errors * in the right-hand side (this runs prior to determining the * length of RHS). */ context->lhs_remaining--; sys_memmove(&context->u.lhs_elements[found_at], &context->u.lhs_elements[found_at + 1], (context->lhs_remaining - found_at) * sizeof(Eterm)); break; } } checks += MAX(1, context->lhs_remaining); context->iterator = next; } BUMP_REDS(p, MIN(checks, budget) / CHECKS_PER_RED); if (is_list(context->iterator)) { return 0; } else if (!is_nil(context->iterator)) { return -1; } if (context->lhs_remaining > 0) { Eterm *hp; int i; hp = HAlloc(p, context->lhs_remaining * 2); for (i = context->lhs_remaining - 1; i >= 0; i--) { Eterm value = context->u.lhs_elements[i]; context->result = CONS(hp, value, context->result); hp += 2; } } ASSERT(context->lhs_remaining > 0 || context->result == NIL); return 1; }