/* This file is part of cqual.
   Copyright (C) 2003 The Regents of the University of California.

cqual 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, or (at your option)
any later version.

cqual 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 cqual; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */



#include <stdio.h>
#include <string.h>
#include <limits.h>
#include "color.h"
#include "effect.h"
#include "analyze.h"
#include "qerror.h"
#include "utils.h"
#include "pam.h"
#include "termhash.h"
#include "typed_ddlist.h"
#include "typed_set.h"
#include "typed_hashset.h"
#include "typed_map.h"
#include "timer.h"

/* #define DEBUG */
#define CHECK_SUMMARY_LENGTH(qe) /*assert(!(qe)->summary_time || ((qe)->u.summary_length >= 0 && (qe)->u.summary_length <= 10000))*/

/**************************************************************************
 *                                                                        *
 * Forward decls                                                          *
 *                                                                        *
 **************************************************************************/

int cmp_qual_byname (qual left, qual right);
unsigned long hash_qual(qual q);
static int cmp_qual_edge_semantic(qual_edge e1, qual_edge e2);
static unsigned long hash_qual_edge(qual_edge e);
bool eq_qual_edge_exact (qual_edge e1, qual_edge e2);
static bool qual_gate_get_qual (qual_gate *qgate, qual q);

/**************************************************************************
 *                                                                        *
 * Data Types                                                             *
 *                                                                        *
 **************************************************************************/

typedef enum { d_bak = -1, d_fwd = 1 } q_direction;


typedef enum { ek_open, ek_flow, ek_close } qedge_kind;

/* An edge in the qualifier graph */
struct Qual_edge {
  qedge_kind kind;
  int summary_time;  /* 0 = not a summary edge */
  qual q1;
  qual q2;
  int index;
  location loc;
  union {
    const char *error_message;
    /* This field often overflows, so be careful when you use it. */
    int summary_length;
  } u;

  /* The gate for this edge */
  qual_gate qgate;
};

DEFINE_HASHSET(qual_edge_hashset,cmp_qual_edge_semantic,hash_qual_edge,qual_edge);
#define scan_qual_edge_hashset(var, scanner, set) \
for (qual_edge_hashset_scan(set, &scanner), var = qual_edge_hashset_next(&scanner); \
     var; \
     var = qual_edge_hashset_next(&scanner))

DEFINE_HASHSET(voidp_hashset,ptr_cmp,ptr_hash,void*);
#define scan_voidp_hashset(var, scanner, set) \
for (voidp_hashset_scan(set, &scanner), var = voidp_hashset_next(&scanner); \
     var; \
     var = voidp_hashset_next(&scanner))


/* The source of a constant bound on a variable */
struct Qual_reason {
  enum { rk_none = 0, rk_edge = 1, rk_location = 2 } kind;
  union {
    location loc;
    struct {
      qual_edge qe;
      /* This field often overflows, so be careful when you use it. */
      int pathlength;
    } e;
  } u;
  bool ci_bound;  /*  context-independent bound (the usual type of analysis) */
};
typedef struct Qual_reason* qual_reason;

DEFINE_MAP(qual_reason_map,qual,qual_reason,hash_qual,eq_qual);
#define scan_qual_reason_map(kvar, dvar, scanner, map) \
for (qual_reason_map_scan(map, &scanner); \
     qual_reason_map_next(&scanner, &kvar, &dvar);)



/* Return codes for bound insertion.  Should be |'able. */
#define INSERT_NOTHING 0
#define INSERT_LOWER   1
#define INSERT_UPPER   2
#define INSERT_EDGE    4
#define INSERTED_NOTHING(x) ((x) == INSERT_NOTHING)
#define INSERTED_LOWER(x)   (((x) & INSERT_LOWER) != 0)
#define INSERTED_UPPER(x)   (((x) & INSERT_UPPER) != 0)
#define INSERTED_BOTH(x)    (INSERTED_LOWER(x) && INSERTED_UPPER(x))
#define INSERTED_EDGE(x)    (((x) & INSERT_EDGE) != 0)



/* Two incompatible constant bounds on a variable */
typedef struct Qual_error {
  qual lbc;
  qual ubc;
}* qual_error;

DEFINE_LIST(qual_error_list,qual_error);



/* The constant bounds on a variable */
typedef struct Qual_bounds {
  qual_reason_map lbr;
  qual_reason_map ubr;
} *qual_bounds;



typedef struct QInstantiation {
  location loc;
  qual q1;
  qual q2;
  polarity p;
} *qinstantiation;

DEFINE_LIST(qinstantiation_list, qinstantiation)



typedef enum cond_dir { c_leq_v, v_leq_c} cond_dir;

typedef struct Condition {
  cond_dir dir;
  qual c, left, right;         /* constraint is
				   c <= v ==> left <= right   if dir = c_leq_v
				   v <= c ==> left <= right   if dir = v_leq_c
				  where v is the variable this constraint
				  is associated with */
  qual_gate qgate;
  location loc;
  bool triggered;              /* TRUE iff this condition has been
				  triggered */
  const char *error_message;
} *cond;

DEFINE_BAG(cond_set, cond);
#define scan_cond_set(var,scanner,set) \
for (cond_set_scan(set, &scanner), var = cond_set_next(&scanner); \
     var; \
     var = cond_set_next(&scanner))


typedef struct po_info {
  qual_set qualifiers;
  bool flow_sensitive;       /* True if these qualifiers are flow-sensitive */
  bool nonprop;
  bool effect;		     /* True if these qualifiers can propagate over
				effect edges */
  bool casts_preserve;	     /* True if these qualifiers can propagate over
				casts_preserve edges */
} *po_info;


struct type_qualifier {
  enum { qk_constant, qk_variable, qk_link} kind;
  
  union {

    /* Constant */
    struct { 
      const char *name;		     /* Name of this qualifier */
      const char *color;             /* Color information for PAM mode */
      level_qual_t level;            /* Level information for qtype */
      sign_qual_t sign;              /* Sign information for qtype */
      qual_set lbc, ubc;             /* Upper- and lower-bounds in order */
      po_info po;                    /* Partial order this qual is in */
      int index;                     /* Index in the gate bitset. */
    } elt;

    /* Variable */
    struct {
      const char *name;
      location loc;
      aloc aloc;
      store store;                   /* for stores */
      qual_edge_hashset lb, ub;      /* upper/lower bounds (all vars), 
					the original constraint graph */
      cond_set cond_set;             /* conditional constraints pending
					on this variable */
      qual_bounds qbounds;           /* Constant bounds on this variable */
      bool interesting:1;            /* True iff this var has initial upper/
					lower bound constants */
      bool preferred:1;              /* True if we should keep this name for 
					the ecr */
      bool param:1;                  /* True if this var marks a parameter
					of a function */
      bool passed:1;                 /* True if this var marks an instantiated
					parameter of a function. */
      bool returned:1;                 /* True if this var is an instantiated
				        return */
      bool ret:1;                    /* True if this var marks the return of a 
					function */
      bool bak_seeded:1;             /* True if this function has already been
					explored (only used for ret nodes) */
      bool fwd_seeded:1;             /* True if this function has already been
					explored (only used for ret nodes) */
      bool bak_fs_seeded:1;          /* True if this function has already been
					explored (only used for ret nodes) */
      bool fwd_fs_seeded:1;          /* True if this function has already been
					explored (only used for ret nodes) */
      bool global:1;                 /* True if this qualifier decorates a 
					global variable */
      bool visited:1;                /* generic flag for graph traversal */
      bool anonymous:1;              /* Whether this variable was created 
					anonymously.  The user probably doesn't 
					care to see anonymous variables. */
      bool fwd_summary_dead_end:1;   /* When looking for summaries through this node,
					give up: there are none. */
      bool bak_summary_dead_end:1;
    } var;

  } u;

    /* Link, for union-find */
    qual link;
    int num_equiv;                 /* Union by rank */
};


DEFINE_HASHSET(qual_hashset,cmp_qual,hash_qual,qual)
#define scan_qual_hashset(var, scanner, set) \
for (qual_hashset_scan(set, &scanner), var = qual_hashset_next(&scanner); \
     var; \
     var = qual_hashset_next(&scanner))


DEFINE_SET(qual_set_byname, qual, cmp_qual_byname)
#define scan_qual_set_byname(var, scanner, set) \
for (qual_set_byname_scan(set, &scanner), var = qual_set_byname_next(&scanner); \
     var; \
     var = qual_set_byname_next(&scanner))


DEFINE_BAG(po_set, po_info);
#define scan_po_set(var,scanner,set) \
for (po_set_scan(set, &scanner), var = po_set_next(&scanner); \
     var; \
     var = po_set_next(&scanner))


DEFINE_LIST(qual_list,qual)


DEFINE_LIST(qual_edge_list, qual_edge)

DEFINE_MAP(qual_edge_pred_map,qual_edge,qual_edge,hash_qual_edge,eq_qual_edge_exact);
#define scan_qual_edge_pred_map(kvar, dvar, scanner, map) \
for (qual_edge_pred_map_scan(map, &scanner); \
     qual_edge_pred_map_next(&scanner, &kvar, &dvar);)

/**************************************************************************
 *                                                                        *
 * Global variables                                                       *
 *                                                                        *
 **************************************************************************/

enum { 
  state_start = 0, state_init = 1, state_pos_defined = 2, 
  state_finish = 3
} state = state_start;

/* exists_X_qual is set to TRUE if any X qualifiers have been defined in any
   partial orders */
bool exists_effect_qual = FALSE;
bool exists_casts_preserve_qual = FALSE;
bool exists_ptrflow_qual = FALSE;
bool exists_fieldflow_qual = FALSE;
bool exists_fieldptrflow_qual = FALSE;

/* used_X_qual is set to TRUE if after end_define_pos() there have been any
   calls to find_qual to look up an X qualifier */
bool used_fs_qual = FALSE;

/* Set (externally) to true if we should print out the qualifier
   constraint graph in dot form. */
int flag_print_quals_graph = 0; 

/* When this flag is TRUE, the type inference engine is only required
   to compute the bounds on type variables with inconsistent bounds.
   This optimization can provide a big speedup. */
int flag_errors_only = 0;

/* When this flag is TRUE, the type inference engine should perform a
   context-summary analysis in addition to the standard
   context-independent analysis. */
int flag_context_summary = 0;

/* For numbering qualifiers uniquely */
static int next_qual = 0;
/* static FILE *graph_file = NULL; */

static qual_set all_quals;
static qual_set all_vars;

/* The set of vars which are reachable (not necessarily CFL reachable)
   from the constants. */
static qual_set important_vars;

static po_set all_pos;
static po_info current_po;

static int num_constants = 0;

static int current_summary_time = 1;

/* Some common gates */
static qual_gate empty_qgate;       /* no quals */
qual_gate open_qgate;               /* all quals */
qual_gate fi_qgate;                 /* flow-insensitive quals */
qual_gate fs_qgate;                 /* flow-sensitive quals */
qual_gate effect_qgate;             /* effect quals */
qual_gate casts_preserve_qgate;     /* for collapsing below pathological casts */

qual_gate ptrflow_down_pos_qgate;
qual_gate ptrflow_down_neg_qgate;
qual_gate ptrflow_up_pos_qgate;
qual_gate ptrflow_up_neg_qgate;

qual_gate fieldflow_down_pos_qgate;
qual_gate fieldflow_down_neg_qgate;
qual_gate fieldflow_up_pos_qgate;
qual_gate fieldflow_up_neg_qgate;

qual_gate fieldptrflow_down_pos_qgate;
qual_gate fieldptrflow_down_neg_qgate;
qual_gate fieldptrflow_up_pos_qgate;
qual_gate fieldptrflow_up_neg_qgate;

/* Some distinguished qualifiers */
qual const_qual = NULL;
qual nonconst_qual = NULL;
qual volatile_qual = NULL;
qual restrict_qual = NULL;
qual noreturn_qual = NULL; /* __attribute__((noreturn)) */
qual init_qual = NULL;     /* __init */
qual noninit_qual = NULL;

/* Set (externally) to number of hotspots to track. 0 = track no
   hotspots. */
int num_hotspots = 0;         

/* Array of size num_hotspots */
qual *hotspots; 

/* For permanent data structures */
static region quals_region;

/* For resolving well-formedness conditions */
static region resolve_region;

/* List of all the qinstantiations (for checking well-formedness) */
static qinstantiation_list qinstantiations;

/* Whether the analysis permits us to unify across flow edges. */
static bool can_unify_flow = TRUE;

/* Some forward declarations. */
static bool qual_gate_set_qual (qual_gate *qgate, qual q, bool allow);
static bool qual_gate_passes_qual (qual_gate *qgate, qual q, q_direction d);
static bool qual_gate_is_closed (qual_gate *qgate);

/**************************************************************************
 *                                                                        *
 * Operations valid on all quals                                          *
 *                                                                        *
 **************************************************************************/

/* Return the ECR for this qualifier */
static qual ecr_qual(qual q)
{
  qual ecr, cur, temp;

  if (!q) return NULL;
  
  if (!q->link)
      return q;

  ecr = q;
  /* Find root */
  while (ecr->link)
    ecr = ecr->link;
	
  /* Compress path */
  cur = q;
  while (cur->link != ecr)
    {
      temp = cur->link;
      cur->link = ecr;
      cur = temp;
    }
  return ecr;
}
 
bool variable_qual(qual q)
{
  q = ecr_qual(q);
  return q->kind == qk_variable;
}

bool constant_qual(qual q)
{
  q = ecr_qual(q);
  return q->kind == qk_constant;
}

const char *name_qual(qual q)
{
  q = ecr_qual(q);
  switch (q->kind)
    {
    case qk_constant: return q->u.elt.name;
    case qk_variable: return q->u.var.name;
    default:
      fail("Unexpected kind %d for qualifier\n", q->kind);
    }
}

bool set_qual_name(qual q, const char *name)
{
  q = ecr_qual(q);
  switch (q->kind)
    {
    case qk_constant: 
      fprintf(stderr, "Warning: trying to set name of constant.\n");
      return FALSE;
    case qk_variable: q->u.var.name = name; return TRUE;
    default:
      fail("Unexpected kind %d for qualifier\n", q->kind);
    }
  
  return FALSE;
}

/* Return the unique internal name used to identify q (q's address) */
const char *unique_name_qual(qual q)
{
  q = ecr_qual(q);
  return ptr_to_ascii(q);
}

/* A total ordering on qualifiers.  Returns 0 if left = right, a value
   <0 if left < right, or a value >0 if left > right */
int cmp_qual(qual left, qual right)
{
  left = ecr_qual(left);
  right = ecr_qual(right);
  if (left == right)
    return 0;
  else if (left < right)
    return -1;
  else
    return 1;
}

int cmp_qual_byname (qual left, qual right)
{
  left = ecr_qual (left);
  right = ecr_qual (right);
  
  return strcmp (name_qual (left), name_qual (right));
}

/* Returns TRUE iff left and right are the same.  Does not generate a
   constraint. */
bool eq_qual(qual left, qual right)
{
  return (ecr_qual(left) == ecr_qual(right));
}

/* Hash function on alocs */
unsigned long hash_qual(qual q)
{
  q = ecr_qual(q);
  switch (q->kind)
    {
    case qk_constant:
      return string_hash(q->u.elt.name);
    case qk_variable:
      if (q->u.var.loc)
	return q->u.var.loc->location_index;
      else
	/* Handle NULL location case */
	return (unsigned long) q;
    default:
      fail ("unknown qual kind!\n");
    }
}

/**************************************************************************
 *                                                                        *
 * Partial order elements                                                 *
 *                                                                        *
 **************************************************************************/

void begin_po_qual(void)
{  
  if (current_po)
    fail("begin_po_qual called without previous end_po_qual\n");
  current_po = ralloc(quals_region, struct po_info);
  current_po->qualifiers = empty_qual_set(quals_region);
  current_po->flow_sensitive = FALSE;
  current_po->nonprop = FALSE;
  current_po->effect = FALSE;
  current_po->casts_preserve = FALSE;
}

void end_po_qual(void)
{
  if (!current_po)
    fail("end_po_qual called without previous begin_po_qual\n");
  po_set_insert(quals_region, &all_pos, current_po);
  current_po = NULL;
}

/* Mark the current partial order flow-sensitive */
void set_po_flow_sensitive(void)
{
  current_po->flow_sensitive = TRUE;
  current_po->nonprop = TRUE;
}

/* Mark the current partial order as non-propagating */
void set_po_nonprop(void)
{
  current_po->nonprop = TRUE;
}

/* Mark the current partial order as able to propagate over effect edges */
void set_po_effect(void)
{
  current_po->effect = TRUE;
  exists_effect_qual = TRUE;
}

/* Mark the current partial order as able to propagate over effect edges */
void set_po_casts_preserve(void)
{
  current_po->casts_preserve = TRUE;
  exists_casts_preserve_qual = TRUE;
}

qual add_qual(const char *name)
{
  /* qual_set_insert does pointer comparisons, not strcmps, so we need
     to do a special search by name first. */
  qual new_qual;

  assert(state == state_init);
  if (!current_po)
    fail("add_qual called without previous begin_po_qual\n");
  if ((new_qual = find_qual(name)))
    {
      if (new_qual->u.elt.po != current_po)
	fail("Qualifier %s in two different partial orders\n",
	     new_qual->u.elt.name);
      return new_qual;
    }
  /* We didn't find the qualifier */
  /*  printf("Adding qualifier %s\n", name);*/
  new_qual = ralloc(quals_region, struct type_qualifier);
  new_qual->kind = qk_constant;
   /* new_qual->mark = FALSE; */
  new_qual->u.elt.name = rstrdup(quals_region, name);
  new_qual->u.elt.color = NULL;
  new_qual->u.elt.level = level_value;
  new_qual->u.elt.sign = sign_pos;
  new_qual->u.elt.lbc = empty_qual_set(quals_region);
  new_qual->u.elt.ubc = empty_qual_set(quals_region);
  new_qual->u.elt.po = current_po;

  new_qual->u.elt.index = num_constants++;
  assert (num_constants <= MAX_QUALS);
  qual_gate_set_qual (&open_qgate, new_qual, TRUE);
  if (current_po->flow_sensitive)
    qual_gate_set_qual (&fs_qgate, new_qual, TRUE);
  else
    qual_gate_set_qual (&fi_qgate, new_qual, TRUE);
  if (current_po->effect)
    qual_gate_set_qual (&effect_qgate, new_qual, TRUE);
  if (current_po->casts_preserve)
    qual_gate_set_qual (&casts_preserve_qgate, new_qual, TRUE);

  qual_set_insert(quals_region, &all_quals, new_qual);
  qual_set_insert(quals_region, &current_po->qualifiers, new_qual);

  if (!strcmp(name, "const"))
    const_qual = new_qual;
  else if (!strcmp(name, "$nonconst"))
    nonconst_qual = new_qual;
  else if (!strcmp(name, "volatile"))
    volatile_qual = new_qual;
  else if (!strcmp(name, "restrict"))
    restrict_qual = new_qual;
  else if (!strcmp(name, "noreturn"))
    noreturn_qual = new_qual;
  else if (!strcmp(name, "$init"))
    init_qual = new_qual;
  else if (!strcmp(name, "$noninit"))
    noninit_qual = new_qual;
  return new_qual;
}

void add_qual_lt(qual left, qual right)
{ 
  assert(left->kind == qk_constant);
  assert(right->kind == qk_constant);
  if (!qual_set_member(left->u.elt.ubc, right))
    {
      qual_set_scanner qs;
      qual q;

      /*
      printf("Adding assumption ");
      print_qual_raw(printf, left);
      printf(" < ");
      print_qual_raw(printf, right);
      printf("\n");
      */

      qual_set_insert(quals_region, &left->u.elt.ubc, right);
      qual_set_insert(quals_region, &right->u.elt.lbc, left);

      /* Add transitively-implied assumptions */
      scan_qual_set(q, qs, right->u.elt.ubc)
	add_qual_lt(left, q);

      scan_qual_set(q, qs, left->u.elt.lbc)
	add_qual_lt(q, right);
    }
  /* We can only unify flow in the discrete partial order, which this
     obviously isn't. */
  can_unify_flow = FALSE;
}

void add_color_qual(qual q, const char *color)
{
  assert(q->kind == qk_constant);
  q->u.elt.color = rstrdup(quals_region, color);
}

void add_level_qual(qual q, level_qual_t lev)
{
  assert(q->kind == qk_constant);
  assert(lev == level_value || lev == level_ref);

  q->u.elt.level = lev;
}
                                          
void add_sign_qual(qual q, sign_qual_t sign)
{
  assert(q->kind == qk_constant);
  assert(sign == sign_pos || sign == sign_neg || sign == sign_eq);
  q->u.elt.sign = sign;
} 

/* Assert that q flows up or down the ptr type hierarchy. */
void add_ptrflow_qual(qual q, flow_qual_t f)
{
  assert(q->kind == qk_constant);
  switch (f) {
  case flow_up:
    if (sign_qual (q) == sign_pos || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&ptrflow_up_pos_qgate, q, TRUE);
    if (sign_qual (q) == sign_neg || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&ptrflow_up_neg_qgate, q, TRUE);
    break;
  case flow_down:
    if (sign_qual (q) == sign_pos || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&ptrflow_down_pos_qgate, q, TRUE);
    if (sign_qual (q) == sign_neg || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&ptrflow_down_neg_qgate, q, TRUE);
    break;
  default:
    fail ("unknown flow direction!\n");
  }
  exists_ptrflow_qual = TRUE;
}

/* Assert that q flows up or down the field type hierarchy. */
void add_fieldflow_qual(qual q, flow_qual_t f)
{
  assert(q->kind == qk_constant);
  switch (f) {
  case flow_up:
    if (sign_qual (q) == sign_pos || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&fieldflow_up_pos_qgate, q, TRUE);
    if (sign_qual (q) == sign_neg || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&fieldflow_up_neg_qgate, q, TRUE);
    break;
  case flow_down:
    if (sign_qual (q) == sign_pos || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&fieldflow_down_pos_qgate, q, TRUE);
    if (sign_qual (q) == sign_neg || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&fieldflow_down_neg_qgate, q, TRUE);
    break;
  default:
    fail ("unknown flow direction!\n");
  }
  exists_fieldflow_qual = TRUE;
}

/* Assert that q flows up or down the fieldptr type hierarchy. */
void add_fieldptrflow_qual(qual q, flow_qual_t f)
{
  assert(q->kind == qk_constant);
  switch (f) {
  case flow_up:
    if (sign_qual (q) == sign_pos || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&fieldptrflow_up_pos_qgate, q, TRUE);
    if (sign_qual (q) == sign_neg || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&fieldptrflow_up_neg_qgate, q, TRUE);
    break;
  case flow_down:
    if (sign_qual (q) == sign_pos || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&fieldptrflow_down_pos_qgate, q, TRUE);
    if (sign_qual (q) == sign_neg || sign_qual(q) == sign_eq)
      qual_gate_set_qual (&fieldptrflow_down_neg_qgate, q, TRUE);
    break;
  default:
    fail ("unknown flow direction!\n");
  }
  exists_fieldptrflow_qual = TRUE;
}

qual_set get_constant_quals (void)
{
  return all_quals;
}

/* Look up a qualifier */
qual find_qual(const char *name)
{
  qual_set_scanner qs;
  qual q;

  scan_qual_set(q, qs, all_quals)
    {
      assert(q->kind == qk_constant);
      if (!strcmp(q->u.elt.name, name))
	{
	  if (state >= state_pos_defined && q->u.elt.po->flow_sensitive)
	    used_fs_qual = TRUE;
	  return q;
	}
    }
  return NULL;
}

/* Change flow-sensitive quals from nonprop to regular quals for this pass */
void reset_flow_sensitive_quals(void)
{
  po_set_scanner ss;
  po_info po;

  scan_po_set(po, ss, all_pos)
    if (po->flow_sensitive)
      po->nonprop = FALSE;
}

level_qual_t level_qual(qual q)
{
  assert(q->kind == qk_constant);
  return q->u.elt.level;
}

sign_qual_t sign_qual(qual q)
{
  assert(q->kind == qk_constant);
  return q->u.elt.sign;
}

/* Return TRUE iff q is flow-sensitive */
bool flow_sensitive_qual(qual q)
{
  assert(q->kind == qk_constant);
  return q->u.elt.po->flow_sensitive;
}

/* Return TRUE iff q is non-propagating */
bool nonprop_qual(qual q)
{
  assert(q->kind == qk_constant);
  return q->u.elt.po->nonprop;
}	

/**************************************************************************
 *                                                                        *
 * Qualifier variables                                                    *
 *                                                                        *
 **************************************************************************/

#ifdef DEBUG
static int check_var (qual q)
{
  qual_edge_hashset_scanner qess;
  qual_edge qe;
  int errors = 0;

  q = ecr_qual (q);
  assert (q->kind == qk_variable);

  scan_qual_edge_hashset (qe, qess, q->u.var.lb)
    {
      if (! eq_qual (qe->q2, q))
	{
	  printf ("uhoh\n");
	  errors++;
	}
      CHECK_SUMMARY_LENGTH(qe);
    }
  
  scan_qual_edge_hashset (qe, qess, q->u.var.ub)
    {
      if (! eq_qual (qe->q1, q))
	{
	  printf ("uhoh\n");
	  errors++;
	}
    }

  return errors;
}
#endif

#ifdef DEBUG
static int check_vars (qual_set vars)
{
  qual_set_scanner qss;
  qual q;
  int errors = 0;

  scan_qual_set (q, qss, vars)
    {
      errors += check_var (q);
    }

  return errors;
}
#else
static int check_vars(qual_set vars)
{
  return 0;
}
#endif

qual make_qvar(const char *name, location loc, bool preferred, bool global)
{
  qual fresh;
  
  assert(state >= state_pos_defined);
  /* assert(loc); jfoster -- comment out because in effect.c a qvar will a
      NULL location is created indirectly in qtype_from_store. */

  fresh = ralloc(quals_region, struct type_qualifier);
  fresh->kind = qk_variable;
  fresh->u.var.name = name;
  fresh->u.var.loc = NULL;
  fresh->u.var.store = NULL;
  fresh->u.var.cond_set = NULL;
  fresh->u.var.lb = empty_qual_edge_hashset(quals_region);
  fresh->u.var.ub = empty_qual_edge_hashset(quals_region);
  fresh->u.var.interesting = FALSE;
  fresh->u.var.preferred = preferred;
  fresh->u.var.param = FALSE;
  fresh->u.var.passed = FALSE;  
  fresh->u.var.returned = FALSE;
  fresh->u.var.ret = FALSE;
  fresh->u.var.bak_seeded = FALSE;
  fresh->u.var.fwd_seeded = FALSE;
  fresh->u.var.visited = FALSE;
  fresh->u.var.anonymous = !preferred;  /*  FIXME */
  fresh->u.var.global = global;
  fresh->u.var.loc = loc;
  fresh->u.var.qbounds = NULL;
  fresh->link = NULL;
  
  /* Since this variable is fresh, we don't need qual_set_insert() to
     check whether this variable is already in the set.  So use
     qual_set_insert_nocheck() instead.  This gives a major speedup. */
  qual_set_insert_nocheck(quals_region,&all_vars,fresh);

  check_vars (all_vars);
  return fresh;
}

qual make_fresh_qvar(const char* base_name, location loc)
{
  const char *name;
  qual q;

  name = rsprintf(quals_region, "%s#%d", base_name, next_qual++);
  q = make_qvar(name, loc, FALSE,FALSE);
  q->u.var.anonymous = TRUE;

  return q;
}

qual find_var(const char *name)
{
  qual_set_scanner qs;
  qual q;

  scan_qual_set(q, qs, all_vars)
    {
      if (q->kind == qk_variable)
	{
	  if (!strcmp (q->u.var.name, name))
	    return q;
	}
    }
 
  return NULL;
}

qual_set get_variable_quals (void)
{
  return all_vars;
}

void set_global_qual (qual q, bool global)
{
  q = ecr_qual (q);
  assert (q->kind == qk_variable);

  assert (state < state_finish);

  q->u.var.global = global;
}

/* TODO Check if qualifier flows into this qual at cast */
void mk_no_qual_qual(location loc, qual q)
{
}

static bool is_ubounded_by_constant (qual q)
{
  q = ecr_qual (q);
  return q->u.var.qbounds && qual_reason_map_size (q->u.var.qbounds->ubr) > 0;
}

static bool is_lbounded_by_constant (qual q)
{
  q = ecr_qual (q);
  return q->u.var.qbounds && qual_reason_map_size (q->u.var.qbounds->lbr) > 0;
}

static bool is_bounded_by_constant (qual q)
{
  return is_ubounded_by_constant (q) && is_lbounded_by_constant (q);
}

/* Associate (s, al) with q; return TRUE if this triggers a propagation */
bool store_aloc_qual(qual q, store s, aloc al)
{
  q = ecr_qual(q);
  assert(q->kind == qk_variable);
  assert(!q->u.var.store && !q->u.var.aloc);
  q->u.var.store = s;
  q->u.var.aloc = al;
  if (is_lbounded_by_constant (q))
    propagate_store_cell_forward(s, al);
  if (is_ubounded_by_constant (q))
    propagate_store_cell_backward(s, al);

  if (! is_bounded_by_constant (q))
    return FALSE;
  else
    return TRUE;
}

bool global_qual(qual q)
{
  q = ecr_qual(q);
  assert(q->kind == qk_variable);
  return q->u.var.global;
}

static bool param_qual(qual q)
{
  q = ecr_qual(q);
  assert (q->kind == qk_variable);
  return q->u.var.param || global_qual(q);
}

static bool passed_qual(qual q)
{
  q = ecr_qual(q);
  assert (q->kind == qk_variable);
  return q->u.var.passed || global_qual(q);
}

static bool ret_qual(qual q)
{
  q = ecr_qual(q);
  assert (q->kind == qk_variable);
  return q->u.var.ret || global_qual(q);
}

static bool returned_qual(qual q)
{
  q = ecr_qual(q);
  assert (q->kind == qk_variable);
  return q->u.var.returned || global_qual(q);
}

bool preferred_qual(qual q)
{
  q = ecr_qual(q);
  assert(q->kind == qk_variable);
  return q->u.var.preferred;
}

location location_qual(qual q)
{
  q = ecr_qual(q);
  assert(q->kind == qk_variable);
  return q->u.var.loc;
}

static qual_edge_set _lb_qual (region r, qual q, bool context_summary)
{
  qual_reason_map_scanner qrms;
  qual c;
  qual_reason qr;
  qual_edge_set result = empty_qual_edge_set(r);
  qual_edge qe;
  qual_edge_hashset_scanner qess;

  q = ecr_qual (q);
  assert (q->kind == qk_variable);
  if (q->u.var.qbounds)
    scan_qual_reason_map(c, qr, qrms, q->u.var.qbounds->lbr)
      if (qr->ci_bound || context_summary)
	{
	  qe = ralloc (r, struct Qual_edge);
	  qe->index = qr->kind == rk_location ? location_index (qr->u.loc) : 0;
	  qe->q1 = c;
	  qe->q2 = q;
	  qe->kind = ek_flow;
	  qe->loc = qr->kind == rk_location ? qr->u.loc : NULL;
	  qual_edge_set_insert (r, &result, qe);
	}

  scan_qual_edge_hashset (qe, qess, q->u.var.lb)
    {
      CHECK_SUMMARY_LENGTH(qe);
      qual_edge_set_insert (r, &result, qe);
    }

  return result;
}

qual_edge_set lb_qual (region r, qual q)
{
  return _lb_qual (r, q, FALSE);
}

qual_edge_set cs_lb_qual (region r, qual q)
{
  return _lb_qual (r, q, TRUE);
}

static qual_edge_set _ub_qual (region r, qual q, bool context_summary)
{
  qual_reason_map_scanner qrms;
  qual c;
  qual_reason qr;
  qual_edge_set result = empty_qual_edge_set(r);
  qual_edge qe;
  qual_edge_hashset_scanner qess;

  q = ecr_qual (q);
  assert (q->kind == qk_variable);
  if (q->u.var.qbounds)
    scan_qual_reason_map(c, qr, qrms, q->u.var.qbounds->ubr)
      if (qr->ci_bound || context_summary)
	{
	  qe = ralloc (r, struct Qual_edge);
	  qe->index = qr->kind == rk_location ? location_index (qr->u.loc) : 0;
	  qe->q1 = q;
	  qe->q2 = c;
	  qe->kind = ek_flow;
	  qe->loc = qr->kind == rk_location ? qr->u.loc : NULL;
	  qual_edge_set_insert (r, &result, qe);
	}

  scan_qual_edge_hashset (qe, qess, q->u.var.ub)
    {
      CHECK_SUMMARY_LENGTH(qe);
      qual_edge_set_insert (r, &result, qe);
    }

  return result;
}

qual_edge_set ub_qual (region r, qual q)
{
  return _ub_qual (r, q, FALSE);
}

qual_edge_set cs_ub_qual (region r, qual q)
{
  return _ub_qual (r, q, TRUE);
}

int error_count_qual(qual q)
{
#if 0
   /*  Only used for hotspots, which are deprecated. */
  q = ecr_qual(q);
  assert(q->kind == qk_variable);
  return q->u.var.num_errors;
#else
  return 0;
#endif
}

/* Whether a qualifier is an effect qualifier */
bool effect_qual(qual q)
{
  q = ecr_qual(q);
  assert(q->kind == qk_constant);
  return q->u.elt.po->effect;
}

/* Return TRUE iff q has bound as an originally-specified lower-bound */
static bool _has_lb_qual(qual q, qual bound, bool context_summary)
{
  bool result = FALSE;

  q = ecr_qual (q);
  assert (q->kind == qk_variable);

  if (constant_qual(bound))
    {
      qual_reason qr;
      
      if (q->u.var.qbounds && qual_reason_map_lookup(q->u.var.qbounds->lbr, bound, &qr))
	result = qr->kind == rk_location || result;
    }
  else
    {
      qual_edge_set_scanner qess;
      qual_edge qe;
      region scratch = newregion();
      
      scan_qual_edge_set(qe, qess, _lb_qual(scratch, q, context_summary))
	{
	  if (qe->kind == ek_flow)
	    {
	      if (eq_qual(bound, qe->q1))
		{
		  result = TRUE;
		  break;
		}
	    }
	  CHECK_SUMMARY_LENGTH(qe);
	}
      
      deleteregion (scratch);
    }

  return result;
}

bool has_lb_qual(qual q, qual bound)
{
  return _has_lb_qual (q, bound, FALSE);
}

bool cs_has_lb_qual(qual q, qual bound)
{
  return _has_lb_qual (q, bound, TRUE);
}

/* Return TRUE iff q has bound as an originally-specified upper-bound */
static bool _has_ub_qual(qual q, qual bound, bool context_summary)
{
  bool result = FALSE;

  q = ecr_qual (q);
  assert (q->kind == qk_variable);

  if (constant_qual(bound))
    {
      qual_reason qr;
      
      if (q->u.var.qbounds && qual_reason_map_lookup(q->u.var.qbounds->ubr, bound, &qr))
	result =  qr->kind == rk_location || result;
    }
  else
    {
      qual_edge_set_scanner qess;
      qual_edge qe;
      region scratch = newregion();
      
      scan_qual_edge_set(qe, qess, _ub_qual(scratch, q, context_summary))
	{
	  if (qe->kind == ek_flow)
	    {
	      if (eq_qual(bound, qe->q2))
		{
		  result = TRUE;
		  break;
		}
	    }
	  CHECK_SUMMARY_LENGTH(qe);
	}
      
      deleteregion (scratch);
    }

  return result;
}

bool has_ub_qual(qual q, qual bound)
{
  return _has_ub_qual (q, bound, FALSE);
}

bool cs_has_ub_qual(qual q, qual bound)
{
  return _has_ub_qual (q, bound, TRUE);
}

/* Return TRUE iff q has bound as an originally-specified bound */
bool has_qual(qual q, qual bound)
{
  return has_lb_qual(q, bound) || has_ub_qual(q, bound);
}

bool cs_has_qual(qual q, qual bound)
{
  return cs_has_lb_qual(q, bound) || cs_has_ub_qual(q, bound);
}

/* Return TRUE iff q has any flow-sensitive qualifier as an
   originally-specified bound. */
bool has_fs_qual(qual q)
{
  qual qc;
  qual_reason qr;
  qual_reason_map_scanner qrms;

  q = ecr_qual (q);
  if (! q->u.var.qbounds)
    return FALSE;
  
  scan_qual_reason_map (qc, qr, qrms, q->u.var.qbounds->lbr)
    if ((qr->kind == rk_location || qr->kind == rk_none) &&
	qc->u.elt.po->flow_sensitive)
      return TRUE;

  scan_qual_reason_map (qc, qr, qrms, q->u.var.qbounds->ubr)
    if ((qr->kind == rk_location || qr->kind == rk_none) &&
	qc->u.elt.po->flow_sensitive)
      return TRUE;

  return FALSE;
}

/**************************************************************************
 *                                                                        *
 * Queries on the ordering                                                *
 *                                                                        *
 **************************************************************************/

/* Return TRUE iff the constraints imply q1 <= q2, where at least one
   of q1 and q2 is a constant. This function can only be called after
   finish_quals(). */
static bool _leq_qual (qual q1, qual q2, bool context_summary)
{
  qual_reason qr;

  assert (state == state_finish);
  
  q1 = ecr_qual (q1);
  q2 = ecr_qual (q2);

  if (eq_qual (q1, q2))
    return TRUE;

  if (constant_qual(q1) && variable_qual(q2))
    {
      qual c;
      qual_reason_map_scanner qrms;

      if (!q2->u.var.qbounds)
	return FALSE;

      scan_qual_reason_map (c, qr, qrms, q2->u.var.qbounds->lbr)
	if (qr->ci_bound || context_summary)
	  if (leq_qual (q1, c))
	    return TRUE;

      return FALSE;
    }
  else if (variable_qual(q1) && constant_qual(q2))
    {
      qual c;
      qual_reason_map_scanner qrms;

      if (!q1->u.var.qbounds)
	return FALSE;

      scan_qual_reason_map (c, qr, qrms, q1->u.var.qbounds->ubr)
	if (qr->ci_bound || context_summary)
	  if (leq_qual (c, q2))
	    return TRUE;

      return FALSE;
     }
  else if (constant_qual(q1) && constant_qual(q2))
    {
      return qual_set_member (q1->u.elt.ubc, q2);
    }
  else
    {
      assert (0);
      return FALSE;
    }
}

bool leq_qual (qual q1, qual q2)
{
  return _leq_qual (q1, q2, FALSE);
}

bool cs_leq_qual (qual q1, qual q2)
{
  return _leq_qual (q1, q2, TRUE);
}

int qual_error_cmp_fn(const qual_error qe1, const qual_error qe2)
{
  int r = strcmp(qe1->lbc->u.elt.name, qe2->lbc->u.elt.name);
  if (r)
    return r;
  return strcmp(qe1->ubc->u.elt.name, qe2->ubc->u.elt.name);
}

/* Returns TRUE iff left <= right is consistent with current
   system of constraints.  By defn, returns TRUE if left and right are
   in distinct partial orders.
   
   If error_list is non-NULL, then each contradictory pair of bounds will be 
   inserted into error_list using region r. */
static bool _may_leq_qual(qual left, qual right, bool context_summary,
		      qual_error_list* error_list, region r)
{
  qual_reason_map_scanner scan_lb, scan_ub;
  qual lbc, ubc;
  qual_reason lbr, ubr;
  bool retval = TRUE;

  left = ecr_qual(left);
  right = ecr_qual(right);

  /* Comparing two constants: they are leq if they are in separate
     po's or left <= right in the poset. */
  if (constant_qual(left) && constant_qual(right))
    {
      assert (state >= state_pos_defined);

      if (left == right)
	return TRUE;

      return (qual_set_member(left->u.elt.ubc, right) ||
	      !qual_set_member(left->u.elt.po->qualifiers, right));
    }

  /* var <= const. This is true if every lower bound of var is <=
     const */
  else if (variable_qual(left) && constant_qual(right))
    {
       /* assert (state >= state_finish); */

      if (! left->u.var.qbounds)
	return TRUE;

      scan_qual_reason_map(lbc,lbr,scan_lb,left->u.var.qbounds->lbr)
	if (lbr->ci_bound || context_summary)
	  if (! may_leq_qual (lbc, right))
	    return FALSE;
      
      return TRUE;
    }

  /* const <= var. This is true if every upper bound of var is >=
     const. */
  else if (constant_qual(left) && variable_qual(right))
    {
       /* assert (state >= state_finish); */

      if (! right->u.var.qbounds)
	return TRUE;

      scan_qual_reason_map(ubc,ubr,scan_ub,right->u.var.qbounds->ubr)
	if (ubr->ci_bound || context_summary)
	  if (! may_leq_qual (left, ubc))
	    return FALSE;
      
      return TRUE;
    }

  /* var1 <= var2. This is consistent if every lower bound of var1 is
     <= every upper bound of var2 */
   /* assert (state >= state_finish); */

  if (! left->u.var.qbounds)
    return TRUE;
  if (! right->u.var.qbounds)
    return TRUE;
  
  scan_qual_reason_map(lbc,lbr,scan_lb,left->u.var.qbounds->lbr)
    if (lbr->ci_bound || context_summary)
      scan_qual_reason_map(ubc,ubr,scan_ub,right->u.var.qbounds->ubr)
	if (ubr->ci_bound || context_summary)
	  {
	    assert (constant_qual(lbc) && constant_qual(ubc));
	    if (! may_leq_qual (lbc, ubc))
	      {
#ifdef DEBUG
		print_qual_raw(printf,lbc, &open_qgate);
		printf(" >= ");
		print_qual_raw(printf,ubc, &open_qgate);
		putchar('\n');
#endif
		if (error_list != NULL)
		  {
		    qual_error qe = ralloc (r, struct Qual_error);
		    qe->lbc = lbc;
		    qe->ubc = ubc;
		    *error_list = qual_error_list_cons (r, *error_list, qe);
		    
		    retval = FALSE;
		  }
		else
		  return FALSE;
	      }
	  }

  if (error_list)
    qual_error_list_sort(*error_list, qual_error_cmp_fn);

  return retval;
}

/* Returns TRUE iff left <= right is consistent with current
   system of constraints.  By defn, returns TRUE if left and right are
   in distinct partial orders. */
bool may_leq_qual(qual left, qual right)
{
  return _may_leq_qual(left, right, FALSE, NULL, NULL);
}

bool cs_may_leq_qual(qual left, qual right)
{
  return _may_leq_qual(left, right, TRUE, NULL, NULL);
}

static void reset_visited (qual_set vars)
{
  qual_set_scanner qss;
  qual q;

  scan_qual_set (q, qss, vars)
    if (q->kind == qk_variable)
      q->u.var.visited = FALSE;
}

/**************************************************************************
 *                                                                        *
 * Print/View Qualifiers                                                  *
 *                                                                        *
 **************************************************************************/

/* Print the raw qualifiers */
int print_qual_raw(printf_func pf, qual q, qual_gate *qgate)
{
  if (!q)
    fail("Null qualifier in print_qual_raw\n");
  if (q->link)
    return print_qual_raw(pf, q->link, qgate);
  switch(q->kind)
    {
    case qk_constant: return pf("%s", q->u.elt.name);
    case qk_variable: return pf("%s", q->u.var.name);
    default: fail("Unexpected kind %d for qualifier\n", q->kind);
    }
}

/* TODO Print the qualifiers, nicely */
static int _print_qual(printf_func pf, qual q, qual_gate *qgate, bool context_summary)
{
  q = ecr_qual(q);

  switch (q->kind)
    {
    case qk_constant:
      return print_qual_raw(pf, q, qgate);
    case qk_variable:
      if (q->u.var.qbounds)
	{
	  qual_reason_map_scanner qrms;
	  qual_reason qr;
	  qual qc;
	  region scratch;
	  qual_set_byname bounds;
	  qual_set_scanner qss;
	  int printed = 0;
	  bool first = TRUE;

	  scratch = newregion();

	  bounds = empty_qual_set(scratch);

	  scan_qual_reason_map (qc, qr, qrms, q->u.var.qbounds->lbr)
	    if ((qr->ci_bound || context_summary) && 
		(sign_qual(qc) == sign_pos || sign_qual(qc) == sign_eq) &&
		qual_gate_get_qual(qgate, qc))
	      qual_set_byname_insert (scratch, &bounds, qc);
	  scan_qual_reason_map (qc, qr, qrms, q->u.var.qbounds->ubr)
	    if ((qr->ci_bound || context_summary) && 
		sign_qual(qc) == sign_neg &&
		qual_gate_get_qual(qgate, qc))
	      qual_set_byname_insert (scratch, &bounds, qc);

	  qual_set_byname_remove_dups (cmp_qual_byname, bounds);
	  qual_set_byname_sort (bounds);

	  scan_qual_set_byname (qc, qss, bounds)
	    {
	      if (!first)
		printed += pf (" ");
	      printed += print_qual_raw (pf, qc, qgate);
	      first = FALSE;
	    }

	  deleteregion (scratch);
	  return printed;
	}
      return 0;
    default:
      assert (0);
      return 0;
    }

}

int print_qual(printf_func pf, qual q, qual_gate *qgate)
{
  return _print_qual (pf, q, qgate, FALSE);
}

int cs_print_qual(printf_func pf, qual q, qual_gate *qgate)
{
  return _print_qual (pf, q, qgate, TRUE);
}

/* Apply f to all possible values of q.  f is applied to NULL in
   the error case. */
void scan_qual_bounds(qual q, qual_gate *qgate, qual_traverse_fn f, void *arg)
{
  qual_reason_map_scanner qrms;
  qual p;
  qual_reason qr;

  q = ecr_qual(q);
  assert(q->kind == qk_variable);

  if (! q->u.var.qbounds)
     return;

  scan_qual_reason_map(p, qr, qrms, q->u.var.qbounds->lbr)
    if (((sign_qual(p) == sign_pos) || (sign_qual(p) == sign_eq)) &&
	qual_gate_get_qual(qgate, p))
      f(p, arg);

  scan_qual_reason_map(p, qr, qrms, q->u.var.qbounds->ubr)
    if (sign_qual(p) == sign_neg &&      /* eq handled above */
	qual_gate_get_qual(qgate, p))
      f(p, arg);

  /* Do nothing for nonprop qualifiers */
}

struct add_info
{
  region r;
  qual_set_byname *b;
};

/* Add q to bounds */
static void add_bound(qual q, struct add_info *ai)
{
  qual_set_byname_insert(ai->r, ai->b, q);
}

/* Apply f to all possible values of q in sorted order. */
void scan_qual_bounds_sorted(qual q, qual_gate *qgate, qual_traverse_fn f, void *arg)
{
  region scratch_region;
  qual_set_byname bounds;
  qual_set_byname_scanner qsbn;
  struct add_info ai;
  qual p;

  scratch_region = newregion();
  ai.r = scratch_region;
  ai.b = &bounds;
  bounds = empty_qual_set_byname(ai.r);
  scan_qual_bounds(q, qgate, (qual_traverse_fn) add_bound, &ai);
  qual_set_byname_sort(bounds);
  scan_qual_set_byname(p, qsbn, bounds)
    f(p, arg);
  deleteregion(scratch_region);
}


#define ERROR_BOUND ((qual)-1)

static void color_bound_qual_fn(qual q, void *arg)
{
  qual *bound = (qual*)arg;
  po_set_scanner pss;
  po_info po;

  if (*bound == ERROR_BOUND)
    return;

  if (*bound == NULL)
    {
      *bound = q;
      return;
    }

  if (q->u.elt.po == (*bound)->u.elt.po)
    {
      *bound = ERROR_BOUND;
      return;
    }

  scan_po_set(po, pss, all_pos)
    {
      if (po == q->u.elt.po)
	{
	  *bound = q;
	  return;
	}
      if (po == (*bound)->u.elt.po)
	return;
    }
}

/* Return the constant bound on q that should determine
   the color of q, or NULL if no bounds on q, or
   -1 if q has conflicting bounds. */
qual color_bound_qual(qual q, qual_gate *qgate)
{
  q = ecr_qual(q);
  switch (q->kind)
    {
    case qk_constant:
      return q;
    case qk_variable:
      {
	qual bound = NULL;
	scan_qual_bounds(q, qgate, color_bound_qual_fn, &bound);
	return bound;
      }
    default:
      fail("Unexpected kind %d for qualifier\n", q->kind);
    }
}

/* Return the color of qualifier q.  q may be a constant or variable. */
const char *color_qual(qual q, qual_gate *qgate)
{
  qual cb;
  if (q == NULL)
    return NULL;
  if (q == ERROR_BOUND)
    return pam_color_multiqual;

  cb = color_bound_qual(q, qgate);
  if (cb == NULL)
    return NULL;
  else if (cb == ERROR_BOUND)
    return pam_color_multiqual;
  else
    return cb->u.elt.color;
}

/* Given the color bounds q1 and q2, on different levels of a qtype,
   choose the color bound for the whole qtype. */
qual color_bound_qual_qtype_combine(qual q1, qual q2)
{
  if (q1 == NULL)
    return q2;
  if (q2 == NULL)
    return q1;
  if (q1 == ERROR_BOUND)
    return q1;
  if (q2 == ERROR_BOUND)
    return q2;

  assert (constant_qual(q1) && constant_qual(q2));
  
  if (q1->u.elt.po == q2->u.elt.po)
    {
      qual_set_scanner qss;
      qual po_elt;
      
      scan_qual_set(po_elt, qss, q1->u.elt.po->qualifiers)
	{
	  if (eq_qual(po_elt, q1))
	    return q1;
	  if (eq_qual(po_elt, q2))
	    return q2;	      
	}
      fail("Qualifiers not in their own po???");
    }
  else
    {
      po_set_scanner pss;
      po_info po;

      scan_po_set(po, pss, all_pos)
	{
	  if (po == q1->u.elt.po)
	    return q1;
	  if (po == q2->u.elt.po)
	    return q2;
	}
      fail("Qualifiers not in any po???");
    }
}

/**************************************************************************
 *                                                                        *
 * Qual edges                                                             *
 *                                                                        *
 **************************************************************************/

static void qual_gate_init (qual_gate * qgate, bool backward, bool forward)
{
  qgate->backward = backward;
  qgate->forward = forward;
  memset (qgate->mask, 0, sizeof (qgate->mask));
}

/* Low-low-level gate stuff */
static bool qual_gate_get (gate_word *mask, unsigned int bitindex)
{
  int index;
  int getmask;
  index = bitindex / BITS_PER_GATE_WORD;
  getmask = 1 << (bitindex % BITS_PER_GATE_WORD);
  assert (index < num_constants);
  return (mask[index] & getmask) != 0;
}

static bool qual_gate_set (gate_word *mask, unsigned int bitindex, bool val)
{
  int index;
  int setmask;
  bool old;
  index = bitindex / BITS_PER_GATE_WORD;
  setmask = 1 << (bitindex % BITS_PER_GATE_WORD);
  assert (index < num_constants);
  old = (mask[index] & setmask) != 0;
  if (val)
    mask[index] |= setmask;
  else
    mask[index] &= (~setmask);
  return old;
}

/* Low-level stuff. */
static void qual_gate_set_all (gate_word *mask, bool val)
{
  gate_word newval = val ? -1 : 0;
  int i;
  for (i = 0; i < GATE_WORDS; i++)
    mask[i] = newval & open_qgate.mask[i];
}

static bool qual_gate_and (gate_word *a, gate_word *b, gate_word *r)
{
  int i;
  bool rval = TRUE;
  for (i = 0; i < GATE_WORDS; i++)
    {
      r[i] = a[i] & b[i] & open_qgate.mask[i];
      rval = rval || (r[i] != 0);
    }
  return rval;
}

static void qual_gate_or (gate_word *a, gate_word *b, gate_word *r)
{
  int i;
  for (i = 0; i < GATE_WORDS; i++)
    {
      r[i] = (a[i] | b[i]) & open_qgate.mask[i];
    }
}

/* High-level stuff. */
static bool qual_gate_set_qual (qual_gate *qgate, qual q, bool allow)
{
  q = ecr_qual (q);
  assert (q->kind == qk_constant);
  return qual_gate_set (qgate->mask, q->u.elt.index, allow);
}

#if 0
static void qual_gate_set_qual_set (qual_gate *qgate, qual_set qs, bool allow)
{
  qual q;
  qual_set_scanner qss;
  if (qs)
    scan_qual_set (q, qss, qs)
      qual_gate_set_qual (qgate, q, allow);
  else
    qual_gate_set_all (qgate->mask, allow);
}
#endif

#if 1
static void qual_gate_set_po (qual_gate *qgate, po_info po, bool allow)
{
  qual_set_scanner qss;
  qual q;

  scan_qual_set (q, qss, po->qualifiers)
    qual_gate_set_qual (qgate, q, allow);
}
#endif

#if 1
void qual_gate_set_qual_po (qual_gate *qgate, qual q, bool allow)
{
  q = ecr_qual (q);
  assert (q->kind == qk_constant);
  qual_gate_set_po (qgate, q->u.elt.po, allow);
}
#endif

static bool qual_gate_get_qual (qual_gate *qgate, qual q)
{
  q = ecr_qual (q);
  assert (q->kind == qk_constant);
  return qual_gate_get (qgate->mask, q->u.elt.index);
}

static bool qual_gate_passes_qual (qual_gate *qgate, qual q, q_direction d)
{
  switch (d)
    {
    case d_fwd:
      if (! qgate->forward)
	return FALSE;
      break;
    case d_bak:
      if (! qgate->backward)
	return FALSE;
      break;
    default:
      fail ("Unknown direction in qual_gate_passes\n");
    }
  return qual_gate_get_qual (qgate, q);
}

#if 0
static bool qual_gate_passes_set (qual_gate *qgate, qual_set qs, q_direction d)
{
  qual_set_scanner qss;
  qual q;

  scan_qual_set (q, qss, qs)
    if (!qual_gate_passes_qual (qgate, q, d))
      return FALSE;
  return TRUE;
}
#endif

static bool qual_gate_is_closed (qual_gate *qgate)
{
  int i;

  if (!(qgate->backward || qgate->forward))
    return TRUE;

  for (i = 0; i < GATE_WORDS; i++)
    if (qgate->mask[i] != 0)
      return FALSE;

  return TRUE;
}

static bool compose_qual_gates (qual_gate *a, qual_gate *b, qual_gate *r)
{
  bool forward = a->forward && b->forward;
  bool backward = a->backward && b->backward;
  gate_word rm[GATE_WORDS];

  if (! (forward || backward))
    return FALSE;
  r->forward = forward;
  r->backward = backward;
  if (!qual_gate_and (a->mask, b->mask, rm))
    return FALSE;
  memcpy (r->mask, rm, sizeof (r->mask));
  return TRUE;
}

static void qual_gate_to_string (qual_gate* qgate, char* buf, int len)
{
  static struct { qual_gate *qgate; char* name; } gates[] = 
    {
      {&open_qgate, "ALL" },
      {&fi_qgate, "FI" },
      {&fs_qgate, "FS" },
      {&effect_qgate, "EFF" },
      {&casts_preserve_qgate, "CASTS-PR" },
      {&ptrflow_down_pos_qgate, "PTR-D-P" },
      {&ptrflow_down_neg_qgate, "PTR-D-N" },
      {&ptrflow_up_pos_qgate, "PTR-U-P" },
      {&ptrflow_up_neg_qgate, "PTR-U-N" },
      {&fieldflow_down_pos_qgate, "FIELD-D-P" },
      {&fieldflow_down_neg_qgate, "FIELD-D-N" },
      {&fieldflow_up_pos_qgate, "FIELD-U-P" },
      {&fieldflow_up_neg_qgate, "FIELD-U-N" },
      {&fieldptrflow_down_pos_qgate, "FPTR-D-P" },
      {&fieldptrflow_down_neg_qgate, "FPTR-D-N" },
      {&fieldptrflow_up_pos_qgate, "FPTR-U-P" },
      {&fieldptrflow_up_neg_qgate, "FPTR-U-N" }
    };
  int i;

  for (i = 0; i < sizeof (gates) / sizeof (gates[0]); i++)
    if (!memcmp (qgate, gates[i].qgate, sizeof (*qgate)))
      {
	strncpy (buf, gates[i].name, len - 1);
	buf[len-1] = '\0';
	return;
      }

  /* It's not one of the regular ones, so make a custom label */
  strncpy (buf, "CUSTOM", len - 1);
  buf[len-1] = '\0';
}

qual qual_edge_other_end (qual_edge qe, qual q)
{
  CHECK_SUMMARY_LENGTH(qe);
  if (eq_qual (qe->q1, q))
    return qe->q2;
  else
    return qe->q1;
}

location qual_edge_loc (qual_edge qe)
{
  CHECK_SUMMARY_LENGTH(qe);
  return qe->loc;
}

unsigned long hash_qual_edge(qual_edge e)
{
  CHECK_SUMMARY_LENGTH(e);
  return e->index + ((unsigned long)ecr_qual(e->q1)) + ((unsigned long)ecr_qual(e->q2));
}

int cmp_qual_edge (qual_edge e1, qual_edge e2)
{
  int r; 

  CHECK_SUMMARY_LENGTH(e1);
  CHECK_SUMMARY_LENGTH(e2);

  if ((r = cmp_qual (e1->q1, e2->q1)))
    return r;
  if ((r = cmp_qual (e1->q2, e2->q2)))
    return r;
  if ((r = strcmp(e1->loc->filename, e2->loc->filename)))
    return r;
  if (((r = e1->loc->filepos - e2->loc->filepos)))
    return r;
  return e1->kind - e2->kind;
}

int cmp_qual_edge_exact (qual_edge e1, qual_edge e2)
{
  CHECK_SUMMARY_LENGTH(e1);
  CHECK_SUMMARY_LENGTH(e2);
  return memcmp (e1, e2, sizeof (*e1));
}

bool eq_qual_edge_exact (qual_edge e1, qual_edge e2)
{
  CHECK_SUMMARY_LENGTH(e1);
  CHECK_SUMMARY_LENGTH(e2);
  return cmp_qual_edge_exact(e1, e2) == 0;
}

int cmp_qual_edge_semantic(qual_edge e1, qual_edge e2)
{
  int r;
  CHECK_SUMMARY_LENGTH(e1);
  CHECK_SUMMARY_LENGTH(e2);
  if ((r = e1->kind - e2->kind))
    return r;
  if ((r = ecr_qual(e1->q1) - ecr_qual(e2->q1)))
    return r;
  if ((r = ecr_qual(e1->q2) - ecr_qual(e2->q2)))
    return r;
  if ((r = e1->index - e2->index))
    return r;
  if ((r = e1->qgate.backward - e2->qgate.backward))
    return r;
  if ((r = e1->qgate.forward - e2->qgate.forward))
    return r;
  return 0;
}

bool eq_qual_edge_semantic (qual_edge e1, qual_edge e2)
{
  CHECK_SUMMARY_LENGTH(e1);
  CHECK_SUMMARY_LENGTH(e2);
  return cmp_qual_edge_semantic(e1, e2) == 0;
}

/*
  Make a new edge with symbol s between q1 and q2. All edges are kept
  across queries
 */
static inline bool mkqual_edge(location loc, qedge_kind s, qual q1, qual q2,
			       qual_gate* qgate, const char *error_message,
			       qual_edge *ne)
{
  struct Qual_edge tqe;
  bool retval = FALSE;
  qual_edge result = NULL;
  int l = !loc ? 0 : location_index(loc);

  q1 = ecr_qual (q1);
  q2 = ecr_qual (q2);

  if (! (qgate->backward || qgate->forward))
    return FALSE;
  if (qual_gate_is_closed (qgate))
    return FALSE;

  tqe.kind = s;
  tqe.summary_time = 0;
  tqe.q1 = q1;
  tqe.q2 = q2;
  tqe.index = l;
  tqe.loc = loc;
  tqe.u.error_message = error_message;
  tqe.qgate = *qgate;

  if (! qual_edge_hashset_hash_search(q1->u.var.ub, eq_qual_edge_semantic, 
				      &tqe, &result))
    {
      result = ralloc(quals_region,struct Qual_edge);
      memcpy (result, &tqe, sizeof (*result));

      qual_edge_hashset_insert(quals_region,&q1->u.var.ub,result);
      qual_edge_hashset_insert(quals_region,&q2->u.var.lb,result);
      
      retval = TRUE;
    }
  else
    {
      /* FIXME: Shouldn't we return TRUE when this call widens the gate?
	 But then, how do we store multiple summary paths with incomparable 
	 gates? Ignore the problem for now. */
      qual_gate_or (result->qgate.mask, qgate->mask, 
		    result->qgate.mask);
      /* FIXME: what if the edge already exists with a different error_message? */
    }

  CHECK_SUMMARY_LENGTH(result);

  if (ne)
    *ne = result;

  return retval;
}

static int edge_length(qual_edge qe)
{
  CHECK_SUMMARY_LENGTH(qe);
  if (qe->summary_time)
    return qe->u.summary_length;
  else
    return 1;
}

#if 0
static int total_edge_list_length (qual_edge_list qel)
{
  int tell;
  qual_edge_list_scanner qels;
  qual_edge e;

  if (qel == (qual_edge_list)TRUE)
    return 0;

  tell = 0;
  qual_edge_list_scan (qel, &qels);
  while (qual_edge_list_next(&qels,&e))
    {
      if (e->summary)
	tell += total_edge_list_length (e->summary);
      else
	tell++;
    }
  
  return tell;
}
#endif

#if 0
static bool find_other_end (qual_edge_list qel, qual this_end, qual* other_end)
{
  if (eq_qual (this_end, (qual_edge_list_head(qel))->q1))
    {
      *other_end = (qual_edge_list_get_tail(qel))->q2;
      return TRUE;
    }
  else
    {
      *other_end = (qual_edge_list_head(qel))->q1;
      return FALSE;
    }
}
#endif

static polarity qedge_kind_to_polarity (qedge_kind ek)
{
  switch (ek)
    {
    case ek_close:
      return p_pos;
    case ek_open:
      return p_neg;
    case ek_flow:
      return p_non;
    default:
      printf ("Unknown edge_kind: %d\n", (int)ek);
    }

  return p_non;
}

/**************************************************************************
 *                                                                        *
 * Conditional Constraint Sets                                            *
 *                                                                        *
 **************************************************************************/

/* Add condition to conds.  May add duplicates. */
static cond mkcond(cond_dir dir, qual_gate *qgate, qual c, qual left,
		   qual right, location loc, const char * error_message)
{
  cond cond;

  cond = ralloc(quals_region, struct Condition);
  cond->dir = dir;
  cond->c = c;
  cond->qgate = *qgate;
  cond->left = left;
  cond->right = right;
  cond->loc = loc;
  cond->triggered = FALSE;
  cond->error_message = error_message;
  return cond;
}

/* Adds the conditional constraint l1<=r1 ==> l2<=r2.  This constraint
   is allowed only if at least one of {l1,r1} is a constant. */
void cond_mkleq_qual(location loc, qual_gate *qgate1, qual l1, qual r1,
		     qual_gate *qgate2, qual l2, qual r2,
		     const char *error_message)
{
  assert(constant_qual(l1) || constant_qual(r1));
#ifdef DEBUG
  print_qual_raw(printf, l1, &open_qgate);
  printf(" %p <= ", l1);
  print_qual_raw(printf, r1, &open_qgate);
  printf(" %p  ==>  ", r1);
  print_qual_raw(printf, l2, &open_qgate);
  printf(" %p <= ", l2);
  print_qual_raw(printf, r2, &open_qgate);
  printf(" %p\n", r2);
#endif

  l1 = ecr_qual(l1);
  r1 = ecr_qual(r1);
  l2 = ecr_qual(l2);
  r2 = ecr_qual(r2);
  if (leq_qual(l1, r1))
    /* Condition is true */
    mkleq_qual(loc, qgate2, l2, r2, error_message);
  else if (! constant_qual(l1) || ! constant_qual(r1))
    {
      /* Condition is not true yet; add constraint to pending set */
      qual v, c;
      cond_dir dir;

      if (constant_qual(l1))
	{ c = l1; v = ecr_qual(r1); dir = c_leq_v; }
      else
	{ c = r1; v = ecr_qual(l1); dir = v_leq_c; }
      assert (constant_qual(c) && variable_qual(v));
      cond_set_insert(quals_region, &v->u.var.cond_set,
		      mkcond(dir, qgate2, c, l2, r2, loc, error_message));
    }
}

/* If there are any conditional constraint depending on q, trigger
   any newly satisfied ones */
#if 0
static void cond_set_trigger(qual q)
{
  cond_set_scanner css;
  cond cond;

  q = ecr_qual(q);
  assert(q->kind == qk_variable);
  scan_cond_set(cond, css, q->u.var.cond_set)
    if (!cond->triggered)
      {
	switch (cond->dir)
	  {
	  case c_leq_v:
	    cond->triggered = leq_qual(cond->c, q);
	    break;
	  case v_leq_c:
	    cond->triggered = leq_qual(q, cond->c);
	    break;
	  default:
	    fail("Unexpected condition dir %x\n", cond->dir);
	  }
	if (cond->triggered)
	  mkleq_qual(cond->loc, cond->kind, cond->left,
		     cond->right, cond->error_message);
      }

}
#endif

/**************************************************************************
 *                                                                        *
 * Error path traversal                                                   *
 *                                                                        *
 **************************************************************************/

#if 0
typedef struct Qual_step
{
  struct Qual_step* next;

  qual_edge current;
  enum { sd_down, sd_up, sd_none } direction;
} *qual_step;

DEFINE_LIST(qual_step_list, qual_step)

/* Note: This function looks for the shortest path, crossing only flow
   edges, from start to end, _BEFORE UNIFICATION_.  So this function
   often uses == instead of eq_qual().  Please be careful editing this
   code. */
static qual_edge_list find_shortest_flow_path (qual start, qual end, 
					bool bidi, region r)
{
  qual_step_list worklist;
  qual_step_list_scanner qsls;
  qual_edge_list result;
  qual_set visited;
  qual_step qs;
  region scratch;

  scratch = newregion();
  worklist = new_qual_edge_list_list(scratch);
  
  /* Prime the worklist */
  qs = ralloc (scratch, struct Qual_step);
  qs->next = NULL;
  qs->current = NULL;
  qs->direction = sd_none;
  qual_step_list_append_elt (worklist, qs);

  qual_step_list_scan(worklist, &qsls);
  while (qual_step_list_next(&qsls, &qs))
    {
      qual current;
      qual_edge e;
      qual_edge_set_scanner qess;

      switch (qs->direction)
	{
	case sd_down:
	  current = qs->current->q1;
	  break;
	case sd_up:
	  current = qs->current->q2;
	  break;
	case sd_none:
	  current = start;
	  break;
	default:
	  abort ("unknown qual_step direction!\n");
	}

      if (current == end)
	break;

      if (!qual_set_member (visited, current))
	{
	  qual_set_insert (scratch, &visited, current);

	  scan_qual_edge_set (e, qess, current->u.var.ub)
	    {
	      if (e->q1 == current)
		{
		    qual_step tqs = ralloc (scratch, struct Qual_step);
		    tqs->next = qs;
		    tqs->current = e;
		    qs->direction = sd_up;
		}
	    }
	  if (bidi)
	    {
	      scan_qual_edge_set (e, qess, current->u.var.lb)
		{
		  if (e->q2 == current)
		    {
		      qual_step tqs = ralloc (scratch, struct Qual_step);
		      tqs->next = qs;
		      tqs->current = e;
		      qs->direction = sd_down;
		    }
		}
	    }
	}
    }

  visited = empty_qual_set();
}
#endif

/* Find the flow part of a summary path.  Note: this assumes summary
   edges do not cross global nodes. */
static bool find_flow_path (region r, qual start, qual target, 
			    int summary_time, qual_hashset visited,
			    qual_edge_list path)
{
  qual_edge_list worklist;
  qual_edge_list new_worklist;
  qual_edge_pred_map qepm;
  region scratch;
  qual_edge_hashset_scanner qess;
  qual_edge_list_scanner qels;
  qual_edge qe;
  qual_edge pred;
  qual_edge last = NULL;
  bool result = FALSE;

  start = ecr_qual(start);
  target = ecr_qual(target);

  if (eq_qual(start, target))
    return TRUE;

  scratch = newregion();
  qepm = make_qual_edge_pred_map(scratch, 16);
  new_worklist = new_qual_edge_list(scratch);
  
  /* Prime the worklist */
  qual_hashset_insert(scratch, &visited, start);
  scan_qual_edge_hashset(qe, qess, start->u.var.ub)
    {
      CHECK_SUMMARY_LENGTH(qe);
      if (qe->kind == ek_flow && qe->summary_time < summary_time)
	{
	  new_worklist = qual_edge_list_append_elt(scratch, new_worklist, qe);
	}
    }

  /* Do a bfs. looking for target, and using qepm to keep track of 
     all the paths we've found. */
  while (qual_edge_list_length(new_worklist) > 0)
    {
      worklist = new_worklist;
      new_worklist = new_qual_edge_list(scratch);

      qual_edge_list_scan(worklist, &qels);
      while (qual_edge_list_next(&qels, &pred))
	{
	  CHECK_SUMMARY_LENGTH(pred);

	  if (! qual_hashset_member(visited, pred->q2))
	    {
	      qual q = ecr_qual(pred->q2);
	    
	      if (eq_qual(q, target))
		{
		  last = pred;
		  goto DONE;
		}

	      qual_hashset_insert(scratch, &visited, q);

	      if (!global_qual(q))
		{
		  scan_qual_edge_hashset(qe, qess, q->u.var.ub)
		    {
		      CHECK_SUMMARY_LENGTH(qe);
		      if (qe->kind == ek_flow && ! qual_hashset_member(visited, qe->q2) &&
			  qe->summary_time < summary_time)
			{
			  qual_edge_pred_map_insert(qepm, qe, pred);
			  new_worklist = qual_edge_list_append_elt(scratch, new_worklist, qe);
			}
		    }
		}
	    }
	}
    }

 DONE:
  
  /* If we found target, reconstruct the path */
  if (last)
    {
      path = qual_edge_list_cons(r, path, last);
      while (qual_edge_pred_map_lookup(qepm, last, &last))
	path = qual_edge_list_cons(r, path, last);
      result = TRUE;
    }

  deleteregion(scratch);
  return result;
}

/* Given a summary edge, rediscover the path it summarizes.  If any of
   the edges in the summarized path are also summary edges, they will
   *not* be replaced by the path they summarize. */
static qual_edge_list find_summary_path (region r, qual_edge e)
{
  qual_edge_list qel = NULL;
  qual_edge_list tmp;
  qual_hashset visited;
  region scratch;
  qual q1;
  qual q2;
  qual_edge_hashset_scanner qess1;
  qual_edge_hashset_scanner qess2;
  qual_edge qe1;
  qual_edge qe2;

  assert (e->summary_time);
  CHECK_SUMMARY_LENGTH(e);
  q1 = ecr_qual (e->q1);
  q2 = ecr_qual (e->q2);

  scratch = newregion();
  tmp = new_qual_edge_list (scratch);
  visited = empty_qual_hashset(scratch);

  if (global_qual(q1))
    if (global_qual(q2))
      if (find_flow_path(scratch, q1, q2, e->summary_time, visited, tmp))
	{
	  qel = qual_edge_list_copy(r, tmp);
	  goto DONE;
	}

  if (global_qual(q1))
    scan_qual_edge_hashset(qe2,qess2,q2->u.var.lb)
    {
      CHECK_SUMMARY_LENGTH(qe2);
      if (qe2->kind == ek_close)
	if (find_flow_path(scratch, q1, ecr_qual(qe2->q1), e->summary_time, 
			   visited, tmp))
	  {
	    qel = qual_edge_list_copy(r, tmp);
	    qel = qual_edge_list_append_elt (r, qel, qe2);
	    goto DONE;
	  }
    }

  if (global_qual(q2))
    scan_qual_edge_hashset(qe1,qess1,q1->u.var.ub)
    {
      CHECK_SUMMARY_LENGTH(qe2);
      if (qe1->kind == ek_open)
	if (find_flow_path(scratch, ecr_qual(qe1->q2), q2, e->summary_time, 
			   visited, tmp))
	  {
	    qel = qual_edge_list_copy(r, tmp);
	    qel = qual_edge_list_cons (r, qel, qe1);
	    goto DONE;
	  }
    }

  scan_qual_edge_hashset(qe1,qess1,q1->u.var.ub)
    {
      CHECK_SUMMARY_LENGTH(qe1);

      if (qe1->kind == ek_open)
	scan_qual_edge_hashset(qe2,qess2,q2->u.var.lb)
	{
	  CHECK_SUMMARY_LENGTH(qe2);

	  if (qe2->kind == ek_close)
	    if (find_flow_path(scratch, ecr_qual(qe1->q2), ecr_qual(qe2->q1), 
			       e->summary_time, visited, tmp))
	      {
		qel = qual_edge_list_copy(r, tmp);
		qel = qual_edge_list_cons (r, qel, qe1);
		qel = qual_edge_list_append_elt (r, qel, qe2);
		goto DONE;
	      }
	}
    }

 DONE:
  deleteregion(scratch);
  return qel;
}

bool traversing_head;
bool bidi;
edge_traverse_fn current_f;
void* current_arg;

/* munge arguments and call f:
   - q1 is the qualifier we are traversing towards or away from
   - q2 is the next qualifier along the path
   - upper indicates whether q1 <= q2 or vice-versa
   - d indicates whether we are heading towards (d_fwd) q1, or away from it (d_bak)
*/
static void call_f(qual q1, qual q2, location loc, bool upper, polarity p, q_direction d)
{
  qual left;
  qual right;

  switch (d)
    {
    case d_fwd:
      left = q1;
      right = q2;
      break;
    case d_bak:
      left = q2;
      right = q1;
      break;
    default:
      fprintf (stderr, "unknown path direction %d.\n", d);
      abort ();
      break;
    }

  if (upper != (d == d_fwd))
    p = -p;

  current_f(left, right, bidi, loc, p, traversing_head, current_arg);
  traversing_head = FALSE;
}

static void summary_edge_app(qual_edge qe, bool upper, q_direction d);

struct traverse_error_path_edges_fn_arg
{
  bool upper;
  q_direction d;
};

static void traverse_error_path_edges_fn(qual_edge e, void *arg)
{
  struct traverse_error_path_edges_fn_arg *tepefa = (struct traverse_error_path_edges_fn_arg *)arg;
  qual q1 = ecr_qual(e->q1);
  qual q2 = ecr_qual(e->q2);
  assert(current_f);

  CHECK_SUMMARY_LENGTH(e);
  if (e->summary_time)
    summary_edge_app (e, tepefa->upper, tepefa->d);
  else if (tepefa->upper)
    call_f(q1, q2, e->loc, tepefa->upper, 
	   qedge_kind_to_polarity(e->kind), tepefa->d);
  else
    call_f(q2, q1, e->loc, tepefa->upper, 
	   qedge_kind_to_polarity(e->kind), tepefa->d);
}

static void summary_edge_app(qual_edge qe, bool upper, q_direction d)
{
  region scratch;
  qual_edge_list qel;
  struct traverse_error_path_edges_fn_arg tepefa = { upper, d };

  CHECK_SUMMARY_LENGTH(qe);

  scratch = newregion();
  qel = find_summary_path(scratch, qe);
  assert (qel);
  
  if (upper == (d == d_fwd))
    qual_edge_list_app(qel, traverse_error_path_edges_fn, &tepefa);
  else
    qual_edge_list_rev_app(qel, traverse_error_path_edges_fn, &tepefa);

  deleteregion(scratch);
}

/* Apply current_f to every edge on a path from q to b.

   - expand_summaries indicates whether current_f should be applied to 
     each edge summarized by a polymorphic summary edge, or whether
     current_f should just be applied to the summary edge itself. 
   - same_level == TRUE means don't follow ek_open or ek_close paths
     (which can occur in PN paths)
   - upper indicates whether b is an upper or lower bound of q.
   - if d == d_fwd, the path is traversed starting at q, otherwise starting at b.

   Notes:
   - consecutive calls current_f(left1, right1,...) and current_f(left2, right2,...)
     are guaranteed to have right1 == left2.
*/
static void _traverse_reason_path_edges(qual q, qual b, 
					bool expand_summaries, bool same_level,
					bool upper, q_direction d)
{
  qual_bounds qbounds;
  qual_reason qr;
  bool new_upper;
  
  q = ecr_qual(q);

  qbounds = q->u.var.qbounds;

  /* Look up the first step along the path. */
  if (upper)
    qual_reason_map_lookup(qbounds->ubr, b, &qr);
  else
    qual_reason_map_lookup(qbounds->lbr, b, &qr);

  /* The base case: we've reached b */
  if (qr->kind == rk_location)
    {
      call_f(q, b, qr->u.loc, upper, p_non, d);
      return;
    }

  if (qr->u.e.qe->kind != ek_flow && same_level)
    return;

  CHECK_SUMMARY_LENGTH(qr->u.e.qe);
  /* Determine if this edge leads to or from q, and remember this fact
     in new_upper. */
  new_upper = eq_qual(q, qr->u.e.qe->q1);

  /* If we want to head away from q */
  if (d == d_fwd)
    {
      if (qr->u.e.qe->summary_time && expand_summaries)
	summary_edge_app (qr->u.e.qe, new_upper, d);
      else
	call_f(q, qual_edge_other_end(qr->u.e.qe,q), qr->u.e.qe->loc, 
	       new_upper, qedge_kind_to_polarity(qr->u.e.qe->kind), d);
    }

  _traverse_reason_path_edges(qual_edge_other_end(qr->u.e.qe, q), b, 
			      expand_summaries, same_level, new_upper, d);

  /* If we want to head towards q */
  if (d == d_bak)
    {
      if (qr->u.e.qe->summary_time && expand_summaries)
	summary_edge_app (qr->u.e.qe, new_upper, d);
      else
	call_f(q, qual_edge_other_end(qr->u.e.qe, q), qr->u.e.qe->loc,
	       new_upper, qedge_kind_to_polarity(qr->u.e.qe->kind), d);
    }
}

static void _setup_traverse_reason_path_edges(qual bound,
					      edge_traverse_fn f, void *arg)
{
  current_f = f;
  current_arg = arg;
  traversing_head = TRUE;
  bidi = bound->u.elt.sign == sign_eq;
}

static void _traverse_error_path_edges(qual q, bool context_summary,
				       bool expand_summaries, bool nonerrors,
				       edge_traverse_fn f, void *arg)
{
  qual bound;
  qual_reason qr;
  qual_reason_map_scanner qrms;
  qual_error_list qel;
  qual_error_list redundant;
  qual_set printed_bounds;
  qual_error_list_scanner scan;
  qual_error next_error;
  region scratch;
  
  q = ecr_qual(q);
  assert(q->kind == qk_variable);

  scratch = newregion();
  
  qel = new_qual_error_list (scratch);
  redundant = new_qual_error_list (scratch);
  printed_bounds = empty_qual_set(scratch);
  _may_leq_qual (q, q, context_summary, &qel, scratch);
  
  qual_error_list_scan(qel,&scan);
  
  /* First, do the errors */
  while(qual_error_list_next(&scan,&next_error))
    {
      bool dup = FALSE;
      qual loffender = next_error->lbc;
      qual uoffender = next_error->ubc;

      /* Prevent errors involving sign_eq qualifiers from being
	 explored twice (once in each direction) */
      if (sign_qual(loffender) == sign_eq && sign_qual(uoffender) == sign_eq)
	{
	  qual_error_list_scanner rscan;
	  qual_error rerror;

	  qual_error_list_scan(redundant,&rscan);
	  while(qual_error_list_next(&rscan,&rerror))
	    if (eq_qual(rerror->lbc, uoffender) && eq_qual(rerror->ubc, loffender))
	      {
		dup = TRUE;
		break;
	      }
	}
      if (dup)
	continue;
      redundant = qual_error_list_cons(scratch, redundant, next_error);
      qual_set_insert(scratch, &printed_bounds, uoffender);
      qual_set_insert(scratch, &printed_bounds, loffender);
      	
      if (loffender)
	{
	  _setup_traverse_reason_path_edges(loffender, f, arg);
	  _traverse_reason_path_edges(q,loffender,expand_summaries,FALSE,FALSE, d_bak);
	}
      
      if (uoffender)
	{
	  _setup_traverse_reason_path_edges(loffender, f, arg);
	  traversing_head = FALSE;
	  _traverse_reason_path_edges(q,uoffender,expand_summaries,FALSE,TRUE, d_fwd);
	}
    }

  /* Now print the paths for any qual bounds that aren't involved in
     errors */
  if (nonerrors && q->u.var.qbounds)
    {
      scan_qual_reason_map(bound, qr, qrms, q->u.var.qbounds->lbr)
	if (!qual_set_member (printed_bounds, bound))
	  {
	    _setup_traverse_reason_path_edges(bound, f, arg);
	    _traverse_reason_path_edges(q,bound,expand_summaries,FALSE,FALSE,d_bak);
	    qual_set_insert(scratch, &printed_bounds, bound);
	  }
      
      scan_qual_reason_map(bound, qr, qrms, q->u.var.qbounds->ubr)
	if (!qual_set_member (printed_bounds, bound))
	  {
	    _setup_traverse_reason_path_edges(bound, f, arg);
	    _traverse_reason_path_edges(q,bound,expand_summaries,FALSE,TRUE,d_bak);
	    qual_set_insert(scratch, &printed_bounds, bound);
	  }
    }
  
  deleteregion (scratch);
}

void traverse_error_path_edges(qual q, bool expand_summaries, bool nonerrors,
			       edge_traverse_fn f, void *arg)
{
  _traverse_error_path_edges (q, FALSE, expand_summaries, nonerrors, f, arg);
}

void cs_traverse_error_path_edges(qual q, bool expand_summaries, bool nonerrors,
				  edge_traverse_fn f, void *arg)
{
  _traverse_error_path_edges (q, TRUE, expand_summaries, nonerrors, f, arg);
}

/**************************************************************************
 *                                                                        *
 * Graph file generation and other debugging output                       *
 *                                                                        *
 **************************************************************************/

static FILE* print_graph_file_target;

#if 0
static const char *qedge_kind_to_string(qedge_kind k)
{
  switch (k)
    {
    case ek_flow:
      return "M";
    case ek_open:
      return "(";
    case ek_close:
      return ")";
    }
  assert(0);
  return "ERROR";
}
#endif

#if 0
static const char *q_direction_to_string(q_direction d)
{
  switch (d)
    {
    case d_fwd:
      return "f";
    case d_bak:
      return "b";
    default:
      fprintf (stderr, "unknown path direction %d.\n", d);
      abort ();
    }
  assert (0);
  return "ERROR";
}
#endif

const char *polarity_to_string(polarity p)
{
  switch (p)
    {
    case p_neg:
      return "-";
    case p_pos:
      return "+";
    case p_non:
      return "T";
    case p_sub:
      return "S";
    default:
      fprintf (stderr, "unknown polarity %d.\n", p);
      abort ();
    }
  assert(0);
  return "ERROR";
}

static int print_graph_file(const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);
  return vfprintf(print_graph_file_target, fmt, args);
}

static void print_graph_name (FILE* f, const qual q)
{
  print_qual_raw(print_graph_file, q, &open_qgate);
  if (flag_ugly)
    print_graph_file ("(%p)", q);
  if (q->kind == qk_variable && q->u.var.global)
    print_graph_file ("[g]");
}

static void add_graph_node (FILE* f, const qual q)
{
  print_graph_file_target = f;
  fprintf(f, "\"");
  print_graph_name (f, q);
  fprintf(f, "\"\n");
}

static void add_graph_edge(FILE* f, const qual left, const qual right, 
		    const char* label, const bool dotted)
{
  print_graph_file_target = f;

  fprintf(f, "\"");
  print_graph_name(f, left);
  fprintf(f, "\"->\"");
  print_graph_name(f, right);
  fprintf(f, "\"");
  fprintf (f, "[");
  if (label)
    fprintf(f, "label=\"%s\",", label);
  fprintf(f, "style=%s", dotted ? "dotted" : "solid");
  fprintf (f, "]\n");
}

static char* location_label (location loc)
{
  static char buf[1024];
  snprintf (buf, sizeof(buf), "%s:%ld", loc->filename, loc->lineno);
  buf[sizeof(buf)-1] = '\0';
  return buf;
}

static char* edge_label(const qual_edge e)
{
  static char gate_buf[1024];
  static char buf[1024];

  CHECK_SUMMARY_LENGTH(e);
  qual_gate_to_string (&e->qgate, gate_buf, sizeof (gate_buf));
  snprintf (buf, 1000, "%d,%s,%s",
	    location_index(e->loc), 
	    polarity_to_string(qedge_kind_to_polarity(e->kind)),
	    gate_buf);
  buf[sizeof(buf)-1] = '\0';
  return buf;
}

static void dump_quals_graph_constant_bounds (FILE* f, qual q)
{
  if (q->u.var.qbounds)
    {
      qual_reason_map_scanner qrms;
      qual qc;
      qual_reason qr;
      
      scan_qual_reason_map (qc, qr, qrms, q->u.var.qbounds->lbr)
	{
	  if (qr->ci_bound)
	    {
	      if (qr->kind == rk_location)
		add_graph_edge (f, qc, q, location_label (qr->u.loc), FALSE);
	      else
		add_graph_edge (f, qc, q, "*", FALSE);
	    }
	}
      
      scan_qual_reason_map (qc, qr, qrms, q->u.var.qbounds->ubr)
	{
	  if (qr->ci_bound)
	    {
	      if (qr->kind == rk_location)
		add_graph_edge (f, q, qc, location_label (qr->u.loc), FALSE);
	      else
		add_graph_edge (f, q, qc, "*", FALSE);
	    }
	}
    } 
}

static void _dn_dfs (FILE* f, region scratch, 
	      qual root, int depth, qual_edge_set* printed_edges)
{
  qual_edge_hashset_scanner qess;
  qual_edge e;

  assert (root->kind == qk_variable);
      
  if (root->u.var.visited)
    return;
  root->u.var.visited = TRUE;

  if (root->link)
    {
      add_graph_edge (f, root, ecr_qual (root), "+", TRUE);
      _dn_dfs (f, scratch, ecr_qual (root), depth, printed_edges);
      return;
    }

  scan_qual_edge_hashset (e, qess, root->u.var.lb)
    {
      qual q = ecr_qual (e->q1);
      if (! q->link)
	{
	  if ((depth > 0 || q->u.var.visited) &&
	      ! qual_edge_set_member (cmp_qual_edge, *printed_edges, e))
	    {
	      add_graph_edge (f, q, root, edge_label (e), FALSE);
	      qual_edge_set_insert (scratch, printed_edges, e);
	    }

	  if (depth > 0)
	    _dn_dfs (f, scratch, q, depth-1, printed_edges);
	}
    }

  scan_qual_edge_hashset (e, qess, root->u.var.ub)
    {
      qual q = ecr_qual (e->q2);
      if (! q->link)
	{
	  if ((depth > 0 || q->u.var.visited) &&
	      ! qual_edge_set_member (cmp_qual_edge, *printed_edges, e))
	    {
	      add_graph_edge (f, root, q, edge_label (e), FALSE);
	      qual_edge_set_insert (scratch, printed_edges, e);
	    }

	  if (depth > 0)
	    _dn_dfs (f, scratch, q, depth-1, printed_edges);
	}
    }

  dump_quals_graph_constant_bounds (f, root);
}

static void _dump_neighborhood (FILE* f, qual_set vars, qual start, int size)
{
  qual_edge_set printed_edges;
  region scratch;

  reset_visited (vars);

  scratch = newregion();

  printed_edges = empty_qual_edge_set(scratch);

  _dn_dfs (f, scratch, start, size, &printed_edges);

  deleteregion (scratch);
}

static void dump_graph_init (FILE* f)
{
  fprintf(f, "digraph G {\n");
}

static void dump_graph_finish (FILE* f)
{
  fprintf (f, "}\n");
}

static void dump_neighborhood (FILE* f, qual_set vars, qual root, int depth)
{
  dump_graph_init (f);
  _dump_neighborhood (f, vars, root, depth);
  dump_graph_finish (f);
}

static void dump_quals_graph (FILE* f, qual_set vars)
{
  qual_set_scanner qss;
  qual q;
  qual_edge_hashset_scanner qess;
  qual_edge e;

  dump_graph_init (f);

  scan_qual_set (q, qss, vars)
    {
      if (q->kind == qk_link)
	continue;

      add_graph_node (f, q);

      scan_qual_edge_hashset (e, qess, q->u.var.ub)
	add_graph_edge (f, q, ecr_qual(e->q2), edge_label (e), FALSE);

      dump_quals_graph_constant_bounds (f, q);
    }


  dump_graph_finish (f);

}

#if 0
static void print_route(qual_edge_list el)
{
  qual_edge_list_scanner scan;
  qual_edge temp;

  qual_edge_list_scan(el,&scan);

  if(qual_edge_list_next(&scan,&temp))
    {
      print_qual_raw(printf,temp->q1);
      if (temp->kind == ek_flow)
	  printf(" --%s--> ",qedge_kind_to_string(temp->kind));
	else
	  printf(" --%s%d--> ",qedge_kind_to_string(temp->kind),temp->index);
      print_qual_raw(printf,temp->q2);
    }

  while(qual_edge_list_next(&scan,&temp))
    { 
      if (temp->kind == ek_flow)
	printf(" --%s--> ",qedge_kind_to_string(temp->kind));
      else
	printf(" --%s%d--> ",qedge_kind_to_string(temp->kind),temp->index);
      print_qual_raw(printf,temp->q2);
    }
  putchar('\n');
}
#endif

#ifdef DEBUG
static void dump_qual_set (qual_set s)
{
  qual_set_scanner qss;
  qual q;

  printf ("{ ");
  scan_qual_set (q, qss, s)
    {
      if (q->link)
	{
	  printf ("%s[%s]<0x%X>, ", q->u.var.name, ecr_qual(q)->u.var.name,
		  (int)q);
	}
      else
	{
	  printf ("%s<0x%X>, ", q->u.var.name, (int)q);
	}
    }
  printf ("}\n");
}
#endif

/**************************************************************************
 *                                                                        *
 * Constraint generation                                                  *
 *                                                                        *
 **************************************************************************/

#if 0
/* For determining the best reason a constant bounds a variable */
static int total_path_length (qual_edge qe, qual var, qual constant, bool upper)
{
  qual_reason qr;
  bool found_other_end;
  int tpl;
  
  tpl = 0;

  /* Add the length of this path */
  

  /* Add the length of the path from the other end to the constant */
  if (eq_qual (var, p->q1))
    var = p->q2;
  else
    var = p->q1;

  if (upper)
    found_other_end = qual_reason_map_lookup (var->u.var.qbounds->ubr, constant, &qr);
  else
    found_other_end = qual_reason_map_lookup (var->u.var.qbounds->lbr, constant, &qr);
    
  if (found_other_end)
    if (qr->kind == rk_path)
      tpl += qr->u.path.totalpathlength;

  return tpl;
}
#endif

static inline bool insert_X_bound (location loc, 
				   qual constant, qual var, 
				   qual_edge qe,
				   bool upper, bool ci_bound)
{
  bool result = FALSE;
  qual_bounds bounds;
  qual_reason qr;
  bool already_bounds;
  bool bound_upgrade;
  
  var = ecr_qual(var);
  assert(constant->kind == qk_constant && var->kind == qk_variable);
  assert(loc || qe);

  /* Alloc a new bounds structure if this is the first bound on this
     variable */
  if (!(bounds = var->u.var.qbounds))
    {
      bounds = ralloc(quals_region, struct Qual_bounds);
      bounds->lbr = make_qual_reason_map (quals_region, 2);
      bounds->ubr = make_qual_reason_map (quals_region, 2);
      var->u.var.qbounds = bounds;
    }

  var->u.var.interesting = TRUE;

  /* If this is the first time this constant is a bound on this
     variable, allocate a reason for this bound */
  if (upper)
    already_bounds = qual_reason_map_lookup(bounds->ubr, constant, &qr);
  else
    already_bounds = qual_reason_map_lookup(bounds->lbr, constant, &qr);
    
  if (!already_bounds)
    {
      qr = ralloc (quals_region, struct Qual_reason);
      qr->kind = rk_none;
      qr->ci_bound = FALSE;
      if (upper)
	{
	  qual_reason_map_insert (bounds->ubr, constant, qr);
	}
      else
	{
	  qual_reason_map_insert (bounds->lbr, constant, qr);
	}
      result = TRUE;
    }

  bound_upgrade = !qr->ci_bound && ci_bound;
  if (bound_upgrade)
    result = TRUE;
  qr->ci_bound |= ci_bound;

  /* Now store the "best" reason for this constant bound.  locations
     (i.e. original bounds) are better than derived bounds. Derived
     bounds with shorter paths are better than those with longer
     paths.  Any edge path is better than no reason. */
  if (loc && (qr->kind < rk_location || bound_upgrade))
    {
      qr->kind = rk_location;
      qr->u.loc = loc;
      result = TRUE;
    }
  else if (qe && (qr->kind < rk_edge || bound_upgrade))
    {
      qual q2;
      qual_reason qr2;

      qr->kind = rk_edge;
      qr->u.e.qe = qe;
      CHECK_SUMMARY_LENGTH(qe);

      q2 = ecr_qual(qual_edge_other_end(qe, var));
      assert(variable_qual(q2) && q2->u.var.qbounds);
      if (upper)
	insist(qual_reason_map_lookup(q2->u.var.qbounds->ubr, constant, &qr2));
      else
	insist(qual_reason_map_lookup(q2->u.var.qbounds->lbr, constant, &qr2));

      qr->u.e.pathlength = edge_length(qr->u.e.qe);
      if (qr2->kind == rk_edge)
	qr->u.e.pathlength += qr2->u.e.pathlength;
      
#ifdef DEBUG
      if (upper)
	printf ("IXB: %s <= %s: ", constant->u.elt.name, var->u.var.name);
      else
	printf ("IXB: %s <= %s: ", var->u.elt.name, constant->u.var.name);
      /*print_path (p);*/
      printf ("\n");
#endif	  
      
      result = TRUE;
    }

  /* If this is a new bound, trigger propagation through stores */
  if (!already_bounds && var->u.var.store)
    {
      if (upper)
	propagate_store_cell_backward(var->u.var.store, var->u.var.aloc);
      else
	propagate_store_cell_forward(var->u.var.store, var->u.var.aloc);
    }

  return result;
}

/*
  The constant q1 is inserted into q2's lower bounds, and q2 is marked
  interesting
 */
static inline bool insert_lower_bound(location loc, qual q1, qual q2, 
				      qual_edge qe,
				      bool ci_bound)
{
  if (insert_X_bound (loc, q1, q2, qe, FALSE, ci_bound))
    return INSERT_LOWER;
  else
    return INSERT_NOTHING;
}

/*
  The constant q2 is inserted into q1's upper bounds, and q1 is marked
  interesting. We also note that q1 could be involved in an inconsistent
  constraint, since it has an upper bound
 */
static inline bool insert_upper_bound(location loc, qual q1, qual q2, 
				      qual_edge qe,
				      bool ci_bound)
{
  if (insert_X_bound (loc, q2, q1, qe, TRUE, ci_bound))
    return INSERT_UPPER;
  else
    return INSERT_NOTHING;
 
}

/*
static inline bool insert_param(qual formal, qual_edge e)
{
  qual_edge_list actuals;
  assert(formal->kind == qk_variable);
  assert(e->s == ek_open);

  if (!qual_edge_list_map_lookup(qedge_param_map,formal,&actuals))
    {
      actuals = new_qual_edge_list(quals_region);
      qual_edge_list_map_insert(qedge_param_map,formal,actuals);
    }
  
  qual_edge_list_cons(e,actuals);
  return TRUE;
}
*/

/*
static inline bool insert_ret(qual ret, qual_edge e)
{
  qual_edge_list returns;
  assert(ret->kind == qk_variable);
  assert(e->s == ek_close);

  if (!qual_edge_list_map_lookup(qedge_return_map,ret,&returns))
    {
      returns = new_qual_edge_list(quals_region);
      qual_edge_list_map_insert(qedge_param_map,ret,returns);
    }

  qual_edge_list_cons(e,returns);
  return TRUE;
}
*/

/* called internally. this does not imply any transitive constraints,
   though qinstantiations may induce unifications due to well-formedness */
static void _mkinst_qual(location loc, qual_gate *qgate, qual left, qual right, 
			 polarity p, const char *error_message)
{
  qinstantiation inst;
  qual q1 = ecr_qual(left);
  qual q2 = ecr_qual(right);

  if (!flag_poly)
    {
      if (p != p_non)
	{
	  mkleq_qual (loc, qgate, left, right, error_message);
	}
      else
	{
	  mkleq_qual (loc, qgate, left, right, error_message);
	  mkleq_qual (loc, qgate, right, left, error_message);
	}
    }

  assert(q1->kind == qk_variable);
  assert(q2->kind == qk_variable);
  
#ifdef CONSTRAINT_DEBUG
  print_qual_raw(printf, left, &open_qgate);
  printf(" %p (=%d,%s ", left,location_index(loc),polarity_to_string(p));
  print_qual_raw(printf, right, &open_qgate);
  printf(" %p\n", right);
#endif


  inst = ralloc(resolve_region,struct QInstantiation);
  inst->loc = loc;
  inst->p = p;
  inst->q1 = q1;
  inst->q2 = q2;
  qinstantiation_list_cons(resolve_region, qinstantiations, inst);

  if (p == p_pos)
    {
      mkqual_edge(loc,ek_close,q1,q2, qgate, error_message, NULL);
      q1->u.var.ret = TRUE;
      q2->u.var.returned = TRUE;
      
       /* insert_ret(q1,e); */
    }
  else if (p == p_neg)
    {
      mkqual_edge(loc,ek_open,q1,q2, qgate, error_message, NULL);
      q1->u.var.passed = TRUE;
      q2->u.var.param = TRUE;
      
       /* insert_param(q1,e); */
    }
  else /* p_non */
    {
      mkqual_edge(loc,ek_flow,q1,q2, qgate, error_message, NULL);
      mkqual_edge(loc,ek_flow,q2,q1, qgate, error_message, NULL);
    }

  check_vars (all_vars);
}

/* left (= right with polarity p at the index described by loc. 
   return true if an error occurred */
void mkinst_qual(location loc, qual_gate *qgate, qual left, qual right, polarity p,
		 const char *error_message)
{
  _mkinst_qual(loc,qgate,left,right,p, NULL);
}

/* called internally, orig is true if the constraint is not derived
   transitively.  makes a new intraprocedural path edge between left
   and right, if left and right are both vars. if one of left or right
   is a constant, it is added to lbc or ubc respectively via
   insert_X_bound */
static int _mkleq_qual(location loc, qual left, qual right, 
		       bool orig, bool ci_bound,
		       qual_gate* qgate, const char* error_message,
		       qual_edge *new_edge, qual_edge causal_edge)
{
  bool result = INSERT_NOTHING;
  qual q1 = ecr_qual(left);
  qual q2 = ecr_qual(right);

  assert (!q1->link);
  assert (!q2->link);

#ifdef CONSTRAINT_DEBUG
 {
#define MAX_QGATE 50
   char qual_gate_name[MAX_QGATE];
   qual_gate_to_string(qgate, qual_gate_name, MAX_QGATE);
   print_qual_raw(printf, left, &open_qgate);
   printf(" %p <=(%s) ", left, qual_gate_name);
   print_qual_raw(printf, right, &open_qgate);
   printf(" %p\n", right);
 }
#endif

  if (new_edge)
    *new_edge = NULL;

  /* Two constants, should never happen */
  if (q1->kind == qk_constant && q2->kind == qk_constant)
    {
      fail("mkleq_qual called on two qualifier constants");
    }
  else if (q1->kind == qk_constant && q2->kind == qk_variable)
    {
      result |= insert_lower_bound(loc, q1,q2, causal_edge, ci_bound);      

      if (sign_qual(q1) == sign_eq || qual_set_empty (q1->u.elt.ubc))
	result |= insert_upper_bound(loc, q2,q1, causal_edge, ci_bound);      
    }
  else if (q1->kind == qk_variable && q2->kind == qk_constant)
    {
      result |= insert_upper_bound(loc, q1,q2, causal_edge, ci_bound);

      if (sign_qual(q2) == sign_eq || qual_set_empty (q2->u.elt.lbc))
	result |= insert_lower_bound(loc, q2,q1, causal_edge, ci_bound);      
    }
  else if (q1->kind == qk_variable && q2->kind == qk_variable)
    {
      mkqual_edge(loc,ek_flow,q1,q2, qgate, error_message, new_edge);

       /* check_vars (all_vars); */
       /* result = qual_edge_set_insert(quals_region,&q1->u.var.ub,e); */
       /* check_vars (all_vars); */
       /* result |= qual_edge_set_insert(quals_region,&q2->u.var.lb,e); */
       /* check_vars (all_vars); */
      result |= INSERT_EDGE;
    }
  
  return result;
}

/* left <= right, return true if an error occurred */
void mkleq_qual(location loc, qual_gate *qgate, qual left, qual right, 
		const char *error_message)
{
  _mkleq_qual(loc,left,right,TRUE,TRUE,qgate,error_message,NULL,NULL);
  check_vars (all_vars);
}

/* CHECK -- need to unify in order to make the instantiations well formed */

/* left == right, return true if an error occurred */
void mkeq_qual(location loc, qual_gate* qgate, qual left, qual right,
	       const char *error_message)
{
#if 1
  mkleq_qual(loc, qgate, left, right, error_message);
  mkleq_qual(loc, qgate, right, left, error_message);
#else
  check_vars (all_vars);
  unify_qual(loc,left,right);
  check_vars (all_vars);
#endif
}

static void bounds_union(qual_bounds new, qual_bounds old)
{
  qual tq;
  qual_reason_map_scanner qrms;
  qual_reason tqr1;
  qual_reason tqr2;
  
  scan_qual_reason_map (tq, tqr1, qrms, old->lbr)
    if (!qual_reason_map_lookup(new->lbr, tq, &tqr2))
      qual_reason_map_insert (new->lbr, tq, tqr1);
  scan_qual_reason_map (tq, tqr1, qrms, old->ubr)
    if (!qual_reason_map_lookup(new->ubr, tq, &tqr2))
      qual_reason_map_insert (new->ubr, tq, tqr1);
}

/* left == right, plus indistinguishable afterwards,
   return true if an error occurred */
void unify_qual(location loc, qual left, qual right, const char * error_message)
{
   /* qual_bounds link_bounds, ecr_bounds; */

  left = ecr_qual(left);
  right = ecr_qual(right);
  if (left == right) /* Short circuit to prevent loops in ecr_qual */
    return;

#ifdef CONSTRAINT_DEBUG
  print_qual_raw(printf, left, &open_qgate);
  printf(" %p == ", left);
  print_qual_raw(printf, right, &open_qgate);
  printf(" %p\n", right);
#endif

  mkeq_qual(loc, &open_qgate, left, right, error_message);

  if (left->kind == qk_variable && right->kind == qk_variable)
    {
      qual new_ecr, new_link;

      if (right->u.var.preferred || left->u.var.anonymous ||
	  (left->num_equiv <= right->num_equiv))
	{
	  new_ecr = right;
	  new_link = left;
	}
      else
	{
	  new_ecr = left;
	  new_link = right;
	}
      
      /* Always try and pick a preferred name.  As a fall-back, choose
	 the name that comes first alphabetically.  That way the name
	 of the ecr is independent of the order of the
	 unifications. */
      if (preferred_qual(left) && !preferred_qual(right))
	new_ecr->u.var.name = left->u.var.name;
      else if (!preferred_qual(left) && preferred_qual(right))
	new_ecr->u.var.name = right->u.var.name;
      else if (strcmp (left->u.var.name, right->u.var.name) > 0)
	new_ecr->u.var.name = right->u.var.name;
      else
	new_ecr->u.var.name = new_ecr->u.var.name;
	

      if (new_link->u.var.qbounds)
	{
	  if (new_ecr->u.var.qbounds)
	    {
	      bounds_union(new_ecr->u.var.qbounds,new_link->u.var.qbounds);
	    }
	  else
	    {
	      new_ecr->u.var.qbounds = new_link->u.var.qbounds;
	    }
	}
      
      new_ecr->num_equiv += new_link->num_equiv;
      new_ecr->u.var.interesting |= new_link->u.var.interesting ;
      new_ecr->u.var.preferred |= new_link->u.var.preferred;
      new_ecr->u.var.param |= new_link->u.var.param;
      new_ecr->u.var.passed |= new_link->u.var.passed;
      new_ecr->u.var.returned |= new_link->u.var.returned;
      new_ecr->u.var.ret |= new_link->u.var.ret;
      new_ecr->u.var.bak_seeded |= new_link->u.var.bak_seeded;
      new_ecr->u.var.fwd_seeded |= new_link->u.var.fwd_seeded;
      new_ecr->u.var.global |= new_link->u.var.global;
      new_ecr->u.var.anonymous &= new_link->u.var.anonymous;
      new_link->link = new_ecr;
      new_link->kind = qk_link;
      /* It's better to do these after the link is set up. */
      new_ecr->u.var.lb = qual_edge_hashset_union(new_ecr->u.var.lb,
					      new_link->u.var.lb);
      new_ecr->u.var.ub = qual_edge_hashset_union(new_ecr->u.var.ub,
					      new_link->u.var.ub);
    }
  check_vars (all_vars);
}

/* Copy the constraint described by e to the identical constraint
   between q1 and q2. */
void copy_constraint (qual_edge e, qual q1, qual q2)
{
  if (constant_qual (q1) || constant_qual (q2))
    mkleq_qual(e->loc, &e->qgate, q1, q2, NULL);
    
  if (e->summary_time == 0)
    mkqual_edge (e->loc, e->kind, q1, q2, &e->qgate, e->u.error_message, NULL);
  else
    mkqual_edge (e->loc, e->kind, q1, q2, &e->qgate, NULL, NULL);
}

/**************************************************************************
 *                                                                        *
 * Solve constraints                                                      *
 *                                                                        *
 **************************************************************************/

static bool _resolve_wellformed()
{ 
  region scratch = newregion();
  term_hash h = make_term_hash(scratch);
  qinstantiation_list_scanner scan;
  qinstantiation next_inst;
  bool result = FALSE;

  
  qinstantiation_list_scan(qinstantiations,&scan);
  while(qinstantiation_list_next(&scan,&next_inst))
    {
      qual q_prime;
      int index = location_index(next_inst->loc);
      qual q1 = ecr_qual(next_inst->q1);
      qual q2 = ecr_qual(next_inst->q2);
      stamp sig[2] = {index,(stamp)q1};

      if ( (q_prime = ecr_qual(term_hash_find(h,sig,2))) != NULL)
	{
	  if (!eq_qual(q_prime,q2))
	    {
	      unify_qual(next_inst->loc,q2,q_prime, NULL);
	      result = TRUE;
	    }
	 
	}
      else
	term_hash_insert(h,(term)q2,sig,2);
    }

  term_hash_delete (h);
  deleteregion(scratch);
  return result;
}

/*
  Iterate to a fixpoint to guarantee well-formedness
 */
static void resolve_wellformed()
{
  bool changed = TRUE;
  int iterations = 0;
  while(changed)
    {
#ifdef DEBUG
      printf("Iteration : %d in resolve_wellformed\n",iterations);
#endif
      changed = _resolve_wellformed();
      iterations++;
    }
}

static bool is_bound_compatible (qual_edge qe, qual_reason r, qual c, q_direction d)
{
  return ! nonprop_qual (c) && qual_gate_passes_qual (&qe->qgate, c, d);
}

static bool new_summary (location loc, qual q1, qual q2, 
			 qual_edge elb, qual_edge eub,
			 qual_gate *path_qgate,
			 int summary_length)
{
  qual_edge se;
  bool result;
  qual_gate qgate = *path_qgate;

  if (elb && ! compose_qual_gates (&elb->qgate, &qgate, &qgate))
    return FALSE;
  if (eub && ! compose_qual_gates (&qgate, &eub->qgate, &qgate))
    return FALSE;

  q1 = ecr_qual (q1);
  q2 = ecr_qual (q2);
  
  result = mkqual_edge (loc, ek_flow, q1, q2, &qgate, NULL, &se);
  if (result)
    {
      if (se->summary_time == 0)
	{
	  se->summary_time = current_summary_time++;
	  se->u.summary_length = summary_length;
	}
      CHECK_SUMMARY_LENGTH(se);
#ifdef DEBUG
 {
   static int num_sums = 0;
   printf ("SUMMARY: %s --> %s\n",
	   name_qual (q1),
	   name_qual(q2));
   
   num_sums++;
   if (num_sums % 100 == 0)
     printf ("num_sums: %d\n", num_sums);
 }
#endif
    }
  CHECK_SUMMARY_LENGTH(se);
  return result;
}

static bool new_summaries (qual q1, qual q2, qual_gate* qgate, int summary_length)
{
  qual_edge_hashset_scanner lbscan,ubscan;
  qual_edge elb, eub;
  bool result = FALSE;

#ifdef DEBUG
  /*
  printf ("SEEKING SUMMARIES: %s -(%s;%s->%s)-> %s\n",
	  name_qual (q1),
	  q_direction_to_string (tf->q1 ? tf->d : d_fwd),
	  tf->q1 ? name_qual(tf->q1) : "*",
	  tf->q1 ? name_qual(tf->q2) : "*",
	  name_qual(q2));
  */
#endif

  if (!global_qual(q1) && !global_qual(q2))
    {
      scan_qual_edge_hashset(elb,lbscan,q1->u.var.lb)
	if (elb->kind == ek_open)
	  scan_qual_edge_hashset(eub,ubscan,q2->u.var.ub)
	    if (eub->kind == ek_close && eub->index == elb->index)
	      result |= new_summary (elb->loc, elb->q1, eub->q2, elb, eub, qgate, summary_length + 2);
    }

  if (global_qual(q1))
    scan_qual_edge_hashset(eub,ubscan,q2->u.var.ub)
      if (eub->kind == ek_close)
	result |= new_summary (q1->u.var.loc, q1, eub->q2, NULL, eub, qgate, summary_length + 1);

  if (global_qual(q2))
    scan_qual_edge_hashset(elb,lbscan,q1->u.var.lb)
      if (elb->kind == ek_open)
	result |= new_summary (elb->loc, elb->q1, q2, elb, NULL, qgate, summary_length + 1);

  /* if (global_qual(q1) && global_qual(q2))
     _fwd_new_summary (p, NULL, NULL, q1, q2); */

  return result;
}

static bool find_bak_summaries (qual q);

static bool _find_bak_summaries (region r, qual origin, qual q, 
				 qual_gate* qgate, qual_hashset visited,
				 int summary_length)
{
  qual_edge_hashset_scanner qess;
  qual_edge qe;
  bool retval = FALSE;
  bool repeat = TRUE;

  q = ecr_qual (q);

  if (qual_hashset_member (visited, q) || q->u.var.bak_summary_dead_end)
    return FALSE;
  qual_hashset_insert (r, &visited, q);

  if (param_qual (q))
    retval |= new_summaries (q, origin, qgate, summary_length);
  else
    q->u.var.bak_summary_dead_end = TRUE;

  if (global_qual(q))
    return retval;

  while (repeat)
    {
      repeat = FALSE;

      scan_qual_edge_hashset (qe, qess, q->u.var.lb)
	{
	  qual_gate new_qgate;

	  if (!compose_qual_gates (&qe->qgate, qgate, &new_qgate))
	    continue;	  
	  
	  if (qe->kind == ek_flow)
	    {
	      qual q1 = ecr_qual(qe->q1);
	      retval |= _find_bak_summaries (r, origin, qe->q1, 
					     &new_qgate, visited, 
					     summary_length + edge_length(qe));
	      q->u.var.bak_summary_dead_end &= q1->u.var.bak_summary_dead_end;
	    }
	  else if (qe->kind == ek_close)
	    repeat |= find_bak_summaries (qe->q1);
	}
    }

  return retval;
}

static bool find_bak_summaries (qual q)
{
  region scratch;
  qual_hashset visited;
  qual_gate qgate;
  bool result;

  q = ecr_qual (q);

  if (q->u.var.bak_seeded)
    return FALSE;
  q->u.var.bak_seeded = TRUE;

  scratch = newregion();
  visited = empty_qual_hashset (scratch);

  qgate.backward = TRUE;
  qgate.forward = TRUE;
  qual_gate_set_all (qgate.mask, TRUE);
  result = _find_bak_summaries (scratch, q, q, &qgate, visited, 0);

  deleteregion(scratch);

  return result;
}

static bool find_fwd_summaries (qual q);

static bool _find_fwd_summaries (region r, qual origin, qual q, 
				 qual_gate* qgate, qual_hashset visited,
				 int summary_length)
{
  qual_edge_hashset_scanner qess;
  qual_edge qe;
  bool retval = FALSE;
  bool repeat = TRUE;

  q = ecr_qual (q);

  if (qual_hashset_member (visited, q) || q->u.var.fwd_summary_dead_end)
    return FALSE;
  qual_hashset_insert (r, &visited, q);

  if (ret_qual (q))
    retval |= new_summaries (origin, q, qgate, summary_length);
  else
    q->u.var.fwd_summary_dead_end = TRUE;

  if (global_qual(q))
    return retval;

  while (repeat)
    {
      repeat = FALSE;

      scan_qual_edge_hashset (qe, qess, q->u.var.ub)
	{
	  qual_gate new_qgate;
	  
	  if (!compose_qual_gates (qgate, &qe->qgate, &new_qgate))
	    continue;
	  
	  if (qe->kind == ek_flow)
	    {
	      qual q2 = ecr_qual(qe->q2);
	      retval |= _find_fwd_summaries (r, origin, qe->q2, &new_qgate, 
					     visited, summary_length + edge_length(qe));
	      q->u.var.fwd_summary_dead_end &= q2->u.var.fwd_summary_dead_end;
	    }
	  else if (qe->kind == ek_open)
	    repeat |= find_fwd_summaries (qe->q2);
	}
    }

  return retval;
}

static bool find_fwd_summaries (qual q)
{
  region scratch;
  qual_hashset visited;
  qual_gate qgate;
  bool result;

  q = ecr_qual (q);

  if (q->u.var.fwd_seeded)
    return FALSE;
  q->u.var.fwd_seeded = TRUE;

  scratch = newregion();
  visited = empty_qual_hashset (scratch);

  qgate.backward = TRUE;
  qgate.forward = TRUE;
  qual_gate_set_all (qgate.mask, TRUE);
  result = _find_fwd_summaries (scratch, q, q, &qgate, visited, 0);

  deleteregion(scratch);

  return result;
}

static void cfl_flow (qual_set vars)
{
  region scratch;
  region newscratch;
  qual_hashset pf_worklist;
  qual_hashset newpf_worklist;
  qual_hashset_scanner qhss;
  qual_set_scanner qss;
  qual q;

  scratch = newregion();
  pf_worklist = empty_qual_hashset(scratch);
  scan_qual_set (q, qss, vars)
    if (q->u.var.qbounds)
      qual_hashset_insert (scratch, &pf_worklist, ecr_qual (q));

  while (qual_hashset_size(pf_worklist) > 0)
    {
      newscratch = newregion();
      newpf_worklist = empty_qual_hashset(newscratch);

      scan_qual_hashset (q, qhss, pf_worklist)
	{
	  qual_edge qe;
	  qual_edge_hashset_scanner qess;

	  if (returned_qual (q))
	    scan_qual_edge_hashset (qe, qess, q->u.var.lb)
	      if (qe->kind == ek_close)
		find_bak_summaries (qe->q1);

	  scan_qual_edge_hashset (qe, qess, q->u.var.lb)
	    {
	      qual_reason_map_scanner qrms;
	      qual_reason qr;
	      qual c;
	      
	      if (qe->kind == ek_close || flag_context_summary)
		continue;
	      
	      scan_qual_reason_map (c, qr, qrms, q->u.var.qbounds->ubr)
		if (is_bound_compatible(qe, qr, c, d_bak))
		  {
		    if (qe->kind == ek_flow || qe->kind == ek_open)
		      {
			int r; 
			r = _mkleq_qual (NULL, qe->q1, c, FALSE, qr->ci_bound, 
					 &qe->qgate, NULL,
					 NULL, qe);
			if (INSERTED_LOWER(r) || INSERTED_UPPER(r))
			  qual_hashset_insert (newscratch, &newpf_worklist, 
					       ecr_qual (qe->q1));
		      }
		    else if (flag_context_summary)
		      {
			int r; 
			/* demote to a context-summary bound */
			r = _mkleq_qual (NULL, qe->q1, c, FALSE, FALSE,
					 &qe->qgate, NULL,
					 NULL, qe);
			if (INSERTED_LOWER(r) || INSERTED_UPPER(r))
			  qual_hashset_insert (newscratch, &newpf_worklist, 
					       ecr_qual (qe->q1));
			
		      }
		  }
	    }

	  if (passed_qual (q))
	    scan_qual_edge_hashset (qe, qess, q->u.var.ub)
	      if (qe->kind == ek_open)
		find_fwd_summaries (qe->q2);
	    
	  scan_qual_edge_hashset (qe, qess, q->u.var.ub)
	    {
	      qual_reason_map_scanner qrms;
	      qual_reason qr;
	      qual c;
	      
	      if (qe->kind == ek_open || flag_context_summary)
		continue;

	      scan_qual_reason_map (c, qr, qrms, q->u.var.qbounds->lbr)
		if (is_bound_compatible(qe, qr, c, d_fwd))
		  {
		    if (qe->kind == ek_flow || qe->kind == ek_close)
		      {
			int r; 
			r = _mkleq_qual (NULL, c, qe->q2, FALSE, qr->ci_bound, 
					 &qe->qgate, NULL,
					 NULL, qe);
			if (INSERTED_LOWER(r) || INSERTED_UPPER(r))
			  qual_hashset_insert (newscratch, &newpf_worklist, 
					       ecr_qual(qe->q2));
		      }
		    else if (flag_context_summary)
		      {
			int r; 
			/* demote to a context-summary bound */
			r = _mkleq_qual (NULL, c, qe->q2, FALSE, FALSE, 
					 &qe->qgate, NULL,
					 NULL, qe);
			if (INSERTED_LOWER(r) || INSERTED_UPPER(r))
			  qual_hashset_insert (newscratch, &newpf_worklist, 
					       ecr_qual(qe->q2));

		      }
		  }
	    }
      
	}

      pf_worklist = newpf_worklist;
      deleteregion (scratch);
      scratch = newscratch;
    }
  
  deleteregion(scratch);
}


#if 0 

#define NBLOBS 10

static qual find_blob (qual_set vars)
{
  qual blobs[NBLOBS];
  int blob_scores[NBLOBS];
  qual_set_scanner scan;
  qual temp;
  int i;
  qual_edge_set_scanner pred_scanner;
  qual_edge pred;
	  
  memset (blobs, 0, sizeof (blobs));
  memset (blob_scores, 0, sizeof (blob_scores));

  scan_qual_set (temp, scan, vars)
    {
      if (temp->kind == qk_variable)
	{
	  int this_blob_score = 0;

	  scan_qual_edge_set (pred, pred_scanner, temp->u.var.lb)
	    {
	       /* if (pred->kind == ek_flow) */
	      this_blob_score++;
	    }

	  for (i = 0; i < NBLOBS; i++)
	    {
	      if ((blobs[i] == NULL) || (this_blob_score > blob_scores[i]) ||
		  ((this_blob_score == blob_scores[i]) && 
		   (strcmp (temp->u.var.name, blobs[i]->u.var.name) >= 0)))
		{
		  memmove (blobs + i + 1, blobs + i, 
			   sizeof (blobs[0]) * (NBLOBS - i - 1));
		  memmove (blob_scores + i + 1, blob_scores + i, 
			   sizeof (blob_scores[0]) * (NBLOBS - i - 1));
		  blobs[i] = temp;
		  blob_scores[i] = this_blob_score;
		  break;
		}
	    }
	}
    }

  for (i = 0; i < NBLOBS && blobs[i]; i++)
    {
      printf ("blob[%d] name: %s global: %d score: %d size: %d", 
	      i, 
	      blobs[i]->u.var.name,
	      blobs[i]->u.var.global,
	      blob_scores[i],
	      qual_edge_set_size (blobs[i]->u.var.lb));
      printf ("\n");
    }
  return blobs[0];
}
#endif

/* A recursive depth-first-search 
   helper function */
static void fiv_dfs (qual q, qual_set* conn_comp, qual_set* cc_cbounds)
{
  qual_edge_hashset_scanner qess;
  qual_edge e;
  qual_bounds qb = NULL;
  qual_reason_map_scanner qrms;
  qual c;
  qual_reason qr;

  /* If this is not a variable (should never happen) or has
     already been visited, stop. */
  q = ecr_qual (q);
  if (q->kind != qk_variable || q->u.var.visited)
    return;

  /* Add this variable to the current connected component */
  qual_set_insert_nocheck (quals_region, conn_comp, q);

  /* Now add all the consant bounds on this variable to the set of
     constant bounds on this connected component */
  qb = q->u.var.qbounds;
  if (qb)
    {
      scan_qual_reason_map (c, qr, qrms, qb->lbr)
	{
	  if (qr->ci_bound)
	    {
	      assert (c->kind == qk_constant);
	      qual_set_insert (quals_region, cc_cbounds, c);
	    }
	}
      scan_qual_reason_map (c, qr, qrms, qb->ubr)
	{
	  if (qr->ci_bound)
	    {
	      assert (c->kind == qk_constant);
	      qual_set_insert (quals_region, cc_cbounds, c);
	    }
	}
    }

  /* Mark this variable as visited */
  q->u.var.visited = TRUE;

  /* Visit its neighbors */
  scan_qual_edge_hashset (e, qess, q->u.var.lb)
    {
      fiv_dfs (e->q1, conn_comp, cc_cbounds);
    }
  scan_qual_edge_hashset (e, qess, q->u.var.ub)
    {
      fiv_dfs (e->q2, conn_comp, cc_cbounds);
    }
}

/* Return whether this set of constant qualifiers 
     contains two qualifiers from the same poset */
static int fiv_could_have_type_error (qual_set cbounds)
{
  po_set_scanner pss;
  po_info po;
  qual_set_scanner qss;
  qual c;
  int count;

  scan_po_set (po, pss, all_pos)
    {
      count = 0;
      scan_qual_set (c, qss, cbounds)
	{
	  assert (c->kind == qk_constant);
	  if (c->u.elt.po == po)
	    {
	      count++;
	      if (count >= 2)
		return TRUE;
	    }
	}
    }
  return FALSE;
}

static qual_set find_important_vars (qual_set vars)
{
  qual_set result;
  qual_set_scanner qss;
  qual start;
  qual_set conn_comp;
  qual_set cc_cbounds;

  result = empty_qual_set (quals_region);

  reset_visited (vars);

  /* printf ("all_vars: %d nodes\n", qual_set_size (all_vars)); */

  scan_qual_set (start, qss, vars)
    {
      if (start->kind == qk_variable &&
	  start->u.var.visited == FALSE)
	{
	  /* We've found a node in a new connected component, so
	  compute all nodes in this connected component and all the
	  constant bounds on variables in this c.c. */
	  conn_comp = empty_qual_set(quals_region);
	  cc_cbounds = empty_qual_set(quals_region);
	  fiv_dfs (start, &conn_comp, &cc_cbounds);

	  if (fiv_could_have_type_error (cc_cbounds))
	    {
	      /* printf ("coco: %d nodes\n", qual_set_size (conn_comp)); */
	      result = qual_set_union_nocheck (result, conn_comp);
	    }
	}
    }

  return result;
}

#if 0
/* Basically, do a monomorphic analysis and mark all the vars that are
   involved in a type error. */
/*static*/ void mark_important_vars (qual_set vars)
{
  region scratch;
  qual_set_map lbounds;
  qual_set_map ubounds;

  scratch = newregion();
  lbounds = make_qual_set_map (scratch, 16);
  ubounds = make_qual_set_map (scratch, 16);

  deleteregion (scratch);
}
#endif

/* In the discrete partial order, everything connected by flow edges
   can be unified. */
#if 0
static void unify_flow (qual_set vars)
{
  qual_set_scanner qss;
  qual q1;
  qual_edge_set_scanner qess;
  qual_edge e;
  qual q2;
  region scratch = newregion();

  scan_qual_set (q1, qss, vars)
    {
      if (q1->kind == qk_variable)
	{
	  qual_edge_set bounds;

	  bounds = qual_edge_set_copy (scratch, q1->u.var.lb);	  
	  scan_qual_edge_set (e, qess, bounds)
	    {
	      q2 = ecr_qual (e->q1);
	      assert (q2->kind == qk_variable);
	      if (e->kind == ek_flow && e->tf.q1 == NULL)
		unify_qual (e->loc, q1, q2, NULL);
	    }
	}
    }

  deleteregion(scratch);
}
#endif 

#if 0
static void remove_redundant_edges (qual_set vars)
{
  qual_set_scanner qss;
  qual q;


  scan_qual_set (q, qss, vars)
    {
      if (q->kind == qk_variable)
	{
	  qual_edge_set_remove_dups (cmp_qual_edge, q->u.var.lb);
	  qual_edge_set_remove_dups (cmp_qual_edge, q->u.var.ub);
	}
    }
}
#endif 

/**************************************************************************
 *                                                                        *
 * Error reporting                                                        *
 *                                                                        *
 **************************************************************************/

struct error_path_expander_arg {
  region r;
  qual_set s;
  qual source;
};

static void error_path_expander_fn(qual left, qual right, bool bidi, location loc,
				   polarity p, bool first, void *arg)
{
  struct error_path_expander_arg * epea = (struct error_path_expander_arg *)arg;
  if (variable_qual(left) && ! eq_qual(epea->source, left))
    qual_set_insert(epea->r, &epea->s, ecr_qual(left));
  if (variable_qual(right) && ! eq_qual(epea->source, right))
    qual_set_insert(epea->r, &epea->s, ecr_qual(right));
}

/* Return the list of qualifiers on the path from start to lbc */
static qual_set expand_error_path_down (region r, qual start, qual lbc, bool same_level)
{
  struct error_path_expander_arg epea = { r, empty_qual_set(r) };
  start = ecr_qual (start);
  _setup_traverse_reason_path_edges(lbc, error_path_expander_fn, &epea);
  _traverse_reason_path_edges(start, lbc, FALSE, same_level, FALSE, d_fwd);
  return epea.s;
}

static qual_set expand_error_path_up (region r, qual start, qual ubc, bool same_level)
{
  struct error_path_expander_arg epea = { r, empty_qual_set(r) };
  start = ecr_qual (start);
  _setup_traverse_reason_path_edges(ubc, error_path_expander_fn, &epea);
  _traverse_reason_path_edges(start, ubc, FALSE, same_level, TRUE, d_fwd);
  return epea.s;
}

/* Is this variable untypeable only because it has flow to/from some
   other untypeable variable? */
static bool is_derivative_error (qual e, qual lbc, qual ubc)
{
  qual_set lpath;
  qual_set upath;
  qual t;
  qual_set_scanner qss;
  region scratch;
  bool retval = FALSE;

  scratch = newregion();

  lpath = expand_error_path_down (scratch, e, lbc, FALSE);
  upath = expand_error_path_up   (scratch, e, ubc, FALSE);

#ifdef DEBUG
  printf ("is_derivative_error: %s\n",e ->u.var.name);
  dump_qual_set (lpath);
  dump_qual_set (upath);
#endif

  scan_qual_set (t, qss, lpath)
    {
      if (!t->u.var.anonymous && ! eq_qual (t, e) && qual_set_member (upath, t))
	{
	  retval = TRUE;
	  break;
	}
    }

  deleteregion (scratch);

  return retval;
}

static bool is_redundant_error(qual q, qual lbc, qual ubc)
{
  qual_set lpath;
  qual_set upath;
  qual t;
  qual_set_scanner qss;
  region scratch;
  bool retval = FALSE;

  scratch = newregion();

  lpath = expand_error_path_down (scratch, q, lbc, TRUE);
  upath = expand_error_path_up   (scratch, q, ubc, TRUE);

  scan_qual_set (t, qss, lpath)
    if (t->u.var.visited)
      {
	retval = TRUE;
	break;
      }

  scan_qual_set (t, qss, upath)
    if (t->u.var.visited)
      {
	retval = TRUE;
	break;
      }

  deleteregion (scratch);

  return retval;
}

static void update_redundant_errors(qual q, qual lbc, qual ubc)
{
  qual_set lpath;
  qual_set upath;
  qual t;
  qual_set_scanner qss;
  region scratch;

  scratch = newregion();

  lpath = expand_error_path_down (scratch, q, lbc, TRUE);
  upath = expand_error_path_up   (scratch, q, ubc, TRUE);

  scan_qual_set (t, qss, lpath)
    t->u.var.visited = TRUE;

  scan_qual_set (t, qss, upath)
    t->u.var.visited = TRUE;

  deleteregion (scratch);
}

#if 0
static void qual_error_complexity_traversal_fn(qual left, qual right, bool bidi, location loc,
					       polarity p, bool first, void *arg)
{
  int *complexity = (int *)arg;
  if (*complexity == 0 && first)
    *complexity += 5;
  (*complexity)++;
}
#endif

/* A Heuristic: The complexity of an error is the length of the paths
   to each bound (+ 3 if q is from a prelude file) */
static int qual_error_complexity(qual q, qual_error qe)
{
  qual_reason qr;
  int lcomplexity = 0;
  int ucomplexity = 0;
  int prelude_penalty = 0;

  prelude_penalty = file_get_isprelude(file_get_orig_name(q->u.var.loc->filename)) ? 3 : 0;

  insist(qual_reason_map_lookup(q->u.var.qbounds->lbr, qe->lbc, &qr));
  if (qr->kind == rk_edge)
    lcomplexity = qr->u.e.pathlength;

  insist(qual_reason_map_lookup(q->u.var.qbounds->ubr, qe->ubc, &qr));
  if (qr->kind == rk_edge)
    ucomplexity = qr->u.e.pathlength;

  return lcomplexity + ucomplexity + prelude_penalty;
}

/* Print an appropriate user error if one exists.
   Return TRUE if we printed one, false otherwise. */
static bool print_user_error_message(qual q, qual_error qe)
{
  qual_reason qr;
  qual_edge e;
  region scratch;
  bool result = FALSE;

  assert (variable_qual(q));
  scratch = newregion();

  assert(qual_reason_map_lookup(q->u.var.qbounds->lbr, qe->lbc, &qr));
  if (qr->kind == rk_edge)
    {
      e = qr->u.e.qe;
      while (e->summary_time != 0)
	{
	  qual_edge_list qel = find_summary_path(scratch, e);
	  e = qual_edge_list_get_tail(qel);
	}

      if (!e->summary_time && e->u.error_message)
	{
	  report_qual_error(e->loc, sev_err, q, e->u.error_message);
	  result = TRUE;
	}
    }

  if (!result)
    {
      assert(qual_reason_map_lookup(q->u.var.qbounds->ubr, qe->ubc, &qr));
      if (qr->kind == rk_edge)
	{
	  e = qr->u.e.qe;
	  while (e->summary_time != 0)
	    {
	      qual_edge_list qel = find_summary_path(scratch, e);
	      e = qual_edge_list_head(qel);
	    }

	  if (!e->summary_time && e->u.error_message)
	    {
	      report_qual_error(e->loc, sev_err, q, e->u.error_message);
	      result = TRUE;
	    }
	}
    }

  deleteregion(scratch);

  return result;
}

/* This finds the length of the longest filename mentioned in this error */
static void explain_error_find_max_filename_len(qual left, qual right, bool bidi,
						location loc, polarity p,
						bool first,void *arg)
{
  int *maxlen = (int *)arg;
  if (loc)
    {
      int l = strlen(loc->filename);
      *maxlen = l > *maxlen ? l : *maxlen;
    }
}

/* This function is cribbed from pam_print_edge */
static void explain_error_print_edge(qual left, qual right, bool bidi, location loc, polarity p,
				     bool first, void *arg)
{
  const char *s, *arrow = NULL;
  static int left_size;
  static qual last_right = NULL;
  int * maxfilenamelen = (int *)arg;
  qual to_print;

  arrow = bidi ? "==" : "<=";

  if (loc)
    fprintf (stderr, "%*s:%-6ld ", *maxfilenamelen, loc->filename, loc->lineno);

  if (first)
    {
      qual init_qual = left;
      to_print = right;
      
      s = unique_name_qual(init_qual);
      left_size = fprintf(stderr, "%s", name_qual(init_qual));
      if (flag_ugly)
	left_size += fprintf(stderr, " %s ", s);
      else
	left_size += fprintf(stderr, " ");
    }
  else
    {
      fprintf(stderr, "%*s", left_size, "");
      to_print = right;
    }
  
  fprintf(stderr, "%s ", arrow);

  s = unique_name_qual(to_print);
  fprintf(stderr, "%s", name_qual(to_print));
  if (flag_ugly)
    fprintf(stderr, " %s\n", s);
  else
    fprintf(stderr, "\n");

  last_right = right;
}



static void explain_error_print_qual(qual q, void *arg)
{
  fdprintf_fd = stderr;
  print_qual_raw(fdprintf, q, &open_qgate);
  fprintf(stderr, " ");
}

static void explain_error(qual q, qual_error qe)
{
  int maxfilenamelen = 0;

  /* Print the qualifier bounds */
  if (flag_ugly)
    fprintf(stderr, "%s %s: ", name_qual(q), unique_name_qual(q));
  else
    fprintf(stderr, "%s: ", name_qual(q));
  scan_qual_bounds_sorted(q, &open_qgate, explain_error_print_qual, NULL);
  fprintf(stderr, "\n");

  /* Print the path(s) to the bounds */
  traverse_error_path_edges(q,TRUE,FALSE,explain_error_find_max_filename_len,&maxfilenamelen);  
  traverse_error_path_edges(q,TRUE,FALSE,explain_error_print_edge,&maxfilenamelen);
  fprintf(stderr, "\n");
}

#define NUM_ERROR_PRIORITIES 60
static bool _report_errors (qual_set vars, bool print_anonymous)
{
  qual_list prioritized_errors[NUM_ERROR_PRIORITIES];
  qual_set_scanner qss;
  qual q;
  bool found_error = FALSE;
  bool printed_error = FALSE;
  region scratch;
  int i;

  scratch = newregion();

  for (i = 0; i < NUM_ERROR_PRIORITIES; i++)
    prioritized_errors[i] = new_qual_list(scratch);

  reset_visited(vars);

  /* Scan the errors, and sort them by how hard they are to explain. */
  scan_qual_set (q, qss, vars)
    {
      /* For each variable with upper or lower bounds */
      if (! q->link && q->kind == qk_variable && q->u.var.interesting)
	{
	  qual_error_list qel;
	  qel = new_qual_error_list (scratch);
	  
	  /* If the variable has inconsistent bounds */
	  if (! _may_leq_qual (q, q, FALSE, &qel, scratch))
	    {
	      qual_error_list_scanner qels;
	      qual_error qe;
	      int min_complexity = NUM_ERROR_PRIORITIES-1;

	      qual_error_list_scan (qel, &qels);
	      while (qual_error_list_next (&qels, &qe))
		{
		  int complexity = qual_error_complexity(q, qe);
		  if (complexity < min_complexity)
		    min_complexity = complexity;
		}

	      /* Sometimes the summary paths are super long. So cap it.
		 This may be a bug. */
	      if (min_complexity < 0)
		min_complexity = NUM_ERROR_PRIORITIES-1;

	      prioritized_errors[min_complexity] = 
		qual_list_cons(scratch, prioritized_errors[min_complexity], q);
	    }
	}
    }

  for (i = 0; i < NUM_ERROR_PRIORITIES; i++)
    {
      qual_list_scanner qls;
      qual q;
      qual_list_scan(prioritized_errors[i], &qls);
      while (qual_list_next(&qls, &q))
	{
	  qual_error_list qel;
	  qual_error_list_scanner qels;
	  qual_error qe;

	  qel = new_qual_error_list (scratch);
	  _may_leq_qual (q, q, FALSE, &qel, scratch);

	  found_error = TRUE;
	  if (!q->u.var.anonymous || print_anonymous)
	    {
	      /* If any of the errors on the variable are original to that
		 variable. */
	      qual_error_list_scan (qel, &qels);
	      while (qual_error_list_next (&qels, &qe))
		{
		  if (!is_derivative_error (q, qe->lbc, qe->ubc) &&
		      !is_redundant_error (q, qe->lbc, qe->ubc))
		    {
		      if (!print_user_error_message(q, qe))
			report_qual_error(q->u.var.loc, sev_err, q,
					  "WARNING: %s treated as %s and %s\n",
					  name_qual (q),
					  name_qual (qe->lbc), 
					  name_qual (qe->ubc));
		      if (flag_explain_errors)
			explain_error(q, qe);

		      update_redundant_errors(q, qe->lbc, qe->ubc);
		      printed_error = TRUE;
		      break;
		    }
		}
	    }
	}
    }

  deleteregion(scratch);

  return found_error && ! printed_error;
}

static void report_errors (qual_set vars)
{
  bool missed_errors;

  /* First try printing errors, but don't print errors on anonymous
     variables */
  missed_errors = _report_errors (vars, FALSE);

  /* If that screwed up (i.e. there were errors but we didn't print
     any), then try again, printing the errors on anonymous variables
     this time. This should happen very rarely, and suppressing the
     anonymous variable errors drastically reduces the extraneous
     errors reported, so this is definitely worth while. */
  if (missed_errors)
    missed_errors = _report_errors (vars, TRUE);

  assert (! missed_errors);

}

/**************************************************************************
 *                                                                        *
 * State transitions                                                      *
 *                                                                        *
 **************************************************************************/

void init_quals(void)
{
  assert(!quals_region);

  quals_region = newregion();
  resolve_region = newregion();

  next_qual = 0;
  all_quals = empty_qual_set(quals_region);
  all_vars = empty_qual_set(quals_region);
  all_pos = empty_po_set(quals_region);
  current_po = NULL;
  qinstantiations = new_qinstantiation_list(resolve_region);
  can_unify_flow = TRUE;

  qual_gate_init (&empty_qgate, TRUE, TRUE);

  /* Quals will be added to these gates as they are created */
  qual_gate_init (&open_qgate, TRUE, TRUE);
  qual_gate_init (&fi_qgate, TRUE, TRUE);
  qual_gate_init (&fs_qgate, TRUE, TRUE);
  qual_gate_init (&effect_qgate, TRUE, TRUE);
  qual_gate_init (&casts_preserve_qgate, TRUE, TRUE);

  /* The qgates are unidirectional because of sign_eq quals. */
  qual_gate_init (&ptrflow_down_pos_qgate, FALSE, TRUE);
  qual_gate_init (&ptrflow_up_pos_qgate, FALSE, TRUE);
  qual_gate_init (&ptrflow_down_neg_qgate, TRUE, FALSE);
  qual_gate_init (&ptrflow_up_neg_qgate, TRUE, FALSE);
  
  qual_gate_init (&fieldflow_down_pos_qgate, FALSE, TRUE);
  qual_gate_init (&fieldflow_up_pos_qgate, FALSE, TRUE);
  qual_gate_init (&fieldflow_down_neg_qgate, TRUE, FALSE);
  qual_gate_init (&fieldflow_up_neg_qgate, TRUE, FALSE);
  
  qual_gate_init (&fieldptrflow_down_pos_qgate, FALSE, TRUE);
  qual_gate_init (&fieldptrflow_up_pos_qgate, FALSE, TRUE);
  qual_gate_init (&fieldptrflow_down_neg_qgate, TRUE, FALSE);
  qual_gate_init (&fieldptrflow_up_neg_qgate, TRUE, FALSE);
  
  assert(num_hotspots >= 0);
  if (num_hotspots != 0)
    /* rarrayalloc initializes hotspots to 0 */
    {
      int i;

      hotspots = rarrayalloc(quals_region, num_hotspots, qual);
      for (i = 0; i < num_hotspots; i++)
	hotspots[i] = NULL;
    }
  else
    hotspots = NULL;

  state = state_init;
}       

void end_define_pos(void)
{
  assert(state == state_init);
  state = state_pos_defined;
}

void finish_quals(void)
{ 
  resolve_wellformed();
  
  /* Find nodes with high out-degree.
     Informational purposes only at present */
   /* find_blob(all_vars); */
  check_vars(all_vars);

  /* Find nodes with high out-degree.
     Informational purposes only at present */
   /* find_blob(all_vars); */
  check_vars(all_vars);

  /* Clean up redundant edges */
   /* remove_redundant_edges (all_vars); */
   /* check_vars(all_vars); */
   /* printf ("Remove redundant edges pass 1 complete\n"); */

  /* See how this affects the outdegree of the graph */
   /* find_blob(all_vars); */
  check_vars(all_vars);

  /* Cull un-intersting variables */
  if (flag_errors_only)
    important_vars = find_important_vars(all_vars);
  else
    important_vars = all_vars;
  check_vars(all_vars);

  if (flag_print_quals_graph)
    {
      char* filename = NULL;
      char* rootname = NULL;
      int depth;
      FILE* f = NULL;
      qual root = NULL;

      /* Decide on a filename */
      filename = getenv ("CQUAL_GRAPH_FILE");
      if (filename == NULL)
	filename = "quals.dot";

      /* See if we should only print out some nbd. */
      rootname = getenv ("CQUAL_GRAPH_ROOT");
      if (rootname)
	{
	  root = find_var (rootname);
	  if (root)
	    printf ("found root\n");
	  else
	    printf ("couldn't find root\n");
	}

      /* If we are only doing a nbd, figure out how big the nbd should
	 be. */
      if (rootname)
	{
	  if (getenv ("CQUAL_GRAPH_DEPTH"))
	    depth = atoi (getenv ("CQUAL_GRAPH_DEPTH"));
	  else
	    depth = 3;
	}
      else
	depth = INT_MAX;

      /* Do it. */
      f = fopen (filename, "w");
      if (f)
	{
	  if (root)
	    dump_neighborhood (f, all_vars, root, depth);
	  else
	    dump_quals_graph (f, important_vars);
	  fclose (f);
	}

    }

  /* See how this affects the outdegree of the graph */
   /* find_blob(all_vars); */
  check_vars(all_vars);

  /* Do the unify-across-flow-edges trick, if it's safe. */
#if 0
  if (can_unify_flow)
    {
      unify_flow (important_vars);
      qual_set_remove_dups (cmp_qual, important_vars);
      printf ("unified across flow edges: %d nodes left\n",
	      qual_set_size (important_vars));
    }
#endif

  /* Observe effects on blobbiness */
   /* find_blob(important_vars); */
  check_vars(all_vars);

  /* Remove redundant edges added during flow merging */
#if 0
  remove_redundant_edges (important_vars);
  check_vars(all_vars);
#endif
  
  /* Print new blob survey results */
   /* find_blob(important_vars); */
  check_vars(all_vars);
  /* printf ("begin preflow\n"); */
  cfl_flow (important_vars);
  /* printf ("end preflow\n"); */

  state = state_finish;

  report_errors (all_vars);
}

void dispose_quals(void)
{
  assert(quals_region);
  deleteregion(quals_region);
  quals_region = 0;
  state = state_start;
}    
