Compare commits

...

10 Commits

Author SHA1 Message Date
BLUELOVETH
23ffa73f4c
Merge pull request #284 from szdytom/integrate-test-with-build-system
Integrate testing with the cmake build system
2024-06-19 16:34:09 +08:00
f4d676548f Add sanitize mode 2024-06-19 07:24:17 +00:00
blueloveTH
3e9a6256ad some fix 2024-06-19 14:25:56 +08:00
273ce0c186 add test cmake 2024-06-19 05:55:49 +00:00
blueloveTH
e1e3e208cb move gc.h 2024-06-19 13:39:37 +08:00
BLUELOVETH
f06f7e21c9
Merge pull request #277 from szdytom/fix-some-leak
Fix memory leaks
2024-06-19 11:20:52 +08:00
BLUELOVETH
2a7067cd03
Merge pull request #281 from szdytom/align-dict-hashtable
Align memory access in `Dict` hashtable
2024-06-19 11:10:27 +08:00
2c5f46f096
call s_clean before pop context 2024-06-18 23:16:38 +08:00
8aa52ba64a
align memory access in Dict hashtable 2024-06-18 21:22:31 +08:00
c4d14847e8
fix leaks 2024-06-17 21:40:50 +08:00
21 changed files with 427 additions and 238 deletions

View File

@ -64,6 +64,25 @@ if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}")
set(PK_IS_MAIN TRUE)
option(PK_BUILD_SHARED_LIB "Build shared library" OFF)
option(PK_BUILD_STATIC_LIB "Build static library" OFF)
# @szdytom favored testing
# disabled by default because @blueloveTH doesn't like it :C
option(BUILD_TESTING "Build the testing tree." OFF)
if (BUILD_TESTING)
option(BUILD_TESTING_SANITIZE "Build the source with sanitizers" OFF)
if (BUILD_TESTING_SANITIZE)
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address /fno-omit-frame-pointer")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /fsanitize=address /fno-omit-frame-pointer")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,leak,undefined")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,leak,undefined")
endif()
endif()
enable_testing()
add_subdirectory(tests/)
endif()
else()
set(PK_IS_MAIN FALSE)
option(PK_BUILD_SHARED_LIB "Build shared library" OFF)
@ -77,12 +96,15 @@ elseif(PK_BUILD_STATIC_LIB)
else()
set(PROJECT_EXE_NAME main)
add_executable(${PROJECT_EXE_NAME} src2/main.cpp)
# shared linked main
if (BUILD_TESTING_SANITIZE)
# static linked main, for sanitizing purpose
add_library(${PROJECT_NAME} STATIC ${POCKETPY_SRC})
target_link_libraries(${PROJECT_EXE_NAME} ${PROJECT_NAME})
else()
# shared linked main, used by default, for CI and others
add_library(${PROJECT_NAME} SHARED ${POCKETPY_SRC})
target_link_libraries(${PROJECT_EXE_NAME} ${PROJECT_NAME} ${CMAKE_DL_LIBS})
# static linked main
# add_library(${PROJECT_NAME} STATIC ${POCKETPY_SRC})
# target_link_libraries(${PROJECT_EXE_NAME} ${PROJECT_NAME})
endif()
endif()
if(PK_USE_CJSON)

View File

@ -9,7 +9,7 @@ order: 0
Sometimes you need to use the following code to prevent the gc from collecting objects.
```cpp
auto _lock = vm->heap.gc_scope_lock();
auto _lock = vm->gc_scope_lock();
```
The scope lock is required if you create a PyVar and then try to run python-level bytecodes.
@ -34,7 +34,7 @@ The scope lock prevents this from happening.
void some_func(VM* vm){
PyVar obj = VAR(List(5));
// safe
auto _lock = vm->heap.gc_scope_lock();
auto _lock = vm->gc_scope_lock();
PyVar iter = vm->py_iter(obj);
PyVar next = vm->py_next(iter);
}

View File

@ -111,7 +111,7 @@ PyObject* VM::bind_field(PyObject* obj, const char* name, F T::*field) {
};
_1 = new_object<NativeFunc>(tp_native_func, fset, 2, field);
}
PyObject* prop = heap.gcnew<Property>(tp_property, _0, _1);
PyObject* prop = new_object<Property>(tp_property, _0, _1).get();
obj->attr().set(StrName(name_sv), prop);
return prop;
}

View File

@ -0,0 +1,40 @@
#include "pocketpy/objects/object.h"
#include "pocketpy/objects/public.h"
#include "pocketpy/common/config.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct pk_ManagedHeap{
c11_vector no_gc;
c11_vector gen;
int gc_threshold;
int gc_counter;
int gc_lock_counter;
pkpy_VM* vm;
void (*_gc_on_delete)(pkpy_VM*, PyObject*);
void (*_gc_marker_ex)(pkpy_VM*);
} pk_ManagedHeap;
void pk_ManagedHeap__ctor(pk_ManagedHeap* self, pkpy_VM* vm);
void pk_ManagedHeap__dtor(pk_ManagedHeap* self);
void pk_ManagedHeap__push_lock(pk_ManagedHeap* self);
void pk_ManagedHeap__pop_lock(pk_ManagedHeap* self);
void pk_ManagedHeap__collect_if_needed(pk_ManagedHeap* self);
int pk_ManagedHeap__collect(pk_ManagedHeap* self);
int pk_ManagedHeap__sweep(pk_ManagedHeap* self);
PyObject* pk_ManagedHeap__new(pk_ManagedHeap* self, pkpy_Type type, int size, bool gc);
// external implementation
void pk_ManagedHeap__mark(pk_ManagedHeap* self);
void pk_ManagedHeap__delete_obj(pk_ManagedHeap* self, PyObject* obj);
#ifdef __cplusplus
}
#endif

View File

@ -1,92 +0,0 @@
#pragma once
#include "pocketpy/common/config.h"
#include "pocketpy/common/vector.hpp"
#include "pocketpy/common/utils.h"
#include "pocketpy/objects/object.hpp"
#include "pocketpy/objects/namedict.hpp"
namespace pkpy {
struct ManagedHeap {
vector<PyObject*> _no_gc;
vector<PyObject*> gen;
VM* vm;
void (*_gc_on_delete)(VM*, PyObject*) = nullptr;
void (*_gc_marker_ex)(VM*) = nullptr;
ManagedHeap(VM* vm) : vm(vm) {}
int gc_threshold = PK_GC_MIN_THRESHOLD;
int gc_counter = 0;
/********************/
int _gc_lock_counter = 0;
struct ScopeLock {
PK_ALWAYS_PASS_BY_POINTER(ScopeLock)
ManagedHeap* heap;
ScopeLock(ManagedHeap* heap) : heap(heap) { heap->_gc_lock_counter++; }
~ScopeLock() { heap->_gc_lock_counter--; }
};
ScopeLock gc_scope_lock() { return ScopeLock(this); }
/********************/
template <typename T, typename... Args>
PyObject* _basic_new(Type type, Args&&... args) {
using __T = std::decay_t<T>;
static_assert(!is_sso_v<__T>, "gcnew cannot be used with SSO types");
// https://github.com/pocketpy/pocketpy/issues/94#issuecomment-1594784476
PyObject* p;
if constexpr(py_sizeof<__T> <= kPoolObjectBlockSize){
p = new (PoolObject_alloc()) PyObject(type, false);
}else{
p = new (std::malloc(py_sizeof<__T>)) PyObject(type, true);
}
new (p->_value_ptr()) T(std::forward<Args>(args)...);
// backdoor for important builtin types
if constexpr(std::is_same_v<__T, DummyInstance>) {
p->_attr = new NameDict();
} else if constexpr(std::is_same_v<__T, Type>) {
p->_attr = new NameDict();
} else if constexpr(std::is_same_v<__T, DummyModule>) {
p->_attr = new NameDict();
}
return p;
}
template <typename T, typename... Args>
PyObject* gcnew(Type type, Args&&... args) {
PyObject* p = _basic_new<T>(type, std::forward<Args>(args)...);
gen.push_back(p);
gc_counter++;
return p;
}
template <typename T, typename... Args>
PyObject* _new(Type type, Args&&... args) {
PyObject* p = _basic_new<T>(type, std::forward<Args>(args)...);
_no_gc.push_back(p);
return p;
}
void _delete(PyObject*);
#if PK_DEBUG_GC_STATS
inline static std::map<Type, int> deleted;
#endif
int sweep();
void _auto_collect();
bool _should_auto_collect() const { return gc_counter >= gc_threshold; }
int collect();
void mark();
};
} // namespace pkpy

View File

@ -0,0 +1,12 @@
#include "pocketpy/objects/object.h"
typedef struct pkpy_VM{
PyVar True;
PyVar False;
PyVar None;
PyVar NotImplemented;
PyVar Ellipsis;
} pkpy_VM;
void pkpy_VM__ctor(pkpy_VM* self);
void pkpy_VM__dtor(pkpy_VM* self);

View File

@ -4,7 +4,7 @@
#include "pocketpy/objects/dict.hpp"
#include "pocketpy/objects/error.hpp"
#include "pocketpy/objects/builtins.hpp"
#include "pocketpy/interpreter/gc.hpp"
#include "pocketpy/interpreter/gc.h"
#include "pocketpy/interpreter/frame.hpp"
#include "pocketpy/interpreter/profiler.hpp"
@ -162,7 +162,7 @@ class VM {
VM* vm; // self reference to simplify code
public:
ManagedHeap heap;
pk_ManagedHeap heap;
ValueStack s_data;
CallStack callstack;
vector<PyTypeInfo> _all_types;
@ -446,7 +446,31 @@ public:
template<typename T, typename ...Args>
PyVar new_object(Type type, Args&&... args){
static_assert(!is_sso_v<T>);
return heap.gcnew<T>(type, std::forward<Args>(args)...);
static_assert(std::is_same_v<T, std::decay_t<T>>);
PyObject* p = (PyObject*)pk_ManagedHeap__new(&heap, type, py_sizeof<T>, true);
new (p->_value_ptr()) T(std::forward<Args>(args)...);
// backdoor for important builtin types
if constexpr(std::is_same_v<T, DummyInstance>
|| std::is_same_v<T, Type>
|| std::is_same_v<T, DummyModule>) {
p->_attr = new NameDict();
}
return p;
}
template<typename T, typename ...Args>
PyVar new_object_no_gc(Type type, Args&&... args){
static_assert(!is_sso_v<T>);
static_assert(std::is_same_v<T, std::decay_t<T>>);
PyObject* p = (PyObject*)pk_ManagedHeap__new(&heap, type, py_sizeof<T>, false);
new (p->_value_ptr()) T(std::forward<Args>(args)...);
// backdoor for important builtin types
if constexpr(std::is_same_v<T, DummyInstance>
|| std::is_same_v<T, Type>
|| std::is_same_v<T, DummyModule>) {
p->_attr = new NameDict();
}
return p;
}
#endif
@ -456,7 +480,14 @@ public:
if(it == nullptr) PK_FATAL_ERROR("T not found in cxx_typeid_map\n")
return *it;
}
/********** old heap op **********/
struct HeapScopeLock {
PK_ALWAYS_PASS_BY_POINTER(HeapScopeLock)
pk_ManagedHeap* heap;
HeapScopeLock(pk_ManagedHeap* heap) : heap(heap) { pk_ManagedHeap__push_lock(heap);}
~HeapScopeLock() { pk_ManagedHeap__pop_lock(heap); }
};
HeapScopeLock gc_scope_lock(){ return {&heap}; }
/********** private **********/
virtual ~VM();
@ -569,13 +600,13 @@ PyVar py_var(VM* vm, __T&& value) {
if constexpr(is_sso_v<T>)
return PyVar(const_type, value);
else
return vm->heap.gcnew<T>(const_type, std::forward<__T>(value));
return vm->new_object<T>(const_type, std::forward<__T>(value));
} else {
Type type = vm->_find_type_in_cxx_typeid_map<T>();
if constexpr(is_sso_v<T>)
return PyVar(type, value);
else
return vm->heap.gcnew<T>(type, std::forward<__T>(value));
return vm->new_object<T>(type, std::forward<__T>(value));
}
}
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "stdint.h"
#include "stdbool.h"
#ifdef __cplusplus
extern "C" {
@ -15,10 +16,24 @@ struct pkpy_G {
pkpy_VM* vm;
} extern pkpy_g;
void py_initialize();
void py_switch_vm(const char* name);
void py_finalize();
bool py_eq(const PyVar*, const PyVar*);
bool py_le(const PyVar*, const PyVar*);
int64_t py_hash(const PyVar*);
/* py_var */
void py_newint(PyVar*, int64_t);
void py_newfloat(PyVar*, double);
void py_newbool(PyVar*, bool);
void py_newstr(PyVar*, const char*);
void py_newstr2(PyVar*, const char*, int);
void py_newbytes(PyVar*, const uint8_t*, int);
void py_newnone(PyVar*);
#ifdef __cplusplus
}
#endif

View File

@ -101,6 +101,7 @@ Error* Compiler::pop_context() noexcept{
assert(func->type != FuncType::UNSET);
}
contexts.back().s_clean();
contexts.pop_back();
return NULL;
}

View File

@ -52,7 +52,7 @@ void VM::__op_unpack_sequence(uint16_t arg) {
ValueError(_S("expected ", (int)arg, " values to unpack, got ", (int)tuple.size()));
}
} else {
auto _lock = heap.gc_scope_lock(); // lock the gc via RAII!!
auto _lock = gc_scope_lock(); // lock the gc via RAII!!
_0 = py_iter(_0);
const PyTypeInfo* ti = _tp_info(_0);
for(int i = 0; i < arg; i++) {
@ -802,7 +802,7 @@ PyVar VM::__run_top_frame() {
DISPATCH()
case OP_REPR: TOP() = VAR(py_repr(TOP())); DISPATCH()
case OP_CALL: {
if(heap._should_auto_collect()) heap._auto_collect();
pk_ManagedHeap__collect_if_needed(&heap);
PyVar _0 = vectorcall(byte.arg & 0xFF, // ARGC
(byte.arg >> 8) & 0xFF, // KWARGC
true);
@ -814,7 +814,7 @@ PyVar VM::__run_top_frame() {
}
DISPATCH()
case OP_CALL_TP: {
if(heap._should_auto_collect()) heap._auto_collect();
pk_ManagedHeap__collect_if_needed(&heap);
PyVar _0;
PyVar _1;
PyVar _2;
@ -1000,7 +1000,7 @@ PyVar VM::__run_top_frame() {
}
DISPATCH()
case OP_UNPACK_EX: {
auto _lock = heap.gc_scope_lock(); // lock the gc via RAII!!
auto _lock = gc_scope_lock(); // lock the gc via RAII!!
PyVar _0 = py_iter(POPX());
const PyTypeInfo* _ti = _tp_info(_0);
PyVar _1;

109
src/interpreter/gc.c Normal file
View File

@ -0,0 +1,109 @@
#include "pocketpy/interpreter/gc.h"
#include "pocketpy/common/memorypool.h"
void pk_ManagedHeap__ctor(pk_ManagedHeap *self, pkpy_VM *vm){
c11_vector__ctor(&self->no_gc, sizeof(PyObject*));
c11_vector__ctor(&self->gen, sizeof(PyObject*));
self->gc_threshold = PK_GC_MIN_THRESHOLD;
self->gc_counter = 0;
self->gc_lock_counter = 0;
self->vm = vm;
self->_gc_on_delete = NULL;
self->_gc_marker_ex = NULL;
}
void pk_ManagedHeap__dtor(pk_ManagedHeap *self){
for(int i = 0; i < self->gen.count; i++){
PyObject* obj = c11__getitem(PyObject*, &self->gen, i);
pk_ManagedHeap__delete_obj(self, obj);
}
for(int i = 0; i < self->no_gc.count; i++){
PyObject* obj = c11__getitem(PyObject*, &self->no_gc, i);
pk_ManagedHeap__delete_obj(self, obj);
}
c11_vector__dtor(&self->no_gc);
c11_vector__dtor(&self->gen);
}
void pk_ManagedHeap__push_lock(pk_ManagedHeap *self){
self->gc_lock_counter++;
}
void pk_ManagedHeap__pop_lock(pk_ManagedHeap *self){
self->gc_lock_counter--;
}
void pk_ManagedHeap__collect_if_needed(pk_ManagedHeap *self){
if(self->gc_counter < self->gc_threshold) return;
if(self->gc_lock_counter > 0) return;
self->gc_counter = 0;
pk_ManagedHeap__collect(self);
self->gc_threshold = self->gen.count * 2;
if(self->gc_threshold < PK_GC_MIN_THRESHOLD){
self->gc_threshold = PK_GC_MIN_THRESHOLD;
}
}
int pk_ManagedHeap__collect(pk_ManagedHeap *self){
assert(self->gc_lock_counter == 0);
pk_ManagedHeap__mark(self);
int freed = pk_ManagedHeap__sweep(self);
return freed;
}
int pk_ManagedHeap__sweep(pk_ManagedHeap *self){
c11_vector alive;
c11_vector__ctor(&alive, sizeof(PyObject*));
c11_vector__reserve(&alive, self->gen.count / 2);
for(int i = 0; i < self->gen.count; i++){
PyObject* obj = c11__getitem(PyObject*, &self->gen, i);
if(obj->gc_marked) {
obj->gc_marked = false;
c11_vector__push(PyObject*, &alive, obj);
} else {
if(self->_gc_on_delete){
self->_gc_on_delete(self->vm, obj);
}
pk_ManagedHeap__delete_obj(self, obj);
}
}
// clear _no_gc marked flag
for(int i=0; i<self->no_gc.count; i++){
PyObject* obj = c11__getitem(PyObject*, &self->no_gc, i);
obj->gc_marked = false;
}
int freed = self->gen.count - alive.count;
// destroy old gen
c11_vector__dtor(&self->gen);
// move alive to gen
self->gen = alive;
PoolObject_shrink_to_fit();
return freed;
}
PyObject* pk_ManagedHeap__new(pk_ManagedHeap *self, pkpy_Type type, int size, bool gc){
PyObject* obj;
// TODO: can we use compile time check?
if(size <= kPoolObjectBlockSize){
obj = PoolObject_alloc();
PyObject__ctor(obj, type, false);
}else{
obj = malloc(size);
PyObject__ctor(obj, type, true);
}
// TODO: can we use compile time check?
if(gc){
c11_vector__push(PyObject*, &self->gen, obj);
self->gc_counter++;
}else{
c11_vector__push(PyObject*, &self->no_gc, obj);
}
return obj;
}

View File

@ -1,56 +0,0 @@
#include "pocketpy/interpreter/gc.hpp"
namespace pkpy {
int ManagedHeap::sweep() {
vector<PyObject*> alive;
alive.reserve(gen.size() / 2);
for(PyObject* obj: gen) {
if(obj->gc_marked) {
obj->gc_marked = false;
alive.push_back(obj);
} else {
#if PK_DEBUG_GC_STATS
deleted[obj->type] += 1;
#endif
if(_gc_on_delete) _gc_on_delete(vm, obj);
_delete(obj);
}
}
// clear _no_gc marked flag
for(PyObject* obj: _no_gc)
obj->gc_marked = false;
int freed = gen.size() - alive.size();
#if PK_DEBUG_GC_STATS
for(auto& [type, count]: deleted) {
std::cout << "GC: " << _type_name(vm, type).sv() << "=" << count << std::endl;
}
std::cout << "GC: " << alive.size() << "/" << gen.size() << " (" << freed << " freed)" << std::endl;
deleted.clear();
#endif
gen.clear();
gen.swap(alive);
PoolObject_shrink_to_fit();
return freed;
}
void ManagedHeap::_auto_collect() {
#if !PK_DEBUG_NO_AUTO_GC
if(_gc_lock_counter > 0) return;
gc_counter = 0;
collect();
gc_threshold = gen.size() * 2;
if(gc_threshold < PK_GC_MIN_THRESHOLD) gc_threshold = PK_GC_MIN_THRESHOLD;
#endif
}
int ManagedHeap::collect() {
assert(_gc_lock_counter == 0);
mark();
int freed = sweep();
return freed;
}
} // namespace pkpy

40
src/interpreter/vm.c Normal file
View File

@ -0,0 +1,40 @@
// #include "pocketpy/interpreter/vm.h"
// #include "pocketpy/objects/base.h"
// void pkpy_VM__ctor(pkpy_VM* self){
// self->True = (PyVar){
// .type=tp_bool,
// .is_ptr=true,
// .extra=1,
// ._obj=pkpy_VM__gcnew(self, tp_bool)
// };
// self->False = (PyVar){
// .type=tp_bool,
// .is_ptr=true,
// .extra=0,
// ._obj=pkpy_VM__gcnew(self, tp_bool)
// };
// self->None = (PyVar){
// .type=tp_none_type,
// .is_ptr=true,
// ._obj=pkpy_VM__gcnew(self, tp_none_type)
// };
// self->NotImplemented = (PyVar){
// .type=tp_not_implemented_type,
// .is_ptr=true,
// ._obj=pkpy_VM__gcnew(self, tp_not_implemented_type)
// };
// self->Ellipsis = (PyVar){
// .type=tp_ellipsis,
// .is_ptr=true,
// ._obj=pkpy_VM__gcnew(self, tp_ellipsis)
// };
// }
// void pkpy_VM__dtor(pkpy_VM* self){
// }

View File

@ -1,6 +1,7 @@
#include "pocketpy/interpreter/vm.hpp"
#include "pocketpy/common/memorypool.h"
#include "pocketpy/objects/base.h"
#include "pocketpy/objects/public.h"
#include <cstddef>
#include <iostream>
@ -77,16 +78,17 @@ struct JsonSerializer {
}
Str serialize() {
auto _lock = vm->heap.gc_scope_lock();
auto _lock = vm->gc_scope_lock();
write_object(root);
return ss.str();
}
};
VM::VM(bool enable_os) : heap(this), enable_os(enable_os) {
VM::VM(bool enable_os) : enable_os(enable_os) {
pkpy_g.vm = (pkpy_VM*)this; // setup the current VM
Pools_initialize();
pkpy_StrName__initialize();
pkpy_g.vm = (pkpy_VM*)this; // setup the current VM
pk_ManagedHeap__ctor(&heap, (pkpy_VM*)this);
static ::PyObject __true_obj = {tp_bool, false, false, NULL};
static ::PyObject __false_obj = {tp_bool, false, false, NULL};
@ -226,7 +228,7 @@ PyVar VM::exec(std::string_view source) { return exec(source, "main.py", EXEC_MO
PyVar VM::eval(std::string_view source) { return exec(source, "<eval>", EVAL_MODE); }
PyObject* VM::new_type_object(PyObject* mod, StrName name, Type base, bool subclass_enabled, PyTypeInfo::Vt vt) {
PyObject* obj = heap._new<Type>(tp_type, Type(_all_types.size()));
PyObject* obj = new_object_no_gc<Type>(tp_type, Type(_all_types.size())).get();
const PyTypeInfo& base_info = _all_types[base];
if(!base_info.subclass_enabled) {
Str error = _S("type ", base_info.name.escape(), " is not `subclass_enabled`");
@ -302,7 +304,7 @@ bool VM::py_callable(PyVar obj) {
}
PyVar VM::__minmax_reduce(bool (VM::*op)(PyVar, PyVar), PyVar args, PyVar key) {
auto _lock = heap.gc_scope_lock();
auto _lock = gc_scope_lock();
const Tuple& args_tuple = PK_OBJ_GET(Tuple, args); // from *args, it must be a tuple
if(is_none(key) && args_tuple.size() == 2) {
// fast path
@ -328,7 +330,7 @@ PyVar VM::__minmax_reduce(bool (VM::*op)(PyVar, PyVar), PyVar args, PyVar key) {
if((this->*op)(view[i], res)) res = view[i];
}
} else {
auto _lock = heap.gc_scope_lock();
auto _lock = gc_scope_lock();
for(int i = 1; i < view.size(); i++) {
PyVar a = call(key, view[i]);
PyVar b = call(key, res);
@ -418,11 +420,8 @@ PyObject* VM::py_import(Str path, bool throw_err) {
}
VM::~VM() {
// clear managed heap
for(PyObject* obj: heap.gen)
heap._delete(obj);
for(PyObject* obj: heap._no_gc)
heap._delete(obj);
// destroy all objects
pk_ManagedHeap__dtor(&heap);
// clear everything
callstack.clear();
s_data.clear();
@ -472,7 +471,7 @@ void VM::__stack_gc_mark(PyVar* begin, PyVar* end) {
}
List VM::py_list(PyVar it) {
auto _lock = heap.gc_scope_lock();
auto _lock = gc_scope_lock();
it = py_iter(it);
List list;
const PyTypeInfo* info = _tp_info(it);
@ -566,7 +565,7 @@ PyVar VM::__py_exec_internal(const CodeObject_& code, PyVar globals, PyVar local
return vm->_exec(code.get(), frame->_module, frame->_callable, frame->_locals);
}
auto _lock = heap.gc_scope_lock(); // for safety
auto _lock = gc_scope_lock(); // for safety
PyObject* globals_obj = nullptr;
Dict* globals_dict = nullptr;
@ -583,7 +582,6 @@ PyVar VM::__py_exec_internal(const CodeObject_& code, PyVar globals, PyVar local
check_compatible_type(globals, VM::tp_dict);
// make a temporary object and copy globals into it
globals_obj = new_object<DummyInstance>(VM::tp_object).get();
globals_obj->_attr = new NameDict();
globals_dict = &PK_OBJ_GET(Dict, globals);
globals_dict->apply([&](PyVar k, PyVar v) {
globals_obj->attr().set(CAST(Str&, k), v);
@ -603,7 +601,7 @@ PyVar VM::__py_exec_internal(const CodeObject_& code, PyVar globals, PyVar local
locals_closure->set(CAST(Str&, k), v);
});
PyObject* _callable =
heap.gcnew<Function>(tp_function, __dynamic_func_decl, globals_obj, nullptr, locals_closure);
new_object<Function>(tp_function, __dynamic_func_decl, globals_obj, nullptr, locals_closure).get();
retval = vm->_exec(code.get(), globals_obj, _callable, vm->s_data._sp);
}
@ -723,7 +721,7 @@ PyVar VM::__format_object(PyVar obj, Str spec) {
}
PyObject* VM::new_module(Str name, Str package) {
PyObject* obj = heap._new<DummyModule>(tp_module);
PyObject* obj = new_object_no_gc<DummyModule>(tp_module).get();
obj->attr().set(__name__, VAR(name));
obj->attr().set(__package__, VAR(package));
// convert to fullname
@ -873,8 +871,8 @@ void VM::__log_s_data(const char* title) {
void VM::__init_builtin_types() {
_all_types.emplace_back(nullptr, Type(), nullptr, "", false); // 0 is not used
_all_types.emplace_back(heap._new<Type>(tp_type, tp_object), Type(), nullptr, "object", true);
_all_types.emplace_back(heap._new<Type>(tp_type, tp_type), tp_object, nullptr, "type", false);
_all_types.emplace_back(new_object_no_gc<Type>(tp_type, tp_object).get(), Type(), nullptr, "object", true);
_all_types.emplace_back(new_object_no_gc<Type>(tp_type, tp_type).get(), tp_object, nullptr, "type", false);
auto validate = [](Type type, PyObject* ret) {
Type ret_t = ret->as<Type>();
@ -946,7 +944,7 @@ void VM::__init_builtin_types() {
}
void VM::__unpack_as_list(ArgsView args, List& list) {
auto _lock = heap.gc_scope_lock();
auto _lock = gc_scope_lock();
for(PyVar obj: args) {
if(is_type(obj, tp_star_wrapper)) {
const StarWrapper& w = _CAST(StarWrapper&, obj);
@ -966,7 +964,7 @@ void VM::__unpack_as_list(ArgsView args, List& list) {
}
void VM::__unpack_as_dict(ArgsView args, Dict& dict) {
auto _lock = heap.gc_scope_lock();
auto _lock = gc_scope_lock();
for(PyVar obj: args) {
if(is_type(obj, tp_star_wrapper)) {
const StarWrapper& w = _CAST(StarWrapper&, obj);
@ -1360,11 +1358,11 @@ void VM::setattr(PyVar obj, StrName name, PyVar value) {
}
PyObject* VM::bind_func(PyObject* obj, StrName name, int argc, NativeFuncC fn, any userdata, BindType bt) {
PyObject* nf = heap.gcnew<NativeFunc>(tp_native_func, fn, argc, std::move(userdata));
PyObject* nf = new_object<NativeFunc>(tp_native_func, fn, argc, std::move(userdata)).get();
switch(bt) {
case BindType::DEFAULT: break;
case BindType::STATICMETHOD: nf = heap.gcnew<StaticMethod>(tp_staticmethod, nf); break;
case BindType::CLASSMETHOD: nf = heap.gcnew<ClassMethod>(tp_classmethod, nf); break;
case BindType::STATICMETHOD: nf = new_object<StaticMethod>(tp_staticmethod, nf).get(); break;
case BindType::CLASSMETHOD: nf = new_object<ClassMethod>(tp_classmethod, nf).get(); break;
}
if(obj != nullptr) obj->attr().set(name, nf);
return nf;
@ -1384,11 +1382,11 @@ PyObject* VM::bind(PyObject* obj, const char* sig, const char* docstring, Native
FuncDecl_ decl = co->func_decls[0];
decl->docstring = docstring;
PyObject* f_obj = heap.gcnew<NativeFunc>(tp_native_func, fn, decl, std::move(userdata));
PyObject* f_obj = new_object<NativeFunc>(tp_native_func, fn, decl, std::move(userdata)).get();
switch(bt) {
case BindType::STATICMETHOD: f_obj = heap.gcnew<StaticMethod>(tp_staticmethod, f_obj); break;
case BindType::CLASSMETHOD: f_obj = heap.gcnew<ClassMethod>(tp_classmethod, f_obj); break;
case BindType::STATICMETHOD: f_obj = new_object<StaticMethod>(tp_staticmethod, f_obj).get(); break;
case BindType::CLASSMETHOD: f_obj = new_object<ClassMethod>(tp_classmethod, f_obj).get(); break;
case BindType::DEFAULT: break;
}
if(obj != nullptr) obj->attr().set(decl->code->name, f_obj);
@ -1403,7 +1401,7 @@ PyObject* VM::bind_property(PyObject* obj, const char* name, NativeFuncC fget, N
PyVar _0 = new_object<NativeFunc>(tp_native_func, fget, 1);
PyVar _1 = vm->None;
if(fset != nullptr) _1 = new_object<NativeFunc>(tp_native_func, fset, 2);
PyObject* prop = heap.gcnew<Property>(tp_property, _0, _1);
PyObject* prop = new_object<Property>(tp_property, _0, _1).get();
obj->attr().set(StrName(name_sv), prop);
return prop;
}
@ -1877,22 +1875,29 @@ void Frame::_gc_mark(VM* vm) const {
vm->obj_gc_mark(_callable);
}
void ManagedHeap::mark() {
for(PyObject* obj: _no_gc)
extern "C"{
void pk_ManagedHeap__mark(pk_ManagedHeap* self){
VM* vm = (VM*)self->vm;
for(int i=0; i<self->no_gc.count; i++){
PyObject* obj = c11__getitem(PyObject*, &self->no_gc, i);
vm->__obj_gc_mark(obj);
vm->callstack.apply([this](Frame& frame) {
}
vm->callstack.apply([vm](Frame& frame) {
frame._gc_mark(vm);
});
vm->obj_gc_mark(vm->__last_exception);
vm->obj_gc_mark(vm->__curr_class);
vm->obj_gc_mark(vm->__c.error);
vm->__stack_gc_mark(vm->s_data.begin(), vm->s_data.end());
if(_gc_marker_ex) _gc_marker_ex(vm);
if(self->_gc_marker_ex) self->_gc_marker_ex((pkpy_VM*)vm);
}
void ManagedHeap::_delete(PyObject* obj) {
const PyTypeInfo* ti = vm->_tp_info(obj->type);
void pk_ManagedHeap__delete_obj(pk_ManagedHeap* self, ::PyObject* __obj){
PyObject* obj = (PyObject*)__obj;
const PyTypeInfo* ti = ((VM*)(self->vm))->_tp_info(obj->type);
if(ti->vt._dtor) ti->vt._dtor(obj->_value_ptr());
if (obj->_attr)
c11_vector__dtor(obj->_attr);
delete obj->_attr; // delete __dict__ if exists
if(obj->gc_is_large){
std::free(obj);
@ -1900,6 +1905,7 @@ void ManagedHeap::_delete(PyObject* obj) {
PoolObject_dealloc(obj);
}
}
}
void Dict::_gc_mark(VM* vm) const {
apply([vm](PyVar k, PyVar v) {

View File

@ -151,7 +151,7 @@ void add_module_io(VM* vm) {
void add_module_os(VM* vm) {
PyObject* mod = vm->new_module("os");
PyObject* path_obj = vm->heap.gcnew<DummyInstance>(VM::tp_object);
PyObject* path_obj = vm->new_object<DummyInstance>(VM::tp_object).get();
mod->attr().set("path", path_obj);
// Working directory is shared by all VMs!!

View File

@ -78,8 +78,8 @@ void add_module_sys(VM* vm) {
vm->setattr(mod, "version", VAR(PK_VERSION));
vm->setattr(mod, "platform", VAR(kPlatformStrings[PK_SYS_PLATFORM]));
PyObject* stdout_ = vm->heap.gcnew<DummyInstance>(vm->tp_object);
PyObject* stderr_ = vm->heap.gcnew<DummyInstance>(vm->tp_object);
PyObject* stdout_ = vm->new_object<DummyInstance>(vm->tp_object).get();
PyObject* stderr_ = vm->new_object<DummyInstance>(vm->tp_object).get();
vm->setattr(mod, "stdout", stdout_);
vm->setattr(mod, "stderr", stderr_);
@ -240,7 +240,7 @@ void add_module_dis(VM* vm) {
void add_module_gc(VM* vm) {
PyObject* mod = vm->new_module("gc");
vm->bind_func(mod, "collect", 0, PK_LAMBDA(VAR(vm->heap.collect())));
vm->bind_func(mod, "collect", 0, PK_LAMBDA(VAR(pk_ManagedHeap__collect(&vm->heap))));
}
void add_module_enum(VM* vm) {

View File

@ -58,8 +58,9 @@ pkpy_Dict pkpy_Dict__copy(const pkpy_Dict* self) {
static int pkpy_Dict__htget(const pkpy_Dict* self, int h) {
#if PK_DICT_COMPACT_MODE
const int *p = (const int*)(((const char*)self->_hashtable) + h * pkpy_Dict__idx_size(self));
return (*p) & pkpy_Dict__idx_null(self);
const int loc = pkpy_Dict__idx_size(self) * h;
const int *p = (const int*)(((const char*)self->_hashtable) + (loc & (~3)));
return (*p >> ((loc & 3) * 8)) & pkpy_Dict__idx_null(self);
#else
return ((const int*)self->_hashtable)[h];
#endif
@ -67,8 +68,10 @@ static int pkpy_Dict__htget(const pkpy_Dict* self, int h) {
static void pkpy_Dict__htset(pkpy_Dict* self, int h, int v) {
#if PK_DICT_COMPACT_MODE
int *p = (int*)(((char*)self->_hashtable) + h * pkpy_Dict__idx_size(self));
*p = v | (*p & ~pkpy_Dict__idx_null(self));
const int loc = pkpy_Dict__idx_size(self) * h;
int *p = (int*)(((char*)self->_hashtable) + (loc & (~3)));
const int shift = (loc & 3) * 8;
*p = (v << shift) | (*p & ~(pkpy_Dict__idx_null(self) << shift));
#else
((int*)self->_hashtable)[h] = v;
#endif

View File

@ -94,7 +94,7 @@ void __init_builtins(VM* _vm) {
return vm->None;
});
_vm->bind_func(_vm->builtins, "super", -1, [](VM* vm, ArgsView args) {
_vm->bind_func(_vm->builtins, "super", -1, [](VM* vm, ArgsView args)->PyVar {
PyObject* class_arg = nullptr;
PyVar self_arg = nullptr;
if(args.size() == 2) {
@ -122,13 +122,13 @@ void __init_builtins(VM* _vm) {
return vm->new_object<Super>(vm->tp_super, self_arg, vm->_all_types[type].base);
});
_vm->bind_func(_vm->builtins, "staticmethod", 1, [](VM* vm, ArgsView args) {
_vm->bind_func(_vm->builtins, "staticmethod", 1, [](VM* vm, ArgsView args)->PyVar {
PyVar func = args[0];
vm->check_type(func, vm->tp_function);
return vm->new_object<StaticMethod>(vm->tp_staticmethod, args[0]);
});
_vm->bind_func(_vm->builtins, "classmethod", 1, [](VM* vm, ArgsView args) {
_vm->bind_func(_vm->builtins, "classmethod", 1, [](VM* vm, ArgsView args)->PyVar {
PyVar func = args[0];
vm->check_type(func, vm->tp_function);
return vm->new_object<ClassMethod>(vm->tp_classmethod, args[0]);
@ -380,7 +380,7 @@ void __init_builtins(VM* _vm) {
return _0._obj == _1._obj ? vm->True : vm->False;
});
_vm->__cached_object_new = _vm->bind_func(VM::tp_object, __new__, 1, [](VM* vm, ArgsView args) {
_vm->__cached_object_new = _vm->bind_func(VM::tp_object, __new__, 1, [](VM* vm, ArgsView args) ->PyVar {
vm->check_type(args[0], vm->tp_type);
Type t = PK_OBJ_GET(Type, args[0]);
return vm->new_object<DummyInstance>(t);
@ -734,7 +734,7 @@ void __init_builtins(VM* _vm) {
});
_vm->bind_func(VM::tp_str, "join", 2, [](VM* vm, ArgsView args) {
auto _lock = vm->heap.gc_scope_lock();
auto _lock = vm->gc_scope_lock();
const Str& self = _CAST(Str&, args[0]);
SStream ss;
PyVar it = vm->py_iter(args[1]); // strong ref
@ -964,7 +964,7 @@ void __init_builtins(VM* _vm) {
});
_vm->bind_func(VM::tp_list, "extend", 2, [](VM* vm, ArgsView args) {
auto _lock = vm->heap.gc_scope_lock();
auto _lock = vm->gc_scope_lock();
List& self = _CAST(List&, args[0]);
PyVar it = vm->py_iter(args[1]); // strong ref
const PyTypeInfo* info = vm->_tp_info(args[1]);
@ -1342,7 +1342,7 @@ void __init_builtins(VM* _vm) {
});
// tp_dict
_vm->bind_func(VM::tp_dict, __new__, -1, [](VM* vm, ArgsView args) {
_vm->bind_func(VM::tp_dict, __new__, -1, [](VM* vm, ArgsView args)->PyVar {
Type cls_t = PK_OBJ_GET(Type, args[0]);
return vm->new_object<Dict>(cls_t);
});
@ -1350,7 +1350,7 @@ void __init_builtins(VM* _vm) {
_vm->bind_func(VM::tp_dict, __init__, -1, [](VM* vm, ArgsView args) {
if(args.size() == 1 + 0) return vm->None;
if(args.size() == 1 + 1) {
auto _lock = vm->heap.gc_scope_lock();
auto _lock = vm->gc_scope_lock();
Dict& self = PK_OBJ_GET(Dict, args[0]);
if(is_type(args[1], vm->tp_dict)) {
Dict& other = CAST(Dict&, args[1]);
@ -1539,7 +1539,7 @@ void __init_builtins(VM* _vm) {
_vm->bind_func(VM::tp_exception, __new__, -1, [](VM* vm, ArgsView args) -> PyVar {
Type cls = PK_OBJ_GET(Type, args[0]);
StrName cls_name = _type_name(vm, cls);
PyObject* e_obj = vm->heap.gcnew<Exception>(cls, cls_name.index);
PyObject* e_obj = vm->new_object<Exception>(cls, cls_name.index).get();
e_obj->_attr = new NameDict();
e_obj->as<Exception>().self = e_obj;
return e_obj;

View File

@ -428,7 +428,7 @@ bool pkpy_unpack_sequence(pkpy_vm* vm_handle, int n) {
VM* vm = (VM*)vm_handle;
PK_ASSERT_NO_ERROR()
PK_ASSERT_N_EXTRA_ELEMENTS(1)
auto _lock = vm->heap.gc_scope_lock();
auto _lock = vm->gc_scope_lock();
PK_PROTECTED(
PyVar _0 = vm->py_iter(vm->s_data.popx());
for(int i=0; i<n; i++){

36
src/public.c Normal file
View File

@ -0,0 +1,36 @@
#include "pocketpy/objects/public.h"
#include "pocketpy/objects/object.h"
#include "pocketpy/interpreter/vm.h"
void py_initialize(){
// initialize the global VM
}
void py_newint(PyVar* self, int64_t val){
self->type = tp_int;
self->is_ptr = false;
self->_i64 = val;
}
void py_newfloat(PyVar* self, double val){
self->type = tp_float;
self->is_ptr = false;
self->_f64 = val;
}
void py_newbool(PyVar* self, bool val){
// return a global singleton
}
void py_newnone(PyVar* self){
// return a heap object
}
void py_newstr(PyVar* self, const char* val){
// return a heap object
}
void py_newstr2(PyVar*, const char*, int);
void py_newbytes(PyVar*, const uint8_t*, int);

22
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,22 @@
# @szdytom favored testing, set BUILD_TESTING to enable it
# You can use scripts/run_tests.py as an alternative
# Note: the CI uses scripts/run_tests.py to run the tests
cmake_minimum_required(VERSION 3.10)
function(pkpy_add_test pyfile)
get_filename_component(test_name ${pyfile} NAME_WE)
add_test(
NAME ${test_name}
COMMAND $<TARGET_FILE:main> ${pyfile}
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/..
)
endfunction()
message("Testing enabled")
file(GLOB PK_PYTHON_TESTCASES_FILES RELATIVE ${CMAKE_CURRENT_LIST_DIR}/.. "*.py")
foreach(pyfile ${PK_PYTHON_TESTCASES_FILES})
pkpy_add_test(${pyfile})
endforeach()