Mercurial > hg > sv-dependency-builds
diff src/fftw-3.3.8/kernel/planner.c @ 82:d0c2a83c1364
Add FFTW 3.3.8 source, and a Linux build
author | Chris Cannam |
---|---|
date | Tue, 19 Nov 2019 14:52:55 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fftw-3.3.8/kernel/planner.c Tue Nov 19 14:52:55 2019 +0000 @@ -0,0 +1,1035 @@ +/* + * Copyright (c) 2000 Matteo Frigo + * Copyright (c) 2000 Massachusetts Institute of Technology + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "kernel/ifftw.h" +#include <string.h> + +/* GNU Coding Standards, Sec. 5.2: "Please write the comments in a GNU + program in English, because English is the one language that nearly + all programmers in all countries can read." + + ingemisco tanquam reus + culpa rubet vultus meus + supplicanti parce [rms] +*/ + +#define VALIDP(solution) ((solution)->flags.hash_info & H_VALID) +#define LIVEP(solution) ((solution)->flags.hash_info & H_LIVE) +#define SLVNDX(solution) ((solution)->flags.slvndx) +#define BLISS(flags) (((flags).hash_info) & BLESSING) +#define INFEASIBLE_SLVNDX ((1U<<BITS_FOR_SLVNDX)-1) + + +#define MAXNAM 64 /* maximum length of registrar's name. + Used for reading wisdom. There is no point + in doing this right */ + + +#ifdef FFTW_DEBUG +static void check(hashtab *ht); +#endif + +/* x <= y */ +#define LEQ(x, y) (((x) & (y)) == (x)) + +/* A subsumes B */ +static int subsumes(const flags_t *a, unsigned slvndx_a, const flags_t *b) +{ + if (slvndx_a != INFEASIBLE_SLVNDX) { + A(a->timelimit_impatience == 0); + return (LEQ(a->u, b->u) && LEQ(b->l, a->l)); + } else { + return (LEQ(a->l, b->l) + && a->timelimit_impatience <= b->timelimit_impatience); + } +} + +static unsigned addmod(unsigned a, unsigned b, unsigned p) +{ + /* gcc-2.95/sparc produces incorrect code for the fast version below. */ +#if defined(__sparc__) && defined(__GNUC__) + /* slow version */ + return (a + b) % p; +#else + /* faster version */ + unsigned c = a + b; + return c >= p ? c - p : c; +#endif +} + +/* + slvdesc management: +*/ +static void sgrow(planner *ego) +{ + unsigned osiz = ego->slvdescsiz, nsiz = 1 + osiz + osiz / 4; + slvdesc *ntab = (slvdesc *)MALLOC(nsiz * sizeof(slvdesc), SLVDESCS); + slvdesc *otab = ego->slvdescs; + unsigned i; + + ego->slvdescs = ntab; + ego->slvdescsiz = nsiz; + for (i = 0; i < osiz; ++i) + ntab[i] = otab[i]; + X(ifree0)(otab); +} + +static void register_solver(planner *ego, solver *s) +{ + slvdesc *n; + int kind; + + if (s) { /* add s to solver list */ + X(solver_use)(s); + + A(ego->nslvdesc < INFEASIBLE_SLVNDX); + if (ego->nslvdesc >= ego->slvdescsiz) + sgrow(ego); + + n = ego->slvdescs + ego->nslvdesc; + + n->slv = s; + n->reg_nam = ego->cur_reg_nam; + n->reg_id = ego->cur_reg_id++; + + A(strlen(n->reg_nam) < MAXNAM); + n->nam_hash = X(hash)(n->reg_nam); + + kind = s->adt->problem_kind; + n->next_for_same_problem_kind = ego->slvdescs_for_problem_kind[kind]; + ego->slvdescs_for_problem_kind[kind] = (int)/*from unsigned*/ego->nslvdesc; + + ego->nslvdesc++; + } +} + +static unsigned slookup(planner *ego, char *nam, int id) +{ + unsigned h = X(hash)(nam); /* used to avoid strcmp in the common case */ + FORALL_SOLVERS(ego, s, sp, { + UNUSED(s); + if (sp->reg_id == id && sp->nam_hash == h + && !strcmp(sp->reg_nam, nam)) + return (unsigned)/*from ptrdiff_t*/(sp - ego->slvdescs); + }); + return INFEASIBLE_SLVNDX; +} + +/* Compute a MD5 hash of the configuration of the planner. + We store it into the wisdom file to make absolutely sure that + we are reading wisdom that is applicable */ +static void signature_of_configuration(md5 *m, planner *ego) +{ + X(md5begin)(m); + X(md5unsigned)(m, sizeof(R)); /* so we don't mix different precisions */ + FORALL_SOLVERS(ego, s, sp, { + UNUSED(s); + X(md5int)(m, sp->reg_id); + X(md5puts)(m, sp->reg_nam); + }); + X(md5end)(m); +} + +/* + md5-related stuff: +*/ + +/* first hash function */ +static unsigned h1(const hashtab *ht, const md5sig s) +{ + unsigned h = s[0] % ht->hashsiz; + A(h == (s[0] % ht->hashsiz)); + return h; +} + +/* second hash function (for double hashing) */ +static unsigned h2(const hashtab *ht, const md5sig s) +{ + unsigned h = 1U + s[1] % (ht->hashsiz - 1); + A(h == (1U + s[1] % (ht->hashsiz - 1))); + return h; +} + +static void md5hash(md5 *m, const problem *p, const planner *plnr) +{ + X(md5begin)(m); + X(md5unsigned)(m, sizeof(R)); /* so we don't mix different precisions */ + X(md5int)(m, plnr->nthr); + p->adt->hash(p, m); + X(md5end)(m); +} + +static int md5eq(const md5sig a, const md5sig b) +{ + return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]; +} + +static void sigcpy(const md5sig a, md5sig b) +{ + b[0] = a[0]; b[1] = a[1]; b[2] = a[2]; b[3] = a[3]; +} + +/* + memoization routines : +*/ + +/* + liber scriptus proferetur + in quo totum continetur + unde mundus iudicetur +*/ +struct solution_s { + md5sig s; + flags_t flags; +}; + +static solution *htab_lookup(hashtab *ht, const md5sig s, + const flags_t *flagsp) +{ + unsigned g, h = h1(ht, s), d = h2(ht, s); + solution *best = 0; + + ++ht->lookup; + + /* search all entries that match; select the one with + the lowest flags.u */ + /* This loop may potentially traverse the whole table, since at + least one element is guaranteed to be !LIVEP, but all elements + may be VALIDP. Hence, we stop after at the first invalid + element or after traversing the whole table. */ + g = h; + do { + solution *l = ht->solutions + g; + ++ht->lookup_iter; + if (VALIDP(l)) { + if (LIVEP(l) + && md5eq(s, l->s) + && subsumes(&l->flags, SLVNDX(l), flagsp) ) { + if (!best || LEQ(l->flags.u, best->flags.u)) + best = l; + } + } else + break; + + g = addmod(g, d, ht->hashsiz); + } while (g != h); + + if (best) + ++ht->succ_lookup; + return best; +} + +static solution *hlookup(planner *ego, const md5sig s, + const flags_t *flagsp) +{ + solution *sol = htab_lookup(&ego->htab_blessed, s, flagsp); + if (!sol) sol = htab_lookup(&ego->htab_unblessed, s, flagsp); + return sol; +} + +static void fill_slot(hashtab *ht, const md5sig s, const flags_t *flagsp, + unsigned slvndx, solution *slot) +{ + ++ht->insert; + ++ht->nelem; + A(!LIVEP(slot)); + slot->flags.u = flagsp->u; + slot->flags.l = flagsp->l; + slot->flags.timelimit_impatience = flagsp->timelimit_impatience; + slot->flags.hash_info |= H_VALID | H_LIVE; + SLVNDX(slot) = slvndx; + + /* keep this check enabled in case we add so many solvers + that the bitfield overflows */ + CK(SLVNDX(slot) == slvndx); + sigcpy(s, slot->s); +} + +static void kill_slot(hashtab *ht, solution *slot) +{ + A(LIVEP(slot)); /* ==> */ A(VALIDP(slot)); + + --ht->nelem; + slot->flags.hash_info = H_VALID; +} + +static void hinsert0(hashtab *ht, const md5sig s, const flags_t *flagsp, + unsigned slvndx) +{ + solution *l; + unsigned g, h = h1(ht, s), d = h2(ht, s); + + ++ht->insert_unknown; + + /* search for nonfull slot */ + for (g = h; ; g = addmod(g, d, ht->hashsiz)) { + ++ht->insert_iter; + l = ht->solutions + g; + if (!LIVEP(l)) break; + A((g + d) % ht->hashsiz != h); + } + + fill_slot(ht, s, flagsp, slvndx, l); +} + +static void rehash(hashtab *ht, unsigned nsiz) +{ + unsigned osiz = ht->hashsiz, h; + solution *osol = ht->solutions, *nsol; + + nsiz = (unsigned)X(next_prime)((INT)nsiz); + nsol = (solution *)MALLOC(nsiz * sizeof(solution), HASHT); + ++ht->nrehash; + + /* init new table */ + for (h = 0; h < nsiz; ++h) + nsol[h].flags.hash_info = 0; + + /* install new table */ + ht->hashsiz = nsiz; + ht->solutions = nsol; + ht->nelem = 0; + + /* copy table */ + for (h = 0; h < osiz; ++h) { + solution *l = osol + h; + if (LIVEP(l)) + hinsert0(ht, l->s, &l->flags, SLVNDX(l)); + } + + X(ifree0)(osol); +} + +static unsigned minsz(unsigned nelem) +{ + return 1U + nelem + nelem / 8U; +} + +static unsigned nextsz(unsigned nelem) +{ + return minsz(minsz(nelem)); +} + +static void hgrow(hashtab *ht) +{ + unsigned nelem = ht->nelem; + if (minsz(nelem) >= ht->hashsiz) + rehash(ht, nextsz(nelem)); +} + +#if 0 +/* shrink the hash table, never used */ +static void hshrink(hashtab *ht) +{ + unsigned nelem = ht->nelem; + /* always rehash after deletions */ + rehash(ht, nextsz(nelem)); +} +#endif + +static void htab_insert(hashtab *ht, const md5sig s, const flags_t *flagsp, + unsigned slvndx) +{ + unsigned g, h = h1(ht, s), d = h2(ht, s); + solution *first = 0; + + /* Remove all entries that are subsumed by the new one. */ + /* This loop may potentially traverse the whole table, since at + least one element is guaranteed to be !LIVEP, but all elements + may be VALIDP. Hence, we stop after at the first invalid + element or after traversing the whole table. */ + g = h; + do { + solution *l = ht->solutions + g; + ++ht->insert_iter; + if (VALIDP(l)) { + if (LIVEP(l) && md5eq(s, l->s)) { + if (subsumes(flagsp, slvndx, &l->flags)) { + if (!first) first = l; + kill_slot(ht, l); + } else { + /* It is an error to insert an element that + is subsumed by an existing entry. */ + A(!subsumes(&l->flags, SLVNDX(l), flagsp)); + } + } + } else + break; + + g = addmod(g, d, ht->hashsiz); + } while (g != h); + + if (first) { + /* overwrite FIRST */ + fill_slot(ht, s, flagsp, slvndx, first); + } else { + /* create a new entry */ + hgrow(ht); + hinsert0(ht, s, flagsp, slvndx); + } +} + +static void hinsert(planner *ego, const md5sig s, const flags_t *flagsp, + unsigned slvndx) +{ + htab_insert(BLISS(*flagsp) ? &ego->htab_blessed : &ego->htab_unblessed, + s, flagsp, slvndx ); +} + + +static void invoke_hook(planner *ego, plan *pln, const problem *p, + int optimalp) +{ + if (ego->hook) + ego->hook(ego, pln, p, optimalp); +} + +#ifdef FFTW_RANDOM_ESTIMATOR +/* a "random" estimate, used for debugging to generate "random" + plans, albeit from a deterministic seed. */ + +unsigned X(random_estimate_seed) = 0; + +static double random_estimate(const planner *ego, const plan *pln, + const problem *p) +{ + md5 m; + X(md5begin)(&m); + X(md5unsigned)(&m, X(random_estimate_seed)); + X(md5int)(&m, ego->nthr); + p->adt->hash(p, &m); + X(md5putb)(&m, &pln->ops, sizeof(pln->ops)); + X(md5putb)(&m, &pln->adt, sizeof(pln->adt)); + X(md5end)(&m); + return ego->cost_hook ? ego->cost_hook(p, m.s[0], COST_MAX) : m.s[0]; +} + +#endif + +double X(iestimate_cost)(const planner *ego, const plan *pln, const problem *p) +{ + double cost = + + pln->ops.add + + pln->ops.mul + +#if HAVE_FMA + + pln->ops.fma +#else + + 2 * pln->ops.fma +#endif + + + pln->ops.other; + if (ego->cost_hook) + cost = ego->cost_hook(p, cost, COST_MAX); + return cost; +} + +static void evaluate_plan(planner *ego, plan *pln, const problem *p) +{ + if (ESTIMATEP(ego) || !BELIEVE_PCOSTP(ego) || pln->pcost == 0.0) { + ego->nplan++; + + if (ESTIMATEP(ego)) { + estimate: + /* heuristic */ +#ifdef FFTW_RANDOM_ESTIMATOR + pln->pcost = random_estimate(ego, pln, p); + ego->epcost += X(iestimate_cost)(ego, pln, p); +#else + pln->pcost = X(iestimate_cost)(ego, pln, p); + ego->epcost += pln->pcost; +#endif + } else { + double t = X(measure_execution_time)(ego, pln, p); + + if (t < 0) { /* unavailable cycle counter */ + /* Real programmers can write FORTRAN in any language */ + goto estimate; + } + + pln->pcost = t; + ego->pcost += t; + ego->need_timeout_check = 1; + } + } + + invoke_hook(ego, pln, p, 0); +} + +/* maintain dynamic scoping of flags, nthr: */ +static plan *invoke_solver(planner *ego, const problem *p, solver *s, + const flags_t *nflags) +{ + flags_t flags = ego->flags; + int nthr = ego->nthr; + plan *pln; + ego->flags = *nflags; + PLNR_TIMELIMIT_IMPATIENCE(ego) = 0; + A(p->adt->problem_kind == s->adt->problem_kind); + pln = s->adt->mkplan(s, p, ego); + ego->nthr = nthr; + ego->flags = flags; + return pln; +} + +/* maintain the invariant TIMED_OUT ==> NEED_TIMEOUT_CHECK */ +static int timeout_p(planner *ego, const problem *p) +{ + /* do not timeout when estimating. First, the estimator is the + planner of last resort. Second, calling X(elapsed_since)() is + slower than estimating */ + if (!ESTIMATEP(ego)) { + /* do not assume that X(elapsed_since)() is monotonic */ + if (ego->timed_out) { + A(ego->need_timeout_check); + return 1; + } + + if (ego->timelimit >= 0 && + X(elapsed_since)(ego, p, ego->start_time) >= ego->timelimit) { + ego->timed_out = 1; + ego->need_timeout_check = 1; + return 1; + } + } + + A(!ego->timed_out); + ego->need_timeout_check = 0; + return 0; +} + +static plan *search0(planner *ego, const problem *p, unsigned *slvndx, + const flags_t *flagsp) +{ + plan *best = 0; + int best_not_yet_timed = 1; + + /* Do not start a search if the planner timed out. This check is + necessary, lest the relaxation mechanism kick in */ + if (timeout_p(ego, p)) + return 0; + + FORALL_SOLVERS_OF_KIND(p->adt->problem_kind, ego, s, sp, { + plan *pln; + + pln = invoke_solver(ego, p, s, flagsp); + + if (ego->need_timeout_check) + if (timeout_p(ego, p)) { + X(plan_destroy_internal)(pln); + X(plan_destroy_internal)(best); + return 0; + } + + if (pln) { + /* read COULD_PRUNE_NOW_P because PLN may be destroyed + before we use COULD_PRUNE_NOW_P */ + int could_prune_now_p = pln->could_prune_now_p; + + if (best) { + if (best_not_yet_timed) { + evaluate_plan(ego, best, p); + best_not_yet_timed = 0; + } + evaluate_plan(ego, pln, p); + if (pln->pcost < best->pcost) { + X(plan_destroy_internal)(best); + best = pln; + *slvndx = (unsigned)/*from ptrdiff_t*/(sp - ego->slvdescs); + } else { + X(plan_destroy_internal)(pln); + } + } else { + best = pln; + *slvndx = (unsigned)/*from ptrdiff_t*/(sp - ego->slvdescs); + } + + if (ALLOW_PRUNINGP(ego) && could_prune_now_p) + break; + } + }); + + return best; +} + +static plan *search(planner *ego, const problem *p, unsigned *slvndx, + flags_t *flagsp) +{ + plan *pln = 0; + unsigned i; + + /* relax impatience in this order: */ + static const unsigned relax_tab[] = { + 0, /* relax nothing */ + NO_VRECURSE, + NO_FIXED_RADIX_LARGE_N, + NO_SLOW, + NO_UGLY + }; + + unsigned l_orig = flagsp->l; + unsigned x = flagsp->u; + + /* guaranteed to be different from X */ + unsigned last_x = ~x; + + for (i = 0; i < sizeof(relax_tab) / sizeof(relax_tab[0]); ++i) { + if (LEQ(l_orig, x & ~relax_tab[i])) + x = x & ~relax_tab[i]; + + if (x != last_x) { + last_x = x; + flagsp->l = x; + pln = search0(ego, p, slvndx, flagsp); + if (pln) break; + } + } + + if (!pln) { + /* search [L_ORIG, U] */ + if (l_orig != last_x) { + last_x = l_orig; + flagsp->l = l_orig; + pln = search0(ego, p, slvndx, flagsp); + } + } + + return pln; +} + +#define CHECK_FOR_BOGOSITY \ + if ((ego->bogosity_hook ? \ + (ego->wisdom_state = ego->bogosity_hook(ego->wisdom_state, p)) \ + : ego->wisdom_state) == WISDOM_IS_BOGUS) \ + goto wisdom_is_bogus; + +static plan *mkplan(planner *ego, const problem *p) +{ + plan *pln; + md5 m; + unsigned slvndx; + flags_t flags_of_solution; + solution *sol; + solver *s; + + ASSERT_ALIGNED_DOUBLE; + A(LEQ(PLNR_L(ego), PLNR_U(ego))); + + if (ESTIMATEP(ego)) + PLNR_TIMELIMIT_IMPATIENCE(ego) = 0; /* canonical form */ + + +#ifdef FFTW_DEBUG + check(&ego->htab_blessed); + check(&ego->htab_unblessed); +#endif + + pln = 0; + + CHECK_FOR_BOGOSITY; + + ego->timed_out = 0; + + ++ego->nprob; + md5hash(&m, p, ego); + + flags_of_solution = ego->flags; + + if (ego->wisdom_state != WISDOM_IGNORE_ALL) { + if ((sol = hlookup(ego, m.s, &flags_of_solution))) { + /* wisdom is acceptable */ + wisdom_state_t owisdom_state = ego->wisdom_state; + + /* this hook is mainly for MPI, to make sure that + wisdom is in sync across all processes for MPI problems */ + if (ego->wisdom_ok_hook && !ego->wisdom_ok_hook(p, sol->flags)) + goto do_search; /* ignore not-ok wisdom */ + + slvndx = SLVNDX(sol); + + if (slvndx == INFEASIBLE_SLVNDX) { + if (ego->wisdom_state == WISDOM_IGNORE_INFEASIBLE) + goto do_search; + else + return 0; /* known to be infeasible */ + } + + flags_of_solution = sol->flags; + + /* inherit blessing either from wisdom + or from the planner */ + flags_of_solution.hash_info |= BLISS(ego->flags); + + ego->wisdom_state = WISDOM_ONLY; + + s = ego->slvdescs[slvndx].slv; + if (p->adt->problem_kind != s->adt->problem_kind) + goto wisdom_is_bogus; + + pln = invoke_solver(ego, p, s, &flags_of_solution); + + CHECK_FOR_BOGOSITY; /* catch error in child solvers */ + + sol = 0; /* Paranoia: SOL may be dangling after + invoke_solver(); make sure we don't accidentally + reuse it. */ + + if (!pln) + goto wisdom_is_bogus; + + ego->wisdom_state = owisdom_state; + + goto skip_search; + } + else if (ego->nowisdom_hook) /* for MPI, make sure lack of wisdom */ + ego->nowisdom_hook(p); /* is in sync across all processes */ + } + + do_search: + /* cannot search in WISDOM_ONLY mode */ + if (ego->wisdom_state == WISDOM_ONLY) + goto wisdom_is_bogus; + + flags_of_solution = ego->flags; + pln = search(ego, p, &slvndx, &flags_of_solution); + CHECK_FOR_BOGOSITY; /* catch error in child solvers */ + + if (ego->timed_out) { + A(!pln); + if (PLNR_TIMELIMIT_IMPATIENCE(ego) != 0) { + /* record (below) that this plan has failed because of + timeout */ + flags_of_solution.hash_info |= BLESSING; + } else { + /* this is not the top-level problem or timeout is not + active: record no wisdom. */ + return 0; + } + } else { + /* canonicalize to infinite timeout */ + flags_of_solution.timelimit_impatience = 0; + } + + skip_search: + if (ego->wisdom_state == WISDOM_NORMAL || + ego->wisdom_state == WISDOM_ONLY) { + if (pln) { + hinsert(ego, m.s, &flags_of_solution, slvndx); + invoke_hook(ego, pln, p, 1); + } else { + hinsert(ego, m.s, &flags_of_solution, INFEASIBLE_SLVNDX); + } + } + + return pln; + + wisdom_is_bogus: + X(plan_destroy_internal)(pln); + ego->wisdom_state = WISDOM_IS_BOGUS; + return 0; +} + +static void htab_destroy(hashtab *ht) +{ + X(ifree)(ht->solutions); + ht->solutions = 0; + ht->nelem = 0U; +} + +static void mkhashtab(hashtab *ht) +{ + ht->nrehash = 0; + ht->succ_lookup = ht->lookup = ht->lookup_iter = 0; + ht->insert = ht->insert_iter = ht->insert_unknown = 0; + + ht->solutions = 0; + ht->hashsiz = ht->nelem = 0U; + hgrow(ht); /* so that hashsiz > 0 */ +} + +/* destroy hash table entries. If FORGET_EVERYTHING, destroy the whole + table. If FORGET_ACCURSED, then destroy entries that are not blessed. */ +static void forget(planner *ego, amnesia a) +{ + switch (a) { + case FORGET_EVERYTHING: + htab_destroy(&ego->htab_blessed); + mkhashtab(&ego->htab_blessed); + /* fall through */ + case FORGET_ACCURSED: + htab_destroy(&ego->htab_unblessed); + mkhashtab(&ego->htab_unblessed); + break; + default: + break; + } +} + +/* FIXME: what sort of version information should we write? */ +#define WISDOM_PREAMBLE PACKAGE "-" VERSION " " STRINGIZE(X(wisdom)) +static const char stimeout[] = "TIMEOUT"; + +/* tantus labor non sit cassus */ +static void exprt(planner *ego, printer *p) +{ + unsigned h; + hashtab *ht = &ego->htab_blessed; + md5 m; + + signature_of_configuration(&m, ego); + + p->print(p, + "(" WISDOM_PREAMBLE " #x%M #x%M #x%M #x%M\n", + m.s[0], m.s[1], m.s[2], m.s[3]); + + for (h = 0; h < ht->hashsiz; ++h) { + solution *l = ht->solutions + h; + if (LIVEP(l)) { + const char *reg_nam; + int reg_id; + + if (SLVNDX(l) == INFEASIBLE_SLVNDX) { + reg_nam = stimeout; + reg_id = 0; + } else { + slvdesc *sp = ego->slvdescs + SLVNDX(l); + reg_nam = sp->reg_nam; + reg_id = sp->reg_id; + } + + /* qui salvandos salvas gratis + salva me fons pietatis */ + p->print(p, " (%s %d #x%x #x%x #x%x #x%M #x%M #x%M #x%M)\n", + reg_nam, reg_id, + l->flags.l, l->flags.u, l->flags.timelimit_impatience, + l->s[0], l->s[1], l->s[2], l->s[3]); + } + } + p->print(p, ")\n"); +} + +/* mors stupebit et natura + cum resurget creatura */ +static int imprt(planner *ego, scanner *sc) +{ + char buf[MAXNAM + 1]; + md5uint sig[4]; + unsigned l, u, timelimit_impatience; + flags_t flags; + int reg_id; + unsigned slvndx; + hashtab *ht = &ego->htab_blessed; + hashtab old; + md5 m; + + if (!sc->scan(sc, + "(" WISDOM_PREAMBLE " #x%M #x%M #x%M #x%M\n", + sig + 0, sig + 1, sig + 2, sig + 3)) + return 0; /* don't need to restore hashtable */ + + signature_of_configuration(&m, ego); + if (m.s[0] != sig[0] || m.s[1] != sig[1] || + m.s[2] != sig[2] || m.s[3] != sig[3]) { + /* invalid configuration */ + return 0; + } + + /* make a backup copy of the hash table (cache the hash) */ + { + unsigned h, hsiz = ht->hashsiz; + old = *ht; + old.solutions = (solution *)MALLOC(hsiz * sizeof(solution), HASHT); + for (h = 0; h < hsiz; ++h) + old.solutions[h] = ht->solutions[h]; + } + + while (1) { + if (sc->scan(sc, ")")) + break; + + /* qua resurget ex favilla */ + if (!sc->scan(sc, "(%*s %d #x%x #x%x #x%x #x%M #x%M #x%M #x%M)", + MAXNAM, buf, ®_id, &l, &u, &timelimit_impatience, + sig + 0, sig + 1, sig + 2, sig + 3)) + goto bad; + + if (!strcmp(buf, stimeout) && reg_id == 0) { + slvndx = INFEASIBLE_SLVNDX; + } else { + if (timelimit_impatience != 0) + goto bad; + + slvndx = slookup(ego, buf, reg_id); + if (slvndx == INFEASIBLE_SLVNDX) + goto bad; + } + + /* inter oves locum praesta */ + flags.l = l; + flags.u = u; + flags.timelimit_impatience = timelimit_impatience; + flags.hash_info = BLESSING; + + CK(flags.l == l); + CK(flags.u == u); + CK(flags.timelimit_impatience == timelimit_impatience); + + if (!hlookup(ego, sig, &flags)) + hinsert(ego, sig, &flags, slvndx); + } + + X(ifree0)(old.solutions); + return 1; + + bad: + /* ``The wisdom of FFTW must be above suspicion.'' */ + X(ifree0)(ht->solutions); + *ht = old; + return 0; +} + +/* + * create a planner + */ +planner *X(mkplanner)(void) +{ + int i; + + static const planner_adt padt = { + register_solver, mkplan, forget, exprt, imprt + }; + + planner *p = (planner *) MALLOC(sizeof(planner), PLANNERS); + + p->adt = &padt; + p->nplan = p->nprob = 0; + p->pcost = p->epcost = 0.0; + p->hook = 0; + p->cost_hook = 0; + p->wisdom_ok_hook = 0; + p->nowisdom_hook = 0; + p->bogosity_hook = 0; + p->cur_reg_nam = 0; + p->wisdom_state = WISDOM_NORMAL; + + p->slvdescs = 0; + p->nslvdesc = p->slvdescsiz = 0; + + p->flags.l = 0; + p->flags.u = 0; + p->flags.timelimit_impatience = 0; + p->flags.hash_info = 0; + p->nthr = 1; + p->need_timeout_check = 1; + p->timelimit = -1; + + mkhashtab(&p->htab_blessed); + mkhashtab(&p->htab_unblessed); + + for (i = 0; i < PROBLEM_LAST; ++i) + p->slvdescs_for_problem_kind[i] = -1; + + return p; +} + +void X(planner_destroy)(planner *ego) +{ + /* destroy hash table */ + htab_destroy(&ego->htab_blessed); + htab_destroy(&ego->htab_unblessed); + + /* destroy solvdesc table */ + FORALL_SOLVERS(ego, s, sp, { + UNUSED(sp); + X(solver_destroy)(s); + }); + + X(ifree0)(ego->slvdescs); + X(ifree)(ego); /* dona eis requiem */ +} + +plan *X(mkplan_d)(planner *ego, problem *p) +{ + plan *pln = ego->adt->mkplan(ego, p); + X(problem_destroy)(p); + return pln; +} + +/* like X(mkplan_d), but sets/resets flags as well */ +plan *X(mkplan_f_d)(planner *ego, problem *p, + unsigned l_set, unsigned u_set, unsigned u_reset) +{ + flags_t oflags = ego->flags; + plan *pln; + + PLNR_U(ego) &= ~u_reset; + PLNR_L(ego) &= ~u_reset; + PLNR_L(ego) |= l_set; + PLNR_U(ego) |= u_set | l_set; + pln = X(mkplan_d)(ego, p); + ego->flags = oflags; + return pln; +} + +/* + * Debugging code: + */ +#ifdef FFTW_DEBUG +static void check(hashtab *ht) +{ + unsigned live = 0; + unsigned i; + + A(ht->nelem < ht->hashsiz); + + for (i = 0; i < ht->hashsiz; ++i) { + solution *l = ht->solutions + i; + if (LIVEP(l)) + ++live; + } + + A(ht->nelem == live); + + for (i = 0; i < ht->hashsiz; ++i) { + solution *l1 = ht->solutions + i; + int foundit = 0; + if (LIVEP(l1)) { + unsigned g, h = h1(ht, l1->s), d = h2(ht, l1->s); + + g = h; + do { + solution *l = ht->solutions + g; + if (VALIDP(l)) { + if (l1 == l) + foundit = 1; + else if (LIVEP(l) && md5eq(l1->s, l->s)) { + A(!subsumes(&l->flags, SLVNDX(l), &l1->flags)); + A(!subsumes(&l1->flags, SLVNDX(l1), &l->flags)); + } + } else + break; + g = addmod(g, d, ht->hashsiz); + } while (g != h); + + A(foundit); + } + } +} +#endif