summaryrefslogtreecommitdiffstats
path: root/tools/profiler/lul/LulMainInt.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /tools/profiler/lul/LulMainInt.h
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/profiler/lul/LulMainInt.h')
-rw-r--r--tools/profiler/lul/LulMainInt.h630
1 files changed, 630 insertions, 0 deletions
diff --git a/tools/profiler/lul/LulMainInt.h b/tools/profiler/lul/LulMainInt.h
new file mode 100644
index 0000000000..f32c56e84f
--- /dev/null
+++ b/tools/profiler/lul/LulMainInt.h
@@ -0,0 +1,630 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef LulMainInt_h
+#define LulMainInt_h
+
+#include "PlatformMacros.h"
+#include "LulMain.h" // for TaggedUWord
+
+#include <string>
+#include <vector>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/HashTable.h"
+
+// This file provides an internal interface inside LUL. If you are an
+// end-user of LUL, do not include it in your code. The end-user
+// interface is in LulMain.h.
+
+namespace lul {
+
+using std::vector;
+
+////////////////////////////////////////////////////////////////
+// DW_REG_ constants //
+////////////////////////////////////////////////////////////////
+
+// These are the Dwarf CFI register numbers, as (presumably) defined
+// in the ELF ABI supplements for each architecture.
+
+enum DW_REG_NUMBER {
+ // No real register has this number. It's convenient to be able to
+ // treat the CFA (Canonical Frame Address) as "just another
+ // register", though.
+ DW_REG_CFA = -1,
+#if defined(GP_ARCH_arm)
+ // ARM registers
+ DW_REG_ARM_R7 = 7,
+ DW_REG_ARM_R11 = 11,
+ DW_REG_ARM_R12 = 12,
+ DW_REG_ARM_R13 = 13,
+ DW_REG_ARM_R14 = 14,
+ DW_REG_ARM_R15 = 15,
+#elif defined(GP_ARCH_arm64)
+ // aarch64 registers
+ DW_REG_AARCH64_X29 = 29,
+ DW_REG_AARCH64_X30 = 30,
+ DW_REG_AARCH64_SP = 31,
+#elif defined(GP_ARCH_amd64)
+ // Because the X86 (32 bit) and AMD64 (64 bit) summarisers are
+ // combined, a merged set of register constants is needed.
+ DW_REG_INTEL_XBP = 6,
+ DW_REG_INTEL_XSP = 7,
+ DW_REG_INTEL_XIP = 16,
+#elif defined(GP_ARCH_x86)
+ DW_REG_INTEL_XBP = 5,
+ DW_REG_INTEL_XSP = 4,
+ DW_REG_INTEL_XIP = 8,
+#elif defined(GP_ARCH_mips64)
+ DW_REG_MIPS_SP = 29,
+ DW_REG_MIPS_FP = 30,
+ DW_REG_MIPS_PC = 34,
+#else
+# error "Unknown arch"
+#endif
+};
+
+////////////////////////////////////////////////////////////////
+// PfxExpr //
+////////////////////////////////////////////////////////////////
+
+enum PfxExprOp {
+ // meaning of mOperand effect on stack
+ PX_Start, // bool start-with-CFA? start, with CFA on stack, or not
+ PX_End, // none stop; result is at top of stack
+ PX_SImm32, // int32 push signed int32
+ PX_DwReg, // DW_REG_NUMBER push value of the specified reg
+ PX_Deref, // none pop X ; push *X
+ PX_Add, // none pop X ; pop Y ; push Y + X
+ PX_Sub, // none pop X ; pop Y ; push Y - X
+ PX_And, // none pop X ; pop Y ; push Y & X
+ PX_Or, // none pop X ; pop Y ; push Y | X
+ PX_CmpGES, // none pop X ; pop Y ; push (Y >=s X) ? 1 : 0
+ PX_Shl // none pop X ; pop Y ; push Y << X
+};
+
+struct PfxInstr {
+ PfxInstr(PfxExprOp opcode, int32_t operand)
+ : mOpcode(opcode), mOperand(operand) {}
+ explicit PfxInstr(PfxExprOp opcode) : mOpcode(opcode), mOperand(0) {}
+ bool operator==(const PfxInstr& other) const {
+ return mOpcode == other.mOpcode && mOperand == other.mOperand;
+ }
+ PfxExprOp mOpcode;
+ int32_t mOperand;
+};
+
+static_assert(sizeof(PfxInstr) <= 8, "PfxInstr size changed unexpectedly");
+
+// Evaluate the prefix expression whose PfxInstrs start at aPfxInstrs[start].
+// In the case of any mishap (stack over/underflow, running off the end of
+// the instruction vector, obviously malformed sequences),
+// return an invalid TaggedUWord.
+// RUNS IN NO-MALLOC CONTEXT
+TaggedUWord EvaluatePfxExpr(int32_t start, const UnwindRegs* aOldRegs,
+ TaggedUWord aCFA, const StackImage* aStackImg,
+ const vector<PfxInstr>& aPfxInstrs);
+
+////////////////////////////////////////////////////////////////
+// LExpr //
+////////////////////////////////////////////////////////////////
+
+// An expression -- very primitive. Denotes either "register +
+// offset", a dereferenced version of the same, or a reference to a
+// prefix expression stored elsewhere. So as to allow convenient
+// handling of Dwarf-derived unwind info, the register may also denote
+// the CFA. A large number of these need to be stored, so we ensure
+// it fits into 8 bytes. See comment below on RuleSet to see how
+// expressions fit into the bigger picture.
+
+enum LExprHow {
+ UNKNOWN = 0, // This LExpr denotes no value.
+ NODEREF, // Value is (mReg + mOffset).
+ DEREF, // Value is *(mReg + mOffset).
+ PFXEXPR // Value is EvaluatePfxExpr(secMap->mPfxInstrs[mOffset])
+};
+
+inline static const char* NameOf_LExprHow(LExprHow how) {
+ switch (how) {
+ case UNKNOWN:
+ return "UNKNOWN";
+ case NODEREF:
+ return "NODEREF";
+ case DEREF:
+ return "DEREF";
+ case PFXEXPR:
+ return "PFXEXPR";
+ default:
+ return "LExpr-??";
+ }
+}
+
+struct LExpr {
+ // Denotes an expression with no value.
+ LExpr() : mHow(UNKNOWN), mReg(0), mOffset(0) {}
+
+ // Denotes any expressible expression.
+ LExpr(LExprHow how, int16_t reg, int32_t offset)
+ : mHow(how), mReg(reg), mOffset(offset) {
+ switch (how) {
+ case UNKNOWN:
+ MOZ_ASSERT(reg == 0 && offset == 0);
+ break;
+ case NODEREF:
+ break;
+ case DEREF:
+ break;
+ case PFXEXPR:
+ MOZ_ASSERT(reg == 0 && offset >= 0);
+ break;
+ default:
+ MOZ_RELEASE_ASSERT(0, "LExpr::LExpr: invalid how");
+ }
+ }
+
+ // Hash it, carefully looking only at defined parts.
+ mozilla::HashNumber hash() const {
+ mozilla::HashNumber h = mHow;
+ switch (mHow) {
+ case UNKNOWN:
+ break;
+ case NODEREF:
+ case DEREF:
+ h = mozilla::AddToHash(h, mReg);
+ h = mozilla::AddToHash(h, mOffset);
+ break;
+ case PFXEXPR:
+ h = mozilla::AddToHash(h, mOffset);
+ break;
+ default:
+ MOZ_RELEASE_ASSERT(0, "LExpr::hash: invalid how");
+ }
+ return h;
+ }
+
+ // And structural equality.
+ bool equals(const LExpr& other) const {
+ if (mHow != other.mHow) {
+ return false;
+ }
+ switch (mHow) {
+ case UNKNOWN:
+ return true;
+ case NODEREF:
+ case DEREF:
+ return mReg == other.mReg && mOffset == other.mOffset;
+ case PFXEXPR:
+ return mOffset == other.mOffset;
+ default:
+ MOZ_RELEASE_ASSERT(0, "LExpr::equals: invalid how");
+ }
+ }
+
+ // Change the offset for an expression that references memory.
+ LExpr add_delta(long delta) {
+ MOZ_ASSERT(mHow == NODEREF);
+ // If this is a non-debug build and the above assertion would have
+ // failed, at least return LExpr() so that the machinery that uses
+ // the resulting expression fails in a repeatable way.
+ return (mHow == NODEREF) ? LExpr(mHow, mReg, mOffset + delta)
+ : LExpr(); // Gone bad
+ }
+
+ // Dereference an expression that denotes a memory address.
+ LExpr deref() {
+ MOZ_ASSERT(mHow == NODEREF);
+ // Same rationale as for add_delta().
+ return (mHow == NODEREF) ? LExpr(DEREF, mReg, mOffset)
+ : LExpr(); // Gone bad
+ }
+
+ // Print a rule for recovery of |aNewReg| whose recovered value
+ // is this LExpr.
+ std::string ShowRule(const char* aNewReg) const;
+
+ // Evaluate this expression, producing a TaggedUWord. |aOldRegs|
+ // holds register values that may be referred to by the expression.
+ // |aCFA| holds the CFA value, if any, that applies. |aStackImg|
+ // contains a chuck of stack that will be consulted if the expression
+ // references memory. |aPfxInstrs| holds the vector of PfxInstrs
+ // that will be consulted if this is a PFXEXPR.
+ // RUNS IN NO-MALLOC CONTEXT
+ TaggedUWord EvaluateExpr(const UnwindRegs* aOldRegs, TaggedUWord aCFA,
+ const StackImage* aStackImg,
+ const vector<PfxInstr>* aPfxInstrs) const;
+
+ // Representation of expressions. If |mReg| is DW_REG_CFA (-1) then
+ // it denotes the CFA. All other allowed values for |mReg| are
+ // nonnegative and are DW_REG_ values.
+ LExprHow mHow : 8;
+ int16_t mReg; // A DW_REG_ value
+ int32_t mOffset; // 32-bit signed offset should be more than enough.
+};
+
+static_assert(sizeof(LExpr) <= 8, "LExpr size changed unexpectedly");
+
+////////////////////////////////////////////////////////////////
+// RuleSet //
+////////////////////////////////////////////////////////////////
+
+// This is platform-dependent. It describes how to recover the CFA and then
+// how to recover the registers for the previous frame. Such "recipes" are
+// specific to particular ranges of machine code, but the associated range
+// is not stored in RuleSet, because in general each RuleSet may be used
+// for many such range fragments ("extents"). See the comments below for
+// Extent and SecMap.
+//
+// The set of LExprs contained in a given RuleSet describe a DAG which
+// says how to compute the caller's registers ("new registers") from
+// the callee's registers ("old registers"). The DAG can contain a
+// single internal node, which is the value of the CFA for the callee.
+// It would be possible to construct a DAG that omits the CFA, but
+// including it makes the summarisers simpler, and the Dwarf CFI spec
+// has the CFA as a central concept.
+//
+// For this to make sense, |mCfaExpr| can't have
+// |mReg| == DW_REG_CFA since we have no previous value for the CFA.
+// All of the other |Expr| fields can -- and usually do -- specify
+// |mReg| == DW_REG_CFA.
+//
+// With that in place, the unwind algorithm proceeds as follows.
+//
+// (0) Initially: we have values for the old registers, and a memory
+// image.
+//
+// (1) Compute the CFA by evaluating |mCfaExpr|. Add the computed
+// value to the set of "old registers".
+//
+// (2) Compute values for the registers by evaluating all of the other
+// |Expr| fields in the RuleSet. These can depend on both the old
+// register values and the just-computed CFA.
+//
+// If we are unwinding without computing a CFA, perhaps because the
+// RuleSets are derived from EXIDX instead of Dwarf, then
+// |mCfaExpr.mHow| will be LExpr::UNKNOWN, so the computed value will
+// be invalid -- that is, TaggedUWord() -- and so any attempt to use
+// that will result in the same value. But that's OK because the
+// RuleSet would make no sense if depended on the CFA but specified no
+// way to compute it.
+//
+// A RuleSet is not allowed to cover zero address range. Having zero
+// length would break binary searching in SecMaps and PriMaps.
+
+class RuleSet {
+ public:
+ RuleSet();
+ void Print(uintptr_t avma, uintptr_t len, void (*aLog)(const char*)) const;
+
+ // Find the LExpr* for a given DW_REG_ value in this class.
+ LExpr* ExprForRegno(DW_REG_NUMBER aRegno);
+
+ // How to compute the CFA.
+ LExpr mCfaExpr;
+ // How to compute caller register values. These may reference the
+ // value defined by |mCfaExpr|.
+#if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
+ LExpr mXipExpr; // return address
+ LExpr mXspExpr;
+ LExpr mXbpExpr;
+#elif defined(GP_ARCH_arm)
+ LExpr mR15expr; // return address
+ LExpr mR14expr;
+ LExpr mR13expr;
+ LExpr mR12expr;
+ LExpr mR11expr;
+ LExpr mR7expr;
+#elif defined(GP_ARCH_arm64)
+ LExpr mX29expr; // frame pointer register
+ LExpr mX30expr; // link register
+ LExpr mSPexpr;
+#elif defined(GP_ARCH_mips64)
+ LExpr mPCexpr;
+ LExpr mFPexpr;
+ LExpr mSPexpr;
+#else
+# error "Unknown arch"
+#endif
+
+ // Machinery in support of hashing.
+ typedef RuleSet Lookup;
+
+ static mozilla::HashNumber hash(RuleSet rs) {
+ mozilla::HashNumber h = rs.mCfaExpr.hash();
+#if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
+ h = mozilla::AddToHash(h, rs.mXipExpr.hash());
+ h = mozilla::AddToHash(h, rs.mXspExpr.hash());
+ h = mozilla::AddToHash(h, rs.mXbpExpr.hash());
+#elif defined(GP_ARCH_arm)
+ h = mozilla::AddToHash(h, rs.mR15expr.hash());
+ h = mozilla::AddToHash(h, rs.mR14expr.hash());
+ h = mozilla::AddToHash(h, rs.mR13expr.hash());
+ h = mozilla::AddToHash(h, rs.mR12expr.hash());
+ h = mozilla::AddToHash(h, rs.mR11expr.hash());
+ h = mozilla::AddToHash(h, rs.mR7expr.hash());
+#elif defined(GP_ARCH_arm64)
+ h = mozilla::AddToHash(h, rs.mX29expr.hash());
+ h = mozilla::AddToHash(h, rs.mX30expr.hash());
+ h = mozilla::AddToHash(h, rs.mSPexpr.hash());
+#elif defined(GP_ARCH_mips64)
+ h = mozilla::AddToHash(h, rs.mPCexpr.hash());
+ h = mozilla::AddToHash(h, rs.mFPexpr.hash());
+ h = mozilla::AddToHash(h, rs.mSPexpr.hash());
+#else
+# error "Unknown arch"
+#endif
+ return h;
+ }
+
+ static bool match(const RuleSet& rs1, const RuleSet& rs2) {
+ return rs1.mCfaExpr.equals(rs2.mCfaExpr) &&
+#if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
+ rs1.mXipExpr.equals(rs2.mXipExpr) &&
+ rs1.mXspExpr.equals(rs2.mXspExpr) &&
+ rs1.mXbpExpr.equals(rs2.mXbpExpr);
+#elif defined(GP_ARCH_arm)
+ rs1.mR15expr.equals(rs2.mR15expr) &&
+ rs1.mR14expr.equals(rs2.mR14expr) &&
+ rs1.mR13expr.equals(rs2.mR13expr) &&
+ rs1.mR12expr.equals(rs2.mR12expr) &&
+ rs1.mR11expr.equals(rs2.mR11expr) && rs1.mR7expr.equals(rs2.mR7expr);
+#elif defined(GP_ARCH_arm64)
+ rs1.mX29expr.equals(rs2.mX29expr) &&
+ rs1.mX30expr.equals(rs2.mX30expr) && rs1.mSPexpr.equals(rs2.mSPexpr);
+#elif defined(GP_ARCH_mips64)
+ rs1.mPCexpr.equals(rs2.mPCexpr) && rs1.mFPexpr.equals(rs2.mFPexpr) &&
+ rs1.mSPexpr.equals(rs2.mSPexpr);
+#else
+# error "Unknown arch"
+#endif
+ }
+};
+
+// Returns |true| for Dwarf register numbers which are members
+// of the set of registers that LUL unwinds on this target.
+static inline bool registerIsTracked(DW_REG_NUMBER reg) {
+ switch (reg) {
+#if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
+ case DW_REG_INTEL_XBP:
+ case DW_REG_INTEL_XSP:
+ case DW_REG_INTEL_XIP:
+ return true;
+#elif defined(GP_ARCH_arm)
+ case DW_REG_ARM_R7:
+ case DW_REG_ARM_R11:
+ case DW_REG_ARM_R12:
+ case DW_REG_ARM_R13:
+ case DW_REG_ARM_R14:
+ case DW_REG_ARM_R15:
+ return true;
+#elif defined(GP_ARCH_arm64)
+ case DW_REG_AARCH64_X29:
+ case DW_REG_AARCH64_X30:
+ case DW_REG_AARCH64_SP:
+ return true;
+#elif defined(GP_ARCH_mips64)
+ case DW_REG_MIPS_FP:
+ case DW_REG_MIPS_SP:
+ case DW_REG_MIPS_PC:
+ return true;
+#else
+# error "Unknown arch"
+#endif
+ default:
+ return false;
+ }
+}
+
+////////////////////////////////////////////////////////////////
+// Extent //
+////////////////////////////////////////////////////////////////
+
+struct Extent {
+ // Three fields, which together take 8 bytes.
+ uint32_t mOffset;
+ uint16_t mLen;
+ uint16_t mDictIx;
+
+ // What this means is: suppose we are looking for the unwind rules for some
+ // code address (AVMA) `avma`. If we can find some SecMap `secmap` such
+ // that `avma` falls in the range
+ //
+ // `[secmap.mMapMinAVMA, secmap.mMapMaxAVMA]`
+ //
+ // then the RuleSet to use is `secmap.mDictionary[dictIx]` iff we can find
+ // an `extent` in `secmap.mExtents` such that `avma` falls into the range
+ //
+ // `[secmap.mMapMinAVMA + extent.offset(),
+ // secmap.mMapMinAVMA + extent.offset() + extent.len())`.
+ //
+ // Packing Extent into the minimum space is important, since there will be
+ // huge numbers of Extents -- around 3 million for libxul.so as of Sept
+ // 2020. Here, we aim for an 8-byte size, with the field sizes chosen
+ // carefully, as follows:
+ //
+ // `offset` denotes a byte offset inside the text section for some shared
+ // object. libxul.so is by far the largest. As of Sept 2020 it has a text
+ // size of up to around 120MB, that is, close to 2^27 bytes. Hence a 32-bit
+ // `offset` field gives a safety margin of around a factor of 32
+ // (== 2 ^(32 - 27)).
+ //
+ // `dictIx` indicates a unique `RuleSet` for some code address range.
+ // Experimentation on x86_64-linux indicates that only around 300 different
+ // `RuleSet`s exist, for libxul.so. A 16-bit bit field allows up to 65536
+ // to be recorded, hence leaving us a generous safety margin.
+ //
+ // `len` indicates the length of the associated address range.
+ //
+ // Note the representation becomes unusable if either `offset` overflows 32
+ // bits or `dictIx` overflows 16 bits. On the other hand, it does not
+ // matter (although is undesirable) if `len` overflows 16 bits, because in
+ // that case we can add multiple size-65535 entries to `secmap.mExtents` to
+ // cover the entire range. Hence the field sizes are biased so as to give a
+ // good safety margin for `offset` and `dictIx` at the cost of stealing bits
+ // from `len`. Almost all `len` values we will ever see in practice are
+ // 65535 or less, so stealing those bits does not matter much.
+ //
+ // If further compression is required, it would be feasible to implement
+ // Extent using 29 bits for the offset, 8 bits for the length and 11 bits
+ // for the dictionary index, giving a total of 6 bytes, provided that the
+ // data is packed into 3 uint16_t's. That would be a bit slower, though,
+ // due to the bit packing, and it would be more fragile, in the sense that
+ // it would fail for any object with more than 512MB of text segment, or
+ // with more than 2048 different `RuleSet`s. For the current (Sept 2020)
+ // libxul.so situation, though, it would work fine.
+
+ Extent(uint32_t offset, uint32_t len, uint32_t dictIx) {
+ MOZ_RELEASE_ASSERT(len < (1 << 16));
+ MOZ_RELEASE_ASSERT(dictIx < (1 << 16));
+ mOffset = offset;
+ mLen = len;
+ mDictIx = dictIx;
+ }
+ uint32_t offset() const { return mOffset; }
+ uint32_t len() const { return mLen; }
+ uint32_t dictIx() const { return mDictIx; }
+ void setLen(uint32_t len) {
+ MOZ_RELEASE_ASSERT(len < (1 << 16));
+ mLen = len;
+ }
+ void Print(void (*aLog)(const char*)) const {
+ char buf[64];
+ SprintfLiteral(buf, "Extent(offs=0x%x, len=%u, dictIx=%u)", this->offset(),
+ this->len(), this->dictIx());
+ aLog(buf);
+ }
+};
+
+static_assert(sizeof(Extent) == 8);
+
+////////////////////////////////////////////////////////////////
+// SecMap //
+////////////////////////////////////////////////////////////////
+
+// A SecMap may have zero address range, temporarily, whilst RuleSets
+// are being added to it. But adding a zero-range SecMap to a PriMap
+// will make it impossible to maintain the total order of the PriMap
+// entries, and so that can't be allowed to happen.
+
+class SecMap {
+ public:
+ // In the constructor, `mapStartAVMA` and `mapLen` define the actual
+ // (in-process) virtual addresses covered by the SecMap. All RuleSets
+ // subsequently added to it by calling `AddRuleSet` must fall into this
+ // address range, and attempts to add ones outside the range will be
+ // ignored. This restriction exists because the type Extent (see below)
+ // indicates an address range for a RuleSet, but for reasons of compactness,
+ // it does not contain the start address of the range. Instead, it contains
+ // a 32-bit offset from the base address of the SecMap. This is also the
+ // reason why the map's size is a `uint32_t` and not a `uintptr_t`.
+ //
+ // The effect is to limit this mechanism to shared objects / executables
+ // whose text section size does not exceed 4GB (2^32 bytes). Given that, as
+ // of Sept 2020, libxul.so's text section size is around 120MB, this does
+ // not seem like much of a limitation.
+ //
+ // From the supplied `mapStartAVMA` and `mapLen`, fields `mMapMinAVMA` and
+ // `mMapMaxAVMA` are calculated. It is intended that no two SecMaps owned
+ // by the same PriMap contain overlapping address ranges, and the PriMap
+ // logic enforces that.
+ //
+ // Some invariants:
+ //
+ // mExtents is nonempty
+ // <=> mMapMinAVMA <= mMapMaxAVMA
+ // && mMapMinAVMA <= apply_delta(mExtents[0].offset())
+ // && apply_delta(mExtents[#rulesets-1].offset()
+ // + mExtents[#rulesets-1].len() - 1) <= mMapMaxAVMA
+ // where
+ // apply_delta(off) = off + mMapMinAVMA
+ //
+ // This requires that no RuleSet has zero length.
+ //
+ // mExtents is empty
+ // <=> mMapMinAVMA > mMapMaxAVMA
+ //
+ // This doesn't constrain mMapMinAVMA and mMapMaxAVMA uniquely, so let's use
+ // mMapMinAVMA == 1 and mMapMaxAVMA == 0 to denote this case.
+
+ SecMap(uintptr_t mapStartAVMA, uint32_t mapLen, void (*aLog)(const char*));
+ ~SecMap();
+
+ // Binary search mRuleSets to find one that brackets |ia|, or nullptr
+ // if none is found. It's not allowable to do this until PrepareRuleSets
+ // has been called first.
+ RuleSet* FindRuleSet(uintptr_t ia);
+
+ // Add a RuleSet to the collection. The rule is copied in. Calling
+ // this makes the map non-searchable.
+ void AddRuleSet(const RuleSet* rs, uintptr_t avma, uintptr_t 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 AddPfxInstr(PfxInstr pfxi);
+
+ // Returns the entire vector of PfxInstrs.
+ const vector<PfxInstr>* GetPfxInstrs() { return &mPfxInstrs; }
+
+ // 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 PrepareRuleSets();
+
+ bool IsEmpty();
+
+ size_t Size() { return mExtents.size() + mDictionary.size(); }
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // The extent of this SecMap as a whole. The extents of all contained
+ // RuleSets must fall inside this. See comment above for details.
+ uintptr_t mMapMinAVMA;
+ uintptr_t mMapMaxAVMA;
+
+ private:
+ // False whilst adding entries; true once it is safe to call FindRuleSet.
+ // Transition (false->true) is caused by calling PrepareRuleSets().
+ bool mUsable;
+
+ // This is used to find and remove duplicate RuleSets while we are adding
+ // them to the SecMap. Almost all RuleSets are duplicates, so de-duping
+ // them is a huge space win. This is non-null while `mUsable` is false, and
+ // becomes null (is discarded) after the call to PrepareRuleSets, which
+ // copies all the entries into `mDictionary`.
+ mozilla::UniquePtr<
+ mozilla::HashMap<RuleSet, uint32_t, RuleSet, InfallibleAllocPolicy>>
+ mUniqifier;
+
+ // This will contain final contents of `mUniqifier`, but ordered
+ // (implicitly) by the `uint32_t` value fields, for fast access.
+ vector<RuleSet> mDictionary;
+
+ // A vector of Extents, sorted by offset value, nonoverlapping (post
+ // PrepareRuleSets()).
+ vector<Extent> mExtents;
+
+ // A vector of PfxInstrs, which are referred to by the RuleSets.
+ // These are provided as a representation of Dwarf expressions
+ // (DW_CFA_val_expression, DW_CFA_expression, DW_CFA_def_cfa_expression),
+ // are relatively expensive to evaluate, and and are therefore
+ // expected to be used only occasionally.
+ //
+ // The vector holds a bunch of separate PfxInstr programs, each one
+ // starting with a PX_Start and terminated by a PX_End, all
+ // concatenated together. When a RuleSet can't recover a value
+ // using a self-contained LExpr, it uses a PFXEXPR whose mOffset is
+ // the index in this vector of start of the necessary PfxInstr program.
+ vector<PfxInstr> mPfxInstrs;
+
+ // A logging sink, for debugging.
+ void (*mLog)(const char*);
+};
+
+} // namespace lul
+
+#endif // ndef LulMainInt_h