kj::Arena for fast arena-style allocation.
diff --git a/c++/src/kj/arena-test.c++ b/c++/src/kj/arena-test.c++
new file mode 100644
index 0000000..583a1ca
--- /dev/null
+++ b/c++/src/kj/arena-test.c++
@@ -0,0 +1,287 @@
+// Copyright (c) 2013, Kenton Varda <[email protected]>
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "arena.h"
+#include "debug.h"
+#include <gtest/gtest.h>
+#include <stdint.h>
+
+namespace kj {
+namespace {
+
+struct TestObject {
+ TestObject() {
+ index = count;
+ KJ_ASSERT(index != throwAt);
+ ++count;
+ }
+ TestObject(const TestObject& other) {
+ KJ_ASSERT(other.index != throwAt);
+ index = -1;
+ copiedCount++;
+ }
+ ~TestObject() noexcept(false) {
+ if (index == -1) {
+ --copiedCount;
+ } else {
+ --count;
+ EXPECT_EQ(index, count);
+ KJ_ASSERT(count != throwAt);
+ }
+ }
+
+ int index;
+
+ static int count;
+ static int copiedCount;
+ static int throwAt;
+};
+
+int TestObject::count = 0;
+int TestObject::copiedCount = 0;
+int TestObject::throwAt = -1;
+
+TEST(Arena, Object) {
+ TestObject::count = 0;
+ TestObject::throwAt = -1;
+
+ {
+ Arena arena;
+
+ TestObject& obj1 = arena.allocate<TestObject>();
+ TestObject& obj2 = arena.allocate<TestObject>();
+
+ EXPECT_LT(&obj1, &obj2);
+
+ EXPECT_EQ(2, TestObject::count);
+ }
+
+ EXPECT_EQ(0, TestObject::count);
+}
+
+TEST(Arena, TrivialObject) {
+ Arena arena;
+
+ int& i1 = arena.allocate<int>();
+ int& i2 = arena.allocate<int>();
+
+ // Trivial objects should be tightly-packed.
+ EXPECT_EQ(&i1 + 1, &i2);
+}
+
+TEST(Arena, OwnObject) {
+ TestObject::count = 0;
+ TestObject::throwAt = -1;
+
+ Arena arena;
+
+ {
+ Own<TestObject> obj1 = arena.allocateOwn<TestObject>();
+ Own<TestObject> obj2 = arena.allocateOwn<TestObject>();
+ EXPECT_LT(obj1.get(), obj2.get());
+
+ EXPECT_EQ(2, TestObject::count);
+ }
+
+ EXPECT_EQ(0, TestObject::count);
+}
+
+TEST(Arena, Array) {
+ TestObject::count = 0;
+ TestObject::throwAt = -1;
+
+ {
+ Arena arena;
+ ArrayPtr<TestObject> arr1 = arena.allocateArray<TestObject>(4);
+ ArrayPtr<TestObject> arr2 = arena.allocateArray<TestObject>(2);
+ EXPECT_EQ(4, arr1.size());
+ EXPECT_EQ(2, arr2.size());
+ EXPECT_LE(arr1.end(), arr2.begin());
+ EXPECT_EQ(6, TestObject::count);
+ }
+
+ EXPECT_EQ(0, TestObject::count);
+}
+
+TEST(Arena, TrivialArray) {
+ Arena arena;
+ ArrayPtr<int> arr1 = arena.allocateArray<int>(16);
+ ArrayPtr<int> arr2 = arena.allocateArray<int>(8);
+
+ // Trivial arrays should be tightly-packed.
+ EXPECT_EQ(arr1.end(), arr2.begin());
+}
+
+TEST(Arena, OwnArray) {
+ TestObject::count = 0;
+ TestObject::throwAt = -1;
+
+ Arena arena;
+
+ {
+ Array<TestObject> arr1 = arena.allocateOwnArray<TestObject>(4);
+ Array<TestObject> arr2 = arena.allocateOwnArray<TestObject>(2);
+ EXPECT_EQ(4, arr1.size());
+ EXPECT_EQ(2, arr2.size());
+ EXPECT_LE(arr1.end(), arr2.begin());
+ EXPECT_EQ(6, TestObject::count);
+ }
+
+ EXPECT_EQ(0, TestObject::count);
+}
+
+#ifndef KJ_NO_EXCEPTIONS
+
+TEST(Arena, ObjectThrow) {
+ TestObject::count = 0;
+ TestObject::throwAt = 1;
+
+ {
+ Arena arena;
+
+ arena.allocate<TestObject>();
+ EXPECT_ANY_THROW(arena.allocate<TestObject>());
+ EXPECT_EQ(1, TestObject::count);
+ }
+
+ EXPECT_EQ(0, TestObject::count);
+}
+
+TEST(Arena, ArrayThrow) {
+ TestObject::count = 0;
+ TestObject::throwAt = 2;
+
+ {
+ Arena arena;
+ EXPECT_ANY_THROW(arena.allocateArray<TestObject>(4));
+ EXPECT_EQ(2, TestObject::count);
+ }
+
+ EXPECT_EQ(0, TestObject::count);
+}
+
+#endif
+
+TEST(Arena, Alignment) {
+ Arena arena;
+
+ char& c = arena.allocate<char>();
+ long& l = arena.allocate<long>();
+ char& c2 = arena.allocate<char>();
+ ArrayPtr<char> arr = arena.allocateArray<char>(8);
+
+ EXPECT_EQ(alignof(long) + sizeof(long), &c2 - &c);
+ EXPECT_EQ(alignof(long), reinterpret_cast<char*>(&l) - &c);
+ EXPECT_EQ(sizeof(char), arr.begin() - &c2);
+}
+
+TEST(Arena, EndOfChunk) {
+ union {
+ byte scratch[16];
+ uint64_t align;
+ };
+ Arena arena(arrayPtr(scratch, sizeof(scratch)));
+
+ uint64_t& i = arena.allocate<uint64_t>();
+ EXPECT_EQ(scratch, reinterpret_cast<byte*>(&i));
+
+ uint64_t& i2 = arena.allocate<uint64_t>();
+ EXPECT_EQ(scratch + 8, reinterpret_cast<byte*>(&i2));
+
+ uint64_t& i3 = arena.allocate<uint64_t>();
+ EXPECT_NE(scratch + 16, reinterpret_cast<byte*>(&i3));
+}
+
+TEST(Arena, EndOfChunkAlignment) {
+ union {
+ byte scratch[10];
+ uint64_t align;
+ };
+ Arena arena(arrayPtr(scratch, sizeof(scratch)));
+
+ uint16_t& i = arena.allocate<uint16_t>();
+ EXPECT_EQ(scratch, reinterpret_cast<byte*>(&i));
+
+ // Although there is technically enough space in the scratch array, it is not aligned correctly.
+ uint64_t& i2 = arena.allocate<uint64_t>();
+ EXPECT_TRUE(reinterpret_cast<byte*>(&i2) < scratch ||
+ reinterpret_cast<byte*>(&i2) > scratch + sizeof(scratch));
+}
+
+TEST(Arena, TooBig) {
+ Arena arena(1024);
+
+ byte& b1 = arena.allocate<byte>();
+
+ ArrayPtr<byte> arr = arena.allocateArray<byte>(1024);
+
+ byte& b2 = arena.allocate<byte>();
+
+ EXPECT_EQ(&b1 + 1, &b2);
+
+ // Write to the array to make sure it's valid.
+ memset(arr.begin(), 0xbe, arr.size());
+}
+
+TEST(Arena, MultiSegment) {
+ Arena arena(24);
+
+ uint64_t& i1 = arena.allocate<uint64_t>();
+ uint64_t& i2 = arena.allocate<uint64_t>();
+ uint64_t& i3 = arena.allocate<uint64_t>();
+
+ EXPECT_EQ(&i1 + 1, &i2);
+ EXPECT_NE(&i2 + 1, &i3);
+
+ i1 = 1234;
+ i2 = 5678;
+ i3 = 9012;
+}
+
+TEST(Arena, Constructor) {
+ Arena arena;
+
+ EXPECT_EQ(123, arena.allocate<uint64_t>(123));
+ EXPECT_EQ("foo", arena.allocate<StringPtr>("foo", 3));
+}
+
+TEST(Arena, Strings) {
+ Arena arena;
+
+ StringPtr foo = arena.copyString("foo");
+ StringPtr bar = arena.copyString("bar");
+ StringPtr quux = arena.copyString("quux");
+ StringPtr corge = arena.copyString("corge");
+
+ EXPECT_EQ("foo", foo);
+ EXPECT_EQ("bar", bar);
+ EXPECT_EQ("quux", quux);
+ EXPECT_EQ("corge", corge);
+
+ EXPECT_EQ(foo.end() + 1, bar.begin());
+ EXPECT_EQ(bar.end() + 1, quux.begin());
+ EXPECT_EQ(quux.end() + 1, corge.begin());
+}
+
+} // namespace
+} // namespace kj
diff --git a/c++/src/kj/arena.c++ b/c++/src/kj/arena.c++
new file mode 100644
index 0000000..c3ec3bc
--- /dev/null
+++ b/c++/src/kj/arena.c++
@@ -0,0 +1,149 @@
+// Copyright (c) 2013, Kenton Varda <[email protected]>
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "arena.h"
+#include "debug.h"
+#include <stdint.h>
+
+namespace kj {
+
+const Arena::ArrayDisposerImpl Arena::ArrayDisposerImpl::instance = Arena::ArrayDisposerImpl();
+
+Arena::Arena(size_t chunkSize): state(chunkSize) {}
+
+Arena::Arena(ArrayPtr<byte> scratch, size_t chunkSize): state(chunkSize) {
+ state.pos = scratch.begin();
+ state.chunkEnd = scratch.end();
+}
+
+Arena::~Arena() noexcept(false) {
+ // Run cleanup explicitly. It will be executed again implicitly when state's destructor is
+ // called. This ensures that if the first pass throws an exception, remaining objects are still
+ // destroyed. If the second pass throws, the program terminates, but any destructors that could
+ // throw should be using UnwindDetector to avoid this.
+ state.cleanup();
+}
+
+void Arena::State::cleanup() {
+ while (objectList != nullptr) {
+ void* ptr = objectList + 1;
+ auto destructor = objectList->destructor;
+ objectList = objectList->next;
+ destructor(ptr);
+ }
+
+ while (chunkList != nullptr) {
+ void* ptr = chunkList;
+ chunkList = chunkList->next;
+ operator delete(ptr);
+ }
+}
+
+namespace {
+
+constexpr bool isPowerOfTwo(size_t value) {
+ return (value & value - 1) == 0;
+}
+
+inline byte* alignTo(byte* p, uint alignment) {
+ // Round the pointer up to the next aligned value.
+
+ KJ_DASSERT(isPowerOfTwo(alignment), alignment);
+ uintptr_t mask = alignment - 1;
+ uintptr_t i = reinterpret_cast<uintptr_t>(p);
+ return reinterpret_cast<byte*>((i + mask) & ~mask);
+}
+
+} // namespace
+
+void* Arena::allocateBytes(size_t amount, uint alignment, bool hasDisposer) {
+ // Code below depends on power-of-two alignment and header sizes.
+ static_assert(isPowerOfTwo(sizeof(ChunkHeader)), "sizeof(ChunkHeader) is not a power of 2.");
+ static_assert(isPowerOfTwo(sizeof(ObjectHeader)), "sizeof(ObjectHeader) is not a power of 2.");
+ KJ_DASSERT(isPowerOfTwo(alignment), alignment);
+
+ // Offset we must apply if the allocated space is being prefixed with these headers.
+ uint chunkHeaderSize = kj::max(alignment, sizeof(ChunkHeader));
+ uint objectHeaderSize = kj::max(alignment, sizeof(ObjectHeader));
+
+ if (hasDisposer) {
+ amount += objectHeaderSize;
+ }
+
+ void* result;
+ byte* alignedPos = alignTo(state.pos, alignment);
+ byte* endPos = alignedPos + amount;
+ if (endPos <= state.chunkEnd) {
+ // There's enough space in the current chunk.
+ result = alignedPos;
+ state.pos = alignedPos + amount;
+ } else if (amount + chunkHeaderSize > state.chunkSize ||
+ state.chunkEnd - state.pos > state.chunkSize / 4) {
+ // This object is too big to fit in one chunk, or we'd waste more than a quarter of the chunk
+ // by starting a new one now. Instead, allocate the object in its own independent chunk.
+ ChunkHeader* newChunk = reinterpret_cast<ChunkHeader*>(operator new(chunkHeaderSize + amount));
+ result = reinterpret_cast<byte*>(newChunk) + chunkHeaderSize;
+ newChunk->next = state.chunkList;
+ state.chunkList = newChunk;
+ // We don't update state.pos and state.chunkEnd because the new chunk has no extra space but
+ // the old chunk might.
+ } else {
+ // Allocate a new chunk.
+ ChunkHeader* newChunk = reinterpret_cast<ChunkHeader*>(operator new(state.chunkSize));
+ result = reinterpret_cast<byte*>(newChunk) + chunkHeaderSize;
+ newChunk->next = state.chunkList;
+ state.chunkList = newChunk;
+ state.pos = reinterpret_cast<byte*>(result) + amount;
+ state.chunkEnd = reinterpret_cast<byte*>(newChunk) + state.chunkSize;
+ }
+
+ if (hasDisposer) {
+ // Reserve space for the ObjectHeader, but don't add it to the object list yet.
+ result = reinterpret_cast<byte*>(result) + objectHeaderSize;
+ }
+ return result;
+}
+
+StringPtr Arena::copyString(StringPtr content) {
+ char* data = reinterpret_cast<char*>(allocateBytes(content.size() + 1, 1, false));
+ memcpy(data, content.cStr(), content.size() + 1);
+ return StringPtr(data, content.size());
+}
+
+void Arena::setDestructor(void* ptr, void (*destructor)(void*)) {
+ ObjectHeader* header = reinterpret_cast<ObjectHeader*>(ptr) - 1;
+ header->destructor = destructor;
+ header->next = state.objectList;
+ state.objectList = header;
+}
+
+void Arena::ArrayDisposerImpl::disposeImpl(
+ void* firstElement, size_t elementSize, size_t elementCount,
+ size_t capacity, void (*destroyElement)(void*)) const {
+ if (destroyElement != nullptr) {
+ ExceptionSafeArrayUtil guard(firstElement, elementSize, elementCount, destroyElement);
+ guard.destroyAll();
+ }
+}
+
+} // namespace kj
diff --git a/c++/src/kj/arena.h b/c++/src/kj/arena.h
new file mode 100644
index 0000000..00bb469
--- /dev/null
+++ b/c++/src/kj/arena.h
@@ -0,0 +1,224 @@
+// Copyright (c) 2013, Kenton Varda <[email protected]>
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef KJ_ARENA_H_
+#define KJ_ARENA_H_
+
+#include "memory.h"
+#include "array.h"
+#include "string.h"
+
+namespace kj {
+
+class Arena {
+ // A class which allows several objects to be allocated in contiguous chunks of memory, then
+ // frees them all at once.
+
+public:
+ Arena(size_t chunkSize = 1024);
+ // Create an Arena that tries to allocate in chunks of the given size. It may deviate from the
+ // size if an object is too large to fit or to avoid excessive fragmentation. Each chunk has
+ // a one-word header which is included in the chunk size.
+
+ Arena(ArrayPtr<byte> scratch, size_t chunkSize = 1024);
+ // Allocates from the given scratch space first, only resorting to the heap when it runs out.
+
+ KJ_DISALLOW_COPY(Arena);
+ ~Arena() noexcept(false);
+
+ template <typename T, typename... Params>
+ T& allocate(Params&&... params);
+ template <typename T>
+ ArrayPtr<T> allocateArray(size_t size);
+ // Allocate an object or array of type T. If T has a non-trivial destructor, that destructor
+ // will be run during the Arena's destructor. Such destructors are run in opposite order of
+ // allocation. Note that these methods must maintain a list of destructors to call, which has
+ // overhead, but this overhead only applies if T has a non-trivial destructor.
+
+ template <typename T, typename... Params>
+ Own<T> allocateOwn(Params&&... params);
+ template <typename T>
+ Array<T> allocateOwnArray(size_t size);
+ template <typename T>
+ ArrayBuilder<T> allocateOwnArrayBuilder(size_t capacity);
+ // Allocate an object or array of type T. Destructors are executed when the returned Own<T>
+ // or Array<T> goes out-of-scope, which must happen before the Arena is destroyed. This variant
+ // is useful when you need to control when the destructor is called. This variant also avoids
+ // the need for the Arena itself to keep track of destructors to call later, which may make it
+ // slightly more efficient.
+
+ StringPtr copyString(StringPtr content);
+ // Make a copy of the given string inside the arena, and return a pointer to the copy.
+
+private:
+ struct ChunkHeader {
+ ChunkHeader* next;
+ };
+ struct ObjectHeader {
+ void (*destructor)(void*);
+ ObjectHeader* next;
+ };
+
+ struct State {
+ size_t chunkSize;
+ ChunkHeader* chunkList;
+ ObjectHeader* objectList;
+ byte* pos;
+ byte* chunkEnd;
+
+ inline State(size_t chunkSize)
+ : chunkSize(chunkSize), chunkList(nullptr), objectList(nullptr),
+ pos(nullptr), chunkEnd(nullptr) {}
+ inline ~State() noexcept(false) { cleanup(); }
+
+ void cleanup();
+ // Run all destructors, leaving the above pointers null. If a destructor throws, the State is
+ // left in a consistent state, such that if cleanup() is called again, it will pick up where
+ // it left off.
+ };
+ State state;
+
+ void* allocateBytes(size_t amount, uint alignment, bool hasDisposer);
+ // Allocate the given number of bytes. `hasDisposer` must be true if `setDisposer()` may be
+ // called on this pointer later.
+
+ void setDestructor(void* ptr, void (*destructor)(void*));
+ // Schedule the given destructor to be executed when the Arena is destroyed. `ptr` must be a
+ // pointer previously returned by an `allocateBytes()` call for which `hasDisposer` was true.
+
+ template <typename T>
+ class DisposerImpl: public Disposer {
+ public:
+ static const DisposerImpl instance;
+
+ void disposeImpl(void* pointer) const override {
+ reinterpret_cast<T*>(pointer)->~T();
+ }
+ };
+
+ class ArrayDisposerImpl: public ArrayDisposer {
+ public:
+ static const ArrayDisposerImpl instance;
+
+ void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount,
+ size_t capacity, void (*destroyElement)(void*)) const override;
+ };
+
+ template <typename T>
+ static void destroyArray(void* pointer) {
+ size_t elementCount = *reinterpret_cast<size_t*>(pointer);
+ constexpr size_t prefixSize = kj::max(alignof(T), sizeof(size_t));
+ ArrayDisposerImpl::instance.disposeImpl(
+ reinterpret_cast<byte*>(pointer) + prefixSize,
+ sizeof(T), elementCount, elementCount, &destroyObject<T>);
+ }
+
+ template <typename T>
+ static void destroyObject(void* pointer) {
+ dtor(*reinterpret_cast<T*>(pointer));
+ }
+};
+
+// =======================================================================================
+// Inline implementation details
+
+template <typename T, typename... Params>
+T& Arena::allocate(Params&&... params) {
+ T& result = *reinterpret_cast<T*>(allocateBytes(
+ sizeof(T), alignof(T), !__has_trivial_destructor(T)));
+ if (!__has_trivial_constructor(T) || sizeof...(Params) > 0) {
+ ctor(result, kj::fwd<Params>(params)...);
+ }
+ if (!__has_trivial_destructor(T)) {
+ setDestructor(&result, &destroyObject<T>);
+ }
+ return result;
+}
+
+template <typename T>
+ArrayPtr<T> Arena::allocateArray(size_t size) {
+ if (__has_trivial_destructor(T)) {
+ ArrayPtr<T> result =
+ arrayPtr(reinterpret_cast<T*>(allocateBytes(
+ sizeof(T) * size, alignof(T), false)), size);
+ if (!__has_trivial_constructor(T)) {
+ for (size_t i = 0; i < size; i++) {
+ ctor(result[i]);
+ }
+ }
+ return result;
+ } else {
+ // Allocate with a 64-bit prefix in which we store the array size.
+ constexpr size_t prefixSize = kj::max(alignof(T), sizeof(size_t));
+ void* base = allocateBytes(sizeof(T) * size + prefixSize, alignof(T), true);
+ size_t& tag = *reinterpret_cast<size_t*>(base);
+ ArrayPtr<T> result =
+ arrayPtr(reinterpret_cast<T*>(reinterpret_cast<byte*>(base) + prefixSize), size);
+ setDestructor(base, &destroyArray<T>);
+
+ if (__has_trivial_constructor(T)) {
+ tag = size;
+ } else {
+ // In case of constructor exceptions, we need the tag to end up storing the number of objects
+ // that were successfully constructed, so that they'll be properly destroyed.
+ tag = 0;
+ for (size_t i = 0; i < size; i++) {
+ ctor(result[i]);
+ tag = i + 1;
+ }
+ }
+ return result;
+ }
+}
+
+template <typename T, typename... Params>
+Own<T> Arena::allocateOwn(Params&&... params) {
+ T& result = *reinterpret_cast<T*>(allocateBytes(sizeof(T), alignof(T), false));
+ if (!__has_trivial_constructor(T) || sizeof...(Params) > 0) {
+ ctor(result, kj::fwd<Params>(params)...);
+ }
+ return Own<T>(&result, DisposerImpl<T>::instance);
+}
+
+template <typename T>
+Array<T> Arena::allocateOwnArray(size_t size) {
+ ArrayBuilder<T> result = allocateOwnArrayBuilder<T>(size);
+ for (size_t i = 0; i < size; i++) {
+ result.add();
+ }
+ return result.finish();
+}
+
+template <typename T>
+ArrayBuilder<T> Arena::allocateOwnArrayBuilder(size_t capacity) {
+ return ArrayBuilder<T>(
+ reinterpret_cast<T*>(allocateBytes(sizeof(T) * capacity, alignof(T), false)),
+ capacity, ArrayDisposerImpl::instance);
+}
+
+template <typename T>
+const Arena::DisposerImpl<T> Arena::DisposerImpl<T>::instance = Arena::DisposerImpl<T>();
+
+} // namespace kj
+
+#endif // KJ_ARENA_H_
diff --git a/c++/src/kj/array.c++ b/c++/src/kj/array.c++
index 5ade889..5f5a11c 100644
--- a/c++/src/kj/array.c++
+++ b/c++/src/kj/array.c++
@@ -24,37 +24,26 @@
#include "array.h"
namespace kj {
+
+void ExceptionSafeArrayUtil::construct(size_t count, void (*constructElement)(void*)) {
+ while (count > 0) {
+ constructElement(pos);
+ pos += elementSize;
+ ++constructedElementCount;
+ --count;
+ }
+}
+
+void ExceptionSafeArrayUtil::destroyAll() {
+ while (constructedElementCount > 0) {
+ pos -= elementSize;
+ --constructedElementCount;
+ destroyElement(pos);
+ }
+}
+
namespace _ { // private
-struct HeapArrayDisposer::ExceptionGuard {
- byte* pos;
- size_t elementSize;
- size_t elementCount;
- size_t constructedCount;
- void (*destroyElement)(void*);
-
- ExceptionGuard(void* ptr, size_t elementSize, size_t elementCount,
- void (*destroyElement)(void*))
- : pos(reinterpret_cast<byte*>(ptr) + elementSize * elementCount),
- elementSize(elementSize), elementCount(elementCount),
- destroyElement(destroyElement) {}
-
- ~ExceptionGuard() noexcept(false) {
- if (pos != nullptr) {
- destroyAll();
- operator delete(pos);
- }
- }
-
- void destroyAll() {
- while (elementCount > 0) {
- pos -= elementSize;
- --elementCount;
- destroyElement(pos);
- }
- }
-};
-
void* HeapArrayDisposer::allocateImpl(size_t elementSize, size_t elementCount, size_t capacity,
void (*constructElement)(void*),
void (*destroyElement)(void*)) {
@@ -70,13 +59,9 @@
--elementCount;
}
} else {
- ExceptionGuard guard(result, elementSize, 0, destroyElement);
- while (guard.elementCount < elementCount) {
- constructElement(guard.pos);
- guard.pos += elementSize;
- ++guard.elementCount;
- }
- guard.pos = nullptr;
+ ExceptionSafeArrayUtil guard(result, elementSize, 0, destroyElement);
+ guard.construct(elementCount, constructElement);
+ guard.release();
}
return result;
@@ -90,11 +75,9 @@
if (destroyElement == nullptr) {
operator delete(firstElement);
} else {
- ExceptionGuard guard(firstElement, elementSize, elementCount, destroyElement);
+ KJ_DEFER(operator delete(firstElement));
+ ExceptionSafeArrayUtil guard(firstElement, elementSize, elementCount, destroyElement);
guard.destroyAll();
-
- // If an exception is thrown, we'll continue the destruction process in ExceptionGuard's
- // destructor. If _that_ throws an exception, the program terminates according to C++ rules.
}
}
diff --git a/c++/src/kj/array.h b/c++/src/kj/array.h
index 488a5d9..17a422a 100644
--- a/c++/src/kj/array.h
+++ b/c++/src/kj/array.h
@@ -61,6 +61,45 @@
struct Dispose_;
};
+class ExceptionSafeArrayUtil {
+ // Utility class that assists in constructing or destroying elements of an array, where the
+ // constructor or destructor could throw exceptions. In case of an exception,
+ // ExceptionSafeArrayUtil's destructor will call destructors on all elements that have been
+ // constructed but not destroyed. Remember that destructors that throw exceptions are required
+ // to use UnwindDetector to detect unwind and avoid exceptions in this case. Therefore, no more
+ // than one exception will be thrown (and the program will not terminate).
+
+public:
+ inline ExceptionSafeArrayUtil(void* ptr, size_t elementSize, size_t constructedElementCount,
+ void (*destroyElement)(void*))
+ : pos(reinterpret_cast<byte*>(ptr) + elementSize * constructedElementCount),
+ elementSize(elementSize), constructedElementCount(constructedElementCount),
+ destroyElement(destroyElement) {}
+ KJ_DISALLOW_COPY(ExceptionSafeArrayUtil);
+
+ inline ~ExceptionSafeArrayUtil() noexcept(false) {
+ if (constructedElementCount > 0) destroyAll();
+ }
+
+ void construct(size_t count, void (*constructElement)(void*));
+ // Construct the given number of elements.
+
+ void destroyAll();
+ // Destroy all elements. Call this immediately before ExceptionSafeArrayUtil goes out-of-scope
+ // to ensure that one element throwing an exception does not prevent the others from being
+ // destroyed.
+
+ void release() { constructedElementCount = 0; }
+ // Prevent ExceptionSafeArrayUtil's destructor from destroying the constructed elements.
+ // Call this after you've successfully finished constructing.
+
+private:
+ byte* pos;
+ size_t elementSize;
+ size_t constructedElementCount;
+ void (*destroyElement)(void*);
+};
+
// =======================================================================================
// Array
@@ -185,8 +224,6 @@
template <typename T, bool hasTrivialConstructor = __has_trivial_constructor(T),
bool hasNothrowConstructor = __has_nothrow_constructor(T)>
struct Allocate_;
-
- struct ExceptionGuard;
};
} // namespace _ (private)
@@ -295,14 +332,14 @@
void addAll(Iterator start, Iterator end);
Array<T> finish() {
- // We could safely remove this check as long as HeapArrayDisposer relies on operator delete
- // (which doesn't need to know the original capacity) or if we created a custom disposer for
- // ArrayBuilder which stores the capacity in a prefix. But that would mean we can't allow
- // arbitrary disposers with ArrayBuilder in the future, and anyway this check might catch bugs.
- // Probably we should just create a new Vector-like data structure if we want to allow building
- // of arrays without knowing the final size in advance.
+ // We could safely remove this check if we assume that the disposer implementation doesn't
+ // need to know the original capacity, as is thes case with HeapArrayDisposer since it uses
+ // operator new() or if we created a custom disposer for ArrayBuilder which stores the capacity
+ // in a prefix. But that would make it hard to write cleverer heap allocators, and anyway this
+ // check might catch bugs. Probably people should use Vector if they want to build arrays
+ // without knowing the final size in advance.
KJ_IREQUIRE(pos == endPtr, "ArrayBuilder::finish() called prematurely.");
- Array<T> result(reinterpret_cast<T*>(ptr), pos - ptr, _::HeapArrayDisposer::instance);
+ Array<T> result(reinterpret_cast<T*>(ptr), pos - ptr, *disposer);
ptr = nullptr;
pos = nullptr;
endPtr = nullptr;
diff --git a/c++/src/kj/common-test.c++ b/c++/src/kj/common-test.c++
index dea24df..7a0f2e8 100644
--- a/c++/src/kj/common-test.c++
+++ b/c++/src/kj/common-test.c++
@@ -246,5 +246,23 @@
EXPECT_EQ(1234567890123456789ll, kj::max('a', 1234567890123456789ll));
}
+TEST(Common, Defer) {
+ uint i = 0;
+ uint j = 1;
+ bool k = false;
+
+ {
+ KJ_DEFER(++i);
+ KJ_DEFER(j += 3; k = true);
+ EXPECT_EQ(0, i);
+ EXPECT_EQ(1, j);
+ EXPECT_FALSE(k);
+ }
+
+ EXPECT_EQ(1, i);
+ EXPECT_EQ(4, j);
+ EXPECT_TRUE(k);
+}
+
} // namespace
} // namespace kj
diff --git a/c++/src/kj/common.h b/c++/src/kj/common.h
index 62aa1b3..87312c3 100644
--- a/c++/src/kj/common.h
+++ b/c++/src/kj/common.h
@@ -176,6 +176,12 @@
#define KJ_ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
+#define KJ_CONCAT_(x, y) x##y
+#define KJ_CONCAT(x, y) KJ_CONCAT_(x, y)
+#define KJ_UNIQUE_NAME(prefix) KJ_CONCAT(prefix, __LINE__)
+// Create a unique identifier name. We use concatenate __LINE__ rather than __COUNTER__ so that
+// the name can be used multiple times in the same macro.
+
// =======================================================================================
// Template metaprogramming helpers.
@@ -283,9 +289,9 @@
template<typename T> constexpr T&& fwd(NoInfer<T>& t) noexcept { return static_cast<T&&>(t); }
template <typename T, typename U>
-auto min(T&& a, U&& b) -> decltype(a < b ? a : b) { return a < b ? a : b; }
+inline constexpr auto min(T&& a, U&& b) -> decltype(a < b ? a : b) { return a < b ? a : b; }
template <typename T, typename U>
-auto max(T&& a, U&& b) -> decltype(a > b ? a : b) { return a > b ? a : b; }
+inline constexpr auto max(T&& a, U&& b) -> decltype(a > b ? a : b) { return a > b ? a : b; }
// =======================================================================================
// Manually invoking constructors and destructors
@@ -710,6 +716,29 @@
return static_cast<To&>(from);
}
+// =======================================================================================
+// Defer
+
+namespace _ { // private
+
+template <typename Func>
+class Deferred {
+public:
+ inline Deferred(Func func): func(func) {}
+ inline ~Deferred() { func(); }
+private:
+ Func func;
+};
+
+template <typename Func>
+Deferred<Decay<Func>> defer(Func&& func) {
+ return Deferred<Decay<Func>>(kj::fwd<Func>(func));
+}
+
+} // namespace _ (private)
+
+#define KJ_DEFER(code) auto KJ_UNIQUE_NAME(_kjDefer) = ::kj::_::defer([&](){code;})
+
} // namespace kj
#endif // KJ_COMMON_H_
diff --git a/c++/src/kj/debug.h b/c++/src/kj/debug.h
index 73e3b3b..ca7b841 100644
--- a/c++/src/kj/debug.h
+++ b/c++/src/kj/debug.h
@@ -140,11 +140,12 @@
errorNumber, code, #__VA_ARGS__, ##__VA_ARGS__);; f.fatal())
#define KJ_CONTEXT(...) \
- auto _kjContextFunc = [&]() -> ::kj::_::Debug::Context::Value { \
+ auto KJ_UNIQUE_NAME(_kjContextFunc) = [&]() -> ::kj::_::Debug::Context::Value { \
return ::kj::_::Debug::Context::Value(__FILE__, __LINE__, \
::kj::_::Debug::makeContextDescription(#__VA_ARGS__, ##__VA_ARGS__)); \
}; \
- ::kj::_::Debug::ContextImpl<decltype(_kjContextFunc)> _kjContext(_kjContextFunc)
+ ::kj::_::Debug::ContextImpl<decltype(KJ_UNIQUE_NAME(_kjContextFunc))> \
+ KJ_UNIQUE_NAME(_kjContext)(KJ_UNIQUE_NAME(_kjContextFunc))
#ifdef NDEBUG
#define KJ_DLOG(...) do {} while (false)