Bug 1464903 Part 3 - Record/replay utilities, r=froydnj.

--HG--
extra : rebase_source : 014d5737b4db1b4bb2de94c869bd4b9bcf88936d
This commit is contained in:
Brian Hackett 2018-07-22 11:42:50 +00:00
parent e6baf647d7
commit 20e02d2085
9 changed files with 1761 additions and 0 deletions

View File

@ -0,0 +1,108 @@
/* -*- 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 mozilla_recordreplay_ChunkAllocator_h
#define mozilla_recordreplay_ChunkAllocator_h
#include "SpinLock.h"
namespace mozilla {
namespace recordreplay {
// ChunkAllocator is a simple allocator class for creating objects which can be
// fetched by their integer id. Objects are stored as a linked list of arrays;
// like a linked list, existing entries can be accessed without taking or
// holding a lock, and using an array in each element mitigates the runtime
// cost of O(n) lookup.
//
// ChunkAllocator contents are never destroyed.
template <typename T>
class ChunkAllocator
{
struct Chunk;
typedef Atomic<Chunk*, SequentiallyConsistent, Behavior::DontPreserve> ChunkPointer;
// A page sized block holding a next pointer and an array of as many things
// as possible.
struct Chunk
{
uint8_t mStorage[PageSize - sizeof(Chunk*)];
ChunkPointer mNext;
Chunk() : mStorage{}, mNext(nullptr) {}
static size_t MaxThings() {
return sizeof(mStorage) / sizeof(T);
}
T* GetThing(size_t i) {
MOZ_RELEASE_ASSERT(i < MaxThings());
return reinterpret_cast<T*>(&mStorage[i * sizeof(T)]);
}
};
ChunkPointer mFirstChunk;
Atomic<size_t, SequentiallyConsistent, Behavior::DontPreserve> mCapacity;
SpinLock mLock;
void EnsureChunk(ChunkPointer* aChunk) {
if (!*aChunk) {
*aChunk = new Chunk();
mCapacity += Chunk::MaxThings();
}
}
ChunkAllocator(const ChunkAllocator&) = delete;
ChunkAllocator& operator=(const ChunkAllocator&) = delete;
public:
// ChunkAllocators are allocated in static storage and should not have
// constructors. Their memory will be initially zero.
ChunkAllocator() = default;
~ChunkAllocator() = default;
// Get an existing entry from the allocator.
inline T* Get(size_t aId) {
Chunk* chunk = mFirstChunk;
while (aId >= Chunk::MaxThings()) {
aId -= Chunk::MaxThings();
chunk = chunk->mNext;
}
return chunk->GetThing(aId);
}
// Get an existing entry from the allocator, or null. This may return an
// entry that has not been created yet.
inline T* MaybeGet(size_t aId) {
return (aId < mCapacity) ? Get(aId) : nullptr;
}
// Create a new entry with the specified ID. This must not be called on IDs
// that have already been used with this allocator.
inline T* Create(size_t aId) {
if (aId < mCapacity) {
T* res = Get(aId);
return new(res) T();
}
AutoSpinLock lock(mLock);
ChunkPointer* pchunk = &mFirstChunk;
while (aId >= Chunk::MaxThings()) {
aId -= Chunk::MaxThings();
EnsureChunk(pchunk);
Chunk* chunk = *pchunk;
pchunk = &chunk->mNext;
}
EnsureChunk(pchunk);
Chunk* chunk = *pchunk;
T* res = chunk->GetThing(aId);
return new(res) T();
}
};
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_ChunkAllocator_h

View File

@ -0,0 +1,446 @@
/* -*- 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/. */
#include "File.h"
#include "ipc/ChildIPC.h"
#include "mozilla/Compression.h"
#include "mozilla/Sprintf.h"
#include "ProcessRewind.h"
#include "SpinLock.h"
#include <algorithm>
namespace mozilla {
namespace recordreplay {
///////////////////////////////////////////////////////////////////////////////
// Stream
///////////////////////////////////////////////////////////////////////////////
void
Stream::ReadBytes(void* aData, size_t aSize)
{
MOZ_RELEASE_ASSERT(mFile->OpenForReading());
size_t totalRead = 0;
while (true) {
// Read what we can from the data buffer.
MOZ_RELEASE_ASSERT(mBufferPos <= mBufferLength);
size_t bufAvailable = mBufferLength - mBufferPos;
size_t bufRead = std::min(bufAvailable, aSize);
if (aData) {
memcpy(aData, &mBuffer[mBufferPos], bufRead);
aData = (char*)aData + bufRead;
}
mBufferPos += bufRead;
mStreamPos += bufRead;
totalRead += bufRead;
aSize -= bufRead;
if (!aSize) {
return;
}
MOZ_RELEASE_ASSERT(mBufferPos == mBufferLength);
// If we try to read off the end of a stream then we must have hit the end
// of the replay for this thread.
while (mChunkIndex == mChunks.length()) {
MOZ_RELEASE_ASSERT(mName == StreamName::Event || mName == StreamName::Assert);
HitEndOfRecording();
}
const StreamChunkLocation& chunk = mChunks[mChunkIndex++];
EnsureMemory(&mBallast, &mBallastSize, chunk.mCompressedSize, BallastMaxSize(),
DontCopyExistingData);
mFile->ReadChunk(mBallast.get(), chunk);
EnsureMemory(&mBuffer, &mBufferSize, chunk.mDecompressedSize, BUFFER_MAX,
DontCopyExistingData);
size_t bytesWritten;
if (!Compression::LZ4::decompress(mBallast.get(), chunk.mCompressedSize,
mBuffer.get(), chunk.mDecompressedSize, &bytesWritten) ||
bytesWritten != chunk.mDecompressedSize)
{
MOZ_CRASH();
}
mBufferPos = 0;
mBufferLength = chunk.mDecompressedSize;
}
}
bool
Stream::AtEnd()
{
MOZ_RELEASE_ASSERT(mFile->OpenForReading());
return mBufferPos == mBufferLength && mChunkIndex == mChunks.length();
}
void
Stream::WriteBytes(const void* aData, size_t aSize)
{
MOZ_RELEASE_ASSERT(mFile->OpenForWriting());
// Prevent the entire file from being flushed while we write this data.
AutoReadSpinLock streamLock(mFile->mStreamLock);
while (true) {
// Fill up the data buffer first.
MOZ_RELEASE_ASSERT(mBufferPos <= mBufferSize);
size_t bufAvailable = mBufferSize - mBufferPos;
size_t bufWrite = (bufAvailable < aSize) ? bufAvailable : aSize;
memcpy(&mBuffer[mBufferPos], aData, bufWrite);
mBufferPos += bufWrite;
mStreamPos += bufWrite;
if (bufWrite == aSize) {
return;
}
aData = (char*)aData + bufWrite;
aSize -= bufWrite;
// Grow the file's buffer if it is not at its maximum size.
if (mBufferSize < BUFFER_MAX) {
EnsureMemory(&mBuffer, &mBufferSize, mBufferSize + 1, BUFFER_MAX, CopyExistingData);
continue;
}
Flush(/* aTakeLock = */ true);
}
}
size_t
Stream::ReadScalar()
{
// Read back a pointer sized value using the same encoding as WriteScalar.
size_t value = 0, shift = 0;
while (true) {
uint8_t bits;
ReadBytes(&bits, 1);
value |= (size_t)(bits & 127) << shift;
if (!(bits & 128)) {
break;
}
shift += 7;
}
return value;
}
void
Stream::WriteScalar(size_t aValue)
{
// Pointer sized values are written out as unsigned values with an encoding
// optimized for small values. Each written byte successively captures 7 bits
// of data from the value, starting at the low end, with the high bit in the
// byte indicating whether there are any more non-zero bits in the value.
//
// With this encoding, values less than 2^7 (128) require one byte, values
// less than 2^14 (16384) require two bytes, and so forth, but negative
// numbers end up requiring ten bytes on a 64 bit architecture.
do {
uint8_t bits = aValue & 127;
aValue = aValue >> 7;
if (aValue) {
bits |= 128;
}
WriteBytes(&bits, 1);
} while (aValue);
}
void
Stream::CheckInput(size_t aValue)
{
size_t oldValue = aValue;
RecordOrReplayScalar(&oldValue);
if (oldValue != aValue) {
child::ReportFatalError("Input Mismatch: Recorded: %zu Replayed %zu\n", oldValue, aValue);
Unreachable();
}
}
void
Stream::EnsureMemory(UniquePtr<char[]>* aBuf, size_t* aSize,
size_t aNeededSize, size_t aMaxSize, ShouldCopy aCopy)
{
// Once a stream buffer grows, it never shrinks again. Buffers start out
// small because most streams are very small.
MOZ_RELEASE_ASSERT(!!*aBuf == !!*aSize);
MOZ_RELEASE_ASSERT(aNeededSize <= aMaxSize);
if (*aSize < aNeededSize) {
size_t newSize = std::min(std::max<size_t>(256, aNeededSize * 2), aMaxSize);
char* newBuf = new char[newSize];
if (*aBuf && aCopy == CopyExistingData) {
memcpy(newBuf, aBuf->get(), *aSize);
}
aBuf->reset(newBuf);
*aSize = newSize;
}
}
void
Stream::Flush(bool aTakeLock)
{
MOZ_RELEASE_ASSERT(mFile && mFile->OpenForWriting());
if (!mBufferPos) {
return;
}
size_t bound = Compression::LZ4::maxCompressedSize(mBufferPos);
EnsureMemory(&mBallast, &mBallastSize, bound, BallastMaxSize(),
DontCopyExistingData);
size_t compressedSize = Compression::LZ4::compress(mBuffer.get(), mBufferPos, mBallast.get());
MOZ_RELEASE_ASSERT(compressedSize != 0);
MOZ_RELEASE_ASSERT((size_t)compressedSize <= bound);
StreamChunkLocation chunk =
mFile->WriteChunk(mBallast.get(), compressedSize, mBufferPos, aTakeLock);
mChunks.append(chunk);
MOZ_ALWAYS_TRUE(++mChunkIndex == mChunks.length());
mBufferPos = 0;
}
/* static */ size_t
Stream::BallastMaxSize()
{
return Compression::LZ4::maxCompressedSize(BUFFER_MAX);
}
///////////////////////////////////////////////////////////////////////////////
// File
///////////////////////////////////////////////////////////////////////////////
// Information in a file index about a chunk.
struct FileIndexChunk
{
uint32_t /* StreamName */ mName;
uint32_t mNameIndex;
StreamChunkLocation mChunk;
FileIndexChunk()
{
PodZero(this);
}
FileIndexChunk(StreamName aName, uint32_t aNameIndex, const StreamChunkLocation& aChunk)
: mName((uint32_t) aName), mNameIndex(aNameIndex), mChunk(aChunk)
{}
};
// We expect to find this at every index in a file.
static const uint64_t MagicValue = 0xd3e7f5fae445b3ac;
// Index of chunks in a file. There is an index at the start of the file
// (which is always empty) and at various places within the file itself.
struct FileIndex
{
// This should match MagicValue.
uint64_t mMagic;
// How many FileIndexChunk instances follow this structure.
uint32_t mNumChunks;
// The location of the next index in the file, or zero.
uint64_t mNextIndexOffset;
explicit FileIndex(uint32_t aNumChunks)
: mMagic(MagicValue), mNumChunks(aNumChunks), mNextIndexOffset(0)
{}
};
bool
File::Open(const char* aName, Mode aMode)
{
MOZ_RELEASE_ASSERT(!mFd);
MOZ_RELEASE_ASSERT(aName);
mMode = aMode;
mFd = DirectOpenFile(aName, mMode == WRITE);
if (OpenForWriting()) {
// Write an empty index at the start of the file.
FileIndex index(0);
DirectWrite(mFd, &index, sizeof(index));
mWriteOffset += sizeof(index);
return true;
}
// Read in every index in the file.
ReadIndexResult result;
do {
result = ReadNextIndex(nullptr);
if (result == ReadIndexResult::InvalidFile) {
return false;
}
} while (result == ReadIndexResult::FoundIndex);
return true;
}
void
File::Close()
{
if (!mFd) {
return;
}
if (OpenForWriting()) {
Flush();
}
Clear();
}
File::ReadIndexResult
File::ReadNextIndex(InfallibleVector<Stream*>* aUpdatedStreams)
{
// Unlike in the Flush() case, we don't have to worry about other threads
// attempting to read data from streams in this file while we are reading
// the new index.
MOZ_ASSERT(OpenForReading());
// Read in the last index to see if there is another one.
DirectSeekFile(mFd, mLastIndexOffset + offsetof(FileIndex, mNextIndexOffset));
uint64_t nextIndexOffset;
if (DirectRead(mFd, &nextIndexOffset, sizeof(nextIndexOffset)) != sizeof(nextIndexOffset)) {
return ReadIndexResult::InvalidFile;
}
if (!nextIndexOffset) {
return ReadIndexResult::EndOfFile;
}
mLastIndexOffset = nextIndexOffset;
FileIndex index(0);
DirectSeekFile(mFd, nextIndexOffset);
if (DirectRead(mFd, &index, sizeof(index)) != sizeof(index)) {
return ReadIndexResult::InvalidFile;
}
if (index.mMagic != MagicValue) {
return ReadIndexResult::InvalidFile;
}
MOZ_RELEASE_ASSERT(index.mNumChunks);
size_t indexBytes = index.mNumChunks * sizeof(FileIndexChunk);
FileIndexChunk* chunks = new FileIndexChunk[index.mNumChunks];
if (DirectRead(mFd, chunks, indexBytes) != indexBytes) {
return ReadIndexResult::InvalidFile;
}
for (size_t i = 0; i < index.mNumChunks; i++) {
const FileIndexChunk& indexChunk = chunks[i];
Stream* stream = OpenStream((StreamName) indexChunk.mName, indexChunk.mNameIndex);
stream->mChunks.append(indexChunk.mChunk);
if (aUpdatedStreams) {
aUpdatedStreams->append(stream);
}
}
delete[] chunks;
return ReadIndexResult::FoundIndex;
}
bool
File::Flush()
{
MOZ_ASSERT(OpenForWriting());
AutoSpinLock lock(mLock);
InfallibleVector<FileIndexChunk> newChunks;
for (auto& vector : mStreams) {
for (const UniquePtr<Stream>& stream : vector) {
if (stream) {
stream->Flush(/* aTakeLock = */ false);
for (size_t i = stream->mFlushedChunks; i < stream->mChunkIndex; i++) {
newChunks.emplaceBack(stream->mName, stream->mNameIndex, stream->mChunks[i]);
}
stream->mFlushedChunks = stream->mChunkIndex;
}
}
}
if (newChunks.empty()) {
return false;
}
// Write the new index information at the end of the file.
uint64_t indexOffset = mWriteOffset;
size_t indexBytes = newChunks.length() * sizeof(FileIndexChunk);
FileIndex index(newChunks.length());
DirectWrite(mFd, &index, sizeof(index));
DirectWrite(mFd, newChunks.begin(), indexBytes);
mWriteOffset += sizeof(index) + indexBytes;
// Update the next index offset for the last index written.
MOZ_RELEASE_ASSERT(sizeof(index.mNextIndexOffset) == sizeof(indexOffset));
DirectSeekFile(mFd, mLastIndexOffset + offsetof(FileIndex, mNextIndexOffset));
DirectWrite(mFd, &indexOffset, sizeof(indexOffset));
DirectSeekFile(mFd, mWriteOffset);
mLastIndexOffset = indexOffset;
return true;
}
StreamChunkLocation
File::WriteChunk(const char* aStart,
size_t aCompressedSize, size_t aDecompressedSize,
bool aTakeLock)
{
Maybe<AutoSpinLock> lock;
if (aTakeLock) {
lock.emplace(mLock);
}
StreamChunkLocation chunk;
chunk.mOffset = mWriteOffset;
chunk.mCompressedSize = aCompressedSize;
chunk.mDecompressedSize = aDecompressedSize;
DirectWrite(mFd, aStart, aCompressedSize);
mWriteOffset += aCompressedSize;
return chunk;
}
void
File::ReadChunk(char* aDest, const StreamChunkLocation& aChunk)
{
AutoSpinLock lock(mLock);
DirectSeekFile(mFd, aChunk.mOffset);
size_t res = DirectRead(mFd, aDest, aChunk.mCompressedSize);
if (res != aChunk.mCompressedSize) {
MOZ_CRASH();
}
}
Stream*
File::OpenStream(StreamName aName, size_t aNameIndex)
{
AutoSpinLock lock(mLock);
auto& vector = mStreams[(size_t)aName];
while (aNameIndex >= vector.length()) {
vector.emplaceBack();
}
UniquePtr<Stream>& stream = vector[aNameIndex];
if (!stream) {
stream.reset(new Stream(this, aName, aNameIndex));
}
return stream.get();
}
} // namespace recordreplay
} // namespace mozilla

277
toolkit/recordreplay/File.h Normal file
View File

@ -0,0 +1,277 @@
/* -*- 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 mozilla_recordreplay_File_h
#define mozilla_recordreplay_File_h
#include "InfallibleVector.h"
#include "ProcessRecordReplay.h"
#include "SpinLock.h"
#include "mozilla/PodOperations.h"
#include "mozilla/RecordReplay.h"
#include "mozilla/UniquePtr.h"
namespace mozilla {
namespace recordreplay {
// Structure managing file I/O. Each file contains an index for a set of named
// streams, whose contents are compressed and interleaved throughout the file.
// Additionally, we directly manage the file handle and all associated memory.
// This makes it easier to restore memory snapshots without getting confused
// about the state of the file handles which the process has opened. Data
// written and read from files is automatically compressed with LZ4.
//
// Files are used internally for any disk accesses which the record/replay
// infrastructure needs to make. Currently, this is only for accessing the
// recording file.
//
// File is threadsafe for simultaneous read/read and write/write accesses.
// Stream is not threadsafe.
// A location of a chunk of a stream within a file.
struct StreamChunkLocation
{
// Offset into the file of the start of the chunk.
uint64_t mOffset;
// Compressed (stored) size of the chunk.
uint32_t mCompressedSize;
// Decompressed size of the chunk.
uint32_t mDecompressedSize;
inline bool operator == (const StreamChunkLocation& aOther) const {
return mOffset == aOther.mOffset
&& mCompressedSize == aOther.mCompressedSize
&& mDecompressedSize == aOther.mDecompressedSize;
}
};
enum class StreamName
{
Main,
Lock,
Event,
Assert,
Count
};
class File;
class Stream
{
friend class File;
// File this stream belongs to.
File* mFile;
// Prefix name for this stream.
StreamName mName;
// Index which, when combined to mName, uniquely identifies this stream in
// the file.
size_t mNameIndex;
// When writing, all chunks that have been flushed to disk. When reading, all
// chunks in the entire stream.
InfallibleVector<StreamChunkLocation> mChunks;
// Data buffer.
UniquePtr<char[]> mBuffer;
// The maximum number of bytes to buffer before compressing and writing to
// disk, and the maximum number of bytes that can be decompressed at once.
static const size_t BUFFER_MAX = 1024 * 1024;
// The capacity of mBuffer, at most BUFFER_MAX.
size_t mBufferSize;
// During reading, the number of accessible bytes in mBuffer.
size_t mBufferLength;
// The number of bytes read or written from mBuffer.
size_t mBufferPos;
// The number of uncompressed bytes read or written from the stream.
size_t mStreamPos;
// Any buffer available for use when decompressing or compressing data.
UniquePtr<char[]> mBallast;
size_t mBallastSize;
// The number of chunks that have been completely read or written. When
// writing, this equals mChunks.length().
size_t mChunkIndex;
// When writing, the number of chunks in this stream when the file was last
// flushed.
size_t mFlushedChunks;
Stream(File* aFile, StreamName aName, size_t aNameIndex)
: mFile(aFile)
, mName(aName)
, mNameIndex(aNameIndex)
, mBuffer(nullptr)
, mBufferSize(0)
, mBufferLength(0)
, mBufferPos(0)
, mStreamPos(0)
, mBallast(nullptr)
, mBallastSize(0)
, mChunkIndex(0)
, mFlushedChunks(0)
{}
public:
StreamName Name() const { return mName; }
size_t NameIndex() const { return mNameIndex; }
void ReadBytes(void* aData, size_t aSize);
void WriteBytes(const void* aData, size_t aSize);
size_t ReadScalar();
void WriteScalar(size_t aValue);
bool AtEnd();
inline void RecordOrReplayBytes(void* aData, size_t aSize) {
if (IsRecording()) {
WriteBytes(aData, aSize);
} else {
ReadBytes(aData, aSize);
}
}
template <typename T>
inline void RecordOrReplayScalar(T* aPtr) {
if (IsRecording()) {
WriteScalar((size_t)*aPtr);
} else {
*aPtr = (T)ReadScalar();
}
}
template <typename T>
inline void RecordOrReplayValue(T* aPtr) {
RecordOrReplayBytes(aPtr, sizeof(T));
}
// Make sure that a value is the same while replaying as it was while
// recording.
void CheckInput(size_t aValue);
// Add a thread event to this file. Each thread event in a file is followed
// by additional data specific to that event. Generally, CheckInput should be
// used while recording or replaying the data for a thread event so that any
// discrepancies with the recording are found immediately.
inline void RecordOrReplayThreadEvent(ThreadEvent aEvent) {
CheckInput((size_t)aEvent);
}
inline size_t StreamPosition() {
return mStreamPos;
}
private:
enum ShouldCopy {
DontCopyExistingData,
CopyExistingData
};
void EnsureMemory(UniquePtr<char[]>* aBuf, size_t* aSize, size_t aNeededSize, size_t aMaxSize,
ShouldCopy aCopy);
void Flush(bool aTakeLock);
static size_t BallastMaxSize();
};
class File
{
public:
enum Mode {
WRITE,
READ
};
friend class Stream;
private:
// Open file handle, or 0 if closed.
FileHandle mFd;
// Whether this file is open for writing or reading.
Mode mMode;
// When writing, the current offset into the file.
uint64_t mWriteOffset;
// The offset of the last index read or written to the file.
uint64_t mLastIndexOffset;
// All streams in this file, indexed by stream name and name index.
typedef InfallibleVector<UniquePtr<Stream>> StreamVector;
StreamVector mStreams[(size_t) StreamName::Count];
// Lock protecting access to this file.
SpinLock mLock;
// When writing, lock for synchronizing file flushes (writer) with other
// threads writing to streams in this file (readers).
ReadWriteSpinLock mStreamLock;
void Clear() {
mFd = 0;
mMode = READ;
mWriteOffset = 0;
mLastIndexOffset = 0;
for (auto& vector : mStreams) {
vector.clear();
}
PodZero(&mLock);
PodZero(&mStreamLock);
}
public:
File() { Clear(); }
~File() { Close(); }
bool Open(const char* aName, Mode aMode);
void Close();
bool OpenForWriting() const { return mFd && mMode == WRITE; }
bool OpenForReading() const { return mFd && mMode == READ; }
Stream* OpenStream(StreamName aName, size_t aNameIndex);
// Prevent/allow other threads to write to streams in this file.
void PreventStreamWrites() { mStreamLock.WriteLock(); }
void AllowStreamWrites() { mStreamLock.WriteUnlock(); }
// Flush any changes since the last Flush() call to disk, returning whether
// there were such changes.
bool Flush();
enum class ReadIndexResult {
InvalidFile,
EndOfFile,
FoundIndex
};
// Read any data added to the file by a Flush() call. aUpdatedStreams is
// optional and filled in with streams whose contents have changed, and may
// have duplicates.
ReadIndexResult ReadNextIndex(InfallibleVector<Stream*>* aUpdatedStreams);
private:
StreamChunkLocation WriteChunk(const char* aStart,
size_t aCompressedSize, size_t aDecompressedSize,
bool aTakeLock);
void ReadChunk(char* aDest, const StreamChunkLocation& aChunk);
};
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_File_h

View File

@ -0,0 +1,140 @@
/* -*- 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 mozilla_recordreplay_InfallibleVector_h
#define mozilla_recordreplay_InfallibleVector_h
#include "mozilla/Vector.h"
namespace mozilla {
namespace recordreplay {
// This file declares two classes, InfallibleVector and StaticInfallibleVector,
// which behave like normal vectors except that all their operations are
// infallible: we will immediately crash if any operation on the underlying
// vector fails.
//
// StaticInfallibleVector is designed for use in static storage, and does not
// have a static constructor or destructor in release builds.
template<typename Outer, typename T, size_t MinInlineCapacity, class AllocPolicy>
class InfallibleVectorOperations
{
typedef Vector<T, MinInlineCapacity, AllocPolicy> InnerVector;
InnerVector& Vector() { return static_cast<Outer*>(this)->Vector(); }
const InnerVector& Vector() const { return static_cast<const Outer*>(this)->Vector(); }
public:
size_t length() const { return Vector().length(); }
bool empty() const { return Vector().empty(); }
T* begin() { return Vector().begin(); }
const T* begin() const { return Vector().begin(); }
T* end() { return Vector().end(); }
const T* end() const { return Vector().end(); }
T& operator[](size_t aIndex) { return Vector()[aIndex]; }
const T& operator[](size_t aIndex) const { return Vector()[aIndex]; }
T& back() { return Vector().back(); }
const T& back() const { return Vector().back(); }
void popBack() { Vector().popBack(); }
T popCopy() { return Vector().popCopy(); }
void erase(T* aT) { Vector().erase(aT); }
void clear() { Vector().clear(); }
void reserve(size_t aRequest) {
if (!Vector().reserve(aRequest)) {
MOZ_CRASH();
}
}
void resize(size_t aNewLength) {
if (!Vector().resize(aNewLength)) {
MOZ_CRASH();
}
}
template<typename U> void append(U&& aU) {
if (!Vector().append(std::forward<U>(aU))) {
MOZ_CRASH();
}
}
template<typename U> void append(const U* aBegin, size_t aLength) {
if (!Vector().append(aBegin, aLength)) {
MOZ_CRASH();
}
}
void appendN(const T& aT, size_t aN) {
if (!Vector().appendN(aT, aN)) {
MOZ_CRASH();
}
}
template<typename... Args> void emplaceBack(Args&&... aArgs) {
if (!Vector().emplaceBack(std::forward<Args>(aArgs)...)) {
MOZ_CRASH();
}
}
template<typename... Args> void infallibleEmplaceBack(Args&&... aArgs) {
Vector().infallibleEmplaceBack(std::forward<Args>(aArgs)...);
}
template<typename U> void insert(T* aP, U&& aVal) {
if (!Vector().insert(aP, std::forward<U>(aVal))) {
MOZ_CRASH();
}
}
};
template<typename T,
size_t MinInlineCapacity = 0,
class AllocPolicy = MallocAllocPolicy>
class InfallibleVector
: public InfallibleVectorOperations<InfallibleVector<T, MinInlineCapacity, AllocPolicy>,
T, MinInlineCapacity, AllocPolicy>
{
typedef Vector<T, MinInlineCapacity, AllocPolicy> InnerVector;
InnerVector mVector;
public:
InnerVector& Vector() { return mVector; }
const InnerVector& Vector() const { return mVector; }
};
template<typename T,
size_t MinInlineCapacity = 0,
class AllocPolicy = MallocAllocPolicy>
class StaticInfallibleVector
: public InfallibleVectorOperations<StaticInfallibleVector<T, MinInlineCapacity, AllocPolicy>,
T, MinInlineCapacity, AllocPolicy>
{
typedef Vector<T, MinInlineCapacity, AllocPolicy> InnerVector;
mutable InnerVector* mVector;
void EnsureVector() const {
if (!mVector) {
// N.B. This class can only be used with alloc policies that have a
// default constructor.
AllocPolicy policy;
void* memory = policy.template pod_malloc<InnerVector>(1);
MOZ_RELEASE_ASSERT(memory);
mVector = new(memory) InnerVector();
}
}
public:
// InfallibleVectors are allocated in static storage and should not have
// constructors. Their memory will be initially zero.
InnerVector& Vector() { EnsureVector(); return *mVector; }
const InnerVector& Vector() const { EnsureVector(); return *mVector; }
};
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_InfallibleVector_h

View File

@ -0,0 +1,79 @@
/* -*- 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 mozilla_recordreplay_Monitor_h
#define mozilla_recordreplay_Monitor_h
#include "mozilla/PlatformConditionVariable.h"
namespace mozilla {
namespace recordreplay {
// Simple wrapper around mozglue mutexes and condvars. This is a lighter weight
// abstraction than mozilla::Monitor and has simpler interactions with the
// record/replay system.
class Monitor : public detail::MutexImpl
{
public:
Monitor()
: detail::MutexImpl(Behavior::DontPreserve)
{}
void Lock() { detail::MutexImpl::lock(); }
void Unlock() { detail::MutexImpl::unlock(); }
void Wait() { mCondVar.wait(*this); }
void Notify() { mCondVar.notify_one(); }
void NotifyAll() { mCondVar.notify_all(); }
void WaitUntil(TimeStamp aTime) {
AutoEnsurePassThroughThreadEvents pt;
mCondVar.wait_for(*this, aTime - TimeStamp::Now());
}
private:
detail::ConditionVariableImpl mCondVar;
};
// RAII class to lock a monitor.
struct MOZ_RAII MonitorAutoLock
{
explicit MonitorAutoLock(Monitor& aMonitor)
: mMonitor(aMonitor)
{
mMonitor.Lock();
}
~MonitorAutoLock()
{
mMonitor.Unlock();
}
private:
Monitor& mMonitor;
};
// RAII class to unlock a monitor.
struct MOZ_RAII MonitorAutoUnlock
{
explicit MonitorAutoUnlock(Monitor& aMonitor)
: mMonitor(aMonitor)
{
mMonitor.Unlock();
}
~MonitorAutoUnlock()
{
mMonitor.Lock();
}
private:
Monitor& mMonitor;
};
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_Monitor_h

View File

@ -0,0 +1,175 @@
/* -*- 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 mozilla_recordreplay_SpinLock_h
#define mozilla_recordreplay_SpinLock_h
#include "mozilla/Assertions.h"
#include "mozilla/Atomics.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/GuardObjects.h"
#include <sched.h>
namespace mozilla {
namespace recordreplay {
// This file provides a couple of primitive lock implementations that are
// implemented using atomic operations. Using these locks does not write to any
// heap locations other than the lock's members, nor will it call any system
// locking APIs. These locks are used in places where reentrance into APIs
// needs to be avoided, or where writes to heap memory are not allowed.
// A basic spin lock.
class SpinLock
{
public:
inline void Lock();
inline void Unlock();
private:
Atomic<bool, SequentiallyConsistent, Behavior::DontPreserve> mLocked;
};
// A basic read/write spin lock. This lock permits either multiple readers and
// no writers, or one writer.
class ReadWriteSpinLock
{
public:
inline void ReadLock();
inline void ReadUnlock();
inline void WriteLock();
inline void WriteUnlock();
private:
SpinLock mLock; // Protects mReaders.
int32_t mReaders; // -1 when in use for writing.
};
// RAII class to lock a spin lock.
struct MOZ_RAII AutoSpinLock
{
explicit AutoSpinLock(SpinLock& aLock)
: mLock(aLock)
{
mLock.Lock();
}
~AutoSpinLock()
{
mLock.Unlock();
}
private:
SpinLock& mLock;
};
// RAII class to lock a read/write spin lock for reading.
struct AutoReadSpinLock
{
explicit AutoReadSpinLock(ReadWriteSpinLock& aLock)
: mLock(aLock)
{
mLock.ReadLock();
}
~AutoReadSpinLock()
{
mLock.ReadUnlock();
}
private:
ReadWriteSpinLock& mLock;
};
// RAII class to lock a read/write spin lock for writing.
struct AutoWriteSpinLock
{
explicit AutoWriteSpinLock(ReadWriteSpinLock& aLock)
: mLock(aLock)
{
mLock.WriteLock();
}
~AutoWriteSpinLock()
{
mLock.WriteUnlock();
}
private:
ReadWriteSpinLock& mLock;
};
///////////////////////////////////////////////////////////////////////////////
// Inline definitions
///////////////////////////////////////////////////////////////////////////////
// Try to yield execution to another thread.
static inline void
ThreadYield()
{
sched_yield();
}
inline void
SpinLock::Lock()
{
while (mLocked.exchange(true)) {
ThreadYield();
}
}
inline void
SpinLock::Unlock()
{
DebugOnly<bool> rv = mLocked.exchange(false);
MOZ_ASSERT(rv);
}
inline void
ReadWriteSpinLock::ReadLock()
{
while (true) {
AutoSpinLock ex(mLock);
if (mReaders != -1) {
mReaders++;
return;
}
}
}
inline void
ReadWriteSpinLock::ReadUnlock()
{
AutoSpinLock ex(mLock);
MOZ_ASSERT(mReaders > 0);
mReaders--;
}
inline void
ReadWriteSpinLock::WriteLock()
{
while (true) {
AutoSpinLock ex(mLock);
if (mReaders == 0) {
mReaders = -1;
return;
}
}
}
inline void
ReadWriteSpinLock::WriteUnlock()
{
AutoSpinLock ex(mLock);
MOZ_ASSERT(mReaders == -1);
mReaders = 0;
}
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_SpinLock_h

View File

@ -0,0 +1,366 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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 mozilla_recordreplay_SplayTree_h
#define mozilla_recordreplay_SplayTree_h
#include "mozilla/Types.h"
#include "ProcessRecordReplay.h"
//#define ENABLE_COHERENCY_CHECKS
namespace mozilla {
namespace recordreplay {
/*
* Class which represents a splay tree with nodes allocated from an alloc
* policy.
*
* Splay trees are balanced binary search trees for which search, insert and
* remove are all amortized O(log n).
*
* T indicates the type of tree elements, L has a Lookup type and a static
* 'ssize_t compare(const L::Lookup&, const T&)' method ordering the elements.
*/
template <class T, class L, class AllocPolicy, size_t ChunkPages>
class SplayTree
{
struct Node {
T mItem;
Node* mLeft;
Node* mRight;
Node* mParent;
explicit Node(const T& aItem)
: mItem(aItem), mLeft(nullptr), mRight(nullptr), mParent(nullptr)
{}
};
AllocPolicy mAlloc;
Node* mRoot;
Node* mFreeList;
SplayTree(const SplayTree&) = delete;
SplayTree& operator=(const SplayTree&) = delete;
public:
explicit SplayTree(const AllocPolicy& aAlloc = AllocPolicy())
: mAlloc(aAlloc), mRoot(nullptr), mFreeList(nullptr)
{}
bool empty() const {
return !mRoot;
}
void clear() {
while (mRoot) {
remove(mRoot);
}
}
Maybe<T> maybeLookup(const typename L::Lookup& aLookup, bool aRemove = false) {
if (!mRoot) {
return Nothing();
}
Node* last = lookup(aLookup);
splay(last);
checkCoherency(mRoot, nullptr);
Maybe<T> res;
if (L::compare(aLookup, last->mItem) == 0) {
res = Some(last->mItem);
if (aRemove) {
remove(last);
}
}
return res;
}
// Lookup an item which matches aLookup, or the closest item less than it.
Maybe<T> lookupClosestLessOrEqual(const typename L::Lookup& aLookup, bool aRemove = false) {
if (!mRoot) {
return Nothing();
}
Node* last = lookup(aLookup);
Node* search = last;
while (search && L::compare(aLookup, search->mItem) < 0) {
search = search->mParent;
}
Maybe<T> res = search ? Some(search->mItem) : Nothing();
if (aRemove && search) {
remove(search);
} else {
splay(last);
}
checkCoherency(mRoot, nullptr);
return res;
}
void insert(const typename L::Lookup& aLookup, const T& aValue) {
MOZ_RELEASE_ASSERT(L::compare(aLookup, aValue) == 0);
Node* element = allocateNode(aValue);
if (!mRoot) {
mRoot = element;
return;
}
Node* last = lookup(aLookup);
ssize_t cmp = L::compare(aLookup, last->mItem);
Node** parentPointer;
if (cmp < 0) {
parentPointer = &last->mLeft;
} else if (cmp > 0) {
parentPointer = &last->mRight;
} else {
// The lookup matches an existing entry in the tree. Place it to the left
// of the element just looked up.
if (!last->mLeft) {
parentPointer = &last->mLeft;
} else {
last = last->mLeft;
while (last->mRight) {
last = last->mRight;
}
parentPointer = &last->mRight;
}
}
MOZ_RELEASE_ASSERT(!*parentPointer);
*parentPointer = element;
element->mParent = last;
splay(element);
checkCoherency(mRoot, nullptr);
}
class Iter {
friend class SplayTree;
SplayTree* mTree;
Node* mNode;
bool mRemoved;
Iter(SplayTree* aTree, Node* aNode)
: mTree(aTree), mNode(aNode), mRemoved(false)
{}
public:
const T& ref() {
return mNode->mItem;
}
bool done() {
return !mNode;
}
Iter& operator++() {
MOZ_RELEASE_ASSERT(!mRemoved);
if (mNode->mRight) {
mNode = mNode->mRight;
while (mNode->mLeft) {
mNode = mNode->mLeft;
}
} else {
while (true) {
Node* cur = mNode;
mNode = mNode->mParent;
if (!mNode || mNode->mLeft == cur) {
break;
}
}
}
return *this;
}
void removeEntry() {
mTree->remove(mNode);
mRemoved = true;
}
};
Iter begin() {
Node* node = mRoot;
while (node && node->mLeft) {
node = node->mLeft;
}
return Iter(this, node);
}
private:
// Lookup an item matching aLookup, or the closest node to it.
Node* lookup(const typename L::Lookup& aLookup) const {
MOZ_RELEASE_ASSERT(mRoot);
Node* node = mRoot;
Node* parent;
do {
parent = node;
ssize_t c = L::compare(aLookup, node->mItem);
if (c == 0) {
return node;
}
node = (c < 0) ? node->mLeft : node->mRight;
} while (node);
return parent;
}
void remove(Node* aNode) {
splay(aNode);
MOZ_RELEASE_ASSERT(aNode && aNode == mRoot);
// Find another node which can be swapped in for the root: either the
// rightmost child of the root's left, or the leftmost child of the
// root's right.
Node* swap;
Node* swapChild;
if (mRoot->mLeft) {
swap = mRoot->mLeft;
while (swap->mRight) {
swap = swap->mRight;
}
swapChild = swap->mLeft;
} else if (mRoot->mRight) {
swap = mRoot->mRight;
while (swap->mLeft) {
swap = swap->mLeft;
}
swapChild = swap->mRight;
} else {
freeNode(mRoot);
mRoot = nullptr;
return;
}
// The selected node has at most one child, in swapChild. Detach it
// from the subtree by replacing it with that child.
if (swap == swap->mParent->mLeft) {
swap->mParent->mLeft = swapChild;
} else {
swap->mParent->mRight = swapChild;
}
if (swapChild) {
swapChild->mParent = swap->mParent;
}
mRoot->mItem = swap->mItem;
freeNode(swap);
checkCoherency(mRoot, nullptr);
}
size_t NodesPerChunk() const {
return ChunkPages * PageSize / sizeof(Node);
}
Node* allocateNode(const T& aValue) {
if (!mFreeList) {
Node* nodeArray = mAlloc.template pod_malloc<Node>(NodesPerChunk());
for (size_t i = 0; i < NodesPerChunk() - 1; i++) {
nodeArray[i].mLeft = &nodeArray[i + 1];
}
mFreeList = nodeArray;
}
Node* node = mFreeList;
mFreeList = node->mLeft;
new(node) Node(aValue);
return node;
}
void freeNode(Node* aNode) {
aNode->mLeft = mFreeList;
mFreeList = aNode;
}
void splay(Node* aNode) {
// Rotate the element until it is at the root of the tree. Performing
// the rotations in this fashion preserves the amortized balancing of
// the tree.
MOZ_RELEASE_ASSERT(aNode);
while (aNode != mRoot) {
Node* parent = aNode->mParent;
if (parent == mRoot) {
// Zig rotation.
rotate(aNode);
MOZ_RELEASE_ASSERT(aNode == mRoot);
return;
}
Node* grandparent = parent->mParent;
if ((parent->mLeft == aNode) == (grandparent->mLeft == parent)) {
// Zig-zig rotation.
rotate(parent);
rotate(aNode);
} else {
// Zig-zag rotation.
rotate(aNode);
rotate(aNode);
}
}
}
void rotate(Node* aNode) {
// Rearrange nodes so that node becomes the parent of its current
// parent, while preserving the sortedness of the tree.
Node* parent = aNode->mParent;
if (parent->mLeft == aNode) {
// x y
// y c ==> a x
// a b b c
parent->mLeft = aNode->mRight;
if (aNode->mRight) {
aNode->mRight->mParent = parent;
}
aNode->mRight = parent;
} else {
MOZ_RELEASE_ASSERT(parent->mRight == aNode);
// x y
// a y ==> x c
// b c a b
parent->mRight = aNode->mLeft;
if (aNode->mLeft) {
aNode->mLeft->mParent = parent;
}
aNode->mLeft = parent;
}
aNode->mParent = parent->mParent;
parent->mParent = aNode;
if (Node* grandparent = aNode->mParent) {
if (grandparent->mLeft == parent) {
grandparent->mLeft = aNode;
} else {
grandparent->mRight = aNode;
}
} else {
mRoot = aNode;
}
}
#ifdef ENABLE_COHERENCY_CHECKS
Node* checkCoherency(Node* aNode, Node* aMinimum) {
if (!aNode) {
MOZ_RELEASE_ASSERT(!mRoot);
return nullptr;
}
MOZ_RELEASE_ASSERT(aNode->mParent || aNode == mRoot);
MOZ_RELEASE_ASSERT(!aMinimum || L::compare(L::getLookup(aMinimum->mItem), aNode->mItem) <= 0);
if (aNode->mLeft) {
MOZ_RELEASE_ASSERT(aNode->mLeft->mParent == aNode);
Node* leftMaximum = checkCoherency(aNode->mLeft, aMinimum);
MOZ_RELEASE_ASSERT(L::compare(L::getLookup(leftMaximum->mItem), aNode->mItem) <= 0);
}
if (aNode->mRight) {
MOZ_RELEASE_ASSERT(aNode->mRight->mParent == aNode);
return checkCoherency(aNode->mRight, aNode);
}
return aNode;
}
#else
inline void checkCoherency(Node* aNode, Node* aMinimum) {}
#endif
};
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_SplayTree_h

View File

@ -0,0 +1,87 @@
/* -*- 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/. */
#include "ValueIndex.h"
#include "mozilla/Assertions.h"
namespace mozilla {
namespace recordreplay {
size_t
ValueIndex::Insert(const void* aValue)
{
MOZ_RELEASE_ASSERT(!Contains(aValue));
size_t index = mIndexCount++;
mValueToIndex.insert(ValueToIndexMap::value_type(aValue, index));
mIndexToValue.insert(IndexToValueMap::value_type(index, aValue));
return index;
}
void
ValueIndex::Remove(const void* aValue)
{
size_t index;
if (!MaybeGetIndex(aValue, &index)) {
return;
}
mValueToIndex.erase(aValue);
mIndexToValue.erase(index);
}
size_t
ValueIndex::GetIndex(const void* aValue)
{
size_t index;
if (!MaybeGetIndex(aValue, &index)) {
MOZ_CRASH();
}
return index;
}
bool
ValueIndex::MaybeGetIndex(const void* aValue, size_t* aIndex)
{
ValueToIndexMap::const_iterator iter = mValueToIndex.find(aValue);
if (iter != mValueToIndex.end()) {
*aIndex = iter->second;
return true;
}
return false;
}
bool
ValueIndex::Contains(const void* aValue)
{
size_t index;
return MaybeGetIndex(aValue, &index);
}
const void*
ValueIndex::GetValue(size_t aIndex)
{
IndexToValueMap::const_iterator iter = mIndexToValue.find(aIndex);
MOZ_RELEASE_ASSERT(iter != mIndexToValue.end());
return iter->second;
}
bool
ValueIndex::IsEmpty()
{
MOZ_ASSERT(mValueToIndex.empty() == mIndexToValue.empty());
return mValueToIndex.empty();
}
const ValueIndex::ValueToIndexMap&
ValueIndex::GetValueToIndexMap()
{
return mValueToIndex;
}
} // namespace recordreplay
} // namespace mozilla

View File

@ -0,0 +1,83 @@
/* -*- 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 mozilla_recordreplay_ValueIndex_h
#define mozilla_recordreplay_ValueIndex_h
#include "mozilla/Types.h"
#include <unordered_map>
namespace mozilla {
namespace recordreplay {
// ValueIndexes are a bidirectional map between arbitrary pointers and indexes.
// These are used while recording and replaying to handle the general issue
// that pointer values are not preserved during replay: recording a pointer and
// replaying its bits later will not yield a pointer to the same heap value,
// but rather a pointer to garbage that must not be dereferenced.
//
// When entries are added to a ValueIndex at consistent points between
// recording and replaying, then the resulting indexes will be consistent, and
// that index can be recorded and later replayed and used to find the
// replay-specific pointer value corresponding to the pointer used at that
// point in the recording. Entries can be removed from the ValueIndex at
// different points in the recording and replay without affecting the indexes
// that will be generated later.
//
// This is a helper class that is used in various places to help record/replay
// pointers to heap data.
class ValueIndex
{
public:
ValueIndex()
: mIndexCount(0)
{}
typedef std::unordered_map<const void*, size_t> ValueToIndexMap;
// Add a new entry to the map.
size_t Insert(const void* aValue);
// Remove an entry from the map, unless there is no entry for aValue.
void Remove(const void* aValue);
// Get the index for an entry in the map. The entry must exist in the map.
size_t GetIndex(const void* aValue);
// Get the index for an entry in the map if there is one, otherwise return
// false.
bool MaybeGetIndex(const void* aValue, size_t* aIndex);
// Return whether there is an entry for aValue.
bool Contains(const void* aValue);
// Get the value associated with an index. The index must exist in the map.
const void* GetValue(size_t aIndex);
// Whether the map is empty.
bool IsEmpty();
// Raw read-only access to the map contents.
const ValueToIndexMap& GetValueToIndexMap();
private:
typedef std::unordered_map<size_t, const void*> IndexToValueMap;
// Map from pointer values to indexes.
ValueToIndexMap mValueToIndex;
// Map from indexes to pointer values.
IndexToValueMap mIndexToValue;
// The total number of entries that have ever been added to this map.
size_t mIndexCount;
};
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_ValueIndex_h