Fixed a bug in the member function which occurred
when an associative domain was full of actual data
and "deleted" slot sentinels.  A sentinel value
(NOPLACE, as in "no place that this could be") was
returned that was not checked against before using
it to access into a variable.  Simple fix once it
was found, that allows Jim to jettison his
associative domain/array workarounds.

git-svn-id: 88467cb1fb04b8a755be7e1ee1026be4190196ef@11436 3a8e244f-b0f2-452b-bcba-4c88e055c3ca

use List;

param _MIN_SIZE = 0;
param _MAX_SIZE = 26;

var _ps : _ddata(int) = _ddata(int, 27);
_ps.init();
_ps(_MIN_SIZE) = 23;
_ps(1) = 53;
_ps(2) = 97;
_ps(3) = 193;
_ps(4) = 389;
_ps(5) = 769;
_ps(6) = 1543;
_ps(7) = 3079;
_ps(8) = 6151;
_ps(9) = 12289;
_ps(10) = 24593;
_ps(11) = 49157;
_ps(12) = 98317;
_ps(13) = 196613;
_ps(14) = 393241;
_ps(15) = 786433;
_ps(16) = 1572869;
_ps(17) = 3145739;
_ps(18) = 6291469;
_ps(19) = 12582917;
_ps(20) = 25165843;
_ps(21) = 50331653;
_ps(22) = 100663319;
_ps(23) = 201326611;
_ps(24) = 402653189;
_ps(25) = 805306457;
_ps(_MAX_SIZE) = 1610612741;

param _EMPTY   = -1;
param _DELETED = -2;
param _NOPLACE = -3;

record _ind_data_t {
  type itype;
  var  data: itype;
  var  valid: bool;
}

class SingleLocaleAssociativeDomain: BaseDomain {
  type ind_type;
  var _arrs2: list(BaseArray);    // WAW: unfortunately redundant list
  var num_inds: int;
  var size: int = 0;
  var table: _ddata(int);
  var inds: _ddata(_ind_data_t(ind_type));
  var free_inds: _stack(int);

  def getIndices()
    return this; // stopgap measure given old implementation

  def setIndices(b: SingleLocaleAssociativeDomain) {
    this = oldAssignHelper(this, b);
  }

  def buildEmptyDomain()
    return SingleLocaleAssociativeDomain(ind_type=ind_type);

  // compiler-internal routines

  def initialize() {
    table  = _ddata(int, _ps(size));
    table.init( _EMPTY);
    inds =  _ddata(_ind_data_t(ind_type), _ps(size)/2);
    inds.init();
    free_inds = _stack(int);
  }

  def these() {
    var i = 0;
    var numFound = 0;
    while numFound < num_inds {
      if (inds(i).valid) {
        numFound += 1;
        yield inds(i).data;
      }
      i += 1;
    }
  }

  // internal routines

  def _half() {
    if (size > _MIN_SIZE) {
      var new_len = _ps(size-1);

      var new_table = _ddata(int, new_len);
      new_table.init(_EMPTY);

      var new_inds = _ddata(_ind_data_t(ind_type), new_len/2);
      new_inds.init();
      var old_map: _ddata(int);
      old_map = _ddata( int, num_inds);
      old_map.init();
      var inds_count = 0;
      var inds_pos = 0;
      while inds_count<num_inds {
        var ind = inds(inds_pos);
        if (ind.valid) {
          old_map(inds_count) = table( _map(ind.data));
          new_inds(inds_count) = ind;
          new_table( _map(ind.data, new_table, new_inds)) = inds_count;
          inds_count += 1;
        }
        inds_pos += 1;
      }

      for ia in _arrs2 {
        ia._resize( new_len/2, old_map);
      }

      table = new_table;
      inds = new_inds;
      size -= 1;
      free_inds.empty();
    }
  }

  def _double() {
    if (size < _MAX_SIZE) {
      var new_len = _ps(size+1);

      var new_table = _ddata( int, new_len);
      new_table.init( _EMPTY);
      var new_inds = _ddata( _ind_data_t(ind_type), new_len/2);
      new_inds.init();
      var old_map: _ddata(int);
      old_map = _ddata( int, inds.size);
      old_map.init();
      for inds_pos in 0..inds.size-1 {
        var ind = inds(inds_pos);
        if (ind.valid) {                 // should be dense
          old_map(inds_pos) = table( _map(ind.data));
          new_inds(inds_pos) = ind;
          new_table( _map( ind.data, new_table, new_inds)) = inds_pos;
        } else {
          halt( "doubling without being full");
        }
      }

      for ia in _arrs2 {
        ia._resize( new_len/2, old_map);
      }

      table = new_table;
      inds = new_inds;
      size += 1;
      free_inds.empty();
    } else {
      halt( "cannot double indefinite domain");
    }
  }

  def _map(ind: ind_type,
           itable: _ddata(int) = table,
           iinds: _ddata(_ind_data_t(ind_type)) = inds,
           deletedOK: bool = false): int {
    param debug = false;
    if debug then writeln("itable.size = ", itable.size);
    const base = _indefinite_hash(ind);
    for probe in 0..itable.size {
      var i = ((base + probe**2) % itable.size):int(32);
      if debug then writeln("Trying i = ", i);
      var table_i = itable(i);
      if debug then writeln("table_i = ", table_i);
      select (table_i) {
        when _EMPTY {
          if debug then writeln("EMPTY");
          return i;
        }
        when _DELETED {
          if debug then writeln("DELETED");
          if deletedOK then
            return i;
          if debug then writeln("...but not OK");
        }
        otherwise {
          if debug then writeln("iinds(table_i).data = ", iinds(table_i).data);
          if iinds(table_i).data==ind then
            return i;
        }
      }
    }
    return _NOPLACE;
  }

  def _get_index(ind : ind_type) {
    var i = _map(ind);
    if (table(i) < 0) then
      halt("array index out of bounds: ", ind);
    if (i >= 0) then
      return table(i);
    else
      halt( "index not found: ", ind);
    // will never get here, but to avoid compiler warnings:
    return 0;
  }

  def buildArray(type eltType) {
    var ia = SingleLocaleAssociativeArray(eltType, ind_type, dom=this);
    _arrs2.append(ia);
    return ia;
  }

  // public routines

  def add(ind : ind_type) {
    var hash = _map(ind, deletedOK = true);
    var ind_pos = table(hash);
    if (ind_pos==_EMPTY || ind_pos==_DELETED) {

      if (free_inds.length > 0) {      // recycle
        ind_pos = free_inds.pop();
      } else {
        if (num_inds == inds.size) {
          _double();
        }
        ind_pos = num_inds;
        hash = _map(ind, deletedOK = true);
      }
      table(hash) = ind_pos;
      inds(ind_pos).data = ind;
      inds(ind_pos).valid = true;
      num_inds += 1;
    } // else, already in domain
  }

  def remove( ind: ind_type) {
    var ind_pos = _map(ind);
    if (ind_pos >= 0) {
      var table_i = table(ind_pos);
      if (table_i >= 0) {
        inds(table_i).valid = false;
        num_inds -= 1;
        free_inds.push( table_i);
        table(ind_pos) = _DELETED;

        for ia in _arrs2 do
          ia.purge( table_i);
      } else {
        halt("index not in domain: ", ind);
      }
    }

    if (size > _MIN_SIZE) then
      if (num_inds < _ps(size-1)/2) {  // downsize?
        _half();
      }
  }

  def member( ind: ind_type) {
    const mapind = _map(ind);
    if (mapind >= 0) {
      var ind_pos = table(mapind);
      return (ind_pos >= 0);
    }
    return false;
  }

  def numIndices {
    return num_inds;
  }
}

def oldAssignHelper(a: SingleLocaleAssociativeDomain, b: SingleLocaleAssociativeDomain) {
  var indices: list(a.ind_type);
  var inds_count = 0;
  var inds_pos = 0;
  var total_inds = a.num_inds;
  while inds_count<total_inds {
    var ind = a.inds( inds_pos);
    if (ind.valid) {
      indices.append(ind.data);
      inds_count += 1;
    }
    inds_pos += 1;
  }

  for ind in indices {
    if (!b.member(ind)) {
      a.remove( ind);
    }
  }

  inds_count = 0;
  inds_pos = 0;
  total_inds = b.num_inds;
  while inds_count<total_inds {
    var ind = b.inds( inds_pos);
    if (ind.valid) {
      if (!a.member(ind.data)) {
        a.add( ind.data);
      }
      inds_count += 1;
    }
    inds_pos += 1;
  }
  return a;  // WAW: YAH, "a" instead of "b" because we want to match keys, not do a deep copy (e.g., _arrs2)
}

def SingleLocaleAssociativeDomain.writeThis(f: Writer) {
  f.write("[");
  var inds_count = 0;
  var inds_pos = 0;
  while inds_count<num_inds {
    var ind = inds( inds_pos);
    if (ind.valid) {
      f.write(ind.data);
      inds_count += 1;
      if (inds_count<num_inds) then f.write(", ");
    }
    inds_pos += 1;
  }
  f.write("]");
}

class SingleLocaleAssociativeArray: BaseArray {
  type eltType;
  type ind_type;
  var dom : SingleLocaleAssociativeDomain(ind_type=ind_type);
  var data : _ddata(eltType);

  def initialize() {
    data = _ddata( eltType, _ps(0)/2);
    data.init();
  }

  def purge( ind: int) {
    var d: eltType;
    data( ind) = d;
  }

  def _resize( length: int, old_map: _ddata(int)) {
    var new_data: _ddata(eltType) = _ddata( eltType, length);
    new_data.init();
    for i in 0..old_map.size-1 {
      new_data(i) = data(old_map(i));
    }
    data = new_data;
  }

  def this(ind : ind_type) var : eltType
    return data(dom._get_index(ind));

  def these() var {
    for i in 0..dom.num_inds-1 do
      yield data(i);
  }

  // WAW: hack due to domain-array interface. The array is not reallocated
  // here. Values of indices not in the new domain are reset. The
  // domain assignment will perform the reallocation of each array that
  // maps to it.
  def reallocate(d: _domain) {
    if (d._value.type == dom.type) {
      var td = d._value;
      var inds_count = 0;
      var inds_pos = 0;
      while (inds_pos < dom.num_inds) && (inds_count < dom.inds.size) {
	var ind = dom.inds(inds_count);
	if ind.valid {
	  var i = td._map( ind.data);
	  if (i < 0) then {
	    purge( dom.table(dom._map( ind.data)));
	  }
	  inds_pos += 1;
	}
	inds_count += 1;
      }
    }
  }

  def numElements {
    return dom.numIndices;
  }
}

def SingleLocaleAssociativeArray.writeThis(f: Writer) {
  var inds_count = 0;
  var inds_pos = 0;
  while inds_count<dom.num_inds {
    var ind = dom.inds(inds_pos);
    if (ind.valid) {
      f.write(data(inds_pos));
      inds_count += 1;
      if (inds_count < dom.num_inds) then f.write(" ");
    }
    inds_pos += 1;
  }
}

// Thomas Wang's 64b mix function from http://www.concentric.net/~Ttwang/tech/inthash.htm
def _gen_key(i: int(64)): int(64) {
  var key = i;
  key += ~(key << 32);
  key = key ^ (key >> 22);
  key += ~(key << 13);
  key = key ^ (key >> 8);
  key += (key << 3);
  key = key ^ (key >> 15);
  key += ~(key << 27);
  key = key ^ (key >> 31);
  return key & 0x7fffffffffffffff;  // YAH, make non-negative
}

pragma "inline"
def _indefinite_hash(b: bool): int(64) {
  if (b)
    return 17;
  else
    return 31;
}

pragma "inline"
def _indefinite_hash(i: int(64)): int(64) {
  return _gen_key(i);
}

pragma "inline"
def _indefinite_hash(u: uint(64)): int(64) {
  return _gen_key(u:int(64));
}

pragma "inline"
def _indefinite_hash(f: real): int(64) {
  return _gen_key(__primitive( "real2int", f));
}

pragma "inline"
def _indefinite_hash(c: complex): int(64) {
  return _gen_key(c.re:int ^ c.im:int);
}

// Use djb2 (Dan Bernstein in comp.lang.c.
pragma "inline"
def _indefinite_hash(x : string): int(64) {
  var hash: int(64) = 0;
  for c in 1..length(x) {
    hash = ((hash << 5) + hash) ^ ascii(x.substring(c));
  }
  return _gen_key(hash);
}

pragma "inline"
def _indefinite_hash(o: object): int(64) {
  return _gen_key(__primitive( "object2int", o));
}

use List;

param _MIN_SIZE = 0;
param _MAX_SIZE = 26;

var _ps : _ddata(int) = _ddata(int, 27);
_ps.init();
_ps(_MIN_SIZE) = 23;
_ps(1) = 53;
_ps(2) = 97;
_ps(3) = 193;
_ps(4) = 389;
_ps(5) = 769;
_ps(6) = 1543;
_ps(7) = 3079;
_ps(8) = 6151;
_ps(9) = 12289;
_ps(10) = 24593;
_ps(11) = 49157;
_ps(12) = 98317;
_ps(13) = 196613;
_ps(14) = 393241;
_ps(15) = 786433;
_ps(16) = 1572869;
_ps(17) = 3145739;
_ps(18) = 6291469;
_ps(19) = 12582917;
_ps(20) = 25165843;
_ps(21) = 50331653;
_ps(22) = 100663319;
_ps(23) = 201326611;
_ps(24) = 402653189;
_ps(25) = 805306457;
_ps(_MAX_SIZE) = 1610612741;

param _EMPTY   = -1;
param _DELETED = -2;
param _NOPLACE = -3;

record _ind_data_t {
  type itype;
  var  data: itype;
  var  valid: bool;
}

class SingleLocaleAssociativeDomain: BaseDomain {
  type ind_type;
  var _arrs2: list(BaseArray);    // WAW: unfortunately redundant list
  var num_inds: int;
  var size: int = 0;
  var table: _ddata(int);
  var inds: _ddata(_ind_data_t(ind_type));
  var free_inds: _stack(int);

  def getIndices()
    return this; // stopgap measure given old implementation

  def setIndices(b: SingleLocaleAssociativeDomain) {
    this = oldAssignHelper(this, b);
  }

  def buildEmptyDomain()
    return SingleLocaleAssociativeDomain(ind_type=ind_type);

  // compiler-internal routines

  def initialize() {
    table  = _ddata(int, _ps(size));
    table.init( _EMPTY);
    inds =  _ddata(_ind_data_t(ind_type), _ps(size)/2);
    inds.init();
    free_inds = _stack(int);
  }

  def these() {
    var i = 0;
    var numFound = 0;
    while numFound < num_inds {
      if (inds(i).valid) {
        numFound += 1;
        yield inds(i).data;
      }
      i += 1;
    }
  }

  // internal routines

  def _half() {
    if (size > _MIN_SIZE) {
      var new_len = _ps(size-1);

      var new_table = _ddata(int, new_len);
      new_table.init(_EMPTY);

      var new_inds = _ddata(_ind_data_t(ind_type), new_len/2);
      new_inds.init();
      var old_map: _ddata(int);
      old_map = _ddata( int, num_inds);
      old_map.init();
      var inds_count = 0;
      var inds_pos = 0;
      while inds_count<num_inds {
        var ind = inds(inds_pos);
        if (ind.valid) {
          old_map(inds_count) = table( _map(ind.data));
          new_inds(inds_count) = ind;
          new_table( _map(ind.data, new_table, new_inds)) = inds_count;
          inds_count += 1;
        }
        inds_pos += 1;
      }

      for ia in _arrs2 {
        ia._resize( new_len/2, old_map);
      }

      table = new_table;
      inds = new_inds;
      size -= 1;
      free_inds.empty();
    }
  }

  def _double() {
    if (size < _MAX_SIZE) {
      var new_len = _ps(size+1);

      var new_table = _ddata( int, new_len);
      new_table.init( _EMPTY);
      var new_inds = _ddata( _ind_data_t(ind_type), new_len/2);
      new_inds.init();
      var old_map: _ddata(int);
      old_map = _ddata( int, inds.size);
      old_map.init();
      for inds_pos in 0..inds.size-1 {
        var ind = inds(inds_pos);
        if (ind.valid) {                 // should be dense
          old_map(inds_pos) = table( _map(ind.data));
          new_inds(inds_pos) = ind;
          new_table( _map( ind.data, new_table, new_inds)) = inds_pos;
        } else {
          halt( "doubling without being full");
        }
      }

      for ia in _arrs2 {
        ia._resize( new_len/2, old_map);
      }

      table = new_table;
      inds = new_inds;
      size += 1;
      free_inds.empty();
    } else {
      halt( "cannot double indefinite domain");
    }
  }

  def _map(ind: ind_type,
           itable: _ddata(int) = table,
           iinds: _ddata(_ind_data_t(ind_type)) = inds,
           deletedOK: bool = false): int {
    param debug = false;
    if debug then writeln("itable.size = ", itable.size);
    const base = _indefinite_hash(ind);
    for probe in 0..itable.size {
      var i = ((base + probe**2) % itable.size):int(32);
      if debug then writeln("Trying i = ", i);
      var table_i = itable(i);
      if debug then writeln("table_i = ", table_i);
      select (table_i) {
        when _EMPTY {
          if debug then writeln("EMPTY");
          return i;
        }
        when _DELETED {
          if debug then writeln("DELETED");
          if deletedOK then
            return i;
          if debug then writeln("...but not OK");
        }
        otherwise {
          if debug then writeln("iinds(table_i).data = ", iinds(table_i).data);
          if iinds(table_i).data==ind then
            return i;
        }
      }
    }
    return _NOPLACE;
  }

  def _get_index(ind : ind_type) {
    var i = _map(ind);
    if (table(i) < 0) then
      halt("array index out of bounds: ", ind);
    if (i >= 0) then
      return table(i);
    else
      halt( "index not found: ", ind);
    // will never get here, but to avoid compiler warnings:
    return 0;
  }

  def buildArray(type eltType) {
    var ia = SingleLocaleAssociativeArray(eltType, ind_type, dom=this);
    _arrs2.append(ia);
    return ia;
  }

  // public routines

  def add(ind : ind_type) {
    var hash = _map(ind, deletedOK = true);
    var ind_pos = table(hash);
    if (ind_pos==_EMPTY || ind_pos==_DELETED) {

      if (free_inds.length > 0) {      // recycle
        ind_pos = free_inds.pop();
      } else {
        if (num_inds == inds.size) {
          _double();
        }
        ind_pos = num_inds;
        hash = _map(ind, deletedOK = true);
      }
      table(hash) = ind_pos;
      inds(ind_pos).data = ind;
      inds(ind_pos).valid = true;
      num_inds += 1;
    } // else, already in domain
  }

  def remove( ind: ind_type) {
    var ind_pos = _map(ind);
    if (ind_pos >= 0) {
      var table_i = table(ind_pos);
      if (table_i >= 0) {
        inds(table_i).valid = false;
        num_inds -= 1;
        free_inds.push( table_i);
        table(ind_pos) = _DELETED;

        for ia in _arrs2 do
          ia.purge( table_i);
      } else {
        halt("index not in domain: ", ind);
      }
    }

    if (size > _MIN_SIZE) then
      if (num_inds < _ps(size-1)/2) {  // downsize?
        _half();
      }
  }

  def member( ind: ind_type) {
    var ind_pos = table(_map(ind));
    return (ind_pos >= 0);
  }

  def numIndices {
    return num_inds;
  }
}

def oldAssignHelper(a: SingleLocaleAssociativeDomain, b: SingleLocaleAssociativeDomain) {
  var indices: list(a.ind_type);
  var inds_count = 0;
  var inds_pos = 0;
  var total_inds = a.num_inds;
  while inds_count<total_inds {
    var ind = a.inds( inds_pos);
    if (ind.valid) {
      indices.append(ind.data);
      inds_count += 1;
    }
    inds_pos += 1;
  }

  for ind in indices {
    if (!b.member(ind)) {
      a.remove( ind);
    }
  }

  inds_count = 0;
  inds_pos = 0;
  total_inds = b.num_inds;
  while inds_count<total_inds {
    var ind = b.inds( inds_pos);
    if (ind.valid) {
      if (!a.member(ind.data)) {
        a.add( ind.data);
      }
      inds_count += 1;
    }
    inds_pos += 1;
  }
  return a;  // WAW: YAH, "a" instead of "b" because we want to match keys, not do a deep copy (e.g., _arrs2)
}

def SingleLocaleAssociativeDomain.writeThis(f: Writer) {
  f.write("[");
  var inds_count = 0;
  var inds_pos = 0;
  while inds_count<num_inds {
    var ind = inds( inds_pos);
    if (ind.valid) {
      f.write(ind.data);
      inds_count += 1;
      if (inds_count<num_inds) then f.write(", ");
    }
    inds_pos += 1;
  }
  f.write("]");
}

class SingleLocaleAssociativeArray: BaseArray {
  type eltType;
  type ind_type;
  var dom : SingleLocaleAssociativeDomain(ind_type=ind_type);
  var data : _ddata(eltType);

  def initialize() {
    data = _ddata( eltType, _ps(0)/2);
    data.init();
  }

  def purge( ind: int) {
    var d: eltType;
    data( ind) = d;
  }

  def _resize( length: int, old_map: _ddata(int)) {
    var new_data: _ddata(eltType) = _ddata( eltType, length);
    new_data.init();
    for i in 0..old_map.size-1 {
      new_data(i) = data(old_map(i));
    }
    data = new_data;
  }

  def this(ind : ind_type) var : eltType
    return data(dom._get_index(ind));

  def these() var {
    for i in 0..dom.num_inds-1 do
      yield data(i);
  }

  // WAW: hack due to domain-array interface. The array is not reallocated
  // here. Values of indices not in the new domain are reset. The
  // domain assignment will perform the reallocation of each array that
  // maps to it.
  def reallocate(d: _domain) {
    if (d._value.type == dom.type) {
      var td = d._value;
      var inds_count = 0;
      var inds_pos = 0;
      while (inds_pos < dom.num_inds) && (inds_count < dom.inds.size) {
	var ind = dom.inds(inds_count);
	if ind.valid {
	  var i = td._map( ind.data);
	  if (i < 0) then {
	    purge( dom.table(dom._map( ind.data)));
	  }
	  inds_pos += 1;
	}
	inds_count += 1;
      }
    }
  }

  def numElements {
    return dom.numIndices;
  }
}

def SingleLocaleAssociativeArray.writeThis(f: Writer) {
  var inds_count = 0;
  var inds_pos = 0;
  while inds_count<dom.num_inds {
    var ind = dom.inds(inds_pos);
    if (ind.valid) {
      f.write(data(inds_pos));
      inds_count += 1;
      if (inds_count < dom.num_inds) then f.write(" ");
    }
    inds_pos += 1;
  }
}

// Thomas Wang's 64b mix function from http://www.concentric.net/~Ttwang/tech/inthash.htm
def _gen_key(i: int(64)): int(64) {
  var key = i;
  key += ~(key << 32);
  key = key ^ (key >> 22);
  key += ~(key << 13);
  key = key ^ (key >> 8);
  key += (key << 3);
  key = key ^ (key >> 15);
  key += ~(key << 27);
  key = key ^ (key >> 31);
  return key & 0x7fffffffffffffff;  // YAH, make non-negative
}

pragma "inline"
def _indefinite_hash(b: bool): int(64) {
  if (b)
    return 17;
  else
    return 31;
}

pragma "inline"
def _indefinite_hash(i: int(64)): int(64) {
  return _gen_key(i);
}

pragma "inline"
def _indefinite_hash(u: uint(64)): int(64) {
  return _gen_key(u:int(64));
}

pragma "inline"
def _indefinite_hash(f: real): int(64) {
  return _gen_key(__primitive( "real2int", f));
}

pragma "inline"
def _indefinite_hash(c: complex): int(64) {
  return _gen_key(c.re:int ^ c.im:int);
}

// Use djb2 (Dan Bernstein in comp.lang.c.
pragma "inline"
def _indefinite_hash(x : string): int(64) {
  var hash: int(64) = 0;
  for c in 1..length(x) {
    hash = ((hash << 5) + hash) ^ ascii(x.substring(c));
  }
  return _gen_key(hash);
}

pragma "inline"
def _indefinite_hash(o: object): int(64) {
  return _gen_key(__primitive( "object2int", o));
}


Changed associative domain/array dsiSorted iterators to iterate over a domain
and index into an array instead of iterating over the array directly. This
avoids a nil dereference bug when some optimizations are disabled.

git-svn-id: 88467cb1fb04b8a755be7e1ee1026be4190196ef@17272 3a8e244f-b0f2-452b-bcba-4c88e055c3ca

use Sort /* only QuickSort */;

// TODO: make the domain parameterized by this?
// TODO: make int(64) the default index type here and in arithemtic domains
type chpl_table_index_type = int(32);

/* These declarations could/should both be nested within
   DefaultAssociativeDom? */
enum chpl__hash_status { empty, full, deleted };

record chpl_TableEntry {
  type idxType;
  var status: chpl__hash_status = chpl__hash_status.empty;
  var idx: idxType;
}

def chpl__primes return (23, 53, 97, 193, 389, 769, 1543,
                         3079, 6151, 12289, 24593, 49157, 98317, 196613,
                         393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843,
                         50331653, 100663319, 201326611, 402653189, 805306457, 1610612741);

class DefaultAssociativeDom: BaseAssociativeDom {
  type idxType;

  var dist: DefaultDist;

  // The guts of the associative domain
  var numEntries: chpl_table_index_type = 0;
  var tableSizeNum = 1;
  var tableSize = chpl__primes(tableSizeNum);
  var tableDom = [0..tableSize-1];
  var table: [tableDom] chpl_TableEntry(idxType);

  // TODO: An ugly [0..-1] domain appears several times in the code --
  //       replace with a named constant/param?
  var postponeResize = false;

  def linksDistribution() param return false;

  def DefaultAssociativeDom(type idxType, dist: DefaultDist) {
    this.dist = dist;
  }

  //
  // Standard Internal Domain Interface
  //
  def dsiBuildArray(type eltType) {
    return new DefaultAssociativeArr(eltType=eltType, idxType=idxType,
                                     dom=this);
  }

  def dsiSerialWrite(f: Writer) {
    var first = true;
    f.write("[");
    for idx in this {
      if first then
        first = false;
      else
        f.write(", ");
      f.write(idx);
    }
    f.write("]");
  }

  //
  // Standard user domain interface
  //

  def dsiNumIndices {
    return numEntries;
  }

  def dsiIndsIterSafeForRemoving() {
    postponeResize = true;
    for i in this.these() do
      yield i;
    postponeResize = false;
    if (numEntries*8 < tableSize && tableSizeNum > 1) {
      _resize(grow=false);
    }
  }

  def these() {
    if !_isEnumeratedType(idxType) {
      for slot in _fullSlots() {
        yield table(slot).idx;
      }
    } else {
      for val in chpl_enumerate(idxType) {
        var (match, slot) = _findFilledSlot(val);
        if match then
          yield table(slot).idx;
      }
    }
  }

  def these(param tag: iterator) where tag == iterator.leader {
    compilerWarning("parallel iteration over associative domain has been serialized (see note in $CHPL_HOME/STATUS)");
    yield 0..dsiNumIndices-1;
  }

  def these(param tag: iterator, follower) where tag == iterator.follower {
    for (slot,i) in (_fullSlots(),0..) {
      if follower.member(i) then
        yield table(slot).idx;
    }
  }

  //
  // Associative Domain Interface
  //
  def dsiClear() {
    for slot in tableDom {
      table(slot).status = chpl__hash_status.empty;
    }
    numEntries = 0;
  }

  def dsiMember(idx: idxType): bool {
    return _findFilledSlot(idx)(1);
  }

  def dsiAdd(idx: idxType): index(tableDom) {
    if ((numEntries+1)*2 > tableSize) {
      // TODO: should also make sure that we don't exceed the maximum table
      // size
      _resize(grow=true);
    }
    const (foundSlot, slotNum) = _findEmptySlot(idx);
    if (foundSlot) {
      table(slotNum).status = chpl__hash_status.full;
      table(slotNum).idx = idx;
      numEntries += 1;
    } else {
      if (slotNum < 0) {
        halt("couldn't add ", idx, " -- ", numEntries, " / ", tableSize, " taken");
        return -1;
      }
      // otherwise, re-adding an index that's already in there
    }
    return slotNum;
  }

  def dsiRemove(idx: idxType) {
    const (foundSlot, slotNum) = _findFilledSlot(idx);
    if (foundSlot) {
      for a in _arrs do
        a.clearEntry(idx);
      table(slotNum).status = chpl__hash_status.deleted;
      numEntries -= 1;
    } else {
      halt("index not in domain: ", idx);
    }
    if (numEntries*8 < tableSize && tableSizeNum > 1) {
      _resize(grow=false);
    }
  }

  def dsiSorted() {
    var tableCopy: [0..#numEntries] idxType;

    for (tmp, slot) in (tableCopy.domain, _fullSlots()) do
      tableCopy(tmp) = table(slot).idx;

    QuickSort(tableCopy);

    for ind in tableCopy do
      yield ind;
  }

  //
  // Internal interface (private)
  //
  def _resize(grow:bool) {
    if postponeResize then return;
    // back up the arrays
    _backupArrays();

    // copy the table (TODO: could use swap between two versions)
    var copyDom = tableDom;
    var copyTable: [copyDom] chpl_TableEntry(idxType) = table;

    // grow original table
    tableDom = [0..-1:chpl_table_index_type]; // non-preserving resize
    numEntries = 0; // reset, because the adds below will re-set this
    tableSizeNum += if grow then 1 else -1;
    tableSize = chpl__primes(tableSizeNum);
    tableDom = [0..tableSize-1];

    // insert old data into newly resized table
    for slot in _fullSlots(copyTable) {
      const newslot = dsiAdd(copyTable(slot).idx);
      _preserveArrayElements(oldslot=slot, newslot=newslot);
    }

    _removeArrayBackups();
  }

  def _findFilledSlot(idx: idxType, tab = table): (bool, index(tableDom)) {
    for slotNum in _lookForSlots(idx, tab.domain.high+1) {
      const slotStatus = tab(slotNum).status;
      if (slotStatus == chpl__hash_status.empty) {
        return (false, -1);
      } else if (slotStatus == chpl__hash_status.full) {
        if (tab(slotNum).idx == idx) {
          return (true, slotNum);
        }
      }
    }
    return (false, -1);
  }

  def _findEmptySlot(idx: idxType): (bool, index(tableDom)) {
    for slotNum in _lookForSlots(idx) {
      const slotStatus = table(slotNum).status;
      if (slotStatus == chpl__hash_status.empty ||
          slotStatus == chpl__hash_status.deleted) {
        return (true, slotNum);
      } else if (table(slotNum).idx == idx) {
        return (false, slotNum);
      }
    }
    return (false, -1);
  }

  def _lookForSlots(idx: idxType, numSlots = tableSize) {
    const baseSlot = chpl__defaultHashWrapper(idx);
    for probe in 0..numSlots/2 {
      yield (baseSlot + probe**2)%numSlots;
    }
  }

  def _fullSlots(tab = table) {
    for slot in tab.domain {
      if tab(slot).status == chpl__hash_status.full then
        yield slot;
    }
  }
}

class DefaultAssociativeArr: BaseArr {
  type eltType;
  type idxType;
  var dom : DefaultAssociativeDom(idxType);

  var data : [dom.tableDom] eltType;

  var tmpDom = [0..-1:chpl_table_index_type];
  var tmpTable: [tmpDom] eltType;

  //
  // Standard internal array interface
  //

  def dsiGetBaseDom() return dom;

  def clearEntry(idx: idxType) {
    const initval: eltType;
    dsiAccess(idx) = initval;
  }

  def dsiAccess(idx : idxType) var : eltType {
    const (found, slotNum) = dom._findFilledSlot(idx);
    if (found) then
      return data(slotNum);
    else {
      halt("array index out of bounds: ", idx);
      return data(0);
    }
  }

  def these() var {
    for slot in dom {
      yield dsiAccess(slot);
    }
  }

  def these(param tag: iterator) where tag == iterator.leader {
    compilerWarning("parallel iteration over associative array has been serialized (see note in $CHPL_HOME/STATUS)");
    for block in dom.these(tag=iterator.leader) do
      yield block;
  }

  def these(param tag: iterator, follower) var where tag == iterator.follower {
    for i in dom.these(tag=iterator.follower, follower) do
      yield this(i);
  }

  def dsiSerialWrite(f: Writer) {
    var first = true;
    for val in this {
      if (first) then
        first = false;
      else
        f.write(" ");
      f.write(val);
    }
  }

  //
  // Associative array interface
  //

  def dsiSorted() {
    var tableCopy: [0..dom.dsiNumIndices-1] eltType;
    for (copy, slot) in (tableCopy.domain, dom._fullSlots()) do
      tableCopy(copy) = data(slot);

    QuickSort(tableCopy);

    for elem in tableCopy do
      yield elem;
  }

  //
  // Internal associative array interface
  //

  def _backupArray() {
    tmpDom = dom.tableDom;
    tmpTable = data;
  }

  def _removeArrayBackup() {
    tmpDom = [0..-1:chpl_table_index_type];
  }

  def _preserveArrayElement(oldslot, newslot) {
    data(newslot) = tmpTable(oldslot);
  }
}

def chpl__defaultHashWrapper(x): chpl_table_index_type {
  const hash = chpl__defaultHash(x);
  return (hash & max(chpl_table_index_type)): chpl_table_index_type;
}

// Thomas Wang's 64b mix function from http://www.concentric.net/~Ttwang/tech/inthash.htm
def _gen_key(i: int(64)): int(64) {
  var key = i;
  key += ~(key << 32);
  key = key ^ (key >> 22);
  key += ~(key << 13);
  key = key ^ (key >> 8);
  key += (key << 3);
  key = key ^ (key >> 15);
  key += ~(key << 27);
  key = key ^ (key >> 31);
  return (key & max(int(64))): int(64);  // YAH, make non-negative
}

pragma "inline"
def chpl__defaultHash(b: bool): int(64) {
  if (b) then
    return 0;
  else
    return 1;
}

pragma "inline"
def chpl__defaultHash(i: int(64)): int(64) {
  return _gen_key(i);
}

pragma "inline"
def chpl__defaultHash(u: uint(64)): int(64) {
  return _gen_key(u:int(64));
}

pragma "inline"
def chpl__defaultHash(f: real): int(64) {
  return _gen_key(__primitive( "real2int", f));
}

pragma "inline"
def chpl__defaultHash(c: complex): int(64) {
  return _gen_key(__primitive("real2int", c.re) ^ __primitive("real2int", c.im));
}

pragma "inline"
def chpl__defaultHash(u: chpl_taskID_t): int(64) {
  return _gen_key(u:int(64));
}

// Use djb2 (Dan Bernstein in comp.lang.c.
pragma "inline"
def chpl__defaultHash(x : string): int(64) {
  var hash: int(64) = 0;
  for c in 1..length(x) {
    hash = ((hash << 5) + hash) ^ ascii(x.substring(c));
  }
  return _gen_key(hash);
}

pragma "inline"
def chpl__defaultHash(o: object): int(64) {
  return _gen_key(__primitive( "object2int", o));
}

use Sort /* only QuickSort */;

// TODO: make the domain parameterized by this?
// TODO: make int(64) the default index type here and in arithemtic domains
type chpl_table_index_type = int(32);

/* These declarations could/should both be nested within
   DefaultAssociativeDom? */
enum chpl__hash_status { empty, full, deleted };

record chpl_TableEntry {
  type idxType;
  var status: chpl__hash_status = chpl__hash_status.empty;
  var idx: idxType;
}

def chpl__primes return (23, 53, 97, 193, 389, 769, 1543,
                         3079, 6151, 12289, 24593, 49157, 98317, 196613,
                         393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843,
                         50331653, 100663319, 201326611, 402653189, 805306457, 1610612741);

class DefaultAssociativeDom: BaseAssociativeDom {
  type idxType;

  var dist: DefaultDist;

  // The guts of the associative domain
  var numEntries: chpl_table_index_type = 0;
  var tableSizeNum = 1;
  var tableSize = chpl__primes(tableSizeNum);
  var tableDom = [0..tableSize-1];
  var table: [tableDom] chpl_TableEntry(idxType);

  // TODO: An ugly [0..-1] domain appears several times in the code --
  //       replace with a named constant/param?
  var postponeResize = false;

  def linksDistribution() param return false;

  def DefaultAssociativeDom(type idxType, dist: DefaultDist) {
    this.dist = dist;
  }

  //
  // Standard Internal Domain Interface
  //
  def dsiBuildArray(type eltType) {
    return new DefaultAssociativeArr(eltType=eltType, idxType=idxType,
                                     dom=this);
  }

  def dsiSerialWrite(f: Writer) {
    var first = true;
    f.write("[");
    for idx in this {
      if first then
        first = false;
      else
        f.write(", ");
      f.write(idx);
    }
    f.write("]");
  }

  //
  // Standard user domain interface
  //

  def dsiNumIndices {
    return numEntries;
  }

  def dsiIndsIterSafeForRemoving() {
    postponeResize = true;
    for i in this.these() do
      yield i;
    postponeResize = false;
    if (numEntries*8 < tableSize && tableSizeNum > 1) {
      _resize(grow=false);
    }
  }

  def these() {
    if !_isEnumeratedType(idxType) {
      for slot in _fullSlots() {
        yield table(slot).idx;
      }
    } else {
      for val in chpl_enumerate(idxType) {
        var (match, slot) = _findFilledSlot(val);
        if match then
          yield table(slot).idx;
      }
    }
  }

  def these(param tag: iterator) where tag == iterator.leader {
    compilerWarning("parallel iteration over associative domain has been serialized (see note in $CHPL_HOME/STATUS)");
    yield 0..dsiNumIndices-1;
  }

  def these(param tag: iterator, follower) where tag == iterator.follower {
    for (slot,i) in (_fullSlots(),0..) {
      if follower.member(i) then
        yield table(slot).idx;
    }
  }

  //
  // Associative Domain Interface
  //
  def dsiClear() {
    for slot in tableDom {
      table(slot).status = chpl__hash_status.empty;
    }
    numEntries = 0;
  }

  def dsiMember(idx: idxType): bool {
    return _findFilledSlot(idx)(1);
  }

  def dsiAdd(idx: idxType): index(tableDom) {
    if ((numEntries+1)*2 > tableSize) {
      // TODO: should also make sure that we don't exceed the maximum table
      // size
      _resize(grow=true);
    }
    const (foundSlot, slotNum) = _findEmptySlot(idx);
    if (foundSlot) {
      table(slotNum).status = chpl__hash_status.full;
      table(slotNum).idx = idx;
      numEntries += 1;
    } else {
      if (slotNum < 0) {
        halt("couldn't add ", idx, " -- ", numEntries, " / ", tableSize, " taken");
        return -1;
      }
      // otherwise, re-adding an index that's already in there
    }
    return slotNum;
  }

  def dsiRemove(idx: idxType) {
    const (foundSlot, slotNum) = _findFilledSlot(idx);
    if (foundSlot) {
      for a in _arrs do
        a.clearEntry(idx);
      table(slotNum).status = chpl__hash_status.deleted;
      numEntries -= 1;
    } else {
      halt("index not in domain: ", idx);
    }
    if (numEntries*8 < tableSize && tableSizeNum > 1) {
      _resize(grow=false);
    }
  }

  def dsiSorted() {
    var tableCopy: [0..#numEntries] idxType;

    for (tmp, slot) in (tableCopy, _fullSlots()) do
      tmp = table(slot).idx;

    QuickSort(tableCopy);

    for ind in tableCopy do
      yield ind;
  }

  //
  // Internal interface (private)
  //
  def _resize(grow:bool) {
    if postponeResize then return;
    // back up the arrays
    _backupArrays();

    // copy the table (TODO: could use swap between two versions)
    var copyDom = tableDom;
    var copyTable: [copyDom] chpl_TableEntry(idxType) = table;

    // grow original table
    tableDom = [0..-1:chpl_table_index_type]; // non-preserving resize
    numEntries = 0; // reset, because the adds below will re-set this
    tableSizeNum += if grow then 1 else -1;
    tableSize = chpl__primes(tableSizeNum);
    tableDom = [0..tableSize-1];

    // insert old data into newly resized table
    for slot in _fullSlots(copyTable) {
      const newslot = dsiAdd(copyTable(slot).idx);
      _preserveArrayElements(oldslot=slot, newslot=newslot);
    }

    _removeArrayBackups();
  }

  def _findFilledSlot(idx: idxType, tab = table): (bool, index(tableDom)) {
    for slotNum in _lookForSlots(idx, tab.domain.high+1) {
      const slotStatus = tab(slotNum).status;
      if (slotStatus == chpl__hash_status.empty) {
        return (false, -1);
      } else if (slotStatus == chpl__hash_status.full) {
        if (tab(slotNum).idx == idx) {
          return (true, slotNum);
        }
      }
    }
    return (false, -1);
  }

  def _findEmptySlot(idx: idxType): (bool, index(tableDom)) {
    for slotNum in _lookForSlots(idx) {
      const slotStatus = table(slotNum).status;
      if (slotStatus == chpl__hash_status.empty ||
          slotStatus == chpl__hash_status.deleted) {
        return (true, slotNum);
      } else if (table(slotNum).idx == idx) {
        return (false, slotNum);
      }
    }
    return (false, -1);
  }

  def _lookForSlots(idx: idxType, numSlots = tableSize) {
    const baseSlot = chpl__defaultHashWrapper(idx);
    for probe in 0..numSlots/2 {
      yield (baseSlot + probe**2)%numSlots;
    }
  }

  def _fullSlots(tab = table) {
    for slot in tab.domain {
      if tab(slot).status == chpl__hash_status.full then
        yield slot;
    }
  }
}

class DefaultAssociativeArr: BaseArr {
  type eltType;
  type idxType;
  var dom : DefaultAssociativeDom(idxType);

  var data : [dom.tableDom] eltType;

  var tmpDom = [0..-1:chpl_table_index_type];
  var tmpTable: [tmpDom] eltType;

  //
  // Standard internal array interface
  //

  def dsiGetBaseDom() return dom;

  def clearEntry(idx: idxType) {
    const initval: eltType;
    dsiAccess(idx) = initval;
  }

  def dsiAccess(idx : idxType) var : eltType {
    const (found, slotNum) = dom._findFilledSlot(idx);
    if (found) then
      return data(slotNum);
    else {
      halt("array index out of bounds: ", idx);
      return data(0);
    }
  }

  def these() var {
    for slot in dom {
      yield dsiAccess(slot);
    }
  }

  def these(param tag: iterator) where tag == iterator.leader {
    compilerWarning("parallel iteration over associative array has been serialized (see note in $CHPL_HOME/STATUS)");
    for block in dom.these(tag=iterator.leader) do
      yield block;
  }

  def these(param tag: iterator, follower) var where tag == iterator.follower {
    for i in dom.these(tag=iterator.follower, follower) do
      yield this(i);
  }

  def dsiSerialWrite(f: Writer) {
    var first = true;
    for val in this {
      if (first) then
        first = false;
      else
        f.write(" ");
      f.write(val);
    }
  }

  //
  // Associative array interface
  //

  def dsiSorted() {
    var tableCopy: [0..dom.dsiNumIndices-1] eltType;
    for (copy, slot) in (tableCopy, dom._fullSlots()) do
      copy = data(slot);

    QuickSort(tableCopy);

    for elem in tableCopy do
      yield elem;
  }

  //
  // Internal associative array interface
  //

  def _backupArray() {
    tmpDom = dom.tableDom;
    tmpTable = data;
  }

  def _removeArrayBackup() {
    tmpDom = [0..-1:chpl_table_index_type];
  }

  def _preserveArrayElement(oldslot, newslot) {
    data(newslot) = tmpTable(oldslot);
  }
}

def chpl__defaultHashWrapper(x): chpl_table_index_type {
  const hash = chpl__defaultHash(x);
  return (hash & max(chpl_table_index_type)): chpl_table_index_type;
}

// Thomas Wang's 64b mix function from http://www.concentric.net/~Ttwang/tech/inthash.htm
def _gen_key(i: int(64)): int(64) {
  var key = i;
  key += ~(key << 32);
  key = key ^ (key >> 22);
  key += ~(key << 13);
  key = key ^ (key >> 8);
  key += (key << 3);
  key = key ^ (key >> 15);
  key += ~(key << 27);
  key = key ^ (key >> 31);
  return (key & max(int(64))): int(64);  // YAH, make non-negative
}

pragma "inline"
def chpl__defaultHash(b: bool): int(64) {
  if (b) then
    return 0;
  else
    return 1;
}

pragma "inline"
def chpl__defaultHash(i: int(64)): int(64) {
  return _gen_key(i);
}

pragma "inline"
def chpl__defaultHash(u: uint(64)): int(64) {
  return _gen_key(u:int(64));
}

pragma "inline"
def chpl__defaultHash(f: real): int(64) {
  return _gen_key(__primitive( "real2int", f));
}

pragma "inline"
def chpl__defaultHash(c: complex): int(64) {
  return _gen_key(__primitive("real2int", c.re) ^ __primitive("real2int", c.im));
}

pragma "inline"
def chpl__defaultHash(u: chpl_taskID_t): int(64) {
  return _gen_key(u:int(64));
}

// Use djb2 (Dan Bernstein in comp.lang.c.
pragma "inline"
def chpl__defaultHash(x : string): int(64) {
  var hash: int(64) = 0;
  for c in 1..length(x) {
    hash = ((hash << 5) + hash) ^ ascii(x.substring(c));
  }
  return _gen_key(hash);
}

pragma "inline"
def chpl__defaultHash(o: object): int(64) {
  return _gen_key(__primitive( "object2int", o));
}


Removed a suspicious line in the _copy() function
for Chapel Arrays which seemed to target array
wrapper records that contained no class value (and
returned that record rather than halting or otherwise
whining).  Removing this line doesn't seem to break
anything, so pulled it out.

git-svn-id: 88467cb1fb04b8a755be7e1ee1026be4190196ef@11878 3a8e244f-b0f2-452b-bcba-4c88e055c3ca

use List;

def _build_domain_type(dist, param rank : int,
                       type dimensional_index_type = int) {
  var x = dist.buildDomain(rank, dimensional_index_type);
  if rank > 1 {
    type index_type = rank*dimensional_index_type;
    return _domain(x.type, index_type, dimensional_index_type, rank, x);
  } else {
    return _domain(x.type, dimensional_index_type, dimensional_index_type, rank, x);
  }
}

def _build_domain_type(dist, type ind) where !__primitive("isEnumType", ind) {
  var x = dist.buildDomain(ind);
  return _domain(x.type, ind, ind, 1, x);
}

def _build_domain_type(dist, type ind) where __primitive("isEnumType", ind) {
  var x = dist.buildEnumDomain(ind);
  return _domain(x.type, ind, ind, 1, x);
}

def _build_subdomain_type(dom)
  return dom.buildSubdomain();

def _build_sparse_subdomain_type(dist, parentDom) {
  var x = dist.buildSparseDomain(parentDom.rank, parentDom._dim_index_type, parentDom._value);
  if (parentDom.rank > 1) {
    // BLC: would like to just inline this definition of indType rather
    // than name it, but currently we can't use the homogenous tuple
    // syntax in an expression context.  Would like to invent a
    // non-ambiguous homogenous tuple syntax to disambiguate these cases
    // and allow it to be used anywhere.
    type indType = parentDom.rank*parentDom._dim_index_type;
    return _domain(x.type, indType, parentDom._dim_index_type, parentDom.rank, x);
  } else {
    return _domain(x.type, parentDom._dim_index_type, parentDom._dim_index_type, parentDom.rank, x);
  }
}

record _ArrayTypeInfo {
  type eltType;
  var dom: _domain;
}

def _init(a: _ArrayTypeInfo)
  return a.dom.buildArray(a.eltType);

def _build_array_type(dom, type eltType)
  return _ArrayTypeInfo(eltType, dom);

def _build_array(dom, type eltType)
  return dom.buildArray(eltType);

def _build_domain(x) // where x:_domain // see test/arrays/bradc/paulslice.chpl
  return x;                             // is * on ranges?

//
// computes && reduction over stridable of ranges
//
def _any_stridable(ranges, param d: int = 1) param {
  if ranges(d).stridable == true then
    return true;
  else if ranges.size > d then
    return _any_stridable(ranges, d+1);
  else
    return false;
}

def _build_domain(ranges: range(?eltType,bounded,?stridable) ...?rank) {
  type t = ranges(1).eltType;
  param domain_stridable = _any_stridable(ranges);
  var d: domain(rank, t)
           distributed (SingleLocaleDistribution(stridable=domain_stridable));
  d.setIndices(ranges);
  return d;
}

def _build_open_interval_upper(x: _domain)
  return x.buildOpenIntervalUpper();

def _build_index_type(param i: int) where i > 1 {
  var x : i*int;
  return x;
}

def _build_index_type(param i: int) where i == 1 {
  var x : int;
  return x;
}

def _build_index_type(dom) {
  var x: dom._index_type;
  return x;
}

pragma "domain"
record _domain {
  type _domain_type;
  type _index_type;
  type _dim_index_type;
  param rank : int;
  var _value : _domain_type;
  var _promotionType : _index_type;

  def initialize() {
    if _value == nil {
      var x = buildEmptyDomain();
      _value = x._value;
    }
  }

  def these() {
    for i in _value.these() do
      yield i;
  }

  def this(d: _domain) {
    return this((...d._value.ranges));
  }

  def this(i: range(?eltType,?boundedType,?stridable) ...rank) {
    // assuming stridable dependent on incoming ranges for now;
    // really should check stridablility of current domain as well to be
    // correct.
    var newRanges: rank*range(_dim_index_type, bounded, _any_stridable(i));
    for param j in 1..rank {
      newRanges(j) = _value.dim(j)(i(j));
    }
    // this should really be something like:
    //           var subdomainClass = dist.buildDomain(...)
    // but it seems that domains don't store a pointer to their
    // distribution currently, so we build a SingleLocaleArithmetic
    // for now.  Ugh!
    return [(...newRanges)];
    // And then we would do:
    //    return _domain(_domain_type, _index_type, _dim_index_type, rank,
    //                   subdomainClass);
  }

  def dim(d : int)
    return _value.dim(d);

  def dimIter(param d, ind) {
    for i in _value.dimIter(d, ind) do yield i;
  }

  def buildArray(type eltType) {
    var x = _value.buildArray(eltType);
    _value._arrs.append(x);
    return _array(x.type, _index_type, _dim_index_type, eltType, rank, x);
  }

  // buildEmptyDomain is meant to return an uninitialized domain for
  // the given domain type. For example, it should return the same
  // sort of thing that var D: domain(2) dist(yourDist); would result
  // in if you didn't initialize it.  It should be a static method and
  // not refer to any fields that aren't types and params.
  def buildEmptyDomain() {
    var x = _value.buildEmptyDomain();
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def buildSubdomain() {
    var x = _value.buildSubdomain();
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def buildOpenIntervalUpper()
    return _domain(_domain_type, _index_type, _dim_index_type, rank, _value.buildOpenIntervalUpper());

  def clear() {
    _value.clear();
  }

  def add(i) {
    _value.add(i);
  }

  def remove(i) {
    _value.remove(i);
  }

  def numIndices return _value.numIndices;
  def low return _value.low;
  def high return _value.high;

  def member(i) {
    return _value.member(i);
  }

  def expand(i: int ...rank)
    return expand(i);

  def expand(i: rank*int) {
    var x = _value.expand(i);
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def expand(i: int) where rank > 1 {
    var x = _value.expand(i);
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def exterior(i: int ...rank)
    return exterior(i);

  def exterior(i: rank*int) {
    var x = _value.exterior(i);
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def interior(i: int ...rank)
    return interior(i);

  def interior(i: rank*int) {
    var x = _value.interior(i);
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def translate(i: int ...rank)
    return translate(i);

  def translate(i: rank*int) {
    var x = _value.translate(i);
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def subBlocks {
    for d in _value.subBlocks do
      yield d;
  }

  def setIndices(x) {
    _value.setIndices(x);
  }

  def getIndices()
    return _value.getIndices();
}

def _isDomain(x: _domain) param return true;
def _isDomain(x) param return false;

def =(a: _domain, b: _domain) {
  //  if a == nil then return b; // stopgap: why? --sjd
  for e in a._value._arrs do
    e.reallocate(b);
  a._value.setIndices(b._value.getIndices());
  return a;
}

def =(a: _domain, b: _tuple) {
  for ind in 1..b.size {
    a.add(b(ind));
  }
  return a;
}

def =(a: _domain, b) {  // b is iteratable
  a.clear();
  for ind in b {
    a.add(ind);
  }
  return a;
}

pragma "inline" def _pass(a: _domain)
  return a;

def _init(a: _domain)
  return a.buildEmptyDomain();

def _copy(a: _domain) {
  var b: a.buildEmptyDomain();
  b.setIndices(a.getIndices());
  return b;
}

def _domain.writeThis(f: Writer) {
  f.write(_value);
}

def by(a: _domain, b) {
  var x = a._value.strideBy(b);
  return _domain(x.type, a._index_type, a._dim_index_type, a.rank, x);
}

// this is a wrapper class for all arrays
pragma "array"
record _array {
  type _array_type;
  type _index_type;
  type _dim_index_type;
  type eltType;
  param rank : int;
  var _value : _array_type;
  var _promotionType : eltType;

  pragma "valid lvalue"
  def _dom {
    var x : _domain(_value.dom.type, _index_type, _dim_index_type, rank, _value.dom);
    //    x._value = _value.dom;
    return x;
  }

  pragma "valid lvalue"
  def this(d: _domain) {
    _value.checkSlice(d._value);
    var x = _value.slice(_dom(d)._value);
    return _array(x.type, _index_type, _dim_index_type, eltType, rank, x);
  }

  pragma "valid lvalue"
  def this(rs: range(?_eltType,?boundedType,?stridable) ...rank) {
    _value.checkSlice(rs);
    var x = _value.slice(_dom((...rs))._value);
    return _array(x.type, _index_type, _dim_index_type, eltType, rank, x);
  }

  def this(i: _index_type) var
    return _value(i);

  def this(i: _dim_index_type ...rank) var where rank > 1
    return _value(i);

  def validRankChangeArguments(t) param {
    def validRankChangeArgument(r: range(?eltType,?boundedType,?stridable)) param
      return true;

    def validRankChangeArgument(i: _dim_index_type) param
      return true;

    def validRankChangeArgument(x) param
      return false;

    def help(param dim: int) param {
      if !validRankChangeArgument(t(dim)) then
        return false;
      else if dim < t.size then
        return help(dim+1);
      else
        return true;
    }
    return help(1);
  }

  pragma "valid lvalue"
  def this(irs ...rank) where validRankChangeArguments(irs) {
    def isRange(r: range(?e,?b,?s)) param return 1;
    def isRange(r) param return 0;
    def _tupleize(x) {
      var y: 1*x.type;
      y(1) = x;
      return y;
    }
    def collectRanges(param dim: int) {
      if isRange(irs(dim))
        return collectRanges(dim+1, _tupleize(irs(dim)));
      else
        return collectRanges(dim+1);
    }
    def collectRanges(param dim: int, x: _tuple) {
      if dim > irs.size {
        return x;
      } else if dim < irs.size {
        if isRange(irs(dim)) then
          return collectRanges(dim+1, ((...x), irs(dim)));
        else
          return collectRanges(dim+1, x);
      } else {
        if isRange(irs(dim)) then
          return ((...x), irs(dim));
        else
          return x;
      }
    }
    var rs = collectRanges(1);
    var x = _value.rankChange(rs.size, _any_stridable(rs), irs, rs);
    if rs.size == 1 then {
      return _array(x.type, _dim_index_type, _dim_index_type, eltType, 1, x);
    } else {
      type _new_index_type = rs.size*_dim_index_type;
      return _array(x.type, _new_index_type, _dim_index_type, eltType, rs.size, x);
    }
  }

  def these() var {
    for i in _value.these() do
      yield i;
  }

  def numElements return _dom.numIndices; // assume dom name

  def reindex(d: _domain) where rank == 1 {
    var x = _value.reindex(d._value);
    return _array(x.type, x.dim_type, x.dim_type, eltType, rank, x);
  }

  def reindex(d: _domain) where rank != 1 {
    var x = _value.reindex(d._value);
    type xDimIndexType = x.dim_type;       // BLC: hacks to get around
    type xIndexType = rank*xDimIndexType;  // inflexibility of tuple syntax
    return _array(x.type, xIndexType, x.dim_type, eltType, rank, x);
  }

  def IRV var {
    return _value.IRV;
  }
}

pragma "inline" def =(a: _array, b) {
  for (i,bb) in (a._dom,b) do
    a(i) = bb;
  return a;
}

def =(a: _array, b: _tuple) {
  a._value.tupleInit(b);
  return a;
}

def =(a: _array, b: a.eltType) {
  for i in a._dom do
    a(i) = b;
  return a;
}

def _copy(a: _array) {
  var b : [a._dom] a.eltType;
  b = a;
  return b;
}

pragma "inline" def _pass(a: _array) {
  return a;
}

def _init(a: _array) {
  return a._dom.buildArray(a.eltType);
}

def _array.writeThis(f: Writer) {
  f.write(_value);
}

// this is the class that all array classes are derived from
pragma "BaseArray"
class BaseArray {
  def reallocate(d: _domain) {
    halt("reallocating not support for this array type");
  }

  def purge( ind: int) {
    halt("purging not supported for this array type");
  }

  def _resize( length: int, old_map: _ddata(int)) {
    halt("resizing not supported for this array type");
  }

  def sparseShiftArray(shiftrange, initrange) {
    halt("sparseGrowDomain not supported for non-sparse arrays");
  }
}

class BaseDomain {
  var _arrs: list of BaseArray;

  def member(ind) : bool {
    halt("membership test not supported for this domain type");
    return false;
  }
}

class BaseArithmeticDomain : BaseDomain {
  def bbox(d:int) {
    halt("bbox not supported for this domain type");
    return 1..0;
  }
}

class BaseSparseArithmeticDomain : BaseArithmeticDomain {
}

pragma "inline" def _pass(ic: _iteratorClass)
  return ic;

def _copy(ic: _iteratorClass) {
  var capacity = 4, size = 0;
  var data = _ddata(__primitive("get_ic_type", ic), capacity);
  data.init();

  for e in ic {
    if size == capacity {
      capacity = capacity * 2;
      var tmp = _ddata(__primitive("get_ic_type", ic), capacity);
      tmp.init();
      for i in 0..size-1 do
        tmp(i) = data(i);
      data = tmp;
    }
    data(size) = e;
    size = size + 1;
  }

  var A: [1..size] __primitive("get_ic_type", ic);
  for i in 0..size-1 do
    A(i+1) = data(i);
  return A;
}

//
// print out iterators
//
def _iteratorClass.writeThis(f: Writer) {
  var first: bool = true;
  for e in this {
    if !first then
      f.write(" ");
    else
      first = false;
    f.write(e);
  }
}

//
// Swap operators for arrays and domains
//
pragma "inline" def _chpl_swap(x: [?D1], y:[?D2]) {
  for (i,j) in (D1, D2) do
    x(i) <=> y(j);
}

/*  This might be preferable for domains, but it doesn't work because
    domains apparently can't be modified once sent into a function!?

pragma "inline" def _chpl_swap(x: domain, y: domain) {
  const t = y;
  y = x;
  x = y;
}
*/

use List;

def _build_domain_type(dist, param rank : int,
                       type dimensional_index_type = int) {
  var x = dist.buildDomain(rank, dimensional_index_type);
  if rank > 1 {
    type index_type = rank*dimensional_index_type;
    return _domain(x.type, index_type, dimensional_index_type, rank, x);
  } else {
    return _domain(x.type, dimensional_index_type, dimensional_index_type, rank, x);
  }
}

def _build_domain_type(dist, type ind) where !__primitive("isEnumType", ind) {
  var x = dist.buildDomain(ind);
  return _domain(x.type, ind, ind, 1, x);
}

def _build_domain_type(dist, type ind) where __primitive("isEnumType", ind) {
  var x = dist.buildEnumDomain(ind);
  return _domain(x.type, ind, ind, 1, x);
}

def _build_subdomain_type(dom)
  return dom.buildSubdomain();

def _build_sparse_subdomain_type(dist, parentDom) {
  var x = dist.buildSparseDomain(parentDom.rank, parentDom._dim_index_type, parentDom._value);
  if (parentDom.rank > 1) {
    // BLC: would like to just inline this definition of indType rather
    // than name it, but currently we can't use the homogenous tuple
    // syntax in an expression context.  Would like to invent a
    // non-ambiguous homogenous tuple syntax to disambiguate these cases
    // and allow it to be used anywhere.
    type indType = parentDom.rank*parentDom._dim_index_type;
    return _domain(x.type, indType, parentDom._dim_index_type, parentDom.rank, x);
  } else {
    return _domain(x.type, parentDom._dim_index_type, parentDom._dim_index_type, parentDom.rank, x);
  }
}

record _ArrayTypeInfo {
  type eltType;
  var dom: _domain;
}

def _init(a: _ArrayTypeInfo)
  return a.dom.buildArray(a.eltType);

def _build_array_type(dom, type eltType)
  return _ArrayTypeInfo(eltType, dom);

def _build_array(dom, type eltType)
  return dom.buildArray(eltType);

def _build_domain(x) // where x:_domain // see test/arrays/bradc/paulslice.chpl
  return x;                             // is * on ranges?

//
// computes && reduction over stridable of ranges
//
def _any_stridable(ranges, param d: int = 1) param {
  if ranges(d).stridable == true then
    return true;
  else if ranges.size > d then
    return _any_stridable(ranges, d+1);
  else
    return false;
}

def _build_domain(ranges: range(?eltType,bounded,?stridable) ...?rank) {
  type t = ranges(1).eltType;
  param domain_stridable = _any_stridable(ranges);
  var d: domain(rank, t)
           distributed (SingleLocaleDistribution(stridable=domain_stridable));
  d.setIndices(ranges);
  return d;
}

def _build_open_interval_upper(x: _domain)
  return x.buildOpenIntervalUpper();

def _build_index_type(param i: int) where i > 1 {
  var x : i*int;
  return x;
}

def _build_index_type(param i: int) where i == 1 {
  var x : int;
  return x;
}

def _build_index_type(dom) {
  var x: dom._index_type;
  return x;
}

pragma "domain"
record _domain {
  type _domain_type;
  type _index_type;
  type _dim_index_type;
  param rank : int;
  var _value : _domain_type;
  var _promotionType : _index_type;

  def initialize() {
    if _value == nil {
      var x = buildEmptyDomain();
      _value = x._value;
    }
  }

  def these() {
    for i in _value.these() do
      yield i;
  }

  def this(d: _domain) {
    return this((...d._value.ranges));
  }

  def this(i: range(?eltType,?boundedType,?stridable) ...rank) {
    // assuming stridable dependent on incoming ranges for now;
    // really should check stridablility of current domain as well to be
    // correct.
    var newRanges: rank*range(_dim_index_type, bounded, _any_stridable(i));
    for param j in 1..rank {
      newRanges(j) = _value.dim(j)(i(j));
    }
    // this should really be something like:
    //           var subdomainClass = dist.buildDomain(...)
    // but it seems that domains don't store a pointer to their
    // distribution currently, so we build a SingleLocaleArithmetic
    // for now.  Ugh!
    return [(...newRanges)];
    // And then we would do:
    //    return _domain(_domain_type, _index_type, _dim_index_type, rank,
    //                   subdomainClass);
  }

  def dim(d : int)
    return _value.dim(d);

  def dimIter(param d, ind) {
    for i in _value.dimIter(d, ind) do yield i;
  }

  def buildArray(type eltType) {
    var x = _value.buildArray(eltType);
    _value._arrs.append(x);
    return _array(x.type, _index_type, _dim_index_type, eltType, rank, x);
  }

  // buildEmptyDomain is meant to return an uninitialized domain for
  // the given domain type. For example, it should return the same
  // sort of thing that var D: domain(2) dist(yourDist); would result
  // in if you didn't initialize it.  It should be a static method and
  // not refer to any fields that aren't types and params.
  def buildEmptyDomain() {
    var x = _value.buildEmptyDomain();
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def buildSubdomain() {
    var x = _value.buildSubdomain();
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def buildOpenIntervalUpper()
    return _domain(_domain_type, _index_type, _dim_index_type, rank, _value.buildOpenIntervalUpper());

  def clear() {
    _value.clear();
  }

  def add(i) {
    _value.add(i);
  }

  def remove(i) {
    _value.remove(i);
  }

  def numIndices return _value.numIndices;
  def low return _value.low;
  def high return _value.high;

  def member(i) {
    return _value.member(i);
  }

  def expand(i: int ...rank)
    return expand(i);

  def expand(i: rank*int) {
    var x = _value.expand(i);
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def expand(i: int) where rank > 1 {
    var x = _value.expand(i);
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def exterior(i: int ...rank)
    return exterior(i);

  def exterior(i: rank*int) {
    var x = _value.exterior(i);
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def interior(i: int ...rank)
    return interior(i);

  def interior(i: rank*int) {
    var x = _value.interior(i);
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def translate(i: int ...rank)
    return translate(i);

  def translate(i: rank*int) {
    var x = _value.translate(i);
    return _domain(x.type, _index_type, _dim_index_type, rank, x);
  }

  def subBlocks {
    for d in _value.subBlocks do
      yield d;
  }

  def setIndices(x) {
    _value.setIndices(x);
  }

  def getIndices()
    return _value.getIndices();
}

def _isDomain(x: _domain) param return true;
def _isDomain(x) param return false;

def =(a: _domain, b: _domain) {
  //  if a == nil then return b; // stopgap: why? --sjd
  for e in a._value._arrs do
    e.reallocate(b);
  a._value.setIndices(b._value.getIndices());
  return a;
}

def =(a: _domain, b: _tuple) {
  for ind in 1..b.size {
    a.add(b(ind));
  }
  return a;
}

def =(a: _domain, b) {  // b is iteratable
  a.clear();
  for ind in b {
    a.add(ind);
  }
  return a;
}

pragma "inline" def _pass(a: _domain)
  return a;

def _init(a: _domain)
  return a.buildEmptyDomain();

def _copy(a: _domain) {
  var b: a.buildEmptyDomain();
  b.setIndices(a.getIndices());
  return b;
}

def _domain.writeThis(f: Writer) {
  f.write(_value);
}

def by(a: _domain, b) {
  var x = a._value.strideBy(b);
  return _domain(x.type, a._index_type, a._dim_index_type, a.rank, x);
}

// this is a wrapper class for all arrays
pragma "array"
record _array {
  type _array_type;
  type _index_type;
  type _dim_index_type;
  type eltType;
  param rank : int;
  var _value : _array_type;
  var _promotionType : eltType;

  pragma "valid lvalue"
  def _dom {
    var x : _domain(_value.dom.type, _index_type, _dim_index_type, rank, _value.dom);
    //    x._value = _value.dom;
    return x;
  }

  pragma "valid lvalue"
  def this(d: _domain) {
    _value.checkSlice(d._value);
    var x = _value.slice(_dom(d)._value);
    return _array(x.type, _index_type, _dim_index_type, eltType, rank, x);
  }

  pragma "valid lvalue"
  def this(rs: range(?_eltType,?boundedType,?stridable) ...rank) {
    _value.checkSlice(rs);
    var x = _value.slice(_dom((...rs))._value);
    return _array(x.type, _index_type, _dim_index_type, eltType, rank, x);
  }

  def this(i: _index_type) var
    return _value(i);

  def this(i: _dim_index_type ...rank) var where rank > 1
    return _value(i);

  def validRankChangeArguments(t) param {
    def validRankChangeArgument(r: range(?eltType,?boundedType,?stridable)) param
      return true;

    def validRankChangeArgument(i: _dim_index_type) param
      return true;

    def validRankChangeArgument(x) param
      return false;

    def help(param dim: int) param {
      if !validRankChangeArgument(t(dim)) then
        return false;
      else if dim < t.size then
        return help(dim+1);
      else
        return true;
    }
    return help(1);
  }

  pragma "valid lvalue"
  def this(irs ...rank) where validRankChangeArguments(irs) {
    def isRange(r: range(?e,?b,?s)) param return 1;
    def isRange(r) param return 0;
    def _tupleize(x) {
      var y: 1*x.type;
      y(1) = x;
      return y;
    }
    def collectRanges(param dim: int) {
      if isRange(irs(dim))
        return collectRanges(dim+1, _tupleize(irs(dim)));
      else
        return collectRanges(dim+1);
    }
    def collectRanges(param dim: int, x: _tuple) {
      if dim > irs.size {
        return x;
      } else if dim < irs.size {
        if isRange(irs(dim)) then
          return collectRanges(dim+1, ((...x), irs(dim)));
        else
          return collectRanges(dim+1, x);
      } else {
        if isRange(irs(dim)) then
          return ((...x), irs(dim));
        else
          return x;
      }
    }
    var rs = collectRanges(1);
    var x = _value.rankChange(rs.size, _any_stridable(rs), irs, rs);
    if rs.size == 1 then {
      return _array(x.type, _dim_index_type, _dim_index_type, eltType, 1, x);
    } else {
      type _new_index_type = rs.size*_dim_index_type;
      return _array(x.type, _new_index_type, _dim_index_type, eltType, rs.size, x);
    }
  }

  def these() var {
    for i in _value.these() do
      yield i;
  }

  def numElements return _dom.numIndices; // assume dom name

  def reindex(d: _domain) where rank == 1 {
    var x = _value.reindex(d._value);
    return _array(x.type, x.dim_type, x.dim_type, eltType, rank, x);
  }

  def reindex(d: _domain) where rank != 1 {
    var x = _value.reindex(d._value);
    type xDimIndexType = x.dim_type;       // BLC: hacks to get around
    type xIndexType = rank*xDimIndexType;  // inflexibility of tuple syntax
    return _array(x.type, xIndexType, x.dim_type, eltType, rank, x);
  }

  def IRV var {
    return _value.IRV;
  }
}

pragma "inline" def =(a: _array, b) {
  for (i,bb) in (a._dom,b) do
    a(i) = bb;
  return a;
}

def =(a: _array, b: _tuple) {
  a._value.tupleInit(b);
  return a;
}

def =(a: _array, b: a.eltType) {
  for i in a._dom do
    a(i) = b;
  return a;
}

def _copy(a: _array) {
  if a._value == nil then return a;
  var b : [a._dom] a.eltType;
  b = a;
  return b;
}

pragma "inline" def _pass(a: _array) {
  return a;
}

def _init(a: _array) {
  return a._dom.buildArray(a.eltType);
}

def _array.writeThis(f: Writer) {
  f.write(_value);
}

// this is the class that all array classes are derived from
pragma "BaseArray"
class BaseArray {
  def reallocate(d: _domain) {
    halt("reallocating not support for this array type");
  }

  def purge( ind: int) {
    halt("purging not supported for this array type");
  }

  def _resize( length: int, old_map: _ddata(int)) {
    halt("resizing not supported for this array type");
  }

  def sparseShiftArray(shiftrange, initrange) {
    halt("sparseGrowDomain not supported for non-sparse arrays");
  }
}

class BaseDomain {
  var _arrs: list of BaseArray;

  def member(ind) : bool {
    halt("membership test not supported for this domain type");
    return false;
  }
}

class BaseArithmeticDomain : BaseDomain {
  def bbox(d:int) {
    halt("bbox not supported for this domain type");
    return 1..0;
  }
}

class BaseSparseArithmeticDomain : BaseArithmeticDomain {
}

pragma "inline" def _pass(ic: _iteratorClass)
  return ic;

def _copy(ic: _iteratorClass) {
  var capacity = 4, size = 0;
  var data = _ddata(__primitive("get_ic_type", ic), capacity);
  data.init();

  for e in ic {
    if size == capacity {
      capacity = capacity * 2;
      var tmp = _ddata(__primitive("get_ic_type", ic), capacity);
      tmp.init();
      for i in 0..size-1 do
        tmp(i) = data(i);
      data = tmp;
    }
    data(size) = e;
    size = size + 1;
  }

  var A: [1..size] __primitive("get_ic_type", ic);
  for i in 0..size-1 do
    A(i+1) = data(i);
  return A;
}

//
// print out iterators
//
def _iteratorClass.writeThis(f: Writer) {
  var first: bool = true;
  for e in this {
    if !first then
      f.write(" ");
    else
      first = false;
    f.write(e);
  }
}

//
// Swap operators for arrays and domains
//
pragma "inline" def _chpl_swap(x: [?D1], y:[?D2]) {
  for (i,j) in (D1, D2) do
    x(i) <=> y(j);
}

/*  This might be preferable for domains, but it doesn't work because
    domains apparently can't be modified once sent into a function!?

pragma "inline" def _chpl_swap(x: domain, y: domain) {
  const t = y;
  y = x;
  x = y;
}
*/


Handled cases in DistributedBoundedFIFO where user we user would repeatedly enqueue/dequeue on an empty queue and cause performance loss

use BlockDist;
use Time;
use Random;

/*
  TODO: In QueueFactory, replace with 'proc type Queue.makeBoundedFIFO' etc.
*/

// Determines status of a slot.
const SLOT_EMPTY = 0;
const SLOT_FULL = 1;

record DistributedBoundedFIFOSlot {
  type eltType;

  var elt : eltType;
  var status : atomic int;
  var isEnq : atomic bool;
  var isDeq : atomic bool;
  var promise : atomic uint;
}

class DistributedBoundedFIFO {
  type eltType;
  var cap : int;

  // Two monotonically increasing counters used in deciding which locale to choose from
  var globalHead : atomic uint;
  var globalTail : atomic uint;
  var queueSize : atomic int;

  // per-locale data
  var eltSlotsSpace = {0..#cap};
  var eltSlotsDomain = eltSlotsSpace dmapped Block(boundingBox=eltSlotsSpace);
  var eltSlots : [eltSlotsDomain] DistributedBoundedFIFOSlot(eltType);

  proc DistributedBoundedFIFO(type eltType) {
    if cap == 0 then halt("Cap cannot be 0!");
  }

  proc enqueue(elt : eltType) : bool {
    // Fast path... Check if queue is full...
    if queueSize.read() >= cap {
      return false;
    }

    // Try to see if there is room...
    while true {
      var sz = queueSize.fetchAdd(1);
      if sz >= cap {
        return false;
      } else if sz >= 0 {
        break;
      }
    }

    while true {
      var head = globalHead.fetchAdd(1) % cap : uint;
      ref slot = eltSlots[head : int];
      var promise = slot.promise.fetchAdd(1);

      // If the promise is 0, then it will become EMPTY soon and we can add to it.
      // However, we need to wait until after a concurrent dequeue operation finishes.
      if promise == 0 {
        // Another enqueuer is waiting on this cell...
        while slot.isEnq.testAndSet() do chpl_task_yield();

        slot.status.waitFor(SLOT_EMPTY);
        slot.elt = elt;
        slot.status.write(SLOT_FULL);

        slot.isEnq.write(false);
        return true;
      }
      // If the promise is negative, that means that a dequeue operation has skipped
      // this slot, and we must do so as well to correct the queue.
      else if promise < 0 {
        continue;
      }
      // If the promise is positive, that means that the queue is full. This means
      // that the promise is > 0, meaning any other dequeue will correct and skip
      // this slot by decrementing the count atomically.
      else if promise > 0 {
        return false;
      }
    }

    halt("Broke out of enqueue loop...");
  }

  proc dequeue() : (bool, eltType) {
    // Fast path... check if queue is empty...
    if queueSize.read() < 1 {
      return (false, _defaultOf(eltType));
    }

    while true {
      var sz = queueSize.fetchSub(1);
      if sz <= 0 {
        return (false, _defaultOf(eltType));
      } else if sz <= cap {
        break;
      }
    }

    while true {
      var tail = globalTail.fetchAdd(1) % cap : uint;
      ref slot = eltSlots[tail : int];
      var promise = slot.promise.fetchSub(1);

      // If the promise is 1, then it will become FULL soon and we can take from it.
      // However, we need to wait until after a concurrent enqueue operation finishes.
      if promise == 1 {
        // Another dequeuer is waiting on this cell...
        while slot.isDeq.testAndSet() do chpl_task_yield();

        slot.status.waitFor(SLOT_FULL);
        var elt = slot.elt;
        slot.status.write(SLOT_EMPTY);

        slot.isDeq.write(false);
        return (true, elt);
      }
      // If the slot promises more than 1 element, then an enqueue operation has skipped
      // this slot, and we must do so as well to correct the queue.
      else if promise > 1 {
        continue;
      }
      // If the promise is 0, then the queue is empty; if the promise is negative,
      // that means other dequeue operations have also skipped this and it has no yet
      // been remedied by other enqueue operation.
      else if promise < 1 {
        return (false, _defaultOf(eltType));
      }
    }

    halt("Broke out of dequeue loop...");
  }
}

proc main() {
  var nJitter = 0;
  var nComputations = 0;
  var nElements = 1000000;
  var nTrials = 8;
  var enqueueTrialTime : [1 .. nTrials] real;
  var dequeueTrialTime : [1 .. nTrials] real;

  // Obtain average time for enqueue followed by dequeued...
  for i in 1 .. nTrials {
    var queue = new DistributedBoundedFIFO(int, cap=nElements);
    // We only use one or the other, but we must declare both.
    // TODO: Have both implement some parent 'Queue' class/interface?
    var timer = new Timer();
    timer.start();

    coforall loc in Locales do on loc {
      var iterations = nElements;

      coforall tid in 0 .. #here.maxTaskPar {
        var x : atomic int;
        var randStr = makeRandomStream(int);
        for j in 1 .. iterations / here.maxTaskPar {
          queue.enqueue(j);
          var nComps = nComputations + (if nJitter then (randStr.getNext() % nJitter) else 0);
          for i in 1 .. nComps {
            // Hopefully compiler doesn't throw away?
            x.write(sin(i) : int);
          }
        }
      }
    }

    timer.stop();
    enqueueTrialTime[i] = (nElements * numLocales) / timer.elapsed();
    writeln(i, "/", nTrials, ": ", (+ reduce enqueueTrialTime) / i);
    timer.clear();
    timer.start();

    coforall loc in Locales do on loc {
      var iterations = nElements;

      coforall tid in 0 .. #here.maxTaskPar {
        var x : atomic int;
        var randStr = makeRandomStream(int);
        for j in 1 .. iterations / here.maxTaskPar {
          var retval = queue.dequeue();
          var nComps = nComputations + (if nJitter then (randStr.getNext() % nJitter) else 0);
          for i in 1 .. nComps {
            // Hopefully compiler doesn't throw away?
            x.write(sin(i) : int);
          }
        }
      }
    }

    timer.stop();
    dequeueTrialTime[i] = (nElements * numLocales) / timer.elapsed();
    writeln(i, "/", nTrials, ": ", (+ reduce dequeueTrialTime) / i);

    delete queue;
  }
}

use BlockDist;
use Time;
use Random;

/*
  TODO: In QueueFactory, replace with 'proc type Queue.makeBoundedFIFO' etc.
*/

// Determines status of a slot.
const SLOT_EMPTY = 0;
const SLOT_FULL = 1;

record DistributedBoundedFIFOSlot {
  type eltType;

  var elt : eltType;
  var status : atomic int;
  var isEnq : atomic bool;
  var isDeq : atomic bool;
  var promise : atomic uint;
}

class DistributedBoundedFIFO {
  type eltType;
  var cap : int;

  // Two monotonically increasing counters used in deciding which locale to choose from
  var globalHead : atomic uint;
  var globalTail : atomic uint;
  var queueSize : atomic int;

  // per-locale data
  var eltSlotsSpace = {0..#cap};
  var eltSlotsDomain = eltSlotsSpace dmapped Block(boundingBox=eltSlotsSpace);
  var eltSlots : [eltSlotsDomain] DistributedBoundedFIFOSlot(eltType);

  proc DistributedBoundedFIFO(type eltType) {
    if cap == 0 then halt("Cap cannot be 0!");
  }

  proc enqueue(elt : eltType) : bool {
    // Try to see if there is room...
    while true {
      var sz = queueSize.fetchAdd(1);
      if sz >= cap {
        return false;
      } else if sz >= 0 {
        break;
      }
    }

    while true {
      var head = globalHead.fetchAdd(1) % cap : uint;
      ref slot = eltSlots[head : int];
      var promise = slot.promise.fetchAdd(1);

      // If the promise is 0, then it will become EMPTY soon and we can add to it.
      // However, we need to wait until after a concurrent dequeue operation finishes.
      if promise == 0 {
        // Another enqueuer is waiting on this cell...
        while slot.isEnq.testAndSet() do chpl_task_yield();

        slot.status.waitFor(SLOT_EMPTY);
        slot.elt = elt;
        slot.status.write(SLOT_FULL);

        slot.isEnq.write(false);
        return true;
      }
      // If the promise is negative, that means that a dequeue operation has skipped
      // this slot, and we must do so as well to correct the queue.
      else if promise < 0 {
        continue;
      }
      // If the promise is positive, that means that the queue is full. This means
      // that the promise is > 0, meaning any other dequeue will correct and skip
      // this slot by decrementing the count atomically.
      else if promise > 0 {
        return false;
      }
    }

    halt("Broke out of enqueue loop...");
  }

  proc dequeue() : (bool, eltType) {
    while true {
      var sz = queueSize.fetchSub(1);
      if sz <= 0 {
        return (false, _defaultOf(eltType));
      } else if sz <= cap {
        break;
      }
    }

    while true {
      var tail = globalTail.fetchAdd(1) % cap : uint;
      ref slot = eltSlots[tail : int];
      var promise = slot.promise.fetchSub(1);

      // If the promise is 1, then it will become FULL soon and we can take from it.
      // However, we need to wait until after a concurrent enqueue operation finishes.
      if promise == 1 {
        // Another dequeuer is waiting on this cell...
        while slot.isDeq.testAndSet() do chpl_task_yield();

        slot.status.waitFor(SLOT_FULL);
        var elt = slot.elt;
        slot.status.write(SLOT_EMPTY);

        slot.isDeq.write(false);
        return (true, elt);
      }
      // If the slot promises more than 1 element, then an enqueue operation has skipped
      // this slot, and we must do so as well to correct the queue.
      else if promise > 1 {
        continue;
      }
      // If the promise is 0, then the queue is empty; if the promise is negative,
      // that means other dequeue operations have also skipped this and it has no yet
      // been remedied by other enqueue operation.
      else if promise < 1 {
        return (false, _defaultOf(eltType));
      }
    }

    halt("Broke out of dequeue loop...");
  }
}

proc main() {
  var nJitter = 0;
  var nComputations = 0;
  var nElements = 1000000;
  var nTrials = 8;
  var enqueueTrialTime : [1 .. nTrials] real;
  var dequeueTrialTime : [1 .. nTrials] real;

  // Obtain average time for enqueue followed by dequeued...
  for i in 1 .. nTrials {
    var queue = new DistributedBoundedFIFO(int, cap=nElements);
    // We only use one or the other, but we must declare both.
    // TODO: Have both implement some parent 'Queue' class/interface?
    var timer = new Timer();
    timer.start();

    coforall loc in Locales do on loc {
      var iterations = nElements;

      coforall tid in 0 .. #here.maxTaskPar {
        var x : atomic int;
        var randStr = makeRandomStream(int);
        for j in 1 .. iterations / here.maxTaskPar {
          queue.enqueue(j);
          var nComps = nComputations + (if nJitter then (randStr.getNext() % nJitter) else 0);
          for i in 1 .. nComps {
            // Hopefully compiler doesn't throw away?
            x.write(sin(i) : int);
          }
        }
      }
    }

    timer.stop();
    enqueueTrialTime[i] = (nElements * numLocales) / timer.elapsed();
    writeln(i, "/", nTrials, ": ", (+ reduce enqueueTrialTime) / i);
    timer.clear();
    timer.start();

    coforall loc in Locales do on loc {
      var iterations = nElements;

      coforall tid in 0 .. #here.maxTaskPar {
        var x : atomic int;
        var randStr = makeRandomStream(int);
        for j in 1 .. iterations / here.maxTaskPar {
          var retval = queue.dequeue();
          var nComps = nComputations + (if nJitter then (randStr.getNext() % nJitter) else 0);
          for i in 1 .. nComps {
            // Hopefully compiler doesn't throw away?
            x.write(sin(i) : int);
          }
        }
      }
    }

    timer.stop();
    dequeueTrialTime[i] = (nElements * numLocales) / timer.elapsed();
    writeln(i, "/", nTrials, ": ", (+ reduce dequeueTrialTime) / i);

    delete queue;
  }
}