From 5cfd66f8b276211b70608938089e206f6a4f28a1 Mon Sep 17 00:00:00 2001 From: blueloveTH Date: Fri, 10 May 2024 14:03:00 +0800 Subject: [PATCH] impl `py_exec` and `py_eval` --- include/pocketpy/vm.h | 4 ++- src/ceval.cpp | 36 +++++++++++++------ src/pocketpy.cpp | 4 +++ src/vm.cpp | 83 +++++++++++++++++++++++++++++++++---------- tests/43_eval.py | 15 +++++++- 5 files changed, 111 insertions(+), 31 deletions(-) diff --git a/include/pocketpy/vm.h b/include/pocketpy/vm.h index 9af41e66..ff0f3de2 100644 --- a/include/pocketpy/vm.h +++ b/include/pocketpy/vm.h @@ -156,6 +156,7 @@ public: PyObject* __curr_class; PyObject* __cached_object_new; std::map __cached_codes; + FuncDecl_ __dynamic_func_decl; #if PK_ENABLE_PROFILER LineProfiler* _profiler = nullptr; @@ -409,9 +410,10 @@ public: #if PK_DEBUG_CEVAL_STEP void __log_s_data(const char* title = nullptr); #endif + PyObject* __py_exec_internal(const CodeObject_& code, PyObject* globals, PyObject* locals); void __breakpoint(); PyObject* __format_object(PyObject*, Str); - PyObject* __run_top_frame(lightfunction on_will_pop_base_frame = {}); + PyObject* __run_top_frame(); void __pop_frame(); PyObject* __py_generator(Frame&& frame, ArgsView buffer); void __op_unpack_sequence(uint16_t arg); diff --git a/src/ceval.cpp b/src/ceval.cpp index 8249c435..a3b8d661 100644 --- a/src/ceval.cpp +++ b/src/ceval.cpp @@ -80,7 +80,7 @@ bool VM::py_ge(PyObject* _0, PyObject* _1){ #undef BINARY_F_COMPARE -PyObject* VM::__run_top_frame(lightfunction on_will_pop_base_frame){ +PyObject* VM::__run_top_frame(){ Frame* frame = &callstack.top(); const Frame* base_frame = frame; bool need_raise = false; @@ -260,8 +260,17 @@ __NEXT_STEP:; PyObject* _0 = POPX(); if(frame->_callable != nullptr){ PyObject** slot = frame->_locals.try_get_name(_name); - if(slot == nullptr) vm->UnboundLocalError(_name); - *slot = _0; + if(slot != nullptr){ + *slot = _0; // store in locals if possible + }else{ + Function& func = PK_OBJ_GET(Function, frame->_callable); + if(func.decl == __dynamic_func_decl){ + PK_DEBUG_ASSERT(func._closure != nullptr); + func._closure->set(_name, _0); + }else{ + vm->UnboundLocalError(_name); + } + } }else{ frame->f_globals().set(_name, _0); } @@ -307,8 +316,18 @@ __NEXT_STEP:; StrName _name(byte.arg); if(frame->_callable != nullptr){ PyObject** slot = frame->_locals.try_get_name(_name); - if(slot == nullptr) vm->UnboundLocalError(_name); - *slot = PY_NULL; + if(slot != nullptr){ + *slot = PY_NULL; + }else{ + Function& func = PK_OBJ_GET(Function, frame->_callable); + if(func.decl == __dynamic_func_decl){ + PK_DEBUG_ASSERT(func._closure != nullptr); + bool ok = func._closure->del(_name); + if(!ok) vm->UnboundLocalError(_name); + }else{ + vm->UnboundLocalError(_name); + } + } }else{ if(!frame->f_globals().del(_name)) vm->NameError(_name); } @@ -709,12 +728,10 @@ __NEXT_STEP:; } DISPATCH() case OP_RETURN_VALUE:{ PyObject* _0 = byte.arg == BC_NOARG ? POPX() : None; + __pop_frame(); if(frame == base_frame){ // [ frameBase<- ] - if(on_will_pop_base_frame) on_will_pop_base_frame(frame); - __pop_frame(); return _0; }else{ - __pop_frame(); frame = &callstack.top(); PUSH(_0); goto __NEXT_FRAME; @@ -984,9 +1001,6 @@ __NEXT_STEP:; PyObject* e_obj = POPX(); Exception& _e = PK_OBJ_GET(Exception, e_obj); bool is_base_frame_to_be_popped = frame == base_frame; - if(is_base_frame_to_be_popped){ - if(on_will_pop_base_frame) on_will_pop_base_frame(frame); - } __pop_frame(); if(callstack.empty()) throw _e; // propagate to the top level frame = &callstack.top(); diff --git a/src/pocketpy.cpp b/src/pocketpy.cpp index 34add8db..098061a4 100644 --- a/src/pocketpy.cpp +++ b/src/pocketpy.cpp @@ -1609,6 +1609,10 @@ void VM::__post_init_builtin_types(){ _lazy_modules["itertools"] = kPythonLibs_itertools; try{ + // initialize dummy func_decl for exec/eval + CodeObject_ dynamic_co = compile("def _(): pass", "", EXEC_MODE); + __dynamic_func_decl = dynamic_co->func_decls.at(0); + // initialize builtins CodeObject_ code = compile(kPythonLibs_builtins, "", EXEC_MODE); this->_exec(code, this->builtins); code = compile(kPythonLibs__set, "", EXEC_MODE); diff --git a/src/vm.cpp b/src/vm.cpp index 4efb6961..4fb24f90 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -505,29 +505,76 @@ i64 VM::py_hash(PyObject* obj){ } } -void VM::py_exec(std::string_view source, PyObject* globals, PyObject* locals){ - (void)(locals); - CodeObject_ code = vm->compile(source, "", EXEC_MODE, true); - if(globals == vm->None){ - Frame* frame = &vm->callstack.top(); - vm->_exec(code.get(), frame->_module, frame->_callable, frame->_locals); - return; +PyObject* VM::__py_exec_internal(const CodeObject_& code, PyObject* globals, PyObject* locals){ + Frame* frame = &vm->callstack.top(); + + // fast path + if(globals == vm->None && locals == vm->None){ + return vm->_exec(code.get(), frame->_module, frame->_callable, frame->_locals); } - vm->check_type(globals, VM::tp_mappingproxy); - PyObject* obj = PK_OBJ_GET(MappingProxy, globals).obj; - vm->_exec(code, obj); + + PyObject* globals_obj = nullptr; + Dict* globals_dict = nullptr; + + NameDict_ locals_closure = nullptr; + Dict* locals_dict = nullptr; + + if(globals == vm->None){ + globals_obj = frame->_module; + }else{ + if(is_type(globals, VM::tp_mappingproxy)){ + globals_obj = PK_OBJ_GET(MappingProxy, globals).obj; + }else{ + check_compatible_type(globals, VM::tp_dict); + // make a temporary object and copy globals into it + globals_obj = heap.gcnew(VM::tp_object); + globals_obj->_enable_instance_dict(); + globals_dict = &PK_OBJ_GET(Dict, globals); + globals_dict->apply([&](PyObject* k, PyObject* v){ + globals_obj->attr().set(CAST(Str&, k), v); + }); + } + } + + PyObject* retval = nullptr; + + if(locals == vm->None){ + retval = vm->_exec(code, globals_obj); // only globals + }else{ + check_compatible_type(locals, VM::tp_dict); + locals_dict = &PK_OBJ_GET(Dict, locals); + locals_closure = std::make_shared(); + locals_dict->apply([&](PyObject* k, PyObject* v){ + locals_closure->set(CAST(Str&, k), v); + }); + PyObject* _callable = VAR(Function(__dynamic_func_decl, globals_obj, nullptr, locals_closure)); + retval = vm->_exec(code.get(), globals_obj, _callable, vm->s_data._sp); + } + + if(globals_dict){ + globals_dict->clear(); + globals_obj->attr().apply([&](StrName k, PyObject* v){ + globals_dict->set(VAR(k.sv()), v); + }); + } + + if(locals_dict){ + locals_dict->clear(); + locals_closure->apply([&](StrName k, PyObject* v){ + locals_dict->set(VAR(k.sv()), v); + }); + } + return retval; +} + +void VM::py_exec(std::string_view source, PyObject* globals, PyObject* locals){ + CodeObject_ code = vm->compile(source, "", EXEC_MODE, true); + __py_exec_internal(code, globals, locals); } PyObject* VM::py_eval(std::string_view source, PyObject* globals, PyObject* locals){ - (void)(locals); CodeObject_ code = vm->compile(source, "", EVAL_MODE, true); - if(globals == vm->None){ - Frame* frame = &vm->callstack.top(); - return vm->_exec(code.get(), frame->_module, frame->_callable, frame->_locals); - } - vm->check_type(globals, VM::tp_mappingproxy); - PyObject* obj = PK_OBJ_GET(MappingProxy, globals).obj; - return vm->_exec(code, obj); + return __py_exec_internal(code, globals, locals); } PyObject* VM::__format_object(PyObject* obj, Str spec){ diff --git a/tests/43_eval.py b/tests/43_eval.py index c5e91944..991b0745 100644 --- a/tests/43_eval.py +++ b/tests/43_eval.py @@ -38,4 +38,17 @@ def abc(): exec('a=1', g.__dict__) return g.a -assert abc() == 1 \ No newline at end of file +res = abc() +assert (res==1), res + + +# test locals and globals +globals = {'a': 1} +locals = {'a': 1} + +exec('a=2', globals, locals) +assert locals == {'a': 2} +assert globals == {'a': 1} + +exec('a=2', globals) +assert globals == {'a': 2}