blueloveTH 1 год назад
Родитель
Сommit
5cfd66f8b2
5 измененных файлов с 110 добавлено и 30 удалено
  1. 3 1
      include/pocketpy/vm.h
  2. 25 11
      src/ceval.cpp
  3. 4 0
      src/pocketpy.cpp
  4. 64 17
      src/vm.cpp
  5. 14 1
      tests/43_eval.py

+ 3 - 1
include/pocketpy/vm.h

@@ -156,6 +156,7 @@ public:
     PyObject* __curr_class; 
     PyObject* __cached_object_new;
     std::map<std::string_view, CodeObject_> __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<void(Frame*)> 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);

+ 25 - 11
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<void(Frame*)> 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();

+ 4 - 0
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", "<dynamic>", EXEC_MODE);
+        __dynamic_func_decl = dynamic_co->func_decls.at(0);
+        // initialize builtins
         CodeObject_ code = compile(kPythonLibs_builtins, "<builtins>", EXEC_MODE);
         this->_exec(code, this->builtins);
         code = compile(kPythonLibs__set, "<set>", EXEC_MODE);

+ 64 - 17
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>", EXEC_MODE, true);
+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);
+    }
+
+    PyObject* globals_obj = nullptr;
+    Dict* globals_dict = nullptr;
+
+    NameDict_ locals_closure = nullptr;
+    Dict* locals_dict = nullptr;
+
     if(globals == vm->None){
-        Frame* frame = &vm->callstack.top();
-        vm->_exec(code.get(), frame->_module, frame->_callable, frame->_locals);
-        return;
+        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<DummyInstance>(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);
+            });
+        }
     }
-    vm->check_type(globals, VM::tp_mappingproxy);
-    PyObject* obj = PK_OBJ_GET(MappingProxy, globals).obj;
-    vm->_exec(code, obj);
+
+    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<NameDict>();
+        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>", 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>", 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){

+ 14 - 1
tests/43_eval.py

@@ -38,4 +38,17 @@ def abc():
     exec('a=1', g.__dict__)
     return g.a
 
-assert abc() == 1
+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}