#pragma warning (disable:4786)
#include "MemManager7.h"
#include <windows.h>
#include <map>
#include <vector>
#include <deque>
#include <algorithm>
#include <math.h>
#include "avl_tree.h"
using namespace std;
#include "Compress.h"
#define AUDIT
#define CHECK_POINTER(s)
namespace Mm
{
////////////////////////////////////////////////////////////////////////////////
// result codes
static const Result R_OK (Result::eOk, MAKE_STRING_TOKEN(0,22));
static const Result E_TRANSACTION_OPEN (Result::eError, 0, 1);
static const Result E_ROLLBACK_STATE (Result::eError, 0, 2);
static const Result E_NO_TRANSACTION (Result::eError, 0, 3);
static const Result E_VIRTUAL_ALLOCATE (Result::eError, 0, 4);
static const Result E_VIRTUAL_PROTECT (Result::eError, 0, 5);
static const Result E_NO_DATA (Result::eError, 0, 6);
static const Result E_INVALID_ARGUMENT (Result::eError, 0, 7);
////////////////////////////////////////////////////////////////////////////////
// system invariants
static const int PAGE_SIZE = 4096;
static const int BLOCK_SIZE = PAGE_SIZE * 16;
////////////////////////////////////////////////////////////////////////////////
// freelist node
struct Free {
Free() : size(0), next(0) {}; // use only with placement new
private:
Free(const Free&) {}
~Free() {};
public:
typedef unsigned long SIZE_TYPE;
SIZE_TYPE size; // length of the availble buffer starting at '&next'
Free* next; // TODO: remove
struct _bylocation {
_bylocation() : lt(0), gt(0), bf(0) {}
Free* lt;
Free* gt;
int bf;
} loc;
static Free* FromPtr(void* p)
{
return reinterpret_cast<Free*>((char*)p - sizeof(SIZE_TYPE));
}
void* UserPtr() const
{
return static_cast<void*>(const_cast<Free**>(&next));
}
void* Begin() const
{
return static_cast<void*>(const_cast<Free*>(this));
}
void* End() const
{
return reinterpret_cast<void*>((char*)UserPtr() + size);
}
bool Adjacent(const Free* f) const
{
return (f && (this->End() == f->Begin()));
}
bool Overlap(Free* f) const
{
return (this->End() > f->Begin());
}
};
////////////////////////////////////////////////////////////////////////////////
//
struct AddressAbstractor { // for AVL tree
typedef Free* handle;
typedef unsigned long key;
typedef size_t size;
handle get_less(handle h, bool access) { return h->loc.lt; }
void set_less(handle h, handle lh) { h->loc.lt = lh; }
handle get_greater(handle h, bool access) { return h->loc.gt; }
void set_greater(handle h, handle gh) { h->loc.gt = gh; }
int get_balance_factor(handle h) { return h->loc.bf; }
void set_balance_factor(handle h, int bf) { h->loc.bf = bf; }
int compare_key_key(key k1, key k2)
{
if (k1 == k2) return(0);
if (k1 > k2) return(1);
return(-1);
}
int compare_key_node(key k, handle h) { return(compare_key_key(k, (key)h)); }
int compare_node_node(handle h1, handle h2) { return(compare_key_key((key)h1, (key)h2)); }
handle null(void) { return(0); }
static bool read_error(void) { return(false); }
};
////////////////////////////////////////////////////////////////////////////////
//
struct AllocationManager
{
Free* sFreeHead;
abstract_container::avl_tree<AddressAbstractor, 31> locTree;
AllocationManager() : sFreeHead(0) {}
Free* Slice(Free* from, size_t bytes)
{
const size_t true_bytes = bytes + sizeof(Free::SIZE_TYPE);
if ((from->size - true_bytes) < sizeof(from->size))
return 0;
Free* slice = reinterpret_cast<Free*>((char*)from->UserPtr() + (from->size - true_bytes));
memset(slice, 0, sizeof(Free));
slice->size = bytes;
from->size -= true_bytes;
return slice;
}
void Insert(Free* node)
{
AUDIT
// precautionary
node->next = 0;
memset(&node->loc, 0, sizeof(node->loc));
if (sFreeHead == 0) {
sFreeHead = node;
locTree.insert(node);
} else if (sFreeHead->Begin() > node->Begin()) {
node->next = sFreeHead;
sFreeHead = node;
locTree.insert(node);
AUDIT
Compact(sFreeHead);
} else {
Free* cursor = locTree.search((size_t)node, abstract_container::LESS);
node->next = cursor->next;
cursor->next = node;
locTree.insert(node);
AUDIT
Compact(node);
AUDIT
Compact(cursor);
AUDIT
}
}
void Compact(Free* node)
{
if (node->Adjacent(node->next)) {
locTree.remove((size_t)node->next);
node->size += (node->next->size + sizeof(node->size));
node->next = node->next->next;
}
}
size_t FreeSpace()
{
size_t unused = 0;
Free* f = sFreeHead;
while (f) { unused += f->size; f = f->next; }
return unused;
}
size_t FreeCount()
{
size_t blocks = 0;
Free* f = sFreeHead;
while (f) { ++blocks; f = f->next; }
return blocks;
}
void Audit()
{
}
};
////////////////////////////////////////////////////////////////////////////////
// typedef and definition of global page to space map
typedef map<void*, size_t> PageSpaceMap;
PageSpaceMap pages;
////////////////////////////////////////////////////////////////////////////////
// representations of transactions and spaces
typedef map<void*, void*> PageMap;
typedef map<void*, size_t> BlockMap;
struct Transaction {
Transaction() : id(++nextId) {}
PageMap backups;
BlockMap added;
size_t id;
static size_t nextId;
};
size_t Transaction::nextId = 0;
typedef deque<Transaction> Transactions;
struct Space {
Space() : transacting(false), id(nextId++), data(0) {}
Transactions done;
Transactions undone;
Transactions cleanup; // TODO: cleanup backups at the time of truncations?
bool transacting;
size_t id;
static size_t nextId;
AllocationManager* data;
AllocationManager* AssertData()
{
if (!transacting)
return NULL; // throw?
if (data == NULL) {
// we need our own private, transacted memory
// NOTE: we're putting an entire page in here, find some other
// uses for it?
void* p = ::VirtualAlloc(0, PAGE_SIZE, MEM_COMMIT, PAGE_READONLY);
pages[p] = id;
done[0].added[p] = PAGE_SIZE;
data = new(p) AllocationManager();
}
return data;
}
Free* More(size_t size)
{
if (!transacting)
return NULL; // throw?
const size_t actual_size = ((size / BLOCK_SIZE) + 1) * BLOCK_SIZE;
void* p = ::VirtualAlloc(0, actual_size, MEM_COMMIT, PAGE_READONLY);
if (!p)
throw "virtual alloc failed";
for (unsigned int i = 0; i < actual_size; i += PAGE_SIZE) {
void* q = (char*)p + i;
pages[q] = id;
}
Free* f = new (p) Free();
f->size = actual_size - sizeof(f->size);
done[0].added[p] = actual_size;
AUDIT
return f;
}
void Audit()
{
}
};
size_t Space::nextId = 0;
typedef map<size_t, Space> Spaces;
Spaces spaces;
////////////////////////////////////////////////////////////////////////////////
// exception filter maps address to space, and creates backup copy of the
// target page
int ExceptionFilter(void* exp)
{
LPEXCEPTION_POINTERS e = static_cast<LPEXCEPTION_POINTERS>(exp);
if (e->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
return EXCEPTION_CONTINUE_SEARCH; // return (*previous)(e);
bool writing = (e->ExceptionRecord->ExceptionInformation[0] != 0);
void* addr = (void*)e->ExceptionRecord->ExceptionInformation[1];
void* page = (void*)((unsigned long)addr & ~(PAGE_SIZE - 1));
if (writing == false)
return EXCEPTION_CONTINUE_SEARCH; // return (*previous)(e);
PageSpaceMap::iterator i = pages.find(page);
if (i == pages.end())
return EXCEPTION_CONTINUE_SEARCH; // return (*previous)(e);
Spaces::iterator s = spaces.find(i->second);
if (s == spaces.end())
return EXCEPTION_CONTINUE_SEARCH; // my error
Space& space = s->second;
if (!space.transacting)
return EXCEPTION_CONTINUE_SEARCH; // user error
void* backup = ::VirtualAlloc(0, PAGE_SIZE, MEM_COMMIT, PAGE_READWRITE);
if (!backup)
return EXCEPTION_CONTINUE_SEARCH; // out of memory error
memmove(backup, page, PAGE_SIZE);
Transaction& t = space.done[0];
t.backups[page] = backup;
DWORD old = 0;
BOOL ok = FALSE;
ok = ::VirtualProtect(backup, PAGE_SIZE, PAGE_READONLY, &old);
if (ok == FALSE)
return EXCEPTION_CONTINUE_SEARCH; // return (*previous)(e);
ok = ::VirtualProtect(page, PAGE_SIZE, PAGE_READWRITE, &old);
if (ok == FALSE)
return EXCEPTION_CONTINUE_SEARCH; // return (*previous)(e);
return EXCEPTION_CONTINUE_EXECUTION;
}
////////////////////////////////////////////////////////////////////////////////
// API helper functions and implementation
struct MarkBlockFree
{
MarkBlockFree(Space& s) : space(s) {}
void operator ()(const BlockMap::value_type& b)
{
Free* f = reinterpret_cast<Free*>(b.first);
f->size = b.second - sizeof(Free::SIZE_TYPE);
space.data->Insert(f);
}
Space& space;
};
static void ReleaseBlock(const BlockMap::value_type& b)
{
::VirtualFree(b.first, 0, MEM_RELEASE);
}
static void ReleaseDelta(const PageMap::value_type& b)
{
delete[] b.second;
}
struct RecycleTransaction
{
RecycleTransaction(Space& s) : space(s) {}
void operator() (const Transactions::value_type& t)
{
for_each(t.added.begin(), t.added.end(), MarkBlockFree(space));
for_each(t.backups.begin(), t.backups.end(), ReleaseDelta);
}
Space& space;
};
static void ReleaseTransaction(const Transactions::value_type& t)
{
for_each(t.added.begin(), t.added.end(), ReleaseBlock);
for_each(t.backups.begin(), t.backups.end(), ReleaseDelta);
}
static unsigned long total_rle_len = 0;
struct CommitBackup
{
CommitBackup(Space& s) : space(s) {}
void operator() (const PageMap::value_type& v)
{
DWORD* after = (DWORD*)v.first;
DWORD* before = (DWORD*)v.second;
DWORD* delta = (DWORD*)before; // rewrite to conserve memory
if (!before)
throw "missing 'before' page";
{ // scope
DWORD old = 0;
BOOL ok = ::VirtualProtect(before, PAGE_SIZE, PAGE_READWRITE, &old);
if (ok == FALSE)
throw "virtual protect failed";
}
for (unsigned int j = 0; j < (PAGE_SIZE / sizeof(DWORD)); ++j) {
const DWORD a = after[j];
const DWORD b = before[j];
delta[j] = (a ^ b);
}
static char buf[4096 * 2];
unsigned int len = Compress(delta, buf);
total_rle_len += len;
unsigned char* d = new unsigned char[len];
for (unsigned int i = 0; i < len; ++i)
d[i] = buf[i];
Transaction& t = space.done[0]; // current transaction
{ // scope
BOOL ok = ::VirtualFree(v.second, 0, MEM_RELEASE);
if (ok == FALSE)
throw "virtual free failed in clear backup";
}
// assign the compressed delta to the backup's place in the map
t.backups[v.first] = d;
{ // scope
DWORD old = 0;
BOOL ok = ::VirtualProtect(after, PAGE_SIZE, PAGE_READONLY, &old);
if (ok == FALSE)
throw "virtual protect failed";
}
}
Space& space;
};
static void CancelBackup(const PageMap::value_type& v)
{
void* page = v.first;
void* backup = v.second;
if (!backup)
throw "missing 'backup' page";
{ // scope
DWORD old = 0;
BOOL ok = ::VirtualProtect(page, PAGE_SIZE, PAGE_READWRITE, &old);
if (ok == FALSE)
throw "virtual protect failed in cancel backup";
}
memcpy(page, backup, PAGE_SIZE);
{ // scope
DWORD old = 0;
BOOL ok = ::VirtualProtect(page, PAGE_SIZE, PAGE_READONLY, &old);
if (ok == FALSE)
throw "virtual protect failed in cancel backup";
}
{ // scope
BOOL ok = ::VirtualFree(backup, 0, MEM_RELEASE);
if (ok == FALSE)
throw "virtual free failed in clear backup";
}
}
static void ApplyDelta(PageMap::value_type& v)
{
DWORD old = 0;
BOOL ok = ::VirtualProtect(v.first, PAGE_SIZE, PAGE_READWRITE, &old);
if (ok == FALSE)
throw;
Mm::ApplyCompressedDelta(v.first, v.second);
ok = ::VirtualProtect(v.first, PAGE_SIZE, PAGE_READONLY, &old);
if (ok == FALSE)
throw;
}
bool IsTransacting(const Spaces::value_type& s)
{
return s.second.transacting;
}
struct OwnerIs
{
OwnerIs(Space& s) : space(s) {}
bool operator()(const PageSpaceMap::value_type& s)
{
return (s.second == space.id);
}
Space& space;
};
void ClearSpace(Spaces::value_type& s)
{
SPACEID sid = s.first;
Space& space = s.second;
for_each(space.undone.begin(), space.undone.end(), ReleaseTransaction);
space.undone.clear();
for_each(space.done.begin(), space.done.end(), ReleaseTransaction);
space.done.clear();
for_each(space.cleanup.begin(), space.cleanup.end(), ReleaseTransaction);
space.cleanup.clear();
PageSpaceMap::iterator b = pages.begin();
while(b != pages.end()) {
if (b->second == sid) pages.erase(b++);
else ++b;
}
space.data = 0;
}
////////////////////////////////////////////////////////////////////////////////
// Mm API methods
Result InitSpaceZero()
{
spaces[0].id = 0;
return R_OK;
}
SPACEID CreateSpace(size_t size)
{
SPACEID sid = ++Space::nextId;
Space& space = spaces[sid];
if (size) {
space.AssertData();
space.More(size);
}
return space.id;
}
Result Destroy(SPACEID sid)
{
Result clear = Clear(sid);
if (clear.IsError())
return clear;
spaces.erase(sid);
return R_OK;
}
Result Clear(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return E_INVALID_ARGUMENT; // user error
Space& space = s->second;
if (space.transacting)
return E_TRANSACTION_OPEN;
ClearSpace(*s);
return R_OK;
}
Result ClearAll()
{
if (std::find_if(spaces.begin(), spaces.end(), IsTransacting) != spaces.end())
return E_TRANSACTION_OPEN;
std::for_each(spaces.begin(), spaces.end(), ClearSpace);
return R_OK;
}
Result DestroyAll()
{
Result clear = ClearAll();
if (clear.IsError())
return clear;
spaces.swap(Spaces());
return R_OK;
}
Result OpenTransaction(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return E_INVALID_ARGUMENT; // user error
Space& space = s->second;
if (space.transacting)
return E_TRANSACTION_OPEN;
Transaction empty;
space.done.push_front(empty);
space.transacting = true;
return R_OK;
}
Result CommitTransaction(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return E_INVALID_ARGUMENT; // user error
Space& space = s->second;
if (!space.transacting)
return E_NO_TRANSACTION;
if (space.undone.size() > 0) {
for_each(space.undone.begin(), space.undone.end(), RecycleTransaction(space));
space.undone.clear();
}
Transaction& t = space.done[0];
for_each(t.backups.begin(), t.backups.end(), CommitBackup(space));
space.transacting = false;
return R_OK;
}
Result CancelTransaction(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return E_INVALID_ARGUMENT; // user error
Space& space = s->second;
if (!space.transacting)
return E_NO_TRANSACTION;
Transaction& t = space.done[0];
for_each(t.backups.begin(), t.backups.end(), CancelBackup);
t.backups.clear();
space.done.pop_front();
space.transacting = false;
return R_OK;
}
TXNID GetLastTransactionId(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return 0; // user error
const Space& space = s->second;
return (space.done.size() > 0) ? (TXNID)(space.done[0].id) : 0;
}
TXNID GetNextTransactionId(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return 0; // user error
const Space& space = s->second;
return (space.undone.size() > 0) ? (TXNID)(space.undone[0].id) : 0;
}
TXNID GetOpenTransactionId(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return 0; // user error
const Space& space = s->second;
return (space.transacting && space.done.size() > 0) ? (TXNID)(space.done[0].id) : 0;
}
Result Undo(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return E_INVALID_ARGUMENT; // user error
Space& space = s->second;
if (space.transacting)
return E_TRANSACTION_OPEN;
if (space.done.size() < 1)
return E_NO_DATA;
{ // scope t
Transaction& t = space.done[0];
for_each(t.backups.begin(), t.backups.end(), ApplyDelta);
}
space.undone.push_front(space.done[0]);
space.done.pop_front();
return R_OK;
}
Result Redo(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return E_INVALID_ARGUMENT; // user error
Space& space = s->second;
if (space.transacting)
return E_TRANSACTION_OPEN;
if (space.undone.size() < 1)
return E_NO_DATA;
{ // scope t
Transaction& t = space.undone[0];
for_each(t.backups.begin(), t.backups.end(), ApplyDelta);
}
space.done.push_front(space.undone[0]);
space.undone.pop_front();
return R_OK;
}
Result TruncateUndo(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return E_INVALID_ARGUMENT; // user error
Space& space = s->second;
if (space.transacting)
return E_TRANSACTION_OPEN;
space.cleanup.insert(space.cleanup.end(), space.done.begin(), space.done.end());
space.done.clear();
return R_OK;
}
Result TruncateRedo(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return E_INVALID_ARGUMENT; // user error
Space& space = s->second;
if (space.transacting)
return E_TRANSACTION_OPEN;
for_each(space.undone.begin(), space.undone.end(), ReleaseTransaction);
space.undone.clear();
return R_OK;
}
////////////////////////////////////////////////////////////////////////////////
// memory allocation implementation (very simple free list)
void* Allocate(size_t size, SPACEID sid)
{
AUDIT
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return NULL;
Space& space = s->second;
if (!space.transacting)
return NULL;
size = max(size, sizeof(Free));
// TODO: assert that "data" is allocated in space
space.AssertData();
// are there any more free chunks?
if (!space.data->sFreeHead) {
space.data->Insert(space.More(size));
}
AUDIT
// find the first chunk at least the size requested
Free* prev = 0;
Free* f = space.data->sFreeHead;
while (f && (f->size < size)) {
prev = f;
f = f->next;
}
AUDIT
// if we found one, disconnect it
if (f) {
space.data->locTree.remove((size_t)f);
if (prev) prev->next = f->next;
else space.data->sFreeHead = f->next;
f->next = 0;
memset(&f->loc, 0, sizeof(f->loc));
} else {
f = space.More(size);
}
// f is disconnected from the free list at this point
AUDIT
// if the free chunk is too(?) big, carve a peice off and return
// the rest to the free list
if (f->size > (2*(size + sizeof(Free)))) {
Free* tmp = space.data->Slice(f, size); // slice size byte off 'f'
space.data->Insert(f); // return the remainder to the free list
f = tmp;
}
AUDIT
CHECK_POINTER(f)
void* p = reinterpret_cast<void*>((char*)f + sizeof(Free::SIZE_TYPE));
CHECK_POINTER(p)
return p;
}
void* Allocate(void* hint, size_t size)
{
void* page = (void*)((unsigned long)hint & ~(PAGE_SIZE - 1));
PageSpaceMap::iterator i = pages.find(page);
if (i == pages.end())
return NULL;
SPACEID sid = i->second;
return Allocate(size, sid);
}
void Deallocate(void* p, SPACEID sid)
{
if (!p) return;
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
throw "attempt to reallocate unmanaged memory";
Space& space = s->second;
Free* f = Free::FromPtr(p);
AUDIT
space.data->Insert(f);
AUDIT
}
void Deallocate(void* p)
{
void* page = (void*)((unsigned long)p & ~(PAGE_SIZE - 1));
PageSpaceMap::iterator i = pages.find(page);
if (i == pages.end())
throw "attempt to deallocate unmanaged memory";
SPACEID sid = i->second;
Deallocate(p, sid);
}
void Deallocate(void* p, size_t size)
{
if (!p) return;
Deallocate(p);
}
void* Reallocate(void* p, size_t size)
{
if (!p) return p;
void* page = (void*)((unsigned long)p & ~(PAGE_SIZE - 1));
PageSpaceMap::iterator i = pages.find(page);
if (i == pages.end())
return p;
SPACEID sid = i->second;
return Reallocate(p, size, sid);
}
void* Reallocate(void* p, size_t size, SPACEID sid)
{
AUDIT
if (!p)
return Allocate(size, sid);
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return p;
Space& space = s->second;
Free* f = Free::FromPtr(p);
if (size < f->size)
return p;
void* r = Allocate(size, sid);
memcpy(r, p, f->size);
space.data->Insert(f);
return r;
}
size_t FreeSpace(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return 0;
Space& space = s->second;
return (space.data ? space.data->FreeSpace() : 0);
}
size_t FreeCount(SPACEID sid)
{
Spaces::iterator s = spaces.find(sid);
if (s == spaces.end())
return 0;
Space& space = s->second;
return (space.data ? space.data->FreeCount() : 0);
}
};