#include #include #include #include "prolog.h" #include "query.h" struct heap_save { cell *heap; pl_idx size, hp; }; size_t alloc_grow(query *q, void **addr, size_t elem_size, size_t min_elements, size_t max_elements) { if (min_elements > max_elements) max_elements = min_elements; // Cap single allocations when memory limiting is enabled (8GB). // If only the optimistic max overshoots (e.g. check_slot's num*3/2), // clamp to the largest fitting size instead of failing when min fits. const uint64_t limit_bytes = 1024ULL * 1024ULL * 1024ULL * 8ULL; const size_t effective_limit = limit_bytes > SIZE_MAX ? SIZE_MAX : (size_t)limit_bytes; if (q->pl->limit && elem_size) { size_t capped = effective_limit / elem_size; if (max_elements > capped) { if (capped < min_elements) { q->oom = true; return 0; } max_elements = capped; } } size_t elements = max_elements; void *mem = NULL; do { mem = TPL_realloc(*addr, elem_size * elements); if (mem) break; elements = min_elements + (elements - min_elements) / 2; } while (elements > min_elements); if (!mem) { q->oom = true; return 0; } *addr = mem; return elements; } cell *init_tmp_heap(query *q) { if (!q->tmp_heap) { q->tmp_heap = TPL_malloc(q->tmph_size * sizeof(cell)); if (!q->tmp_heap) return NULL; } q->tmphp = 0; return q->tmp_heap; } // The tmp heap is used for temporary allocations (a scratch-pad) // for work in progress. As such it can survive a TPL_realloc() call. // No need to incr refcnt on tmp heap cells. cell *alloc_tmp(query *q, unsigned num_cells) { pl_idx new_size = q->tmphp + num_cells; if (new_size >= q->tmph_size) { size_t elements = alloc_grow(q, (void**)&q->tmp_heap, sizeof(cell), new_size, new_size*5/4); if (!elements) return NULL; q->tmph_size = elements; } cell *c = q->tmp_heap + q->tmphp; q->tmphp = new_size; return c; } static inline bool ref_is_live(const query *q, const cell *c) { if (c->val_ctx >= q->st.fp) return false; const frame *f = GET_FRAME(c->val_ctx); return c->var_num < f->actual_slots; } #define deep_copy(c) \ (!q->noderef || (is_ref(c) && (c->val_ctx <= q->st.cur_ctx) && ref_is_live(q, c) && !is_anon(c))) // The slot a variable ultimately names, following the chain of refs the // way deref() does. static slot *ultimate_slot(const query *q, const cell *c, pl_ctx c_ctx) { if (is_ref(c)) c_ctx = c->val_ctx; const frame *f = GET_FRAME(c_ctx); slot *e = get_slot(q, f, c->var_num); while (is_var(&e->c)) { c_ctx = e->c.val_ctx; c = &e->c; if (is_ref(c)) c_ctx = c->val_ctx; f = GET_FRAME(c_ctx); slot *e2 = get_slot(q, f, c->var_num); if (e == e2) break; e = e2; } return e; } // Whether a variable in the term being copied denotes the same thing as // the variable copy_term/2 is replacing, so that a reference back to it // becomes the target variable instead of a newly invented one. // // Comparing var_num and context is not enough, and neither is comparing // slots. Given // // cyclic(X) :- X = f(g(X,_),_). // wrap(X, Y) :- copy_term(X, Y). // // the back-reference inside the term names the slot of whoever built it // (cyclic/1's X), while copy_term/2 is handed the term through another // slot again (wrap/2's X), arguments being passed by value. All three // name the same term, which is what makes the reference a cycle, so a // bound variable is compared by the term it denotes. Getting this wrong // left copy_term/2 with nothing to replace, and it then invented a // variable for the back-reference: an acyclic unrolling of a cyclic // term, with one variable too many (#1002). // // A term is a cell together with a context, and both halves have to // match: activations of a recursive clause share its cells and are told // apart only by their context. static bool denotes_same(query *q, cell *c, pl_ctx c_ctx, const cell *from_val, pl_ctx from_val_ctx, const slot *from_e) { if (!from_val) return ultimate_slot(q, c, c_ctx) == from_e; const pl_ctx save_latest_ctx = q->latest_ctx; const cell *val = deref(q, c, c_ctx); const pl_ctx val_ctx = q->latest_ctx; q->latest_ctx = save_latest_ctx; return (val == from_val) && (val_ctx == from_val_ctx); } // Whether a dereferenced argument is a reference back to the whole term // being copied. Only the raw argument tells us: dereferencing it lands // on the term itself, and it is the variable that named it which has to // be kept, for copy_vars() to turn into the target variable. Descending // instead copies one level of the cycle before catching it, so that each // copy of a cyclic term came out larger than the last. // // The context matters as much as the cell: the arguments of a recursive // clause's head are the same cells at every depth, and comparing cells // alone takes the deeper one for the term the copy started from. // // Callers test the raw argument for being a variable first: that cell has // just been read, whereas this walks off into the query. static bool cycles_back(const query *q, const cell *c, pl_ctx c_ctx) { return q->clone_root && (c == q->clone_root) && (c_ctx == q->clone_root_ctx); } // Note: convert vars to refs // Note: doesn't increment ref counts // Used by clone_term_to_tmp_internal() to walk plain (non-list) compound // terms iteratively instead of recursing, one frame per unfinished compound. // 'e'/'save_vgen' belong to the *parent* argument slot that caused us to // descend into this node, and are restored once this node (and everything // beneath it) has been fully cloned - i.e. at the point the recursive call // would otherwise have returned. typedef struct { lnode hdr; cell *p1; pl_ctx p1_ctx; int arity; pl_idx save_idx; unsigned depth; slot *e; uint32_t save_vgen; } snode; static cell *clone_term_to_tmp_internal(query *q, cell *p1, pl_ctx p1_ctx, unsigned depth) { #if 0 if (depth >= g_max_depth) { printf("*** OOPS %s %d\n", __FILE__, __LINE__); q->cycle_error = true; return NULL; } #endif pl_idx save_idx = tmp_heap_used(q); cell *tmp = alloc_tmp(q, 1); if (!tmp) return NULL; copy_cells(tmp, p1, 1); if (is_var(p1)) q->has_vars = true; if (is_var(tmp) && !is_ref(tmp) && !q->noderef) { tmp->flags |= FLAG_VAR_REF; tmp->val_ctx = p1_ctx; } if (!is_compound(p1)) return tmp; if (is_iso_list(p1)) { cell *save_p1 = p1; pl_ctx save_p1_ctx = p1_ctx; bool any1 = false, any2 = false; while (is_iso_list(p1)) { slot *e = NULL; cell *h = p1 + 1; pl_ctx h_ctx = p1_ctx; uint32_t save_vgen = 0; int both = 0; if (deep_copy(h)) DEREF_CHECKED(any1, both, save_vgen, e, e->vgen, h, h_ctx, q->vgen); if (both) q->cycle_error = q->cycle_dropped = true; if (is_var(p1 + 1) && cycles_back(q, h, h_ctx)) { h = p1 + 1; h_ctx = p1_ctx; q->cycle_error = true; } cell *rec = clone_term_to_tmp_internal(q, h, h_ctx, depth+1); if (!rec) return NULL; if (e) e->vgen = save_vgen; p1 = p1 + 1; p1 += p1->num_cells; cell *t = p1; pl_ctx t_ctx = p1_ctx; if (is_var(t) && (t->var_num == q->dump_var_num) && (t_ctx == q->dump_var_ctx)) { q->cycle_error = true; break; } both = 0; if (deep_copy(t)) DEREF_CHECKED(any2, both, save_vgen, e, e->vgen, t, t_ctx, q->vgen); if (both) q->cycle_error = q->cycle_dropped = true; if (is_var(p1) && cycles_back(q, t, t_ctx)) { t = p1; t_ctx = p1_ctx; q->cycle_error = true; } p1 = t; p1_ctx = t_ctx; if (is_iso_list(p1)) { cell *tmp = alloc_tmp(q, 1); if (!tmp) return NULL; copy_cells(tmp, p1, 1); } } cell *rec = clone_term_to_tmp_internal(q, p1, p1_ctx, depth+1); if (!rec) return NULL; if (any2) { p1 = save_p1; p1_ctx = save_p1_ctx; while (is_iso_list(p1) && !q->cycle_error) { p1 = p1 + 1; p1 += p1->num_cells; cell *c = p1; pl_ctx c_ctx = p1_ctx; RESTORE_VAR(c, c_ctx, p1, p1_ctx, q->vgen); } } tmp = get_tmp_heap(q, save_idx); tmp->num_cells = tmp_heap_used(q) - save_idx; if (!q->has_vars) tmp->flags |= FLAG_INTERNED_GROUND; return tmp; } // Transform recursion into stack iteration (as in terms.c)... list stack = {0}; snode *n = TPL_malloc(sizeof(snode)); if (!n) return NULL; n->arity = p1->arity; n->p1 = p1 + 1; n->p1_ctx = p1_ctx; n->save_idx = save_idx; n->depth = depth; n->e = NULL; n->save_vgen = 0; list_push_back(&stack, n); cell *result = NULL; while ((n = (snode*)list_back(&stack)) != NULL) { if (n->arity <= 0) { // This node's arguments are all done, so finalize it. This is // the point at which a recursive call would have returned. tmp = get_tmp_heap(q, n->save_idx); tmp->num_cells = tmp_heap_used(q) - n->save_idx; if (!q->has_vars) tmp->flags |= FLAG_INTERNED_GROUND; result = tmp; slot *pending_e = n->e; uint32_t pending_vgen = n->save_vgen; list_pop_back(&stack); TPL_free(n); if (pending_e) pending_e->vgen = pending_vgen; continue; } n->arity--; slot *e = NULL; cell *c = n->p1; pl_ctx c_ctx = n->p1_ctx; uint32_t save_vgen = 0; bool any = false; int both = 0; if (deep_copy(c)) DEREF_CHECKED(any, both, save_vgen, e, e->vgen, c, c_ctx, q->vgen); if (both) q->cycle_error = q->cycle_dropped = true; if (is_var(n->p1) && cycles_back(q, c, c_ctx)) { c = n->p1; c_ctx = n->p1_ctx; q->cycle_error = true; } n->p1 += n->p1->num_cells; if (is_compound(c) && !is_iso_list(c)) { // Instead of recursing, push a new frame and keep iterating. // The (e, save_vgen) pair travels with the child frame and is // restored once the child (its whole subtree) is finished. pl_idx child_idx = tmp_heap_used(q); cell *child = alloc_tmp(q, 1); if (!child) { while ((n = (snode*)list_pop_back(&stack)) != NULL) TPL_free(n); return NULL; } copy_cells(child, c, 1); snode *cn = TPL_malloc(sizeof(snode)); if (!cn) { while ((n = (snode*)list_pop_back(&stack)) != NULL) TPL_free(n); return NULL; } cn->arity = c->arity; cn->p1 = c + 1; cn->p1_ctx = c_ctx; cn->save_idx = child_idx; cn->depth = n->depth + 1; cn->e = e; cn->save_vgen = save_vgen; list_push_back(&stack, cn); } else { // Atoms, variables and lists still recurse (lists have their // own cycle-aware traversal above, and atoms/variables only // ever recurse one level deep). cell *rec = clone_term_to_tmp_internal(q, c, c_ctx, n->depth+1); if (!rec) { while ((n = (snode*)list_pop_back(&stack)) != NULL) TPL_free(n); return NULL; } if (e) e->vgen = save_vgen; } } return result; } cell *clone_term_to_tmp(query *q, cell *p1, pl_ctx p1_ctx) { q->cycle_dropped = false; if (++q->vgen == 0) q->vgen = 1; q->has_vars = false; cell *rec = clone_term_to_tmp_internal(q, p1, p1_ctx, 0); if (!rec) return NULL; return rec; } cell *append_to_tmp(query *q, cell *p1, pl_ctx p1_ctx) { cell *tmp = alloc_tmp(q, p1->num_cells); if (!tmp) return NULL; copy_cells_by_ref(tmp, p1, p1_ctx, p1->num_cells); return tmp; } static int accum_slot(query *q, size_t slot_nbr, unsigned var_num) { const void *vnbr; if (q->vars && sl_get(q->vars, (void*)slot_nbr, &vnbr)) return (unsigned)(size_t)vnbr; if (!q->vars) q->vars = sl_create(NULL, NULL, NULL); sl_app(q->vars, (void*)slot_nbr, (void*)(size_t)var_num); return -1; } static bool copy_vars(query *q, cell *c, bool copy_attrs, cell *from, pl_ctx from_ctx, cell *to, pl_ctx to_ctx) { unsigned num_cells = c->num_cells; unsigned cnt = 0; const slot *from_e = NULL; // the slot 'from' names cell *from_val = NULL; // the term it denotes, if bound pl_ctx from_val_ctx = 0; if (from) { const pl_ctx save_latest_ctx = q->latest_ctx; from_val = deref(q, from, from_ctx); from_val_ctx = q->latest_ctx; q->latest_ctx = save_latest_ctx; from_e = ultimate_slot(q, from, from_ctx); if (is_var(from_val)) from_val = NULL; } for (unsigned i = 0; i < num_cells; i++, c++) { if (!is_ref(c)) continue; c->flags |= FLAG_VAR_LOCAL; if (from && denotes_same(q, c, c->val_ctx, from_val, from_val_ctx, from_e)) { c->var_num = to->var_num; c->val_ctx = to_ctx; // BUGFIX: the replacement fast-path must still carry over // attributes from the source variable onto the target. if (copy_attrs && !c->tmp_attrs) { cell *attrs = from_e->c.val_attrs; if (attrs) { cell *save_tmp_heap = q->tmp_heap; pl_idx save_tmp_hp = q->tmphp; q->tmp_heap = NULL; cell *tmp = copy_term_to_heap(q, attrs, q->st.cur_ctx, false); CHECKED(tmp); c->tmp_attrs = tmp; TPL_free(q->tmp_heap); q->tmp_heap = save_tmp_heap; q->tmphp = save_tmp_hp; } } } else { const frame *f = GET_FRAME(c->val_ctx); // NB. get_ordered_slot_num is pure arithmetic (no deref), so // it is safe even when c->val_ctx names a long-dead frame, as // happens when rebasing an imported (detached) term image. // Only consult the slot itself when attributes are wanted: // dereferencing a dead frame's slot is undefined. const size_t slot_nbr = get_ordered_slot_num(q, f, c->var_num); cell *attrs = NULL; if (copy_attrs) { const slot *e = get_slot(q, f, c->var_num); attrs = c->tmp_attrs ? c->tmp_attrs : e->c.val_attrs; } int var_num; if ((var_num = accum_slot(q, slot_nbr, q->varno)) == -1) { var_num = q->varno++; cnt++; if (create_vars(q, 1) < 0) return false; } if (!q->tab_idx) { q->tab0_varno = var_num; q->tab_idx++; } c->var_num = var_num; c->val_ctx = q->st.cur_ctx; if (copy_attrs && attrs) { cell *save_tmp_heap = q->tmp_heap; pl_idx save_tmp_hp = q->tmphp; q->tmp_heap = NULL; if (!c->tmp_attrs) { cell *tmp = from ?copy_term_to_heap_with_replacement(q, attrs, q->st.cur_ctx, false, from, from_ctx, to, to_ctx) :copy_term_to_heap(q, attrs, q->st.cur_ctx, false); CHECKED(tmp); c->tmp_attrs = tmp; } TPL_free(q->tmp_heap); q->tmp_heap = save_tmp_heap; q->tmphp = save_tmp_hp; } } } return true; } unsigned rebase_term(query *q, cell *c, unsigned start_nbr, bool copy_attrs) { q->vars = NULL; q->varno = start_nbr; q->tab_idx = 0; if (!copy_vars(q, c, copy_attrs, NULL, 0, NULL, 0)) { if (q->vars) sl_destroy(q->vars); q->vars = NULL; return q->varno; } if (q->vars) { sl_destroy(q->vars); q->vars = NULL; } // Turn refs back into vars to recontextualize cell *tmp = c; for (unsigned i = 0; i < c->num_cells; i++, tmp++) { if (!is_ref(tmp)) continue; tmp->flags &= ~FLAG_VAR_REF; } return q->varno; } static cell *copy_term_to_tmp_with_replacement(query *q, cell *p1, pl_ctx p1_ctx, bool copy_attrs, cell *from, pl_ctx from_ctx, cell *to, pl_ctx to_ctx) { cell *c = deref(q, p1, p1_ctx); pl_ctx c_ctx = q->latest_ctx; // Have the walk stop at references back to the whole term, but only // when there is a target variable for such a reference to become: // without one it has to be a newly invented variable wherever the // cycle is caught, and catching it later at least keeps a level of // the structure. cell *save_root = q->clone_root; pl_ctx save_root_ctx = q->clone_root_ctx; q->clone_root = (from && to && is_compound(c)) ? c : NULL; q->clone_root_ctx = c_ctx; cell *tmp = clone_term_to_tmp(q, c, c_ctx); q->clone_root = save_root; q->clone_root_ctx = save_root_ctx; if (!tmp) return NULL; bool created = false; if (!q->vars) { created = true; const frame *f = GET_CURR_FRAME(); q->varno = f->actual_slots; q->tab_idx = 0; } bool ok = copy_vars(q, tmp, copy_attrs, from, from_ctx, to, to_ctx); if (created) { if (q->vars) sl_destroy(q->vars); q->vars = NULL; } c = tmp; for (pl_idx i = 0; i < tmp->num_cells; i++, c++) { if (is_var(c) && copy_attrs && c->tmp_attrs) { const frame *f = GET_FRAME(c->val_ctx); slot *e = get_slot(q, f, c->var_num); e->c.val_attrs = c->tmp_attrs; c->tmp_attrs = NULL; if (!add_trail(q, c->val_ctx, c->var_num, NULL)) return NULL; } } return ok ? tmp : NULL; } cell *copy_term_to_tmp(query *q, cell *p1, pl_ctx p1_ctx, bool copy_attrs) { q->has_vars = false; return copy_term_to_tmp_with_replacement(q, p1, p1_ctx, copy_attrs, NULL, 0, NULL, 0); } cell *alloc_heap(query *q, unsigned num_cells) { size_t page_size = q->heap_pages ? q->heap_pages->page_size*2 : q->heap_size; if (!q->heap_pages || ((q->st.hp + num_cells) >= q->heap_pages->page_size)) { page *a = TPL_calloc(1, sizeof(page)); if (!a) return NULL; a->next = q->heap_pages; unsigned n = MAX_OF(page_size, num_cells); a->cells = TPL_calloc(a->page_size=n, sizeof(cell)); if (!a->cells) { TPL_free(a); return NULL; } a->num = q->st.hp_num++; q->heap_pages = a; q->st.hp = 0; } cell *c = q->heap_pages->cells + q->st.hp; q->st.hp += num_cells; q->heap_pages->idx = q->st.hp; return c; } void trim_heap(query *q) { for (page *a = q->heap_pages; a;) { if (a->num <= q->st.hp_num) break; cell *c = a->cells; for (pl_idx i = 0; i < a->idx; i++, c++) unshare_cell(c); page *save = a; q->heap_pages = a = a->next; TPL_free(save->cells); TPL_free(save); } if (!q->heap_pages) return; while (q->heap_pages->idx > q->st.hp) { cell *c = q->heap_pages->cells + --q->heap_pages->idx; unshare_cell(c); c->tag = TAG_EMPTY; c->val_attrs = NULL; } } cell *clone_term_to_heap(query *q, cell *p1, pl_ctx p1_ctx) { if (!init_tmp_heap(q)) return NULL; q->has_vars = false; p1 = clone_term_to_tmp(q, p1, p1_ctx); if (!p1) return p1; cell *tmp = alloc_heap(q, p1->num_cells); if (!tmp) return NULL; dup_cells(tmp, p1, p1->num_cells); return tmp; } cell *copy_term_to_heap_with_replacement(query *q, cell *p1, pl_ctx p1_ctx, bool copy_attrs, cell *from, pl_ctx from_ctx, cell *to, pl_ctx to_ctx) { if (!init_tmp_heap(q)) return NULL; cell *tmp = copy_term_to_tmp_with_replacement(q, p1, p1_ctx, copy_attrs, is_var(from)?from:NULL, from_ctx, is_var(to)?to:NULL, to_ctx); if (!tmp) return tmp; cell *tmp2 = alloc_heap(q, tmp->num_cells); if (!tmp2) return NULL; dup_cells(tmp2, tmp, tmp->num_cells); if (!copy_attrs) return tmp2; cell *c = tmp2; for (pl_idx i = 0; i < tmp2->num_cells; i++, c++) { if (is_var(c) && copy_attrs && c->tmp_attrs) { const frame *f = GET_FRAME(c->val_ctx); slot *e = get_slot(q, f, c->var_num); e->c.val_attrs = c->tmp_attrs; c->tmp_attrs = NULL; } } return tmp2; } cell *copy_term_to_heap(query *q, cell *p1, pl_ctx p1_ctx, bool copy_attrs) { if (!init_tmp_heap(q)) return NULL; q->has_vars = false; cell *tmp = copy_term_to_tmp_with_replacement(q, p1, p1_ctx, copy_attrs, NULL, 0, NULL, 0); if (!tmp) return tmp; cell *tmp2 = alloc_heap(q, tmp->num_cells); if (!tmp2) return NULL; dup_cells(tmp2, tmp, tmp->num_cells); if (!copy_attrs) return tmp2; cell *c = tmp2; for (pl_idx i = 0; i < tmp2->num_cells; i++, c++) { if (is_var(c) && copy_attrs && c->tmp_attrs) { const frame *f = GET_FRAME(c->val_ctx); slot *e = get_slot(q, f, c->var_num); e->c.val_attrs = c->tmp_attrs; c->tmp_attrs = NULL; if (!add_trail(q, c->val_ctx, c->var_num, NULL)) return NULL; } } return tmp2; } void fix_list(cell *c) { pl_idx cnt = c->num_cells; while (is_iso_list(c)) { c->num_cells = cnt; c = c + 1; // skip . cnt -= 1 + c->num_cells; c = c + c->num_cells; // skip head } } cell *allocate_list(query *q, const cell *c) { if (!init_tmp_heap(q)) return NULL; append_list(q, c); return get_tmp_heap(q, 0); } cell *append_list(query *q, const cell *c) { cell *tmp = alloc_tmp(q, 1+c->num_cells); if (!tmp) return NULL; cell *save = tmp; tmp->tag = TAG_INTERNED; tmp->num_cells = 1 + c->num_cells; tmp->val_off = g_dot_s; tmp->arity = 2; tmp->flags = 0; tmp++; copy_cells(tmp, c, c->num_cells); return save; } cell *end_list(query *q) { cell *tmp = alloc_tmp(q, 1); if (!tmp) return NULL; tmp->tag = TAG_INTERNED; tmp->num_cells = 1; tmp->val_off = g_nil_s; tmp->arity = tmp->flags = 0; if (is_nil(get_tmp_heap(q, 0))) { init_tmp_heap(q); return make_nil(); } pl_idx num_cells = tmp_heap_used(q); tmp = alloc_heap(q, num_cells); if (!tmp) return NULL; dup_cells(tmp, get_tmp_heap(q, 0), num_cells); tmp->num_cells = num_cells; fix_list(tmp); init_tmp_heap(q); return tmp; } cell *end_list_unsafe(query *q) { cell *tmp = alloc_tmp(q, 1); if (!tmp) return NULL; tmp->tag = TAG_INTERNED; tmp->num_cells = 1; tmp->val_off = g_nil_s; tmp->arity = tmp->flags = 0; if (is_nil(get_tmp_heap(q, 0))) { init_tmp_heap(q); return make_nil(); } pl_idx num_cells = tmp_heap_used(q); tmp = alloc_heap(q, num_cells); if (!tmp) return NULL; copy_cells(tmp, get_tmp_heap(q, 0), num_cells); tmp->num_cells = num_cells; fix_list(tmp); init_tmp_heap(q); return tmp; } cell *allocate_structure(query *q, const char *functor, const cell *c) { if (!init_tmp_heap(q)) return NULL; cell *tmp = alloc_tmp(q, 1); if (!tmp) return NULL; tmp->tag = TAG_INTERNED; tmp->num_cells = 1; tmp->val_off = new_atom(q->pl, functor); tmp->arity = 0; tmp->flags = 0; append_structure(q, c); return get_tmp_heap(q, 0); } cell *append_structure(query *q, const cell *c) { cell *tmp = alloc_tmp(q, c->num_cells); if (!tmp) return NULL; copy_cells(tmp, c, c->num_cells); tmp = q->tmp_heap; tmp->arity++; return tmp; } cell *end_structure_heap(query *q) { pl_idx num_cells = tmp_heap_used(q); cell *tmp = alloc_heap(q, num_cells); if (!tmp) return NULL; dup_cells(tmp, get_tmp_heap(q, 0), num_cells); tmp->num_cells = num_cells; if (q->tmp_heap && (q->tmph_size > 1000)) { TPL_free(q->tmp_heap); q->tmp_heap = NULL; q->tmph_size = 1000; } return tmp; } cell *alloc_queuen(query *q, unsigned qnum, const cell *c) { if (!q->queue[qnum]) { q->queue[qnum] = TPL_malloc(sizeof(cell)*q->q_size[qnum]); if (!q->queue[qnum]) return NULL; } while ((q->qp[qnum]+c->num_cells) >= q->q_size[qnum]) { size_t n = q->q_size[qnum] + q->q_size[qnum] / 2; void *ptr = TPL_realloc(q->queue[qnum], sizeof(cell)*n); if (!ptr) return NULL; q->queue[qnum] = ptr; q->q_size[qnum] = n; } cell *dst = q->queue[qnum] + q->qp[qnum]; q->qp[qnum] += dup_cells(dst, c, c->num_cells); q->qcnt[qnum]++; return dst; } cell *import_term(query *q, cell *c, pl_ctx c_ctx) { cell *tmp = TPL_malloc(sizeof(cell) * c->num_cells); if (!tmp) return NULL; dup_cells_by_ref(tmp, c, c_ctx, c->num_cells); const frame *f = GET_CURR_FRAME(); rebase_term(q, tmp, f->actual_slots, false); undo_on_backtrack(q, tmp, UNDO_CELLS); return tmp; }