Skip to content

Commit c9b7e86

Browse files
committed
feat: add arena allocator with block chaining and max size
1 parent e5c18f8 commit c9b7e86

3 files changed

Lines changed: 222 additions & 0 deletions

File tree

include/sql_parser/arena.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#ifndef SQL_PARSER_ARENA_H
2+
#define SQL_PARSER_ARENA_H
3+
4+
#include "sql_parser/common.h"
5+
#include <cstddef>
6+
#include <cstdint>
7+
#include <cstring>
8+
#include <new>
9+
10+
namespace sql_parser {
11+
12+
class Arena {
13+
public:
14+
explicit Arena(size_t block_size = 65536, size_t max_size = 1048576);
15+
~Arena();
16+
17+
Arena(const Arena&) = delete;
18+
Arena& operator=(const Arena&) = delete;
19+
Arena(Arena&&) = delete;
20+
Arena& operator=(Arena&&) = delete;
21+
22+
void* allocate(size_t bytes);
23+
24+
template <typename T>
25+
T* allocate_typed() {
26+
void* mem = allocate(sizeof(T));
27+
if (!mem) return nullptr;
28+
return new (mem) T{};
29+
}
30+
31+
StringRef allocate_string(const char* src, uint32_t len);
32+
33+
void reset();
34+
35+
size_t bytes_used() const;
36+
37+
private:
38+
struct Block {
39+
Block* next;
40+
size_t capacity;
41+
size_t used;
42+
char* data() { return reinterpret_cast<char*>(this) + sizeof(Block); }
43+
};
44+
45+
Block* allocate_block(size_t capacity);
46+
47+
Block* primary_;
48+
Block* current_;
49+
size_t block_size_;
50+
size_t max_size_;
51+
size_t total_allocated_;
52+
};
53+
54+
} // namespace sql_parser
55+
56+
#endif // SQL_PARSER_ARENA_H

src/sql_parser/arena.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#include "sql_parser/arena.h"
2+
#include <cstdlib>
3+
4+
namespace sql_parser {
5+
6+
Arena::Block* Arena::allocate_block(size_t capacity) {
7+
void* mem = std::malloc(sizeof(Block) + capacity);
8+
if (!mem) return nullptr;
9+
Block* block = static_cast<Block*>(mem);
10+
block->next = nullptr;
11+
block->capacity = capacity;
12+
block->used = 0;
13+
return block;
14+
}
15+
16+
Arena::Arena(size_t block_size, size_t max_size)
17+
: block_size_(block_size), max_size_(max_size), total_allocated_(0) {
18+
primary_ = allocate_block(block_size_);
19+
current_ = primary_;
20+
total_allocated_ = block_size_;
21+
}
22+
23+
Arena::~Arena() {
24+
Block* b = primary_;
25+
while (b) {
26+
Block* next = b->next;
27+
std::free(b);
28+
b = next;
29+
}
30+
}
31+
32+
void* Arena::allocate(size_t bytes) {
33+
bytes = (bytes + 7) & ~size_t(7);
34+
35+
if (current_->used + bytes <= current_->capacity) {
36+
void* ptr = current_->data() + current_->used;
37+
current_->used += bytes;
38+
return ptr;
39+
}
40+
41+
size_t new_cap = (bytes > block_size_) ? bytes : block_size_;
42+
if (total_allocated_ + new_cap > max_size_) {
43+
return nullptr;
44+
}
45+
46+
Block* new_block = allocate_block(new_cap);
47+
if (!new_block) return nullptr;
48+
49+
current_->next = new_block;
50+
current_ = new_block;
51+
total_allocated_ += new_cap;
52+
53+
void* ptr = current_->data() + current_->used;
54+
current_->used += bytes;
55+
return ptr;
56+
}
57+
58+
StringRef Arena::allocate_string(const char* src, uint32_t len) {
59+
void* mem = allocate(len);
60+
if (!mem) return StringRef{nullptr, 0};
61+
std::memcpy(mem, src, len);
62+
return StringRef{static_cast<const char*>(mem), len};
63+
}
64+
65+
void Arena::reset() {
66+
Block* b = primary_->next;
67+
while (b) {
68+
Block* next = b->next;
69+
std::free(b);
70+
b = next;
71+
}
72+
primary_->next = nullptr;
73+
primary_->used = 0;
74+
current_ = primary_;
75+
total_allocated_ = block_size_;
76+
}
77+
78+
size_t Arena::bytes_used() const {
79+
size_t used = 0;
80+
const Block* b = primary_;
81+
while (b) {
82+
used += b->used;
83+
b = b->next;
84+
}
85+
return used;
86+
}
87+
88+
} // namespace sql_parser

tests/test_arena.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include <gtest/gtest.h>
2+
#include "sql_parser/arena.h"
3+
4+
using namespace sql_parser;
5+
6+
TEST(ArenaTest, AllocateAndReset) {
7+
Arena arena(4096);
8+
void* p1 = arena.allocate(64);
9+
ASSERT_NE(p1, nullptr);
10+
void* p2 = arena.allocate(64);
11+
ASSERT_NE(p2, nullptr);
12+
EXPECT_NE(p1, p2);
13+
14+
arena.reset();
15+
void* p3 = arena.allocate(64);
16+
ASSERT_NE(p3, nullptr);
17+
EXPECT_EQ(p1, p3);
18+
}
19+
20+
TEST(ArenaTest, AllocateAligned) {
21+
Arena arena(4096);
22+
void* p1 = arena.allocate(1);
23+
void* p2 = arena.allocate(8);
24+
EXPECT_EQ(reinterpret_cast<uintptr_t>(p2) % 8, 0u);
25+
}
26+
27+
TEST(ArenaTest, OverflowToNewBlock) {
28+
Arena arena(128);
29+
void* p1 = arena.allocate(100);
30+
ASSERT_NE(p1, nullptr);
31+
void* p2 = arena.allocate(100);
32+
ASSERT_NE(p2, nullptr);
33+
EXPECT_NE(p1, p2);
34+
}
35+
36+
TEST(ArenaTest, ResetFreesOverflowBlocks) {
37+
Arena arena(128);
38+
arena.allocate(100);
39+
arena.allocate(100);
40+
arena.reset();
41+
void* p = arena.allocate(64);
42+
ASSERT_NE(p, nullptr);
43+
}
44+
45+
TEST(ArenaTest, MaxSizeEnforced) {
46+
Arena arena(128, 256);
47+
void* p1 = arena.allocate(100);
48+
ASSERT_NE(p1, nullptr);
49+
void* p2 = arena.allocate(100);
50+
ASSERT_NE(p2, nullptr);
51+
void* p3 = arena.allocate(100);
52+
EXPECT_EQ(p3, nullptr);
53+
}
54+
55+
TEST(ArenaTest, AllocateTyped) {
56+
Arena arena(4096);
57+
58+
struct TestStruct {
59+
int a;
60+
double b;
61+
};
62+
63+
TestStruct* ts = arena.allocate_typed<TestStruct>();
64+
ASSERT_NE(ts, nullptr);
65+
ts->a = 42;
66+
ts->b = 3.14;
67+
EXPECT_EQ(ts->a, 42);
68+
EXPECT_DOUBLE_EQ(ts->b, 3.14);
69+
}
70+
71+
TEST(ArenaTest, AllocateString) {
72+
Arena arena(4096);
73+
const char* src = "hello world";
74+
StringRef ref = arena.allocate_string(src, 11);
75+
EXPECT_EQ(ref.len, 11u);
76+
EXPECT_EQ(std::memcmp(ref.ptr, "hello world", 11), 0);
77+
EXPECT_NE(ref.ptr, src);
78+
}

0 commit comments

Comments
 (0)