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
 |