mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-18 10:35:11 +00:00
1036 lines
25 KiB
C
1036 lines
25 KiB
C
|
/*
|
||
|
* 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
|