docs: public Frame subRef( Frame fr, String key ) {
      if( fr == null ) return null;
      Futures fs = new Futures();
      for( Vec vec : fr.vecs() ) subRef(vec,fs);
      fs.blockForPending();
      return null;
    }
    // Lower refcounts on all vecs captured in the inner environment
    public ASTOp subRef( ASTOp op ) {
      if( op == null ) return null;
      if( !(op instanceof ASTFunc) ) return null;
      ASTFunc fcn = (ASTFunc)op;
      if( fcn._env != null ) fcn._env.subRef(this);
      else Log.info("Popping fcn object, never executed no environ capture");
      return null;
    }
  
    Vec addRef( Vec vec ) {
      IcedInt I = _refcnt.get(vec);
      assert I==null || I._val>0;
      assert vec.length() == 0 || vec.isUUID() || (vec.at(0) > 0 || vec.at(0) <= 0 || Double.isNaN(vec.at(0)));
      _refcnt.put(vec,new IcedInt(I==null?1:I._val+1));
      if (vec.masterVec()!=null) addRef(vec.masterVec());
      return vec;
    }
    // Add a refcnt to all vecs in this frame
    Frame addRef( Frame fr ) {
      if( fr == null ) return null;
      for( Vec vec : fr.vecs() ) addRef(vec);
      return fr;
    }
    ASTOp addRef( ASTOp op ) {
      if( op == null ) return null;
      if( !(op instanceof ASTFunc) ) return op;
      ASTFunc fcn = (ASTFunc)op;
      if( fcn._env != null ) fcn._env.addRef(this);
      else Log.info("Pushing fcn object, never executed no environ capture");
      return op;
    }
    private void addRef(Env global) {
      for( int i=0; i<_sp; i++ ) {
        if( _ary[i] != null ) global.addRef(_ary[i]);
        if( _fcn[i] != null ) global.addRef(_fcn[i]);
      }
    }
    private void subRef(Env global) {
      for( int i=0; i<_sp; i++ ) {
        if( _ary[i] != null ) global.subRef(_ary[i],_key[i]);
        if( _fcn[i] != null ) global.subRef(_fcn[i]);
      }
    }
  
  
    // Remove everything
    public void remove_and_unlock() {
      // Remove all shallow scopes
      while( _tod > 0 ) popScope();
      // Push changes at the outer scope into the K/V store
      while( _sp > 0 ) {
        if( isAry() && _key[_sp-1] != null ) { // Has a K/V mapping?
          Frame fr = popAry();    // Pop w/o lowering refcnt
          String skey = key();
          Frame fr2=new Frame(Key.make(skey),fr._names.clone(),fr.vecs().clone());
          for( int i=0; i<fr.numCols(); i++ ) {
            Vec v = fr.vecs()[i];
            int refcnt = _refcnt.get(v)._val;
            assert refcnt > 0;
            if( refcnt > 1 ) {    // Need a deep-copy now
              Vec v2 = new Frame(v).deepSlice(null,null).vecs()[0];
              fr2.replace(i,v2);  // Replace with private deep-copy
              subRef(v);     // Now lower refcnt for good assertions
              addRef(v2);
            } // But not down to zero (do not delete items in global scope)
          }
          if( _locked.contains(fr2._key) ) fr2.write_lock(null);     // Upgrade to write-lock
          else { fr2.delete_and_lock(null); _locked.add(fr2._key); } // Clear prior & set new data
          fr2.unlock(null);
          _locked.remove(fr2._key); // Unlocked already
        } else {
          popUncheck();
        }
      }
      // Unlock all things that do not survive, plus also delete them
      for( Key k : _locked ) {
        Frame fr = UKV.get(k);
        fr.unlock(null);  fr.delete(); // Should be atomic really
      }
    }
  
    // Done writing into all things.  Allow rollups.
    public void postWrite() {
      for( Vec vec : _refcnt.keySet() )
        vec.postWrite();
    }
  
    // Count references the "hard way" - used to check refcnting math.
    int compute_refcnt( Vec vec ) {
      int cnt=0;
      HashSet<Vec> refs = new HashSet<Vec>();
      for( int i=0; i<_sp; i++ )
        if( _ary[i] != null) {
          for (Vec v : _ary[i].vecs()) {
            Vec vm;
            if (v.equals(vec)) cnt++;
            else if ((vm = v.masterVec()) !=null && vm.equals(vec)) cnt++;
          }
        }
        else if( _fcn[i] != null && (_fcn[i] instanceof ASTFunc) )
          cnt += ((ASTFunc)_fcn[i])._env.compute_refcnt(vec);
      return cnt + refs.size();
    }
    boolean check_refcnt( Vec vec ) {
      IcedInt I = _refcnt.get(vec);
      int cnt0 = I==null ? 0 : I._val;
      int cnt1 = compute_refcnt(vec);
      if( cnt0==cnt1 ) return true;
      Log.err("Refcnt is "+cnt0+" but computed as "+cnt1);
      return false;
    }
  
    boolean check_all_refcnts() {
      for (Vec v : _refcnt.keySet())
        if (check_refcnt(v) == false)
          return false;
      return true;
    }
  
    // Pop and return the result as a string
    public String resultString( ) {
      assert _tod==0 : "Still have lexical scopes past the global";
      String s = toString(_sp-1,true);
      pop();
      return s;
    }
  
    public String toString(int i, boolean verbose_fcn) {
      if( _ary[i] != null ) return _ary[i]._key+":"+_ary[i].numRows()+"x"+_ary[i].numCols();
      else if( _fcn[i] != null ) return _fcn[i].toString(verbose_fcn);
      else if( _str[i] != null ) return _str[i];
      return Double.toString(_d[i]);
    }
    @Override public String toString() {
      String s="{";
      for( int i=0; i<_sp; i++ )   s += toString(i,false)+",";
      return s+"}";
    }
  }