diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 2ff5ffd..788a98c 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -13,7 +13,11 @@ jobs: - uses: actions/checkout@main - name: c++ --version run: c++ --version - - name: make all -j 8 - run: make all -j 8 + - name: make exprtk_test + run: make exprtk_test - name: exprtk_test run: ./bin/exprtk_test + - name: make example + run: make example + - name: make build + run: make build diff --git a/Makefile b/Makefile index 2981ed1..774db91 100644 --- a/Makefile +++ b/Makefile @@ -1,32 +1,43 @@ COMPILER := -c++ #COMPILER := -clang++ -OPTIMIZATION_OPT := -O2 -DNDEBUG -Dexprtk_disable_caseinsensitivity +OPTIMIZATION_OPT := -O2 -DNDEBUG BASE_OPTIONS := -pedantic-errors -Wall -Wextra -Werror -Wno-long-long OPTIONS := $(BASE_OPTIONS) $(OPTIMIZATION_OPT) LINKER_OPT := -L/usr/lib -lstdc++ -lm -ASAN_OPT := -g -fsanitize=address -fno-omit-frame-pointer +# Suppress GCC var-tracking notes on very large template functions (e.g. run_test02). +ASAN_OPT := -g -fsanitize=address -fno-omit-frame-pointer -fno-var-tracking-assignments +ASAN_BUILD_OPTIONS := $(BASE_OPTIONS) $(ASAN_OPT) -O1 MSAN_OPT := -g -fsanitize=memory -fno-omit-frame-pointer LSAN_OPT := -g -fsanitize=leak -fno-omit-frame-pointer USAN_OPT := -g -fsanitize=undefined -fno-omit-frame-pointer BIN_DIR := bin vpath %.cpp example test -BUILD_SRC := $(sort $(wildcard example/exprtk_*.cpp) $(wildcard test/exprtk_*.cpp)) -BUILD_LIST := $(sort $(foreach f,$(BUILD_SRC),$(notdir $(f:.cpp=)))) -BIN_TARGETS := $(addprefix $(BIN_DIR)/,$(BUILD_LIST)) - - -all: $(BIN_TARGETS) +TEST_SRC := $(sort $(wildcard test/exprtk_*.cpp)) +EXAMPLE_SRC := $(sort $(wildcard example/exprtk_*.cpp)) +BUILD_LIST := $(sort $(foreach f,$(TEST_SRC) $(EXAMPLE_SRC),$(notdir $(f:.cpp=)))) +BIN_TARGETS := $(addprefix $(BIN_DIR)/,$(BUILD_LIST)) # Regenerate single-header exprtk_single.hpp from src/*.hpp (run after src changes) build: @python3 scripts/merge_hpp.py +all: $(BUILD_LIST) + +test: $(TEST_SRC) + +example: $(EXAMPLE_SRC) + exprtk_%: exprtk_%.cpp src/exprtk.hpp @mkdir -p $(BIN_DIR) @echo "Building $(BIN_DIR)/exprtk_$* from $<" $(COMPILER) $(OPTIONS) -Isrc -o $(BIN_DIR)/exprtk_$* $< $(LINKER_OPT) +exprtk_%_asan: exprtk_%.cpp src/exprtk.hpp + @mkdir -p $(BIN_DIR) + @echo "Building $(BIN_DIR)/exprtk_$*_asan from $<" + $(COMPILER) $(ASAN_BUILD_OPTIONS) -Isrc -o $(BIN_DIR)/exprtk_$*_asan $< $(LINKER_OPT) + strip_bin : @for f in $(BIN_TARGETS); do if [ -f $$f ]; then strip -s $$f; echo $$f; fi done; diff --git a/exprtk.hpp b/exprtk.hpp index e4d85db..2669a21 100644 --- a/exprtk.hpp +++ b/exprtk.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -5110,6 +5111,144 @@ namespace exprtk } // namespace exprtk +// === arena_allocator.hpp === +namespace exprtk +{ + namespace details + { + + // Arena allocator for AST nodes. + // + // Allocates nodes in contiguous 64KB pages using placement-new, keeping + // nodes from the same expression close together in memory and dramatically + // improving L1/L2 cache hit rates during value() tree-walk evaluation. + // + // Nodes larger than the page size fall back to heap allocation (tracked + // separately and freed individually). + // + // Lifetime: owned by expression::control_block; destroyed after all node + // destructors have been called by node_collection_destructor::delete_nodes. + class arena_allocator + { + public: + + static const std::size_t page_size = 65536; // 64 KB pages + + arena_allocator() + : current_page_(0) + , current_offset_(0) + {} + + ~arena_allocator() + { + for (std::size_t i = 0; i < pages_.size(); ++i) + { + ::free(pages_[i]); + } + + for (std::size_t i = 0; i < oversized_.size(); ++i) + { + ::free(oversized_[i]); + } + } + + // Allocate size bytes aligned to align bytes. + // Returns a pointer to uninitialized memory, or NULL on failure. + void* allocate(const std::size_t size, const std::size_t align = sizeof(void*)) + { + if (size > page_size) + { + // Oversized allocation: fall back to malloc, track separately. + void* ptr = ::malloc(size); + if (ptr) + { + oversized_.push_back(ptr); + } + return ptr; + } + + if (!try_allocate_from_current(size, align)) + { + if (!add_page()) + { + return 0; + } + } + + return allocate_from_current(size, align); + } + + // Check whether a pointer falls within any arena-managed page. + // Used to determine if an expression_node's memory is arena-owned. + bool is_arena_ptr(const void* ptr) const + { + const char* p = static_cast(ptr); + + for (std::size_t i = 0; i < pages_.size(); ++i) + { + const char* page_start = static_cast(pages_[i]); + if (p >= page_start && p < page_start + page_size) + { + return true; + } + } + + for (std::size_t i = 0; i < oversized_.size(); ++i) + { + if (ptr == oversized_[i]) + { + return true; + } + } + + return false; + } + + private: + + arena_allocator(const arena_allocator&); + arena_allocator& operator=(const arena_allocator&); + + bool add_page() + { + void* page = ::malloc(page_size); + if (!page) return false; + pages_.push_back(page); + current_page_ = page; + current_offset_ = 0; + return true; + } + + bool try_allocate_from_current(const std::size_t size, const std::size_t align) const + { + if (!current_page_) return false; + + const std::size_t aligned_offset = align_offset(current_offset_, align); + return (aligned_offset + size) <= page_size; + } + + void* allocate_from_current(const std::size_t size, const std::size_t align) + { + const std::size_t aligned_offset = align_offset(current_offset_, align); + void* ptr = static_cast(current_page_) + aligned_offset; + current_offset_ = aligned_offset + size; + return ptr; + } + + static std::size_t align_offset(const std::size_t offset, const std::size_t align) + { + return (offset + align - 1) & ~(align - 1); + } + + std::vector pages_; + std::vector oversized_; + void* current_page_; + std::size_t current_offset_; + }; + + } // namespace details +} // namespace exprtk + // === nodes_base.hpp === namespace exprtk { @@ -5708,9 +5847,15 @@ namespace exprtk typedef typename nci_t::noderef_list_t noderef_list_t; typedef node_depth_base > ndb_t; + expression_node() + : arena_managed_(false) + {} + virtual ~expression_node() {} + bool arena_managed_; + inline virtual T value() const { return std::numeric_limits::quiet_NaN(); @@ -6073,7 +6218,16 @@ namespace exprtk { node_ptr_t& node = *node_delete_list[i]; exprtk_debug(("ncd::delete_nodes() - deleting: %p\n", reinterpret_cast(node))); - delete node; + if (node->arena_managed_) + { + // Arena-managed node: call destructor only. + // The arena itself frees the underlying memory in bulk. + node->~Node(); + } + else + { + delete node; + } node = reinterpret_cast(0); } } @@ -19315,6 +19469,383 @@ namespace exprtk class node_allocator { + public: + + node_allocator() + : arena_(0) + {} + + void set_arena(arena_allocator* arena) + { + arena_ = arena; + } + + arena_allocator* get_arena() const + { + return arena_; + } + + private: + + template + inline node_type* arena_new() const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(); + node->arena_managed_ = true; + return node; + } + } + return new node_type(); + } + + template + inline node_type* arena_new(T1& t1) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1); + } + + template + inline node_type* arena_new_c(const T1& t1) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1); + } + + template + inline node_type* arena_new(const T1& t1, const T2& t2) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2); + } + + template + inline node_type* arena_new_cr(const T1& t1, T2& t2) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2); + } + + template + inline node_type* arena_new_rc(T1& t1, const T2& t2) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2); + } + + template + inline node_type* arena_new_rr(T1& t1, T2& t2) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2); + } + + template + inline node_type* arena_new_tt(T1 t1, T2 t2) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2); + } + + template + inline node_type* arena_new_ttt(T1 t1, T2 t2, T3 t3) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3); + } + + template + inline node_type* arena_new_tttt(T1 t1, T2 t2, T3 t3, T4 t4) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3, t4); + } + + template + inline node_type* arena_new_rrr(T1& t1, T2& t2, T3& t3) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3); + } + + template + inline node_type* arena_new_rrrr(T1& t1, T2& t2, T3& t3, T4& t4) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3, t4); + } + + template + inline node_type* arena_new_rrrrr(T1& t1, T2& t2, T3& t3, T4& t4, T5& t5) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3, t4, t5); + } + + template + inline node_type* arena_new_ccc(const T1& t1, const T2& t2, const T3& t3) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3); + } + + template + inline node_type* arena_new_cccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3, t4); + } + + template + inline node_type* arena_new_ccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3, t4, t5); + } + + template + inline node_type* arena_new_cccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5, t6); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3, t4, t5, t6); + } + + template + inline node_type* arena_new_ccccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5, t6, t7); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3, t4, t5, t6, t7); + } + + template + inline node_type* arena_new_cccccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7, const T8& t8) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5, t6, t7, t8); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3, t4, t5, t6, t7, t8); + } + + template + inline node_type* arena_new_ccccccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7, const T8& t8, const T9& t9) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9); + } + + template + inline node_type* arena_new_cccccccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7, const T8& t8, const T9& t9, const T10& t10) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); + node->arena_managed_ = true; + return node; + } + } + return new node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); + } + + arena_allocator* arena_; + public: template @@ -19374,7 +19905,7 @@ namespace exprtk template inline expression_node* allocate() const { - return (new node_type()); + return arena_new(); } template * allocate(T1& t1) const { expression_node* - result = (new node_type(t1)); + result = arena_new(t1); result->node_depth(); return result; } @@ -19402,7 +19933,7 @@ namespace exprtk inline expression_node* allocate_c(const T1& t1) const { expression_node* - result = (new node_type(t1)); + result = arena_new_c(t1); result->node_depth(); return result; } @@ -19412,7 +19943,7 @@ namespace exprtk inline expression_node* allocate(const T1& t1, const T2& t2) const { expression_node* - result = (new node_type(t1, t2)); + result = arena_new(t1, t2); result->node_depth(); return result; } @@ -19422,7 +19953,7 @@ namespace exprtk inline expression_node* allocate_cr(const T1& t1, T2& t2) const { expression_node* - result = (new node_type(t1, t2)); + result = arena_new_cr(t1, t2); result->node_depth(); return result; } @@ -19432,7 +19963,7 @@ namespace exprtk inline expression_node* allocate_rc(T1& t1, const T2& t2) const { expression_node* - result = (new node_type(t1, t2)); + result = arena_new_rc(t1, t2); result->node_depth(); return result; } @@ -19442,7 +19973,7 @@ namespace exprtk inline expression_node* allocate_rr(T1& t1, T2& t2) const { expression_node* - result = (new node_type(t1, t2)); + result = arena_new_rr(t1, t2); result->node_depth(); return result; } @@ -19452,7 +19983,7 @@ namespace exprtk inline expression_node* allocate_tt(T1 t1, T2 t2) const { expression_node* - result = (new node_type(t1, t2)); + result = arena_new_tt(t1, t2); result->node_depth(); return result; } @@ -19462,7 +19993,7 @@ namespace exprtk inline expression_node* allocate_ttt(T1 t1, T2 t2, T3 t3) const { expression_node* - result = (new node_type(t1, t2, t3)); + result = arena_new_ttt(t1, t2, t3); result->node_depth(); return result; } @@ -19472,7 +20003,7 @@ namespace exprtk inline expression_node* allocate_tttt(T1 t1, T2 t2, T3 t3, T4 t4) const { expression_node* - result = (new node_type(t1, t2, t3, t4)); + result = arena_new_tttt(t1, t2, t3, t4); result->node_depth(); return result; } @@ -19482,7 +20013,7 @@ namespace exprtk inline expression_node* allocate_rrr(T1& t1, T2& t2, T3& t3) const { expression_node* - result = (new node_type(t1, t2, t3)); + result = arena_new_rrr(t1, t2, t3); result->node_depth(); return result; } @@ -19492,7 +20023,7 @@ namespace exprtk inline expression_node* allocate_rrrr(T1& t1, T2& t2, T3& t3, T4& t4) const { expression_node* - result = (new node_type(t1, t2, t3, t4)); + result = arena_new_rrrr(t1, t2, t3, t4); result->node_depth(); return result; } @@ -19502,7 +20033,7 @@ namespace exprtk inline expression_node* allocate_rrrrr(T1& t1, T2& t2, T3& t3, T4& t4, T5& t5) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5)); + result = arena_new_rrrrr(t1, t2, t3, t4, t5); result->node_depth(); return result; } @@ -19513,7 +20044,7 @@ namespace exprtk const T3& t3) const { expression_node* - result = (new node_type(t1, t2, t3)); + result = arena_new_ccc(t1, t2, t3); result->node_depth(); return result; } @@ -19525,7 +20056,7 @@ namespace exprtk const T3& t3, const T4& t4) const { expression_node* - result = (new node_type(t1, t2, t3, t4)); + result = arena_new_cccc(t1, t2, t3, t4); result->node_depth(); return result; } @@ -19538,7 +20069,7 @@ namespace exprtk const T5& t5) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5)); + result = arena_new_ccccc(t1, t2, t3, t4, t5); result->node_depth(); return result; } @@ -19551,7 +20082,7 @@ namespace exprtk const T5& t5, const T6& t6) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6)); + result = arena_new_cccccc(t1, t2, t3, t4, t5, t6); result->node_depth(); return result; } @@ -19566,7 +20097,7 @@ namespace exprtk const T7& t7) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6, t7)); + result = arena_new_ccccccc(t1, t2, t3, t4, t5, t6, t7); result->node_depth(); return result; } @@ -19582,7 +20113,7 @@ namespace exprtk const T7& t7, const T8& t8) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6, t7, t8)); + result = arena_new_cccccccc(t1, t2, t3, t4, t5, t6, t7, t8); result->node_depth(); return result; } @@ -19599,7 +20130,7 @@ namespace exprtk const T9& t9) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9)); + result = arena_new_ccccccccc(t1, t2, t3, t4, t5, t6, t7, t8, t9); result->node_depth(); return result; } @@ -19617,7 +20148,7 @@ namespace exprtk const T9& t9, const T10& t10) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)); + result = arena_new_cccccccccc(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); result->node_depth(); return result; } @@ -19626,8 +20157,19 @@ namespace exprtk typename T1, typename T2, typename T3> inline expression_node* allocate_type(T1 t1, T2 t2, T3 t3) const { - expression_node* - result = (new node_type(t1, t2, t3)); + expression_node* result; + if (arena_) + { + void* mem = arena_->allocate(sizeof(node_type), alignof(node_type)); + if (mem) + { + result = ::new (mem) node_type(t1, t2, t3); + result->arena_managed_ = true; + result->node_depth(); + return result; + } + } + result = new node_type(t1, t2, t3); result->node_depth(); return result; } @@ -19638,8 +20180,19 @@ namespace exprtk inline expression_node* allocate_type(T1 t1, T2 t2, T3 t3, T4 t4) const { - expression_node* - result = (new node_type(t1, t2, t3, t4)); + expression_node* result; + if (arena_) + { + void* mem = arena_->allocate(sizeof(node_type), alignof(node_type)); + if (mem) + { + result = ::new (mem) node_type(t1, t2, t3, t4); + result->arena_managed_ = true; + result->node_depth(); + return result; + } + } + result = new node_type(t1, t2, t3, t4); result->node_depth(); return result; } @@ -19652,8 +20205,19 @@ namespace exprtk T3 t3, T4 t4, T5 t5) const { - expression_node* - result = (new node_type(t1, t2, t3, t4, t5)); + expression_node* result; + if (arena_) + { + void* mem = arena_->allocate(sizeof(node_type), alignof(node_type)); + if (mem) + { + result = ::new (mem) node_type(t1, t2, t3, t4, t5); + result->arena_managed_ = true; + result->node_depth(); + return result; + } + } + result = new node_type(t1, t2, t3, t4, t5); result->node_depth(); return result; } @@ -19666,8 +20230,19 @@ namespace exprtk T3 t3, T4 t4, T5 t5, T6 t6) const { - expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6)); + expression_node* result; + if (arena_) + { + void* mem = arena_->allocate(sizeof(node_type), alignof(node_type)); + if (mem) + { + result = ::new (mem) node_type(t1, t2, t3, t4, t5, t6); + result->arena_managed_ = true; + result->node_depth(); + return result; + } + } + result = new node_type(t1, t2, t3, t4, t5, t6); result->node_depth(); return result; } @@ -19681,8 +20256,19 @@ namespace exprtk T5 t5, T6 t6, T7 t7) const { - expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6, t7)); + expression_node* result; + if (arena_) + { + void* mem = arena_->allocate(sizeof(node_type), alignof(node_type)); + if (mem) + { + result = ::new (mem) node_type(t1, t2, t3, t4, t5, t6, t7); + result->arena_managed_ = true; + result->node_depth(); + return result; + } + } + result = new node_type(t1, t2, t3, t4, t5, t6, t7); result->node_depth(); return result; } @@ -19694,7 +20280,14 @@ namespace exprtk "type: %03d addr: %p\n", static_cast(e->type()), reinterpret_cast(e))); - delete e; + if (e->arena_managed_) + { + e->~expression_node(); + } + else + { + delete e; + } e = 0; } }; @@ -21896,6 +22489,7 @@ namespace exprtk : ref_count(0) , expr (0) , results (0) + , arena (0) , retinv_null(false) , return_invoked(&retinv_null) {} @@ -21904,6 +22498,7 @@ namespace exprtk : ref_count(1) , expr (e) , results (0) + , arena (0) , retinv_null(false) , return_invoked(&retinv_null) {} @@ -21921,8 +22516,20 @@ namespace exprtk { switch (local_data_list[i].type) { - case e_expr : delete reinterpret_cast(local_data_list[i].pointer); - break; + case e_expr : + { + expression_ptr eptr = reinterpret_cast(local_data_list[i].pointer); + if (eptr && eptr->arena_managed_) + { + // Arena-managed: call destructor only; memory freed by arena. + eptr->~expression_node(); + } + else + { + delete eptr; + } + } + break; case e_vecholder : delete reinterpret_cast(local_data_list[i].pointer); break; @@ -21945,6 +22552,10 @@ namespace exprtk { delete results; } + + // Arena is destroyed last; node destructors have already been called + // by destroy_node() above, so it is safe to bulk-free the pages now. + delete arena; } static inline cntrl_blck_ptr_t create(expression_ptr e) @@ -21972,6 +22583,7 @@ namespace exprtk expression_ptr expr; local_data_list_t local_data_list; results_context_t* results; + details::arena_allocator* arena; bool retinv_null; bool* return_invoked; @@ -22230,6 +22842,17 @@ namespace exprtk } } + inline void set_arena(details::arena_allocator* arena) + { + if (control_block_) + { + // Transfer ownership of arena to the control_block. + // Any previously owned arena is discarded (should not happen in practice). + delete control_block_->arena; + control_block_->arena = arena; + } + } + control_block* control_block_; symtab_list_t symbol_table_list_; @@ -22893,18 +23516,18 @@ namespace exprtk switch (se.type) { case scope_element::e_literal : delete reinterpret_cast(se.data); - delete se.var_node; + free_node_ptr(se.var_node); break; case scope_element::e_variable : delete reinterpret_cast(se.data); - delete se.var_node; + free_node_ptr(se.var_node); break; case scope_element::e_vector : delete[] reinterpret_cast(se.data); delete se.vec_node; break; - case scope_element::e_vecelem : delete se.var_node; + case scope_element::e_vecelem : free_node_ptr(se.var_node); break; #ifndef exprtk_disable_string_capabilities @@ -22919,6 +23542,18 @@ namespace exprtk se.clear(); } + inline void free_node_ptr(expression_node_ptr& node) + { + if (node) + { + if (node->arena_managed_) + node->~expression_node_t(); + else + delete node; + node = 0; + } + } + inline void cleanup() { for (std::size_t i = 0; i < element_.size(); ++i) @@ -24854,6 +25489,9 @@ namespace exprtk return false; } + details::arena_allocator* arena = new details::arena_allocator(); + node_allocator_.set_arena(arena); + expression_generator_.set_allocator(node_allocator_); if (expression_string.empty()) @@ -24925,10 +25563,14 @@ namespace exprtk expr.set_expression(e); expr.set_retinvk(retinvk_ptr); + expr.set_arena(arena); + arena = 0; register_local_vars(expr); register_return_results(expr); + node_allocator_.set_arena(0); + return !(!expr); } else @@ -24950,6 +25592,13 @@ namespace exprtk dec_.clear (); sem_.cleanup (); return_cleanup(); + + // Free the arena AFTER cleanup so scope-element nodes (which may be + // arena-managed) are properly destructed before their memory is reclaimed. + delete arena; + arena = 0; + node_allocator_.set_arena(0); + expr = expression_t(); return false; diff --git a/src/arena_allocator.hpp b/src/arena_allocator.hpp new file mode 100644 index 0000000..6b825ab --- /dev/null +++ b/src/arena_allocator.hpp @@ -0,0 +1,146 @@ +#ifndef EXPRTK_SRC_ARENA_ALLOCATOR_HPP +#define EXPRTK_SRC_ARENA_ALLOCATOR_HPP + +#include +#include +#include +#include + +namespace exprtk +{ + namespace details + { + + // Arena allocator for AST nodes. + // + // Allocates nodes in contiguous 64KB pages using placement-new, keeping + // nodes from the same expression close together in memory and dramatically + // improving L1/L2 cache hit rates during value() tree-walk evaluation. + // + // Nodes larger than the page size fall back to heap allocation (tracked + // separately and freed individually). + // + // Lifetime: owned by expression::control_block; destroyed after all node + // destructors have been called by node_collection_destructor::delete_nodes. + class arena_allocator + { + public: + + static const std::size_t page_size = 65536; // 64 KB pages + + arena_allocator() + : current_page_(0) + , current_offset_(0) + {} + + ~arena_allocator() + { + for (std::size_t i = 0; i < pages_.size(); ++i) + { + ::free(pages_[i]); + } + + for (std::size_t i = 0; i < oversized_.size(); ++i) + { + ::free(oversized_[i]); + } + } + + // Allocate size bytes aligned to align bytes. + // Returns a pointer to uninitialized memory, or NULL on failure. + void* allocate(const std::size_t size, const std::size_t align = sizeof(void*)) + { + if (size > page_size) + { + // Oversized allocation: fall back to malloc, track separately. + void* ptr = ::malloc(size); + if (ptr) + { + oversized_.push_back(ptr); + } + return ptr; + } + + if (!try_allocate_from_current(size, align)) + { + if (!add_page()) + { + return 0; + } + } + + return allocate_from_current(size, align); + } + + // Check whether a pointer falls within any arena-managed page. + // Used to determine if an expression_node's memory is arena-owned. + bool is_arena_ptr(const void* ptr) const + { + const char* p = static_cast(ptr); + + for (std::size_t i = 0; i < pages_.size(); ++i) + { + const char* page_start = static_cast(pages_[i]); + if (p >= page_start && p < page_start + page_size) + { + return true; + } + } + + for (std::size_t i = 0; i < oversized_.size(); ++i) + { + if (ptr == oversized_[i]) + { + return true; + } + } + + return false; + } + + private: + + arena_allocator(const arena_allocator&); + arena_allocator& operator=(const arena_allocator&); + + bool add_page() + { + void* page = ::malloc(page_size); + if (!page) return false; + pages_.push_back(page); + current_page_ = page; + current_offset_ = 0; + return true; + } + + bool try_allocate_from_current(const std::size_t size, const std::size_t align) const + { + if (!current_page_) return false; + + const std::size_t aligned_offset = align_offset(current_offset_, align); + return (aligned_offset + size) <= page_size; + } + + void* allocate_from_current(const std::size_t size, const std::size_t align) + { + const std::size_t aligned_offset = align_offset(current_offset_, align); + void* ptr = static_cast(current_page_) + aligned_offset; + current_offset_ = aligned_offset + size; + return ptr; + } + + static std::size_t align_offset(const std::size_t offset, const std::size_t align) + { + return (offset + align - 1) & ~(align - 1); + } + + std::vector pages_; + std::vector oversized_; + void* current_page_; + std::size_t current_offset_; + }; + + } // namespace details +} // namespace exprtk + +#endif // EXPRTK_SRC_ARENA_ALLOCATOR_HPP diff --git a/src/expression.hpp b/src/expression.hpp index c7d223b..bd77ac3 100644 --- a/src/expression.hpp +++ b/src/expression.hpp @@ -6,6 +6,7 @@ #include "nodes_compound.hpp" #include "nodes_assign.hpp" #include "lexer.hpp" +#include "arena_allocator.hpp" namespace exprtk { @@ -76,6 +77,7 @@ namespace exprtk : ref_count(0) , expr (0) , results (0) + , arena (0) , retinv_null(false) , return_invoked(&retinv_null) {} @@ -84,6 +86,7 @@ namespace exprtk : ref_count(1) , expr (e) , results (0) + , arena (0) , retinv_null(false) , return_invoked(&retinv_null) {} @@ -101,8 +104,15 @@ namespace exprtk { switch (local_data_list[i].type) { - case e_expr : delete reinterpret_cast(local_data_list[i].pointer); - break; + case e_expr : + { + expression_ptr eptr = reinterpret_cast(local_data_list[i].pointer); + if (eptr) + { + eptr->destroy_self(); + } + } + break; case e_vecholder : delete reinterpret_cast(local_data_list[i].pointer); break; @@ -125,6 +135,10 @@ namespace exprtk { delete results; } + + // Arena is destroyed last; node destructors have already been called + // by destroy_node() above, so it is safe to bulk-free the pages now. + delete arena; } static inline cntrl_blck_ptr_t create(expression_ptr e) @@ -152,6 +166,7 @@ namespace exprtk expression_ptr expr; local_data_list_t local_data_list; results_context_t* results; + details::arena_allocator* arena; bool retinv_null; bool* return_invoked; @@ -410,6 +425,17 @@ namespace exprtk } } + inline void set_arena(details::arena_allocator* arena) + { + if (control_block_) + { + // Transfer ownership of arena to the control_block. + // Any previously owned arena is discarded (should not happen in practice). + delete control_block_->arena; + control_block_->arena = arena; + } + } + control_block* control_block_; symtab_list_t symbol_table_list_; diff --git a/src/node_allocator.hpp b/src/node_allocator.hpp index 26c46af..3f3e9ce 100644 --- a/src/node_allocator.hpp +++ b/src/node_allocator.hpp @@ -2,6 +2,7 @@ #define EXPRTK_SRC_NODE_ALLOCATOR_HPP #include "nodes_base.hpp" +#include "arena_allocator.hpp" namespace exprtk { @@ -10,6 +11,396 @@ namespace exprtk class node_allocator { + public: + + node_allocator() + : arena_(0) + {} + + void set_arena(arena_allocator* arena) + { + arena_ = arena; + } + + arena_allocator* get_arena() const + { + return arena_; + } + + private: + + template + static inline void destroy_arena_node(expression_node* node) + { + static_cast(node)->~node_type(); + } + + template + inline void mark_arena_node(node_type* node) const + { + node->arena_managed_ = true; + node->arena_destroy_fn_ = &destroy_arena_node; + } + + template + inline node_type* arena_new() const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(); + mark_arena_node(node); + return node; + } + } + return new node_type(); + } + + template + inline node_type* arena_new(T1& t1) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1); + mark_arena_node(node); + return node; + } + } + return new node_type(t1); + } + + template + inline node_type* arena_new_c(const T1& t1) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1); + mark_arena_node(node); + return node; + } + } + return new node_type(t1); + } + + template + inline node_type* arena_new(const T1& t1, const T2& t2) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2); + } + + template + inline node_type* arena_new_cr(const T1& t1, T2& t2) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2); + } + + template + inline node_type* arena_new_rc(T1& t1, const T2& t2) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2); + } + + template + inline node_type* arena_new_rr(T1& t1, T2& t2) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2); + } + + template + inline node_type* arena_new_tt(T1 t1, T2 t2) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2); + } + + template + inline node_type* arena_new_ttt(T1 t1, T2 t2, T3 t3) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3); + } + + template + inline node_type* arena_new_tttt(T1 t1, T2 t2, T3 t3, T4 t4) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3, t4); + } + + template + inline node_type* arena_new_rrr(T1& t1, T2& t2, T3& t3) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3); + } + + template + inline node_type* arena_new_rrrr(T1& t1, T2& t2, T3& t3, T4& t4) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3, t4); + } + + template + inline node_type* arena_new_rrrrr(T1& t1, T2& t2, T3& t3, T4& t4, T5& t5) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3, t4, t5); + } + + template + inline node_type* arena_new_ccc(const T1& t1, const T2& t2, const T3& t3) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3); + } + + template + inline node_type* arena_new_cccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3, t4); + } + + template + inline node_type* arena_new_ccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3, t4, t5); + } + + template + inline node_type* arena_new_cccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5, t6); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3, t4, t5, t6); + } + + template + inline node_type* arena_new_ccccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5, t6, t7); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3, t4, t5, t6, t7); + } + + template + inline node_type* arena_new_cccccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7, const T8& t8) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5, t6, t7, t8); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3, t4, t5, t6, t7, t8); + } + + template + inline node_type* arena_new_ccccccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7, const T8& t8, const T9& t9) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9); + } + + template + inline node_type* arena_new_cccccccccc(const T1& t1, const T2& t2, const T3& t3, const T4& t4, const T5& t5, const T6& t6, const T7& t7, const T8& t8, const T9& t9, const T10& t10) const + { + if (arena_) + { + const std::size_t align = alignof(node_type); + void* mem = arena_->allocate(sizeof(node_type), align); + if (mem) + { + node_type* node = ::new (mem) node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); + mark_arena_node(node); + return node; + } + } + return new node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); + } + + arena_allocator* arena_; + public: template @@ -69,7 +460,7 @@ namespace exprtk template inline expression_node* allocate() const { - return (new node_type()); + return arena_new(); } template * allocate(T1& t1) const { expression_node* - result = (new node_type(t1)); + result = arena_new(t1); result->node_depth(); return result; } @@ -97,7 +488,7 @@ namespace exprtk inline expression_node* allocate_c(const T1& t1) const { expression_node* - result = (new node_type(t1)); + result = arena_new_c(t1); result->node_depth(); return result; } @@ -107,7 +498,7 @@ namespace exprtk inline expression_node* allocate(const T1& t1, const T2& t2) const { expression_node* - result = (new node_type(t1, t2)); + result = arena_new(t1, t2); result->node_depth(); return result; } @@ -117,7 +508,7 @@ namespace exprtk inline expression_node* allocate_cr(const T1& t1, T2& t2) const { expression_node* - result = (new node_type(t1, t2)); + result = arena_new_cr(t1, t2); result->node_depth(); return result; } @@ -127,7 +518,7 @@ namespace exprtk inline expression_node* allocate_rc(T1& t1, const T2& t2) const { expression_node* - result = (new node_type(t1, t2)); + result = arena_new_rc(t1, t2); result->node_depth(); return result; } @@ -137,7 +528,7 @@ namespace exprtk inline expression_node* allocate_rr(T1& t1, T2& t2) const { expression_node* - result = (new node_type(t1, t2)); + result = arena_new_rr(t1, t2); result->node_depth(); return result; } @@ -147,7 +538,7 @@ namespace exprtk inline expression_node* allocate_tt(T1 t1, T2 t2) const { expression_node* - result = (new node_type(t1, t2)); + result = arena_new_tt(t1, t2); result->node_depth(); return result; } @@ -157,7 +548,7 @@ namespace exprtk inline expression_node* allocate_ttt(T1 t1, T2 t2, T3 t3) const { expression_node* - result = (new node_type(t1, t2, t3)); + result = arena_new_ttt(t1, t2, t3); result->node_depth(); return result; } @@ -167,7 +558,7 @@ namespace exprtk inline expression_node* allocate_tttt(T1 t1, T2 t2, T3 t3, T4 t4) const { expression_node* - result = (new node_type(t1, t2, t3, t4)); + result = arena_new_tttt(t1, t2, t3, t4); result->node_depth(); return result; } @@ -177,7 +568,7 @@ namespace exprtk inline expression_node* allocate_rrr(T1& t1, T2& t2, T3& t3) const { expression_node* - result = (new node_type(t1, t2, t3)); + result = arena_new_rrr(t1, t2, t3); result->node_depth(); return result; } @@ -187,7 +578,7 @@ namespace exprtk inline expression_node* allocate_rrrr(T1& t1, T2& t2, T3& t3, T4& t4) const { expression_node* - result = (new node_type(t1, t2, t3, t4)); + result = arena_new_rrrr(t1, t2, t3, t4); result->node_depth(); return result; } @@ -197,7 +588,7 @@ namespace exprtk inline expression_node* allocate_rrrrr(T1& t1, T2& t2, T3& t3, T4& t4, T5& t5) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5)); + result = arena_new_rrrrr(t1, t2, t3, t4, t5); result->node_depth(); return result; } @@ -208,7 +599,7 @@ namespace exprtk const T3& t3) const { expression_node* - result = (new node_type(t1, t2, t3)); + result = arena_new_ccc(t1, t2, t3); result->node_depth(); return result; } @@ -220,7 +611,7 @@ namespace exprtk const T3& t3, const T4& t4) const { expression_node* - result = (new node_type(t1, t2, t3, t4)); + result = arena_new_cccc(t1, t2, t3, t4); result->node_depth(); return result; } @@ -233,7 +624,7 @@ namespace exprtk const T5& t5) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5)); + result = arena_new_ccccc(t1, t2, t3, t4, t5); result->node_depth(); return result; } @@ -246,7 +637,7 @@ namespace exprtk const T5& t5, const T6& t6) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6)); + result = arena_new_cccccc(t1, t2, t3, t4, t5, t6); result->node_depth(); return result; } @@ -261,7 +652,7 @@ namespace exprtk const T7& t7) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6, t7)); + result = arena_new_ccccccc(t1, t2, t3, t4, t5, t6, t7); result->node_depth(); return result; } @@ -277,7 +668,7 @@ namespace exprtk const T7& t7, const T8& t8) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6, t7, t8)); + result = arena_new_cccccccc(t1, t2, t3, t4, t5, t6, t7, t8); result->node_depth(); return result; } @@ -294,7 +685,7 @@ namespace exprtk const T9& t9) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9)); + result = arena_new_ccccccccc(t1, t2, t3, t4, t5, t6, t7, t8, t9); result->node_depth(); return result; } @@ -312,7 +703,7 @@ namespace exprtk const T9& t9, const T10& t10) const { expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)); + result = arena_new_cccccccccc(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10); result->node_depth(); return result; } @@ -321,8 +712,19 @@ namespace exprtk typename T1, typename T2, typename T3> inline expression_node* allocate_type(T1 t1, T2 t2, T3 t3) const { - expression_node* - result = (new node_type(t1, t2, t3)); + expression_node* result; + if (arena_) + { + void* mem = arena_->allocate(sizeof(node_type), alignof(node_type)); + if (mem) + { + result = ::new (mem) node_type(t1, t2, t3); + mark_arena_node(static_cast(result)); + result->node_depth(); + return result; + } + } + result = new node_type(t1, t2, t3); result->node_depth(); return result; } @@ -333,8 +735,19 @@ namespace exprtk inline expression_node* allocate_type(T1 t1, T2 t2, T3 t3, T4 t4) const { - expression_node* - result = (new node_type(t1, t2, t3, t4)); + expression_node* result; + if (arena_) + { + void* mem = arena_->allocate(sizeof(node_type), alignof(node_type)); + if (mem) + { + result = ::new (mem) node_type(t1, t2, t3, t4); + mark_arena_node(static_cast(result)); + result->node_depth(); + return result; + } + } + result = new node_type(t1, t2, t3, t4); result->node_depth(); return result; } @@ -347,8 +760,19 @@ namespace exprtk T3 t3, T4 t4, T5 t5) const { - expression_node* - result = (new node_type(t1, t2, t3, t4, t5)); + expression_node* result; + if (arena_) + { + void* mem = arena_->allocate(sizeof(node_type), alignof(node_type)); + if (mem) + { + result = ::new (mem) node_type(t1, t2, t3, t4, t5); + mark_arena_node(static_cast(result)); + result->node_depth(); + return result; + } + } + result = new node_type(t1, t2, t3, t4, t5); result->node_depth(); return result; } @@ -361,8 +785,19 @@ namespace exprtk T3 t3, T4 t4, T5 t5, T6 t6) const { - expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6)); + expression_node* result; + if (arena_) + { + void* mem = arena_->allocate(sizeof(node_type), alignof(node_type)); + if (mem) + { + result = ::new (mem) node_type(t1, t2, t3, t4, t5, t6); + mark_arena_node(static_cast(result)); + result->node_depth(); + return result; + } + } + result = new node_type(t1, t2, t3, t4, t5, t6); result->node_depth(); return result; } @@ -376,8 +811,19 @@ namespace exprtk T5 t5, T6 t6, T7 t7) const { - expression_node* - result = (new node_type(t1, t2, t3, t4, t5, t6, t7)); + expression_node* result; + if (arena_) + { + void* mem = arena_->allocate(sizeof(node_type), alignof(node_type)); + if (mem) + { + result = ::new (mem) node_type(t1, t2, t3, t4, t5, t6, t7); + mark_arena_node(static_cast(result)); + result->node_depth(); + return result; + } + } + result = new node_type(t1, t2, t3, t4, t5, t6, t7); result->node_depth(); return result; } @@ -389,7 +835,7 @@ namespace exprtk "type: %03d addr: %p\n", static_cast(e->type()), reinterpret_cast(e))); - delete e; + e->destroy_self(); e = 0; } }; diff --git a/src/nodes_base.hpp b/src/nodes_base.hpp index fdc1a53..5c3f65e 100644 --- a/src/nodes_base.hpp +++ b/src/nodes_base.hpp @@ -3,6 +3,7 @@ #include "common.hpp" #include "types.hpp" +#include "arena_allocator.hpp" namespace exprtk { @@ -600,10 +601,32 @@ namespace exprtk typedef node_collector_interface > nci_t; typedef typename nci_t::noderef_list_t noderef_list_t; typedef node_depth_base > ndb_t; + typedef void (*arena_destroy_fn_t)(expression_ptr); + + expression_node() + : arena_managed_(false) + , arena_destroy_fn_(0) + {} virtual ~expression_node() {} + bool arena_managed_; + arena_destroy_fn_t arena_destroy_fn_; + + inline void destroy_self() + { + if (arena_managed_) + { + assert(arena_destroy_fn_); + arena_destroy_fn_(this); + } + else + { + delete this; + } + } + inline virtual T value() const { return std::numeric_limits::quiet_NaN(); @@ -966,7 +989,7 @@ namespace exprtk { node_ptr_t& node = *node_delete_list[i]; exprtk_debug(("ncd::delete_nodes() - deleting: %p\n", reinterpret_cast(node))); - delete node; + node->destroy_self(); node = reinterpret_cast(0); } } diff --git a/src/parser.hpp b/src/parser.hpp index e4436b9..acc8f2c 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -380,18 +380,18 @@ namespace exprtk switch (se.type) { case scope_element::e_literal : delete reinterpret_cast(se.data); - delete se.var_node; + free_node_ptr(se.var_node); break; case scope_element::e_variable : delete reinterpret_cast(se.data); - delete se.var_node; + free_node_ptr(se.var_node); break; case scope_element::e_vector : delete[] reinterpret_cast(se.data); delete se.vec_node; break; - case scope_element::e_vecelem : delete se.var_node; + case scope_element::e_vecelem : free_node_ptr(se.var_node); break; #ifndef exprtk_disable_string_capabilities @@ -406,6 +406,15 @@ namespace exprtk se.clear(); } + inline void free_node_ptr(expression_node_ptr& node) + { + if (node) + { + node->destroy_self(); + node = 0; + } + } + inline void cleanup() { for (std::size_t i = 0; i < element_.size(); ++i) @@ -2341,6 +2350,35 @@ namespace exprtk return false; } + details::arena_allocator* arena = new details::arena_allocator(); + node_allocator_.set_arena(arena); + + struct scoped_arena_cleanup + { + scoped_arena_cleanup(details::node_allocator& node_allocator, + details::arena_allocator*& arena) + : node_allocator_(node_allocator) + , arena_(arena) + {} + + ~scoped_arena_cleanup() + { + node_allocator_.set_arena(0); + delete arena_; + arena_ = 0; + } + + inline void release() + { + node_allocator_.set_arena(0); + arena_ = 0; + } + + private: + details::node_allocator& node_allocator_; + details::arena_allocator*& arena_; + } arena_cleanup(node_allocator_, arena); + expression_generator_.set_allocator(node_allocator_); if (expression_string.empty()) @@ -2412,6 +2450,8 @@ namespace exprtk expr.set_expression(e); expr.set_retinvk(retinvk_ptr); + expr.set_arena(arena); + arena_cleanup.release(); register_local_vars(expr); register_return_results(expr); @@ -2437,6 +2477,7 @@ namespace exprtk dec_.clear (); sem_.cleanup (); return_cleanup(); + expr = expression_t(); return false; diff --git a/test/exprtk_arena_regression_test.cpp b/test/exprtk_arena_regression_test.cpp new file mode 100644 index 0000000..faf9c67 --- /dev/null +++ b/test/exprtk_arena_regression_test.cpp @@ -0,0 +1,89 @@ +#include "common.hpp" + +#define private public +#include "exprtk.hpp" +#undef private + +#include +#include +#include + +namespace +{ + typedef double numeric_type; + typedef exprtk::expression expression_t; + typedef exprtk::parser parser_t; + typedef exprtk::symbol_table symbol_table_t; + + bool test_failed_compile_clears_parser_arena() + { + parser_t parser; + expression_t expression; + + if (parser.compile("", expression)) + { + std::printf("test_failed_compile_clears_parser_arena() - empty expression unexpectedly compiled\n"); + return false; + } + + if (0 != parser.node_allocator_.get_arena()) + { + std::printf("test_failed_compile_clears_parser_arena() - parser retained arena after failed compile\n"); + return false; + } + + return true; + } + + bool test_vector_view_refs_are_released_on_expression_destroy() + { + std::vector data(4, numeric_type(1)); + exprtk::vector_view view = exprtk::make_vector_view(data, data.size()); + + if (!view.data_ref_.empty()) + { + std::printf("test_vector_view_refs_are_released_on_expression_destroy() - vector view started with refs\n"); + return false; + } + + { + symbol_table_t symbol_table; + symbol_table.add_vector("x", view); + + expression_t expression; + expression.register_symbol_table(symbol_table); + + parser_t parser; + if (!parser.compile("x", expression)) + { + std::printf("test_vector_view_refs_are_released_on_expression_destroy() - compile failed: %s\n", + parser.error().c_str()); + return false; + } + + if (view.data_ref_.empty()) + { + std::printf("test_vector_view_refs_are_released_on_expression_destroy() - compile did not register vector refs\n"); + return false; + } + } + + if (!view.data_ref_.empty()) + { + std::printf("test_vector_view_refs_are_released_on_expression_destroy() - vector refs remained after expression destruction\n"); + return false; + } + + return true; + } +} + +int main() +{ + bool result = true; + + result &= test_failed_compile_clears_parser_arena(); + result &= test_vector_view_refs_are_released_on_expression_destroy(); + + return result ? 0 : 1; +}