From 5786f5a2af15f09aa0f20b81b2f25231e5dd6bc1 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Fri, 14 Oct 2016 11:10:19 +0300 Subject: [PATCH] Add basic exception supports and tagged refcounting. --- runtime/src/main/cpp/Assert.cpp | 8 ++ runtime/src/main/cpp/Assert.h | 14 ++ runtime/src/main/cpp/Exceptions.cpp | 29 ++++ runtime/src/main/cpp/Exceptions.h | 19 +++ runtime/src/main/cpp/Memory.cpp | 4 +- runtime/src/main/cpp/Memory.h | 207 ++++++---------------------- runtime/src/main/cpp/Names.h | 8 +- 7 files changed, 121 insertions(+), 168 deletions(-) create mode 100644 runtime/src/main/cpp/Assert.cpp create mode 100644 runtime/src/main/cpp/Assert.h create mode 100644 runtime/src/main/cpp/Exceptions.cpp create mode 100644 runtime/src/main/cpp/Exceptions.h diff --git a/runtime/src/main/cpp/Assert.cpp b/runtime/src/main/cpp/Assert.cpp new file mode 100644 index 00000000000..7de799a9a55 --- /dev/null +++ b/runtime/src/main/cpp/Assert.cpp @@ -0,0 +1,8 @@ +#include +#include + +void RuntimeAssertFailed(const char* location, const char* message) { + // TODO: produce stacktrace and such. + fprintf(stderr, "%s: runtime assert: %s\n", location, message); + abort(); +} diff --git a/runtime/src/main/cpp/Assert.h b/runtime/src/main/cpp/Assert.h new file mode 100644 index 00000000000..54a193a2e67 --- /dev/null +++ b/runtime/src/main/cpp/Assert.h @@ -0,0 +1,14 @@ +#ifndef RUNTIME_ASSERT_H +#define RUNTIME_ASSERT_H + +void RuntimeAssertFailed(const char* location, const char* message); + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +#define RuntimeAssert(condition, message) \ + if (!(condition)) { \ + RuntimeAssertFailed( __FILE__ ":" TOSTRING(__LINE__), message); \ + } + +#endif // RUNTIME_ASSERT_H diff --git a/runtime/src/main/cpp/Exceptions.cpp b/runtime/src/main/cpp/Exceptions.cpp new file mode 100644 index 00000000000..244cd896841 --- /dev/null +++ b/runtime/src/main/cpp/Exceptions.cpp @@ -0,0 +1,29 @@ +#include + +#include "Assert.h" +#include "Exceptions.h" + +#ifdef __cplusplus +extern "C" { +#endif +void ThrowNullPointerException() { + void* pc = __builtin_return_address(1); + char message[100]; + snprintf(message, sizeof(message), "NullPointerException at %p", pc); + fprintf(stderr, "%s\n", message); + // TODO: throw it for real. + RuntimeAssert(false, "Throwing is unsupported"); +} + +void ThrowArrayIndexOutOfBoundsException() { + void* pc = __builtin_return_address(1); + char message[100]; + snprintf(message, sizeof(message), "ArrayIndexOutOfBoundsException at %p", pc); + fprintf(stderr, "%s\n", message); + // TODO: throw it for real. + RuntimeAssert(false, "Throwing is unsupported"); +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/runtime/src/main/cpp/Exceptions.h b/runtime/src/main/cpp/Exceptions.h new file mode 100644 index 00000000000..2b4cff40828 --- /dev/null +++ b/runtime/src/main/cpp/Exceptions.h @@ -0,0 +1,19 @@ +#ifndef RUNTIME_EXCEPTIONS_H +#define RUNTIME_EXCEPTIONS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif +// Throws null pointer exception. Context is evaluated from caller's address. +void ThrowNullPointerException(); +// Throws array index out of bounds exception. +// Context is evaluated from caller's address. +void ThrowArrayIndexOutOfBoundsException(); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // RUNTIME_NAMES_H diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index 2b278460315..5119e41b7b7 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -12,7 +12,7 @@ ArenaContainer::ArenaContainer(uint32_t size) { ArenaContainerHeader* header = reinterpret_cast( calloc(size + sizeof(ArenaContainerHeader), 1)); header_ = header; - header->ref_count_ = 1; + header->ref_count_ = CONTAINER_TAG_INCREMENT; header->current_ = reinterpret_cast(header_) + sizeof(ArenaContainerHeader); header->end_ = header->current_ + size; } @@ -21,7 +21,7 @@ void ObjectContainer::Init(const TypeInfo* type_info, uint32_t elements) { header_ = reinterpret_cast( calloc(sizeof(ContainerHeader) + sizeof(ObjHeader) + type_info->size_ * elements, 1)); - header_->ref_count_ = 1; + header_->ref_count_ = CONTAINER_TAG_INCREMENT; SetMeta(GetPlace(), type_info); } diff --git a/runtime/src/main/cpp/Memory.h b/runtime/src/main/cpp/Memory.h index 4335826988f..09eb205f821 100644 --- a/runtime/src/main/cpp/Memory.h +++ b/runtime/src/main/cpp/Memory.h @@ -1,16 +1,29 @@ #ifndef RUNTIME_MEMORY_H #define RUNTIME_MEMORY_H -#include - +#include "Assert.h" #include "TypeInfo.h" typedef enum { - FRAME_SCOPE = 0, - GLOBAL_SCOPE = 1, - ARENA_SCOPE = 2 + SCOPE_FRAME = 0, + SCOPE_GLOBAL = 1, + SCOPE_ARENA = 2 } PlacementHint; +// Must fit in two bits. +typedef enum { + // Container is normal thread local container. + CONTAINER_TAG_NORMAL = 0, + // Container shall not be refcounted (const data, frame locals). + CONTAINER_TAG_NOCOUNT = 1, + // Container shall be atomically refcounted. + CONTAINER_TAG_SHARED = 2, + // Actual value to increment/decrement conatiner by. Tag is in lower bits. + CONTAINER_TAG_INCREMENT = 1 << 2, + // + CONTAINER_TAG_MASK = (CONTAINER_TAG_INCREMENT - 1) +} ContainerTag; + // Could be made 64-bit for large memory configs. typedef uint32_t container_offset_t; @@ -29,7 +42,7 @@ struct ArrayHeader : public ObjHeader { struct ContainerHeader { // Reference counter of container. Maybe use some upper bit of counter for // container type (for polymorphism in ::Release()). - uint32_t ref_count_; + volatile uint32_t ref_count_; }; struct ArenaContainerHeader : public ContainerHeader { @@ -44,17 +57,36 @@ struct ArenaContainerHeader : public ContainerHeader { // to objects. inline void AddRef(ContainerHeader* header) { // Looking at container type we may want to skip AddRef() totally - // (non-escaping stack objects). - header->ref_count_++; + // (non-escaping stack objects, constant objects). + switch (header->ref_count_ & CONTAINER_TAG_MASK) { + case CONTAINER_TAG_NORMAL: + header->ref_count_ += CONTAINER_TAG_INCREMENT; + break; + case CONTAINER_TAG_NOCOUNT: + break; + case CONTAINER_TAG_SHARED: + __sync_fetch_and_add(&header->ref_count_, CONTAINER_TAG_INCREMENT); + break; + } } void FreeObject(ContainerHeader* header); inline void Release(ContainerHeader* header) { - // Looking at container type we may want to skip Release() totally - // (non-escaping stack objects, permanent objects). - if (--header->ref_count_ == 0) { - FreeObject(header); + switch (header->ref_count_ & CONTAINER_TAG_MASK) { + case CONTAINER_TAG_NORMAL: + if ((header->ref_count_ -= CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_NORMAL) { + FreeObject(header); + } + break; + case CONTAINER_TAG_NOCOUNT: + break; + case CONTAINER_TAG_SHARED: + if (__sync_sub_and_fetch( + &header->ref_count_, CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_SHARED) { + FreeObject(header); + } + break; } } @@ -116,7 +148,7 @@ class ArenaContainer : public Container { ~ArenaContainer() { if (header_) { - assert(header_->ref_count_ == 0); + RuntimeAssert(header_->ref_count_ == 0, "Non-zero refcount"); Dispose(); } } @@ -149,155 +181,6 @@ class ArenaContainer : public Container { } }; -// Raw reference to data, meaning T*, invented only for cleaness of intentions. -template -class RawRef { - private: - T* ptr_; - public: - RawRef(T* ptr) : ptr_(ptr) {} - const T& get() const { return *ptr_; } - void set(const T& value) { *ptr_ = value; } -}; - -// Object reference, adds reference counting in container and type information. -class AnyObjRef { - protected: - ObjHeader* ptr_; - - explicit AnyObjRef(ObjHeader* ptr) : ptr_(ptr) { - if (ptr_) { - AddRef(container_header()); - } - } - - public: - ~AnyObjRef() { - if (ptr_) { - Release(container_header()); - } - } - - ContainerHeader* container_header() const { - return reinterpret_cast( - reinterpret_cast(ptr_) - ptr_->container_offset_negative_); - } - - const TypeInfo* type_info() const { - return ptr_->type_info_; - } - - // Accesses raw data inside object specified by offset. Typing by M is optional and - // will be replaced by translator typing. - template - RawRef at() const { - return RawRef( - reinterpret_cast(reinterpret_cast(any_ref()) + offset)); - } - - // Assign reference to certain object. Releases currently held object in its container and - // adds reference to container storing given object. - void Assign(const AnyObjRef& other) { - // TODO: optimize for an important case where containers match? - if (ptr_) { - Release(container_header()); - } - ptr_ = other.ptr_; - if (ptr_) { - AddRef(container_header()); - } - } - - // Returns pointer to the raw data referred by this reference. - uint8_t* any_ref() const { - if (!ptr_) return nullptr; - return reinterpret_cast(ptr_) + sizeof(ObjHeader); - } - - // Uses pointer stored in object's field to create a reference to that object. - AnyObjRef any_obj_at(int offset) const { - assert(ptr_); - return AnyObjRef(*reinterpret_cast(any_ref() + offset)); - } - - // Checks if given reference has null value. - bool null() const { return ptr_ == nullptr; } -}; - -// Returns typeinfo for array of type T. Specialize for types which are allowed as array elements. -template -const TypeInfo* GetArrayTypeInfo() { - return nullptr; -} - -// Reference to an object with particular memory layout specified by T. -// In real runtime will be compile time only type, on runtime all references are -// AnyObjRef. -template -class ObjRef : public AnyObjRef { - private: - explicit ObjRef(ObjHeader* ptr) : AnyObjRef(ptr) {} - - // Reference to raw data in owned class. - T* ref() const { - if (!ptr_) return nullptr; - return reinterpret_cast(any_ref()); - } - - template friend class ArrayRef; - - public: - // Assigns reference, compile time type-safe. - ObjRef(const ObjRef& other) : AnyObjRef(nullptr) { - Assign(other); - } - void Assign(const ObjRef& other) { - AnyObjRef::Assign(other); - } - - // Copies data bits to another place, reference counting is properly accounted for - // by consulting type information. - void CopyTo(ObjRef other) const; - - // Clones object to given container. - ObjRef Clone(ArenaContainer* container) { - ObjRef result = Alloc(container); - CopyTo(result); - return result; - } - - // Takes typed object reference at offset. - template - ObjRef obj_at() const { - return ObjRef(reinterpret_cast(any_ref() + offset)); - } - - // Allocates properly typed object in container. - static ObjRef Alloc(ArenaContainer* container) { - return ObjRef(container->PlaceObject(T::GetTypeInfo())); - } -}; - -// This is an array of value types only, no object references here. -template -class ArrayRef : public AnyObjRef { - protected: - explicit ArrayRef(ArrayHeader* ptr) : AnyObjRef(ptr) {} - ArrayHeader* header() { return reinterpret_cast(ptr_); } - - public: - static ArrayRef Alloc(ArenaContainer* container, int count) { - auto result = ArrayRef(container->PlaceArray(GetArrayTypeInfo(), count)); - result.header()->count_ = count; - return result; - } - - RawRef element_at(int index) const { - assert(header() && index >= 0 && index < header()->count_); - return reinterpret_cast(any_ref() + index * sizeof(T)); - } -}; - #ifdef __cplusplus extern "C" { #endif diff --git a/runtime/src/main/cpp/Names.h b/runtime/src/main/cpp/Names.h index 91a8f332449..4ee931b9d84 100644 --- a/runtime/src/main/cpp/Names.h +++ b/runtime/src/main/cpp/Names.h @@ -1,15 +1,15 @@ #ifndef RUNTIME_NAMES_H #define RUNTIME_NAMES_H -#include +#include // All names in system are stored as hashes (or maybe, for debug builds, // as pointers to uniqued C strings containing names?). // There are two types of hashes: -// - local hash, must be unique per class (CityHash64 is being used) +// - local hash, must be unique per class/scope (CityHash64 is being used) // - global hash, must be unique globally (SHA1 is being used) -// Generic guideline is that global hash is being used in global persistent context, while local -// hashes are more local in scope. +// Generic guideline is that global hash is being used in global persistent +// context, while local hashes are more local in scope. // Local hash. typedef uint64_t LocalHash; // Hash of field name.