Browse Source

Merge pull request #327 from pocketpy/mem-v2

Improve memory managements (mem-v2)
BLUELOVETH 1 year ago
parent
commit
e1e5de396f

+ 2 - 2
README.md

@@ -37,7 +37,7 @@ Please see https://pocketpy.dev for details and try the following resources.
 pkpy should work on any platform with a C11 compiler.
 These platforms are officially tested.
 
-> C99 compilers also work currently according to users' feedback.
+> C99 compilers may also work currently according to users' feedback.
 
 + Windows 64-bit
 + Linux 64-bit / 32-bit
@@ -74,7 +74,7 @@ It is safe to use `main` branch in production if CI badge is green.
 
 To compile it with your project, these flags must be set:
 
-+ `--std=c11` flag must be set (`--std=c99` may also work)
++ `--std=c11` flag must be set
 + For MSVC, `/utf-8` flag must be set
 + `NDEBUG` macro should be defined for release build, or you will get poor performance
 

+ 1 - 1
docs/index.md

@@ -32,7 +32,7 @@ print(primes)
 pkpy should work on any platform with a C11 compiler.
 These platforms are officially tested.
 
-> C99 compilers also work currently according to users' feedback.
+> C99 compilers may also work currently according to users' feedback.
 
 + Windows 64-bit
 + Linux 64-bit / 32-bit

+ 1 - 1
docs/quick-start.md

@@ -29,7 +29,7 @@ It is safe to use `main` branch in production if CI badge is green.
 
 To compile it with your project, these flags must be set:
 
-+ `--std=c11` flag must be set (`--std=c99` may also work)
++ `--std=c11` flag must be set
 + For MSVC, `/utf-8` flag must be set
 + `NDEBUG` macro should be defined for release build, or you will get poor performance
 

+ 1 - 1
docs/retype.yml

@@ -3,7 +3,7 @@ output: .retype
 url: https://pocketpy.dev
 branding:
   title: pocketpy
-  label: v2.0.5
+  label: v2.0.6
   logo: "./static/logo.png"
 favicon: "./static/logo.png"
 meta:

+ 13 - 17
include/pocketpy/common/memorypool.h

@@ -1,22 +1,18 @@
 #pragma once
 
-#define kPoolExprBlockSize      128
-#define kPoolFrameBlockSize     80
-#define kPoolObjectBlockSize    80
+typedef struct FixedMemoryPool {
+    int BlockSize;
+    int BlockCount;
 
-#define kPoolObjectArenaSize    (256*1024)
-#define kPoolObjectMaxBlocks    (kPoolObjectArenaSize / kPoolObjectBlockSize)
+    char* data;
+    char* data_end;
+    int exceeded_bytes;
 
-void MemoryPools__initialize();
-void MemoryPools__finalize();
+    char** _free_list;
+    char** _free_list_end;
+} FixedMemoryPool;
 
-void* PoolExpr_alloc();
-void PoolExpr_dealloc(void*);
-void* PoolFrame_alloc();
-void PoolFrame_dealloc(void*);
-
-void* PoolObject_alloc();
-void PoolObject_dealloc(void* p);
-void PoolObject_shrink_to_fit();
-
-void Pools_debug_info(char* buffer, int size);
+void FixedMemoryPool__ctor(FixedMemoryPool* self, int BlockSize, int BlockCount);
+void FixedMemoryPool__dtor(FixedMemoryPool* self);
+void* FixedMemoryPool__alloc(FixedMemoryPool* self);
+void FixedMemoryPool__dealloc(FixedMemoryPool* self, void* p);

+ 1 - 0
include/pocketpy/common/vector.h

@@ -22,6 +22,7 @@ void c11_vector__clear(c11_vector* self);
 void* c11_vector__emplace(c11_vector* self);
 bool c11_vector__contains(const c11_vector* self, void* elem);
 void* c11_vector__submit(c11_vector* self, int* length);
+void c11_vector__swap(c11_vector* self, c11_vector* other);
 
 #define c11__getitem(T, self, index) (((T*)(self)->data)[index])
 #define c11__setitem(T, self, index, value) ((T*)(self)->data)[index] = value;

+ 2 - 2
include/pocketpy/config.h

@@ -1,10 +1,10 @@
 #pragma once
 // clang-format off
 
-#define PK_VERSION				"2.0.5"
+#define PK_VERSION				"2.0.6"
 #define PK_VERSION_MAJOR            2
 #define PK_VERSION_MINOR            0
-#define PK_VERSION_PATCH            5
+#define PK_VERSION_PATCH            6
 
 /*************** feature settings ***************/
 

+ 10 - 9
include/pocketpy/interpreter/heap.h

@@ -1,24 +1,25 @@
 #include "pocketpy/objects/object.h"
+#include "pocketpy/interpreter/objectpool.h"
 
-typedef struct ManagedHeap{
-    c11_vector no_gc;
-    c11_vector gen;
+typedef struct ManagedHeap {
+    MultiPool small_objects;
+    c11_vector large_objects;
 
-    int gc_threshold;
-    int gc_counter;
+    int freed_ma[3];
+    int gc_threshold;  // threshold for gc_counter
+    int gc_counter;    // objects created since last gc
     bool gc_enabled;
-    
-    VM* vm;
 } ManagedHeap;
 
-void ManagedHeap__ctor(ManagedHeap* self, VM* vm);
+void ManagedHeap__ctor(ManagedHeap* self);
 void ManagedHeap__dtor(ManagedHeap* self);
 
 void ManagedHeap__collect_if_needed(ManagedHeap* self);
 int ManagedHeap__collect(ManagedHeap* self);
 int ManagedHeap__sweep(ManagedHeap* self);
 
-PyObject* ManagedHeap__new(ManagedHeap* self, py_Type type, int slots, int udsize);
+#define ManagedHeap__new(self, type, slots, udsize)                                                \
+    ManagedHeap__gcnew((self), (type), (slots), (udsize))
 PyObject* ManagedHeap__gcnew(ManagedHeap* self, py_Type type, int slots, int udsize);
 
 // external implementation

+ 32 - 0
include/pocketpy/interpreter/objectpool.h

@@ -0,0 +1,32 @@
+#pragma once
+
+#include "pocketpy/common/vector.h"
+#include "pocketpy/common/str.h"
+
+#define kPoolArenaSize (120 * 1024)
+#define kMultiPoolCount 5
+#define kPoolMaxBlockSize (32*kMultiPoolCount)
+
+typedef struct PoolArena {
+    int block_size;
+    int block_count;
+    int unused_length;
+    int* unused;
+    char data[kPoolArenaSize];
+} PoolArena;
+
+typedef struct Pool {
+    c11_vector /* PoolArena* */ arenas;
+    c11_vector /* PoolArena* */ no_free_arenas;
+    int block_size;
+} Pool;
+
+typedef struct MultiPool {
+    Pool pools[kMultiPoolCount];
+} MultiPool;
+
+void* MultiPool__alloc(MultiPool* self, int size);
+int MultiPool__sweep_dealloc(MultiPool* self);
+void MultiPool__ctor(MultiPool* self);
+void MultiPool__dtor(MultiPool* self);
+c11_string* MultiPool__summary(MultiPool* self);

+ 2 - 1
include/pocketpy/interpreter/vm.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include "pocketpy/common/memorypool.h"
 #include "pocketpy/objects/codeobject.h"
 #include "pocketpy/pocketpy.h"
 #include "pocketpy/interpreter/heap.h"
@@ -38,6 +39,7 @@ typedef struct VM {
     py_StackRef __curr_function;
     py_TValue __vectorcall_buffer[PK_MAX_CO_VARNAMES];
 
+    FixedMemoryPool pool_frame;
     ManagedHeap heap;
     ValueStack stack;  // put `stack` at the end for better cache locality
 } VM;
@@ -58,7 +60,6 @@ bool pk__object_new(int argc, py_Ref argv);
 py_TypeInfo* pk__type_info(py_Type type);
 
 bool pk_wrapper__self(int argc, py_Ref argv);
-bool pk_wrapper__NotImplementedError(int argc, py_Ref argv);
 
 const char* pk_op2str(py_Name op);
 

+ 1 - 0
include/pocketpy/objects/namedict.h

@@ -25,3 +25,4 @@ void ModuleDict__dtor(ModuleDict* self);
 void ModuleDict__set(ModuleDict* self, const char* key, py_TValue val);
 py_TValue* ModuleDict__try_get(ModuleDict* self, const char* path);
 bool ModuleDict__contains(ModuleDict* self, const char* path);
+void ModuleDict__apply_mark(ModuleDict* self, void (*marker)(PyObject*));

+ 2 - 3
include/pocketpy/objects/object.h

@@ -5,7 +5,7 @@
 
 typedef struct PyObject {
     py_Type type;  // we have a duplicated type here for convenience
-    bool gc_is_large;
+    // bool _;
     bool gc_marked;
     int slots;  // number of slots in the object
     char flex[];
@@ -23,5 +23,4 @@ void* PyObject__userdata(PyObject* self);
 
 #define PK_OBJ_SLOTS_SIZE(slots) ((slots) >= 0 ? sizeof(py_TValue) * (slots) : sizeof(NameDict))
 
-PyObject* PyObject__new(py_Type type, int slots, int size);
-void PyObject__delete(PyObject* self);
+void PyObject__dtor(PyObject* self);

+ 2 - 0
include/pocketpy/pocketpy.h

@@ -40,6 +40,8 @@ typedef struct py_Callbacks {
     char* (*importfile)(const char*);
     /// Used by `print` to output a string.
     void (*print)(const char*);
+    /// Used by `input` to get a character.
+    int (*getchar)();
 } py_Callbacks;
 
 #define PY_RAISE

+ 3 - 0
include/typings/pkpy.pyi

@@ -11,3 +11,6 @@ class TValue[T]:
 # TValue_float = TValue[float]
 # TValue_vec2i = TValue[vec2i]
 # TValue_vec2 = TValue[vec2]
+
+def memory_usage() -> str:
+    """Return a summary of the memory usage."""

+ 1 - 1
plugins/flutter/pocketpy/pubspec.yaml

@@ -1,6 +1,6 @@
 name: pocketpy
 description: A lightweight Python interpreter for game engines. It supports Android/iOS/Windows/Linux/MacOS.
-version: 2.0.5
+version: 2.0.6
 homepage: https://pocketpy.dev
 repository: https://github.com/pocketpy/pocketpy
 

+ 10 - 283
src/common/memorypool.c

@@ -2,196 +2,9 @@
 #include "pocketpy/pocketpy.h"
 
 #include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdio.h>
 #include <stdbool.h>
 
-typedef struct LinkedListNode {
-    struct LinkedListNode* prev;
-    struct LinkedListNode* next;
-} LinkedListNode;
-
-typedef struct LinkedList {
-    int length;
-    LinkedListNode head;
-    LinkedListNode tail;
-} LinkedList;
-
-static void LinkedList__ctor(LinkedList* self) {
-    self->length = 0;
-    self->head.prev = NULL;
-    self->head.next = &self->tail;
-    self->tail.prev = &self->head;
-    self->tail.next = NULL;
-}
-
-static void LinkedList__push_back(LinkedList* self, LinkedListNode* node) {
-    node->prev = self->tail.prev;
-    node->next = &self->tail;
-    self->tail.prev->next = node;
-    self->tail.prev = node;
-    self->length++;
-}
-
-static void LinkedList__push_front(LinkedList* self, LinkedListNode* node) {
-    node->prev = &self->head;
-    node->next = self->head.next;
-    self->head.next->prev = node;
-    self->head.next = node;
-    self->length++;
-}
-
-static void LinkedList__pop_back(LinkedList* self) {
-    assert(self->length > 0);
-    self->tail.prev->prev->next = &self->tail;
-    self->tail.prev = self->tail.prev->prev;
-    self->length--;
-}
-
-static LinkedListNode* LinkedList__back(LinkedList* self) {
-    assert(self->length > 0);
-    return self->tail.prev;
-}
-
-static void LinkedList__erase(LinkedList* self, LinkedListNode* node) {
-    node->prev->next = node->next;
-    node->next->prev = node->prev;
-    self->length--;
-}
-
-#define LinkedList__apply(self, __STATEMENTS__) \
-    do { \
-        LinkedListNode* node = (self)->head.next; \
-        while(node != &(self)->tail) { \
-            LinkedListNode* next = node->next; \
-            __STATEMENTS__ \
-            node = next; \
-        } \
-    } while(0)
-
-typedef struct MemoryPoolBlock{
-    void* arena;
-    char data[kPoolObjectBlockSize];
-} MemoryPoolBlock;
-
-typedef struct MemoryPoolArena{
-    /* LinkedListNode */
-    LinkedListNode* prev;
-    LinkedListNode* next;
-    /* Arena */
-    MemoryPoolBlock _blocks[kPoolObjectMaxBlocks];
-    MemoryPoolBlock* _free_list[kPoolObjectMaxBlocks];
-    int _free_list_size;
-} MemoryPoolArena;
-
-typedef struct MemoryPool{
-    LinkedList _arenas;
-    LinkedList _empty_arenas;
-} MemoryPool;
-
-static void MemoryPoolArena__ctor(MemoryPoolArena* self) {
-    self->prev = NULL;
-    self->next = NULL;
-    self->_free_list_size = kPoolObjectMaxBlocks;
-    for(int i = 0; i < kPoolObjectMaxBlocks; i++) {
-        self->_blocks[i].arena = self;
-        self->_free_list[i] = &self->_blocks[i];
-    }
-}
-
-static bool MemoryPoolArena__empty(MemoryPoolArena* self) {
-    return self->_free_list_size == 0;
-}
-
-static bool MemoryPoolArena__full(MemoryPoolArena* self) {
-    return self->_free_list_size == kPoolObjectMaxBlocks;
-}
-
-static int MemoryPoolArena__total_bytes(MemoryPoolArena* self) {
-    return kPoolObjectArenaSize;
-}
-
-static int MemoryPoolArena__used_bytes(MemoryPoolArena* self) {
-    return kPoolObjectBlockSize * (kPoolObjectMaxBlocks - self->_free_list_size);
-}
-
-static MemoryPoolBlock* MemoryPoolArena__alloc(MemoryPoolArena* self) {
-    assert(!MemoryPoolArena__empty(self));
-    self->_free_list_size--;
-    return self->_free_list[self->_free_list_size];
-}
-
-static void MemoryPoolArena__dealloc(MemoryPoolArena* self, MemoryPoolBlock* block) {
-    assert(!MemoryPoolArena__full(self));
-    self->_free_list[self->_free_list_size] = block;
-    self->_free_list_size++;
-}
-
-static void MemoryPool__ctor(MemoryPool* self) {
-    LinkedList__ctor(&self->_arenas);
-    LinkedList__ctor(&self->_empty_arenas);
-}
-
-static void* MemoryPool__alloc(MemoryPool* self) {
-    MemoryPoolArena* arena;
-    if(self->_arenas.length == 0){
-        arena = PK_MALLOC(sizeof(MemoryPoolArena));
-        MemoryPoolArena__ctor(arena);
-        LinkedList__push_back(&self->_arenas, (LinkedListNode*)arena);
-    } else {
-        arena = (MemoryPoolArena*)LinkedList__back(&self->_arenas);
-    }
-    void* p = MemoryPoolArena__alloc(arena)->data;
-    if(MemoryPoolArena__empty(arena)) {
-        LinkedList__pop_back(&self->_arenas);
-        LinkedList__push_back(&self->_empty_arenas, (LinkedListNode*)arena);
-    }
-    return p;
-}
-
-static void MemoryPool__dealloc(MemoryPool* self, void* p) {
-    assert(p != NULL);
-    MemoryPoolBlock* block = (MemoryPoolBlock*)((char*)p - sizeof(void*));
-    assert(block->arena != NULL);
-    MemoryPoolArena* arena = (MemoryPoolArena*)block->arena;
-    if(MemoryPoolArena__empty(arena)) {
-        LinkedList__erase(&self->_empty_arenas, (LinkedListNode*)arena);
-        LinkedList__push_front(&self->_arenas, (LinkedListNode*)arena);
-    }
-    MemoryPoolArena__dealloc(arena, block);
-}
-
-static void MemoryPool__shrink_to_fit(MemoryPool* self) {
-    const int MIN_ARENA_COUNT = PK_GC_MIN_THRESHOLD * 100 / (kPoolObjectArenaSize);
-    if(self->_arenas.length < MIN_ARENA_COUNT) return;
-    LinkedList__apply(&self->_arenas,
-            MemoryPoolArena* arena = (MemoryPoolArena*)node;
-            if(MemoryPoolArena__full(arena)) {
-                LinkedList__erase(&self->_arenas, node);
-                PK_FREE(arena);
-            });
-}
-
-
-static void MemoryPool__dtor(MemoryPool* self) {
-    LinkedList__apply(&self->_arenas, PK_FREE(node););
-    LinkedList__apply(&self->_empty_arenas, PK_FREE(node););
-}
-
-typedef struct FixedMemoryPool {
-    int BlockSize;
-    int BlockCount;
-
-    char* data;
-    char* data_end;
-    int exceeded_bytes;
-
-    char** _free_list;
-    char** _free_list_end;
-} FixedMemoryPool;
-
-static void FixedMemoryPool__ctor(FixedMemoryPool* self, int BlockSize, int BlockCount) {
+void FixedMemoryPool__ctor(FixedMemoryPool* self, int BlockSize, int BlockCount) {
     self->BlockSize = BlockSize;
     self->BlockCount = BlockCount;
     self->exceeded_bytes = 0;
@@ -204,12 +17,12 @@ static void FixedMemoryPool__ctor(FixedMemoryPool* self, int BlockSize, int Bloc
     }
 }
 
-static void FixedMemoryPool__dtor(FixedMemoryPool* self) {
+void FixedMemoryPool__dtor(FixedMemoryPool* self) {
     PK_FREE(self->_free_list);
     PK_FREE(self->data);
 }
 
-static void* FixedMemoryPool__alloc(FixedMemoryPool* self) {
+void* FixedMemoryPool__alloc(FixedMemoryPool* self) {
     if(self->_free_list_end != self->_free_list) {
         self->_free_list_end--;
         return *self->_free_list_end;
@@ -219,7 +32,7 @@ static void* FixedMemoryPool__alloc(FixedMemoryPool* self) {
     }
 }
 
-static void FixedMemoryPool__dealloc(FixedMemoryPool* self, void* p) {
+void FixedMemoryPool__dealloc(FixedMemoryPool* self, void* p) {
     bool is_valid = (char*)p >= self->data && (char*)p < self->data_end;
     if(is_valid) {
         *self->_free_list_end = p;
@@ -230,96 +43,10 @@ static void FixedMemoryPool__dealloc(FixedMemoryPool* self, void* p) {
     }
 }
 
-static int FixedMemoryPool__used_bytes(FixedMemoryPool* self) {
-    return (self->_free_list_end - self->_free_list) * self->BlockSize;
-}
-
-static int FixedMemoryPool__total_bytes(FixedMemoryPool* self) {
-    return self->BlockCount * self->BlockSize;
-}
-
-static FixedMemoryPool PoolExpr;
-static FixedMemoryPool PoolFrame;
-static MemoryPool PoolObject;
-
-void MemoryPools__initialize(){
-    FixedMemoryPool__ctor(&PoolExpr, kPoolExprBlockSize, 64);
-    FixedMemoryPool__ctor(&PoolFrame, kPoolFrameBlockSize, 128);
-    MemoryPool__ctor(&PoolObject);
-}
-
-void MemoryPools__finalize(){
-    FixedMemoryPool__dtor(&PoolExpr);
-    FixedMemoryPool__dtor(&PoolFrame);
-    MemoryPool__dtor(&PoolObject);
-}
-
-void* PoolExpr_alloc() {
-    return FixedMemoryPool__alloc(&PoolExpr);
-}
-
-void PoolExpr_dealloc(void* p) {
-    FixedMemoryPool__dealloc(&PoolExpr, p);
-}
-
-void* PoolFrame_alloc() {
-    return FixedMemoryPool__alloc(&PoolFrame);
-}
-
-void PoolFrame_dealloc(void* p) {
-    FixedMemoryPool__dealloc(&PoolFrame, p);
-}
-
-void* PoolObject_alloc() {
-    return MemoryPool__alloc(&PoolObject);
-}
-
-void PoolObject_dealloc(void* p) {
-    MemoryPool__dealloc(&PoolObject, p);
-}
-
-void PoolObject_shrink_to_fit() {
-    MemoryPool__shrink_to_fit(&PoolObject);
-}
-
-void Pools_debug_info(char* buffer, int size) {
-    double BYTES_PER_MB = 1024.0f * 1024.0f;
-    double BYTES_PER_KB = 1024.0f;
-    int n = 0;
-    n = snprintf(
-        buffer, size,  "PoolExpr: %.2f KB (used) / %.2f KB (total) - %.2f KB (exceeded)\n",
-        FixedMemoryPool__used_bytes(&PoolExpr) / BYTES_PER_KB,
-        FixedMemoryPool__total_bytes(&PoolExpr) / BYTES_PER_KB,
-        PoolExpr.exceeded_bytes / BYTES_PER_KB
-    );
-    buffer += n; size -= n;
-    n = snprintf(
-        buffer, size, "PoolFrame: %.2f KB (used) / %.2f KB (total) - %.2f KB (exceeded)\n",
-        FixedMemoryPool__used_bytes(&PoolFrame) / BYTES_PER_KB,
-        FixedMemoryPool__total_bytes(&PoolFrame) / BYTES_PER_KB,
-        PoolFrame.exceeded_bytes / BYTES_PER_KB
-    );
-    buffer += n; size -= n;
-    // PoolObject
-    int empty_arenas = PoolObject._empty_arenas.length;
-    int arenas = PoolObject._arenas.length;
-    // print empty arenas count
-    n = snprintf(
-        buffer, size, "PoolObject: %d empty arenas, %d arenas\n",
-        empty_arenas, arenas
-    );
-    buffer += n; size -= n;
-    // log each non-empty arena
-    LinkedList__apply(&PoolObject._arenas,
-        MemoryPoolArena* arena = (MemoryPoolArena*)node;
-        n = snprintf(
-            buffer, size, "  - %p: %.2f MB (used) / %.2f MB (total)\n",
-            (void*)arena,
-            MemoryPoolArena__used_bytes(arena) / BYTES_PER_MB,
-            MemoryPoolArena__total_bytes(arena) / BYTES_PER_MB
-        );
-        buffer += n; size -= n;
-    );
-}
+// static int FixedMemoryPool__used_bytes(FixedMemoryPool* self) {
+//     return (self->_free_list_end - self->_free_list) * self->BlockSize;
+// }
 
-#undef LinkedList__apply
+// static int FixedMemoryPool__total_bytes(FixedMemoryPool* self) {
+//     return self->BlockCount * self->BlockSize;
+// }

+ 0 - 42
src/common/sstream.c

@@ -244,45 +244,3 @@ void pk_sprintf(c11_sbuf* ss, const char* fmt, ...) {
     pk_vsprintf(ss, fmt, args);
     va_end(args);
 }
-
-int py_replinput(char* buf, int max_size) {
-    buf[0] = '\0';  // reset first char because we check '@' at the beginning
-
-    int size = 0;
-    bool multiline = false;
-    printf(">>> ");
-
-    while(true) {
-        int c = getchar();
-        if(c == EOF) return -1;
-
-        if(c == '\n') {
-            char last = '\0';
-            if(size > 0) last = buf[size - 1];
-            if(multiline) {
-                if(last == '\n') {
-                    break;  // 2 consecutive newlines to end multiline input
-                } else {
-                    printf("... ");
-                }
-            } else {
-                if(last == ':' || last == '(' || last == '[' || last == '{' || buf[0] == '@') {
-                    printf("... ");
-                    multiline = true;
-                } else {
-                    break;
-                }
-            }
-        }
-
-        if(size == max_size - 1) {
-            buf[size] = '\0';
-            return size;
-        }
-
-        buf[size++] = c;
-    }
-
-    buf[size] = '\0';
-    return size;
-}

+ 6 - 0
src/common/vector.c

@@ -62,3 +62,9 @@ void* c11_vector__submit(c11_vector* self, int* length) {
     self->capacity = 0;
     return retval;
 }
+
+void c11_vector__swap(c11_vector *self, c11_vector *other){
+    c11_vector tmp = *self;
+    *self = *other;
+    *other = tmp;
+}

+ 22 - 44
src/compiler/compiler.c

@@ -1,10 +1,10 @@
 #include "pocketpy/compiler/compiler.h"
 #include "pocketpy/compiler/lexer.h"
+#include "pocketpy/objects/base.h"
 #include "pocketpy/objects/codeobject.h"
 #include "pocketpy/objects/sourcedata.h"
 #include "pocketpy/objects/object.h"
 #include "pocketpy/common/sstream.h"
-#include "pocketpy/common/memorypool.h"
 #include <assert.h>
 #include <stdbool.h>
 
@@ -30,8 +30,6 @@ typedef struct ExprVt {
     void (*dtor)(Expr*);
 } ExprVt;
 
-#define static_assert_expr_size(T) static_assert(sizeof(T) <= kPoolExprBlockSize, "")
-
 #define vtcall(f, self, ctx) ((self)->vt->f((self), (ctx)))
 #define vtemit_(self, ctx) vtcall(emit_, (self), (ctx))
 #define vtemit_del(self, ctx) ((self)->vt->emit_del ? vtcall(emit_del, self, ctx) : false)
@@ -44,7 +42,7 @@ typedef struct ExprVt {
     do {                                                                                           \
         if(self) {                                                                                 \
             if((self)->vt->dtor) (self)->vt->dtor(self);                                           \
-            PoolExpr_dealloc(self);                                                                \
+            PK_FREE(self);                                                                         \
         }                                                                                          \
     } while(0)
 
@@ -148,8 +146,7 @@ NameExpr* NameExpr__new(int line, py_Name name, NameScope scope) {
                               .emit_del = NameExpr__emit_del,
                               .emit_store = NameExpr__emit_store,
                               .is_name = true};
-    static_assert_expr_size(NameExpr);
-    NameExpr* self = PoolExpr_alloc();
+    NameExpr* self = PK_MALLOC(sizeof(NameExpr));
     self->vt = &Vt;
     self->line = line;
     self->name = name;
@@ -186,8 +183,7 @@ StarredExpr* StarredExpr__new(int line, Expr* child, int level) {
                               .emit_store = StarredExpr__emit_store,
                               .is_starred = true,
                               .dtor = StarredExpr__dtor};
-    static_assert_expr_size(StarredExpr);
-    StarredExpr* self = PoolExpr_alloc();
+    StarredExpr* self = PK_MALLOC(sizeof(StarredExpr));
     self->vt = &Vt;
     self->line = line;
     self->child = child;
@@ -216,8 +212,7 @@ static void UnaryExpr__emit_(Expr* self_, Ctx* ctx) {
 
 UnaryExpr* UnaryExpr__new(int line, Expr* child, Opcode opcode) {
     const static ExprVt Vt = {.emit_ = UnaryExpr__emit_, .dtor = UnaryExpr__dtor};
-    static_assert_expr_size(UnaryExpr);
-    UnaryExpr* self = PoolExpr_alloc();
+    UnaryExpr* self = PK_MALLOC(sizeof(UnaryExpr));
     self->vt = &Vt;
     self->line = line;
     self->child = child;
@@ -240,8 +235,7 @@ void FStringSpecExpr__emit_(Expr* self_, Ctx* ctx) {
 
 FStringSpecExpr* FStringSpecExpr__new(int line, Expr* child, c11_sv spec) {
     const static ExprVt Vt = {.emit_ = FStringSpecExpr__emit_, .dtor = UnaryExpr__dtor};
-    static_assert_expr_size(FStringSpecExpr);
-    FStringSpecExpr* self = PoolExpr_alloc();
+    FStringSpecExpr* self = PK_MALLOC(sizeof(FStringSpecExpr));
     self->vt = &Vt;
     self->line = line;
     self->child = child;
@@ -263,8 +257,7 @@ void RawStringExpr__emit_(Expr* self_, Ctx* ctx) {
 
 RawStringExpr* RawStringExpr__new(int line, c11_sv value, Opcode opcode) {
     const static ExprVt Vt = {.emit_ = RawStringExpr__emit_};
-    static_assert_expr_size(RawStringExpr);
-    RawStringExpr* self = PoolExpr_alloc();
+    RawStringExpr* self = PK_MALLOC(sizeof(RawStringExpr));
     self->vt = &Vt;
     self->line = line;
     self->value = value;
@@ -288,8 +281,7 @@ void ImagExpr__emit_(Expr* self_, Ctx* ctx) {
 
 ImagExpr* ImagExpr__new(int line, double value) {
     const static ExprVt Vt = {.emit_ = ImagExpr__emit_};
-    static_assert_expr_size(ImagExpr);
-    ImagExpr* self = PoolExpr_alloc();
+    ImagExpr* self = PK_MALLOC(sizeof(ImagExpr));
     self->vt = &Vt;
     self->line = line;
     self->value = value;
@@ -333,8 +325,7 @@ void LiteralExpr__emit_(Expr* self_, Ctx* ctx) {
 
 LiteralExpr* LiteralExpr__new(int line, const TokenValue* value) {
     const static ExprVt Vt = {.emit_ = LiteralExpr__emit_, .is_literal = true};
-    static_assert_expr_size(LiteralExpr);
-    LiteralExpr* self = PoolExpr_alloc();
+    LiteralExpr* self = PK_MALLOC(sizeof(LiteralExpr));
     self->vt = &Vt;
     self->line = line;
     self->value = value;
@@ -362,8 +353,7 @@ void Literal0Expr__emit_(Expr* self_, Ctx* ctx) {
 
 Literal0Expr* Literal0Expr__new(int line, TokenIndex token) {
     const static ExprVt Vt = {.emit_ = Literal0Expr__emit_};
-    static_assert_expr_size(Literal0Expr);
-    Literal0Expr* self = PoolExpr_alloc();
+    Literal0Expr* self = PK_MALLOC(sizeof(Literal0Expr));
     self->vt = &Vt;
     self->line = line;
     self->token = token;
@@ -403,8 +393,7 @@ void SliceExpr__emit_(Expr* self_, Ctx* ctx) {
 
 SliceExpr* SliceExpr__new(int line) {
     const static ExprVt Vt = {.dtor = SliceExpr__dtor, .emit_ = SliceExpr__emit_};
-    static_assert_expr_size(SliceExpr);
-    SliceExpr* self = PoolExpr_alloc();
+    SliceExpr* self = PK_MALLOC(sizeof(SliceExpr));
     self->vt = &Vt;
     self->line = line;
     self->start = NULL;
@@ -433,8 +422,7 @@ static void DictItemExpr__emit_(Expr* self_, Ctx* ctx) {
 
 static DictItemExpr* DictItemExpr__new(int line) {
     const static ExprVt Vt = {.dtor = DictItemExpr__dtor, .emit_ = DictItemExpr__emit_};
-    static_assert_expr_size(DictItemExpr);
-    DictItemExpr* self = PoolExpr_alloc();
+    DictItemExpr* self = PK_MALLOC(sizeof(DictItemExpr));
     self->vt = &Vt;
     self->line = line;
     self->key = NULL;
@@ -521,8 +509,7 @@ bool TupleExpr__emit_del(Expr* self_, Ctx* ctx) {
 }
 
 static SequenceExpr* SequenceExpr__new(int line, const ExprVt* vt, int count, Opcode opcode) {
-    static_assert_expr_size(SequenceExpr);
-    SequenceExpr* self = PoolExpr_alloc();
+    SequenceExpr* self = PK_MALLOC(sizeof(SequenceExpr));
     self->vt = vt;
     self->line = line;
     self->opcode = opcode;
@@ -608,8 +595,7 @@ void CompExpr__emit_(Expr* self_, Ctx* ctx) {
 
 CompExpr* CompExpr__new(int line, Opcode op0, Opcode op1) {
     const static ExprVt Vt = {.dtor = CompExpr__dtor, .emit_ = CompExpr__emit_};
-    static_assert_expr_size(CompExpr);
-    CompExpr* self = PoolExpr_alloc();
+    CompExpr* self = PK_MALLOC(sizeof(CompExpr));
     self->vt = &Vt;
     self->line = line;
     self->op0 = op0;
@@ -633,8 +619,7 @@ static void LambdaExpr__emit_(Expr* self_, Ctx* ctx) {
 
 LambdaExpr* LambdaExpr__new(int line, int index) {
     const static ExprVt Vt = {.emit_ = LambdaExpr__emit_};
-    static_assert_expr_size(LambdaExpr);
-    LambdaExpr* self = PoolExpr_alloc();
+    LambdaExpr* self = PK_MALLOC(sizeof(LambdaExpr));
     self->vt = &Vt;
     self->line = line;
     self->index = index;
@@ -665,8 +650,7 @@ void LogicBinaryExpr__emit_(Expr* self_, Ctx* ctx) {
 
 LogicBinaryExpr* LogicBinaryExpr__new(int line, Opcode opcode) {
     const static ExprVt Vt = {.emit_ = LogicBinaryExpr__emit_, .dtor = LogicBinaryExpr__dtor};
-    static_assert_expr_size(LogicBinaryExpr);
-    LogicBinaryExpr* self = PoolExpr_alloc();
+    LogicBinaryExpr* self = PK_MALLOC(sizeof(LogicBinaryExpr));
     self->vt = &Vt;
     self->line = line;
     self->lhs = NULL;
@@ -705,8 +689,7 @@ GroupedExpr* GroupedExpr__new(int line, Expr* child) {
                               .emit_ = GroupedExpr__emit_,
                               .emit_del = GroupedExpr__emit_del,
                               .emit_store = GroupedExpr__emit_store};
-    static_assert_expr_size(GroupedExpr);
-    GroupedExpr* self = PoolExpr_alloc();
+    GroupedExpr* self = PK_MALLOC(sizeof(GroupedExpr));
     self->vt = &Vt;
     self->line = line;
     self->child = child;
@@ -833,8 +816,7 @@ BinaryExpr* BinaryExpr__new(int line, TokenIndex op, bool inplace) {
     const static ExprVt Vt = {.emit_ = BinaryExpr__emit_,
                               .dtor = BinaryExpr__dtor,
                               .is_binary = true};
-    static_assert_expr_size(BinaryExpr);
-    BinaryExpr* self = PoolExpr_alloc();
+    BinaryExpr* self = PK_MALLOC(sizeof(BinaryExpr));
     self->vt = &Vt;
     self->line = line;
     self->lhs = NULL;
@@ -871,8 +853,7 @@ void TernaryExpr__emit_(Expr* self_, Ctx* ctx) {
 
 TernaryExpr* TernaryExpr__new(int line) {
     const static ExprVt Vt = {.dtor = TernaryExpr__dtor, .emit_ = TernaryExpr__emit_};
-    static_assert_expr_size(TernaryExpr);
-    TernaryExpr* self = PoolExpr_alloc();
+    TernaryExpr* self = PK_MALLOC(sizeof(TernaryExpr));
     self->vt = &Vt;
     self->line = line;
     self->cond = NULL;
@@ -942,8 +923,7 @@ SubscrExpr* SubscrExpr__new(int line) {
         .emit_del = SubscrExpr__emit_del,
         .is_subscr = true,
     };
-    static_assert_expr_size(SubscrExpr);
-    SubscrExpr* self = PoolExpr_alloc();
+    SubscrExpr* self = PK_MALLOC(sizeof(SubscrExpr));
     self->vt = &Vt;
     self->line = line;
     self->lhs = NULL;
@@ -1005,8 +985,7 @@ AttribExpr* AttribExpr__new(int line, Expr* child, py_Name name) {
                               .emit_istore = AttribExpr__emit_istore,
                               .dtor = AttribExpr__dtor,
                               .is_attrib = true};
-    static_assert_expr_size(AttribExpr);
-    AttribExpr* self = PoolExpr_alloc();
+    AttribExpr* self = PK_MALLOC(sizeof(AttribExpr));
     self->vt = &Vt;
     self->line = line;
     self->child = child;
@@ -1078,8 +1057,7 @@ void CallExpr__emit_(Expr* self_, Ctx* ctx) {
 
 CallExpr* CallExpr__new(int line, Expr* callable) {
     const static ExprVt Vt = {.dtor = CallExpr__dtor, .emit_ = CallExpr__emit_};
-    static_assert_expr_size(CallExpr);
-    CallExpr* self = PoolExpr_alloc();
+    CallExpr* self = PK_MALLOC(sizeof(CallExpr));
     self->vt = &Vt;
     self->line = line;
     self->callable = callable;

+ 3 - 3
src/interpreter/frame.c

@@ -1,4 +1,5 @@
 #include "pocketpy/interpreter/frame.h"
+#include "pocketpy/common/memorypool.h"
 #include "pocketpy/interpreter/vm.h"
 #include "pocketpy/objects/base.h"
 #include "pocketpy/objects/codeobject.h"
@@ -42,8 +43,7 @@ Frame* Frame__new(const CodeObject* co,
                   py_StackRef p0,
                   py_StackRef locals,
                   bool has_function) {
-    static_assert(sizeof(Frame) <= kPoolFrameBlockSize, "!(sizeof(Frame) <= kPoolFrameBlockSize)");
-    Frame* self = PoolFrame_alloc();
+    Frame* self = FixedMemoryPool__alloc(&pk_current_vm->pool_frame);
     self->f_back = NULL;
     self->ip = (Bytecode*)co->codes.data - 1;
     self->co = co;
@@ -62,7 +62,7 @@ void Frame__delete(Frame* self) {
         self->uw_list = p->next;
         UnwindTarget__delete(p);
     }
-    PoolFrame_dealloc(self);
+    FixedMemoryPool__dealloc(&pk_current_vm->pool_frame, self);
 }
 
 int Frame__prepare_jump_exception_handler(Frame* self, ValueStack* _s) {

+ 58 - 69
src/interpreter/heap.c

@@ -1,39 +1,46 @@
 #include "pocketpy/interpreter/heap.h"
-#include "pocketpy/common/memorypool.h"
 #include "pocketpy/config.h"
+#include "pocketpy/interpreter/objectpool.h"
 #include "pocketpy/objects/base.h"
+#include "pocketpy/pocketpy.h"
 
-void ManagedHeap__ctor(ManagedHeap* self, VM* vm) {
-    c11_vector__ctor(&self->no_gc, sizeof(PyObject*));
-    c11_vector__ctor(&self->gen, sizeof(PyObject*));
+void ManagedHeap__ctor(ManagedHeap* self) {
+    MultiPool__ctor(&self->small_objects);
+    c11_vector__ctor(&self->large_objects, sizeof(PyObject*));
 
+    for(int i = 0; i < c11__count_array(self->freed_ma); i++) {
+        self->freed_ma[i] = PK_GC_MIN_THRESHOLD;
+    }
     self->gc_threshold = PK_GC_MIN_THRESHOLD;
     self->gc_counter = 0;
     self->gc_enabled = true;
-
-    self->vm = vm;
 }
 
 void ManagedHeap__dtor(ManagedHeap* self) {
-    for(int i = 0; i < self->gen.length; i++) {
-        PyObject* obj = c11__getitem(PyObject*, &self->gen, i);
-        PyObject__delete(obj);
-    }
-    for(int i = 0; i < self->no_gc.length; i++) {
-        PyObject* obj = c11__getitem(PyObject*, &self->no_gc, i);
-        PyObject__delete(obj);
+    // small_objects
+    MultiPool__dtor(&self->small_objects);
+    // large_objects
+    for(int i = 0; i < self->large_objects.length; i++) {
+        PyObject* obj = c11__getitem(PyObject*, &self->large_objects, i);
+        PyObject__dtor(obj);
+        PK_FREE(obj);
     }
-    c11_vector__dtor(&self->no_gc);
-    c11_vector__dtor(&self->gen);
+    c11_vector__dtor(&self->large_objects);
 }
 
 void ManagedHeap__collect_if_needed(ManagedHeap* self) {
     if(!self->gc_enabled) return;
     if(self->gc_counter < self->gc_threshold) return;
     self->gc_counter = 0;
-    ManagedHeap__collect(self);
-    self->gc_threshold = self->gen.length * 2;
-    if(self->gc_threshold < PK_GC_MIN_THRESHOLD) { self->gc_threshold = PK_GC_MIN_THRESHOLD; }
+    int freed = ManagedHeap__collect(self);
+    // adjust `gc_threshold` based on `freed_ma`
+    self->freed_ma[0] = self->freed_ma[1];
+    self->freed_ma[1] = self->freed_ma[2];
+    self->freed_ma[2] = freed;
+    int avg_freed = (self->freed_ma[0] + self->freed_ma[1] + self->freed_ma[2]) / 3;
+    int upper = self->gc_threshold * 2;
+    int lower = c11__max(PK_GC_MIN_THRESHOLD, self->gc_threshold / 2 + 1);
+    self->gc_threshold = c11__min(c11__max(avg_freed, lower), upper);
 }
 
 int ManagedHeap__collect(ManagedHeap* self) {
@@ -43,71 +50,53 @@ int ManagedHeap__collect(ManagedHeap* self) {
 }
 
 int ManagedHeap__sweep(ManagedHeap* self) {
-    c11_vector alive;
-    c11_vector__ctor(&alive, sizeof(PyObject*));
-    c11_vector__reserve(&alive, self->gen.length / 2);
-
-    for(int i = 0; i < self->gen.length; i++) {
-        PyObject* obj = c11__getitem(PyObject*, &self->gen, i);
+    // small_objects
+    int small_freed = MultiPool__sweep_dealloc(&self->small_objects);
+    // large_objects
+    int large_living_count = 0;
+    for(int i = 0; i < self->large_objects.length; i++) {
+        PyObject* obj = c11__getitem(PyObject*, &self->large_objects, i);
         if(obj->gc_marked) {
             obj->gc_marked = false;
-            c11_vector__push(PyObject*, &alive, obj);
+            c11__setitem(PyObject*, &self->large_objects, large_living_count, obj);
+            large_living_count++;
         } else {
-            PyObject__delete(obj);
+            PyObject__dtor(obj);
+            PK_FREE(obj);
+            // type and module objects are perpectual
+            assert(obj->type != tp_type);
+            assert(obj->type != tp_module);
         }
     }
-
-    // clear _no_gc marked flag
-    for(int i = 0; i < self->no_gc.length; i++) {
-        PyObject* obj = c11__getitem(PyObject*, &self->no_gc, i);
-        obj->gc_marked = false;
-    }
-
-    int freed = self->gen.length - alive.length;
-
-    // destroy old gen
-    c11_vector__dtor(&self->gen);
-    // move alive to gen
-    self->gen = alive;
-
-    PoolObject_shrink_to_fit();
-    return freed;
-}
-
-PyObject* ManagedHeap__new(ManagedHeap* self, py_Type type, int slots, int udsize) {
-    PyObject* obj = PyObject__new(type, slots, udsize);
-    c11_vector__push(PyObject*, &self->no_gc, obj);
-    return obj;
+    // shrink `self->large_objects`
+    int large_freed = self->large_objects.length - large_living_count;
+    self->large_objects.length = large_living_count;
+    return small_freed + large_freed;
 }
 
 PyObject* ManagedHeap__gcnew(ManagedHeap* self, py_Type type, int slots, int udsize) {
-    PyObject* obj = PyObject__new(type, slots, udsize);
-    c11_vector__push(PyObject*, &self->gen, obj);
-    self->gc_counter++;
-    return obj;
-}
-
-PyObject* PyObject__new(py_Type type, int slots, int size) {
     assert(slots >= 0 || slots == -1);
-    PyObject* self;
+    PyObject* obj;
     // header + slots + udsize
-    size = sizeof(PyObject) + PK_OBJ_SLOTS_SIZE(slots) + size;
-    if(!PK_LOW_MEMORY_MODE && size <= kPoolObjectBlockSize) {
-        self = PoolObject_alloc();
-        self->gc_is_large = false;
+    int size = sizeof(PyObject) + PK_OBJ_SLOTS_SIZE(slots) + udsize;
+    if(!PK_LOW_MEMORY_MODE && size <= kPoolMaxBlockSize) {
+        obj = MultiPool__alloc(&self->small_objects, size);
+        assert(obj != NULL);
     } else {
-        self = PK_MALLOC(size);
-        self->gc_is_large = true;
+        obj = PK_MALLOC(size);
+        c11_vector__push(PyObject*, &self->large_objects, obj);
     }
-    self->type = type;
-    self->gc_marked = false;
-    self->slots = slots;
+    obj->type = type;
+    obj->gc_marked = false;
+    obj->slots = slots;
 
     // initialize slots or dict
     if(slots >= 0) {
-        memset(self->flex, 0, slots * sizeof(py_TValue));
+        memset(obj->flex, 0, slots * sizeof(py_TValue));
     } else {
-        NameDict__ctor((void*)self->flex);
+        NameDict__ctor((void*)obj->flex);
     }
-    return self;
-}
+
+    self->gc_counter++;
+    return obj;
+}

+ 206 - 0
src/interpreter/objectpool.c

@@ -0,0 +1,206 @@
+#include "pocketpy/interpreter/objectpool.h"
+
+#include "pocketpy/config.h"
+#include "pocketpy/objects/object.h"
+#include "pocketpy/common/sstream.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+static PoolArena* PoolArena__new(int block_size) {
+    assert(kPoolArenaSize % block_size == 0);
+    int block_count = kPoolArenaSize / block_size;
+    PoolArena* self = PK_MALLOC(sizeof(PoolArena) + sizeof(int) * block_count);
+    self->block_size = block_size;
+    self->block_count = block_count;
+    self->unused_length = block_count;
+    self->unused = PK_MALLOC(sizeof(int) * block_count);
+    for(int i = 0; i < block_count; i++) {
+        self->unused[i] = i;
+    }
+    memset(self->data, 0, kPoolArenaSize);
+    return self;
+}
+
+static void PoolArena__delete(PoolArena* self) {
+    for(int i = 0; i < self->block_count; i++) {
+        PyObject* obj = (PyObject*)(self->data + i * self->block_size);
+        if(obj->type != 0) PyObject__dtor(obj);
+    }
+    PK_FREE(self->unused);
+    PK_FREE(self);
+}
+
+static void* PoolArena__alloc(PoolArena* self) {
+    assert(self->unused_length > 0);
+    int index = self->unused[self->unused_length - 1];
+    self->unused_length--;
+    return self->data + index * self->block_size;
+}
+
+static int PoolArena__sweep_dealloc(PoolArena* self) {
+    int freed = 0;
+    self->unused_length = 0;
+    for(int i = 0; i < self->block_count; i++) {
+        PyObject* obj = (PyObject*)(self->data + i * self->block_size);
+        if(obj->type == 0) {
+            // free slot
+            self->unused[self->unused_length] = i;
+            self->unused_length++;
+        } else {
+            if(!obj->gc_marked) {
+                // not marked, need to free
+                obj->type = 0;
+                freed++;
+                self->unused[self->unused_length] = i;
+                self->unused_length++;
+            } else {
+                // marked, clear mark
+                obj->gc_marked = false;
+            }
+        }
+    }
+    return freed;
+}
+
+static void Pool__ctor(Pool* self, int block_size) {
+    c11_vector__ctor(&self->arenas, sizeof(PoolArena*));
+    c11_vector__ctor(&self->no_free_arenas, sizeof(PoolArena*));
+    self->block_size = block_size;
+}
+
+static void Pool__dtor(Pool* self) {
+    c11__foreach(PoolArena*, &self->arenas, arena) PoolArena__delete(*arena);
+    c11__foreach(PoolArena*, &self->no_free_arenas, arena) PoolArena__delete(*arena);
+    c11_vector__dtor(&self->arenas);
+    c11_vector__dtor(&self->no_free_arenas);
+}
+
+static void* Pool__alloc(Pool* self) {
+    PoolArena* arena;
+    if(self->arenas.length == 0) {
+        arena = PoolArena__new(self->block_size);
+        c11_vector__push(PoolArena*, &self->arenas, arena);
+    } else {
+        arena = c11_vector__back(PoolArena*, &self->arenas);
+    }
+    void* ptr = PoolArena__alloc(arena);
+    if(arena->unused_length == 0) {
+        c11_vector__pop(&self->arenas);
+        c11_vector__push(PoolArena*, &self->no_free_arenas, arena);
+    }
+    return ptr;
+}
+
+static int Pool__sweep_dealloc(Pool* self, c11_vector* arenas, c11_vector* no_free_arenas) {
+    c11_vector__clear(arenas);
+    c11_vector__clear(no_free_arenas);
+
+    int freed = 0;
+    for(int i = 0; i < self->arenas.length; i++) {
+        PoolArena* item = c11__getitem(PoolArena*, &self->arenas, i);
+        assert(item->unused_length > 0);
+        freed += PoolArena__sweep_dealloc(item);
+        if(item->unused_length == item->block_count) {
+            // all free
+            if(arenas->length > 0) {
+                // at least one arena
+                PoolArena__delete(item);
+            } else {
+                // no arena
+                c11_vector__push(PoolArena*, arenas, item);
+            }
+        } else {
+            // some free
+            c11_vector__push(PoolArena*, arenas, item);
+        }
+    }
+    for(int i = 0; i < self->no_free_arenas.length; i++) {
+        PoolArena* item = c11__getitem(PoolArena*, &self->no_free_arenas, i);
+        freed += PoolArena__sweep_dealloc(item);
+        if(item->unused_length == 0) {
+            // still no free
+            c11_vector__push(PoolArena*, no_free_arenas, item);
+        } else {
+            if(item->unused_length == item->block_count) {
+                // all free
+                PoolArena__delete(item);
+            } else {
+                // some free
+                c11_vector__push(PoolArena*, arenas, item);
+            }
+        }
+    }
+
+    c11_vector__swap(&self->arenas, arenas);
+    c11_vector__swap(&self->no_free_arenas, no_free_arenas);
+    return freed;
+}
+
+void* MultiPool__alloc(MultiPool* self, int size) {
+    if(size == 0) return NULL;
+    int index = (size - 1) >> 5;
+    if(index < kMultiPoolCount) {
+        Pool* pool = &self->pools[index];
+        return Pool__alloc(pool);
+    }
+    return NULL;
+}
+
+int MultiPool__sweep_dealloc(MultiPool* self) {
+    c11_vector arenas;
+    c11_vector no_free_arenas;
+    c11_vector__ctor(&arenas, sizeof(PoolArena*));
+    c11_vector__ctor(&no_free_arenas, sizeof(PoolArena*));
+    int freed = 0;
+    for(int i = 0; i < kMultiPoolCount; i++) {
+        Pool* item = &self->pools[i];
+        freed += Pool__sweep_dealloc(item, &arenas, &no_free_arenas);
+    }
+    c11_vector__dtor(&arenas);
+    c11_vector__dtor(&no_free_arenas);
+    return freed;
+}
+
+void MultiPool__ctor(MultiPool* self) {
+    for(int i = 0; i < kMultiPoolCount; i++) {
+        Pool__ctor(&self->pools[i], 32 * (i + 1));
+    }
+}
+
+void MultiPool__dtor(MultiPool* self) {
+    for(int i = 0; i < kMultiPoolCount; i++) {
+        Pool__dtor(&self->pools[i]);
+    }
+}
+
+c11_string* MultiPool__summary(MultiPool* self) {
+    c11_sbuf sbuf;
+    c11_sbuf__ctor(&sbuf);
+    for(int i = 0; i < kMultiPoolCount; i++) {
+        Pool* item = &self->pools[i];
+        int total_bytes = (item->arenas.length + item->no_free_arenas.length) * kPoolArenaSize;
+        int used_bytes = 0;
+        for(int j = 0; j < item->arenas.length; j++) {
+            PoolArena* arena = c11__getitem(PoolArena*, &item->arenas, j);
+            used_bytes += (arena->block_count - arena->unused_length) * arena->block_size;
+        }
+        used_bytes += item->no_free_arenas.length * kPoolArenaSize;
+        float used_pct = (float)used_bytes / total_bytes * 100;
+        char buf[256];
+        snprintf(buf,
+                 sizeof(buf),
+                 "Pool<%d>: len(arenas)=%d, len(no_free_arenas)=%d, %d/%d (%.1f%% used)",
+                 item->block_size,
+                 item->arenas.length,
+                 item->no_free_arenas.length,
+                 used_bytes,
+                 total_bytes,
+                 used_pct);
+        c11_sbuf__write_cstr(&sbuf, buf);
+        c11_sbuf__write_char(&sbuf, '\n');
+    }
+    return c11_sbuf__submit(&sbuf);
+}

+ 56 - 15
src/interpreter/vm.c

@@ -66,6 +66,7 @@ void VM__ctor(VM* self) {
 
     self->callbacks.importfile = pk_default_importfile;
     self->callbacks.print = pk_default_print;
+    self->callbacks.getchar = getchar;
 
     self->last_retval = *py_NIL();
     self->curr_exception = *py_NIL();
@@ -76,7 +77,9 @@ void VM__ctor(VM* self) {
     self->__curr_class = NULL;
     self->__curr_function = NULL;
 
-    ManagedHeap__ctor(&self->heap, self);
+    FixedMemoryPool__ctor(&self->pool_frame, sizeof(Frame), 32);
+
+    ManagedHeap__ctor(&self->heap);
     ValueStack__ctor(&self->stack);
 
     /* Init Builtin Types */
@@ -247,6 +250,7 @@ void VM__dtor(VM* self) {
         VM__pop_frame(self);
     ModuleDict__dtor(&self->modules);
     TypeList__dtor(&self->types);
+    FixedMemoryPool__dtor(&self->pool_frame);
     ValueStack__clear(&self->stack);
 }
 
@@ -569,15 +573,10 @@ FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall
 }
 
 /****************************************/
-void PyObject__delete(PyObject* self) {
+void PyObject__dtor(PyObject* self) {
     py_TypeInfo* ti = pk__type_info(self->type);
     if(ti->dtor) ti->dtor(PyObject__userdata(self));
     if(self->slots == -1) NameDict__dtor(PyObject__dict(self));
-    if(self->gc_is_large) {
-        PK_FREE(self);
-    } else {
-        PoolObject_dealloc(self);
-    }
 }
 
 static void mark_object(PyObject* obj);
@@ -636,21 +635,25 @@ void CodeObject__gc_mark(const CodeObject* self) {
 }
 
 void ManagedHeap__mark(ManagedHeap* self) {
-    VM* vm = self->vm;
-    // mark heap objects
-    for(int i = 0; i < self->no_gc.length; i++) {
-        PyObject* obj = c11__getitem(PyObject*, &self->no_gc, i);
+    VM* vm = pk_current_vm;
+    // mark large objects
+    for(int i = 0; i < self->large_objects.length; i++) {
+        PyObject* obj = c11__getitem(PyObject*, &self->large_objects, i);
         mark_object(obj);
     }
     // mark value stack
     for(py_TValue* p = vm->stack.begin; p != vm->stack.end; p++) {
         pk__mark_value(p);
     }
+    // mark modules
+    ModuleDict__apply_mark(&vm->modules, mark_object);
     // mark types
     int types_length = vm->types.length;
     // 0-th type is placeholder
     for(py_Type i = 1; i < types_length; i++) {
         py_TypeInfo* ti = TypeList__get(&vm->types, i);
+        // mark type object
+        pk__mark_value(&ti->self);
         // mark common magic slots
         for(int j = 0; j < PK_MAGIC_SLOTS_COMMON_LENGTH; j++) {
             py_TValue* slot = ti->magic_0 + j;
@@ -745,8 +748,46 @@ bool pk_wrapper__self(int argc, py_Ref argv) {
     return true;
 }
 
-bool pk_wrapper__NotImplementedError(int argc, py_Ref argv) {
-    return py_exception(tp_NotImplementedError, "");
-}
-
 py_TypeInfo* pk__type_info(py_Type type) { return TypeList__get(&pk_current_vm->types, type); }
+
+int py_replinput(char* buf, int max_size) {
+    buf[0] = '\0';  // reset first char because we check '@' at the beginning
+
+    int size = 0;
+    bool multiline = false;
+    printf(">>> ");
+
+    while(true) {
+        int c = pk_current_vm->callbacks.getchar();
+        if(c == EOF) return -1;
+
+        if(c == '\n') {
+            char last = '\0';
+            if(size > 0) last = buf[size - 1];
+            if(multiline) {
+                if(last == '\n') {
+                    break;  // 2 consecutive newlines to end multiline input
+                } else {
+                    printf("... ");
+                }
+            } else {
+                if(last == ':' || last == '(' || last == '[' || last == '{' || buf[0] == '@') {
+                    printf("... ");
+                    multiline = true;
+                } else {
+                    break;
+                }
+            }
+        }
+
+        if(size == max_size - 1) {
+            buf[size] = '\0';
+            return size;
+        }
+
+        buf[size++] = c;
+    }
+
+    buf[size] = '\0';
+    return size;
+}

+ 22 - 0
src/modules/pkpy.c

@@ -1,3 +1,5 @@
+#include "pocketpy/interpreter/objectpool.h"
+#include "pocketpy/objects/base.h"
 #include "pocketpy/pocketpy.h"
 
 #include "pocketpy/common/utils.h"
@@ -33,6 +35,24 @@ DEF_TVALUE_METHODS(float, _f64)
 DEF_TVALUE_METHODS(vec2, _vec2)
 DEF_TVALUE_METHODS(vec2i, _vec2i)
 
+static bool pkpy_memory_usage(int argc, py_Ref argv) {
+    PY_CHECK_ARGC(0);
+    ManagedHeap* heap = &pk_current_vm->heap;
+    c11_string* small_objects_usage = MultiPool__summary(&heap->small_objects);
+    int large_object_count = heap->large_objects.length;
+    c11_sbuf buf;
+    c11_sbuf__ctor(&buf);
+    c11_sbuf__write_cstr(&buf, "== heap.small_objects ==\n");
+    c11_sbuf__write_cstr(&buf, small_objects_usage->data);
+    c11_sbuf__write_cstr(&buf, "== heap.large_objects ==\n");
+    c11_sbuf__write_cstr(&buf, "len(large_objects)=");
+    c11_sbuf__write_int(&buf, large_object_count);
+    // c11_sbuf__write_cstr(&buf, "== vm.pool_frame ==\n");
+    c11_sbuf__py_submit(&buf, py_retval());
+    c11_string__delete(small_objects_usage);
+    return true;
+}
+
 void pk__add_module_pkpy() {
     py_Ref mod = py_newmodule("pkpy");
 
@@ -66,6 +86,8 @@ void pk__add_module_pkpy() {
 
     py_setdict(mod, py_name("TValue"), TValue_dict);
     py_pop();
+
+    py_bindfunc(mod, "memory_usage", pkpy_memory_usage);
 }
 
 #undef DEF_TVALUE_METHODS

+ 7 - 1
src/objects/namedict.c

@@ -72,4 +72,10 @@ py_TValue* ModuleDict__try_get(ModuleDict* self, const char* path) {
 
 bool ModuleDict__contains(ModuleDict* self, const char* path) {
     return ModuleDict__try_get(self, path) != NULL;
-}
+}
+
+void ModuleDict__apply_mark(ModuleDict *self, void (*marker)(PyObject*)) {
+    if(self->left) ModuleDict__apply_mark(self->left, marker);
+    if(self->right) ModuleDict__apply_mark(self->right, marker);
+    marker(self->module._obj);
+}

+ 0 - 2
src/public/internal.c

@@ -27,7 +27,6 @@ void py_initialize() {
     static_assert(sizeof(py_TValue) == 16, "sizeof(py_TValue) != 16");
     static_assert(offsetof(py_TValue, extra) == 4, "offsetof(py_TValue, extra) != 4");
 
-    MemoryPools__initialize();
     py_Name__initialize();
 
     pk_current_vm = pk_all_vm[0] = &pk_default_vm;
@@ -63,7 +62,6 @@ void py_finalize() {
     VM__dtor(&pk_default_vm);
     pk_current_vm = NULL;
     py_Name__finalize();
-    MemoryPools__finalize();
 }
 
 void py_switchvm(int index) {

+ 2 - 1
src/public/modules.c

@@ -1,4 +1,5 @@
 #include "pocketpy/common/str.h"
+#include "pocketpy/objects/base.h"
 #include "pocketpy/objects/codeobject.h"
 #include "pocketpy/pocketpy.h"
 #include "pocketpy/common/utils.h"
@@ -208,7 +209,7 @@ static bool builtins_input(int argc, py_Ref argv) {
     c11_sbuf buf;
     c11_sbuf__ctor(&buf);
     while(true) {
-        int c = getchar();
+        int c = pk_current_vm->callbacks.getchar();
         if(c == '\n') break;
         if(c == EOF) break;
         c11_sbuf__write_char(&buf, c);