#include #include #include "module.h" #include "query.h" #define DO_DUMP 0 #define DUMP_TERM2(s,k,c,c_ctx,running) { \ fprintf(stderr, "%s %s ", s, k); \ q->nl = true; q->quoted = true; \ print_term(q, stderr, c, c_ctx, running); \ q->nl = false; q->quoted = false; \ } // Attributed-variable support for the blackboard, done natively. // // The raw '$bb_put' copy drops variable attributes (they live in frame // slots, not in the term). To let attributed terms survive a round-trip // we store an augmented image '$bb'('$bb_attv'(T, [V-Attrs|...])) where // the pair list records, for every attributed variable reachable from T // (transitively, including through attribute values - attvar graphs may // be cyclic through attributes), the raw slot attribute list. On // '$bb_get' the attributes are re-attached directly to the freshly // imported variables' slots. No Prolog-level residualization runs, and // no cooperation from attribute_goals//1 hooks is needed. static pl_idx g_bb_attv_s = 0; typedef struct { pl_ctx ctx; unsigned var_num; cell *attrs; } bb_attv_ent; typedef struct { cell *c; pl_ctx ctx; } bb_blk_ent; typedef struct { bb_attv_ent *ents; unsigned num_ents, max_ents; bb_blk_ent *blks; unsigned num_blks, done_blks, max_blks; const cell **seen_blks; unsigned num_seen, max_seen; } bb_attv_scan; static bool bb_scan_push_blk(bb_attv_scan *sc, cell *c, pl_ctx ctx) { for (unsigned i = 0; i < sc->num_seen; i++) { if (sc->seen_blks[i] == c) return true; } if (sc->num_seen >= sc->max_seen) { sc->max_seen = sc->max_seen ? sc->max_seen*2 : 64; sc->seen_blks = TPL_realloc(sc->seen_blks, sizeof(cell*)*sc->max_seen); if (!sc->seen_blks) return false; } sc->seen_blks[sc->num_seen++] = c; if (sc->num_blks >= sc->max_blks) { sc->max_blks = sc->max_blks ? sc->max_blks*2 : 64; sc->blks = TPL_realloc(sc->blks, sizeof(bb_blk_ent)*sc->max_blks); if (!sc->blks) return false; } sc->blks[sc->num_blks].c = c; sc->blks[sc->num_blks].ctx = ctx; sc->num_blks++; return true; } // Collect every attributed variable reachable from (T,T_ctx), including // those reachable only through other variables' attribute values. static bool bb_collect_attv(query *q, cell *T, pl_ctx T_ctx, bb_attv_scan *sc) { memset(sc, 0, sizeof(*sc)); if (!bb_scan_push_blk(sc, T, T_ctx)) return false; while (sc->done_blks < sc->num_blks) { bb_blk_ent blk = sc->blks[sc->done_blks++]; cell *c = blk.c; for (pl_idx i = 0; i < blk.c->num_cells; i++, c++) { if (!is_var(c)) continue; cell *d = deref(q, c, blk.ctx); pl_ctx d_ctx = q->latest_ctx; if (!is_var(d)) { if (is_compound(d) && !bb_scan_push_blk(sc, d, d_ctx)) return false; continue; } const frame *f = GET_FRAME(d_ctx); const slot *e = get_slot(q, f, d->var_num); cell *attrs = e->c.val_attrs; if (!attrs) continue; bool found = false; for (unsigned j = 0; j < sc->num_ents; j++) { if ((sc->ents[j].ctx == d_ctx) && (sc->ents[j].var_num == d->var_num)) { found = true; break; } } if (found) continue; if (sc->num_ents >= sc->max_ents) { sc->max_ents = sc->max_ents ? sc->max_ents*2 : 64; sc->ents = TPL_realloc(sc->ents, sizeof(bb_attv_ent)*sc->max_ents); if (!sc->ents) return false; } sc->ents[sc->num_ents].ctx = d_ctx; sc->ents[sc->num_ents].var_num = d->var_num; sc->ents[sc->num_ents].attrs = attrs; sc->num_ents++; if (!bb_scan_push_blk(sc, attrs, d_ctx)) return false; } } return true; } static void bb_scan_free(bb_attv_scan *sc) { if (sc->ents) TPL_free(sc->ents); if (sc->blks) TPL_free(sc->blks); if (sc->seen_blks) TPL_free(sc->seen_blks); } // Build the by-ref source image '$bb'('$bb_attv'(T, [V-Attrs|...])) in a // malloc'd scratch buffer, ready for one consistent copy_term_to_tmp. // Returns the buffer (caller frees) or NULL. static cell *bb_build_attv_image(query *q, cell *bb_wrapper, cell *T, pl_ctx T_ctx, bb_attv_scan *sc) { unsigned n = sc->num_ents; pl_idx list_cells = 1; // trailing nil for (unsigned i = 0; i < n; i++) list_cells += 3 + sc->ents[i].attrs->num_cells; // dot + minus + var + attrs pl_idx total = 2 + T->num_cells + list_cells; cell *S = TPL_malloc(sizeof(cell)*total); if (!S) return NULL; pl_idx idx = 0; S[idx] = *bb_wrapper; // '$bb'/1 header S[idx].num_cells = total; idx++; if (!g_bb_attv_s) g_bb_attv_s = new_atom(q->pl, "$bb_attv"); make_instr(S+idx, g_bb_attv_s, NULL, 2, total-2); idx++; idx += copy_cells_by_ref(S+idx, T, T_ctx, T->num_cells); // Remaining sizes from each pair onward, for the dot num_cells. pl_idx rest = list_cells; for (unsigned i = 0; i < n; i++) { pl_idx pair_sz = 2 + sc->ents[i].attrs->num_cells; // minus + var + attrs make_instr(S+idx, g_dot_s, NULL, 2, rest-1); idx++; make_instr(S+idx, g_minus_s, NULL, 2, pair_sz-1); idx++; make_ref(S+idx, sc->ents[i].var_num, sc->ents[i].ctx); idx++; idx += copy_cells_by_ref(S+idx, sc->ents[i].attrs, sc->ents[i].ctx, sc->ents[i].attrs->num_cells); rest -= 1 + pair_sz; } make_atom(S+idx, g_nil_s); idx++; (void)idx; return S; } // On retrieval of an augmented image, re-attach the recorded attributes // to the freshly imported variables and return a plain '$bb'(T) view. static cell *bb_reattach_attv(query *q, cell *tmp) { if (!g_bb_attv_s) g_bb_attv_s = new_atom(q->pl, "$bb_attv"); cell *inner = tmp+1; if (!is_interned(inner) || (inner->val_off != g_bb_attv_s) || (inner->arity != 2)) return tmp; cell *T = inner+1; cell *lst = T + T->num_cells; while (is_interned(lst) && (lst->val_off == g_dot_s) && (lst->arity == 2)) { cell *pair = lst+1; // '-'(V, Attrs) cell *v = pair+1; cell *attrs = v+1; cell *d = deref(q, v, q->st.cur_ctx); pl_ctx d_ctx = q->latest_ctx; if (is_var(d)) { const frame *f = GET_FRAME(d_ctx); slot *e = get_slot(q, f, d->var_num); if (!e->c.val_attrs) { // The imported cells were stripped of their ref flags by // rebase_term. Attribute cells outlive this call and get // read from other frames, so they must carry an explicit // context again. cell *ac = attrs; for (pl_idx j = 0; j < attrs->num_cells; j++, ac++) { if (is_var(ac) && !is_ref(ac)) { ac->flags |= FLAG_VAR_REF; ac->val_ctx = q->st.cur_ctx; } } if (!add_trail(q, d_ctx, d->var_num, e->c.val_attrs)) return NULL; e->c.val_attrs = attrs; } } lst = pair + pair->num_cells; } cell *tmp2 = alloc_heap(q, 1 + T->num_cells); if (!tmp2) return NULL; *tmp2 = *tmp; tmp2->num_cells = 1 + T->num_cells; dup_cells_by_ref(tmp2+1, T, q->st.cur_ctx, T->num_cells); return tmp2; } static bool bif_bb_b_put_2(query *q) { GET_FIRST_ARG(p1,nonvar); GET_NEXT_ARG(p2,any); if (is_compound(p1) && ((p1->val_off != g_colon_s) || (p1->arity != 2))) return throw_error(q, p1, p1_ctx, "type_error", "callable"); module *m; char tmpbuf[1024]; if (is_compound(p1)) { cell *p1_m = p1 + 1; p1 = p1_m + p1_m->num_cells; if (!is_atom(p1_m) || !is_smallint_or_atom(p1)) return throw_error(q, p1, p1_ctx, "type_error", "atom"); m = find_module(q->pl, C_STR(q, p1_m)); if (!m) return throw_error(q, p1_m, p1_ctx, "existence_error", "module"); } else m = q->pl->global_bb ? q->pl->user_m : q->st.m; if (is_atom(p1)) snprintf(tmpbuf, sizeof(tmpbuf), "%s:%s:b", m->name, C_STR(q, p1)); else snprintf(tmpbuf, sizeof(tmpbuf), "%s:%d:b", m->name, (int)get_smallint(p1)); if (DO_DUMP) DUMP_TERM2("bb_b_put", tmpbuf, p2, p2_ctx, 1); CHECKED(init_tmp_heap(q)); cell *tmp = clone_term_to_tmp(q, p2, p2_ctx); CHECKED(tmp); pl_idx num_cells = tmp->num_cells; cell *val = TPL_malloc(sizeof(cell)*num_cells); CHECKED(val); dup_cells(val, tmp, tmp->num_cells); val->flags |= FLAG_LIVE; char *key = strdup(tmpbuf); CHECKED(key); CHECKED(undo_on_backtrack(q, key, UNDO_BBOARD)); prolog_lock(q->pl); sl_set(m->keyval, key, val); prolog_unlock(q->pl); return true; } static bool bif_bb_put_2(query *q) { GET_FIRST_ARG(p1,nonvar); GET_NEXT_ARG(p2,any); if (is_compound(p1) && ((p1->val_off != g_colon_s) || (p1->arity != 2))) return throw_error(q, p1, p1_ctx, "type_error", "callable"); module *m; char tmpbuf1[1024], tmpbuf2[1024]; if (is_compound(p1)) { cell *p1_m = p1 + 1; p1 = p1_m + p1_m->num_cells; if (!is_atom(p1_m) || !is_smallint_or_atom(p1)) return throw_error(q, p1, p1_ctx, "type_error", "atom"); m = find_module(q->pl, C_STR(q, p1_m)); if (!m) return throw_error(q, p1_m, p1_ctx, "existence_error", "module"); } else m = q->pl->global_bb ? q->pl->user_m : q->st.m; if (is_atom(p1)) snprintf(tmpbuf1, sizeof(tmpbuf1), "%s:%s:b", m->name, C_STR(q, p1)); else snprintf(tmpbuf1, sizeof(tmpbuf1), "%s:%d:b", m->name, (int)get_smallint(p1)); const char *key1 = tmpbuf1; if (is_atom(p1)) snprintf(tmpbuf2, sizeof(tmpbuf2), "%s:%s", m->name, C_STR(q, p1)); else snprintf(tmpbuf2, sizeof(tmpbuf2), "%s:%d", m->name, (int)get_smallint(p1)); if (DO_DUMP) DUMP_TERM2("bb_put", tmpbuf2, p2, p2_ctx, 1); char *key2 = strdup(tmpbuf2); // If the term contains attributed variables, store an augmented // image carrying the raw attributes so they survive the round-trip. cell *scratch = NULL; cell *src = p2; pl_ctx src_ctx = p2_ctx; if (is_compound(p2) && (p2->arity == 1)) { bb_attv_scan sc; if (bb_collect_attv(q, p2+1, p2_ctx, &sc) && sc.num_ents) { scratch = bb_build_attv_image(q, p2, p2+1, p2_ctx, &sc); if (scratch) { src = scratch; src_ctx = q->st.cur_ctx; } } bb_scan_free(&sc); } CHECKED(init_tmp_heap(q)); cell *tmp = copy_term_to_tmp(q, src, src_ctx, false); if (scratch) TPL_free(scratch); CHECKED(tmp); pl_idx num_cells = tmp->num_cells; cell *val = TPL_malloc(sizeof(cell)*num_cells); CHECKED(val); dup_cells(val, tmp, tmp->num_cells); prolog_lock(q->pl); while (sl_del(m->keyval, key1)) ; while (sl_del(m->keyval, key2)) ; sl_app(m->keyval, key2, val); prolog_unlock(q->pl); return true; } static cell *bb_import_term_to_heap(query *q, cell *c, pl_ctx c_ctx) { const frame *f = GET_CURR_FRAME(); cell *tmp = alloc_heap(q, c->num_cells); if (!tmp) return NULL; dup_cells_by_ref(tmp, c, c_ctx, c->num_cells); return tmp; } static bool bif_bb_get_2(query *q) { GET_FIRST_ARG(p1,nonvar); if (is_compound(p1) && ((p1->val_off != g_colon_s) || (p1->arity != 2))) return throw_error(q, p1, p1_ctx, "type_error", "callable"); module *m; char tmpbuf[1024]; if (is_compound(p1)) { cell *p1_m = p1 + 1; p1 = p1_m + p1_m->num_cells; if (!is_atom(p1_m) || !is_smallint_or_atom(p1)) return throw_error(q, p1, p1_ctx, "type_error", "atom"); m = find_module(q->pl, C_STR(q, p1_m)); if (!m) return throw_error(q, p1_m, p1_ctx, "existence_error", "module"); } else m = q->pl->global_bb ? q->pl->user_m : q->st.m; if (is_atom(p1)) snprintf(tmpbuf, sizeof(tmpbuf), "%s:%s:b", m->name, C_STR(q, p1)); else snprintf(tmpbuf, sizeof(tmpbuf), "%s:%d:b", m->name, (int)get_smallint(p1)); const char *key = tmpbuf; cell *val; prolog_lock(q->pl); if (!sl_get(m->keyval, key, (void*)&val)) { if (is_atom(p1)) snprintf(tmpbuf, sizeof(tmpbuf), "%s:%s", m->name, C_STR(q, p1)); else snprintf(tmpbuf, sizeof(tmpbuf), "%s:%d", m->name, (int)get_smallint(p1)); key = tmpbuf; if (!sl_get(m->keyval, key, (void*)&val)) { prolog_unlock(q->pl); return false; } } prolog_unlock(q->pl); cell *tmp = val->flags & FLAG_LIVE ? bb_import_term_to_heap(q, val, q->st.cur_ctx) : import_term(q, val, q->st.cur_ctx); CHECKED(tmp); if (!(val->flags & FLAG_LIVE) && is_compound(tmp) && (tmp->arity == 1)) { tmp = bb_reattach_attv(q, tmp); CHECKED(tmp); } GET_FIRST_ARG(p1x,nonvar); GET_NEXT_ARG(p2,any); if (DO_DUMP) DUMP_TERM2("bb_get", tmpbuf, tmp, q->st.cur_ctx, 1); return unify(q, p2, p2_ctx, tmp, q->st.cur_ctx); } static bool bif_bb_delete_2(query *q) { GET_FIRST_ARG(p1,nonvar); if (is_compound(p1) && ((p1->val_off != g_colon_s) || (p1->arity != 2))) return throw_error(q, p1, p1_ctx, "type_error", "callable"); module *m; char tmpbuf[1024]; if (is_compound(p1)) { cell *p1_m = p1 + 1; p1 = p1_m + p1_m->num_cells; if (!is_atom(p1_m) || !is_smallint_or_atom(p1)) return throw_error(q, p1, p1_ctx, "type_error", "atom"); m = find_module(q->pl, C_STR(q, p1_m)); if (!m) return throw_error(q, p1_m, p1_ctx, "existence_error", "module"); } else m = q->pl->global_bb ? q->pl->user_m : q->st.m; if (is_atom(p1)) snprintf(tmpbuf, sizeof(tmpbuf), "%s:%s", m->name, C_STR(q, p1)); else snprintf(tmpbuf, sizeof(tmpbuf), "%s:%d", m->name, (int)get_smallint(p1)); const char *key = tmpbuf; cell *val; prolog_lock(q->pl); if (!sl_get(m->keyval, key, (void*)&val)) { prolog_unlock(q->pl); return false; } cell *tmp = val->flags & FLAG_LIVE ? bb_import_term_to_heap(q, val, q->st.cur_ctx) : import_term(q, val, q->st.cur_ctx); CHECKED(tmp, prolog_unlock(q->pl)); GET_FIRST_ARG(p1x,nonvar); GET_NEXT_ARG(p2,any); if (DO_DUMP) DUMP_TERM2("bb_delete", tmpbuf, tmp, q->st.cur_ctx, 1); if (!unify(q, p2, p2_ctx, tmp, q->st.cur_ctx)) { prolog_unlock(q->pl); return false; } bool ok = sl_del(m->keyval, key); if (ok) { while (sl_del(m->keyval, key)) ; } prolog_unlock(q->pl); return ok; } static bool bif_bb_update_3(query *q) { GET_FIRST_ARG(p1,nonvar); if (is_compound(p1) && ((p1->val_off != g_colon_s) || (p1->arity != 2))) return throw_error(q, p1, p1_ctx, "type_error", "callable"); module *m; char tmpbuf[1024]; if (is_compound(p1)) { cell *p1_m = p1 + 1; p1 = p1_m + p1_m->num_cells; if (!is_atom(p1_m) || !is_smallint_or_atom(p1)) return throw_error(q, p1, p1_ctx, "type_error", "atom"); m = find_module(q->pl, C_STR(q, p1_m)); if (!m) return throw_error(q, p1_m, p1_ctx, "existence_error", "module"); } else m = q->pl->global_bb ? q->pl->user_m : q->st.m; if (is_atom(p1)) snprintf(tmpbuf, sizeof(tmpbuf), "%s:%s", m->name, C_STR(q, p1)); else snprintf(tmpbuf, sizeof(tmpbuf), "%s:%d", m->name, (int)get_smallint(p1)); char *key = tmpbuf; cell *val; prolog_lock(q->pl); if (!sl_get(m->keyval, key, (void*)&val)) { prolog_unlock(q->pl); return false; } q->noderef = true; cell *tmp = val->flags & FLAG_LIVE ? bb_import_term_to_heap(q, val, q->st.cur_ctx) : import_term(q, val, q->st.cur_ctx); q->noderef = false; CHECKED(tmp, prolog_unlock(q->pl)); GET_FIRST_ARG(p1x,nonvar); GET_NEXT_ARG(p2,any); GET_NEXT_ARG(p3,any); if (DO_DUMP) DUMP_TERM2("bb_update", tmpbuf, p2, p2_ctx, 1); if (!unify(q, p2, p2_ctx, tmp, q->st.cur_ctx)) { prolog_unlock(q->pl); return false; } key = strdup(tmpbuf); tmp = copy_term_to_heap(q, p3, p3_ctx, false); CHECKED(tmp, prolog_unlock(q->pl)); cell *value = TPL_malloc(sizeof(cell)*tmp->num_cells); CHECKED(value, prolog_unlock(q->pl)); dup_cells(value, tmp, tmp->num_cells); while (sl_del(m->keyval, key)) ; sl_app(m->keyval, key, value); prolog_unlock(q->pl); return true; } static bool bif_sys_bb_is_live_1(query *q) { GET_FIRST_ARG(p1,nonvar); if (is_compound(p1) && ((p1->val_off != g_colon_s) || (p1->arity != 2))) return throw_error(q, p1, p1_ctx, "type_error", "callable"); module *m; char tmpbuf[1024]; if (is_compound(p1)) { cell *p1_m = p1 + 1; p1 = p1_m + p1_m->num_cells; if (!is_atom(p1_m) || !is_smallint_or_atom(p1)) return throw_error(q, p1, p1_ctx, "type_error", "atom"); m = find_module(q->pl, C_STR(q, p1_m)); if (!m) return throw_error(q, p1_m, p1_ctx, "existence_error", "module"); } else m = q->pl->global_bb ? q->pl->user_m : q->st.m; if (is_atom(p1)) snprintf(tmpbuf, sizeof(tmpbuf), "%s:%s:b", m->name, C_STR(q, p1)); else snprintf(tmpbuf, sizeof(tmpbuf), "%s:%d:b", m->name, (int)get_smallint(p1)); const char *key = tmpbuf; cell *val; bool live = false; prolog_lock(q->pl); if (sl_get(m->keyval, key, (void*)&val)) live = (val->flags & FLAG_LIVE) ? true : false; prolog_unlock(q->pl); return live; } builtins g_bboard_bifs[] = { {"$bb_b_put", 2, bif_bb_b_put_2, ":atom,+term", false, false, BLAH}, {"$bb_put", 2, bif_bb_put_2, ":atom,+term", false, false, BLAH}, {"$bb_get", 2, bif_bb_get_2, ":atom,?term", false, false, BLAH}, {"$bb_update", 3, bif_bb_update_3, ":atom,?term,?term", false, false, BLAH}, {"$bb_delete", 2, bif_bb_delete_2, ":atom,?term", false, false, BLAH}, {"$bb_is_live", 1, bif_sys_bb_is_live_1, ":atom", false, false, BLAH}, {0} };