This commit is contained in:
BLUELOVETH 2023-07-10 14:00:11 +08:00
parent 7b29b4afb3
commit 349048d456
7 changed files with 132 additions and 238 deletions

View File

@ -14,20 +14,26 @@ Special thanks for [@koltenpearson](https://github.com/koltenpearson) for bringi
## Basic Functions ## Basic Functions
#### `pkpy_vm* pkpy_vm_create(bool use_stdio, bool enable_os)` #### `pkpy_vm* pkpy_new_vm(bool enable_os)`
Creates a new Lua Style VM. Create a new VM.
+ `use_stdio`: if true, the VM will use stdout and stderr
+ `enable_os`: if true, the VM will have access to the os library + `enable_os`: if true, the VM will have access to the os library
#### `bool pkpy_vm_run(pkpy_vm*, const char* source)` #### `bool pkpy_vm_run(pkpy_vm* vm_handle, const char* source)`
Runs the given source code in the VM. Run the given source code in the VM.
+ `source`: the source code to run + `source`: the source code to run
#### `void pkpy_vm_destroy(pkpy_vm*)` #### `void pkpy_delete_vm(pkpy_vm* vm_handle)`
Destroys the VM. Dispose the VM.
#### `bool pkpy_vm_exec(pkpy_vm* vm_handle, const char* source)`
A wrapper of `vm->exec(...)`.
#### `bool pkpy_vm_exec_2(pkpy_vm* vm_handle, const char* source, const char* filename, int mode, const char* module)`
A wrapper of `vm->exec_2(...)`.

View File

@ -128,6 +128,29 @@ struct FuncDecl {
void _gc_mark() const; void _gc_mark() const;
}; };
struct UserData{
char data[16];
bool empty;
UserData(): empty(true) {}
template<typename T>
UserData(T t): empty(false){
static_assert(std::is_trivially_copyable_v<T>);
static_assert(sizeof(T) <= sizeof(data));
memcpy(data, &t, sizeof(T));
}
template <typename T>
T get() const{
static_assert(std::is_trivially_copyable_v<T>);
static_assert(sizeof(T) <= sizeof(data));
#if PK_DEBUG_EXTRA_CHECK
PK_ASSERT(!empty);
#endif
return reinterpret_cast<const T&>(data);
}
};
struct NativeFunc { struct NativeFunc {
NativeFuncC f; NativeFuncC f;
@ -137,27 +160,14 @@ struct NativeFunc {
// new style decl-based call // new style decl-based call
FuncDecl_ decl; FuncDecl_ decl;
using UserData = char[32];
UserData _userdata; UserData _userdata;
bool _has_userdata;
template <typename T> void set_userdata(UserData data) {
void set_userdata(T data) { if(!_userdata.empty && !data.empty){
static_assert(std::is_trivially_copyable_v<T>); // override is not supported
static_assert(sizeof(T) <= sizeof(UserData)); throw std::runtime_error("userdata already set");
if(_has_userdata) throw std::runtime_error("userdata already set");
_has_userdata = true;
memcpy(_userdata, &data, sizeof(T));
} }
_userdata = data;
template <typename T>
T get_userdata() const {
static_assert(std::is_trivially_copyable_v<T>);
static_assert(sizeof(T) <= sizeof(UserData));
#if PK_DEBUG_EXTRA_CHECK
if(!_has_userdata) throw std::runtime_error("userdata not set");
#endif
return reinterpret_cast<const T&>(_userdata);
} }
NativeFunc(NativeFuncC f, int argc, bool method); NativeFunc(NativeFuncC f, int argc, bool method);
@ -201,8 +211,8 @@ struct Py_<NativeFunc> final: PyObject {
template<typename T> template<typename T>
T lambda_get_userdata(PyObject** p){ T lambda_get_userdata(PyObject** p){
if(p[-1] != PY_NULL) return PK_OBJ_GET(NativeFunc, p[-1]).get_userdata<T>(); if(p[-1] != PY_NULL) return PK_OBJ_GET(NativeFunc, p[-1])._userdata.get<T>();
else return PK_OBJ_GET(NativeFunc, p[-2]).get_userdata<T>(); else return PK_OBJ_GET(NativeFunc, p[-2])._userdata.get<T>();
} }
} // namespace pkpy } // namespace pkpy

View File

@ -63,6 +63,9 @@ struct ValueStackImpl {
void clear() { _sp = _begin; } void clear() { _sp = _begin; }
bool is_overflow() const { return _sp >= _max_end; } bool is_overflow() const { return _sp >= _max_end; }
PyObject* operator[](int i) const { return _begin[i]; }
PyObject*& operator[](int i) { return _begin[i]; }
ValueStackImpl(const ValueStackImpl&) = delete; ValueStackImpl(const ValueStackImpl&) = delete;
ValueStackImpl(ValueStackImpl&&) = delete; ValueStackImpl(ValueStackImpl&&) = delete;
ValueStackImpl& operator=(const ValueStackImpl&) = delete; ValueStackImpl& operator=(const ValueStackImpl&) = delete;

View File

@ -14,37 +14,15 @@ typedef struct pkpy_vm_handle pkpy_vm;
typedef int (*pkpy_function)(pkpy_vm*); typedef int (*pkpy_function)(pkpy_vm*);
/* Basic Functions */ /* Basic Functions */
PK_EXPORT pkpy_vm* pkpy_vm_create(bool use_stdio, bool enable_os); PK_EXPORT pkpy_vm* pkpy_new_vm(bool enable_os);
PK_EXPORT void pkpy_vm_destroy(pkpy_vm*); PK_EXPORT void pkpy_delete_vm(pkpy_vm* vm);
PK_EXPORT bool pkpy_vm_exec(pkpy_vm* vm, const char* source);
//we we take a lot of inspiration from the lua api for these bindings PK_EXPORT bool pkpy_vm_exec_2(pkpy_vm* vm, const char* source, const char* filename, int mode, const char* module);
//the key difference being most methods return a bool,
//true if it succeeded false if it did not
//if a method returns false call the pkpy_clear_error method to check the error and clear it
//if pkpy_clear_error returns false it means that no error was set, and it takes no action
//if pkpy_clear_error returns true it means there was an error and it was cleared,
//it will provide a string summary of the error in the message parameter (if it is not NULL)
//if null is passed in as message, and it will just print the message to stderr
PK_EXPORT bool pkpy_clear_error(pkpy_vm*, char** message);
//NOTE you are responsible for freeing message
//this will cause the vm to enter an error state and report the given message
//when queried
//note that at the moment this is more like a panic than throwing an error
//the user will not be able to catch it with python code
PK_EXPORT bool pkpy_error(pkpy_vm*, const char* name, const char* message);
/* Stack Manipulation */
PK_EXPORT bool pkpy_pop(pkpy_vm*, int n); PK_EXPORT bool pkpy_pop(pkpy_vm*, int n);
PK_EXPORT bool pkpy_dup_top(pkpy_vm*);
//push the item at index onto the top of the stack (as well as leaving it where PK_EXPORT bool pkpy_rot_two(pkpy_vm*);
//it is on the stack)
PK_EXPORT bool pkpy_push(pkpy_vm*, int index);
PK_EXPORT bool pkpy_push_function(pkpy_vm*, pkpy_function, int); PK_EXPORT bool pkpy_push_function(pkpy_vm*, pkpy_function, int);
PK_EXPORT bool pkpy_push_int(pkpy_vm*, int); PK_EXPORT bool pkpy_push_int(pkpy_vm*, int);
PK_EXPORT bool pkpy_push_float(pkpy_vm*, double); PK_EXPORT bool pkpy_push_float(pkpy_vm*, double);
@ -53,26 +31,32 @@ PK_EXPORT bool pkpy_push_string(pkpy_vm*, const char*);
PK_EXPORT bool pkpy_push_stringn(pkpy_vm*, const char*, int length); PK_EXPORT bool pkpy_push_stringn(pkpy_vm*, const char*, int length);
PK_EXPORT bool pkpy_push_voidp(pkpy_vm*, void*); PK_EXPORT bool pkpy_push_voidp(pkpy_vm*, void*);
PK_EXPORT bool pkpy_push_none(pkpy_vm*); PK_EXPORT bool pkpy_push_none(pkpy_vm*);
PK_EXPORT bool pkpy_push_eval(pkpy_vm*, const char* source);
PK_EXPORT bool pkpy_push_module(pkpy_vm*, const char* name);
/* Error Handling */
PK_EXPORT bool pkpy_clear_error(pkpy_vm*, char** message);
PK_EXPORT bool pkpy_error(pkpy_vm*, const char* name, const char* message);
//will return true if the vm is currently in an error state
PK_EXPORT bool pkpy_check_error(pkpy_vm*);
/* Variables */
PK_EXPORT bool pkpy_set_global(pkpy_vm*, const char* name); PK_EXPORT bool pkpy_set_global(pkpy_vm*, const char* name);
PK_EXPORT bool pkpy_get_global(pkpy_vm*, const char* name); PK_EXPORT bool pkpy_get_global(pkpy_vm*, const char* name);
//will return true if global exists
PK_EXPORT bool pkpy_check_global(pkpy_vm*, const char* name);
PK_EXPORT bool pkpy_getattr(pkpy_vm*, const char* name);
PK_EXPORT bool pkpy_setattr(pkpy_vm*, const char* name);
/* Callables */
//first push callable you want to call
//then push the arguments to send
//argc is the number of arguments that was pushed (not counting the callable)
PK_EXPORT bool pkpy_call(pkpy_vm*, int argc); PK_EXPORT bool pkpy_call(pkpy_vm*, int argc);
//first push the object the method belongs to (self)
//then push the the argments
//argc is the number of arguments that was pushed (not counting the callable or self)
//name is the name of the method to call on the object
PK_EXPORT bool pkpy_call_method(pkpy_vm*, const char* name, int argc); PK_EXPORT bool pkpy_call_method(pkpy_vm*, const char* name, int argc);
/* Types */
//we will break with the lua api here
//lua uses 1 as the index to the first pushed element for all of these functions
//but we will start counting at zero to match python
//we will allow negative numbers to count backwards from the top
PK_EXPORT bool pkpy_to_int(pkpy_vm*, int index, int* ret); PK_EXPORT bool pkpy_to_int(pkpy_vm*, int index, int* ret);
PK_EXPORT bool pkpy_to_float(pkpy_vm*, int index, double* ret); PK_EXPORT bool pkpy_to_float(pkpy_vm*, int index, double* ret);
PK_EXPORT bool pkpy_to_bool(pkpy_vm*, int index, bool* ret); PK_EXPORT bool pkpy_to_bool(pkpy_vm*, int index, bool* ret);
@ -97,34 +81,6 @@ PK_EXPORT bool pkpy_is_string(pkpy_vm*, int index);
PK_EXPORT bool pkpy_is_voidp(pkpy_vm*, int index); PK_EXPORT bool pkpy_is_voidp(pkpy_vm*, int index);
PK_EXPORT bool pkpy_is_none(pkpy_vm*, int index); PK_EXPORT bool pkpy_is_none(pkpy_vm*, int index);
//will return true if global exists
PK_EXPORT bool pkpy_check_global(pkpy_vm*, const char* name);
//will return true if the vm is currently in an error state
PK_EXPORT bool pkpy_check_error(pkpy_vm*);
//will return true if at least free empty slots remain on the stack
PK_EXPORT bool pkpy_check_stack(pkpy_vm*, int free);
//returns the number of elements on the stack
PK_EXPORT int pkpy_stack_size(pkpy_vm*);
PK_EXPORT bool pkpy_getattr(pkpy_vm*, const char* name);
PK_EXPORT bool pkpy_setattr(pkpy_vm*, const char* name);
PK_EXPORT bool pkpy_eval(pkpy_vm*, const char* source);
// create a new native module with the given name and push it onto the stack
PK_EXPORT bool pkpy_new_module(void* vm, const char* name);
/* vm api */
// for backwards compatibility
#define pkpy_vm_run(vm, source) pkpy_vm_exec(vm, source)
PK_EXPORT bool pkpy_vm_exec(pkpy_vm* vm, const char* source);
PK_EXPORT bool pkpy_vm_exec_2(pkpy_vm* vm, const char* source, const char* filename, int mode, const char* module);
/* special api */ /* special api */
// free a pointer allocated from pkpy's heap // free a pointer allocated from pkpy's heap

View File

@ -117,9 +117,6 @@ public:
NameDict _modules; // loaded modules NameDict _modules; // loaded modules
std::map<StrName, Str> _lazy_modules; // lazy loaded modules std::map<StrName, Str> _lazy_modules; // lazy loaded modules
PyObject* _reg[32]; // registers for user purpose, also used by C-API
static constexpr int REG_COUNT = sizeof(_reg) / sizeof(void*);
PyObject* None; PyObject* None;
PyObject* True; PyObject* True;
PyObject* False; PyObject* False;
@ -456,8 +453,8 @@ public:
PyObject* _py_generator(Frame&& frame, ArgsView buffer); PyObject* _py_generator(Frame&& frame, ArgsView buffer);
void _prepare_py_call(PyObject**, ArgsView, ArgsView, const FuncDecl_&); void _prepare_py_call(PyObject**, ArgsView, ArgsView, const FuncDecl_&);
// new style binding api // new style binding api
PyObject* bind(PyObject*, const char*, const char*, NativeFuncC, void* userdata=nullptr); PyObject* bind(PyObject*, const char*, const char*, NativeFuncC, UserData userdata={});
PyObject* bind(PyObject*, const char*, NativeFuncC, void* userdata=nullptr); PyObject* bind(PyObject*, const char*, NativeFuncC, UserData userdata={});
}; };
DEF_NATIVE_2(Str, tp_str) DEF_NATIVE_2(Str, tp_str)

View File

@ -1,77 +1,60 @@
#include "pocketpy.h" #include "pocketpy.h"
#include "pocketpy/tuplelist.h"
#include "pocketpy_c.h" #include "pocketpy_c.h"
using namespace pkpy; using namespace pkpy;
typedef int (*LuaStyleFuncC)(VM*); typedef int (*LuaStyleFuncC)(VM*);
struct LuaStack: public ValueStackImpl<32>{
PyObject*& at(int i) {
if(i < 0 || i >= size()){
throw std::runtime_error("lua stack index out of range");
}
return _begin[i];
}
PyObject* const& at(int i) const {
if(i < 0 || i >= size()){
throw std::runtime_error("lua stack index out of range");
}
return _begin[i];
}
void safe_push(PyObject* obj){
if(size() >= max_size()) throw std::runtime_error("lua stack overflow");
push(obj);
}
void safe_pop(){
if(size() == 0) throw std::runtime_error("lua stack is empty");
pop();
}
PyObject*& safe_top(){
if(size() == 0) throw std::runtime_error("lua stack is empty");
return top();
}
};
#define ERRHANDLER_OPEN \ #define ERRHANDLER_OPEN \
if (vm->error != nullptr) \ if (vm->_c.error != nullptr) \
return false; \ return false; \
try { try {
#define ERRHANDLER_CLOSE \ #define ERRHANDLER_CLOSE \
} catch(Exception& e ) { \ } catch(Exception& e ) { \
vm->error = py_var(vm, e); \ vm->_c.error = py_var(vm, e); \
return false; \ return false; \
} catch(const std::exception& re){ \ } catch(const std::exception& re){ \
auto e = Exception("std::exception", re.what()); \ auto e = Exception("std::exception", re.what()); \
vm->error = py_var(vm, e); \ vm->_c.error = py_var(vm, e); \
return false; \ return false; \
} }
pkpy_vm* pkpy_new_vm(bool enable_os){
class CVM: public VM { return (pkpy_vm*)new VM(enable_os);
public:
LuaStack c_data; // operation stack
PyObject* error;
CVM(bool use_stdio, bool enable_os) : VM(enable_os) {
error = nullptr;
heap._gc_marker_ex = [](VM* vm_) {
CVM* vm = (CVM*)vm_;
for(PyObject* obj: vm->c_data) if(obj!=nullptr) PK_OBJ_MARK(obj);
if(vm->error != nullptr) PK_OBJ_MARK(vm->error);
};
if (!use_stdio) {
_stdout = _stderr = [](VM* vm, const Str& s){
PK_UNUSED(vm);
PK_UNUSED(s);
};
} }
void pkpy_delete_vm(pkpy_vm* vm){
return delete (VM*)vm;
} }
};
bool pkpy_vm_exec(pkpy_vm* vm_handle, const char* source) {
VM* vm = (VM*) vm_handle;
PyObject* res;
ERRHANDLER_OPEN
CodeObject_ code = vm->compile(source, "main.py", EXEC_MODE);
res = vm->_exec(code, vm->_main);
ERRHANDLER_CLOSE
return res != nullptr;
}
bool pkpy_vm_exec_2(pkpy_vm* vm_handle, const char* source, const char* filename, int mode, const char* module){
VM* vm = (VM*) vm_handle;
PyObject* res;
PyObject* mod;
ERRHANDLER_OPEN
if(module == nullptr){
mod = vm->_main;
}else{
mod = vm->_modules[module]; // may raise
}
CodeObject_ code = vm->compile(source, filename, (CompileMode)mode);
res = vm->_exec(code, mod);
ERRHANDLER_CLOSE
return res != nullptr;
}
//for now I will unpack a tuple automatically, we may not want to handle //for now I will unpack a tuple automatically, we may not want to handle
@ -114,89 +97,34 @@ bool pkpy_clear_error(pkpy_vm* vm_handle, char** message) {
return true; return true;
} }
pkpy_vm* pkpy_vm_create(bool use_stdio, bool enable_os) {
CVM* vm = new CVM(use_stdio, enable_os);
return (pkpy_vm*) vm;
}
bool pkpy_vm_exec(pkpy_vm* vm_handle, const char* source) {
CVM* vm = (CVM*) vm_handle;
PyObject* res;
ERRHANDLER_OPEN
CodeObject_ code = vm->compile(source, "main.py", EXEC_MODE);
res = vm->_exec(code, vm->_main);
ERRHANDLER_CLOSE
return res != nullptr;
}
bool pkpy_vm_exec_2(pkpy_vm* vm_handle, const char* source, const char* filename, int mode, const char* module){
CVM* vm = (CVM*) vm_handle;
PyObject* res;
PyObject* mod;
ERRHANDLER_OPEN
if(module == nullptr){
mod = vm->_main;
}else{
mod = vm->_modules[module]; // may raise
}
CodeObject_ code = vm->compile(source, filename, (CompileMode)mode);
res = vm->_exec(code, mod);
ERRHANDLER_CLOSE
return res != nullptr;
}
void pkpy_vm_destroy(pkpy_vm* vm_handle) {
CVM* vm = (CVM*) vm_handle;
delete vm;
}
PyObject* c_function_wrapper(VM* vm, ArgsView args) { PyObject* c_function_wrapper(VM* vm, ArgsView args) {
LuaStyleFuncC f = lambda_get_userdata<LuaStyleFuncC>(args.begin()); LuaStyleFuncC f = lambda_get_userdata<LuaStyleFuncC>(args.begin());
CVM* cvm = (CVM*) vm; PyObject** curr_sp = &vm->s_data.top();
int retc = f(vm);
//setup c stack
LuaStack local_stack;
for (int i = 0; i < args.size(); i++)
local_stack.safe_push(args[i]);
// tmp is controlled by RAII
auto tmp = CVM::TempStack(cvm, &local_stack);
int retc = f(cvm);
// propagate_if_errored // propagate_if_errored
if (cvm->error != nullptr){ if (vm->_c.error != nullptr){
Exception e = _py_cast<Exception&>(vm, cvm->error); Exception e = _py_cast<Exception&>(vm, vm->_c.error);
cvm->error = nullptr; vm->_c.error = nullptr;
tmp.restore();
vm->_error(e); vm->_error(e);
} }
tmp.restore(); PK_ASSERT(retc == vm->s_data._sp-curr_sp);
if(retc == 0) return vm->None;
PyObject* ret = cvm->None; if (retc == 1) return vm->s_data.popx();
ArgsView ret_view(curr_sp, vm->s_data._sp);
if (retc == 1) return py_var(vm, ret_view.to_tuple());
ret = local_stack.safe_top();
else if (retc > 1) {
Tuple t(retc);
for (int i = 0; i < retc; i++) {
int stack_index = (local_stack.size() - retc) + i;
t[i] = local_stack.at(stack_index);
} }
ret = py_var(cvm, t); bool pkpy_push_function(pkpy_vm* vm_handle, const char* sig, pkpy_function f) {
} VM* vm = (VM*) vm_handle;
return ret;
}
bool pkpy_push_function(pkpy_vm* vm_handle, pkpy_function f, int argc) {
CVM* vm = (CVM*) vm_handle;
NativeFunc nf = NativeFunc(c_function_wrapper, argc, false);
nf.set_userdata(f);
ERRHANDLER_OPEN ERRHANDLER_OPEN
vm->c_data->safe_push(py_var(vm, nf)); PyObject* f_obj = vm->bind(
nullptr,
sig,
nullptr,
c_function_wrapper,
f
);
vm->s_data.push(f_obj);
ERRHANDLER_CLOSE ERRHANDLER_CLOSE
return true; return true;
} }

View File

@ -4,7 +4,6 @@ namespace pkpy{
VM::VM(bool enable_os) : heap(this), enable_os(enable_os) { VM::VM(bool enable_os) : heap(this), enable_os(enable_os) {
this->vm = this; this->vm = this;
for(int i=0; i<REG_COUNT; i++) _reg[i] = nullptr;
_stdout = [](VM* vm, const Str& s) { _stdout = [](VM* vm, const Str& s) {
PK_UNUSED(vm); PK_UNUSED(vm);
std::cout << s; std::cout << s;
@ -957,11 +956,11 @@ void VM::setattr(PyObject* obj, StrName name, PyObject* value){
obj->attr().set(name, value); obj->attr().set(name, value);
} }
PyObject* VM::bind(PyObject* obj, const char* sig, NativeFuncC fn, void* userdata){ PyObject* VM::bind(PyObject* obj, const char* sig, NativeFuncC fn, UserData userdata){
return bind(obj, sig, nullptr, fn, userdata); return bind(obj, sig, nullptr, fn, userdata);
} }
PyObject* VM::bind(PyObject* obj, const char* sig, const char* docstring, NativeFuncC fn, void* userdata){ PyObject* VM::bind(PyObject* obj, const char* sig, const char* docstring, NativeFuncC fn, UserData userdata){
CodeObject_ co; CodeObject_ co;
try{ try{
// fn(a, b, *c, d=1) -> None // fn(a, b, *c, d=1) -> None
@ -978,10 +977,8 @@ PyObject* VM::bind(PyObject* obj, const char* sig, const char* docstring, Native
decl->docstring = Str(docstring).strip(); decl->docstring = Str(docstring).strip();
} }
PyObject* f_obj = VAR(NativeFunc(fn, decl)); PyObject* f_obj = VAR(NativeFunc(fn, decl));
if(userdata != nullptr){
PK_OBJ_GET(NativeFunc, f_obj).set_userdata(userdata); PK_OBJ_GET(NativeFunc, f_obj).set_userdata(userdata);
} if(obj != nullptr) obj->attr().set(decl->code->name, f_obj);
obj->attr().set(decl->code->name, f_obj);
return f_obj; return f_obj;
} }
@ -1000,9 +997,6 @@ void ManagedHeap::mark() {
for(PyObject* obj: vm->s_data) PK_OBJ_MARK(obj); for(PyObject* obj: vm->s_data) PK_OBJ_MARK(obj);
if(_gc_marker_ex) _gc_marker_ex(vm); if(_gc_marker_ex) _gc_marker_ex(vm);
if(vm->_last_exception) PK_OBJ_MARK(vm->_last_exception); if(vm->_last_exception) PK_OBJ_MARK(vm->_last_exception);
for(int i=0; i<vm->REG_COUNT; i++){
if(vm->_reg[i] != nullptr) PK_OBJ_MARK(vm->_reg[i]);
}
} }
Str obj_type_name(VM *vm, Type type){ Str obj_type_name(VM *vm, Type type){