# Global Metrics

path: .metrics.cyclomatic.average
old: 1.1428571428571428
new: 5.320754716981132
path: .metrics.cyclomatic.sum
old: 8.0
new: 282.0
path: .metrics.halstead.effort
old: 4256.48269608278
new: 8710177.568448305
path: .metrics.halstead.estimated_program_length
old: 183.90099886735345
new: 5373.464165518031
path: .metrics.halstead.level
old: 0.12083333333333332
new: 0.007875012304706726
path: .metrics.halstead.time
old: 236.4712608934878
new: 483898.7538026836
path: .metrics.halstead.purity_ratio
old: 1.9156354048682651
new: 0.7242841576382304
path: .metrics.halstead.length
old: 96.0
new: 7419.0
path: .metrics.halstead.vocabulary
old: 41.0
new: 607.0
path: .metrics.halstead.n2
old: 29.0
new: 560.0
path: .metrics.halstead.volume
old: 514.324992443336
new: 68592.7555277109
path: .metrics.halstead.n1
old: 12.0
new: 47.0
path: .metrics.halstead.N2
old: 40.0
new: 3026.0
path: .metrics.halstead.bugs
old: 0.08754795245844293
new: 14.111182797575804
path: .metrics.halstead.N1
old: 56.0
new: 4393.0
path: .metrics.halstead.difficulty
old: 8.275862068965518
new: 126.98392857142858
path: .metrics.cognitive.sum
old: 0.0
new: 216.0
path: .metrics.cognitive.average
old: 0.0
new: 4.5
path: .metrics.mi.mi_visual_studio
old: 43.06997857773964
new: 0.0
path: .metrics.mi.mi_sei
old: 63.573019774799235
new: -122.31349461571514
path: .metrics.mi.mi_original
old: 73.64966336793478
new: -75.5446814462194
path: .metrics.nexits.average
old: 0.0
new: 1.875
path: .metrics.nexits.sum
old: 0.0
new: 90.0
path: .metrics.nargs.sum
old: 1.0
new: 87.0
path: .metrics.nargs.average
old: 1.0
new: 1.8125
path: .metrics.loc.sloc
old: 49.0
new: 2081.0
path: .metrics.loc.blank
old: 12.0
new: 239.0
path: .metrics.loc.ploc
old: 27.0
new: 1369.0
path: .metrics.loc.lloc
old: 0.0
new: 690.0
path: .metrics.loc.cloc
old: 10.0
new: 473.0
path: .metrics.nom.total
old: 1.0
new: 48.0
path: .metrics.nom.functions
old: 1.0
new: 48.0

# Spaces Data

## Minimal test - lines (34, 2081)

path: .spaces[0].metrics.mi.mi_original
old: 141.92938490684145
new: -75.02830859594827
path: .spaces[0].metrics.mi.mi_visual_studio
old: 82.99964029639851
new: 0.0
path: .spaces[0].metrics.mi.mi_sei
old: 129.26360748831726
new: -121.74381501523662
path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 216.0
path: .spaces[0].metrics.cognitive.average
old: null
new: 4.5
path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 5.403846153846154
path: .spaces[0].metrics.cyclomatic.sum
old: 2.0
new: 281.0
path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 87.0
path: .spaces[0].metrics.nargs.average
old: null
new: 1.8125
path: .spaces[0].metrics.halstead.length
old: 4.0
new: 7405.0
path: .spaces[0].metrics.halstead.difficulty
old: 1.0
new: 129.40036563071297
path: .spaces[0].metrics.halstead.bugs
old: 0.0013333333333333333
new: 14.239496142195392
path: .spaces[0].metrics.halstead.level
old: 1.0
new: 0.007727953434488995
path: .spaces[0].metrics.halstead.n1
old: 2.0
new: 47.0
path: .spaces[0].metrics.halstead.time
old: 0.4444444444444444
new: 490513.890528447
path: .spaces[0].metrics.halstead.N2
old: 2.0
new: 3012.0
path: .spaces[0].metrics.halstead.n2
old: 2.0
new: 547.0
path: .spaces[0].metrics.halstead.vocabulary
old: 4.0
new: 594.0
path: .spaces[0].metrics.halstead.N1
old: 2.0
new: 4393.0
path: .spaces[0].metrics.halstead.effort
old: 8.0
new: 8829250.029512046
path: .spaces[0].metrics.halstead.estimated_program_length
old: 4.0
new: 5236.247847496378
path: .spaces[0].metrics.halstead.volume
old: 8.0
new: 68232.03308952968
path: .spaces[0].metrics.halstead.purity_ratio
old: 1.0
new: 0.7071232744762158
path: .spaces[0].metrics.nom.total
old: 0.0
new: 48.0
path: .spaces[0].metrics.nom.functions
old: 0.0
new: 48.0
path: .spaces[0].metrics.loc.blank
old: 0.0
new: 237.0
path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 690.0
path: .spaces[0].metrics.loc.cloc
old: 0.0
new: 463.0
path: .spaces[0].metrics.loc.sloc
old: 3.0
new: 2048.0
path: .spaces[0].metrics.loc.ploc
old: 3.0
new: 1348.0
path: .spaces[0].metrics.nexits.average
old: null
new: 1.875
path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 90.0

### Code

    namespace lul {

    using mozilla::CheckedInt;
    using mozilla::DebugOnly;
    using mozilla::MallocSizeOf;
    using mozilla::Unused;
    using std::pair;
    using std::string;
    using std::vector;

    // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
    //
    // Some functions in this file are marked RUNS IN NO-MALLOC CONTEXT.
    // Any such function -- and, hence, the transitive closure of those
    // reachable from it -- must not do any dynamic memory allocation.
    // Doing so risks deadlock.  There is exactly one root function for
    // the transitive closure: Lul::Unwind.
    //
    // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING

    ////////////////////////////////////////////////////////////////
    // RuleSet                                                    //
    ////////////////////////////////////////////////////////////////

    static const char* NameOf_DW_REG(int16_t aReg) {
      switch (aReg) {
        case DW_REG_CFA:
          return "cfa";
    #if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
        case DW_REG_INTEL_XBP:
          return "xbp";
        case DW_REG_INTEL_XSP:
          return "xsp";
        case DW_REG_INTEL_XIP:
          return "xip";
    #elif defined(GP_ARCH_arm)
        case DW_REG_ARM_R7:
          return "r7";
        case DW_REG_ARM_R11:
          return "r11";
        case DW_REG_ARM_R12:
          return "r12";
        case DW_REG_ARM_R13:
          return "r13";
        case DW_REG_ARM_R14:
          return "r14";
        case DW_REG_ARM_R15:
          return "r15";
    #elif defined(GP_ARCH_arm64)
        case DW_REG_AARCH64_X29:
          return "x29";
        case DW_REG_AARCH64_X30:
          return "x30";
        case DW_REG_AARCH64_SP:
          return "sp";
    #elif defined(GP_ARCH_mips64)
        case DW_REG_MIPS_SP:
          return "sp";
        case DW_REG_MIPS_FP:
          return "fp";
        case DW_REG_MIPS_PC:
          return "pc";
    #else
    #  error "Unsupported arch"
    #endif
        default:
          return "???";
      }
    }

    string LExpr::ShowRule(const char* aNewReg) const {
      char buf[64];
      string res = string(aNewReg) + "=";
      switch (mHow) {
        case UNKNOWN:
          res += "Unknown";
          break;
        case NODEREF:
          SprintfLiteral(buf, "%s+%d", NameOf_DW_REG(mReg), (int)mOffset);
          res += buf;
          break;
        case DEREF:
          SprintfLiteral(buf, "*(%s+%d)", NameOf_DW_REG(mReg), (int)mOffset);
          res += buf;
          break;
        case PFXEXPR:
          SprintfLiteral(buf, "PfxExpr-at-%d", (int)mOffset);
          res += buf;
          break;
        default:
          res += "???";
          break;
      }
      return res;
    }

    void RuleSet::Print(uintptr_t avma, uintptr_t len,
                        void (*aLog)(const char*)) const {
      char buf[96];
      SprintfLiteral(buf, "[%llx .. %llx]: let ", (unsigned long long int)avma,
                     (unsigned long long int)(avma + len - 1));
      string res = string(buf);
      res += mCfaExpr.ShowRule("cfa");
      res += " in";
      // For each reg we care about, print the recovery expression.
    #if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
      res += mXipExpr.ShowRule(" RA");
      res += mXspExpr.ShowRule(" SP");
      res += mXbpExpr.ShowRule(" BP");
    #elif defined(GP_ARCH_arm)
      res += mR15expr.ShowRule(" R15");
      res += mR7expr.ShowRule(" R7");
      res += mR11expr.ShowRule(" R11");
      res += mR12expr.ShowRule(" R12");
      res += mR13expr.ShowRule(" R13");
      res += mR14expr.ShowRule(" R14");
    #elif defined(GP_ARCH_arm64)
      res += mX29expr.ShowRule(" X29");
      res += mX30expr.ShowRule(" X30");
      res += mSPexpr.ShowRule(" SP");
    #elif defined(GP_ARCH_mips64)
      res += mPCexpr.ShowRule(" PC");
      res += mSPexpr.ShowRule(" SP");
      res += mFPexpr.ShowRule(" FP");
    #else
    #  error "Unsupported arch"
    #endif
      aLog(res.c_str());
    }

    LExpr* RuleSet::ExprForRegno(DW_REG_NUMBER aRegno) {
      switch (aRegno) {
        case DW_REG_CFA:
          return &mCfaExpr;
    #if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
        case DW_REG_INTEL_XIP:
          return &mXipExpr;
        case DW_REG_INTEL_XSP:
          return &mXspExpr;
        case DW_REG_INTEL_XBP:
          return &mXbpExpr;
    #elif defined(GP_ARCH_arm)
        case DW_REG_ARM_R15:
          return &mR15expr;
        case DW_REG_ARM_R14:
          return &mR14expr;
        case DW_REG_ARM_R13:
          return &mR13expr;
        case DW_REG_ARM_R12:
          return &mR12expr;
        case DW_REG_ARM_R11:
          return &mR11expr;
        case DW_REG_ARM_R7:
          return &mR7expr;
    #elif defined(GP_ARCH_arm64)
        case DW_REG_AARCH64_X29:
          return &mX29expr;
        case DW_REG_AARCH64_X30:
          return &mX30expr;
        case DW_REG_AARCH64_SP:
          return &mSPexpr;
    #elif defined(GP_ARCH_mips64)
        case DW_REG_MIPS_SP:
          return &mSPexpr;
        case DW_REG_MIPS_FP:
          return &mFPexpr;
        case DW_REG_MIPS_PC:
          return &mPCexpr;
    #else
    #  error "Unknown arch"
    #endif
        default:
          return nullptr;
      }
    }

    RuleSet::RuleSet() {
      // All fields are of type LExpr and so are initialised by LExpr::LExpr().
    }

    ////////////////////////////////////////////////////////////////
    // SecMap                                                     //
    ////////////////////////////////////////////////////////////////

    // See header file LulMainInt.h for comments about invariants.

    SecMap::SecMap(uintptr_t mapStartAVMA, uint32_t mapLen,
                   void (*aLog)(const char*))
        : mUsable(false),
          mUniqifier(new mozilla::HashMap),
          mLog(aLog) {
      if (mapLen == 0) {
        // Degenerate case.
        mMapMinAVMA = 1;
        mMapMaxAVMA = 0;
      } else {
        mMapMinAVMA = mapStartAVMA;
        mMapMaxAVMA = mapStartAVMA + (uintptr_t)mapLen - 1;
      }
    }

    SecMap::~SecMap() {
      mExtents.clear();
      mDictionary.clear();
      if (mUniqifier) {
        mUniqifier->clear();
        mUniqifier = nullptr;
      }
    }

    // RUNS IN NO-MALLOC CONTEXT
    RuleSet* SecMap::FindRuleSet(uintptr_t ia) {
      // Binary search mExtents to find one that brackets |ia|.
      // lo and hi need to be signed, else the loop termination tests
      // don't work properly.  Note that this works correctly even when
      // mExtents.size() == 0.

      // Can't do this until the array has been sorted and preened.
      MOZ_ASSERT(mUsable);

      long int lo = 0;
      long int hi = (long int)mExtents.size() - 1;
      while (true) {
        // current unsearched space is from lo to hi, inclusive.
        if (lo > hi) {
          // not found
          return nullptr;
        }
        long int mid = lo + ((hi - lo) / 2);
        Extent* mid_extent = &mExtents[mid];
        uintptr_t mid_offset = mid_extent->offset();
        uintptr_t mid_len = mid_extent->len();
        uintptr_t mid_minAddr = mMapMinAVMA + mid_offset;
        uintptr_t mid_maxAddr = mid_minAddr + mid_len - 1;
        if (ia < mid_minAddr) {
          hi = mid - 1;
          continue;
        }
        if (ia > mid_maxAddr) {
          lo = mid + 1;
          continue;
        }
        MOZ_ASSERT(mid_minAddr <= ia && ia <= mid_maxAddr);
        uint32_t mid_extent_dictIx = mid_extent->dictIx();
        MOZ_RELEASE_ASSERT(mid_extent_dictIx < mExtents.size());
        return &mDictionary[mid_extent_dictIx];
      }
      // NOTREACHED
    }

    // Add a RuleSet to the collection.  The rule is copied in.  Calling
    // this makes the map non-searchable.
    void SecMap::AddRuleSet(const RuleSet* rs, uintptr_t avma, uintptr_t len) {
      mUsable = false;

      // Zero length RuleSet?  Meaningless, but ignore it anyway.
      if (len == 0) {
        return;
      }

      // Ignore attempts to add RuleSets whose address range doesn't fall within
      // the declared address range for the SecMap.  Maybe we should print some
      // kind of error message rather than silently ignoring them.
      if (!(avma >= mMapMinAVMA && avma + len - 1 <= mMapMaxAVMA)) {
        return;
      }

      // Because `mMapStartAVMA` .. `mMapEndAVMA` can specify at most a 2^32-1 byte
      // chunk of address space, the following must now hold.
      MOZ_RELEASE_ASSERT(len <= (uintptr_t)0xFFFFFFFF);

      // See if `mUniqifier` already has `rs`.  If so set `dictIx` to the assigned
      // dictionary index; if not, add `rs` to `mUniqifier` and assign a new
      // dictionary index.  This is the core of the RuleSet-de-duplication process.
      uint32_t dictIx = 0;
      mozilla::HashMap::AddPtr
          p = mUniqifier->lookupForAdd(*rs);
      if (!p) {
        dictIx = mUniqifier->count();
        // If this ever fails, Extents::dictIx will need to be changed to be a
        // type wider than the current uint16_t.
        MOZ_RELEASE_ASSERT(dictIx < (1 << 16));
        // This returns `false` on OOM.  We ignore the return value since we asked
        // for it to use the InfallibleAllocPolicy.
        DebugOnly addedOK = mUniqifier->add(p, *rs, dictIx);
        MOZ_ASSERT(addedOK);
      } else {
        dictIx = p->value();
      }

      uint32_t offset = (uint32_t)(avma - mMapMinAVMA);
      while (len > 0) {
        // Because Extents::len is a uint16_t, we have to add multiple `mExtents`
        // entries to cover the case where `len` is equal to or greater than 2^16.
        // This happens only exceedingly rarely.  In order to get more test
        // coverage on what would otherwise be a very low probability (less than
        // 0.0002%) corner case, we do this in steps of 4095.  On libxul.so as of
        // Sept 2020, this increases the number of `mExtents` entries by about
        // 0.05%, hence has no meaningful effect on space use, but increases the
        // use of this corner case, and hence its test coverage, by a factor of 250.
        uint32_t this_step_len = (len > 4095) ? 4095 : len;
        mExtents.emplace_back(offset, this_step_len, dictIx);
        offset += this_step_len;
        len -= this_step_len;
      }
    }

    // Add a PfxInstr to the vector of such instrs, and return the index
    // in the vector.  Calling this makes the map non-searchable.
    uint32_t SecMap::AddPfxInstr(PfxInstr pfxi) {
      mUsable = false;
      mPfxInstrs.push_back(pfxi);
      return mPfxInstrs.size() - 1;
    }

    static bool CmpExtentsByOffsetLE(const Extent& ext1, const Extent& ext2) {
      return ext1.offset() < ext2.offset();
    }

    // Prepare the map for searching, by sorting it, de-overlapping entries and
    // removing any resulting zero-length entries.  At the start of this routine,
    // all Extents should fall within [mMapMinAVMA, mMapMaxAVMA] and not have zero
    // length, as a result of the checks in AddRuleSet().
    void SecMap::PrepareRuleSets() {
      // At this point, the de-duped RuleSets are in `mUniqifier`, and
      // `mDictionary` is empty.  This method will, amongst other things, copy
      // them into `mDictionary` in order of their assigned dictionary-index
      // values, as established by `SecMap::AddRuleSet`, and free `mUniqifier`;
      // after this method, it has no further use.
      MOZ_RELEASE_ASSERT(mUniqifier);
      MOZ_RELEASE_ASSERT(mDictionary.empty());

      if (mExtents.empty()) {
        mUniqifier->clear();
        mUniqifier = nullptr;
        return;
      }

      if (mMapMinAVMA == 1 && mMapMaxAVMA == 0) {
        // The map is empty.  This should never happen.
        mExtents.clear();
        mUniqifier->clear();
        mUniqifier = nullptr;
        return;
      }
      MOZ_RELEASE_ASSERT(mMapMinAVMA <= mMapMaxAVMA);

      // We must have at least one Extent, and as a consequence there must be at
      // least one entry in the uniqifier.
      MOZ_RELEASE_ASSERT(!mExtents.empty() && !mUniqifier->empty());

    #ifdef DEBUG
      // Check invariants on incoming Extents.
      for (size_t i = 0; i < mExtents.size(); ++i) {
        Extent* ext = &mExtents[i];
        uint32_t len = ext->len();
        MOZ_ASSERT(len > 0);
        MOZ_ASSERT(len <= 4095 /* per '4095' in AddRuleSet() */);
        uint32_t offset = ext->offset();
        uintptr_t avma = mMapMinAVMA + (uintptr_t)offset;
        // Upper bounds test.  There's no lower bounds test because `offset` is a
        // positive displacement from `mMapMinAVMA`, so a small underrun will
        // manifest as `len` being close to 2^32.
        MOZ_ASSERT(avma + (uintptr_t)len - 1 <= mMapMaxAVMA);
      }
    #endif

      // Sort by start addresses.
      std::sort(mExtents.begin(), mExtents.end(), CmpExtentsByOffsetLE);

      // Iteratively truncate any overlaps and remove any zero length
      // entries that might result, or that may have been present
      // initially.  Unless the input is seriously screwy, this is
      // expected to iterate only once.
      while (true) {
        size_t i;
        size_t n = mExtents.size();
        size_t nZeroLen = 0;

        if (n == 0) {
          break;
        }

        for (i = 1; i < n; ++i) {
          Extent* prev = &mExtents[i - 1];
          Extent* here = &mExtents[i];
          MOZ_ASSERT(prev->offset() <= here->offset());
          if (prev->offset() + prev->len() > here->offset()) {
            prev->setLen(here->offset() - prev->offset());
          }
          if (prev->len() == 0) {
            nZeroLen++;
          }
        }

        if (mExtents[n - 1].len() == 0) {
          nZeroLen++;
        }

        // At this point, the entries are in-order and non-overlapping.
        // If none of them are zero-length, we are done.
        if (nZeroLen == 0) {
          break;
        }

        // Slide back the entries to remove the zero length ones.
        size_t j = 0;  // The write-point.
        for (i = 0; i < n; ++i) {
          if (mExtents[i].len() == 0) {
            continue;
          }
          if (j != i) {
            mExtents[j] = mExtents[i];
          }
          ++j;
        }
        MOZ_ASSERT(i == n);
        MOZ_ASSERT(nZeroLen <= n);
        MOZ_ASSERT(j == n - nZeroLen);
        while (nZeroLen > 0) {
          mExtents.pop_back();
          nZeroLen--;
        }

        MOZ_ASSERT(mExtents.size() == j);
      }

      size_t nExtents = mExtents.size();

    #ifdef DEBUG
      // Do a final check on the extents: their address ranges must be
      // ascending, non overlapping, non zero sized.
      if (nExtents > 0) {
        MOZ_ASSERT(mExtents[0].len() > 0);
        for (size_t i = 1; i < nExtents; ++i) {
          const Extent* prev = &mExtents[i - 1];
          const Extent* here = &mExtents[i];
          MOZ_ASSERT(prev->offset() < here->offset());
          MOZ_ASSERT(here->len() > 0);
          MOZ_ASSERT(prev->offset() + prev->len() <= here->offset());
        }
      }
    #endif

      // Create the final dictionary by enumerating the uniqifier.
      size_t nUniques = mUniqifier->count();

      RuleSet dummy;
      mozilla::PodZero(&dummy);

      mDictionary.reserve(nUniques);
      for (size_t i = 0; i < nUniques; i++) {
        mDictionary.push_back(dummy);
      }

      for (auto iter = mUniqifier->iter(); !iter.done(); iter.next()) {
        MOZ_RELEASE_ASSERT(iter.get().value() < nUniques);
        mDictionary[iter.get().value()] = iter.get().key();
      }

      mUniqifier = nullptr;

      char buf[150];
      SprintfLiteral(
          buf,
          "PrepareRuleSets: %lu extents, %lu rulesets, "
          "avma min/max 0x%llx, 0x%llx\n",
          (unsigned long int)nExtents, (unsigned long int)mDictionary.size(),
          (unsigned long long int)mMapMinAVMA, (unsigned long long int)mMapMaxAVMA);
      buf[sizeof(buf) - 1] = 0;
      mLog(buf);

      // Is now usable for binary search.
      mUsable = true;

    #if 0
      mLog("\nRulesets after preening\n");
      for (size_t i = 0; i < nExtents; ++i) {
        const Extent* extent = &mExtents[i];
        uintptr_t avma = mMapMinAVMA + (uintptr_t)extent->offset();
        mDictionary[extent->dictIx()].Print(avma, extent->len(), mLog);
        mLog("\n");
      }
      mLog("\n");
    #endif
    }

    bool SecMap::IsEmpty() { return mExtents.empty(); }

    size_t SecMap::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
      size_t n = aMallocSizeOf(this);

      // It's conceivable that these calls would be unsafe with some
      // implementations of std::vector, but it seems to be working for now...
      n += aMallocSizeOf(mPfxInstrs.data());

      if (mUniqifier) {
        n += mUniqifier->shallowSizeOfIncludingThis(aMallocSizeOf);
      }
      n += aMallocSizeOf(mDictionary.data());
      n += aMallocSizeOf(mExtents.data());

      return n;
    }

    ////////////////////////////////////////////////////////////////
    // SegArray                                                   //
    ////////////////////////////////////////////////////////////////

    // A SegArray holds a set of address ranges that together exactly
    // cover an address range, with no overlaps or holes.  Each range has
    // an associated value, which in this case has been specialised to be
    // a simple boolean.  The representation is kept to minimal canonical
    // form in which adjacent ranges with the same associated value are
    // merged together.  Each range is represented by a |struct Seg|.
    //
    // SegArrays are used to keep track of which parts of the address
    // space are known to contain instructions.
    class SegArray {
     public:
      void add(uintptr_t lo, uintptr_t hi, bool val) {
        if (lo > hi) {
          return;
        }
        split_at(lo);
        if (hi < UINTPTR_MAX) {
          split_at(hi + 1);
        }
        std::vector::size_type iLo, iHi, i;
        iLo = find(lo);
        iHi = find(hi);
        for (i = iLo; i <= iHi; ++i) {
          mSegs[i].val = val;
        }
        preen();
      }

      // RUNS IN NO-MALLOC CONTEXT
      bool getBoundingCodeSegment(/*OUT*/ uintptr_t* rx_min,
                                  /*OUT*/ uintptr_t* rx_max, uintptr_t addr) {
        std::vector::size_type i = find(addr);
        if (!mSegs[i].val) {
          return false;
        }
        *rx_min = mSegs[i].lo;
        *rx_max = mSegs[i].hi;
        return true;
      }

      SegArray() {
        Seg s(0, UINTPTR_MAX, false);
        mSegs.push_back(s);
      }

     private:
      struct Seg {
        Seg(uintptr_t lo, uintptr_t hi, bool val) : lo(lo), hi(hi), val(val) {}
        uintptr_t lo;
        uintptr_t hi;
        bool val;
      };

      void preen() {
        for (std::vector::iterator iter = mSegs.begin();
             iter < mSegs.end() - 1; ++iter) {
          if (iter[0].val != iter[1].val) {
            continue;
          }
          iter[0].hi = iter[1].hi;
          mSegs.erase(iter + 1);
          // Back up one, so as not to miss an opportunity to merge
          // with the entry after this one.
          --iter;
        }
      }

      // RUNS IN NO-MALLOC CONTEXT
      std::vector::size_type find(uintptr_t a) {
        long int lo = 0;
        long int hi = (long int)mSegs.size();
        while (true) {
          // The unsearched space is lo .. hi inclusive.
          if (lo > hi) {
            // Not found.  This can't happen.
            return (std::vector::size_type)(-1);
          }
          long int mid = lo + ((hi - lo) / 2);
          uintptr_t mid_lo = mSegs[mid].lo;
          uintptr_t mid_hi = mSegs[mid].hi;
          if (a < mid_lo) {
            hi = mid - 1;
            continue;
          }
          if (a > mid_hi) {
            lo = mid + 1;
            continue;
          }
          return (std::vector::size_type)mid;
        }
      }

      void split_at(uintptr_t a) {
        std::vector::size_type i = find(a);
        if (mSegs[i].lo == a) {
          return;
        }
        mSegs.insert(mSegs.begin() + i + 1, mSegs[i]);
        mSegs[i].hi = a - 1;
        mSegs[i + 1].lo = a;
      }

      void show() {
        printf("<< %d entries:\n", (int)mSegs.size());
        for (std::vector::iterator iter = mSegs.begin(); iter < mSegs.end();
             ++iter) {
          printf("  %016llx  %016llx  %s\n", (unsigned long long int)(*iter).lo,
                 (unsigned long long int)(*iter).hi,
                 (*iter).val ? "true" : "false");
        }
        printf(">>\n");
      }

      std::vector mSegs;
    };

    ////////////////////////////////////////////////////////////////
    // PriMap                                                     //
    ////////////////////////////////////////////////////////////////

    class PriMap {
     public:
      explicit PriMap(void (*aLog)(const char*)) : mLog(aLog) {}

      // RUNS IN NO-MALLOC CONTEXT
      pair*> Lookup(uintptr_t ia) {
        SecMap* sm = FindSecMap(ia);
        return pair*>(
            sm ? sm->FindRuleSet(ia) : nullptr, sm ? sm->GetPfxInstrs() : nullptr);
      }

      // Add a secondary map.  No overlaps allowed w.r.t. existing
      // secondary maps.
      void AddSecMap(mozilla::UniquePtr&& aSecMap) {
        // We can't add an empty SecMap to the PriMap.  But that's OK
        // since we'd never be able to find anything in it anyway.
        if (aSecMap->IsEmpty()) {
          return;
        }

        // Iterate through the SecMaps and find the right place for this
        // one.  At the same time, ensure that the in-order
        // non-overlapping invariant is preserved (and, generally, holds).
        // FIXME: this gives a cost that is O(N^2) in the total number of
        // shared objects in the system.  ToDo: better.
        MOZ_ASSERT(aSecMap->mMapMinAVMA <= aSecMap->mMapMaxAVMA);

        size_t num_secMaps = mSecMaps.size();
        uintptr_t i;
        for (i = 0; i < num_secMaps; ++i) {
          mozilla::UniquePtr& sm_i = mSecMaps[i];
          MOZ_ASSERT(sm_i->mMapMinAVMA <= sm_i->mMapMaxAVMA);
          if (aSecMap->mMapMinAVMA < sm_i->mMapMaxAVMA) {
            // |aSecMap| needs to be inserted immediately before mSecMaps[i].
            break;
          }
        }
        MOZ_ASSERT(i <= num_secMaps);
        if (i == num_secMaps) {
          // It goes at the end.
          mSecMaps.push_back(std::move(aSecMap));
        } else {
          std::vector>::iterator iter =
              mSecMaps.begin() + i;
          mSecMaps.insert(iter, std::move(aSecMap));
        }
        char buf[100];
        SprintfLiteral(buf, "AddSecMap: now have %d SecMaps\n",
                       (int)mSecMaps.size());
        buf[sizeof(buf) - 1] = 0;
        mLog(buf);
      }

      // Remove and delete any SecMaps in the mapping, that intersect
      // with the specified address range.
      void RemoveSecMapsInRange(uintptr_t avma_min, uintptr_t avma_max) {
        MOZ_ASSERT(avma_min <= avma_max);
        size_t num_secMaps = mSecMaps.size();
        if (num_secMaps > 0) {
          intptr_t i;
          // Iterate from end to start over the vector, so as to ensure
          // that the special case where |avma_min| and |avma_max| denote
          // the entire address space, can be completed in time proportional
          // to the number of elements in the map.
          for (i = (intptr_t)num_secMaps - 1; i >= 0; i--) {
            mozilla::UniquePtr& sm_i = mSecMaps[i];
            if (sm_i->mMapMaxAVMA < avma_min || avma_max < sm_i->mMapMinAVMA) {
              // There's no overlap.  Move on.
              continue;
            }
            // We need to remove mSecMaps[i] and slide all those above it
            // downwards to cover the hole.
            mSecMaps.erase(mSecMaps.begin() + i);
          }
        }
      }

      // Return the number of currently contained SecMaps.
      size_t CountSecMaps() { return mSecMaps.size(); }

      size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
        size_t n = aMallocSizeOf(this);

        // It's conceivable that this call would be unsafe with some
        // implementations of std::vector, but it seems to be working for now...
        n += aMallocSizeOf(mSecMaps.data());

        for (size_t i = 0; i < mSecMaps.size(); i++) {
          n += mSecMaps[i]->SizeOfIncludingThis(aMallocSizeOf);
        }

        return n;
      }

     private:
      // RUNS IN NO-MALLOC CONTEXT
      SecMap* FindSecMap(uintptr_t ia) {
        // Binary search mSecMaps to find one that brackets |ia|.
        // lo and hi need to be signed, else the loop termination tests
        // don't work properly.
        long int lo = 0;
        long int hi = (long int)mSecMaps.size() - 1;
        while (true) {
          // current unsearched space is from lo to hi, inclusive.
          if (lo > hi) {
            // not found
            return nullptr;
          }
          long int mid = lo + ((hi - lo) / 2);
          mozilla::UniquePtr& mid_secMap = mSecMaps[mid];
          uintptr_t mid_minAddr = mid_secMap->mMapMinAVMA;
          uintptr_t mid_maxAddr = mid_secMap->mMapMaxAVMA;
          if (ia < mid_minAddr) {
            hi = mid - 1;
            continue;
          }
          if (ia > mid_maxAddr) {
            lo = mid + 1;
            continue;
          }
          MOZ_ASSERT(mid_minAddr <= ia && ia <= mid_maxAddr);
          return mid_secMap.get();
        }
        // NOTREACHED
      }

     private:
      // sorted array of per-object ranges, non overlapping, non empty
      std::vector> mSecMaps;

      // a logging sink, for debugging.
      void (*mLog)(const char*);
    };

    ////////////////////////////////////////////////////////////////
    // LUL                                                        //
    ////////////////////////////////////////////////////////////////

    #define LUL_LOG(_str)                                           \
      do {                                                          \
        char buf[200];                                              \
        SprintfLiteral(buf, "LUL: pid %d tid %d lul-obj %p: %s",    \
                       profiler_current_process_id(),               \
                       profiler_current_thread_id(), this, (_str)); \
        buf[sizeof(buf) - 1] = 0;                                   \
        mLog(buf);                                                  \
      } while (0)

    LUL::LUL(void (*aLog)(const char*))
        : mLog(aLog),
          mAdminMode(true),
          mAdminThreadId(profiler_current_thread_id()),
          mPriMap(new PriMap(aLog)),
          mSegArray(new SegArray()),
          mUSU(new UniqueStringUniverse()) {
      LUL_LOG("LUL::LUL: Created object");
    }

    LUL::~LUL() {
      LUL_LOG("LUL::~LUL: Destroyed object");
      delete mPriMap;
      delete mSegArray;
      mLog = nullptr;
      delete mUSU;
    }

    void LUL::MaybeShowStats() {
      // This is racey in the sense that it can't guarantee that
      //   n_new == n_new_Context + n_new_CFI + n_new_Scanned
      // if it should happen that mStats is updated by some other thread
      // in between computation of n_new and n_new_{Context,CFI,FP}.
      // But it's just stats printing, so we don't really care.
      uint32_t n_new = mStats - mStatsPrevious;
      if (n_new >= 5000) {
        uint32_t n_new_Context = mStats.mContext - mStatsPrevious.mContext;
        uint32_t n_new_CFI = mStats.mCFI - mStatsPrevious.mCFI;
        uint32_t n_new_FP = mStats.mFP - mStatsPrevious.mFP;
        mStatsPrevious = mStats;
        char buf[200];
        SprintfLiteral(buf,
                       "LUL frame stats: TOTAL %5u"
                       "    CTX %4u    CFI %4u    FP %4u",
                       n_new, n_new_Context, n_new_CFI, n_new_FP);
        buf[sizeof(buf) - 1] = 0;
        mLog(buf);
      }
    }

    size_t LUL::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
      size_t n = aMallocSizeOf(this);
      n += mPriMap->SizeOfIncludingThis(aMallocSizeOf);

      // Measurement of the following members may be added later if DMD finds it
      // is worthwhile:
      // - mSegArray
      // - mUSU

      return n;
    }

    void LUL::EnableUnwinding() {
      LUL_LOG("LUL::EnableUnwinding");
      // Don't assert for Admin mode here.  That is, tolerate a call here
      // if we are already in Unwinding mode.
      MOZ_RELEASE_ASSERT(profiler_current_thread_id() == mAdminThreadId);

      mAdminMode = false;
    }

    void LUL::NotifyAfterMap(uintptr_t aRXavma, size_t aSize, const char* aFileName,
                             const void* aMappedImage) {
      MOZ_RELEASE_ASSERT(mAdminMode);
      MOZ_RELEASE_ASSERT(profiler_current_thread_id() == mAdminThreadId);

      mLog(":\n");
      char buf[200];
      SprintfLiteral(buf, "NotifyMap %llx %llu %s\n",
                     (unsigned long long int)aRXavma, (unsigned long long int)aSize,
                     aFileName);
      buf[sizeof(buf) - 1] = 0;
      mLog(buf);

      // We can't have a SecMap covering more than 2^32-1 bytes of address space.
      // See the definition of SecMap for why.  Rather than crash the system, just
      // limit the SecMap's size accordingly.  This case is never actually
      // expected to happen.
      if (((unsigned long long int)aSize) > 0xFFFFFFFFULL) {
        aSize = (uintptr_t)0xFFFFFFFF;
      }
      MOZ_RELEASE_ASSERT(aSize <= 0xFFFFFFFF);

      // Ignore obviously-stupid notifications.
      if (aSize > 0) {
        // Here's a new mapping, for this object.
        mozilla::UniquePtr smap =
            mozilla::MakeUnique(aRXavma, (uint32_t)aSize, mLog);

        // Read CFI or EXIDX unwind data into |smap|.
        if (!aMappedImage) {
          (void)lul::ReadSymbolData(string(aFileName), std::vector(),
                                    smap.get(), (void*)aRXavma, aSize, mUSU, mLog);
        } else {
          (void)lul::ReadSymbolDataInternal(
              (const uint8_t*)aMappedImage, string(aFileName),
              std::vector(), smap.get(), (void*)aRXavma, aSize, mUSU, mLog);
        }

        mLog("NotifyMap .. preparing entries\n");

        smap->PrepareRuleSets();

        SprintfLiteral(buf, "NotifyMap got %lld entries\n",
                       (long long int)smap->Size());
        buf[sizeof(buf) - 1] = 0;
        mLog(buf);

        // Add it to the primary map (the top level set of mapped objects).
        mPriMap->AddSecMap(std::move(smap));

        // Tell the segment array about the mapping, so that the stack
        // scan and __kernel_syscall mechanisms know where valid code is.
        mSegArray->add(aRXavma, aRXavma + aSize - 1, true);
      }
    }

    void LUL::NotifyExecutableArea(uintptr_t aRXavma, size_t aSize) {
      MOZ_RELEASE_ASSERT(mAdminMode);
      MOZ_RELEASE_ASSERT(profiler_current_thread_id() == mAdminThreadId);

      mLog(":\n");
      char buf[200];
      SprintfLiteral(buf, "NotifyExecutableArea %llx %llu\n",
                     (unsigned long long int)aRXavma,
                     (unsigned long long int)aSize);
      buf[sizeof(buf) - 1] = 0;
      mLog(buf);

      // Ignore obviously-stupid notifications.
      if (aSize > 0) {
        // Tell the segment array about the mapping, so that the stack
        // scan and __kernel_syscall mechanisms know where valid code is.
        mSegArray->add(aRXavma, aRXavma + aSize - 1, true);
      }
    }

    void LUL::NotifyBeforeUnmap(uintptr_t aRXavmaMin, uintptr_t aRXavmaMax) {
      MOZ_RELEASE_ASSERT(mAdminMode);
      MOZ_RELEASE_ASSERT(profiler_current_thread_id() == mAdminThreadId);

      mLog(":\n");
      char buf[100];
      SprintfLiteral(buf, "NotifyUnmap %016llx-%016llx\n",
                     (unsigned long long int)aRXavmaMin,
                     (unsigned long long int)aRXavmaMax);
      buf[sizeof(buf) - 1] = 0;
      mLog(buf);

      MOZ_ASSERT(aRXavmaMin <= aRXavmaMax);

      // Remove from the primary map, any secondary maps that intersect
      // with the address range.  Also delete the secondary maps.
      mPriMap->RemoveSecMapsInRange(aRXavmaMin, aRXavmaMax);

      // Tell the segment array that the address range no longer
      // contains valid code.
      mSegArray->add(aRXavmaMin, aRXavmaMax, false);

      SprintfLiteral(buf, "NotifyUnmap: now have %d SecMaps\n",
                     (int)mPriMap->CountSecMaps());
      buf[sizeof(buf) - 1] = 0;
      mLog(buf);
    }

    size_t LUL::CountMappings() {
      MOZ_RELEASE_ASSERT(mAdminMode);
      MOZ_RELEASE_ASSERT(profiler_current_thread_id() == mAdminThreadId);

      return mPriMap->CountSecMaps();
    }

    // RUNS IN NO-MALLOC CONTEXT
    static TaggedUWord DerefTUW(TaggedUWord aAddr, const StackImage* aStackImg) {
      if (!aAddr.Valid()) {
        return TaggedUWord();
      }

      // Lower limit check.  |aAddr.Value()| is the lowest requested address
      // and |aStackImg->mStartAvma| is the lowest address we actually have,
      // so the comparison is straightforward.
      if (aAddr.Value() < aStackImg->mStartAvma) {
        return TaggedUWord();
      }

      // Upper limit check.  We must compute the highest requested address
      // and the highest address we actually have, but being careful to
      // avoid overflow.  In particular if |aAddr| is 0xFFF...FFF or the
      // 3/7 values below that, then we will get overflow.  See bug #1245477.
      typedef CheckedInt CheckedUWord;
      CheckedUWord highest_requested_plus_one =
          CheckedUWord(aAddr.Value()) + CheckedUWord(sizeof(uintptr_t));
      CheckedUWord highest_available_plus_one =
          CheckedUWord(aStackImg->mStartAvma) + CheckedUWord(aStackImg->mLen);
      if (!highest_requested_plus_one.isValid()     // overflow?
          || !highest_available_plus_one.isValid()  // overflow?
          || (highest_requested_plus_one.value() >
              highest_available_plus_one.value())) {  // in range?
        return TaggedUWord();
      }

      return TaggedUWord(
          *(uintptr_t*)(&aStackImg
                             ->mContents[aAddr.Value() - aStackImg->mStartAvma]));
    }

    // RUNS IN NO-MALLOC CONTEXT
    static TaggedUWord EvaluateReg(int16_t aReg, const UnwindRegs* aOldRegs,
                                   TaggedUWord aCFA) {
      switch (aReg) {
        case DW_REG_CFA:
          return aCFA;
    #if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
        case DW_REG_INTEL_XBP:
          return aOldRegs->xbp;
        case DW_REG_INTEL_XSP:
          return aOldRegs->xsp;
        case DW_REG_INTEL_XIP:
          return aOldRegs->xip;
    #elif defined(GP_ARCH_arm)
        case DW_REG_ARM_R7:
          return aOldRegs->r7;
        case DW_REG_ARM_R11:
          return aOldRegs->r11;
        case DW_REG_ARM_R12:
          return aOldRegs->r12;
        case DW_REG_ARM_R13:
          return aOldRegs->r13;
        case DW_REG_ARM_R14:
          return aOldRegs->r14;
        case DW_REG_ARM_R15:
          return aOldRegs->r15;
    #elif defined(GP_ARCH_arm64)
        case DW_REG_AARCH64_X29:
          return aOldRegs->x29;
        case DW_REG_AARCH64_X30:
          return aOldRegs->x30;
        case DW_REG_AARCH64_SP:
          return aOldRegs->sp;
    #elif defined(GP_ARCH_mips64)
        case DW_REG_MIPS_SP:
          return aOldRegs->sp;
        case DW_REG_MIPS_FP:
          return aOldRegs->fp;
        case DW_REG_MIPS_PC:
          return aOldRegs->pc;
    #else
    #  error "Unsupported arch"
    #endif
        default:
          MOZ_ASSERT(0);
          return TaggedUWord();
      }
    }

    // RUNS IN NO-MALLOC CONTEXT
    // See prototype for comment.
    TaggedUWord EvaluatePfxExpr(int32_t start, const UnwindRegs* aOldRegs,
                                TaggedUWord aCFA, const StackImage* aStackImg,
                                const vector& aPfxInstrs) {
      // A small evaluation stack, and a stack pointer, which points to
      // the highest numbered in-use element.
      const int N_STACK = 10;
      TaggedUWord stack[N_STACK];
      int stackPointer = -1;
      for (int i = 0; i < N_STACK; i++) stack[i] = TaggedUWord();

    #define PUSH(_tuw)                                             \
      do {                                                         \
        if (stackPointer >= N_STACK - 1) goto fail; /* overflow */ \
        stack[++stackPointer] = (_tuw);                            \
      } while (0)

    #define POP(_lval)                                   \
      do {                                               \
        if (stackPointer < 0) goto fail; /* underflow */ \
        _lval = stack[stackPointer--];                   \
      } while (0)

      // Cursor in the instruction sequence.
      size_t curr = start + 1;

      // Check the start point is sane.
      size_t nInstrs = aPfxInstrs.size();
      if (start < 0 || (size_t)start >= nInstrs) goto fail;

      {
        // The instruction sequence must start with PX_Start.  If not,
        // something is seriously wrong.
        PfxInstr first = aPfxInstrs[start];
        if (first.mOpcode != PX_Start) goto fail;

        // Push the CFA on the stack to start with (or not), as required by
        // the original DW_OP_*expression* CFI.
        if (first.mOperand != 0) PUSH(aCFA);
      }

      while (true) {
        if (curr >= nInstrs) goto fail;  // ran off the end of the sequence

        PfxInstr pfxi = aPfxInstrs[curr++];
        if (pfxi.mOpcode == PX_End) break;  // we're done

        switch (pfxi.mOpcode) {
          case PX_Start:
            // This should appear only at the start of the sequence.
            goto fail;
          case PX_End:
            // We just took care of that, so we shouldn't see it again.
            MOZ_ASSERT(0);
            goto fail;
          case PX_SImm32:
            PUSH(TaggedUWord((intptr_t)pfxi.mOperand));
            break;
          case PX_DwReg: {
            DW_REG_NUMBER reg = (DW_REG_NUMBER)pfxi.mOperand;
            MOZ_ASSERT(reg != DW_REG_CFA);
            PUSH(EvaluateReg(reg, aOldRegs, aCFA));
            break;
          }
          case PX_Deref: {
            TaggedUWord addr;
            POP(addr);
            PUSH(DerefTUW(addr, aStackImg));
            break;
          }
          case PX_Add: {
            TaggedUWord x, y;
            POP(x);
            POP(y);
            PUSH(y + x);
            break;
          }
          case PX_Sub: {
            TaggedUWord x, y;
            POP(x);
            POP(y);
            PUSH(y - x);
            break;
          }
          case PX_And: {
            TaggedUWord x, y;
            POP(x);
            POP(y);
            PUSH(y & x);
            break;
          }
          case PX_Or: {
            TaggedUWord x, y;
            POP(x);
            POP(y);
            PUSH(y | x);
            break;
          }
          case PX_CmpGES: {
            TaggedUWord x, y;
            POP(x);
            POP(y);
            PUSH(y.CmpGEs(x));
            break;
          }
          case PX_Shl: {
            TaggedUWord x, y;
            POP(x);
            POP(y);
            PUSH(y << x);
            break;
          }
          default:
            MOZ_ASSERT(0);
            goto fail;
        }
      }  // while (true)

      // Evaluation finished.  The top value on the stack is the result.
      if (stackPointer >= 0) {
        return stack[stackPointer];
      }
      // Else fall through

    fail:
      return TaggedUWord();

    #undef PUSH
    #undef POP
    }

    // RUNS IN NO-MALLOC CONTEXT
    TaggedUWord LExpr::EvaluateExpr(const UnwindRegs* aOldRegs, TaggedUWord aCFA,
                                    const StackImage* aStackImg,
                                    const vector* aPfxInstrs) const {
      switch (mHow) {
        case UNKNOWN:
          return TaggedUWord();
        case NODEREF: {
          TaggedUWord tuw = EvaluateReg(mReg, aOldRegs, aCFA);
          tuw = tuw + TaggedUWord((intptr_t)mOffset);
          return tuw;
        }
        case DEREF: {
          TaggedUWord tuw = EvaluateReg(mReg, aOldRegs, aCFA);
          tuw = tuw + TaggedUWord((intptr_t)mOffset);
          return DerefTUW(tuw, aStackImg);
        }
        case PFXEXPR: {
          MOZ_ASSERT(aPfxInstrs);
          if (!aPfxInstrs) {
            return TaggedUWord();
          }
          return EvaluatePfxExpr(mOffset, aOldRegs, aCFA, aStackImg, *aPfxInstrs);
        }
        default:
          MOZ_ASSERT(0);
          return TaggedUWord();
      }
    }

    // RUNS IN NO-MALLOC CONTEXT
    static void UseRuleSet(/*MOD*/ UnwindRegs* aRegs, const StackImage* aStackImg,
                           const RuleSet* aRS, const vector* aPfxInstrs) {
      // Take a copy of regs, since we'll need to refer to the old values
      // whilst computing the new ones.
      UnwindRegs old_regs = *aRegs;

      // Mark all the current register values as invalid, so that the
      // caller can see, on our return, which ones have been computed
      // anew.  If we don't even manage to compute a new PC value, then
      // the caller will have to abandon the unwind.
      // FIXME: Create and use instead: aRegs->SetAllInvalid();
    #if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
      aRegs->xbp = TaggedUWord();
      aRegs->xsp = TaggedUWord();
      aRegs->xip = TaggedUWord();
    #elif defined(GP_ARCH_arm)
      aRegs->r7 = TaggedUWord();
      aRegs->r11 = TaggedUWord();
      aRegs->r12 = TaggedUWord();
      aRegs->r13 = TaggedUWord();
      aRegs->r14 = TaggedUWord();
      aRegs->r15 = TaggedUWord();
    #elif defined(GP_ARCH_arm64)
      aRegs->x29 = TaggedUWord();
      aRegs->x30 = TaggedUWord();
      aRegs->sp = TaggedUWord();
      aRegs->pc = TaggedUWord();
    #elif defined(GP_ARCH_mips64)
      aRegs->sp = TaggedUWord();
      aRegs->fp = TaggedUWord();
      aRegs->pc = TaggedUWord();
    #else
    #  error "Unsupported arch"
    #endif

      // This is generally useful.
      const TaggedUWord inval = TaggedUWord();

      // First, compute the CFA.
      TaggedUWord cfa = aRS->mCfaExpr.EvaluateExpr(&old_regs, inval /*old cfa*/,
                                                   aStackImg, aPfxInstrs);

      // If we didn't manage to compute the CFA, well .. that's ungood,
      // but keep going anyway.  It'll be OK provided none of the register
      // value rules mention the CFA.  In any case, compute the new values
      // for each register that we're tracking.

    #if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
      aRegs->xbp =
          aRS->mXbpExpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
      aRegs->xsp =
          aRS->mXspExpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
      aRegs->xip =
          aRS->mXipExpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
    #elif defined(GP_ARCH_arm)
      aRegs->r7 = aRS->mR7expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
      aRegs->r11 =
          aRS->mR11expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
      aRegs->r12 =
          aRS->mR12expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
      aRegs->r13 =
          aRS->mR13expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
      aRegs->r14 =
          aRS->mR14expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
      aRegs->r15 =
          aRS->mR15expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
    #elif defined(GP_ARCH_arm64)
      aRegs->x29 =
          aRS->mX29expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
      aRegs->x30 =
          aRS->mX30expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
      aRegs->sp = aRS->mSPexpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
    #elif defined(GP_ARCH_mips64)
      aRegs->sp = aRS->mSPexpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
      aRegs->fp = aRS->mFPexpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
      aRegs->pc = aRS->mPCexpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
    #else
    #  error "Unsupported arch"
    #endif

      // We're done.  Any regs for which we didn't manage to compute a
      // new value will now be marked as invalid.
    }

    // RUNS IN NO-MALLOC CONTEXT
    void LUL::Unwind(/*OUT*/ uintptr_t* aFramePCs,
                     /*OUT*/ uintptr_t* aFrameSPs,
                     /*OUT*/ size_t* aFramesUsed,
                     /*OUT*/ size_t* aFramePointerFramesAcquired,
                     size_t aFramesAvail, UnwindRegs* aStartRegs,
                     StackImage* aStackImg) {
      MOZ_RELEASE_ASSERT(!mAdminMode);

      /////////////////////////////////////////////////////////
      // BEGIN UNWIND

      *aFramesUsed = 0;

      UnwindRegs regs = *aStartRegs;
      TaggedUWord last_valid_sp = TaggedUWord();

      while (true) {
        if (DEBUG_MAIN) {
          char buf[300];
          mLog("\n");
    #if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
          SprintfLiteral(
              buf, "LoopTop: rip %d/%llx  rsp %d/%llx  rbp %d/%llx\n",
              (int)regs.xip.Valid(), (unsigned long long int)regs.xip.Value(),
              (int)regs.xsp.Valid(), (unsigned long long int)regs.xsp.Value(),
              (int)regs.xbp.Valid(), (unsigned long long int)regs.xbp.Value());
          buf[sizeof(buf) - 1] = 0;
          mLog(buf);
    #elif defined(GP_ARCH_arm)
          SprintfLiteral(
              buf,
              "LoopTop: r15 %d/%llx  r7 %d/%llx  r11 %d/%llx"
              "  r12 %d/%llx  r13 %d/%llx  r14 %d/%llx\n",
              (int)regs.r15.Valid(), (unsigned long long int)regs.r15.Value(),
              (int)regs.r7.Valid(), (unsigned long long int)regs.r7.Value(),
              (int)regs.r11.Valid(), (unsigned long long int)regs.r11.Value(),
              (int)regs.r12.Valid(), (unsigned long long int)regs.r12.Value(),
              (int)regs.r13.Valid(), (unsigned long long int)regs.r13.Value(),
              (int)regs.r14.Valid(), (unsigned long long int)regs.r14.Value());
          buf[sizeof(buf) - 1] = 0;
          mLog(buf);
    #elif defined(GP_ARCH_arm64)
          SprintfLiteral(
              buf,
              "LoopTop: pc %d/%llx  x29 %d/%llx  x30 %d/%llx"
              "  sp %d/%llx\n",
              (int)regs.pc.Valid(), (unsigned long long int)regs.pc.Value(),
              (int)regs.x29.Valid(), (unsigned long long int)regs.x29.Value(),
              (int)regs.x30.Valid(), (unsigned long long int)regs.x30.Value(),
              (int)regs.sp.Valid(), (unsigned long long int)regs.sp.Value());
          buf[sizeof(buf) - 1] = 0;
          mLog(buf);
    #elif defined(GP_ARCH_mips64)
          SprintfLiteral(
              buf, "LoopTop: pc %d/%llx  sp %d/%llx  fp %d/%llx\n",
              (int)regs.pc.Valid(), (unsigned long long int)regs.pc.Value(),
              (int)regs.sp.Valid(), (unsigned long long int)regs.sp.Value(),
              (int)regs.fp.Valid(), (unsigned long long int)regs.fp.Value());
          buf[sizeof(buf) - 1] = 0;
          mLog(buf);
    #else
    #  error "Unsupported arch"
    #endif
        }

    #if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
        TaggedUWord ia = regs.xip;
        TaggedUWord sp = regs.xsp;
    #elif defined(GP_ARCH_arm)
        TaggedUWord ia = (*aFramesUsed == 0 ? regs.r15 : regs.r14);
        TaggedUWord sp = regs.r13;
    #elif defined(GP_ARCH_arm64)
        TaggedUWord ia = (*aFramesUsed == 0 ? regs.pc : regs.x30);
        TaggedUWord sp = regs.sp;
    #elif defined(GP_ARCH_mips64)
        TaggedUWord ia = regs.pc;
        TaggedUWord sp = regs.sp;
    #else
    #  error "Unsupported arch"
    #endif

        if (*aFramesUsed >= aFramesAvail) {
          break;
        }

        // If we don't have a valid value for the PC, give up.
        if (!ia.Valid()) {
          break;
        }

        // If this is the innermost frame, record the SP value, which
        // presumably is valid.  If this isn't the innermost frame, and we
        // have a valid SP value, check that its SP value isn't less that
        // the one we've seen so far, so as to catch potential SP value
        // cycles.
        if (*aFramesUsed == 0) {
          last_valid_sp = sp;
        } else {
          MOZ_ASSERT(last_valid_sp.Valid());
          if (sp.Valid()) {
            if (sp.Value() < last_valid_sp.Value()) {
              // Hmm, SP going in the wrong direction.  Let's stop.
              break;
            }
            // Remember where we got to.
            last_valid_sp = sp;
          }
        }

        // For the innermost frame, the IA value is what we need.  For all
        // other frames, it's actually the return address, so back up one
        // byte so as to get it into the calling instruction.
        aFramePCs[*aFramesUsed] = ia.Value() - (*aFramesUsed == 0 ? 0 : 1);
        aFrameSPs[*aFramesUsed] = sp.Valid() ? sp.Value() : 0;
        (*aFramesUsed)++;

        // Find the RuleSet for the current IA, if any.  This will also
        // query the backing (secondary) maps if it isn't found in the
        // thread-local cache.

        // If this isn't the innermost frame, back up into the calling insn.
        if (*aFramesUsed > 1) {
          ia = ia + TaggedUWord((uintptr_t)(-1));
        }

        pair*> ruleset_and_pfxinstrs =
            mPriMap->Lookup(ia.Value());
        const RuleSet* ruleset = ruleset_and_pfxinstrs.first;
        const vector* pfxinstrs = ruleset_and_pfxinstrs.second;

        if (DEBUG_MAIN) {
          char buf[100];
          SprintfLiteral(buf, "ruleset for 0x%llx = %p\n",
                         (unsigned long long int)ia.Value(), ruleset);
          buf[sizeof(buf) - 1] = 0;
          mLog(buf);
        }

    #if defined(GP_PLAT_x86_android) || defined(GP_PLAT_x86_linux)
        /////////////////////////////////////////////
        ////
        // On 32 bit x86-linux, syscalls are often done via the VDSO
        // function __kernel_vsyscall, which doesn't have a corresponding
        // object that we can read debuginfo from.  That effectively kills
        // off all stack traces for threads blocked in syscalls.  Hence
        // special-case by looking at the code surrounding the program
        // counter.
        //
        // 0xf7757420 <__kernel_vsyscall+0>:	push   %ecx
        // 0xf7757421 <__kernel_vsyscall+1>:	push   %edx
        // 0xf7757422 <__kernel_vsyscall+2>:	push   %ebp
        // 0xf7757423 <__kernel_vsyscall+3>:	mov    %esp,%ebp
        // 0xf7757425 <__kernel_vsyscall+5>:	sysenter
        // 0xf7757427 <__kernel_vsyscall+7>:	nop
        // 0xf7757428 <__kernel_vsyscall+8>:	nop
        // 0xf7757429 <__kernel_vsyscall+9>:	nop
        // 0xf775742a <__kernel_vsyscall+10>:	nop
        // 0xf775742b <__kernel_vsyscall+11>:	nop
        // 0xf775742c <__kernel_vsyscall+12>:	nop
        // 0xf775742d <__kernel_vsyscall+13>:	nop
        // 0xf775742e <__kernel_vsyscall+14>:	int    $0x80
        // 0xf7757430 <__kernel_vsyscall+16>:	pop    %ebp
        // 0xf7757431 <__kernel_vsyscall+17>:	pop    %edx
        // 0xf7757432 <__kernel_vsyscall+18>:	pop    %ecx
        // 0xf7757433 <__kernel_vsyscall+19>:	ret
        //
        // In cases where the sampled thread is blocked in a syscall, its
        // program counter will point at "pop %ebp".  Hence we look for
        // the sequence "int $0x80; pop %ebp; pop %edx; pop %ecx; ret", and
        // the corresponding register-recovery actions are:
        //    new_ebp = *(old_esp + 0)
        //    new eip = *(old_esp + 12)
        //    new_esp = old_esp + 16
        //
        // It may also be the case that the program counter points two
        // nops before the "int $0x80", viz, is __kernel_vsyscall+12, in
        // the case where the syscall has been restarted but the thread
        // hasn't been rescheduled.  The code below doesn't handle that;
        // it could easily be made to.
        //
        if (!ruleset && *aFramesUsed == 1 && ia.Valid() && sp.Valid()) {
          uintptr_t insns_min, insns_max;
          uintptr_t eip = ia.Value();
          bool b = mSegArray->getBoundingCodeSegment(&insns_min, &insns_max, eip);
          if (b && eip - 2 >= insns_min && eip + 3 <= insns_max) {
            uint8_t* eipC = (uint8_t*)eip;
            if (eipC[-2] == 0xCD && eipC[-1] == 0x80 && eipC[0] == 0x5D &&
                eipC[1] == 0x5A && eipC[2] == 0x59 && eipC[3] == 0xC3) {
              TaggedUWord sp_plus_0 = sp;
              TaggedUWord sp_plus_12 = sp;
              TaggedUWord sp_plus_16 = sp;
              sp_plus_12 = sp_plus_12 + TaggedUWord(12);
              sp_plus_16 = sp_plus_16 + TaggedUWord(16);
              TaggedUWord new_ebp = DerefTUW(sp_plus_0, aStackImg);
              TaggedUWord new_eip = DerefTUW(sp_plus_12, aStackImg);
              TaggedUWord new_esp = sp_plus_16;
              if (new_ebp.Valid() && new_eip.Valid() && new_esp.Valid()) {
                regs.xbp = new_ebp;
                regs.xip = new_eip;
                regs.xsp = new_esp;
                continue;
              }
            }
          }
        }
        ////
        /////////////////////////////////////////////
    #endif  // defined(GP_PLAT_x86_android) || defined(GP_PLAT_x86_linux)

        // So, do we have a ruleset for this address?  If so, use it now.
        if (ruleset) {
          if (DEBUG_MAIN) {
            ruleset->Print(ia.Value(), 1 /*bogus, but doesn't matter*/, mLog);
            mLog("\n");
          }
          // Use the RuleSet to compute the registers for the previous
          // frame.  |regs| is modified in-place.
          UseRuleSet(®s, aStackImg, ruleset, pfxinstrs);
          continue;
        }

    #if defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_x86_linux) ||     \
        defined(GP_PLAT_amd64_android) || defined(GP_PLAT_x86_android) || \
        defined(GP_PLAT_amd64_freebsd)
        // There's no RuleSet for the specified address.  On amd64/x86_linux, see if
        // it's possible to recover the caller's frame by using the frame pointer.

        // We seek to compute (new_IP, new_SP, new_BP) from (old_BP, stack image),
        // and assume the following layout:
        //
        //                 <--- new_SP
        //   +----------+
        //   |  new_IP  |  (return address)
        //   +----------+
        //   |  new_BP  |  <--- old_BP
        //   +----------+
        //   |   ....   |
        //   |   ....   |
        //   |   ....   |
        //   +----------+  <---- old_SP (arbitrary, but must be <= old_BP)

        const size_t wordSzB = sizeof(uintptr_t);
        TaggedUWord old_xsp = regs.xsp;

        // points at new_BP ?
        TaggedUWord old_xbp = regs.xbp;
        // points at new_IP ?
        TaggedUWord old_xbp_plus1 = regs.xbp + TaggedUWord(1 * wordSzB);
        // is the new_SP ?
        TaggedUWord old_xbp_plus2 = regs.xbp + TaggedUWord(2 * wordSzB);

        if (old_xbp.Valid() && old_xbp.IsAligned() && old_xsp.Valid() &&
            old_xsp.IsAligned() && old_xsp.Value() <= old_xbp.Value()) {
          // We don't need to do any range, alignment or validity checks for
          // addresses passed to DerefTUW, since that performs them itself, and
          // returns an invalid value on failure.  Any such value will poison
          // subsequent uses, and we do a final check for validity before putting
          // the computed values into |regs|.
          TaggedUWord new_xbp = DerefTUW(old_xbp, aStackImg);
          if (new_xbp.Valid() && new_xbp.IsAligned() &&
              old_xbp.Value() < new_xbp.Value()) {
            TaggedUWord new_xip = DerefTUW(old_xbp_plus1, aStackImg);
            TaggedUWord new_xsp = old_xbp_plus2;
            if (new_xbp.Valid() && new_xip.Valid() && new_xsp.Valid()) {
              regs.xbp = new_xbp;
              regs.xip = new_xip;
              regs.xsp = new_xsp;
              (*aFramePointerFramesAcquired)++;
              continue;
            }
          }
        }
    #elif defined(GP_ARCH_arm64)
        // Here is an example of generated code for prologue and epilogue..
        //
        // stp     x29, x30, [sp, #-16]!
        // mov     x29, sp
        // ...
        // ldp     x29, x30, [sp], #16
        // ret
        //
        // Next is another example of generated code.
        //
        // stp     x20, x19, [sp, #-32]!
        // stp     x29, x30, [sp, #16]
        // add     x29, sp, #0x10
        // ...
        // ldp     x29, x30, [sp, #16]
        // ldp     x20, x19, [sp], #32
        // ret
        //
        // Previous x29 and x30 register are stored in the address of x29 register.
        // But since sp register value depends on local variables, we cannot compute
        // previous sp register from current sp/fp/lr register and there is no
        // regular rule for sp register in prologue. But since return address is lr
        // register, if x29 is valid, we will get return address without sp
        // register.
        //
        // So we assume the following layout that if no rule set. x29 is frame
        // pointer, so we will be able to compute x29 and x30 .
        //
        //   +----------+  <--- new_sp (cannot compute)
        //   |   ....   |
        //   +----------+
        //   |  new_lr  |  (return address)
        //   +----------+
        //   |  new_fp  |  <--- old_fp
        //   +----------+
        //   |   ....   |
        //   |   ....   |
        //   +----------+  <---- old_sp (arbitrary, but unused)

        TaggedUWord old_fp = regs.x29;
        if (old_fp.Valid() && old_fp.IsAligned() && last_valid_sp.Valid() &&
            last_valid_sp.Value() <= old_fp.Value()) {
          TaggedUWord new_fp = DerefTUW(old_fp, aStackImg);
          if (new_fp.Valid() && new_fp.IsAligned() &&
              old_fp.Value() < new_fp.Value()) {
            TaggedUWord old_fp_plus1 = old_fp + TaggedUWord(8);
            TaggedUWord new_lr = DerefTUW(old_fp_plus1, aStackImg);
            if (new_lr.Valid()) {
              regs.x29 = new_fp;
              regs.x30 = new_lr;
              // When using frame pointer to walk stack, we cannot compute sp
              // register since we cannot compute sp register from fp/lr/sp
              // register, and there is no regular rule to compute previous sp
              // register. So mark as invalid.
              regs.sp = TaggedUWord();
              (*aFramePointerFramesAcquired)++;
              continue;
            }
          }
        }
    #endif  // defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_x86_linux) ||
            // defined(GP_PLAT_amd64_android) || defined(GP_PLAT_x86_android) ||
            // defined(GP_PLAT_amd64_freebsd)

        // We failed to recover a frame either using CFI or FP chasing, and we
        // have no other ways to recover the frame.  So we have to give up.
        break;

      }  // top level unwind loop

      // END UNWIND
      /////////////////////////////////////////////////////////
    }

    ////////////////////////////////////////////////////////////////
    // LUL Unit Testing                                           //
    ////////////////////////////////////////////////////////////////

    static const int LUL_UNIT_TEST_STACK_SIZE = 32768;

    #if defined(GP_ARCH_mips64)
    static __attribute__((noinline)) unsigned long __getpc(void) {
      unsigned long rtaddr;
      __asm__ volatile("move %0, $31" : "=r"(rtaddr));
      return rtaddr;
    }
    #endif

    // This function is innermost in the test call sequence.  It uses LUL
    // to unwind, and compares the result with the sequence specified in
    // the director string.  These need to agree in order for the test to
    // pass.  In order not to screw up the results, this function needs
    // to have a not-very big stack frame, since we're only presenting
    // the innermost LUL_UNIT_TEST_STACK_SIZE bytes of stack to LUL, and
    // that chunk unavoidably includes the frame for this function.
    //
    // This function must not be inlined into its callers.  Doing so will
    // cause the expected-vs-actual backtrace consistency checking to
    // fail.  Prints summary results to |aLUL|'s logging sink and also
    // returns a boolean indicating whether or not the test passed.
    static __attribute__((noinline)) bool GetAndCheckStackTrace(
        LUL* aLUL, const char* dstring) {
      // Get hold of the current unwind-start registers.
      UnwindRegs startRegs;
      memset(&startRegs, 0, sizeof(startRegs));
    #if defined(GP_ARCH_amd64)
      volatile uintptr_t block[3];
      MOZ_ASSERT(sizeof(block) == 24);
      __asm__ __volatile__(
          "leaq 0(%%rip), %%r15"
          "\n\t"
          "movq %%r15, 0(%0)"
          "\n\t"
          "movq %%rsp, 8(%0)"
          "\n\t"
          "movq %%rbp, 16(%0)"
          "\n"
          :
          : "r"(&block[0])
          : "memory", "r15");
      startRegs.xip = TaggedUWord(block[0]);
      startRegs.xsp = TaggedUWord(block[1]);
      startRegs.xbp = TaggedUWord(block[2]);
      const uintptr_t REDZONE_SIZE = 128;
      uintptr_t start = block[1] - REDZONE_SIZE;
    #elif defined(GP_PLAT_x86_linux) || defined(GP_PLAT_x86_android)
      volatile uintptr_t block[3];
      MOZ_ASSERT(sizeof(block) == 12);
      __asm__ __volatile__(
          ".byte 0xE8,0x00,0x00,0x00,0x00" /*call next insn*/
          "\n\t"
          "popl %%edi"
          "\n\t"
          "movl %%edi, 0(%0)"
          "\n\t"
          "movl %%esp, 4(%0)"
          "\n\t"
          "movl %%ebp, 8(%0)"
          "\n"
          :
          : "r"(&block[0])
          : "memory", "edi");
      startRegs.xip = TaggedUWord(block[0]);
      startRegs.xsp = TaggedUWord(block[1]);
      startRegs.xbp = TaggedUWord(block[2]);
      const uintptr_t REDZONE_SIZE = 0;
      uintptr_t start = block[1] - REDZONE_SIZE;
    #elif defined(GP_PLAT_arm_linux) || defined(GP_PLAT_arm_android)
      volatile uintptr_t block[6];
      MOZ_ASSERT(sizeof(block) == 24);
      __asm__ __volatile__(
          "mov r0, r15"
          "\n\t"
          "str r0,  [%0, #0]"
          "\n\t"
          "str r14, [%0, #4]"
          "\n\t"
          "str r13, [%0, #8]"
          "\n\t"
          "str r12, [%0, #12]"
          "\n\t"
          "str r11, [%0, #16]"
          "\n\t"
          "str r7,  [%0, #20]"
          "\n"
          :
          : "r"(&block[0])
          : "memory", "r0");
      startRegs.r15 = TaggedUWord(block[0]);
      startRegs.r14 = TaggedUWord(block[1]);
      startRegs.r13 = TaggedUWord(block[2]);
      startRegs.r12 = TaggedUWord(block[3]);
      startRegs.r11 = TaggedUWord(block[4]);
      startRegs.r7 = TaggedUWord(block[5]);
      const uintptr_t REDZONE_SIZE = 0;
      uintptr_t start = block[1] - REDZONE_SIZE;
    #elif defined(GP_ARCH_arm64)
      volatile uintptr_t block[4];
      MOZ_ASSERT(sizeof(block) == 32);
      __asm__ __volatile__(
          "adr x0, . \n\t"
          "str x0, [%0, #0] \n\t"
          "str x29, [%0, #8] \n\t"
          "str x30, [%0, #16] \n\t"
          "mov x0, sp \n\t"
          "str x0, [%0, #24] \n\t"
          :
          : "r"(&block[0])
          : "memory", "x0");
      startRegs.pc = TaggedUWord(block[0]);
      startRegs.x29 = TaggedUWord(block[1]);
      startRegs.x30 = TaggedUWord(block[2]);
      startRegs.sp = TaggedUWord(block[3]);
      const uintptr_t REDZONE_SIZE = 0;
      uintptr_t start = block[1] - REDZONE_SIZE;
    #elif defined(GP_ARCH_mips64)
      volatile uintptr_t block[3];
      MOZ_ASSERT(sizeof(block) == 24);
      __asm__ __volatile__(
          "sd $29, 8(%0)     \n"
          "sd $30, 16(%0)    \n"
          :
          : "r"(block)
          : "memory");
      block[0] = __getpc();
      startRegs.pc = TaggedUWord(block[0]);
      startRegs.sp = TaggedUWord(block[1]);
      startRegs.fp = TaggedUWord(block[2]);
      const uintptr_t REDZONE_SIZE = 0;
      uintptr_t start = block[1] - REDZONE_SIZE;
    #else
    #  error "Unsupported platform"
    #endif

      // Get hold of the innermost LUL_UNIT_TEST_STACK_SIZE bytes of the
      // stack.
      uintptr_t end = start + LUL_UNIT_TEST_STACK_SIZE;
      uintptr_t ws = sizeof(void*);
      start &= ~(ws - 1);
      end &= ~(ws - 1);
      uintptr_t nToCopy = end - start;
      if (nToCopy > lul::N_STACK_BYTES) {
        nToCopy = lul::N_STACK_BYTES;
      }
      MOZ_ASSERT(nToCopy <= lul::N_STACK_BYTES);
      StackImage* stackImg = new StackImage();
      stackImg->mLen = nToCopy;
      stackImg->mStartAvma = start;
      if (nToCopy > 0) {
        MOZ_MAKE_MEM_DEFINED((void*)start, nToCopy);
        memcpy(&stackImg->mContents[0], (void*)start, nToCopy);
      }

      // Unwind it.
      const int MAX_TEST_FRAMES = 64;
      uintptr_t framePCs[MAX_TEST_FRAMES];
      uintptr_t frameSPs[MAX_TEST_FRAMES];
      size_t framesAvail = mozilla::ArrayLength(framePCs);
      size_t framesUsed = 0;
      size_t framePointerFramesAcquired = 0;
      aLUL->Unwind(&framePCs[0], &frameSPs[0], &framesUsed,
                   &framePointerFramesAcquired, framesAvail, &startRegs, stackImg);

      delete stackImg;

      // if (0) {
      //  // Show what we have.
      //  fprintf(stderr, "Got %d frames:\n", (int)framesUsed);
      //  for (size_t i = 0; i < framesUsed; i++) {
      //    fprintf(stderr, "  [%2d]   SP %p   PC %p\n",
      //            (int)i, (void*)frameSPs[i], (void*)framePCs[i]);
      //  }
      //  fprintf(stderr, "\n");
      //}

      // Check to see if there's a consistent binding between digits in
      // the director string ('1' .. '8') and the PC values acquired by
      // the unwind.  If there isn't, the unwinding has failed somehow.
      uintptr_t binding[8];  // binding for '1' .. binding for '8'
      memset((void*)binding, 0, sizeof(binding));

      // The general plan is to work backwards along the director string
      // and forwards along the framePCs array.  Doing so corresponds to
      // working outwards from the innermost frame of the recursive test set.
      const char* cursor = dstring;

      // Find the end.  This leaves |cursor| two bytes past the first
      // character we want to look at -- see comment below.
      while (*cursor) cursor++;

      // Counts the number of consistent frames.
      size_t nConsistent = 0;

      // Iterate back to the start of the director string.  The starting
      // points are a bit complex.  We can't use framePCs[0] because that
      // contains the PC in this frame (above).  We can't use framePCs[1]
      // because that will contain the PC at return point in the recursive
      // test group (TestFn[1-8]) for their call "out" to this function,
      // GetAndCheckStackTrace.  Although LUL will compute a correct
      // return address, that will not be the same return address as for a
      // recursive call out of the the function to another function in the
      // group.  Hence we can only start consistency checking at
      // framePCs[2].
      //
      // To be consistent, then, we must ignore the last element in the
      // director string as that corresponds to framePCs[1].  Hence the
      // start points are: framePCs[2] and the director string 2 bytes
      // before the terminating zero.
      //
      // Also as a result of this, the number of consistent frames counted
      // will always be one less than the length of the director string
      // (not including its terminating zero).
      size_t frameIx;
      for (cursor = cursor - 2, frameIx = 2;
           cursor >= dstring && frameIx < framesUsed; cursor--, frameIx++) {
        char c = *cursor;
        uintptr_t pc = framePCs[frameIx];
        // If this doesn't hold, the director string is ill-formed.
        MOZ_ASSERT(c >= '1' && c <= '8');
        int n = ((int)c) - ((int)'1');
        if (binding[n] == 0) {
          // There's no binding for |c| yet, so install |pc| and carry on.
          binding[n] = pc;
          nConsistent++;
          continue;
        }
        // There's a pre-existing binding for |c|.  Check it's consistent.
        if (binding[n] != pc) {
          // Not consistent.  Give up now.
          break;
        }
        // Consistent.  Keep going.
        nConsistent++;
      }

      // So, did we succeed?
      bool passed = nConsistent + 1 == strlen(dstring);

      // Show the results.
      char buf[200];
      SprintfLiteral(buf, "LULUnitTest:   dstring = %s\n", dstring);
      buf[sizeof(buf) - 1] = 0;
      aLUL->mLog(buf);
      SprintfLiteral(buf, "LULUnitTest:     %d consistent, %d in dstring: %s\n",
                     (int)nConsistent, (int)strlen(dstring),
                     passed ? "PASS" : "FAIL");
      buf[sizeof(buf) - 1] = 0;
      aLUL->mLog(buf);

      return passed;
    }

    // Macro magic to create a set of 8 mutually recursive functions with
    // varying frame sizes.  These will recurse amongst themselves as
    // specified by |strP|, the directory string, and call
    // GetAndCheckStackTrace when the string becomes empty, passing it the
    // original value of the string.  This checks the result, printing
    // results on |aLUL|'s logging sink, and also returns a boolean
    // indicating whether or not the results are acceptable (correct).

    #define DECL_TEST_FN(NAME) \
      bool NAME(LUL* aLUL, const char* strPorig, const char* strP);

    #define GEN_TEST_FN(NAME, FRAMESIZE)                                          \
      bool NAME(LUL* aLUL, const char* strPorig, const char* strP) {              \
        /* Create a frame of size (at least) FRAMESIZE, so that the */            \
        /* 8 functions created by this macro offer some variation in frame */     \
        /* sizes.  This isn't as simple as it might seem, since a clever */       \
        /* optimizing compiler (eg, clang-5) detects that the array is unused */  \
        /* and removes it.  We try to defeat this by passing it to a function */  \
        /* in a different compilation unit, and hoping that clang does not */     \
        /* notice that the call is a no-op. */                                    \
        char space[FRAMESIZE];                                                    \
        Unused << write(1, space, 0); /* write zero bytes of |space| to stdout */ \
                                                                                  \
        if (*strP == '\0') {                                                      \
          /* We've come to the end of the director string. */                     \
          /* Take a stack snapshot. */                                            \
          return GetAndCheckStackTrace(aLUL, strPorig);                           \
        } else {                                                                  \
          /* Recurse onwards.  This is a bit subtle.  The obvious */              \
          /* thing to do here is call onwards directly, from within the */        \
          /* arms of the case statement.  That gives a problem in that */         \
          /* there will be multiple return points inside each function when */    \
          /* unwinding, so it will be difficult to check for consistency */       \
          /* against the director string.  Instead, we make an indirect */        \
          /* call, so as to guarantee that there is only one call site */         \
          /* within each function.  This does assume that the compiler */         \
          /* won't transform it back to the simple direct-call form. */           \
          /* To discourage it from doing so, the call is bracketed with */        \
          /* __asm__ __volatile__ sections so as to make it not-movable. */       \
          bool (*nextFn)(LUL*, const char*, const char*) = NULL;                  \
          switch (*strP) {                                                        \
            case '1':                                                             \
              nextFn = TestFn1;                                                   \
              break;                                                              \
            case '2':                                                             \
              nextFn = TestFn2;                                                   \
              break;                                                              \
            case '3':                                                             \
              nextFn = TestFn3;                                                   \
              break;                                                              \
            case '4':                                                             \
              nextFn = TestFn4;                                                   \
              break;                                                              \
            case '5':                                                             \
              nextFn = TestFn5;                                                   \
              break;                                                              \
            case '6':                                                             \
              nextFn = TestFn6;                                                   \
              break;                                                              \
            case '7':                                                             \
              nextFn = TestFn7;                                                   \
              break;                                                              \
            case '8':                                                             \
              nextFn = TestFn8;                                                   \
              break;                                                              \
            default:                                                              \
              nextFn = TestFn8;                                                   \
              break;                                                              \
          }                                                                       \
          /* "use" |space| immediately after the recursive call, */               \
          /* so as to dissuade clang from deallocating the space while */         \
          /* the call is active, or otherwise messing with the stack frame. */    \
          __asm__ __volatile__("" ::: "cc", "memory");                            \
          bool passed = nextFn(aLUL, strPorig, strP + 1);                         \
          Unused << write(1, space, 0);                                           \
          __asm__ __volatile__("" ::: "cc", "memory");                            \
          return passed;                                                          \
        }                                                                         \
      }

    // The test functions are mutually recursive, so it is necessary to
    // declare them before defining them.
    DECL_TEST_FN(TestFn1)
    DECL_TEST_FN(TestFn2)
    DECL_TEST_FN(TestFn3)
    DECL_TEST_FN(TestFn4)
    DECL_TEST_FN(TestFn5)
    DECL_TEST_FN(TestFn6)
    DECL_TEST_FN(TestFn7)
    DECL_TEST_FN(TestFn8)

    GEN_TEST_FN(TestFn1, 123)
    GEN_TEST_FN(TestFn2, 456)
    GEN_TEST_FN(TestFn3, 789)
    GEN_TEST_FN(TestFn4, 23)
    GEN_TEST_FN(TestFn5, 47)
    GEN_TEST_FN(TestFn6, 117)
    GEN_TEST_FN(TestFn7, 1)
    GEN_TEST_FN(TestFn8, 99)

    // This starts the test sequence going.  Call here to generate a
    // sequence of calls as directed by the string |dstring|.  The call
    // sequence will, from its innermost frame, finish by calling
    // GetAndCheckStackTrace() and passing it |dstring|.
    // GetAndCheckStackTrace() will unwind the stack, check consistency
    // of those results against |dstring|, and print a pass/fail message
    // to aLUL's logging sink.  It also updates the counters in *aNTests
    // and aNTestsPassed.
    __attribute__((noinline)) void TestUnw(/*OUT*/ int* aNTests,
                                           /*OUT*/ int* aNTestsPassed, LUL* aLUL,
                                           const char* dstring) {
      // Ensure that the stack has at least this much space on it.  This
      // makes it safe to saw off the top LUL_UNIT_TEST_STACK_SIZE bytes
      // and hand it to LUL.  Safe in the sense that no segfault can
      // happen because the stack is at least this big.  This is all
      // somewhat dubious in the sense that a sufficiently clever compiler
      // (clang, for one) can figure out that space[] is unused and delete
      // it from the frame.  Hence the somewhat elaborate hoop jumping to
      // fill it up before the call and to at least appear to use the
      // value afterwards.
      int i;
      volatile char space[LUL_UNIT_TEST_STACK_SIZE];
      for (i = 0; i < LUL_UNIT_TEST_STACK_SIZE; i++) {
        space[i] = (char)(i & 0x7F);
      }

      // Really run the test.
      bool passed = TestFn1(aLUL, dstring, dstring);

      // Appear to use space[], by visiting the value to compute some kind
      // of checksum, and then (apparently) using the checksum.
      int sum = 0;
      for (i = 0; i < LUL_UNIT_TEST_STACK_SIZE; i++) {
        // If this doesn't fool LLVM, I don't know what will.
        sum += space[i] - 3 * i;
      }
      __asm__ __volatile__("" : : "r"(sum));

      // Update the counters.
      (*aNTests)++;
      if (passed) {
        (*aNTestsPassed)++;
      }
    }

    void RunLulUnitTests(/*OUT*/ int* aNTests, /*OUT*/ int* aNTestsPassed,
                         LUL* aLUL) {
      aLUL->mLog(":\n");
      aLUL->mLog("LULUnitTest: BEGIN\n");
      *aNTests = *aNTestsPassed = 0;
      TestUnw(aNTests, aNTestsPassed, aLUL, "11111111");
      TestUnw(aNTests, aNTestsPassed, aLUL, "11222211");
      TestUnw(aNTests, aNTestsPassed, aLUL, "111222333");
      TestUnw(aNTests, aNTestsPassed, aLUL, "1212121231212331212121212121212");
      TestUnw(aNTests, aNTestsPassed, aLUL, "31415827271828325332173258");
      TestUnw(aNTests, aNTestsPassed, aLUL,
              "123456781122334455667788777777777777777777777");
      aLUL->mLog("LULUnitTest: END\n");
      aLUL->mLog(":\n");
    }

    }  // namespace lul

## Minimal test - lines (58, 102)

path: .spaces[0].spaces[0].metrics.mi.mi_original
old: null
new: 71.16975773973967
path: .spaces[0].spaces[0].metrics.mi.mi_visual_studio
old: null
new: 41.619741368268805
path: .spaces[0].spaces[0].metrics.mi.mi_sei
old: null
new: 28.808162029658902
path: .spaces[0].spaces[0].metrics.nexits.average
old: null
new: 17.0
path: .spaces[0].spaces[0].metrics.nexits.sum
old: 0.0
new: 17.0
path: .spaces[0].spaces[0].metrics.loc.ploc
old: 1.0
new: 45.0
path: .spaces[0].spaces[0].metrics.loc.lloc
old: 0.0
new: 35.0
path: .spaces[0].spaces[0].metrics.loc.sloc
old: 1.0
new: 45.0
path: .spaces[0].spaces[0].metrics.nom.functions
old: 0.0
new: 1.0
path: .spaces[0].spaces[0].metrics.nom.total
old: 0.0
new: 1.0
path: .spaces[0].spaces[0].metrics.cognitive.average
old: null
new: 2.0
path: .spaces[0].spaces[0].metrics.cognitive.sum
old: 0.0
new: 2.0
path: .spaces[0].spaces[0].metrics.nargs.average
old: null
new: 0.0
path: .spaces[0].spaces[0].metrics.cyclomatic.average
old: 1.0
new: 18.0
path: .spaces[0].spaces[0].metrics.cyclomatic.sum
old: 1.0
new: 18.0
path: .spaces[0].spaces[0].metrics.halstead.length
old: 1.0
new: 123.0
path: .spaces[0].spaces[0].metrics.halstead.N2
old: 1.0
new: 41.0
path: .spaces[0].spaces[0].metrics.halstead.n2
old: 1.0
new: 39.0
path: .spaces[0].spaces[0].metrics.halstead.effort
old: 0.0
new: 4013.8671078894017
path: .spaces[0].spaces[0].metrics.halstead.vocabulary
old: 1.0
new: 50.0
path: .spaces[0].spaces[0].metrics.halstead.estimated_program_length
old: null
new: 244.18443434063795
path: .spaces[0].spaces[0].metrics.halstead.volume
old: 0.0
new: 694.1943113422911
path: .spaces[0].spaces[0].metrics.halstead.difficulty
old: 0.0
new: 5.782051282051282
path: .spaces[0].spaces[0].metrics.halstead.level
old: null
new: 0.1729490022172949
path: .spaces[0].spaces[0].metrics.halstead.purity_ratio
old: null
new: 1.9852393035824225
path: .spaces[0].spaces[0].metrics.halstead.N1
old: 0.0
new: 82.0
path: .spaces[0].spaces[0].metrics.halstead.time
old: 0.0
new: 222.9926171049667
path: .spaces[0].spaces[0].metrics.halstead.bugs
old: 0.0
new: 0.08418875201199857
path: .spaces[0].spaces[0].metrics.halstead.n1
old: 0.0
new: 11.0

### Code

    static const char* NameOf_DW_REG(int16_t aReg) {
      switch (aReg) {
        case DW_REG_CFA:
          return "cfa";
    #if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
        case DW_REG_INTEL_XBP:
          return "xbp";
        case DW_REG_INTEL_XSP:
          return "xsp";
        case DW_REG_INTEL_XIP:
          return "xip";
    #elif defined(GP_ARCH_arm)
        case DW_REG_ARM_R7:
          return "r7";
        case DW_REG_ARM_R11:
          return "r11";
        case DW_REG_ARM_R12:
          return "r12";
        case DW_REG_ARM_R13:
          return "r13";
        case DW_REG_ARM_R14:
          return "r14";
        case DW_REG_ARM_R15:
          return "r15";
    #elif defined(GP_ARCH_arm64)
        case DW_REG_AARCH64_X29:
          return "x29";
        case DW_REG_AARCH64_X30:
          return "x30";
        case DW_REG_AARCH64_SP:
          return "sp";
    #elif defined(GP_ARCH_mips64)
        case DW_REG_MIPS_SP:
          return "sp";
        case DW_REG_MIPS_FP:
          return "fp";
        case DW_REG_MIPS_PC:
          return "pc";
    #else
    #  error "Unsupported arch"
    #endif
        default:
          return "???";
      }
    }