diff --git a/fastlib/base/build.py b/fastlib/base/build.py index d920190d04..e1480b2bec 100644 --- a/fastlib/base/build.py +++ b/fastlib/base/build.py @@ -16,7 +16,7 @@ customrule( doit_fn = config_doit) librule( - sources = ["common.c", "cc.cc", "ccmem.cc"], + sources = ["common.c", "cc.cc", "ccmem.cc", "otrav.cc"], headers = ["cc.h", "ccmem.h", "common.h", "compiler.h", "compiler_impl.h", "test.h", "fortran.h", - "debug.h", "scale.h", ":config_headers"]) + "debug.h", "scale.h", "otrav.h", ":config_headers"]) diff --git a/fastlib/base/cc.h b/fastlib/base/cc.h index 1592fc3ed3..241622beea 100644 --- a/fastlib/base/cc.h +++ b/fastlib/base/cc.h @@ -10,7 +10,6 @@ #ifndef BASE_CC_H #define BASE_CC_H -#include "common.h" #include "compiler.h" #include "debug.h" #include "scale.h" @@ -75,6 +74,16 @@ inline T max(T a, T b) { #define CC_ASSIGNMENT_OPERATOR(cl) \ public: const cl&operator=(const cl&o) \ {if(this!=&o){this->~cl();new(this)cl(o);}return *this;} + +/** + * Creates a copy constructor using the Copy method. + * + * This does not call the default constructor, so if you are explicitly + * checking for debug poisons, your checks will fail. + */ +#define ALLOW_COPY(cl) \ + public: cl(const cl& other) { Copy(other); } \ + CC_ASSIGNMENT_OPERATOR(cl) /** * Defines inequality comparators for this class, given the friend diff --git a/fastlib/base/ccmem.h b/fastlib/base/ccmem.h index 115906a4af..ac4d9d6c39 100644 --- a/fastlib/base/ccmem.h +++ b/fastlib/base/ccmem.h @@ -40,6 +40,11 @@ * */ namespace mem { + template + struct Chunk { + T data[t_elems]; + }; + /** * In debug mode, sets the entire chunk of memory to a BIG_BAD_NUMBER. * @param array chunk of memory @@ -162,14 +167,67 @@ namespace mem { } /** * Copies bit-by-bit from one location to another (memcpy). + * @param dest the destination + * @param src the source * @param elems the desired number of *elements* - * @return a new pointer + * @return the destination pointer */ template - inline T * Copy(T* dest, const T* src, size_t elems = 1) { + inline T * Copy(T* dest, const T* src, size_t elems) { return CopyBytes(dest, src, elems * sizeof(T)); } + template + inline void ChunkCopy(T* dest, const T* src) { + *reinterpret_cast*>(dest) + = reinterpret_cast*>(src); + } + + template + struct CopyHelper { + static void DoCopy(T* dest, const T* src) { + ChunkCopy(dest, src); + } + }; + + template + struct CopyHelper<0, 1, 1, bytes, T> { + static void DoCopy(T* dest, const T* src) { + ChunkCopy(dest, src); + } + }; + + template + struct CopyHelper<0, 0, 1, bytes, T> { + static void DoCopy(T* dest, const T* src) { + ChunkCopy(dest, src); + } + }; + + template + struct CopyHelper<0, 0, 0, bytes, T> { + static void DoCopy(T* dest, const T* src) { + ChunkCopy(dest, src); + } + }; + + /** + * Copies bit-by-bit from one location to another (memcpy). + * + * Attempts to be smart when it can. + * + * @param dest + */ + template + inline T * Copy(T* dest, const T* src) { + CopyHelper::DoCopy(dest, src); + return dest; + } + template inline T * DupBytes(const T* src, size_t size) { T* p = AllocBytes(size); return CopyBytes(p, src, size); @@ -307,9 +365,51 @@ namespace mem { } template - void Swap(T* a, T* b, size_t elems = 1) { + inline void Swap(T* a, T* b, size_t elems = 1) { SwapBytes(a, b, elems * sizeof(T)); } + + /** + * Adds a byte-by-byte difference to a pointer. + * + * This is different from pointer addition because this requires an + * intermediate cast to character in order to get per-byte addition. + * + * @param x the pointer offset + * @param difference_in_bytes the number of bytes to add + * @return the sum + */ + template + inline T* PointerAdd(T* x, ptrdiff_t difference_in_bytes) { + return reinterpret_cast(reinterpret_cast(x) + + difference_in_bytes); + } + + /** + * Finds the byte-by-byte distance between two pointers, lhs - rhs. + * + * This is different from pointer subtraction because this requires an + * intermediate cast to character in order to get per-byte differences. + * + * @param lhs the "positive" pointer + * @param rhs the "negative" pointer + * @return the difference, (char*)rhs - (char*)lhs + */ + template + inline ptrdiff_t PointerDiff(const T* lhs, const T* rhs) { + return reinterpret_cast(lhs) - reinterpret_cast(rhs); + } + + /** + * Finds the inter-valued absolute address of a pointer. + * + * @param pointer the pointer to get the absolute address of + * @return the pointer, but in integer form + */ + template + inline ptrdiff_t PointerAbsoluteAddress(const T* pointer) { + return reinterpret_cast(pointer); + } }; diff --git a/fastlib/base/debug.h b/fastlib/base/debug.h index a5231853a9..60e9d8375b 100644 --- a/fastlib/base/debug.h +++ b/fastlib/base/debug.h @@ -186,10 +186,10 @@ extern int print_warnings; */ #ifdef __cplusplus #define DEBUG_POISON_PTR(x) \ - DEBUG_ONLY(debug_poison_ptr(x)) + DEBUG_ONLY(debug_poison_ptr__impl(x)) template -void debug_poison_ptr(T *&x) { +void debug_poison_ptr__impl(T *&x) { x = BIG_BAD_POINTER(T); } #else diff --git a/fastlib/base/otrav.cc b/fastlib/base/otrav.cc new file mode 100644 index 0000000000..0263a565c1 --- /dev/null +++ b/fastlib/base/otrav.cc @@ -0,0 +1,20 @@ +/** + * @file otrav.cpp + * + * Definitions for object-traversal. + */ + +#include "otrav.h" + +namespace ot_private { + void OTPrinter::Write(const char *format, ...) { + va_list vl; + for (int i = 0; i < indent_amount_; i++) { + putc(' ', stream_); + } + va_start(vl, format); + vfprintf(stream_, format, vl); + va_end(vl); + putc('\n', stream_); + } +}; diff --git a/fastlib/base/otrav.h b/fastlib/base/otrav.h new file mode 100644 index 0000000000..ac498ecbbd --- /dev/null +++ b/fastlib/base/otrav.h @@ -0,0 +1,688 @@ +/** + * @file otrav.h + * + * Object-tree traversal. + * + * This is for traversing a directed acyclic graph of pointers, i.e. the + * actual underlying data structure. It turns out a generalized DAG + * traversal framework allows for the following to be available at no + * additional work on the application programmer: + * + * @li Serialization (save to disk) + * @li Deserialization (read from disk) + * @li Object freezing/thawing/refreezing + * (storing bulk flattened objects in RAM) + * @li Debug print, or save to s-expression or XML + * @li Destructors and copy constructors + * + * + * This has no support for (at least currently): + * + * @li Cycles + * @li Polymorphism (i.e. object-oriented inheritance) + */ + + +#ifndef BASE_OTRAV_H +#define BASE_OTRAV_H + +#include "ccmem.h" + +#include +#include + +#define PASADENA(x) 3 +// TODO: Remove nullability from arrays + +/** + * Within OT_DEF, declare a sub-object (or primitive) that is directly + * contained, NOT pointed to. + */ +#define OT_MY_OBJECT(x) v_OT->MyObject(this->x) +/** + * Within OT_DEF, declare a static-sized array embedded within your object. + * + * The length of the array is determined automatically via sizeof. + */ +#define OT_MY_ARRAY(x) v_OT->MyArray(this->x, sizeof(this->x) / sizeof(this->x[0])) +/** + * Within OT_DEF, declare an object being pointed to, managed by + * new and delete. + */ +#define OT_PTR(x) v_OT->Ptr(this->x, false) +/** + * Within OT_DEF, declare an array being pointed to, managed by + * new[] and delete[]. + */ +#define OT_ARRAY(x, i) v_OT->Array(this->x, i, false) +/** + * Within OT_DEF, declare an array or object being pointed to managed by + * malloc and free. + */ +#define OT_MALLOC_ARRAY(x, i) v_OT->MallocArray(this->x, i, false) +/** + * Within OT_DEF, declare a pointer to an object that might be NULL. + */ +#define OT_PTR_NULLABLE(x) v_OT->Ptr(this->x, true) +/** + * Within OT_DEF, declare a pointer to an array that might be NULL. + */ +#define OT_ARRAY_NULLABLE(x, i) v_OT->Array(this->x, i, true) +/** + * Within OT_DEF, declare a pointer to a malloced array that might be NULL. + */ +#define OT_MALLOC_ARRAY_NULLABLE(x, i) v_OT->MallocArray(this->x, i, true) + +/** + * Define the object traversal for this object. + * + * Example: + * @code + * class MyTree { + * private: + * int value; + * MyTree *left; + * MyTree *right; + * int num_extra_data; + * Data *extra_data_array; + * + * OT_DEF(MyTree) { + * OT_MY_OBJECT(value); + * OT_PTR_NULLABLE(left); + * OT_PTR_NULLABLE(right); + * OT_MY_OBJECT(num_extra_data); + * OT_ARRAY(extra_data_array, num_extra_data); + * } + * }; + * ... rest of class definition ... + * @endcode + * + * The OT_DEF declares its own members, and its pointers. Notice that + * OT_MY_OBJECT(num_extra_data) must come before the subsequent + * line that uses num_extra_data as an array length. If deserialization is + * occuring, each OT_... call is actually deserializing each + * member, so num_extra_data is uninitialized until OT_MY_OBJECT + * is called on it. + * + * Fine-point: If you have an array of pointers, you are pretty much doomed + * to declare the array of pointers and iterate over the array yourself for + * each pointer, treating each element of the array as a separate pointer. + * + * @see OT_MY_OBJECT, OT_MY_ARRAY, OT_PTR, OT_ARRAY, OT_MALLOC_ARRAY, + * OT_PTR_NULLABLE, OT_ARRAY_NULLABLE, OT_MALLOC_ARRAY_NULLABLE. + */ +#define OT_DEF(AClass) \ + public: \ + template \ + friend void TraverseObject(AClass *obj_OT, Visitor *v_OT) { \ + obj_OT->TraverseObject__OT_(v_OT); \ + } \ + private: \ + template \ + void TraverseObject__OT_(Visitor *v_OT) + +// Re-think how this is supposed to work. +// /** +// * Create an automatically-generated print method for your class. +// */ +// #define OT_GENERATE_PRINT(AClass) +// public: +// template<> +// friend void Print(const AClass& obj, FILE *stream) { +// OTPrint(obj, stream); +// } + +// TODO: Automatically generate copy constructors and the like + +/** + * Like OT_DEF, but automatically generates as many standard methods as + * possible. + */ +#define OT_FULL(AClass) \ + OT_GENERATE_PRINT(AClass) \ + OT_DEF(AClass) + +/** + * Specify a clean-up step to run after deserialization, for instance, to + * populate transient fields. + * + * An example is ArrayList - it has both a length and capacity. The capacity + * need not be stored, but upon deserialization, the capacity must be + * initialized to a valid value, such as the length. + */ +#define OT_FIX(AClass) \ + public: \ + friend void TraverseObjectPostprocess(AClass *x) { \ + x->TraverseObjectPostprocess__OT_(); \ + } \ + private: \ + void TraverseObjectPostprocess__OT_() + + +// The object-tree-visitor interface. +// class OTBlankVisitor { +// public: +// /** visits an object with no OT implementation */ +// template void Primitive(T& x); +// /** visits an internal object */ +// template void MyObject(T& x); +// /** visits an array */ +// template void MyArray(T* x, index_t i); +// /** visits an object pointed to, allocated with new */ +// template void Ptr(T*& x, bool nullable); +// /** visits an array pointed to, allocated with new[] */ +// template void Array(T*& x, index_t i, bool nullable); +// /** visits an array pointed to, allocated with malloc */ +// template void MallocArray(T*& x, index_t i, bool nullable); +// }; + +/** + * Perform object-tree traversal on a single object with a given object-tree + * visitor. + * + * The visitor can perform pretty much any function it wants with the + * contents of each data type. It can print, serialize, deserialize, + * pointer-freeze, etc. + */ +template +inline void TraverseObject(T* x, Visitor* v) { + v->Primitive(*x); +} + +/** + * Postprocess function for making copies, to fix anything that may be + * inaccurate from a plain copy. + * + * You will probably never need to implement this. This exists + * mainly so that lazy-rezing data structures (i.e. ArrayList) can serialize + * themselves as their trimmed size -- the TraverseObject function neglects + * saving the capacity, and fills in the capacity upon deserialization. + * Note this should NOT dereference any pointers within the object, just + * update things like flags. + */ +template +inline void TraverseObjectPostprocess(T* x) { +} + +/** + * Traverses an array with a particular visitor. + * + * This is a convenience method that just calls TraverseObject on each + * element. + */ +template +inline void TraverseArray(T* x, index_t n_elems, Visitor *v) { + for (index_t i = 0; i < n_elems; i++) { + TraverseObject(&x[i], v); + } +} + +/** + * Private namespace for object-traversal utilities. + */ +namespace ot_private { + // TODO: Conservatory serialization and deserialization + + /** Visits an object with no OT implementation. */ + template inline void OTPrinter_Primitive(const T& x, Printer* printer) { + printer->Write("%s (don't know how to print)", typeinfo(x).name()); + } + template inline void OTPrinter_Primitive(const char* x, Printer* printer) { + printer->Write("string %s", x); + } + template inline void OTPrinter_Primitive(char x, Printer* printer) { + printer->Write("char %d", x); + } + template inline void OTPrinter_Primitive(short x, Printer* printer) { + printer->Write("short %d", x); + } + template inline void OTPrinter_Primitive(int x, Printer* printer) { + printer->Write("int %d", x); + } + template inline void OTPrinter_Primitive(long x, Printer* printer) { + printer->Write("long %ld", x); + } + template inline void OTPrinter_Primitive(unsigned char x, Printer* printer) { + printer->Write("uchar %u", x); + } + template inline void OTPrinter_Primitive(unsigned short x, Printer* printer) { + printer->Write("ushort %u", x); + } + template inline void OTPrinter_Primitive(unsigned int x, Printer* printer) { + printer->Write("uint %u", x); + } + template inline void OTPrinter_Primitive(unsigned long x, Printer* printer) { + printer->Write("ulong %lu", x); + } + template inline void OTPrinter_Primitive(float x, Printer* printer) { + printer->Write("float %f", x); + } + template inline void OTPrinter_Primitive(double x, Printer* printer) { + printer->Write("double %f", x); + } + + /** + * Takes an OT-compatible object and prints it to screen. + */ + class OTPrinter { + private: + FILE *stream_; + int indent_amount_; + + public: + template + void InitBegin(const T& x, FILE *stream_in) const { + stream_ = stream_in; + indent_amount_ = 0; + TraverseObject(const_cast(&x), this); + } + + template inline void Primitive(const T& x) { + OTPrinter_Primitive(x, this); + } + + template void Object(T* obj, bool nullable) { + if (nullable && !obj) { + Write("object %s NULL {}", typeid(T).name()); + } else { + Indent(2); + Write("object %s {", typeid(T).name()); + TraverseObject(obj, this); + Write("} end object %s", typeid(T).name()); + Indent(-2); + } + } + + template void Array(T* array, index_t len, + bool nullable) { + if (nullable && !array) { + Write("array %s NULL {}", typeid(T).name()); + } else { + Indent(2); + Write("array %s len %"LI"d {", typeid(T).name(), len); + TraverseObject(array, this); + Write("} end array %s", typeid(T).name()); + Indent(-2); + } + } + + /** Visits an internal object. */ + template void MyObject(T& x) { + // Recurse in case this sub-object has pointers + Object(&x); + } + /** Visits an array. */ + template void MyArray(T* x, index_t len) { + // Recurse in case any of these objects have pointers + Array(x, len, false); + } + + /** + * Visits an object pointed to, allocated with new. + * + * This allocates space within the block for the pointer, copies the + * data pointed to, and recurses on the data pointed to. + */ + template void Ptr(T*& source_region, bool nullable) { + Object(source_region, nullable); + } + + /** Visits an array pointed to, allocated with malloc */ + template void MallocArray(T*& source_region, index_t len, + bool nullable) { + Array(source_region, len, nullable); + } + + public: + void Indent(int delta) { + indent_amount_ += delta; + } + + void Write(const char *format, ...); + }; + + /** + * Takes an OT-compatible object and saves a linear copy in a block of + * memory. + * + * This is analogous to serialization but distinct. Serialization does + * not allocate space for transient fields such as pointers. However, this + * dumps every object in its entirety, with the hope that bringing the + * object "back to life" is very quick. When stored, each pointer is + * normalized to zero, and the object can be brought back to life by just + * renormalizing all the pointers. + * + * The code here is far more complex than I expected it to be -- please + * read the comments! + */ + class OTPointerFreezer { + private: + /** The block of memory to freeze into. */ + char *block_; + /** The current position within the block. */ + ptrdiff_t pos_; + /** + * For updating pointers with normalized pointers, this is the difference + * between the destination and source regions for the *current* object + * being considered. + */ + ptrdiff_t freeze_offset_; + + public: + template + void InitBegin(char *block_in, const T& x) { + block_ = block_in; + pos_ = sizeof(T); + freeze_offset_ = PointerDiff(block_, &x); + + mem::Copy(mem::PointerAbsoluteAddress(block_), &x); + // we must cast away const due to TraverseObject's limitations + TraverseObject(const_cast(&x), this); + } + + /** Visits an object with no OT implementation. */ + template void Primitive(T& x) { + // Primitives can be bit-copied + } + + /** Visits an internal object. */ + template void MyObject(T& x) { + // Recurse in case this sub-object has pointers + TraverseObject(&x, this); + } + /** Visits an array. */ + template void MyArray(T* x, index_t len) { + // Recurse in case any of these objects have pointers + TraverseArray(x, len, this); + } + + /** + * Visits an object pointed to, allocated with new. + * + * This allocates space within the block for the pointer, copies the + * data pointed to, and recurses on the data pointed to. + */ + template void Ptr(T*& source_region, bool nullable); + + /** + * Visits an array pointed to, allocated with new[]. + * + * This allocates space within the block for the array, copies the + * data pointed to, and recurses on the array's elements. + */ + template void Array(T*& source_region, index_t len, + bool nullable); + + /** Visits an array pointed to, allocated with malloc */ + template void MallocArray(T*& source_region, index_t len, + bool nullable) { + Array(source_region, len, nullable); + } + + private: + template + /** + * Gets a pointer to the pointer in the destination region that needs + * to be updated. A picture might help. + * + * @param source_region_ptr the pointer to the original pointer, in + * its original location within the larger structure, used with + * pointer arithmetic for updating the resulting pointers + */ + T* DestinationEquivalentPointer_(T** source_region_ptr) { + return PointerAdd(source_region_ptr, freeze_offset_); + } + /** + * Aligns the current position to the given stride, and returns a + * normalized-to-zero pointer for its data, fixing the result pointer + * too. + * + * In reality, this is just a couple assembly instructions. + * + * @param source_region_ptr the pointer to the original pointer, in + * its original location within the larger structure, used with + * pointer arithmetic for updating the resulting pointers + */ + template + T* TranslateAndFixPointer_(T** source_region_ptr) { + // Make sure we are aligned to the proper alignment for the data + pos_ = stride_align(pos_, T); + // Find the pointer in the frozen block by adding the "freeze offset" + // This offset basically says "Given some memory within the live object + // that is being frozen, find the corresponding memory within the + // object that is being frozen". + T** pointer_to_fix = DestinationEquivalentPointer_(source_region_ptr); + // We already copied the source region to the destination we are + // considering, so the value of these two pointers should be equal. + DEBUG_ASSERT(*pointer_to_fix == *source_region_ptr); + // Now, we normalize the pointer such that zero is the beginning of the + // chynk of memory. + *pointer_to_fix = reinterpret_cast(pos_); + // Return the pointer within the block where future accesses should occur. + return reinterpret_cast(block_ + pos_); + } + }; + + template void OTPointerFreezer::Ptr( + T*& source_region, bool nullable) { + if (nullable && unlikely(source_region == NULL)) { + *DestinationEquivalentPointer_(&source_region) = NULL; + } else { + // Get the pointer we will write into, and fix our internal pointer + T* dest = TranslateAndFixPointer_(&source_region); + // Copy the object and progress + pos_ += sizeof(T); + mem::Copy(dest, source_region); + // Save our old freeze offset + size_t freeze_offset_tmp = freeze_offset_; + // Calculate new freeze offset as the distance between the source and + // destination memory regions. + freeze_offset_ = PointerDiff(dest, source_region); + // Recurse on the object. + TraverseObject(source_region, this); + TraverseObjectPostprocess(dest); + // Revert to the old freeze offset. + freeze_offset_ = freeze_offset_tmp; + } + } + + template void OTPointerFreezer::Array( + T*& source_region, index_t len, bool nullable) { + if (nullable && unlikely(source_region == NULL)) { + *DestinationEquivalentPointer_(&source_region) = NULL; + } else { + // Get the pointer we will write into, and fix our internal pointer + T* dest = TranslateAndFixPointer_(&source_region); + // Calculate the total size allocated, copy, and progress + size_t size = len * sizeof(T); + pos_ += size; + mem::CopyBytes(reinterpret_cast(block_ + pos_), source_region, size); + // Save old freeze offset + size_t freeze_offset_tmp = freeze_offset_; + // Calculate new freeze offset + freeze_offset_ = PointerDiff(dest, source_region); + // Recurse over each object + for (index_t i = 0; i < len; i++) { + T* dest_array_element = &dest[i]; + TraverseObject(dest_array_element); + TraverseObjectPostprocess(dest_array_element); + } + // Restore old freeze offset because we have returned to the old object + freeze_offset_ = freeze_offset_tmp; + } + } + + class OTFrozenSizeCalculator { + private: + size_t pos_; + + public: + template + void InitBegin(const T& obj) { + pos_ = 0; + TraverseObject(const_cast(&obj), this); + } + + /** + * Returns the calculated size. + */ + size_t size() const { + return pos_; + } + + /** visits an object with no OT implementation */ + template void Primitive(T& x) {} + /** visits an internal object */ + template void MyObject(T& x) { + TraverseObject(&x, this); + } + /** visits an array */ + template void MyArray(T* x, index_t len) { + TraverseArray(x, len, this); + } + + /** visits an object pointed to, allocated with new */ + template void Ptr(T*& x, bool nullable) { + if (!nullable || x != NULL) { + PretendLayout_(1); + TraverseObject(x, this); + } + } + /** visits an array pointed to, allocated with new[] */ + template void Array(T*& x, index_t len, bool nullable) { + if (!nullable || x != NULL) { + PretendLayout_(len); + TraverseArray(x, len, this); + } + } + /** visits an array pointed to, allocated with malloc */ + template void MallocArray(T*& x, index_t len, bool nullable) { + Array(x, len, nullable); + } + + private: + template + void PretendLayout_(index_t count) { + pos_ = stride_align(pos_, T) + sizeof(T) * count; + } + }; + + class OTPointerThawer { + private: + ptrdiff_t offset_; + + public: + template + void InitBegin(char *data) { + offset_ = reinterpret_cast(data); + TraverseObject(reinterpret_cast(data), this); + } + + /** visits an object with no OT implementation */ + template void Primitive(T& x) {} + /** visits an internal object */ + template void MyObject(T& x) { + TraverseObject(&x, this); + } + /** visits an array */ + template void MyArray(T* x, index_t len) { + TraverseArray(x, len, this); + } + /** visits an object pointed to, allocated with new */ + template void Ptr(T*& x, bool nullable) { + if (!nullable || x != NULL) { + x = mem::PointerAdd(x, offset_); + TraverseObject(x, this); + } + } + /** visits an array pointed to, allocated with new[] */ + template void Array(T*& x, index_t len, bool nullable) { + if (!nullable || x != NULL) { + x = mem::PointerAdd(x, offset_); + TraverseArray(x, len, this); + } + } + /** visits an array pointed to, allocated with malloc */ + template void MallocArray(T*& x, index_t len, bool nullable) { + Array(x, len, nullable); + } + }; + + class OTPointerRefreezer { + private: + ptrdiff_t offset_; + + public: + template + void InitBegin(char *data) { + offset_ = -reinterpret_cast(data); + TraverseObject(reinterpret_cast(data), this); + } + + /** visits an object with no OT implementation */ + template void Primitive(T& x) {} + /** visits an internal object */ + template void MyObject(T& x) { + TraverseObject(&x, this); + } + /** visits an array */ + template void MyArray(T* x, index_t len) { + TraverseArray(x, len, this); + } + /** visits an object pointed to, allocated with new */ + template void Ptr(T*& x, bool nullable) { + if (!nullable || x != NULL) { + TraverseObject(x, this); + x = mem::PointerAdd(x, offset_); + } + } + /** visits an array pointed to, allocated with new[] */ + template void Array(T*& x, index_t len, bool nullable) { + if (!nullable || x != NULL) { + TraverseArray(x, len, this); + x = mem::PointerAdd(x, offset_); + } + } + /** visits an array pointed to, allocated with malloc */ + template void MallocArray(T*& x, index_t len, bool nullable) { + Array(x, len, nullable); + } + }; +}; // namespace ot_private + +template +void OTPrint(const T& object, FILE *stream) { + ot_private::OTPrinter printer; + printer.InitBegin(object, stream); +} + +/** + * Makes a copy of an object, freezing it for the first time. + */ +template +T* OTPointerFreeze(const T& live_object, char *block) { + ot_private::OTPointerFreezer freezer; + freezer.InitBegin(live_object, block); +} + +/** + * Takes an object that is laid out serially, and adjusts all its pointers + * so that they are normalized to zero. + */ +template +T* OTPointerRefreeze(char *block) { + ot_private::OTPointerRefreezer fixer; + fixer.InitBegin(block); +} + +/** + * Takes an object that is laid out serially with all its pointers + * normalized to zero, and makes all the pointers live again. + */ +template +T* OTPointerThaw(char *block) { + ot_private::OTPointerThawer fixer; + fixer.InitBegin(block); +} + +#endif diff --git a/fastlib/col/arraylist.h b/fastlib/col/arraylist.h index 7babb0a467..54cfde8311 100644 --- a/fastlib/col/arraylist.h +++ b/fastlib/col/arraylist.h @@ -11,6 +11,7 @@ #include "base/ccmem.h" #include "base/scale.h" +#include "base/otrav.h" /** * Fast expandable array with debug-mode bounds checking. @@ -64,6 +65,15 @@ class ArrayList { Element* ptr_; index_t size_; index_t cap_; + + OT_DEF(ArrayList) { + OT_MY_OBJECT(size_); + OT_MALLOC_ARRAY_NULLABLE(ptr_, size_); + } + + OT_FIX(ArrayList) { + cap_ = size_; + } public: ArrayList() { diff --git a/fastlib/col/heap.h b/fastlib/col/heap.h index 3d7d8be295..9adc2980ad 100644 --- a/fastlib/col/heap.h +++ b/fastlib/col/heap.h @@ -35,6 +35,10 @@ class MinHeap { ArrayList entries_; + OT_DEF(MinHeap) { + OT_MY_OBJECT(entries_); + } + public: MinHeap() {} ~MinHeap() {} diff --git a/fastlib/col/string.h b/fastlib/col/string.h index 699c125feb..9b5cd8c98f 100644 --- a/fastlib/col/string.h +++ b/fastlib/col/string.h @@ -29,6 +29,10 @@ class String { private: ArrayList array_; + + OT_DEF(String) { + OT_MY_OBJECT(array_); + } public: String() {} diff --git a/fastlib/fastlib/fastlib.h b/fastlib/fastlib/fastlib.h index 479340922d..ebb66c1a9a 100644 --- a/fastlib/fastlib/fastlib.h +++ b/fastlib/fastlib/fastlib.h @@ -20,6 +20,13 @@ //#include "file/serialize.h" #include "file/textfile.h" #include "fx/fx.h" +#include "par/thread.h" +#include "par/grain.h" +#include "tree/spacetree.h" +#include "tree/bounds.h" +#include "tree/statistic.h" +#include "tree/kdtree.h" + /** @mainpage FASTlib Documentation * diff --git a/fastlib/fastlib/fastlib_int.h b/fastlib/fastlib/fastlib_int.h index 1007187286..a0ddf755d4 100644 --- a/fastlib/fastlib/fastlib_int.h +++ b/fastlib/fastlib/fastlib_int.h @@ -28,3 +28,4 @@ #include "tree/statistic.h" #include "tree/kdtree.h" +#include "base/otrav.h" diff --git a/fastlib/file/serialize.h b/fastlib/file/serialize.h index 8f22fb4748..7e4c152719 100644 --- a/fastlib/file/serialize.h +++ b/fastlib/file/serialize.h @@ -70,15 +70,15 @@ class NativeArraySerializer { public: NativeArraySerializer() {} ~NativeArraySerializer() {} - + void Init() { data_.Init(); } - + void PutMagic(file::magic_t magic_num) { Put(magic_num); } - + /** * Appends a struct or primitive to the end. * @@ -89,7 +89,7 @@ class NativeArraySerializer { void Put(const T& val) { mem::CopyBytes(data_.AddBack(sizeof(T)), &val, sizeof(T)); } - + /** * Appends an array of structs or primtives to the end. * @@ -101,6 +101,16 @@ class NativeArraySerializer { size_t bytes = count * sizeof(T); mem::CopyBytes(data_.AddBack(bytes), array, bytes); } + + /** + * Appends an array of non-primitives to the end. + */ + template + void Serialize(const T* array, index_t count) { + for (index_t i = 0; i < count; i++) { + array[i].Serialize(this); + } + } /** * Returns the data serialized, clearing the internal @@ -191,6 +201,16 @@ class NativeArrayDeserializer { pos_ += bytes; } + /** + * Retrieves an array of Serializables. + */ + template + void Deserialize(T *dest, index_t count) { + for (index_t i = 0; i < count; i++) { + dest[i].Deserialize(this); + } + } + /** * Returns whether finished. * diff --git a/fastlib/fx/fx.c b/fastlib/fx/fx.c index 020e932241..4d93aa55ae 100644 --- a/fastlib/fx/fx.c +++ b/fastlib/fx/fx.c @@ -100,6 +100,8 @@ static void fx__attempt_speedup() } void fx_init(int argc, char **argv) { + DEBUG_ASSERT_MSG(fx_root == NULL, "Cannot call fx_init twice."); + pthread_mutex_init(&fx__mutex, NULL); fx_root = malloc(sizeof(struct datanode)); @@ -206,6 +208,9 @@ static void fx__write(struct datanode *node) void fx_done(void) { struct timestamp now; + DEBUG_ASSERT_MSG(fx_root != NULL, + "fx_done called twice, or fx_init was not called."); + /* TODO: Report compile flags. */ /* TODO: Report the command line verbatim. (Avoid doing this.) */ timestamp_now(&now); @@ -218,6 +223,7 @@ void fx_done(void) { datanode_destroy(fx_root); free(fx_root); + fx_root = NULL; pthread_mutex_destroy(&fx__mutex); } diff --git a/fastlib/la/matrix.h b/fastlib/la/matrix.h index 7fd32963f5..7e161f92d1 100644 --- a/fastlib/la/matrix.h +++ b/fastlib/la/matrix.h @@ -13,6 +13,7 @@ #include "base/scale.h" #include "base/cc.h" #include "base/ccmem.h" +#include "base/otrav.h" #include #include @@ -50,6 +51,15 @@ class Vector { /** Whether this should be freed, i.e. it is not an alias. */ bool should_free_; + OT_DEF(Vector) { + OT_MY_OBJECT(length_); + OT_MALLOC_ARRAY(ptr_, length_); + } + + OT_FIX(Vector) { + should_free_ = true; + } + public: /** * Creates a completely uninitialized Vector which must be initialized. @@ -346,6 +356,16 @@ class Matrix { index_t n_cols_; /** Whether I am a strong copy (not an alias). */ bool should_free_; + + OT_DEF(Matrix) { + OT_MY_OBJECT(n_rows_); + OT_MY_OBJECT(n_cols_); + OT_MALLOC_ARRAY(ptr_, n_elements()); + } + + OT_FIX(Matrix) { + should_free_ = true; + } public: /** diff --git a/fastlib/tree/bounds.h b/fastlib/tree/bounds.h index e8ab961f7c..07be479c5b 100644 --- a/fastlib/tree/bounds.h +++ b/fastlib/tree/bounds.h @@ -22,19 +22,32 @@ * * @experimental */ -struct DBound { +struct DRange { public: double lo; double hi; public: - DBound() {} + DRange() {} + DRange(double lo_in, double hi_in) + : lo(lo_in), hi(hi_in) + {} - void Init() { + void InitEmptySet() { lo = DBL_MAX; hi = -DBL_MAX; } + void InitUniversalSet() { + lo = DBL_MAX; + hi = -DBL_MAX; + } + + void Init(double lo_in, double hi_in) { + lo = lo_in; + hi = hi_in; + } + double width() const { return hi - lo; } @@ -42,6 +55,86 @@ struct DBound { double mid() const { return (hi + lo) / 2; } + + const DRange& operator |= (const DRange& other) { + if (unlikely(other.lo > lo)) { + lo = other.lo; + } + if (unlikely(other.hi < hi)) { + hi = other.hi; + } + return *this; + } + + const DRange& operator &= (const DRange& other) { + if (unlikely(other.lo < lo)) { + lo = other.lo; + } + if (unlikely(other.hi > hi)) { + hi = other.hi; + } + return *this; + } + + /** Accumulates a bound difference. */ + const DRange& operator += (const DRange& other) { + lo += other.lo; + hi += other.hi; + return *this; + } + + /** Reverses a bound difference. */ + const DRange& operator -= (const DRange& other) { + lo -= other.lo; + hi -= other.hi; + return *this; + } + + /** Uniformly increases both lower and upper bounds. */ + const DRange& operator += (double d) { + lo += d; + hi += d; + return *this; + } + + /** Uniformly decreases both upper and lower bounds. */ + const DRange& operator -= (double d) { + lo -= d; + hi -= d; + return *this; + } + + friend DRange operator + (const DRange& a, const DRange& b) { + DRange result; + result.lo = a.lo + b.lo; + result.hi = a.hi + b.hi; + return result; + } + + friend DRange operator - (const DRange& a, const DRange& b) { + DRange result; + result.lo = a.lo - b.lo; + result.hi = a.hi - b.hi; + return result; + } + + friend DRange operator + (const DRange& a, double b) { + DRange result; + result.lo = a.lo + b; + result.hi = a.hi + b; + return result; + } + + friend DRange operator - (const DRange& a, double b) { + DRange result; + result.lo = a.lo - b; + result.hi = a.hi - b; + return result; + } + + bool Contains(double d) const { + return d >= lo || d <= hi; + } }; /** @@ -51,7 +144,7 @@ struct DBound { */ class DHrectBound { private: - DBound *bounds_; + DRange *bounds_; //double diagonal_sq_; index_t dim_; @@ -70,7 +163,7 @@ class DHrectBound { DEBUG_ASSERT_MSG(dim_ == BIG_BAD_NUMBER, "Already initialized"); s->Get(&dim_); - bounds_ = mem::Alloc(dim_); + bounds_ = mem::Alloc(dim_); s->Get(bounds_, dim_); //ComputeDiagonal_(); @@ -85,10 +178,10 @@ class DHrectBound { void Init(index_t dimension) { DEBUG_ASSERT_MSG(dim_ == BIG_BAD_NUMBER, "Already initialized"); - bounds_ = mem::Alloc(dimension); + bounds_ = mem::Alloc(dimension); for (index_t i = 0; i < dimension; i++) { - bounds_[i].Init(); + bounds_[i].InitEmptySet(); } dim_ = dimension; @@ -98,7 +191,7 @@ class DHrectBound { bool Belongs(const Vector& point) const { for (index_t i = 0; i < point.length(); i++) { - const DBound *bound = &bounds_[i]; + const DRange *bound = &bounds_[i]; if (point[i] > bound->hi || point[i] < bound->lo) { return false; } @@ -107,15 +200,15 @@ class DHrectBound { return true; } - double MinDistanceSqToInstance(const Vector& point) const { + double MinDistanceSqToPoint(const Vector& point) const { DEBUG_ASSERT(point.length() == dim_); - return MinDistanceSqToInstance(point.ptr()); + return MinDistanceSqToPoint(point.ptr()); } - double MinDistanceSqToInstance(const double *mpoint) const { + double MinDistanceSqToPoint(const double *mpoint) const { double sumsq = 0; //index_t mdim = dim_; - const DBound *mbound = bounds_; + const DRange *mbound = bounds_; index_t d = dim_; @@ -135,7 +228,7 @@ class DHrectBound { return sumsq / 4; } - double MaxDistanceSqToInstance(const Vector& point) const { + double MaxDistanceSqToPoint(const Vector& point) const { double sumsq = 0; DEBUG_ASSERT(point.length() == dim_); @@ -152,8 +245,8 @@ class DHrectBound { double MinDistanceSqToBound(const DHrectBound& other) const { double sumsq = 0; - const DBound *a = this->bounds_; - const DBound *b = other.bounds_; + const DRange *a = this->bounds_; + const DRange *b = other.bounds_; index_t mdim = dim_; DEBUG_ASSERT(dim_ == other.dim_); @@ -188,8 +281,8 @@ class DHrectBound { double MinDistanceSqToBoundFarEnd(const DHrectBound& other) const { double sumsq = 0; - const DBound *a = this->bounds_; - const DBound *b = other.bounds_; + const DRange *a = this->bounds_; + const DRange *b = other.bounds_; index_t mdim = dim_; DEBUG_ASSERT(dim_ == other.dim_); @@ -209,8 +302,8 @@ class DHrectBound { double MaxDistanceSqToBound(const DHrectBound& other) const { double sumsq = 0; - const DBound *a = this->bounds_; - const DBound *b = other.bounds_; + const DRange *a = this->bounds_; + const DRange *b = other.bounds_; DEBUG_ASSERT(dim_ == other.dim_); @@ -225,8 +318,8 @@ class DHrectBound { double MidDistanceSqToBound(const DHrectBound& other) const { double sumsq = 0; - const DBound *a = this->bounds_; - const DBound *b = other.bounds_; + const DRange *a = this->bounds_; + const DRange *b = other.bounds_; DEBUG_ASSERT(dim_ == other.dim_); @@ -243,7 +336,7 @@ class DHrectBound { DEBUG_ASSERT(vector.length() == dim_); for (index_t i = 0; i < dim_; i++) { - DBound* bound = &bounds_[i]; + DRange* bound = &bounds_[i]; double d = vector[i]; if (unlikely(d > bound->hi)) { @@ -255,7 +348,7 @@ class DHrectBound { } } - const DBound& get(index_t i) const { + const DRange& get(index_t i) const { return bounds_[i]; } @@ -292,26 +385,26 @@ class DEuclideanMetric { * * @experimental */ -template +template class BallBound { FORBID_COPY(BallBound); public: typedef TMetric Metric; - typedef TInstance Instance; + typedef TPoint Point; private: - Instance center_; + Point center_; double radius_; public: BallBound() {} - const Instance& center() const { + const Point& center() const { return center; } - Instance& center() { + Point& center() { return center; } @@ -323,19 +416,19 @@ class BallBound { radius = d; } - double DistanceToCenter(const Instance& point) { + double DistanceToCenter(const Point& point) { return Metric::CalculateMetric(point, center_); } - bool Belongs(const Instance& point) { + bool Belongs(const Point& point) { return DistanceToCenter(point) <= radius_; } - double MinDistanceToInstance(const Instance& point) { + double MinDistanceToPoint(const Point& point) { return max(0.0, DistanceToCenter(point) - radius_); } - double MaxDistanceToInstance(const Instance& point) { + double MaxDistanceToPoint(const Point& point) { return DistanceToCenter(point) + radius_; } @@ -352,7 +445,7 @@ class BallBound { return DistanceToCenter(other.center_); } - double MidDistanceToInstance(const Instance& point) { + double MidDistanceToPoint(const Point& point) { return DistanceToCenter(point); } }; diff --git a/fastlib/u/garryb/allnn/allnn.cc b/fastlib/u/garryb/allnn/allnn.cc index 4a15e888d0..6489264bc1 100644 --- a/fastlib/u/garryb/allnn/allnn.cc +++ b/fastlib/u/garryb/allnn/allnn.cc @@ -280,7 +280,7 @@ void AllNNDualTree::BaseCase(Tree *q, const Tree *r, double closest_offer) { double q_best_dist = results_[q_i].dist; if (closest_offer < q_best_dist - && r->bound().MinDistanceSqToInstance(q_col) < q_best_dist) { + && r->bound().MinDistanceSqToPoint(q_col) < q_best_dist) { for (index_t r_i = r->begin(); r_i < r_end; r_i++) { const double *r_col = r_matrix_.GetColumnPtr(r_i); double dist = la::DistanceSqEuclidean( diff --git a/fastlib/u/garryb/superpar/otree.h b/fastlib/u/garryb/superpar/otree.h index 94b737966d..09e552efb7 100644 --- a/fastlib/u/garryb/superpar/otree.h +++ b/fastlib/u/garryb/superpar/otree.h @@ -1,112 +1,659 @@ -#define USE_OT(AClass, visitor) \ + + + + + + + + + + + + + + + + + + + + + + + + +do not edit this file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#define OT_MY_OBJECT(x) v_OT->MyObject(this->x) +#define OT_MY_ARRAY(x, i) v_OT->MyArray(this->x, i) +#define OT_PTR(x) v_OT->Ptr(this->x, false) +#define OT_ARRAY(x, i) v_OT->Array(this->x, i, false) +#define OT_MALLOC_ARRAY(x, i) v_OT->MallocArray(this->x, i, false) +#define OT_PTR_NULLABLE(x) v_OT->Ptr(this->x, true) +#define OT_ARRAY_NULLABLE(x, i) v_OT->Array(this->x, i, true) +#define OT_MALLOC_ARRAY_NULLABLE(x, i) v_OT->MallocArray(this->x, i, true) + +#define OT_DEF(AClass) \ public: \ template \ - friend void TraverseObject(AClass *x, Visitor *v) { \ - x->TraverseObject(v); \ + friend void TraverseObject(AClass *obj_OT, Visitor *v_OT) { \ + obj_OT->TraverseObject__OT_(v_OT); \ } \ - \ private: \ template \ - void TraverseObject_(Visitor *visitor_variable_name) \ + void TraverseObject__OT_(Visitor *v_OT) \ + /* user fills in definition */ -#define USE_OT_FULL(AClass, visitor_variable_name) \ +#define OT_GENERATE_PRINT(AClass) \ public: \ - ~AClass() { \ - OTDestructorVisitor v; \ - TraverseObject_(&v); \ + template<> \ + friend void Print(const AClass& obj, FILE *stream) { \ + OTPrint(obj, stream); + } + +// TODO: Automatically generate copy constructors and the like + +#define OT_FULL(AClass) \ + OT_GENERATE_PRINT(AClass) \ + OT_DEF(AClass) + +#define OT_FIX(AClass) \ + public: \ + template \ + friend void TraverseObjectPostprocess(AClass *x) { \ + x->TraverseObjectPostprocess__OT_(v); \ } \ - \ - AClass(const AClass& other) { \ - OTCopyConstructorVisitor v(other); \ - TraverseObject_(&v); \ - } \ - \ - const AClass& operator = (const AClass& other) { \ - OTAssignmentVisitor v(other); \ - TraverseObject_(&v); \ - } \ - \ - USE_OT(AClass, visitor) + private: \ + template \ + friend void TraverseObjectPostprocess__OT_() -class OTVisitor { - public: - template void Mine(T& x); - template void Ptr(T*& x); - template void Array(T*& x, int i); - template void MallocPtr(T*& x); - template void MallocArray(T*& x, int i); -}; +// The object-tree-visitor interface. +// class OTBlankVisitor { +// public: +// /** visits an object with no OT implementation */ +// template void Primitive(T& x); +// /** visits an internal object */ +// template void MyObject(T& x); +// /** visits an array */ +// template void MyArray(T* x, index_t i); +// /** visits an object pointed to, allocated with new */ +// template void Ptr(T*& x, bool nullable); +// /** visits an array pointed to, allocated with new[] */ +// template void Array(T*& x, index_t i, bool nullable); +// /** visits an array pointed to, allocated with malloc */ +// template void MallocArray(T*& x, index_t i, bool nullable); +// }; -template -void TraverseObject(X* x, Visitor* v) { +/** + * Object-tree traversal on a single object. + * + * Objects should implement this in order to allow serialization + * and pointer freeze. + */ +template +inline void TraverseObject(T* x, Visitor* v) { v->Primitive(*x); } -class OTDestructorVisitor { - public: - template void Primitive(T& x) {} - template void Mine(T& x) { - // not necessary for destructors - destructors chain automatically - } - template void Ptr(T*& x) { - delete x; - } - template void Array(T*& x, int i) { - delete[] x; - } - template void MallocPtr(T*& x) { - x->~T(); free(x); DEBUG_POISON_PTR(x); - } - template void MallocArray(T*& x, int i) { - mem::DestructAll(x, t); free(x); DEBUG_POISON_PTR(x); - } -}; +/** + * Postprocess function for making copies, to fix anything that may be + * inaccurate from a plain copy. + * + * You will probably never need to implement this. This exists + * mainly so that lazy-rezing data structures (i.e. ArrayList) can serialize + * themselves as their trimmed size -- the TraverseObject function neglects + * saving the capacity, and fills in the capacity upon deserialization. + * Note this should NOT dereference any pointers within the object, just + * update things like flags. + */ +template +inline void TraverseObjectPostprocess(T* x) { +} -class OTCopyConstructorVisitor { - public: - template void Primitive(T& x) { - *x = *GetPointer(x); +/** + * Traverses an array with a particular visitor. + */ +template +inline void TraverseArray(T* x, index_t n_elems, Visitor *v) { + for (index_t i = 0; i < n_elems; i++) { + TraverseObject(&x[i], v); } - template void Mine(T& x) { - TraverseObject(x, this); - } - template void Ptr(T*& x) { - recursively copy x - } - template void Array(T*& x, int i) { - recursively copy x - } - template void MallocPtr(T*& x) { - recursively copy x - } - template void MallocArray(T*& x, int i) { - recursively copy x - } -}; +} -class OTDestructorVisitor { - void -}; +/** + * Private namespace for object-traversal utilities. + */ +namespace ot_private { + // TODO: Conservatory serialization and deserialization + /** + * Takes an OT-compatible object and prints it to screen. + */ + class OTPrinter { + private: + FILE *stream_; + int indent_amount_; + + public: + template + void InitBegin(const T& x, FILE *stream_in) const { + stream_ = stream_in; + indent_amount_ = 0; + TraverseObject(const_cast&x, this); + } + + /** Visits an object with no OT implementation. */ + template void Primitive(const T& x) const { + Write_("%s (don't know how to print)", typeinfo(x).name()); + } + template<> void Primitive(const char* x) const { + Write_("string %s", x); + } + template<> void Primitive(char x) const { + Write_("char %d", x); + } + template<> void Primitive(short x) const { + Write_("short %d", x); + } + template<> void Primitive(int x) const { + Write_("int %d", x); + } + template<> void Primitive(long x) const { + Write_("long %ld", x); + } + template<> void Primitive(char x) const { + Write_("uchar %u", x); + } + template<> void Primitive(short x) const { + Write_("ushort %u", x); + } + template<> void Primitive(int x) const { + Write_("uint %u", x); + } + template<> void Primitive(long x) const { + Write_("ulong %lu", x); + } + template<> void Primitive(float x) const { + Write_("float %f", x); + } + template<> void Primitive(double x) const { + Write_("double %f", x); + } -class AClass { - Foo a; - Bar b; + template void Object(T& obj, bool nullable) { + if (nullable && !source_region) { + Write_("object %s NULL {}", typeid(T).name()); + } else { + Indent_(2); + Write_("object %s {", typeid(source_region).name()); + TraverseObject(source_region, this); + Write_("} end object %s", typeid(T).name()); + Indent_(-2); + } + } + + template void Array(T* source_region, index_t len, + bool nullable) { + if (nullable && !source_region) { + Write_("array %s NULL {}", typeid(T).name()); + } else { + Indent_(2); + Write_("array %s len %"LI"d {", typeid(source_region).name(), len); + TraverseObject(source_region, this); + Write_("} end array %s", typeid(T).name()); + Indent_(-2); + } + } + + /** Visits an internal object. */ + template void MyObject(T& x) { + // Recurse in case this sub-object has pointers + Object(x); + } + /** Visits an array. */ + template void MyArray(T* x, index_t len) { + // Recurse in case any of these objects have pointers + Array(x, len, false); + } + + /** + * Visits an object pointed to, allocated with new. + * + * This allocates space within the block for the pointer, copies the + * data pointed to, and recurses on the data pointed to. + */ + template void Ptr(T*& source_region, bool nullable) { + Object(*source_region, nullable); + } + + /** Visits an array pointed to, allocated with malloc */ + template void MallocArray(T*& source_region, index_t len, + bool nullable) { + Array(source_region, len, nullable); + } + private: + void Indent_(int delta) { + indent_amount_ += delta; + } + + void Write_(const char *format, ...); + }; - USE_OT(AClass, v) { - v->Mine(a); - v->Mine(b); + void OTPrinter::Write_(const char *format, ...) { + va_list vl; + for (int i = 0; i < indent_amount_; i++) { + putc(' ', stream); + } + va_start(vl, format); + vfprintf(stream_, format, vl); + va_end(vl); + putc('\n', stream_); } -}; -class Vector { - double *d; - index_t len; - - USE_OT(Vector, v) { - v->Mine(len); - v->MallocPtr(d, len); + + + + + + /** + * Takes an OT-compatible object and saves a linear copy in a block of + * memory. + * + * This is analogous to serialization but distinct. Serialization does + * not allocate space for transient fields such as pointers. However, this + * dumps every object in its entirety, with the hope that bringing the + * object "back to life" is very quick. When stored, each pointer is + * normalized to zero, and the object can be brought back to life by just + * renormalizing all the pointers. + * + * The code here is far more complex than I expected it to be -- please + * read the comments! + */ + class OTPointerFreezer { + private: + /** The block of memory to freeze into. */ + char *block_; + /** The current position within the block. */ + ptrdiff_t pos_; + /** + * For updating pointers with normalized pointers, this is the difference + * between the destination and source regions for the *current* object + * being considered. + */ + ptrdiff_t freeze_offset_; + + public: + template + void InitBegin(char *block_in, const T& x) { + block_ = block_in; + pos_ = sizeof(T); + freeze_offset_ = PointerDiff(block_, &x); + + mem::Copy(mem::PointerAbsoluteAddress(block_), &x); + // we must cast away const due to TraverseObject's limitations + TraverseObject(const_cast&x, this); + } + + /** Visits an object with no OT implementation. */ + template void Primitive(T& x) { + // Primitives can be bit-copied + } + + /** Visits an internal object. */ + template void MyObject(T& x) { + // Recurse in case this sub-object has pointers + TraverseObject(&x, this); + } + /** Visits an array. */ + template void MyArray(T* x, index_t len) { + // Recurse in case any of these objects have pointers + TraverseArray(x, len, this); + } + + /** + * Visits an object pointed to, allocated with new. + * + * This allocates space within the block for the pointer, copies the + * data pointed to, and recurses on the data pointed to. + */ + template void Ptr(T*& source_region, bool nullable); + + /** + * Visits an array pointed to, allocated with new[]. + * + * This allocates space within the block for the array, copies the + * data pointed to, and recurses on the array's elements. + */ + template void Array(T*& source_region, index_t len, + bool nullable); + + /** Visits an array pointed to, allocated with malloc */ + template void MallocArray(T*& source_region, index_t len, + bool nullable) { + Array(source_region, len, nullable); + } + + private: + template + /** + * Gets a pointer to the pointer in the destination region that needs + * to be updated. A picture might help. + * + * @param source_region_ptr the pointer to the original pointer, in + * its original location within the larger structure, used with + * pointer arithmetic for updating the resulting pointers + */ + T* DestinationEquivalentPointer_(T** source_region_ptr) { + return PointerAdd(source_region_ptr, freeze_offset_); + } + /** + * Aligns the current position to the given stride, and returns a + * normalized-to-zero pointer for its data, fixing the result pointer + * too. + * + * In reality, this is just a couple assembly instructions. + * + * @param source_region_ptr the pointer to the original pointer, in + * its original location within the larger structure, used with + * pointer arithmetic for updating the resulting pointers + */ + template + T* TranslateAndFixPointer_(T** source_region_ptr) { + // Make sure we are aligned to the proper alignment for the data + pos_ = stride_align(pos_, T); + // Find the pointer in the frozen block by adding the "freeze offset" + // This offset basically says "Given some memory within the live object + // that is being frozen, find the corresponding memory within the + // object that is being frozen". + T** pointer_to_fix = DestinationEquivalentPointer_(source_region_ptr); + // We already copied the source region to the destination we are + // considering, so the value of these two pointers should be equal. + DEBUG_ASSERT(*pointer_to_fix == *source_region); + // Now, we normalize the pointer such that zero is the beginning of the + // chynk of memory. + *pointer_to_fix = reinterpret_cast(pos_); + // Return the pointer within the block where future accesses should occur. + return reinterpret_cast(block_ + pos); + } + }; + + template void OTPointerFreezer::Ptr( + T*& source_region, bool nullable) { + if (nullable && unlikely(source_region == NULL)) { + *DestinationEquivalentPointer_(&source_region) = NULL; + } else { + // Get the pointer we will write into, and fix our internal pointer + T* dest = TranslateAndFixPointer_(&source_region); + // Copy the object and progress + pos_ += sizeof(T); + mem::Copy(dest, source_region); + // Save our old freeze offset + size_t freeze_offset_tmp = freeze_offset; + // Calculate new freeze offset as the distance between the source and + // destination memory regions. + freeze_offset_ = PointerDiff(dest, source_region); + // Recurse on the object. + TraverseObject(source_region, this); + TraverseObjectPostprocess(dest); + // Revert to the old freeze offset. + freeze_offset = freeze_offset_tmp; + } } -}; + + template void OTPointerFreezer::Array( + T*& source_region, index_t len) { + if (nullable && unlikely(source_region == NULL)) { + *DestinationEquivalentPointer_(&source_region) = NULL; + } else { + // Get the pointer we will write into, and fix our internal pointer + T* dest = TranslateAndFixPointer_(&source_region); + // Calculate the total size allocated, copy, and progress + size_t size = n * sizeof(T); + pos_ += size; + mem::CopyBytes(reinterpret_cast(block_ + pos), source_region, size); + // Save old freeze offset + size_t freeze_offset_tmp = freeze_offset; + // Calculate new freeze offset + freeze_offset_ = PointerDiff(dest, source_region); + // Recurse over each object + for (index_t i = 0; i < len; i++) { + T* dest_array_element = &dest[i]; + TraverseObject(dest_array_element); + TraverseObjectPostprocess(dest_array_element); + } + // Restore old freeze offset because we have returned to the old object + freeze_offset_ = freeze_offset_tmp; + } + } + + class OTFrozenSizeCalculator { + private: + size_t pos_; + + public: + template + void InitBegin(const T& obj) { + pos_ = 0; + TraverseObject(const_cast(&obj), this); + } + + /** + * Returns the calculated size. + */ + size_t size() const { + return pos_; + } + + /** visits an object with no OT implementation */ + template void Primitive(T& x) {} + /** visits an internal object */ + template void MyObject(T& x) { + TraverseObject(&x, this); + } + /** visits an array */ + template void MyArray(T* x, index_t len) { + TraverseArray(x, len, this); + } + + /** visits an object pointed to, allocated with new */ + template void Ptr(T*& x, bool nullable) { + if (!nullable || x != NULL) { + PretendLayout_(1); + TraverseObject(x, this); + } + } + /** visits an array pointed to, allocated with new[] */ + template void Array(T*& x, index_t len, bool nullable) { + if (!nullable || x != NULL) { + PretendLayout_(len); + TraverseArray(x, len, this); + } + } + /** visits an array pointed to, allocated with malloc */ + template void MallocArray(T*& x, index_t len, bool nullable) { + Array(x, len, nullable); + } + + private: + template + void PretendLayout_(index_t count) { + pos_ = stride_align(pos_, T) + sizeof(T) * count; + } + }; + + class OTPointerThawer { + private: + ptrdiff_t offset_; + + public: + template + void InitBegin(char *data) { + offset_ = reinterpret_cast(data); + TraverseObject(reinterpret_cast(data), this); + } + + /** visits an object with no OT implementation */ + template void Primitive(T& x) {} + /** visits an internal object */ + template void MyObject(T& x) { + TraverseObject(&x, this); + } + /** visits an array */ + template void MyArray(T* x, index_t len) { + TraverseArray(x, len, this); + } + /** visits an object pointed to, allocated with new */ + template void Ptr(T*& x, bool nullable) { + if (!nullable || x != NULL) { + x = mem::PointerAdd(x, offset); + TraverseObject(x, this); + } + } + /** visits an array pointed to, allocated with new[] */ + template void Array(T*& x, index_t len, bool nullable) { + if (!nullable || x != NULL) { + x = mem::PointerAdd(x, offset); + TraverseArray(x, len, this); + } + } + /** visits an array pointed to, allocated with malloc */ + template void MallocArray(T*& x, index_t len, bool nullable) { + Array(x, len, nullable); + } + }; + + class OTPointerRefreezer { + private: + ptrdiff_t offset_; + + public: + template + void InitBegin(char *data) { + offset_ = -reinterpret_cast(data); + TraverseObject(reinterpret_cast(data), this); + } + + /** visits an object with no OT implementation */ + template void Primitive(T& x) {} + /** visits an internal object */ + template void MyObject(T& x) { + TraverseObject(&x, this); + } + /** visits an array */ + template void MyArray(T* x, index_t len) { + TraverseArray(x, len, this); + } + /** visits an object pointed to, allocated with new */ + template void Ptr(T*& x, bool nullable) { + if (!nullable || x != NULL) { + TraverseObject(x, this); + x = mem::PointerAdd(x, offset); + } + } + /** visits an array pointed to, allocated with new[] */ + template void Array(T*& x, index_t len, bool nullable) { + if (!nullable || x != NULL) { + TraverseArray(x, len, this); + x = mem::PointerAdd(x, offset); + } + } + /** visits an array pointed to, allocated with malloc */ + template void MallocArray(T*& x, index_t len, bool nullable) { + Array(x, len, nullable); + } + }; +}; // namespace ot_private + +template +void OTPrint(const T& object, FILE *stream) { + ot_private::OTPrinter printer; + printer.InitBegin(object, stream); +} + +/** + * Makes a copy of an object, freezing it for the first time. + */ +template +T* OTPointerFreeze(const T& live_object, char *block) { + ot_private::OTPointerFreezer freezer; + freezer.InitBegin(live_object, block); +} + +/** + * Takes an object that is laid out serially, and adjusts all its pointers + * so that they are normalized to zero. + */ +template +T* OTPointerRefreeze(char *block) { + ot_private::OTPointerFixer fixer; + fixer.InitBegin(block); +} + +/** + * Takes an object that is laid out serially with all its pointers + * normalized to zero, and makes all the pointers live again. + */ +template +T* OTPointerThaw(char *block) { + ot_private::OTPointerFixer fixer; + fixer.InitBegin(block); +}