Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replay allocators #52

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ jobs:
cmake-flags: "-G Ninja -DSANITIZER=address,undefined -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang"
dependencies: "sudo apt install ninja-build"

- variant: "Sanitizer with replay"
platform: "ubuntu-latest"
build-type: "Release"
cmake-flags: "-G Ninja -DUSE_REPLAY_ALLOCATOR=On -DSANITIZER=address,undefined -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang"
dependencies: "sudo apt install ninja-build"


- platform: "windows-2019"
build-type: "Debug"
# Exclude perf tests in Debug on Windows as they take too long.
Expand Down
4 changes: 4 additions & 0 deletions src/rt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ if(USE_SYSTEMATIC_TESTING)
target_compile_definitions(verona_rt INTERFACE USE_SYSTEMATIC_TESTING)
endif()

if(USE_REPLAY_ALLOCATOR)
target_compile_definitions(verona_rt INTERFACE USE_REPLAY_ALLOCATOR)
endif()

if(USE_CRASH_LOGGING)
target_compile_definitions(verona_rt INTERFACE USE_FLIGHT_RECORDER)
endif()
Expand Down
1 change: 1 addition & 0 deletions src/rt/debug/harness.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ class SystematicTestHarness
Scheduler& sched = Scheduler::get();
#ifdef USE_SYSTEMATIC_TESTING
Systematic::set_seed(seed);
heap::set_seed(seed);
if (seed % 2 == 1)
{
sched.set_fair(true);
Expand Down
14 changes: 7 additions & 7 deletions src/rt/debug/systematic.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

#include "../debug/logging.h"
#include "../pal/semaphore.h"
#include "ds/prng.h"
#include "ds/scramble.h"
#include "test/xoroshiro.h"

#include <snmalloc/snmalloc.h>

Expand Down Expand Up @@ -86,19 +86,19 @@ namespace verona::rt
/// Return a mutable reference to the pseudo random number generator (PRNG).
/// It is assumed that the PRNG will only be setup once via `set_seed`.
/// After it is setup, the PRNG must only be used via `get_prng_next`.
static xoroshiro::p128r32& get_prng_for_setup()
static PRNG& get_prng_for_setup()
{
static xoroshiro::p128r32 prng;
static PRNG prng;
return prng;
}

/// Return a mutable reference to the scrambler. It is assumed that the
/// scrambler will only be setup once via `set_seed`. After it is setup, the
/// scrambler must only be accessed via a const reference
/// (see `get_scrambler`).
static verona::Scramble& get_scrambler_for_setup()
static verona::rt::Scramble& get_scrambler_for_setup()
{
static verona::Scramble scrambler;
static verona::rt::Scramble scrambler;
return scrambler;
}

Expand All @@ -113,15 +113,15 @@ namespace verona::rt
}

/// Return a const reference to the scrambler.
static const verona::Scramble& get_scrambler()
static const verona::rt::Scramble& get_scrambler()
{
return get_scrambler_for_setup();
}

static void set_seed(uint64_t seed)
{
auto& rng = get_prng_for_setup();
rng.set_state(seed);
rng.set_seed(seed);
get_scrambler_for_setup().setup(rng);
}

Expand Down
41 changes: 41 additions & 0 deletions src/rt/debug/testprng.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright Microsoft and Project Verona Contributors.
// SPDX-License-Identifier: MIT
#pragma once

#include "ds/prng.h"

#include <random>

struct TestPRNG
{
#ifdef USE_SYSTEMATIC_TESTING
// Use xoroshiro for systematic testing, because it's simple and
// and deterministic across platforms.
verona::rt::PRNG rand;
#else
// We don't mind data races for our PRNG, because concurrent testing means
// our results will already be nondeterministic. However, data races may
// cause xoroshiro to abort.
std::mt19937_64 rand;
#endif

TestPRNG(size_t seed) : rand(seed) {}

uint64_t next()
{
#ifdef USE_SYSTEMATIC_TESTING
return rand.next();
#else
return rand();
#endif
}

void seed(size_t seed)
{
#ifdef USE_SYSTEMATIC_TESTING
return rand.set_seed(seed);
#else
return rand.seed(seed);
#endif
}
};
173 changes: 173 additions & 0 deletions src/rt/ds/heap.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,177 @@

#pragma once

#include <array>
#include <snmalloc/snmalloc.h>

#if defined(__SANITIZE_ADDRESS__)
# define HAS_ASAN
#elif defined(__has_feature)
# if __has_feature(address_sanitizer)
# define HAS_ASAN
# endif
#endif

#if defined(HAS_ASAN)
# include <sanitizer/asan_interface.h>
// Asan does not appear to support a __asan_update_deallocation_context
# define VERONA_TRACK_FREE(ptr, size) __asan_poison_memory_region(ptr, size);
# define VERONA_TRACK_ALLOC(ptr, size) \
__asan_unpoison_memory_region(ptr, size); \
__asan_update_allocation_context(ptr);
# define VERONA_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address))
#else
# define VERONA_TRACK_FREE(ptr, size)
# define VERONA_TRACK_ALLOC(ptr, size)
# define VERONA_NO_SANITIZE_ADDRESS
#endif

namespace verona::rt::heap
{
#ifdef USE_REPLAY_ALLOCATOR
class ReplayAllocator
{
struct Node
{
Node* next;
};

static inline std::array<Node*, 256> allocs;
static inline std::array<size_t, 256> lengths;
static inline snmalloc::FlagWord lock;
static inline PRNG rng;

public:
static void set_seed(uint64_t seed)
{
rng.set_seed(seed);
}

VERONA_NO_SANITIZE_ADDRESS
static void* alloc(size_t size)
{
auto sc = snmalloc::size_to_sizeclass_full(size);
auto idx = sc.index();
{
snmalloc::FlagLock l(lock);
if (lengths[idx] > 0)
{
// Reuse an element if there are at least 16, or
// the randomisation says to.
auto r = rng.next();
// std::cout << std::hex << r << std::endl;

auto reuse = lengths[idx] > 16 || ((r & 0xf) == 0);
if (reuse)
{
auto r = rng.next();
Node** prev = &allocs[idx];
for (size_t i = 0; i < r % lengths[idx]; i++)
{
prev = &(*prev)->next;
}

auto curr = *prev;
auto next = curr->next;
*prev = next;
lengths[idx]--;
VERONA_TRACK_ALLOC(curr, size);
return curr;
}
}
}

return snmalloc::ThreadAlloc::get().alloc(size);
}

static void dealloc(void* ptr, size_t size)
{
auto sc = snmalloc::size_to_sizeclass_full(size);
auto idx = sc.index();
snmalloc::FlagLock l(lock);
auto hd = reinterpret_cast<Node*>(ptr);
hd->next = allocs[idx];
allocs[idx] = hd;
lengths[idx]++;
VERONA_TRACK_FREE(ptr, size);
}

VERONA_NO_SANITIZE_ADDRESS
static void flush()
{
for (size_t i = 0; i < allocs.size(); i++)
{
auto hd = allocs[i];
size_t count = 0;
while (hd != nullptr)
{
auto next = hd->next;
snmalloc::ThreadAlloc::get().dealloc(
hd, snmalloc::sizeclass_to_size(i));
hd = next;
count++;
}
assert(count == lengths[i]);
lengths[i] = 0;
allocs[i] = nullptr;
}
}
};

inline void* alloc(size_t size)
{
return ReplayAllocator::alloc(size);
}

template<size_t size>
inline void* alloc()
{
return ReplayAllocator::alloc(size);
}

inline void* calloc(size_t size)
{
auto obj = ReplayAllocator::alloc(size);
memset(obj, 0, size);
return obj;
}

template<size_t size>
inline void* calloc()
{
auto obj = ReplayAllocator::alloc(size);
memset(obj, 0, size);
return obj;
}

inline void dealloc(void* ptr, size_t size)
{
ReplayAllocator::dealloc(ptr, size);
}

inline void dealloc(void* ptr)
{
auto size = snmalloc::ThreadAlloc::get().alloc_size(ptr);
dealloc(ptr, size);
}

template<size_t size>
inline void dealloc(void* ptr)
{
ReplayAllocator::dealloc(ptr, size);
}

inline void debug_check_empty()
{
ReplayAllocator::flush();
snmalloc::debug_check_empty<snmalloc::Alloc::Config>();
}

inline void set_seed(uint64_t seed)
{
ReplayAllocator::set_seed(seed);
}
#else
inline void* alloc(size_t size)
{
return snmalloc::ThreadAlloc::get().alloc(size);
Expand Down Expand Up @@ -49,4 +216,10 @@ namespace verona::rt::heap
{
snmalloc::debug_check_empty<snmalloc::Alloc::Config>();
}

inline void set_seed(uint64_t seed)
{
// Do nothing
}
#endif
} // namespace verona::rt::heap
47 changes: 47 additions & 0 deletions src/rt/ds/prng.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright Microsoft and Project Verona Contributors.
// SPDX-License-Identifier: MIT
#pragma once

#include <test/xoroshiro.h>

namespace verona::rt
{
class PRNG
{
xoroshiro::p128r32 rng;

public:
PRNG() : rng(5489) {}

PRNG(uint64_t seed)
{
set_seed(seed);
}

void set_seed(uint64_t seed)
{
rng.set_state(seed);
// Discard the first 10 values to avoid correlation with the seed.
// Otherwise, using adjacent seeds will result in poor initial randomness.
for (size_t i = 0; i < 10; i++)
rng.next();
}

uint32_t next()
{
return rng.next();
}

uint32_t next(uint32_t max)
{
return rng.next() % max;
}

uint64_t next64()
{
auto top = rng.next();
auto bottom = rng.next();
return (static_cast<uint64_t>(top) << 32) | bottom;
}
};
} // namespace verona::rt
7 changes: 4 additions & 3 deletions src/rt/ds/scramble.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// Copyright Microsoft and Project Verona Contributors.
// SPDX-License-Identifier: MIT
#pragma once
#include "test/xoroshiro.h"

#include "ds/prng.h"

#include <cstdint>
#include <iomanip>
#include <iostream>

namespace verona
namespace verona::rt
{
/**
* This class is used to provide alternative orderings on a pointer type.
Expand Down Expand Up @@ -35,7 +36,7 @@ namespace verona
public:
Scramble() {}

void setup(xoroshiro::p128r32& r)
void setup(verona::rt::PRNG& r)
{
for (size_t i = 0; i < ROUNDS; i++)
{
Expand Down
Loading
Loading