mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Bug 1464903 Part 3 - Record/replay utilities, r=froydnj.
--HG-- extra : rebase_source : 014d5737b4db1b4bb2de94c869bd4b9bcf88936d
This commit is contained in:
parent
e6baf647d7
commit
20e02d2085
108
toolkit/recordreplay/ChunkAllocator.h
Normal file
108
toolkit/recordreplay/ChunkAllocator.h
Normal 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
|
446
toolkit/recordreplay/File.cpp
Normal file
446
toolkit/recordreplay/File.cpp
Normal 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
277
toolkit/recordreplay/File.h
Normal 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
|
140
toolkit/recordreplay/InfallibleVector.h
Normal file
140
toolkit/recordreplay/InfallibleVector.h
Normal 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
|
79
toolkit/recordreplay/Monitor.h
Normal file
79
toolkit/recordreplay/Monitor.h
Normal 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
|
175
toolkit/recordreplay/SpinLock.h
Normal file
175
toolkit/recordreplay/SpinLock.h
Normal 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
|
366
toolkit/recordreplay/SplayTree.h
Normal file
366
toolkit/recordreplay/SplayTree.h
Normal 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
|
87
toolkit/recordreplay/ValueIndex.cpp
Normal file
87
toolkit/recordreplay/ValueIndex.cpp
Normal 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
|
83
toolkit/recordreplay/ValueIndex.h
Normal file
83
toolkit/recordreplay/ValueIndex.h
Normal 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
|
Loading…
Reference in New Issue
Block a user