Have more concrete grasp on how I am going to be doing work stealing. I believe I am closer to having it down and being scalable (which is important as well)

use Queue;
use Random;
use Time;
use BigInteger;

// Segment statuses
const UNLOCKED : uint = 0;
const ENQUEUE : uint = 1;
const DEQUEUE : uint = 2;
const STEALING : uint = 3;
const STEALING_INIT : uint = 4;
const VICTIM : uint = 5;

// Work Stealing statuses
const UNINITIALIZED : int = -2;
const INITIALIZED : int = -1;
const CLAIMED : int = 0;
const FINISHED_WITH_NO_WORK : int = 1;
const FINISHED_WITH_WORK : int = 2;

config param ELEMS_PER_BLOCK = 1024;

class LocalWorkQueueSegmentBlock {
  type eltType;
  var idx = 1;
  var elems : ELEMS_PER_BLOCK * eltType;
  var next : LocalWorkQueueSegmentBlock(eltType);
}

record LocalWorkQueueSegment {
  type eltType;

  var status : atomic uint;

  var headBlock : LocalWorkQueueSegmentBlock(eltType);
  var tailBlock : LocalWorkQueueSegmentBlock(eltType);

  // For when we are stealing work... helper threads may attempt to
  // help during a steal by setting one of the values from -1 to 0, and
  // when they are finished they may set it to 1 indicating that they are finished.
  // The first part helps distribute work to tasks waiting and doing nothing, and the
  // last part helps us know when we can declare that work stealing is finished.
  var workStealingStatus : [{0 .. #here.maxTaskPar}] atomic int;
  var workStolen : [{0 .. #here.maxTaskPar}] LocalWorkQueueSegmentBlock(eltType);
  var workStealingIdx : atomic int;

  // Number of elements currently in the list to allow faster determinism of whether
  // or not an operation should attempt this bucket.
  var nElems : atomic uint;

  inline proc isEmpty {
    return nElems.read() == 0;
  }

  inline proc take {
    if isEmpty then return (false, _defaultOf(eltType));

    var elem = headBlock.elems[headBlock.idx];
    headBlock.idx = headBlock.idx - 1;
    nElems.sub(1);

    // Fix list if we consumed last one...
    if headBlock.idx == 0 {
      var block = headBlock;
      headBlock = headBlock.next;
      delete block;
    }

    return (true, elem);
  }
}

class LocalWorkQueue : Queue {
  // Place in own cache line
  var startIdxEnq : atomic uint;
  var startIdxDeq : atomic uint;
  var workStealer : atomic int;

  var maxParallelSegmentSpace = {0 .. #here.maxTaskPar};
  var segments : [maxParallelSegmentSpace] LocalWorkQueueSegment(eltType);

  const maxIterationsPerPhase = here.maxTaskPar;

  proc LocalWorkQueue(type eltType) {
    workStealer.write(-1);
  }

  /*
    Enqueue...
  */

  proc enqueue(elt : eltType) {
    var startIdx = startIdxEnq.fetchAdd(1) % here.maxTaskPar : uint;
    var segmentIdx = enqueueAcquire(startIdx);
    ref seg = segments[segmentIdx];
    var block = seg.tailBlock;

    // Empty?
    if block == nil then {
      seg.tailBlock = new LocalWorkQueueSegmentBlock(eltType);
      seg.headBlock = seg.tailBlock;
    }

    // Full?
    if block.idx > ELEMS_PER_BLOCK {
      block.next = new LocalWorkQueueSegmentBlock(eltType);
      seg.tailBlock = block.next;
      block = block.next;
    }

    block.elems[block.idx] = elt;
    block.idx = block.idx + 1;

    seg.nElems.add(1);
    seg.status.write(UNLOCKED);
  }

  proc enqueueAcquire(startIdx) : int {
    var retval = enqueueAcquirePhase1(startIdx);
    if retval == -1 {
      retval = enqueueAcquirePhase2(startIdx);
    }

    return retval;
  }

  // In Phase 1, we attempt to acquire the first bucket that is unlocked
  inline proc enqueueAcquirePhase1(startIdx) : int {
    var idx = startIdx : int;
    var iterations = 0;

    // Find first free bucket
    while iterations < maxIterationsPerPhase {
      ref seg = segments[idx];

      // Attempt to acquire...
      if seg.status.compareExchangeStrong(UNLOCKED, ENQUEUE) {
        return idx;
      }

      iterations = iterations + 1;
      idx = (idx + 1) % here.maxTaskPar;
    }

    return -1;
  }

  // In Phase 2, we attempt to acquire the first bucket that is locked...
  inline proc enqueueAcquirePhase2(startIdx) : int {
    var idx = startIdx : int;

    // Find first free bucket
    while true {
      ref seg = segments[idx];

      // Attempt to acquire...
      while true {
        var currStatus = seg.status.read();

        select currStatus {
          when UNLOCKED do {
            if seg.status.compareExchangeStrong(UNLOCKED, ENQUEUE) {
              return idx;
            }
          }
          // If someone is stealing, then that means that we'd be waiting for a while
          // anyway, so we may as well attempt to find a node with a shorter wait time.
          when STEALING || STEALING_INIT {
            break;
          }
        }

        // TODO: Implement exponential backoff + yielding
        chpl_task_yield();
      }

      idx = (idx + 1) % here.maxTaskPar;
    }

    halt("Broke out of phase 2 without getting segment...");
  }

  /*
    Dequeue...
  */

  proc dequeue() : (bool, eltType) {
    var startIdx = startIdxDeq.fetchAdd(1) % here.maxTaskPar : uint;

  }

  // Phase 1: acquire unlocked bucket which contains elements
  inline proc dequeueAcquirePhase1(startIdx) : int {
    var idx = startIdx : int;
    var maxIterations = here.maxTaskPar;
    var iterations = 0;

    // Find first non-empty free bucket
    while iterations < maxIterations {
      ref seg = segments[idx];

      // Attempt to acquire...
      if seg.nElems.peek() != 0 && seg.status.compareExchangeStrong(UNLOCKED, DEQUEUE) {
        return idx;
      }

      iterations = iterations + 1;
      idx = (idx + 1) % here.maxTaskPar;
    }

    return -1;
  }

  // Phase 2: acquire locked bucket which contains elements
  inline proc dequeueAcquirePhase2(startIdx) : int {
    var idx = startIdx : int;
    var maxIterations = here.maxTaskPar;
    var iterations = 0;

    // Find first non-empty bucket
    while iterations < maxIterations {
      ref seg = segments[idx];

      // Attempt to acquire...
      while !seg.isEmpty {
        if seg.status.read() == UNLOCKED && seg.status.compareExchangeStrong(UNLOCKED, DEQUEUE) {
          var (hasElem, elem) = seg.take;
          seg.status.write(UNLOCKED);
          if hasElem {
            return (hasElem, elem);
          }
        }

        // Backoff
        chpl_task_yield();
      }

      iterations = iterations + 1;
      idx = (idx + 1) % here.maxTaskPar;
    }

    return -1;
  }

  // Phase 3: Take any bucket whether it is locked or unlocked, empty or non-empty
  inline proc dequeueAcquirePhase3(startIdx) : (bool, eltType) {
    var idx : int = startIdx;
    var backoff = 0;

    // Attempt to acquire...
    while true {
      ref seg = segments[idx];
      var currStatus = seg.status.read();

      select currStatus {
        // Quick acquire
        when UNLOCKED {
          backoff = 0;
          if seg.status.compareExchangeStrong(UNLOCKED, DEQUEUE) {
            // Steal if empty... because we help if we are not the one set, we only
            // know whether the queue is empty if we were the work stealer.
            if seg.isEmpty {
              var (_idx, hasElem, elem) = stealWork(idx);
              if _idx == idx && !hasElem then return (false, _defaultOf(eltType));
              else idx = _idx;
              continue;
            }

            var retval = seg.take;
            seg.status.write(UNLOCKED);
            return retval;
          }
        }
        // This segment is being stolen from. Switch to that segment...
        when VICTIM {
          var currStealer = workStealer.read();
          if currStealer != -1 then idx = currStealer;
          backoff = 0;
          continue;
        }
        // If someone else is stealing work, we add ourself as a potential helper
        // to help speed this along, and hopefully we get an element for ourself.
        when STEALING || STEALING_INIT {
          var (hasElem, elem) = helpStealWork(startIdx);

          // We have our element...
          if hasElem then return (hasElem, elem);
          backoff = 0;
        }
      }

      // Backoff to maximum...
      if backoff == 0 then chpl_task_yield();
      else sleep(max(1000, 2 ** backoff), TimeUnits.microseconds);
    }

    halt("Somehow exited loop in Phase 3...");
  }

  /*
    Work Stealing
  */

  // We have the lock but it is empty, try to set ourself as work stealer...
  // As our index can change based on whether or not we become the work stealer
  // or help someone else become the work stealer, we return our acquired index.
  proc stealWork(idx) : (int, bool, eltType) {
    ref seg = segments[idx];

    // If we set ourself as work stealer, we may proceed, otherwise if someone else
    // is set, we help them out instead...
    while true {
      if workStealer.compareExchangeStrong(-1, idx) then break;

      // Someone else is set...
      var currStealer = workStealer.read();
      if currStealer != -1 {
        seg.status.write(UNLOCKED);
        var (hasElem, elem) = helpStealWork(currStealer);
        return (currStealer, hasElem, elem);
      }
    }

    // We are the current work stealer, so our responsibility is to initialize
    // all work statuses and signal that we may begin our work stealing
    for status in seg.workStealingStatus do status.write(INITIALIZED);
    seg.workStealingIdx.write(0);
    seg.status.write(STEALING_INIT);

    // Help steal work with everyone else
    var (hasElem, elem) = helpStealWork(idx);

    // We are finished, all work stolen (if any) are now available...
    // As work stealer, our next job is merge it all together...
    for i in 0..#here.maxTaskPar {
      // Spin until they are finished... if they finish but have no work, skip them
      while seg.workStealingStatus[i].read() == CLAIMED then chpl_task_yield();
      if i == idx || seg.workStealingStatus[i].read() != FINISHED_WITH_WORK then continue;

      // TODO: Find a way copy data from one tuple to another that is faster
      var blockElems = block.idx - 1;
      seg.nElems.add(blockElems);

      ref stolenBlock = seg.workStolen[i];
      ref tailBlock = seg.tailBlock;
      while stolenBlock.idx > 0 {
        // If we haven't stolen an element by now, do so
        if !hasElem {
          elem = stolenBlock.elems[stolenBlock.idx];
          hasElem = true;
          stolenBlock.idx = stolenBlock.idx - 1;
          continue;
        }

        if tailBlock.idx > ELEMS_PER_BLOCK {
          tailBlock.next = new LocalWorkQueueSegmentBlock(eltType);
          seg.tailBlock = tailBlock.next;
          tailBlock = tailBlock.next;
        }

        tailBlock.elems[tailBlock.idx] = stolenBlock.elems[stolenBlock.idx];
        stolenBlock.idx = stolenBlock.idx - 1;
        tailBlock.idx = tailBlock.idx + 1;
      }

      delete stolenBlock;
    }

    // No longer work stealer
    workStealer.write(-1);
    seg.status.write(UNLOCKED);

    // At this point, we have successfully stolen work. Furthermore, we have
    // stolen at least one element for ourself.
    return (idx, hasElem, elem);
  }

  proc helpStealWork(idx) : (bool, eltType) {
    ref seg = segments[idx];

    while true {
      var currStatus = seg.status.read();

      select currStatus {
        // They are still initializing...
        when STEALING {
          // Do nothing...
        }
        // They are initialized, we can now attempt to help...
        when STEALING_INIT {
          break;
        }
        // We're not stealing work...
        otherwise do return (false, _defaultOf(eltType));
      }
    }

    var (hasElem, elem) : (bool, eltType);

    // We obtain our index which tells us which segment we need to steal from
    // It should be noted that we handle the edge cases where we get preempted long
    // enough here that the current work stealing endeavor is over. In the case where
    // work stealing is finished and the next has not started, 'ourIdx' will be greater
    // than or equal to here.maxTaskPar, meaning that we would spin anyway... If the next
    // work stealing endeavor has started, but it has not yet been initialized, then technically
    // the counter would be below here.maxTaskPar, but the workStealingStatus would likely be
    // 'FINISHED_WITH_WORK' or 'FINISHED_WITH_NO_WORK'. In the case, that it is neither then we have
    // already setup everything anyway, and we can partake in the next work stealing.
    while true {
      var ourIdx = seg.workStealingIdx.fetchAdd(1);
      ref wsStatus = seg.workStealingStatus[ourIdx];

      if ourIdx >= here.maxTaskPar {
        return (hasElem, elem);
      }
      if wsStatus.compareExchangeStrong(INITIALIZED, CLAIMED) == false {
        return (hasElem, elem);
      }
      if ourIdx == idx then continue;

      // Do our part by stealing work...
      ref otherSeg = segments[ourIdx];
      while !otherSeg.isEmpty {
        if otherSeg.status.read() != UNLOCKED {
          chpl_task_yield();
        } else if otherSeg.status.compareExchangeStrong(UNLOCKED, VICTIM) {
          break;
        }
      }

      if otherSeg.isEmpty() {
        wsStatus.write(FINISHED_WITH_NO_WORK);
        otherSeg.status.write(UNLOCKED);
        continue;
      }

      // Adjust index if it is full (went past end)
      if otherSeg.headBlock.idx > here.maxTaskPar {
        otherSeg.headBlock.idx = otherSeg.headBlock.idx - 1;
      }

      if !hasElem {
        // Steal an item...
        (hasElem, elem) = (true, otherSeg.headBlock.elems[otherSeg.headBlock.idx]);
        otherSeg.headBlock.idx = otherSeg.headBlock.idx - 1;
        otherSeg.nElems.sub(1);

        // Fix list if we consumed last one...
        if otherSeg.headBlock.idx == 0 {
          var block = otherSeg.headBlock;
          otherSeg.headBlock = otherSeg.headBlock.next;
          delete block;
        }

        if otherSeg.nElems.read() == 0 {
          wsStatus.write(FINISHED_WITH_NO_WORK);
          otherSeg.status.write(UNLOCKED);
          continue;
        }
      }

      // Report what has been stolen...
      seg.workStolen[ourIdx] = otherSeg.headBlock;
      otherSeg.headBlock = otherSeg.headBlock.next;
      wsStatus.write(FINISHED_WITH_WORK);
      otherSeg.status.write(UNLOCKED);
    }
  }
}

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

  // Obtain average time for enqueue followed by dequeued...
  for i in 1 .. nTrials {
    var queue = new LocalWorkQueue(int);
    // 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 / timer.elapsed();
    writeln(i, "/", nTrials, ": ", (+ reduce enqueueTrialTime) / i);
    timer.clear();
    delete queue;
  }
}

use Queue;
use Random;
use Time;
use BigInteger;

// Segment statuses
const UNLOCKED : uint = 0;
const ENQUEUE : uint = 1;
const DEQUEUE : uint = 2;
const STEALING : uint = 3;
const STEALING_INIT : uint = 4;
const VICTIM : uint = 5;

// Work Stealing statuses
const UNINITIALIZED : int = -2;
const INITIALIZED : int = -1;
const CLAIMED : int = 0;
const FINISHED_WITH_NO_WORK : int = 1;
const FINISHED_WITH_WORK : int = 2;

config param ELEMS_PER_BLOCK = 1024;

class LocalWorkQueueSegmentBlock {
  type eltType;
  var idx = 1;
  var elems : ELEMS_PER_BLOCK * eltType;
  var next : LocalWorkQueueSegmentBlock(eltType);
}

record LocalWorkQueueSegment {
  type eltType;

  var status : atomic uint;

  var headBlock : LocalWorkQueueSegmentBlock(eltType);
  var tailBlock : LocalWorkQueueSegmentBlock(eltType);

  // For when we are stealing work... helper threads may attempt to
  // help during a steal by setting one of the values from -1 to 0, and
  // when they are finished they may set it to 1 indicating that they are finished.
  // The first part helps distribute work to tasks waiting and doing nothing, and the
  // last part helps us know when we can declare that work stealing is finished.
  var workStealingStatus : [{0 .. #here.maxTaskPar}] atomic int;
  var workStolen : [{0 .. #here.maxTaskPar}] LocalWorkQueueSegmentBlock(eltType);
  var workStealingIdx : atomic int;

  // Number of elements currently in the list to allow faster determinism of whether
  // or not an operation should attempt this bucket.
  var nElems : atomic uint;

  inline proc isEmpty {
    return nElems.read() == 0;
  }
}

class LocalWorkQueue : Queue {
  // Place in own cache line
  var startIdxEnq : atomic uint;
  var startIdxDeq : atomic uint;
  var workStealer : atomic int;

  var maxParallelSegmentSpace = {0 .. #here.maxTaskPar};
  var segments : [maxParallelSegmentSpace] LocalWorkQueueSegment(eltType);

  const maxIterationsPerPhase = here.maxTaskPar;

  proc LocalWorkQueue(type eltType) {
    workStealer.write(-1);
  }

  /*
    Enqueue...
  */

  proc enqueue(elt : eltType) {
    var startIdx = startIdxEnq.fetchAdd(1) % here.maxTaskPar : uint;
    var segmentIdx = enqueueAcquire(startIdx);
    ref seg = segments[segmentIdx];
    var block = seg.tailBlock;

    // Empty?
    if block == nil then {
      seg.tailBlock = new LocalWorkQueueSegmentBlock(eltType);
      seg.headBlock = seg.tailBlock;
    }

    // Full?
    if block.idx > ELEMS_PER_BLOCK {
      block.next = new LocalWorkQueueSegmentBlock(eltType);
      seg.tailBlock = block.next;
      block = block.next;
    }

    block.elems[block.idx] = elt;
    block.idx = block.idx + 1;

    seg.nElems.add(1);
    seg.status.write(UNLOCKED);
  }

  proc enqueueAcquire(startIdx) : int {
    var retval = enqueueAcquirePhase1(startIdx);
    if retval == -1 {
      retval = enqueueAcquirePhase2(startIdx);
    }

    return retval;
  }

  // In Phase 1, we attempt to acquire the first bucket that is unlocked
  inline proc enqueueAcquirePhase1(startIdx) : int {
    var idx = startIdx : int;
    var iterations = 0;

    // Find first free bucket
    while iterations < maxIterationsPerPhase {
      ref seg = segments[idx];

      // Attempt to acquire...
      if seg.status.compareExchangeStrong(UNLOCKED, ENQUEUE) {
        return idx;
      }

      iterations = iterations + 1;
      idx = (idx + 1) % here.maxTaskPar;
    }

    return -1;
  }

  // In Phase 2, we attempt to acquire the first bucket that is locked...
  inline proc enqueueAcquirePhase2(startIdx) : int {
    var idx = startIdx : int;

    // Find first free bucket
    while true {
      ref seg = segments[idx];

      // Attempt to acquire...
      while true {
        var currStatus = seg.status.read();

        select currStatus {
          when UNLOCKED do {
            if seg.status.compareExchangeStrong(UNLOCKED, ENQUEUE) {
              return idx;
            }
          }
          // If someone is stealing, then that means that we'd be waiting for a while
          // anyway, so we may as well attempt to find a node with a shorter wait time.
          when STEALING || STEALING_INIT {
            break;
          }
        }

        // TODO: Implement exponential backoff + yielding
        chpl_task_yield();
      }

      idx = (idx + 1) % here.maxTaskPar;
    }

    halt("Broke out of phase 2 without getting segment...");
  }

  /*
    Dequeue...
  */

  proc dequeue() : (bool, eltType) {
    var startIdx = startIdxDeq.fetchAdd(1) % here.maxTaskPar : uint;

  }

  // Phase 1: acquire unlocked bucket which contains elements
  inline proc dequeueAcquirePhase1(startIdx) : int {
    var idx = startIdx : int;
    var maxIterations = here.maxTaskPar;
    var iterations = 0;

    // Find first non-empty free bucket
    while iterations < maxIterations {
      ref seg = segments[idx];

      // Attempt to acquire...
      if seg.nElems.peek() != 0 && seg.status.compareExchangeStrong(UNLOCKED, DEQUEUE) {
        return idx;
      }

      iterations = iterations + 1;
      idx = (idx + 1) % here.maxTaskPar;
    }

    return -1;
  }

  // Phase 2: acquire locked bucket which contains elements
  inline proc dequeueAcquirePhase2(startIdx) : int {
    var idx = startIdx : int;
    var maxIterations = here.maxTaskPar;
    var iterations = 0;

    // Find first non-empty bucket
    while iterations < maxIterations {
      ref seg = segments[idx];

      // Attempt to acquire...
      while seg.nElems.read() != 0 {
        var currStatus = seg.status.read();

        select currStatus {
          // Quick acquire
          when UNLOCKED {
            if seg.status.compareExchangeStrong(UNLOCKED, DEQUEUE) {
              return idx;
            }
          }
          // This segment is being stolen from. Assist the current stealer.
          when VICTIM {
            // TODO
          }
          // If someone else is stealing work, we add ourself as a potential helper
          // to help speed this along, and hopefully we get an element for ourself.
          when STEALING || STEALING_INIT {
            // TODO
          }
        }

        // Backoff
        chpl_task_yield();
      }

      iterations = iterations + 1;
      idx = (idx + 1) % here.maxTaskPar;
    }

    return -1;
  }

  // Phase 3: Take any bucket whether it is locked or unlocked, empty or non-empty
  inline proc dequeueAcquirePhase3(startIdx) : (bool, eltType) {
    ref seg = segments[startIdx];

    // Attempt to acquire...
    while true {
      var backoff = 0;
      var currStatus = seg.status.read();

      select currStatus {
        // Quick acquire
        when UNLOCKED {
          if seg.status.compareExchangeStrong(UNLOCKED, DEQUEUE) {
            return idx;
          }
        }
        // This segment is being stolen from. Assist the current stealer.
        when VICTIM {
          // TODO
          backoff = 0;
        }
        // If someone else is stealing work, we add ourself as a potential helper
        // to help speed this along, and hopefully we get an element for ourself.
        when STEALING || STEALING_INIT {
          var (hasElem, elem) = helpStealWork(startIdx);

          // We have our element...
          if hasElem then return (hasElem, elem);

          // We don't have our element, but no element currently exists.
          if seg.isEmpty {
            return (false, _defaultOf(eltType));
          }
          backoff = 0;
        }
      }

      // Backoff to maximum...
      if backoff == 0 then chpl_task_yield();
      else sleep(max(1000, 2 ** backoff), TimeUnits.microseconds);
    }

    halt("Somehow exited loop in Phase 3...");
  }

  /*
    Work Stealing
  */

  // We have the lock but it is empty, try to set ourself as work stealer...
  // As our index can change based on whether or not we become the work stealer
  // or help someone else become the work stealer, we return our acquired index.
  proc stealWork(idx) : (bool, eltType) {
    ref seg = segments[idx];

    // If we set ourself as work stealer, we may proceed, otherwise if someone else
    // is set, we help them out instead...
    while true {
      if workStealer.compareExchangeStrong(-1, idx) then break;

      // Someone else is set...
      var currStealer = workStealer.read();
      if currStealer != -1 {
        seg.status.write(UNLOCKED);
        return helpStealWork(currStealer);
      }
    }

    // We are the current work stealer, so our responsibility is to initialize
    // all work statuses and signal that we may begin our work stealing
    for status in seg.workStealingStatus do status.write(INITIALIZED);
    seg.workStealingIdx.write(0);
    seg.status.write(STEALING_INIT);

    // Help steal work with everyone else
    var (hasElem, elem) = helpStealWork(idx);

    // We are finished, all work stolen (if any) are now available...
    // As work stealer, our next job is merge it all together...
    for i in 0..#here.maxTaskPar {
      // Spin until they are finished... if they finish but have no work, skip them
      while seg.workStealingStatus[i].read() == CLAIMED then chpl_task_yield();
      if i == idx || seg.workStealingStatus[i].read() != FINISHED_WITH_WORK then continue;

      // TODO: Find a way copy data from one tuple to another that is faster
      var blockElems = block.idx - 1;
      seg.nElems.add(blockElems);

      ref stolenBlock = seg.workStolen[i];
      ref tailBlock = seg.tailBlock;
      while stolenBlock.idx > 0 {
        // If we haven't stolen an element by now, do so
        if !hasElem {
          elem = stolenBlock.elems[stolenBlock.idx];
          hasElem = true;
          stolenBlock.idx = stolenBlock.idx - 1;
          continue;
        }

        if tailBlock.idx > ELEMS_PER_BLOCK {
          tailBlock.next = new LocalWorkQueueSegmentBlock(eltType);
          seg.tailBlock = tailBlock.next;
          tailBlock = tailBlock.next;
        }

        tailBlock.elems[tailBlock.idx] = stolenBlock.elems[stolenBlock.idx];
        stolenBlock.idx = stolenBlock.idx - 1;
        tailBlock.idx = tailBlock.idx + 1;
      }

      delete stolenBlock;
    }

    // No longer work stealer
    workStealer.write(-1);
    seg.status.write(UNLOCKED);

    // At this point, we have successfully stolen work. Furthermore, we have
    // stolen at least one element for ourself.
    return (hasElem, elem);
  }

  proc helpStealWork(idx) : (bool, eltType) {
    ref seg = segments[idx];

    while true {
      var currStatus = seg.status.read();

      select currStatus {
        // They are still initializing...
        when STEALING {
          // Do nothing...
        }
        // They are initialized, we can now attempt to help...
        when STEALING_INIT {
          break;
        }
        // We're not stealing work...
        otherwise do return (false, _defaultOf(eltType));
      }
    }

    var (hasElem, elem) : (bool, eltType);

    // We obtain our index which tells us which segment we need to steal from
    // It should be noted that we handle the edge cases where we get preempted long
    // enough here that the current work stealing endeavor is over. In the case where
    // work stealing is finished and the next has not started, 'ourIdx' will be greater
    // than or equal to here.maxTaskPar, meaning that we would spin anyway... If the next
    // work stealing endeavor has started, but it has not yet been initialized, then technically
    // the counter would be below here.maxTaskPar, but the workStealingStatus would likely be
    // 'FINISHED_WITH_WORK' or 'FINISHED_WITH_NO_WORK'. In the case, that it is neither then we have
    // already setup everything anyway, and we can partake in the next work stealing.
    while true {
      var ourIdx = seg.workStealingIdx.fetchAdd(1);
      ref wsStatus = seg.workStealingStatus[ourIdx];

      if ourIdx >= here.maxTaskPar {
        return (hasElem, elem);
      }
      if wsStatus.compareExchangeStrong(INITIALIZED, CLAIMED) == false {
        return (hasElem, elem);
      }
      if ourIdx == idx then continue;

      // Do our part by stealing work...
      ref otherSeg = segments[ourIdx];
      while !otherSeg.isEmpty {
        if otherSeg.status.read() != UNLOCKED {
          chpl_task_yield();
        } else if otherSeg.status.compareExchangeStrong(UNLOCKED, VICTIM) {
          break;
        }
      }

      if otherSeg.isEmpty() {
        wsStatus.write(FINISHED_WITH_NO_WORK);
        otherSeg.status.write(UNLOCKED);
        continue;
      }

      if !hasElem {
        // Steal an item... adjust index if it is full (went past end)
        if otherSeg.headBlock.idx > here.maxTaskPar {
          otherSeg.headBlock.idx = otherSeg.headBlock.idx - 1;
        }
        (hasElem, elem) = (true, otherSeg.headBlock.elems[otherSeg.headBlock.idx]);
        otherSeg.headBlock.idx = otherSeg.headBlock.idx - 1;
        otherSeg.nElems.sub(1);

        // Fix list if we consumed last one...
        if otherSeg.headBlock.idx == 0 {
          var block = otherSeg.headBlock;
          otherSeg.headBlock = otherSeg.headBlock.next;
          delete block;
        }

        if otherSeg.nElems.read() == 0 {
          wsStatus.write(FINISHED_WITH_NO_WORK);
          otherSeg.status.write(UNLOCKED);
          continue;
        }
      }

      // Report what has been stolen...
      seg.workStolen[ourIdx] = otherSeg.headBlock;
      otherSeg.headBlock = otherSeg.headBlock.next;
      wsStatus.write(FINISHED_WITH_WORK);
      otherSeg.status.write(UNLOCKED);
    }
  }
}

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

  // Obtain average time for enqueue followed by dequeued...
  for i in 1 .. nTrials {
    var queue = new LocalWorkQueue(int);
    // 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 / timer.elapsed();
    writeln(i, "/", nTrials, ": ", (+ reduce enqueueTrialTime) / i);
    timer.clear();
    delete queue;
  }
}


Wording for implicit addition; it's not really its own syntax, just a feature

/*

  Associative Primer

  This is a tutorial on Chapel's associative domains and arrays.

  It may be beneficial to read the 'arrays.chpl' and 'domains.chpl'
  primers before proceeding if you're not already familiar with Chapel's
  domains and arrays.

*/

//
// Associative domains are commonly used to represent arbitrary sets, or to
// represent the set of keys for a dictionary/hash table.
//
// The current implementation supports most Chapel types, and will eventually
// support any language-defined type.
//
{
  var A : domain(int);     // a domain of integers
  var B : domain(string);  // a domain of strings
  var C : domain(real);    // a domain of reals

  class X {
    var x : int;
  }

  //
  // A domain whose indices are classes of type 'X'. The indices are hashed on
  // identity.
  //
  var D : domain(X);

  record Y {
    var y : real;
  }

  //
  // A domain whose indices are records of type 'Y'. The indices are hashed on
  // value.
  //
  var E : domain(Y);
}

//
// Below, 'Names' is an associative domain of strings.  Associative domains
// start out empty (no indices).
//
var Names: domain(string);
writeln("An empty associative domain: ", Names);
writeln();

//
// We can also use the domain-literal syntax to create an associative domain.
//
// Below, 'Days' is an associative domain of strings.
//
var Days = {"Sunday", "Wednesday", "Saturday"};

//
// Duplicate indices do not exist in associative domains. This means that if
// we have duplicate indices in a domain-literal, then only one of the
// duplicate indices will be present in the domain.
//
// In the example below, "Sunday" will only occur once in the 'DuplicateDays'
// domain. This makes 'DuplicateDays' equivalent to 'Days'.
//

var DuplicateDays = {"Sunday", "Sunday", "Wednesday", "Saturday"};

if Days != DuplicateDays then
  halt("Days and DuplicateDays should be equivalent");

//
// The '+=' operator can be used to add indices to an associative domain.
//
Names += "Alice";
Names += "Bob";
Names += "Clark";
Names += "Dana";

//
// The 'add' method is equivalent to '+='
//
// The order in which indices are printed is not guaranteed for associative
// domains.
//
Names.add("Ellen");
Names.add("Frank");
writeln("A populated associative domain of strings: ", Names);
writeln();

//
// Below we can add a range of integers with the '+=' or 'add' operators
// because of 'promotion'. Those functions take integer arguments, and because
// a range consists of integers the Chapel compiler is able to 'promote' the
// code to handle iteration and invokation of the function with the range's
// values as arguments.
//
// Promotion works with common Chapel types like ranges, domains, and arrays.
//
var DaysInJune : domain(int);
DaysInJune += 1..30;

//
// The '-=' operator is used to remove indices from an associative domain.
// The 'remove' method performs the same function.
//
Names -= "Bob";
Names += "Robert";
Names.remove("Frank");
writeln("Removed 'Bob' and 'Frank', added 'Robert': ", Names);
writeln();

//
// If we want to check that "Bob" and "Frank" were indeed removed we can use the
// 'member' function, which returns a boolean.
//
if Names.member("Bob") then
  halt("Error: Bob should have been removed!");
if Names.member("Frank") then
  halt("Error: Frank should have been removed!");

//
// Like all other domains, we can use associative domains to define an array.
// 'Scores' is the Chapel-equivalent of a "map", "dictionary", or "hash table"
// found in other languages. Where 'Names' provides the keys, 'Scores' provides
// the values.
//
// Scores: an associative array of strings to integers.
//
var Scores : [Names] int;

//
// Like other arrays, we can initialize associative arrays with a default value
// when declared.
//
var HoursInDay : [Days] int = 24;
for hours in HoursInDay do
  if hours != 24 then
    halt("HoursInDay's values should have all been initialized to '24'");

//
// We could also use the array-literal syntax to create an associative
// array.
//
// Below, 'DaysInMonth' is an associative array of strings to integers.
//
var DaysInMonth = ["June" => 30, "January" => 31, "September" => 30];

//
// All array elements are initialized to the default value for that type.
//
writeln("Our 'Scores' associative array: ", Scores);

//
// Printing an array only prints its values. Let's write a function that
// prints our associative array in a more human-readable format.
//

//
// Given an array, print in the following format:
// [ idx => val, ... ]
//
// We'll use the 'sorted' iterator to print in a consistent order. Otherwise,
// the order in which indices are yielded is nondeterministic.
//
proc prettyPrint(arr : [?dom]) {
  write("[ ");
  var first = true;
  for k in dom.sorted() {
    if !first {
      write(", ", k, " => ", arr[k]);
    } else {
      write(k, " => ", arr[k]);
      first = false;
    }
  }
  writeln(" ]");
}

//
// Using our new routine, print our array.
//
write("Our 'Scores' array, pretty-printed: ");
prettyPrint(Scores);
writeln();

//
// Let's initialize our 'Scores' array.
//
// Traditional array operations like whole-array assignment and indexing
// are supported for associative arrays.
//
Scores = 100; // every value in 'Scores' is now '100'.

Scores["Robert"] = 33;
Scores["Alice"] = 42;
Scores["Dana"] = 91;
write("Our 'Scores' array initialized with some values: ");
prettyPrint(Scores);
writeln();

//
// Like other Chapel arrays, we can use reductions.
//
writeln("The average score is ", (+ reduce Scores) / Scores.size);
writeln();

//
// Because Chapel arrays are updated when their domain is changed, we can
// add indices after the array is declared. When we add an index to the
// defining domain, the value in the array is initialized to the default
// value for that type.
//
Names += "John";
if Scores["John"] != 0 then
  halt("Scores[\"John\"] should have been initialized to 0.");

//
// Once added to the 'Names' domain, we can use it like any other index in
// the array.
//
Scores["John"] = 76;

//
// We can also implicitly add indices to an associative array if it is the
// only array defined over its domain.
//
// Here, the string "Gary" is added as an index, and the corresponding
// array element is initialized to 10.
//
// The first line of code below is equivalent to the following:
//   Names += "Gary";
//   Scores["Gary"] = 10;
//
Scores["Gary"] = 10;
write("Our updated 'Scores' array after adding some more names: ");
prettyPrint(Scores);
writeln();

//
// We can also verify that the underlying 'Names' domain was modified:
//
if !Names.member("Gary") then
  halt("Error: 'Gary' should be present in the 'Names' domain");

//
// If we were to define another array with 'Names' as the backing domain,
// then we could no longer use this convenient feature.
//
var Ages : [Names] int;

//
// Here, 'Ages and Scores' are backed by the same associative domain: 'Names'.
// We can no longer write something like
//   Ages["Mark"] = 33;
// or
//   Score["Sam"] = 42;
// if "Mark" or "Sam" are not valid indices in the 'Names' domain.
//

//
// This restriction exists because Chapel binds arrays to their domains for
// their lifetime to support semantic reasoning by the compiler and users.
// Extending the array via indexing will modify its domain, which would in
// turn modify the keys and values of any other arrays sharing that domain.
//
// We consider this behavior to be too surprising to support ("I modified
// 'Ages', why did 'Scores' change as well?"). Thus, to extend an array which
// shares its domain, the user is required to modify the domain directly.
// By modifying such a domain, it should be less surprising that arrays
// bound to that domain are affected.
//
Names.add("Mark");
Ages["Mark"] = 33;
Scores["Mark"] = 81;

//
// The program will still halt if one tries to access an index not in the
// domain:
//   writeln(Scores["Sally"]);
//

//
// Set operations are available only on associative domains and arrays.
// These operations work with any supported index type.
//
// The supported set operations are:
//   Union (| or +)
//   Intersection (&)
//   Difference (-)
//   Symmetric Difference (^)
//
// For other array and domain types, these operators are promoted unless
// otherwise stated.
//

var primeDom = {2, 3, 5, 7, 11, 13, 17};  // some prime numbers
var fibDom   = {0, 1, 1, 2, 3, 5, 8, 13}; // part of the fibonnaci sequence

var primeAndFib = primeDom & fibDom;
writeln("Some primes in the fibonnaci sequence: ", primeAndFib);
writeln("Some primes not in the fibonnaci sequence: ", primeDom - primeAndFib);
writeln();

var Women = {"Alice", "Dana", "Ellen"};
var Men = Names - Women;

writeln("Women = ", Women);
writeln("Men = ", Men);
writeln();

if (Men | Women) != Names then
  halt("The union of the 'Men' and 'Women' sets should be equivalent to 'Names'");

//
// Let's create some new associative arrays.
//
var AboveFifty : domain(string);
for (name, score) in zip(Names, Scores) do
  if score > 50 then AboveFifty.add(name);

var Passing : [AboveFifty] bool = true;
var Failing : [Names - AboveFifty] bool = false;

//
// 'PassedClass' is a new associative array with its own domain. While it currently
// happens to share the same indices as the 'Names' domain, they are not the
// same domain object. This means we can use the implicit index addition
// feature to add to the 'PassedClass' array.
//
var PassedClass = Passing | Failing;
PassedClass["Nancy"] = false;
write("Who had a passing grade?\n\t");
prettyPrint(PassedClass);
writeln();

if Names.member("Nancy") then
  halt("Error: 'Names' domain should not have been modified by adding an index to 'PassedClass'");

//
// Let's use our 'primes' and 'fibs' domains to define associative arrays.
//
var Primes : [primeDom] bool;
var Fibs : [fibDom] bool = true;

//
// 'IsFib' is an associative array of integers to bools, where the boolean
// value is true if the number is in the fibonnaci sequence. When performing
// a union between two arrays with overlapping indices, the values of the
// second array take precedence.
//
var IsFib  = Primes | Fibs;
write("Which numbers are in the fibonnaci sequence?\n\t");
prettyPrint(IsFib);
writeln();

//
// op= variants of set operations are supported for associative arrays:
//
// Union: +=, |=
// Intersection: &=
// Difference: -=
// Symmetric Difference: ^=
//
// However, this is only permitted when the array's domain is not shared with
// other arrays.
//
// Let's construct an array identical to 'PassedClass' using |=.
//
var PC : [Names - AboveFifty] bool = false;
PC |= Passing;
PC["Nancy"] = false;

for (a, b) in zip(PassedClass, PC) do
  if a != b then
    halt("PassedClass and PC are different, but shouldn't be.");

//
// Future Directions
//
// Today, associative domains cannot be distributed across multiple locales.
// A prototype domain map exists, and if users are interested more effort can
// be made to move from a prototype to a polished feature.
//

/*

  Associative Primer

  This is a tutorial on Chapel's associative domains and arrays.

  It may be beneficial to read the 'arrays.chpl' and 'domains.chpl'
  primers before proceeding if you're not already familiar with Chapel's
  domains and arrays.

*/

//
// Associative domains are commonly used to represent arbitrary sets, or to
// represent the set of keys for a dictionary/hash table.
//
// The current implementation supports most Chapel types, and will eventually
// support any language-defined type.
//
{
  var A : domain(int);     // a domain of integers
  var B : domain(string);  // a domain of strings
  var C : domain(real);    // a domain of reals

  class X {
    var x : int;
  }

  //
  // A domain whose indices are classes of type 'X'. The indices are hashed on
  // identity.
  //
  var D : domain(X);

  record Y {
    var y : real;
  }

  //
  // A domain whose indices are records of type 'Y'. The indices are hashed on
  // value.
  //
  var E : domain(Y);
}

//
// Below, 'Names' is an associative domain of strings.  Associative domains
// start out empty (no indices).
//
var Names: domain(string);
writeln("An empty associative domain: ", Names);
writeln();

//
// We can also use the domain-literal syntax to create an associative domain.
//
// Below, 'Days' is an associative domain of strings.
//
var Days = {"Sunday", "Wednesday", "Saturday"};

//
// Duplicate indices do not exist in associative domains. This means that if
// we have duplicate indices in a domain-literal, then only one of the
// duplicate indices will be present in the domain.
//
// In the example below, "Sunday" will only occur once in the 'DuplicateDays'
// domain. This makes 'DuplicateDays' equivalent to 'Days'.
//

var DuplicateDays = {"Sunday", "Sunday", "Wednesday", "Saturday"};

if Days != DuplicateDays then
  halt("Days and DuplicateDays should be equivalent");

//
// The '+=' operator can be used to add indices to an associative domain.
//
Names += "Alice";
Names += "Bob";
Names += "Clark";
Names += "Dana";

//
// The 'add' method is equivalent to '+='
//
// The order in which indices are printed is not guaranteed for associative
// domains.
//
Names.add("Ellen");
Names.add("Frank");
writeln("A populated associative domain of strings: ", Names);
writeln();

//
// Below we can add a range of integers with the '+=' or 'add' operators
// because of 'promotion'. Those functions take integer arguments, and because
// a range consists of integers the Chapel compiler is able to 'promote' the
// code to handle iteration and invokation of the function with the range's
// values as arguments.
//
// Promotion works with common Chapel types like ranges, domains, and arrays.
//
var DaysInJune : domain(int);
DaysInJune += 1..30;

//
// The '-=' operator is used to remove indices from an associative domain.
// The 'remove' method performs the same function.
//
Names -= "Bob";
Names += "Robert";
Names.remove("Frank");
writeln("Removed 'Bob' and 'Frank', added 'Robert': ", Names);
writeln();

//
// If we want to check that "Bob" and "Frank" were indeed removed we can use the
// 'member' function, which returns a boolean.
//
if Names.member("Bob") then
  halt("Error: Bob should have been removed!");
if Names.member("Frank") then
  halt("Error: Frank should have been removed!");

//
// Like all other domains, we can use associative domains to define an array.
// 'Scores' is the Chapel-equivalent of a "map", "dictionary", or "hash table"
// found in other languages. Where 'Names' provides the keys, 'Scores' provides
// the values.
//
// Scores: an associative array of strings to integers.
//
var Scores : [Names] int;

//
// Like other arrays, we can initialize associative arrays with a default value
// when declared.
//
var HoursInDay : [Days] int = 24;
for hours in HoursInDay do
  if hours != 24 then
    halt("HoursInDay's values should have all been initialized to '24'");

//
// We could also use the array-literal syntax to create an associative
// array.
//
// Below, 'DaysInMonth' is an associative array of strings to integers.
//
var DaysInMonth = ["June" => 30, "January" => 31, "September" => 30];

//
// All array elements are initialized to the default value for that type.
//
writeln("Our 'Scores' associative array: ", Scores);

//
// Printing an array only prints its values. Let's write a function that
// prints our associative array in a more human-readable format.
//

//
// Given an array, print in the following format:
// [ idx => val, ... ]
//
// We'll use the 'sorted' iterator to print in a consistent order. Otherwise,
// the order in which indices are yielded is nondeterministic.
//
proc prettyPrint(arr : [?dom]) {
  write("[ ");
  var first = true;
  for k in dom.sorted() {
    if !first {
      write(", ", k, " => ", arr[k]);
    } else {
      write(k, " => ", arr[k]);
      first = false;
    }
  }
  writeln(" ]");
}

//
// Using our new routine, print our array.
//
write("Our 'Scores' array, pretty-printed: ");
prettyPrint(Scores);
writeln();

//
// Let's initialize our 'Scores' array.
//
// Traditional array operations like whole-array assignment and indexing
// are supported for associative arrays.
//
Scores = 100; // every value in 'Scores' is now '100'.

Scores["Robert"] = 33;
Scores["Alice"] = 42;
Scores["Dana"] = 91;
write("Our 'Scores' array initialized with some values: ");
prettyPrint(Scores);
writeln();

//
// Like other Chapel arrays, we can use reductions.
//
writeln("The average score is ", (+ reduce Scores) / Scores.size);
writeln();

//
// Because Chapel arrays are updated when their domain is changed, we can
// add indices after the array is declared. When we add an index to the
// defining domain, the value in the array is initialized to the default
// value for that type.
//
Names += "John";
if Scores["John"] != 0 then
  halt("Scores[\"John\"] should have been initialized to 0.");

//
// Once added to the 'Names' domain, we can use it like any other index in
// the array.
//
Scores["John"] = 76;

//
// We can also implicitly add indices to an associative array if it is the
// only array defined over its domain.
//
// Here, the string "Gary" is added as an index, and the corresponding
// array element is initialized to 10.
//
// The first line of code below is equivalent to the following:
//   Names += "Gary";
//   Scores["Gary"] = 10;
//
Scores["Gary"] = 10;
write("Our updated 'Scores' array after adding some more names: ");
prettyPrint(Scores);
writeln();

//
// We can also verify that the underlying 'Names' domain was modified:
//
if !Names.member("Gary") then
  halt("Error: 'Gary' should be present in the 'Names' domain");

//
// If we were to define another array with 'Names' as the backing domain,
// then we could no longer use this convenient syntax.
//
var Ages : [Names] int;

//
// Here, 'Ages and Scores' are backed by the same associative domain: 'Names'.
// We can no longer write something like
//   Ages["Mark"] = 33;
// or
//   Score["Sam"] = 42;
// if "Mark" or "Sam" are not valid indices in the 'Names' domain.
//

//
// This restriction exists because Chapel binds arrays to their domains for
// their lifetime to support semantic reasoning by the compiler and users.
// Extending the array via indexing will modify its domain, which would in
// turn modify the keys and values of any other arrays sharing that domain.
//
// We consider this behavior to be too surprising to support ("I modified
// 'Ages', why did 'Scores' change as well?"). Thus, to extend an array which
// shares its domain, the user is required to modify the domain directly.
// By modifying such a domain, it should be less surprising that arrays
// bound to that domain are affected.
//
Names.add("Mark");
Ages["Mark"] = 33;
Scores["Mark"] = 81;

//
// The program will still halt if one tries to access an index not in the
// domain:
//   writeln(Scores["Sally"]);
//

//
// Set operations are available only on associative domains and arrays.
// These operations work with any supported index type.
//
// The supported set operations are:
//   Union (| or +)
//   Intersection (&)
//   Difference (-)
//   Symmetric Difference (^)
//
// For other array and domain types, these operators are promoted unless
// otherwise stated.
//

var primeDom = {2, 3, 5, 7, 11, 13, 17};  // some prime numbers
var fibDom   = {0, 1, 1, 2, 3, 5, 8, 13}; // part of the fibonnaci sequence

var primeAndFib = primeDom & fibDom;
writeln("Some primes in the fibonnaci sequence: ", primeAndFib);
writeln("Some primes not in the fibonnaci sequence: ", primeDom - primeAndFib);
writeln();

var Women = {"Alice", "Dana", "Ellen"};
var Men = Names - Women;

writeln("Women = ", Women);
writeln("Men = ", Men);
writeln();

if (Men | Women) != Names then
  halt("The union of the 'Men' and 'Women' sets should be equivalent to 'Names'");

//
// Let's create some new associative arrays.
//
var AboveFifty : domain(string);
for (name, score) in zip(Names, Scores) do
  if score > 50 then AboveFifty.add(name);

var Passing : [AboveFifty] bool = true;
var Failing : [Names - AboveFifty] bool = false;

//
// 'PassedClass' is a new associative array with its own domain. While it currently
// happens to share the same indices as the 'Names' domain, they are not the
// same domain object. This means we can use the implicit index addition
// syntax to add to the 'PassedClass' array.
//
var PassedClass = Passing | Failing;
PassedClass["Nancy"] = false;
write("Who had a passing grade?\n\t");
prettyPrint(PassedClass);
writeln();

if Names.member("Nancy") then
  halt("Error: 'Names' domain should not have been modified by adding an index to 'PassedClass'");

//
// Let's use our 'primes' and 'fibs' domains to define associative arrays.
//
var Primes : [primeDom] bool;
var Fibs : [fibDom] bool = true;

//
// 'IsFib' is an associative array of integers to bools, where the boolean
// value is true if the number is in the fibonnaci sequence. When performing
// a union between two arrays with overlapping indices, the values of the
// second array take precedence.
//
var IsFib  = Primes | Fibs;
write("Which numbers are in the fibonnaci sequence?\n\t");
prettyPrint(IsFib);
writeln();

//
// op= variants of set operations are supported for associative arrays:
//
// Union: +=, |=
// Intersection: &=
// Difference: -=
// Symmetric Difference: ^=
//
// However, this is only permitted when the array's domain is not shared with
// other arrays.
//
// Let's construct an array identical to 'PassedClass' using |=.
//
var PC : [Names - AboveFifty] bool = false;
PC |= Passing;
PC["Nancy"] = false;

for (a, b) in zip(PassedClass, PC) do
  if a != b then
    halt("PassedClass and PC are different, but shouldn't be.");

//
// Future Directions
//
// Today, associative domains cannot be distributed across multiple locales.
// A prototype domain map exists, and if users are interested more effort can
// be made to move from a prototype to a polished feature.
//