Quellcode durchsuchen

Merge pull request #341 from pocketpy/refactor

Refactor `Frame` struct
BLUELOVETH vor 1 Jahr
Ursprung
Commit
e1373766b1

+ 11 - 13
3rd/libhv/src/HttpServer.cpp

@@ -208,34 +208,32 @@ static bool libhv_HttpServer_ws_recv(int argc, py_Ref argv) {
         py_newnone(py_retval());
         return true;
     }
-    py_newtuple(py_retval(), 2);
+    py_Ref data = py_newtuple(py_retval(), 2);
     switch(msg.type) {
         case WsMessageType::onopen: {
             // "onopen", (channel, request)
             assert(msg.request != nullptr);
-            py_newstr(py_tuple_getitem(py_retval(), 0), "onopen");
-            py_Ref args = py_tuple_getitem(py_retval(), 1);
-            py_newtuple(args, 2);
-            py_newint(py_tuple_getitem(args, 0), (py_i64)msg.channel);
-            libhv_HttpRequest_create(py_tuple_getitem(args, 1), msg.request);
+            py_newstr(py_offset(data, 0), "onopen");
+            py_Ref p = py_newtuple(py_offset(data, 1), 2);
+            py_newint(py_offset(p, 0), (py_i64)msg.channel);
+            libhv_HttpRequest_create(py_offset(p, 1), msg.request);
             break;
         }
         case WsMessageType::onclose: {
             // "onclose", channel
-            py_newstr(py_tuple_getitem(py_retval(), 0), "onclose");
-            py_newint(py_tuple_getitem(py_retval(), 1), (py_i64)msg.channel);
+            py_newstr(py_offset(data, 0), "onclose");
+            py_newint(py_offset(data, 1), (py_i64)msg.channel);
             break;
         }
         case WsMessageType::onmessage: {
             // "onmessage", (channel, body)
-            py_newstr(py_tuple_getitem(py_retval(), 0), "onmessage");
-            py_Ref args = py_tuple_getitem(py_retval(), 1);
-            py_newtuple(args, 2);
-            py_newint(py_tuple_getitem(args, 0), (py_i64)msg.channel);
+            py_newstr(py_offset(data, 0), "onmessage");
+            py_Ref p = py_newtuple(py_offset(data, 1), 2);
+            py_newint(py_offset(p, 0), (py_i64)msg.channel);
             c11_sv sv;
             sv.data = msg.body.data();
             sv.size = msg.body.size();
-            py_newstrv(py_tuple_getitem(args, 1), sv);
+            py_newstrv(py_offset(p, 1), sv);
             break;
         }
     }

+ 8 - 9
3rd/libhv/src/WebSocketClient.cpp

@@ -59,7 +59,7 @@ py_Type libhv_register_WebSocketClient(py_GlobalRef mod) {
                     http_headers* p_headers = (http_headers*)ctx;
                     if(!py_checkstr(key)) return false;
                     if(!py_checkstr(value)) return false;
-                    p_headers->operator[](py_tostr(key)) = py_tostr(value);
+                    p_headers->operator[] (py_tostr(key)) = py_tostr(value);
                     return true;
                 },
                 &headers);
@@ -99,22 +99,21 @@ py_Type libhv_register_WebSocketClient(py_GlobalRef mod) {
             py_newnone(py_retval());
             return true;
         } else {
-            py_newtuple(py_retval(), 2);
+            py_Ref p = py_newtuple(py_retval(), 2);
             switch(mq_msg.first) {
                 case WsMessageType::onopen: {
-                    py_newstr(py_tuple_getitem(py_retval(), 0), "onopen");
-                    py_newnone(py_tuple_getitem(py_retval(), 1));
+                    py_newstr(py_offset(p, 0), "onopen");
+                    py_newnone(py_offset(p, 1));
                     break;
                 }
                 case WsMessageType::onclose: {
-                    py_newstr(py_tuple_getitem(py_retval(), 0), "onclose");
-                    py_newnone(py_tuple_getitem(py_retval(), 1));
+                    py_newstr(py_offset(p, 0), "onclose");
+                    py_newnone(py_offset(p, 1));
                     break;
                 }
                 case WsMessageType::onmessage: {
-                    py_newstr(py_tuple_getitem(py_retval(), 0), "onmessage");
-                    py_newstrv(py_tuple_getitem(py_retval(), 1),
-                               {mq_msg.second.data(), (int)mq_msg.second.size()});
+                    py_newstr(py_offset(p, 0), "onmessage");
+                    py_newstrv(py_offset(p, 1), {mq_msg.second.data(), (int)mq_msg.second.size()});
                     break;
                 }
             }

+ 1 - 1
amalgamate.py

@@ -11,7 +11,7 @@ ROOT = 'include/pocketpy'
 PUBLIC_HEADERS = ['config.h', 'export.h', 'linalg.h', 'pocketpy.h']
 
 COPYRIGHT = '''/*
- *  Copyright (c) 2024 blueloveTH
+ *  Copyright (c) 2025 blueloveTH
  *  Distributed Under The MIT License
  *  https://github.com/pocketpy/pocketpy
  */

+ 1 - 1
docs/license.md

@@ -11,7 +11,7 @@ pkpy is licensed under the [MIT License](http://opensource.org/licenses/MIT).
 ```
 MIT License
 
-Copyright (c) 2024 blueloveTH
+Copyright (c) 2025 blueloveTH
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 0 - 7
include/pocketpy/common/strname.h

@@ -1,7 +0,0 @@
-#pragma once
-
-#include <stdint.h>
-#include "pocketpy/common/str.h"
-
-void py_Name__initialize();
-void py_Name__finalize();

+ 18 - 14
include/pocketpy/interpreter/frame.h

@@ -4,10 +4,9 @@
 #include "pocketpy/objects/codeobject.h"
 #include "pocketpy/objects/namedict.h"
 #include "pocketpy/objects/object.h"
-#include "pocketpy/common/strname.h"
 #include "pocketpy/pocketpy.h"
 
-py_TValue* FastLocals__try_get_by_name(py_TValue* locals, const CodeObject* co, py_Name name);
+void FastLocals__to_dict(py_TValue* locals, const CodeObject* co) PY_RETURN;
 NameDict* FastLocals__to_namedict(py_TValue* locals, const CodeObject* co);
 
 typedef struct ValueStack {
@@ -18,7 +17,7 @@ typedef struct ValueStack {
 } ValueStack;
 
 void ValueStack__ctor(ValueStack* self);
-void ValueStack__clear(ValueStack* self);
+void ValueStack__dtor(ValueStack* self);
 
 typedef struct UnwindTarget {
     struct UnwindTarget* next;
@@ -31,28 +30,33 @@ void UnwindTarget__delete(UnwindTarget* self);
 
 typedef struct Frame {
     struct Frame* f_back;
-    const Bytecode* ip;
     const CodeObject* co;
+    py_StackRef p0;  // unwinding base
     py_GlobalRef module;
-    py_StackRef p0;      // unwinding base
-    py_StackRef locals;  // locals base
-    bool has_function;   // is p0 a function?
-    bool is_dynamic;     // is dynamic frame?
+    py_Ref globals;  // a module object or a dict object
+    py_Ref locals;
+    bool is_locals_special;
+    int ip;
     UnwindTarget* uw_list;
 } Frame;
 
 Frame* Frame__new(const CodeObject* co,
-                  py_GlobalRef module,
                   py_StackRef p0,
-                  py_StackRef locals,
-                  bool has_function);
+                  py_GlobalRef module,
+                  py_Ref globals,
+                  py_Ref locals,
+                  bool is_locals_special);
 void Frame__delete(Frame* self);
 
-int Frame__ip(const Frame* self);
 int Frame__lineno(const Frame* self);
 int Frame__iblock(const Frame* self);
-py_TValue* Frame__f_locals_try_get(Frame* self, py_Name name);
-py_TValue* Frame__f_closure_try_get(Frame* self, py_Name name);
+
+int Frame__getglobal(Frame* self, py_Name name) PY_RAISE PY_RETURN;
+bool Frame__setglobal(Frame* self, py_Name name, py_TValue* val) PY_RAISE;
+int Frame__delglobal(Frame* self, py_Name name) PY_RAISE;
+
+py_Ref Frame__getclosure(Frame* self, py_Name name);
+py_StackRef Frame__getlocal_noproxy(Frame* self, py_Name name);
 
 int Frame__prepare_jump_exception_handler(Frame* self, ValueStack*);
 

+ 18 - 0
include/pocketpy/interpreter/name.h

@@ -0,0 +1,18 @@
+#pragma once
+
+#include "pocketpy/objects/base.h"
+#include "pocketpy/common/smallmap.h"
+
+typedef struct {
+    char* data;     // null-terminated data
+    int size;       // size of the data excluding the null-terminator
+    py_TValue obj;  // cached `str` object (lazy initialized)
+} RInternedEntry;
+
+typedef struct {
+    c11_smallmap_s2n interned;
+    c11_vector /* T=RInternedEntry */ r_interned;
+} InternedNames;
+
+void InternedNames__ctor(InternedNames* self);
+void InternedNames__dtor(InternedNames* self);

+ 4 - 3
include/pocketpy/interpreter/vm.h

@@ -7,6 +7,7 @@
 #include "pocketpy/interpreter/frame.h"
 #include "pocketpy/interpreter/modules.h"
 #include "pocketpy/interpreter/typeinfo.h"
+#include "pocketpy/interpreter/name.h"
 
 // TODO:
 // 1. __eq__ and __ne__ fallbacks
@@ -41,6 +42,7 @@ typedef struct VM {
     py_StackRef __curr_function;
     py_TValue __vectorcall_buffer[PK_MAX_CO_VARNAMES];
 
+    InternedNames names;
     FixedMemoryPool pool_frame;
     ManagedHeap heap;
     ValueStack stack;  // put `stack` at the end for better cache locality
@@ -94,6 +96,7 @@ bool pk_loadmethod(py_StackRef self, py_Name name);
 bool pk_callmagic(py_Name name, int argc, py_Ref argv);
 
 bool pk_exec(CodeObject* co, py_Ref module);
+bool pk_execdyn(CodeObject* co, py_Ref module, py_Ref globals, py_Ref locals);
 
 /// Assumes [a, b] are on the stack, performs a binary op.
 /// The result is stored in `self->last_retval`.
@@ -128,11 +131,9 @@ py_Type pk_staticmethod__register();
 py_Type pk_classmethod__register();
 py_Type pk_generator__register();
 py_Type pk_namedict__register();
-py_Type pk_locals__register();
 py_Type pk_code__register();
 
 py_TValue pk_builtins__register();
 
 /* mappingproxy */
-void pk_mappingproxy__namedict(py_Ref out, py_Ref object);
-void pk_mappingproxy__locals(py_Ref out, Frame* frame);
+void pk_mappingproxy__namedict(py_Ref out, py_Ref object);

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

@@ -19,5 +19,6 @@ typedef struct py_TValue {
         PyObject* _obj;
         c11_vec2 _vec2;
         c11_vec2i _vec2i;
+        void* _ptr;
     };
 } py_TValue;

+ 6 - 6
include/pocketpy/objects/codeobject.h

@@ -23,7 +23,6 @@ typedef enum FuncType {
 typedef enum NameScope {
     NAME_LOCAL,
     NAME_GLOBAL,
-    NAME_GLOBAL_UNKNOWN,
 } NameScope;
 
 typedef enum CodeBlockType {
@@ -128,11 +127,12 @@ void FuncDecl__gc_mark(const FuncDecl* self);
 // runtime function
 typedef struct Function {
     FuncDecl_ decl;
-    py_TValue module;      // weak ref
-    PyObject* clazz;       // weak ref
-    NameDict* closure;     // strong ref
-    py_CFunction cfunc;    // wrapped C function
+    py_GlobalRef module;    // maybe NULL, weak ref
+    py_Ref globals;         // maybe NULL, strong ref
+    NameDict* closure;      // maybe NULL, strong ref
+    PyObject* clazz;        // weak ref; for super()
+    py_CFunction cfunc;     // wrapped C function; for decl-based binding
 } Function;
 
-void Function__ctor(Function* self, FuncDecl_ decl, py_TValue* module);
+void Function__ctor(Function* self, FuncDecl_ decl, py_GlobalRef module, py_Ref globals);
 void Function__dtor(Function* self);

+ 0 - 4
include/pocketpy/objects/error.h

@@ -1,10 +1,6 @@
 #pragma once
 
-#include "pocketpy/common/str.h"
-#include "pocketpy/common/strname.h"
-#include "pocketpy/objects/codeobject.h"
 #include "pocketpy/objects/sourcedata.h"
-#include "pocketpy/objects/object.h"
 #include "pocketpy/pocketpy.h"
 
 typedef struct{

+ 4 - 3
include/pocketpy/pocketpy.h

@@ -135,7 +135,6 @@ PK_API bool py_compile(const char* source,
 /// Python equivalent to `globals()`.
 PK_API void py_newglobals(py_OutRef);
 /// Python equivalent to `locals()`.
-/// @return a temporary object, which expires on the associated function return.
 PK_API void py_newlocals(py_OutRef);
 
 /************* Values Creation *************/
@@ -176,7 +175,7 @@ PK_API void py_newellipsis(py_OutRef);
 PK_API void py_newnil(py_OutRef);
 /// Create a `tuple` with `n` UNINITIALIZED elements.
 /// You should initialize all elements before using it.
-PK_API void py_newtuple(py_OutRef, int n);
+PK_API py_ObjectRef py_newtuple(py_OutRef, int n);
 /// Create an empty `list`.
 PK_API void py_newlist(py_OutRef);
 /// Create a `list` with `n` UNINITIALIZED elements.
@@ -204,6 +203,8 @@ PK_API void py_newboundmethod(py_OutRef out, py_Ref self, py_Ref func);
 PK_API py_Name py_name(const char*);
 /// Convert a name to a null-terminated string.
 PK_API const char* py_name2str(py_Name);
+/// Convert a name to a python `str` object with cache.
+PK_API py_GlobalRef py_name2ref(py_Name);
 /// Convert a `c11_sv` to a name.
 PK_API py_Name py_namev(c11_sv);
 /// Convert a name to a `c11_sv`.
@@ -535,7 +536,7 @@ PK_API void py_clearexc(py_StackRef p0);
 #define AttributeError(self, n)                                                                    \
     py_exception(tp_AttributeError, "'%t' object has no attribute '%n'", (self)->type, (n))
 #define UnboundLocalError(n)                                                                       \
-    py_exception(tp_UnboundLocalError, "local variable '%n' referenced before assignment", (n))
+    py_exception(tp_UnboundLocalError, "cannot access local variable '%n' where it is not associated with a value", (n))
 
 PK_API bool StopIteration() PY_RAISE;
 PK_API bool KeyError(py_Ref key) PY_RAISE;

+ 1 - 0
pyrightconfig.json

@@ -3,5 +3,6 @@
     "reportMissingModuleSource": "none",
     "reportArgumentType": "none",
     "reportWildcardImportFromLibrary": "none",
+    "reportRedeclaration": "none",
     "pythonVersion": "3.12"
 }

+ 0 - 64
src/common/strname.c

@@ -1,64 +0,0 @@
-#include "pocketpy/common/strname.h"
-#include "pocketpy/common/smallmap.h"
-#include "pocketpy/common/utils.h"
-#include "pocketpy/common/vector.h"
-#include "pocketpy/pocketpy.h"
-
-#include <stdio.h>
-
-// TODO: use a more efficient data structure
-static c11_smallmap_s2n _interned;
-static c11_vector /*T=char* */ _r_interned;
-
-void py_Name__initialize() {
-    c11_smallmap_s2n__ctor(&_interned);
-    for(int i = 0; i < _r_interned.length; i++) {
-        PK_FREE(c11__at(char*, &_r_interned, i));
-    }
-    c11_vector__ctor(&_r_interned, sizeof(c11_sv));
-
-#define MAGIC_METHOD(x)                                                                            \
-    if(x != py_name(#x)) abort();
-#include "pocketpy/xmacros/magics.h"
-#undef MAGIC_METHOD
-}
-
-void py_Name__finalize() {
-    // free all char*
-    for(int i = 0; i < _r_interned.length; i++) {
-        PK_FREE(c11__getitem(char*, &_r_interned, i));
-    }
-    c11_smallmap_s2n__dtor(&_interned);
-    c11_vector__dtor(&_r_interned);
-}
-
-py_Name py_name(const char* name) { return py_namev((c11_sv){name, strlen(name)}); }
-
-py_Name py_namev(c11_sv name) {
-    // TODO: PK_GLOBAL_SCOPE_LOCK()
-    uint16_t index = c11_smallmap_s2n__get(&_interned, name, 0);
-    if(index != 0) return index;
-    // generate new index
-    if(_interned.length > 65530) c11__abort("py_Name index overflow");
-    // NOTE: we must allocate the string in the heap so iterators are not invalidated
-    char* p = PK_MALLOC(name.size + 1);
-    memcpy(p, name.data, name.size);
-    p[name.size] = '\0';
-    c11_vector__push(char*, &_r_interned, p);
-    index = _r_interned.length;  // 1-based
-    // save to _interned
-    c11_smallmap_s2n__set(&_interned, (c11_sv){p, name.size}, index);
-    assert(_interned.length == _r_interned.length);
-    return index;
-}
-
-const char* py_name2str(py_Name index) {
-    assert(index > 0 && index <= _interned.length);
-    return c11__getitem(char*, &_r_interned, index - 1);
-}
-
-c11_sv py_name2sv(py_Name index) {
-    assert(index > 0 && index <= _interned.length);
-    const char* p = py_name2str(index);
-    return (c11_sv){p, strlen(p)};
-}

+ 24 - 18
src/compiler/compiler.c

@@ -102,17 +102,20 @@ void NameExpr__emit_(Expr* self_, Ctx* ctx) {
     NameExpr* self = (NameExpr*)self_;
     int index = c11_smallmap_n2i__get(&ctx->co->varnames_inv, self->name, -1);
     if(self->scope == NAME_LOCAL && index >= 0) {
+        // we know this is a local variable
         Ctx__emit_(ctx, OP_LOAD_FAST, index, self->line);
     } else {
         Opcode op = ctx->level <= 1 ? OP_LOAD_GLOBAL : OP_LOAD_NONLOCAL;
-        if(ctx->is_compiling_class && self->scope == NAME_GLOBAL) {
-            // if we are compiling a class, we should use OP_LOAD_ATTR_GLOBAL instead of
-            // OP_LOAD_GLOBAL this supports @property.setter
-            op = OP_LOAD_CLASS_GLOBAL;
-            // exec()/eval() won't work with OP_LOAD_ATTR_GLOBAL in class body
-        } else {
-            // we cannot determine the scope when calling exec()/eval()
-            if(self->scope == NAME_GLOBAL_UNKNOWN) op = OP_LOAD_NAME;
+        if(self->scope == NAME_GLOBAL) {
+            if(ctx->co->src->is_dynamic) {
+                op = OP_LOAD_NAME;
+            } else {
+                if(ctx->is_compiling_class) {
+                    // if we are compiling a class, we should use `OP_LOAD_CLASS_GLOBAL`
+                    // this is for @property.setter
+                    op = OP_LOAD_CLASS_GLOBAL;
+                }
+            }
         }
         Ctx__emit_(ctx, op, self->name, self->line);
     }
@@ -124,8 +127,11 @@ bool NameExpr__emit_del(Expr* self_, Ctx* ctx) {
         case NAME_LOCAL:
             Ctx__emit_(ctx, OP_DELETE_FAST, Ctx__add_varname(ctx, self->name), self->line);
             break;
-        case NAME_GLOBAL: Ctx__emit_(ctx, OP_DELETE_GLOBAL, self->name, self->line); break;
-        case NAME_GLOBAL_UNKNOWN: Ctx__emit_(ctx, OP_DELETE_NAME, self->name, self->line); break;
+        case NAME_GLOBAL: {
+            Opcode op = ctx->co->src->is_dynamic ? OP_DELETE_NAME : OP_DELETE_GLOBAL;
+            Ctx__emit_(ctx, op, self->name, self->line);
+            break;
+        }
         default: c11__unreachable();
     }
     return true;
@@ -1219,8 +1225,10 @@ static int Ctx__add_const(Ctx* self, py_Ref v) {
 static void Ctx__emit_store_name(Ctx* self, NameScope scope, py_Name name, int line) {
     switch(scope) {
         case NAME_LOCAL: Ctx__emit_(self, OP_STORE_FAST, Ctx__add_varname(self, name), line); break;
-        case NAME_GLOBAL: Ctx__emit_(self, OP_STORE_GLOBAL, name, line); break;
-        case NAME_GLOBAL_UNKNOWN: Ctx__emit_(self, OP_STORE_NAME, name, line); break;
+        case NAME_GLOBAL: {
+            Opcode op = self->co->src->is_dynamic ? OP_STORE_NAME : OP_STORE_GLOBAL;
+            Ctx__emit_(self, op, name, line);
+        } break;
         default: c11__unreachable();
     }
 }
@@ -1331,9 +1339,7 @@ static void Compiler__dtor(Compiler* self) {
     if((err = B)) return err
 
 static NameScope name_scope(Compiler* self) {
-    NameScope s = self->contexts.length > 1 ? NAME_LOCAL : NAME_GLOBAL;
-    if(self->src->is_dynamic && s == NAME_GLOBAL) s = NAME_GLOBAL_UNKNOWN;
-    return s;
+    return self->contexts.length > 1 ? NAME_LOCAL : NAME_GLOBAL;
 }
 
 Error* SyntaxError(Compiler* self, const char* fmt, ...) {
@@ -1720,7 +1726,7 @@ static Error* exprName(Compiler* self) {
     NameScope scope = name_scope(self);
     // promote this name to global scope if needed
     if(c11_smallmap_n2i__contains(&ctx()->global_names, name)) {
-        if(scope == NAME_GLOBAL_UNKNOWN) return SyntaxError(self, "cannot use global keyword here");
+        if(self->src->is_dynamic) return SyntaxError(self, "cannot use global keyword here");
         scope = NAME_GLOBAL;
     }
     NameExpr* e = NameExpr__new(prev()->line, name, scope);
@@ -2185,9 +2191,9 @@ static Error* read_literal(Compiler* self, py_Ref out) {
                 if(curr()->type == TK_RPAREN) break;
             }
             consume(TK_RPAREN);
-            py_newtuple(out, count);
+            py_Ref p = py_newtuple(out, count);
             for(int i = 0; i < count; i++) {
-                py_tuple_setitem(out, i, &cpnts[i]);
+                p[i] = cpnts[i];
             }
             return NULL;
         }

+ 115 - 98
src/interpreter/ceval.c

@@ -33,7 +33,7 @@ static bool stack_format_object(VM* self, c11_sv spec);
     } while(0)
 #define DISPATCH_JUMP_ABSOLUTE(__target)                                                           \
     do {                                                                                           \
-        frame->ip = c11__at(Bytecode, &frame->co->codes, __target);                                \
+        frame->ip = __target;                                                                      \
         goto __NEXT_STEP;                                                                          \
     } while(0)
 
@@ -86,15 +86,18 @@ static bool unpack_dict_to_buffer(py_Ref key, py_Ref val, void* ctx) {
 
 FrameResult VM__run_top_frame(VM* self) {
     Frame* frame = self->top_frame;
+    Bytecode* codes;
+
     const Frame* base_frame = frame;
 
     while(true) {
         Bytecode byte;
     __NEXT_FRAME:
+        codes = frame->co->codes.data;
         frame->ip++;
 
     __NEXT_STEP:
-        byte = *frame->ip;
+        byte = codes[frame->ip];
 
 #ifndef NDEBUG
         pk_print_stack(self, frame, byte);
@@ -176,8 +179,12 @@ FrameResult VM__run_top_frame(VM* self) {
                 CHECK_STACK_OVERFLOW();
                 FuncDecl_ decl = c11__getitem(FuncDecl_, &frame->co->func_decls, byte.arg);
                 Function* ud = py_newobject(SP(), tp_function, 0, sizeof(Function));
-                Function__ctor(ud, decl, frame->module);
+                Function__ctor(ud, decl, frame->module, frame->globals);
                 if(decl->nested) {
+                    if(frame->is_locals_special) {
+                        RuntimeError("cannot create closure from special locals");
+                        goto __ERROR;
+                    }
                     ud->closure = FastLocals__to_namedict(frame->locals, frame->co);
                     py_Name name = py_name(decl->code.name->data);
                     // capture itself to allow recursion
@@ -191,6 +198,7 @@ FrameResult VM__run_top_frame(VM* self) {
                 DISPATCH();
                 /*****************************************/
             case OP_LOAD_FAST: {
+                assert(!frame->is_locals_special);
                 PUSH(&frame->locals[byte.arg]);
                 if(py_isnil(TOP())) {
                     py_Name name = c11__getitem(uint16_t, &frame->co->varnames, byte.arg);
@@ -200,38 +208,45 @@ FrameResult VM__run_top_frame(VM* self) {
                 DISPATCH();
             }
             case OP_LOAD_NAME: {
-                assert(frame->is_dynamic);
+                assert(frame->is_locals_special);
                 py_Name name = byte.arg;
-                py_TValue* tmp;
-                py_newstr(SP()++, py_name2str(name));
                 // locals
-                if(!py_isnone(&frame->p0[1])) {
-                    if(py_getitem(&frame->p0[1], TOP())) {
-                        py_assign(TOP(), py_retval());
-                        DISPATCH();
-                    } else {
-                        if(py_matchexc(tp_KeyError)) {
-                            py_clearexc(NULL);
-                        } else {
+                switch(frame->locals->type) {
+                    case tp_locals: {
+                        Frame* noproxy = frame->locals->_ptr;
+                        py_Ref slot = Frame__getlocal_noproxy(noproxy, name);
+                        if(slot == NULL) break;
+                        if(py_isnil(slot)) {
+                            UnboundLocalError(name);
                             goto __ERROR;
                         }
+                        PUSH(slot);
+                        DISPATCH();
                     }
+                    case tp_dict: {
+                        int res = py_dict_getitem(frame->locals, py_name2ref(name));
+                        if(res == 1) {
+                            PUSH(&self->last_retval);
+                            DISPATCH();
+                        }
+                        if(res == 0) break;
+                        assert(res == -1);
+                        goto __ERROR;
+                    }
+                    case tp_nil: break;
+                    default: c11__unreachable();
                 }
                 // globals
-                if(py_getitem(&frame->p0[0], TOP())) {
-                    py_assign(TOP(), py_retval());
+                int res = Frame__getglobal(frame, name);
+                if(res == 1) {
+                    PUSH(&self->last_retval);
                     DISPATCH();
-                } else {
-                    if(py_matchexc(tp_KeyError)) {
-                        py_clearexc(NULL);
-                    } else {
-                        goto __ERROR;
-                    }
                 }
+                if(res == -1) goto __ERROR;
                 // builtins
-                tmp = py_getdict(&self->builtins, name);
+                py_Ref tmp = py_getdict(&self->builtins, name);
                 if(tmp != NULL) {
-                    py_assign(TOP(), tmp);
+                    PUSH(tmp);
                     DISPATCH();
                 }
                 NameError(name);
@@ -239,16 +254,18 @@ FrameResult VM__run_top_frame(VM* self) {
             }
             case OP_LOAD_NONLOCAL: {
                 py_Name name = byte.arg;
-                py_Ref tmp = Frame__f_closure_try_get(frame, name);
+                py_Ref tmp = Frame__getclosure(frame, name);
                 if(tmp != NULL) {
                     PUSH(tmp);
                     DISPATCH();
                 }
-                tmp = py_getdict(frame->module, name);
-                if(tmp != NULL) {
-                    PUSH(tmp);
+                int res = Frame__getglobal(frame, name);
+                if(res == 1) {
+                    PUSH(&self->last_retval);
                     DISPATCH();
                 }
+                if(res == -1) goto __ERROR;
+
                 tmp = py_getdict(&self->builtins, name);
                 if(tmp != NULL) {
                     PUSH(tmp);
@@ -259,12 +276,13 @@ FrameResult VM__run_top_frame(VM* self) {
             }
             case OP_LOAD_GLOBAL: {
                 py_Name name = byte.arg;
-                py_Ref tmp = py_getdict(frame->module, name);
-                if(tmp != NULL) {
-                    PUSH(tmp);
+                int res = Frame__getglobal(frame, name);
+                if(res == 1) {
+                    PUSH(&self->last_retval);
                     DISPATCH();
                 }
-                tmp = py_getdict(&self->builtins, name);
+                if(res == -1) goto __ERROR;
+                py_Ref tmp = py_getdict(&self->builtins, name);
                 if(tmp != NULL) {
                     PUSH(tmp);
                     DISPATCH();
@@ -289,11 +307,12 @@ FrameResult VM__run_top_frame(VM* self) {
                     DISPATCH();
                 }
                 // load global if attribute not found
-                tmp = py_getdict(frame->module, name);
-                if(tmp) {
-                    PUSH(tmp);
+                int res = Frame__getglobal(frame, name);
+                if(res == 1) {
+                    PUSH(&self->last_retval);
                     DISPATCH();
                 }
+                if(res == -1) goto __ERROR;
                 tmp = py_getdict(&self->builtins, name);
                 if(tmp) {
                     PUSH(tmp);
@@ -335,41 +354,41 @@ FrameResult VM__run_top_frame(VM* self) {
                 TypeError("'%t' object is not subscriptable", SECOND()->type);
                 goto __ERROR;
             }
-            case OP_STORE_FAST: frame->locals[byte.arg] = POPX(); DISPATCH();
+            case OP_STORE_FAST: {
+                assert(!frame->is_locals_special);
+                frame->locals[byte.arg] = POPX();
+                DISPATCH();
+            }
             case OP_STORE_NAME: {
-                assert(frame->is_dynamic);
+                assert(frame->is_locals_special);
                 py_Name name = byte.arg;
-                py_newstr(SP()++, py_name2str(name));
-                // [value, name]
-                if(!py_isnone(&frame->p0[1])) {
-                    // locals
-                    if(py_setitem(&frame->p0[1], TOP(), SECOND())) {
-                        STACK_SHRINK(2);
-                        DISPATCH();
-                    } else {
-                        if(py_matchexc(tp_KeyError)) {
-                            py_clearexc(NULL);
-                            NameError(name);
+                switch(frame->locals->type) {
+                    case tp_locals: {
+                        Frame* noproxy = frame->locals->_ptr;
+                        py_Ref slot = Frame__getlocal_noproxy(noproxy, name);
+                        if(slot == NULL) {
+                            UnboundLocalError(name);
+                            goto __ERROR;
                         }
-                        goto __ERROR;
+                        *slot = POPX();
+                        DISPATCH();
                     }
-                } else {
-                    // globals
-                    if(py_setitem(&frame->p0[0], TOP(), SECOND())) {
-                        STACK_SHRINK(2);
+                    case tp_dict: {
+                        if(!py_dict_setitem(frame->locals, py_name2ref(name), TOP())) goto __ERROR;
+                        POP();
+                        DISPATCH();
+                    }
+                    case tp_nil: {
+                        // globals
+                        if(!Frame__setglobal(frame, name, TOP())) goto __ERROR;
+                        POP();
                         DISPATCH();
-                    } else {
-                        if(py_matchexc(tp_KeyError)) {
-                            py_clearexc(NULL);
-                            NameError(name);
-                        }
-                        goto __ERROR;
                     }
+                    default: c11__unreachable();
                 }
-                DISPATCH();
             }
             case OP_STORE_GLOBAL: {
-                py_setdict(frame->module, byte.arg, TOP());
+                if(!Frame__setglobal(frame, byte.arg, TOP())) goto __ERROR;
                 POP();
                 DISPATCH();
             }
@@ -397,6 +416,7 @@ FrameResult VM__run_top_frame(VM* self) {
                 goto __ERROR;
             }
             case OP_DELETE_FAST: {
+                assert(!frame->is_locals_special);
                 py_Ref tmp = &frame->locals[byte.arg];
                 if(py_isnil(tmp)) {
                     py_Name name = c11__getitem(py_Name, &frame->co->varnames, byte.arg);
@@ -407,44 +427,42 @@ FrameResult VM__run_top_frame(VM* self) {
                 DISPATCH();
             }
             case OP_DELETE_NAME: {
-                assert(frame->is_dynamic);
+                assert(frame->is_locals_special);
                 py_Name name = byte.arg;
-                py_newstr(SP()++, py_name2str(name));
-                if(!py_isnone(&frame->p0[1])) {
-                    // locals
-                    if(py_delitem(&frame->p0[1], TOP())) {
-                        POP();
-                        DISPATCH();
-                    } else {
-                        if(py_matchexc(tp_KeyError)) {
-                            py_clearexc(NULL);
-                            NameError(name);
+                switch(frame->locals->type) {
+                    case tp_locals: {
+                        Frame* noproxy = frame->locals->_ptr;
+                        py_Ref slot = Frame__getlocal_noproxy(noproxy, name);
+                        if(slot == NULL || py_isnil(slot)) {
+                            UnboundLocalError(name);
+                            goto __ERROR;
                         }
+                        py_newnil(slot);
+                        DISPATCH();
+                    }
+                    case tp_dict: {
+                        int res = py_dict_delitem(frame->locals, py_name2ref(name));
+                        if(res == 1) DISPATCH();
+                        if(res == 0) UnboundLocalError(name);
                         goto __ERROR;
                     }
-                } else {
-                    // globals
-                    if(py_delitem(&frame->p0[0], TOP())) {
-                        POP();
-                        DISPATCH();
-                    } else {
-                        if(py_matchexc(tp_KeyError)) {
-                            py_clearexc(NULL);
-                            NameError(name);
-                        }
+                    case tp_nil: {
+                        // globals
+                        int res = Frame__delglobal(frame, name);
+                        if(res == 1) DISPATCH();
+                        if(res == 0) NameError(name);
                         goto __ERROR;
                     }
+                    default: c11__unreachable();
                 }
-                DISPATCH();
             }
             case OP_DELETE_GLOBAL: {
                 py_Name name = byte.arg;
-                bool ok = py_deldict(frame->module, name);
-                if(!ok) {
-                    NameError(name);
-                    goto __ERROR;
-                }
-                DISPATCH();
+                int res = Frame__delglobal(frame, name);
+                if(res == 1) DISPATCH();
+                if(res == -1) goto __ERROR;
+                NameError(name);
+                goto __ERROR;
             }
 
             case OP_DELETE_ATTR: {
@@ -493,11 +511,10 @@ FrameResult VM__run_top_frame(VM* self) {
             }
             case OP_BUILD_TUPLE: {
                 py_TValue tmp;
-                py_newtuple(&tmp, byte.arg);
+                py_Ref p = py_newtuple(&tmp, byte.arg);
                 py_TValue* begin = SP() - byte.arg;
-                for(int i = 0; i < byte.arg; i++) {
-                    py_tuple_setitem(&tmp, i, begin + i);
-                }
+                for(int i = 0; i < byte.arg; i++)
+                    p[i] = begin[i];
                 SP() = begin;
                 PUSH(&tmp);
                 DISPATCH();
@@ -860,7 +877,7 @@ FrameResult VM__run_top_frame(VM* self) {
                             ImportError("cannot import name '%n'", name);
                             goto __ERROR;
                         } else {
-                            py_setdict(frame->module, name, value);
+                            if(!Frame__setglobal(frame, name, value)) goto __ERROR;
                         }
                     }
                 } else {
@@ -869,7 +886,7 @@ FrameResult VM__run_top_frame(VM* self) {
                         if(!kv->key) continue;
                         c11_sv name = py_name2sv(kv->key);
                         if(name.size == 0 || name.data[0] == '_') continue;
-                        py_setdict(frame->module, kv->key, &kv->value);
+                        if(!Frame__setglobal(frame, kv->key, &kv->value)) goto __ERROR;
                     }
                 }
                 POP();
@@ -998,8 +1015,7 @@ FrameResult VM__run_top_frame(VM* self) {
             case OP_END_CLASS: {
                 // [cls or decorated]
                 py_Name name = byte.arg;
-                // set into f_globals
-                py_setdict(frame->module, name, TOP());
+                if(!Frame__setglobal(frame, name, TOP())) goto __ERROR;
 
                 if(py_istype(TOP(), tp_type)) {
                     // call on_end_subclass
@@ -1166,7 +1182,7 @@ FrameResult VM__run_top_frame(VM* self) {
         py_BaseException__stpush(&self->curr_exception,
                                  frame->co->src,
                                  Frame__lineno(frame),
-                                 frame->has_function ? frame->co->name->data : NULL);
+                                 !frame->is_locals_special ? frame->co->name->data : NULL);
     __ERROR_RE_RAISE:
         do {
         } while(0);
@@ -1183,6 +1199,7 @@ FrameResult VM__run_top_frame(VM* self) {
                 return RES_ERROR;
             }
             frame = self->top_frame;
+            codes = frame->co->codes.data;
             goto __ERROR;
         }
     }

+ 79 - 31
src/interpreter/frame.c

@@ -11,19 +11,28 @@ void ValueStack__ctor(ValueStack* self) {
     self->end = self->begin + PK_VM_STACK_SIZE;
 }
 
-void ValueStack__clear(ValueStack* self) { self->sp = self->begin; }
+void ValueStack__dtor(ValueStack* self) { self->sp = self->begin; }
 
-py_TValue* FastLocals__try_get_by_name(py_TValue* locals, const CodeObject* co, py_Name name) {
-    int index = c11_smallmap_n2i__get(&co->varnames_inv, name, -1);
-    if(index == -1) return NULL;
-    return &locals[index];
+void FastLocals__to_dict(py_TValue* locals, const CodeObject* co) {
+    py_StackRef dict = py_pushtmp();
+    py_newdict(dict);
+    c11__foreach(c11_smallmap_n2i_KV, &co->varnames_inv, entry) {
+        py_TValue* value = &locals[entry->value];
+        if(!py_isnil(value)) {
+            bool ok = py_dict_setitem(dict, py_name2ref(entry->key), value);
+            assert(ok);
+            (void)ok;
+        }
+    }
+    py_assign(py_retval(), dict);
+    py_pop();
 }
 
 NameDict* FastLocals__to_namedict(py_TValue* locals, const CodeObject* co) {
     NameDict* dict = NameDict__new();
     c11__foreach(c11_smallmap_n2i_KV, &co->varnames_inv, entry) {
         py_TValue value = locals[entry->value];
-        if(!py_isnil(&value)) { NameDict__set(dict, entry->key, value); }
+        if(!py_isnil(&value)) NameDict__set(dict, entry->key, value);
     }
     return dict;
 }
@@ -39,19 +48,25 @@ UnwindTarget* UnwindTarget__new(UnwindTarget* next, int iblock, int offset) {
 void UnwindTarget__delete(UnwindTarget* self) { PK_FREE(self); }
 
 Frame* Frame__new(const CodeObject* co,
-                  py_GlobalRef module,
                   py_StackRef p0,
-                  py_StackRef locals,
-                  bool has_function) {
+                  py_GlobalRef module,
+                  py_Ref globals,
+                  py_Ref locals,
+                  bool is_locals_special) {
+    assert(module->type == tp_module);
+    assert(globals->type == tp_module || globals->type == tp_dict);
+    if(is_locals_special) {
+        assert(locals->type == tp_nil || locals->type == tp_locals || locals->type == tp_dict);
+    }
     Frame* self = FixedMemoryPool__alloc(&pk_current_vm->pool_frame);
     self->f_back = NULL;
-    self->ip = (Bytecode*)co->codes.data - 1;
     self->co = co;
-    self->module = module;
     self->p0 = p0;
+    self->module = module;
+    self->globals = globals;
     self->locals = locals;
-    self->has_function = has_function;
-    self->is_dynamic = co->src->is_dynamic;
+    self->is_locals_special = is_locals_special;
+    self->ip = -1;
     self->uw_list = NULL;
     return self;
 }
@@ -75,7 +90,7 @@ int Frame__prepare_jump_exception_handler(Frame* self, ValueStack* _s) {
     }
     if(iblock < 0) return -1;
     UnwindTarget* uw = Frame__find_unwind_target(self, iblock);
-    _s->sp = (self->locals + uw->offset);  // unwind the stack
+    _s->sp = (self->p0 + uw->offset);  // unwind the stack
     return c11__at(CodeBlock, &self->co->blocks, iblock)->end;
 }
 
@@ -91,38 +106,71 @@ void Frame__set_unwind_target(Frame* self, py_TValue* sp) {
     int iblock = Frame__iblock(self);
     UnwindTarget* existing = Frame__find_unwind_target(self, iblock);
     if(existing) {
-        existing->offset = sp - self->locals;
+        existing->offset = sp - self->p0;
     } else {
         UnwindTarget* prev = self->uw_list;
-        self->uw_list = UnwindTarget__new(prev, iblock, sp - self->locals);
+        self->uw_list = UnwindTarget__new(prev, iblock, sp - self->p0);
     }
 }
 
 void Frame__gc_mark(Frame* self) {
-    pk__mark_value(self->module);
+    pk__mark_value(self->globals);
+    if(self->is_locals_special) pk__mark_value(self->locals);
     CodeObject__gc_mark(self->co);
 }
 
-py_TValue* Frame__f_closure_try_get(Frame* self, py_Name name) {
-    if(!self->has_function) return NULL;
-    Function* ud = py_touserdata(self->p0);
-    if(ud->closure == NULL) return NULL;
-    return NameDict__try_get(ud->closure, name);
-}
-
-int Frame__ip(const Frame* self) { return self->ip - (Bytecode*)self->co->codes.data; }
-
 int Frame__lineno(const Frame* self) {
-    int ip = Frame__ip(self);
+    int ip = self->ip;
     return c11__getitem(BytecodeEx, &self->co->codes_ex, ip).lineno;
 }
 
 int Frame__iblock(const Frame* self) {
-    int ip = Frame__ip(self);
+    int ip = self->ip;
     return c11__getitem(BytecodeEx, &self->co->codes_ex, ip).iblock;
 }
 
-py_TValue* Frame__f_locals_try_get(Frame* self, py_Name name) {
-    assert(!self->is_dynamic);
-    return FastLocals__try_get_by_name(self->locals, self->co, name);
+int Frame__getglobal(Frame* self, py_Name name) {
+    if(self->globals->type == tp_module) {
+        py_ItemRef item = py_getdict(self->globals, name);
+        if(item != NULL) {
+            py_assign(py_retval(), item);
+            return 1;
+        }
+        return 0;
+    } else {
+        return py_dict_getitem(self->globals, py_name2ref(name));
+    }
+}
+
+bool Frame__setglobal(Frame* self, py_Name name, py_TValue* val) {
+    if(self->globals->type == tp_module) {
+        py_setdict(self->globals, name, val);
+        return true;
+    } else {
+        return py_dict_setitem(self->globals, py_name2ref(name), val);
+    }
+}
+
+int Frame__delglobal(Frame* self, py_Name name) {
+    if(self->globals->type == tp_module) {
+        bool found = py_deldict(self->globals, name);
+        return found ? 1 : 0;
+    } else {
+        return py_dict_delitem(self->globals, py_name2ref(name));
+    }
+}
+
+py_StackRef Frame__getlocal_noproxy(Frame* self, py_Name name) {
+    assert(!self->is_locals_special);
+    int index = c11_smallmap_n2i__get(&self->co->varnames_inv, name, -1);
+    if(index == -1) return NULL;
+    return &self->locals[index];
+}
+
+py_Ref Frame__getclosure(Frame* self, py_Name name) {
+    if(self->is_locals_special) return NULL;
+    assert(self->p0->type == tp_function);
+    Function* ud = py_touserdata(self->p0);
+    if(ud->closure == NULL) return NULL;
+    return NameDict__try_get(ud->closure, name);
 }

+ 2 - 1
src/interpreter/generator.c

@@ -28,10 +28,11 @@ static bool generator__next__(int argc, py_Ref argv) {
     if(ud->state == 2) return StopIteration();
 
     // reset frame->p0
+    assert(!ud->frame->is_locals_special);
     int locals_offset = ud->frame->locals - ud->frame->p0;
     ud->frame->p0 = py_peek(0);
     ud->frame->locals = ud->frame->p0 + locals_offset;
-
+    
     // restore the context
     py_Ref backup = py_getslot(argv, 0);
     int length = py_list_len(backup);

+ 75 - 0
src/interpreter/name.c

@@ -0,0 +1,75 @@
+#include "pocketpy/interpreter/name.h"
+#include "pocketpy/interpreter/vm.h"
+
+void InternedNames__ctor(InternedNames* self) {
+    c11_smallmap_s2n__ctor(&self->interned);
+    c11_vector__ctor(&self->r_interned, sizeof(RInternedEntry));
+
+    // initialize all magic names
+#define MAGIC_METHOD(x)                                                                            \
+    if(x != py_name(#x)) abort();
+#include "pocketpy/xmacros/magics.h"
+#undef MAGIC_METHOD
+}
+
+void InternedNames__dtor(InternedNames* self) {
+    for(int i = 0; i < self->r_interned.length; i++) {
+        PK_FREE(c11__getitem(RInternedEntry, &self->r_interned, i).data);
+    }
+    c11_smallmap_s2n__dtor(&self->interned);
+    c11_vector__dtor(&self->r_interned);
+}
+
+py_Name py_name(const char* name) {
+    c11_sv sv;
+    sv.data = name;
+    sv.size = strlen(name);
+    return py_namev(sv);
+}
+
+py_Name py_namev(c11_sv name) {
+    InternedNames* self = &pk_current_vm->names;
+    uint16_t index = c11_smallmap_s2n__get(&self->interned, name, 0);
+    if(index != 0) return index;
+    // generate new index
+    if(self->interned.length > 65530) c11__abort("py_Name index overflow");
+    // NOTE: we must allocate the string in the heap so iterators are not invalidated
+    char* p = PK_MALLOC(name.size + 1);
+    memcpy(p, name.data, name.size);
+    p[name.size] = '\0';
+    RInternedEntry* entry = c11_vector__emplace(&self->r_interned);
+    entry->data = p;
+    entry->size = name.size;
+    memset(&entry->obj, 0, sizeof(py_TValue));
+    index = self->r_interned.length;  // 1-based
+    // save to _interned
+    c11_smallmap_s2n__set(&self->interned, (c11_sv){p, name.size}, index);
+    assert(self->interned.length == self->r_interned.length);
+    return index;
+}
+
+const char* py_name2str(py_Name index) {
+    InternedNames* self = &pk_current_vm->names;
+    assert(index > 0 && index <= self->interned.length);
+    return c11__getitem(RInternedEntry, &self->r_interned, index - 1).data;
+}
+
+c11_sv py_name2sv(py_Name index) {
+    InternedNames* self = &pk_current_vm->names;
+    assert(index > 0 && index <= self->interned.length);
+    RInternedEntry entry = c11__getitem(RInternedEntry, &self->r_interned, index - 1);
+    return (c11_sv){entry.data, entry.size};
+}
+
+py_GlobalRef py_name2ref(py_Name index) {
+    InternedNames* self = &pk_current_vm->names;
+    assert(index > 0 && index <= self->interned.length);
+    RInternedEntry* entry = c11__at(RInternedEntry, &self->r_interned, index - 1);
+    if(entry->obj.type == tp_nil) {
+        c11_sv sv;
+        sv.data = entry->data;
+        sv.size = entry->size;
+        py_newstrv(&entry->obj, sv);
+    }
+    return &entry->obj;
+}

+ 21 - 11
src/interpreter/vm.c

@@ -57,6 +57,7 @@ static void py_TypeInfo__ctor(py_TypeInfo* self,
 
 void VM__ctor(VM* self) {
     self->top_frame = NULL;
+    InternedNames__ctor(&self->names);
 
     ModuleDict__ctor(&self->modules, NULL, *py_NIL());
     TypeList__ctor(&self->types);
@@ -126,7 +127,7 @@ void VM__ctor(VM* self) {
     validate(tp_Exception, pk_Exception__register());
     validate(tp_bytes, pk_bytes__register());
     validate(tp_namedict, pk_namedict__register());
-    validate(tp_locals, pk_locals__register());
+    validate(tp_locals, pk_newtype("locals", tp_object, NULL, NULL, false, true));
     validate(tp_code, pk_code__register());
 
     validate(tp_dict, pk_dict__register());
@@ -257,7 +258,8 @@ void VM__dtor(VM* self) {
     ModuleDict__dtor(&self->modules);
     TypeList__dtor(&self->types);
     FixedMemoryPool__dtor(&self->pool_frame);
-    ValueStack__clear(&self->stack);
+    ValueStack__dtor(&self->stack);
+    InternedNames__dtor(&self->names);
 }
 
 void VM__push_frame(VM* self, Frame* frame) {
@@ -281,7 +283,11 @@ static void _clip_int(int* value, int min, int max) {
     if(*value > max) *value = max;
 }
 
-bool pk__parse_int_slice(py_Ref slice, int length, int* restrict start, int* restrict stop, int* restrict step) {
+bool pk__parse_int_slice(py_Ref slice,
+                         int length,
+                         int* restrict start,
+                         int* restrict stop,
+                         int* restrict step) {
     if(py_isint(slice)) {
         int index = py_toint(slice);
         bool ok = pk__normalize_index(&index, length);
@@ -399,9 +405,9 @@ static bool
     if(decl->starred_arg != -1) {
         int exceed_argc = p1 - t;
         py_Ref vargs = &buffer[decl->starred_arg];
-        py_newtuple(vargs, exceed_argc);
+        py_Ref data = py_newtuple(vargs, exceed_argc);
         for(int j = 0; j < exceed_argc; j++) {
-            py_tuple_setitem(vargs, j, t++);
+            data[j] = *t++;
         }
     } else {
         // kwdefaults override
@@ -431,9 +437,8 @@ static bool
                                  co->name->data);
             } else {
                 // add to **kwargs
-                bool ok = py_dict_setitem_by_str(&buffer[decl->starred_kwarg],
-                                                 py_name2str(key),
-                                                 &p1[2 * j + 1]);
+                bool ok =
+                    py_dict_setitem(&buffer[decl->starred_kwarg], py_name2ref(key), &p1[2 * j + 1]);
                 if(!ok) return false;
             }
         }
@@ -480,7 +485,7 @@ FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall
                 // submit the call
                 if(!fn->cfunc) {
                     // python function
-                    VM__push_frame(self, Frame__new(co, &fn->module, p0, argv, true));
+                    VM__push_frame(self, Frame__new(co, p0, fn->module, fn->globals, argv, false));
                     return opcall ? RES_CALL : VM__run_top_frame(self);
                 } else {
                     // decl-based binding
@@ -509,7 +514,7 @@ FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall
                 // submit the call
                 if(!fn->cfunc) {
                     // python function
-                    VM__push_frame(self, Frame__new(co, &fn->module, p0, argv, true));
+                    VM__push_frame(self, Frame__new(co, p0, fn->module, fn->globals, argv, false));
                     return opcall ? RES_CALL : VM__run_top_frame(self);
                 } else {
                     // decl-based binding
@@ -525,7 +530,7 @@ FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall
                 // copy buffer back to stack
                 self->stack.sp = argv + co->nlocals;
                 memcpy(argv, self->__vectorcall_buffer, co->nlocals * sizeof(py_TValue));
-                Frame* frame = Frame__new(co, &fn->module, p0, argv, true);
+                Frame* frame = Frame__new(co, p0, fn->module, fn->globals, argv, false);
                 pk_newgenerator(py_retval(), frame, p0, self->stack.sp);
                 self->stack.sp = p0;  // reset the stack
                 return RES_RETURN;
@@ -693,6 +698,11 @@ void ManagedHeap__mark(ManagedHeap* self) {
     for(int i = 0; i < c11__count_array(vm->reg); i++) {
         pk__mark_value(&vm->reg[i]);
     }
+    // mark interned names
+    for(int i = 0; i < vm->names.r_interned.length; i++) {
+        RInternedEntry* entry = c11__at(RInternedEntry, &vm->names.r_interned, i);
+        pk__mark_value(&entry->obj);
+    }
 }
 
 void pk_print_stack(VM* self, Frame* frame, Bytecode byte) {

+ 9 - 13
src/modules/array2d.c

@@ -606,8 +606,7 @@ static bool array2d_like_get_bounding_rect(int argc, py_Ref argv) {
     if(width <= 0 || height <= 0) {
         return ValueError("value not found");
     } else {
-        py_newtuple(py_retval(), 4);
-        py_TValue* data = py_tuple_data(py_retval());
+        py_TValue* data = py_newtuple(py_retval(), 4);
         py_newint(&data[0], left);
         py_newint(&data[1], top);
         py_newint(&data[2], width);
@@ -786,8 +785,7 @@ static bool array2d_like_iterator__next__(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
     c11_array2d_like_iterator* self = py_touserdata(argv);
     if(self->j >= self->array->n_rows) return StopIteration();
-    py_newtuple(py_retval(), 2);
-    py_TValue* data = py_tuple_data(py_retval());
+    py_TValue* data = py_newtuple(py_retval(), 2);
     py_newvec2i(&data[0],
                 (c11_vec2i){
                     {self->i, self->j}
@@ -1081,14 +1079,13 @@ static bool chunked_array2d__delitem__(int argc, py_Ref argv) {
 static bool chunked_array2d__iter__(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
     c11_chunked_array2d* self = py_touserdata(argv);
-    py_newtuple(py_pushtmp(), self->chunks.length);
+    py_Ref data = py_newtuple(py_pushtmp(), self->chunks.length);
     for(int i = 0; i < self->chunks.length; i++) {
-        py_Ref slot = py_tuple_getitem(py_peek(-1), i);
         c11_chunked_array2d_chunks_KV* kv =
             c11__at(c11_chunked_array2d_chunks_KV, &self->chunks, i);
-        py_newtuple(slot, 2);
-        py_newvec2i(py_tuple_getitem(slot, 0), kv->key);
-        py_tuple_setitem(slot, 1, &kv->value[0]);
+        py_Ref p = py_newtuple(&data[i], 2);
+        py_newvec2i(&p[0], kv->key);  // pos
+        p[1] = kv->value[0];          // context
     }
     bool ok = py_iter(py_peek(-1));
     if(!ok) return false;
@@ -1147,10 +1144,9 @@ static bool chunked_array2d_world_to_chunk(int argc, py_Ref argv) {
     c11_vec2i pos = py_tovec2i(&argv[1]);
     c11_vec2i chunk_pos, local_pos;
     c11_chunked_array2d__world_to_chunk(self, pos.x, pos.y, &chunk_pos, &local_pos);
-    py_newtuple(py_retval(), 2);
-    py_TValue* data = py_tuple_data(py_retval());
-    py_newvec2i(&data[0], chunk_pos);
-    py_newvec2i(&data[1], local_pos);
+    py_TValue* p = py_newtuple(py_retval(), 2);
+    py_newvec2i(&p[0], chunk_pos);
+    py_newvec2i(&p[1], local_pos);
     return true;
 }
 

+ 1 - 1
src/modules/enum.c

@@ -7,7 +7,7 @@ static bool Enum__wrapper_field(py_Name name, py_Ref value, void* ctx) {
     if(name_sv.size == 0 || name_sv.data[0] == '_') return true;
     py_push(ctx);
     py_pushnil();
-    py_newstr(py_pushtmp(), py_name2str(name));
+    py_assign(py_pushtmp(), py_name2ref(name));
     py_push(value);
     bool ok = py_vectorcall(2, 0);
     if(!ok) return false;

+ 3 - 3
src/modules/linalg.c

@@ -432,12 +432,12 @@ static bool vec2_smoothdamp_STATIC(int argc, py_Ref argv) {
     }
 
     py_Ref ret = py_retval();
-    py_newtuple(ret, 2);
-    py_newvec2(py_tuple_getitem(ret, 0),
+    py_Ref p = py_newtuple(ret, 2);
+    py_newvec2(&p[0],
                (c11_vec2){
                    {output_x, output_y}
     });
-    py_newvec2(py_tuple_getitem(ret, 1), currentVelocity);
+    py_newvec2(&p[1], currentVelocity);
     return true;
 }
 

+ 3 - 5
src/modules/math.c

@@ -136,11 +136,9 @@ static bool math_modf(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
     double i;
     double f = modf(py_tofloat(py_arg(0)), &i);
-    py_newtuple(py_retval(), 2);
-    py_Ref _0 = py_tuple_getitem(py_retval(), 0);
-    py_Ref _1 = py_tuple_getitem(py_retval(), 1);
-    py_newfloat(_0, f);
-    py_newfloat(_1, i);
+    py_Ref p = py_newtuple(py_retval(), 2);
+    py_newfloat(&p[0], f);
+    py_newfloat(&p[1], i);
     return true;
 }
 

+ 5 - 7
src/modules/pickle.c

@@ -477,22 +477,21 @@ static py_Type pkl__fix_type(py_Type type, c11_smallmap_n2i* type_mapping) {
 
 bool py_pickle_loads_body(const unsigned char* p, int memo_length, c11_smallmap_n2i* type_mapping) {
     py_StackRef p0 = py_peek(0);
-    py_StackRef memo = py_pushtmp();
-    py_newtuple(memo, memo_length);
+    py_Ref p_memo = py_newtuple(py_pushtmp(), memo_length);
     while(true) {
         PickleOp op = (PickleOp)*p;
         p++;
         switch(op) {
             case PKL_MEMO_GET: {
                 int index = pkl__read_int(&p);
-                py_Ref val = py_tuple_getitem(memo, index);
+                py_Ref val = &p_memo[index];
                 assert(!py_isnil(val));
                 py_push(val);
                 break;
             }
             case PKL_MEMO_SET: {
                 int index = pkl__read_int(&p);
-                py_tuple_setitem(memo, index, py_peek(-1));
+                p_memo[index] = *py_peek(-1);
                 break;
             }
             case PKL_NIL: {
@@ -589,10 +588,9 @@ bool py_pickle_loads_body(const unsigned char* p, int memo_length, c11_smallmap_
             case PKL_BUILD_TUPLE: {
                 int length = pkl__read_int(&p);
                 py_OutRef val = py_retval();
-                py_newtuple(val, length);
+                py_Ref p = py_newtuple(val, length);
                 for(int i = length - 1; i >= 0; i--) {
-                    py_StackRef item = py_peek(-1);
-                    py_tuple_setitem(val, i, item);
+                    p[i] = *py_peek(-1);
                     py_pop();
                 }
                 py_push(val);

+ 4 - 3
src/objects/codeobject.c

@@ -159,12 +159,13 @@ void CodeObject__dtor(CodeObject* self) {
     c11_vector__dtor(&self->func_decls);
 }
 
-void Function__ctor(Function* self, FuncDecl_ decl, py_TValue* module) {
+void Function__ctor(Function* self, FuncDecl_ decl, py_GlobalRef module, py_Ref globals) {
     PK_INCREF(decl);
     self->decl = decl;
-    self->module = module ? *module : *py_NIL();
-    self->clazz = NULL;
+    self->module = module;
+    self->globals = globals;
     self->closure = NULL;
+    self->clazz = NULL;
     self->cfunc = NULL;
 }
 

+ 35 - 9
src/public/exec.c

@@ -3,16 +3,13 @@
 #include "pocketpy/pocketpy.h"
 
 #include "pocketpy/common/utils.h"
-#include "pocketpy/common/sstream.h"
-#include "pocketpy/objects/object.h"
 #include "pocketpy/interpreter/vm.h"
 #include "pocketpy/compiler/compiler.h"
-
-static void code__gc_mark(void* ud) { CodeObject__gc_mark(ud); }
+#include <assert.h>
 
 py_Type pk_code__register() {
     py_Type type = pk_newtype("code", tp_object, NULL, (py_Dtor)CodeObject__dtor, false, true);
-    pk__tp_set_marker(type, code__gc_mark);
+    pk__tp_set_marker(type, (void (*)(void*))CodeObject__gc_mark);
     return type;
 }
 
@@ -57,14 +54,43 @@ bool pk_exec(CodeObject* co, py_Ref module) {
     assert(module->type == tp_module);
 
     py_StackRef sp = vm->stack.sp;
-    if(co->src->is_dynamic) sp -= 3;  // [globals, locals, code]
+    Frame* frame = Frame__new(co, sp, module, module, py_NIL(), true);
+    VM__push_frame(vm, frame);
+    FrameResult res = VM__run_top_frame(vm);
+    if(res == RES_ERROR) return false;
+    assert(res == RES_RETURN);
+    return true;
+}
+
+bool pk_execdyn(CodeObject* co, py_Ref module, py_Ref globals, py_Ref locals) {
+    VM* vm = pk_current_vm;
+    if(!module) module = &vm->main;
+    assert(module->type == tp_module);
+
+    py_StackRef sp = vm->stack.sp;
+    assert(globals != NULL && locals != NULL);
 
-    Frame* frame = Frame__new(co, module, sp, sp, false);
+    // check globals
+    if(globals->type == tp_namedict) {
+        globals = py_getslot(globals, 0);
+        assert(globals->type == tp_module);
+    } else {
+        if(!py_istype(globals, tp_dict)) { return TypeError("globals must be a dict object"); }
+    }
+    // check locals
+    switch(locals->type) {
+        case tp_locals: break;
+        case tp_dict: break;
+        case tp_nil: break;
+        default: return TypeError("locals must be a dict object");
+    }
+
+    Frame* frame = Frame__new(co, sp, module, globals, locals, true);
     VM__push_frame(vm, frame);
     FrameResult res = VM__run_top_frame(vm);
     if(res == RES_ERROR) return false;
-    if(res == RES_RETURN) return true;
-    c11__unreachable();
+    assert(res == RES_RETURN);
+    return true;
 }
 
 bool py_exec(const char* source, const char* filename, enum py_CompileMode mode, py_Ref module) {

+ 0 - 3
src/public/internal.c

@@ -27,8 +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");
 
-    py_Name__initialize();
-
     pk_current_vm = pk_all_vm[0] = &pk_default_vm;
 
     // initialize some convenient references
@@ -61,7 +59,6 @@ void py_finalize() {
     pk_current_vm = &pk_default_vm;
     VM__dtor(&pk_default_vm);
     pk_current_vm = NULL;
-    py_Name__finalize();
 }
 
 void py_switchvm(int index) {

+ 61 - 24
src/public/modules.c

@@ -498,23 +498,47 @@ void py_newglobals(py_Ref out) {
         pk_mappingproxy__namedict(out, &pk_current_vm->main);
         return;
     }
-    if(frame->is_dynamic) {
-        py_assign(out, &frame->p0[0]);
+    if(frame->globals->type == tp_module) {
+        pk_mappingproxy__namedict(out, frame->globals);
     } else {
-        pk_mappingproxy__namedict(out, frame->module);
+        *out = *frame->globals;  // dict
     }
 }
 
 void py_newlocals(py_Ref out) {
     Frame* frame = pk_current_vm->top_frame;
-    if(frame->is_dynamic) {
-        py_assign(out, &frame->p0[1]);
+    if(!frame) {
+        py_newdict(out);
+        return;
+    }
+    if(frame->is_locals_special) {
+        switch(frame->locals->type) {
+            case tp_locals: frame = frame->locals->_ptr; break;
+            case tp_dict: *out = *frame->locals; return;
+            case tp_nil: py_newdict(out); return;
+            default: c11__unreachable();
+        }
+    }
+    FastLocals__to_dict(frame->locals, frame->co);
+    py_assign(out, py_retval());
+}
+
+static void pk_push_special_locals() {
+    Frame* frame = pk_current_vm->top_frame;
+    if(!frame) {
+        py_pushnil();
         return;
     }
-    if(frame->has_function) {
-        pk_mappingproxy__locals(out, frame);
+    if(frame->is_locals_special) {
+        py_push(frame->locals);
     } else {
-        py_newglobals(out);
+        py_StackRef out = py_pushtmp();
+        out->type = tp_locals;
+        out->is_ptr = false;
+        out->extra = 0;
+        // this is a weak reference
+        // which will expire when the frame is destroyed
+        out->_ptr = frame;
     }
 }
 
@@ -522,52 +546,64 @@ static bool _builtins_execdyn(const char* title, int argc, py_Ref argv, enum py_
     switch(argc) {
         case 1: {
             py_newglobals(py_pushtmp());
-            py_newlocals(py_pushtmp());
+            pk_push_special_locals();
             break;
         }
         case 2: {
+            // globals
             if(py_isnone(py_arg(1))) {
                 py_newglobals(py_pushtmp());
             } else {
                 py_push(py_arg(1));
             }
-            py_pushnone();
+            // locals
+            py_pushnil();
             break;
         }
         case 3: {
+            // globals
             if(py_isnone(py_arg(1))) {
                 py_newglobals(py_pushtmp());
             } else {
                 py_push(py_arg(1));
             }
-            py_push(py_arg(2));
+            // locals
+            if(py_isnone(py_arg(2))) {
+                py_pushnil();
+            } else {
+                py_push(py_arg(2));
+            }
             break;
         }
         default: return TypeError("%s() takes at most 3 arguments", title);
     }
 
-    py_Ref code;
     if(py_isstr(argv)) {
         bool ok = py_compile(py_tostr(argv), "<string>", mode, true);
         if(!ok) return false;
-        code = py_retval();
+        py_push(py_retval());
     } else if(py_istype(argv, tp_code)) {
-        code = argv;
+        py_push(argv);
     } else {
         return TypeError("%s() expected 'str' or 'code', got '%t'", title, argv->type);
     }
 
-    py_push(code);  // keep it alive
-
+    Frame* frame = pk_current_vm->top_frame;
     // [globals, locals, code]
-    CodeObject* co = py_touserdata(code);
-    if(!co->src->is_dynamic) {
-        if(argc != 1)
-            return ValueError("code object is not dynamic, so globals and locals must be None");
+    CodeObject* code = py_touserdata(py_peek(-1));
+    if(code->src->is_dynamic) {
+        bool ok = pk_execdyn(code, frame ? frame->module : NULL, py_peek(-3), py_peek(-2));
         py_shrink(3);
+        return ok;
+    } else {
+        if(argc != 1) {
+            return ValueError(
+                "code object is not dynamic, `globals` and `locals` must not be specified");
+        }
+        bool ok = pk_exec(code, frame ? frame->module : NULL);
+        py_shrink(3);
+        return ok;
     }
-    Frame* frame = pk_current_vm->top_frame;
-    return pk_exec(co, frame ? frame->module : NULL);
 }
 
 static bool builtins_exec(int argc, py_Ref argv) {
@@ -736,6 +772,7 @@ py_TValue pk_builtins__register() {
 
 static void function__gc_mark(void* ud) {
     Function* func = ud;
+    if(func->globals) pk__mark_value(func->globals);
     if(func->closure) pk__mark_namedict(func->closure);
     FuncDecl__gc_mark(func->decl);
 }
@@ -779,14 +816,14 @@ static bool super__new__(int argc, py_Ref argv) {
     py_Ref self_arg = NULL;
     if(argc == 1) {
         // super()
-        if(frame->has_function) {
+        if(!frame->is_locals_special) {
             py_TValue* callable = frame->p0;
             if(callable->type == tp_boundmethod) callable = py_getslot(frame->p0, 1);
             if(callable->type == tp_function) {
                 Function* func = py_touserdata(callable);
                 if(func->clazz != NULL) {
                     class_arg = *(py_Type*)PyObject__userdata(func->clazz);
-                    if(frame->co->nlocals > 0) self_arg = &frame->locals[0];
+                    if(frame->co->nlocals > 0) { self_arg = &frame->locals[0]; }
                 }
             }
         }

+ 7 - 7
src/public/py_dict.c

@@ -496,14 +496,14 @@ static bool dict_items(int argc, py_Ref argv) {
 static bool dict_keys(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
     Dict* self = py_touserdata(argv);
-    py_newtuple(py_retval(), self->length);
+    py_Ref p = py_newtuple(py_retval(), self->length);
     DictIterator iter;
     DictIterator__ctor(&iter, self);
     int i = 0;
     while(1) {
         DictEntry* entry = DictIterator__next(&iter);
         if(!entry) break;
-        py_tuple_setitem(py_retval(), i++, &entry->key);
+        p[i++] = entry->key;
     }
     assert(i == self->length);
     return true;
@@ -512,14 +512,14 @@ static bool dict_keys(int argc, py_Ref argv) {
 static bool dict_values(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
     Dict* self = py_touserdata(argv);
-    py_newtuple(py_retval(), self->length);
+    py_Ref p = py_newtuple(py_retval(), self->length);
     DictIterator iter;
     DictIterator__ctor(&iter, self);
     int i = 0;
     while(1) {
         DictEntry* entry = DictIterator__next(&iter);
         if(!entry) break;
-        py_tuple_setitem(py_retval(), i++, &entry->val);
+        p[i++] = entry->val;
     }
     assert(i == self->length);
     return true;
@@ -570,9 +570,9 @@ static bool dict_items__next__(int argc, py_Ref argv) {
     DictIterator* iter = py_touserdata(py_arg(0));
     DictEntry* entry = (DictIterator__next(iter));
     if(entry) {
-        py_newtuple(py_retval(), 2);
-        py_tuple_setitem(py_retval(), 0, &entry->key);
-        py_tuple_setitem(py_retval(), 1, &entry->val);
+        py_Ref p = py_newtuple(py_retval(), 2);
+        p[0] = entry->key;
+        p[1] = entry->val;
         return true;
     }
     return StopIteration();

+ 2 - 2
src/public/py_exception.c

@@ -88,8 +88,8 @@ static bool BaseException_args(int argc, py_Ref argv){
     PY_CHECK_ARGC(1);
     py_Ref arg = py_getslot(argv, 0);
     if(!py_isnil(arg)) {
-        py_newtuple(py_retval(), 1);
-        py_setslot(py_retval(), 0, arg);
+        py_Ref p = py_newtuple(py_retval(), 1);
+        p[0] = *arg;
     }else{
         py_newtuple(py_retval(), 0);
     }

+ 10 - 75
src/public/py_mappingproxy.c

@@ -1,9 +1,8 @@
 #include "pocketpy/pocketpy.h"
 
-#include "pocketpy/common/utils.h"
 #include "pocketpy/objects/object.h"
 #include "pocketpy/interpreter/vm.h"
-#include "pocketpy/common/sstream.h"
+#include <stdbool.h>
 
 void pk_mappingproxy__namedict(py_Ref out, py_Ref object) {
     py_newobject(out, tp_namedict, 1, 0);
@@ -58,26 +57,26 @@ static bool namedict_items(int argc, py_Ref argv) {
         for(int j = 0; j < PK_MAGIC_SLOTS_COMMON_LENGTH; j++) {
             if(py_isnil(ti->magic_0 + j)) continue;
             py_Ref slot = py_list_emplace(py_retval());
-            py_newtuple(slot, 2);
-            py_newstr(py_tuple_getitem(slot, 0), py_name2str(j + PK_MAGIC_SLOTS_UNCOMMON_LENGTH));
-            py_assign(py_tuple_getitem(slot, 1), ti->magic_0 + j);
+            py_Ref p = py_newtuple(slot, 2);
+            p[0] = *py_name2ref(j + PK_MAGIC_SLOTS_UNCOMMON_LENGTH);
+            p[1] = ti->magic_0[j];
         }
         if(ti->magic_1) {
             for(int j = 0; j < PK_MAGIC_SLOTS_UNCOMMON_LENGTH; j++) {
                 if(py_isnil(ti->magic_1 + j)) continue;
                 py_Ref slot = py_list_emplace(py_retval());
-                py_newtuple(slot, 2);
-                py_newstr(py_tuple_getitem(slot, 0), py_name2str(j));
-                py_assign(py_tuple_getitem(slot, 1), ti->magic_1 + j);
+                py_Ref p = py_newtuple(slot, 2);
+                p[0] = *py_name2ref(j);
+                p[1] = ti->magic_1[j];
             }
         }
     }
     for(int i = 0; i < dict->length; i++) {
         py_Ref slot = py_list_emplace(py_retval());
-        py_newtuple(slot, 2);
+        py_Ref p = py_newtuple(slot, 2);
         NameDict_KV* kv = c11__at(NameDict_KV, dict, i);
-        py_newstr(py_tuple_getitem(slot, 0), py_name2str(kv->key));
-        py_assign(py_tuple_getitem(slot, 1), &kv->value);
+        p[0] = *py_name2ref(kv->key);
+        p[1] = kv->value;
     }
     return true;
 }
@@ -103,67 +102,3 @@ py_Type pk_namedict__register() {
     py_bindmethod(type, "clear", namedict_clear);
     return type;
 }
-
-//////////////////////
-
-void pk_mappingproxy__locals(py_Ref out, Frame* frame) {
-    assert(frame->has_function && !frame->is_dynamic);
-    Frame** ud = py_newobject(out, tp_locals, 0, sizeof(Frame*));
-    *ud = frame;
-}
-
-static bool locals__getitem__(int argc, py_Ref argv) {
-    PY_CHECK_ARGC(2);
-    PY_CHECK_ARG_TYPE(1, tp_str);
-    Frame** ud = py_touserdata(argv);
-    py_Name name = py_namev(py_tosv(py_arg(1)));
-    py_Ref slot = Frame__f_locals_try_get(*ud, name);
-    if(!slot || py_isnil(slot)) return KeyError(py_arg(1));
-    py_assign(py_retval(), slot);
-    return true;
-}
-
-static bool locals__setitem__(int argc, py_Ref argv) {
-    PY_CHECK_ARGC(3);
-    PY_CHECK_ARG_TYPE(1, tp_str);
-    Frame** ud = py_touserdata(argv);
-    py_Name name = py_namev(py_tosv(py_arg(1)));
-    py_Ref slot = Frame__f_locals_try_get(*ud, name);
-    if(!slot) return KeyError(py_arg(1));
-    py_assign(slot, py_arg(2));
-    py_newnone(py_retval());
-    return true;
-}
-
-static bool locals__delitem__(int argc, py_Ref argv) {
-    PY_CHECK_ARGC(2);
-    PY_CHECK_ARG_TYPE(1, tp_str);
-    Frame** ud = py_touserdata(argv);
-    py_Name name = py_namev(py_tosv(py_arg(1)));
-    py_Ref res = Frame__f_locals_try_get(*ud, name);
-    if(!res || py_isnil(res)) return KeyError(py_arg(1));
-    py_newnil(res);
-    py_newnone(py_retval());
-    return true;
-}
-
-static bool locals__contains__(int argc, py_Ref argv) {
-    PY_CHECK_ARGC(2);
-    PY_CHECK_ARG_TYPE(1, tp_str);
-    Frame** ud = py_touserdata(argv);
-    py_Name name = py_namev(py_tosv(py_arg(1)));
-    py_Ref slot = Frame__f_locals_try_get(*ud, name);
-    py_newbool(py_retval(), slot && !py_isnil(slot));
-    return true;
-}
-
-py_Type pk_locals__register() {
-    py_Type type = pk_newtype("locals", tp_object, NULL, NULL, false, true);
-
-    py_bindmagic(type, __getitem__, locals__getitem__);
-    py_bindmagic(type, __setitem__, locals__setitem__);
-    py_bindmagic(type, __delitem__, locals__delitem__);
-    py_bindmagic(type, __contains__, locals__contains__);
-    py_newnone(py_tpgetmagic(type, __hash__));
-    return type;
-}

+ 3 - 3
src/public/py_number.c

@@ -181,9 +181,9 @@ static bool int__divmod__(int argc, py_Ref argv) {
     py_i64 lhs = py_toint(&argv[0]);
     py_i64 rhs = py_toint(&argv[1]);
     if(rhs == 0) return ZeroDivisionError("integer division or modulo by zero");
-    py_newtuple(py_retval(), 2);
-    py_newint(py_getslot(py_retval(), 0), cpy11__fast_floor_div(lhs, rhs));
-    py_newint(py_getslot(py_retval(), 1), cpy11__fast_mod(lhs, rhs));
+    py_Ref p = py_newtuple(py_retval(), 2);
+    py_newint(&p[0], cpy11__fast_floor_div(lhs, rhs));
+    py_newint(&p[1], cpy11__fast_mod(lhs, rhs));
     return true;
 }
 

+ 1 - 1
src/public/py_object.c

@@ -84,7 +84,7 @@ static bool type__base__(int argc, py_Ref argv) {
 static bool type__name__(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
     py_TypeInfo* ti = pk__type_info(py_totype(argv));
-    py_newstr(py_retval(), py_name2str(ti->name));
+    py_assign(py_retval(), py_name2ref(ti->name));
     return true;
 }
 

+ 1 - 1
src/public/py_ops.c

@@ -153,7 +153,7 @@ bool py_getattr(py_Ref self, py_Name name) {
     if(fallback) {
         py_push(fallback);
         py_push(self);
-        py_newstr(py_pushtmp(), py_name2str(name));
+        py_assign(py_pushtmp(), py_name2ref(name));
         return py_vectorcall(1, 0);
     }
 

+ 6 - 5
src/public/py_tuple.c

@@ -5,12 +5,13 @@
 #include "pocketpy/objects/object.h"
 #include "pocketpy/interpreter/vm.h"
 
-void py_newtuple(py_Ref out, int n) {
+py_ObjectRef py_newtuple(py_Ref out, int n) {
     VM* vm = pk_current_vm;
     PyObject* obj = ManagedHeap__gcnew(&vm->heap, tp_tuple, n, 0);
     out->type = tp_tuple;
     out->is_ptr = true;
     out->_obj = obj;
+    return PyObject__slots(obj);
 }
 
 py_Ref py_tuple_getitem(py_Ref self, int i) { return py_getslot(self, i); }
@@ -59,9 +60,9 @@ static bool tuple__new__(int argc, py_Ref argv) {
         py_Ref tmp = py_pushtmp();
         *tmp = *py_retval();  // backup the list
         int length = py_list_len(tmp);
-        py_newtuple(py_retval(), length);
+        py_Ref p = py_newtuple(py_retval(), length);
         for(int i = 0; i < py_tuple_len(py_retval()); i++) {
-            py_tuple_setitem(py_retval(), i, py_list_getitem(tmp, i));
+            p[i] = *py_list_getitem(tmp, i);
         }
         py_pop();
         return true;
@@ -86,9 +87,9 @@ static bool tuple__getitem__(int argc, py_Ref argv) {
         py_newlist(tmp);
         PK_SLICE_LOOP(i, start, stop, step) py_list_append(tmp, py_getslot(argv, i));
         // convert list to tuple
-        py_newtuple(py_retval(), py_list_len(tmp));
+        py_Ref p = py_newtuple(py_retval(), py_list_len(tmp));
         for(int i = 0; i < py_tuple_len(py_retval()); i++) {
-            py_tuple_setitem(py_retval(), i, py_list_getitem(tmp, i));
+            p[i] = *py_list_getitem(tmp, i);
         }
         py_pop();
         return true;

+ 1 - 1
src/public/values.c

@@ -104,7 +104,7 @@ py_Name
     decl->docstring = docstring;
     // construct the function
     Function* ud = py_newobject(out, tp_function, slots, sizeof(Function));
-    Function__ctor(ud, decl, NULL);
+    Function__ctor(ud, decl, NULL, NULL);
     ud->cfunc = f;
     CodeObject__dtor(&code);
     PK_DECREF(source);

+ 12 - 5
tests/66_eval.py

@@ -31,12 +31,10 @@ def f():
     )
     assert b == 8
 
-class G: pass
-
 def abc():
-    g = G()
-    exec('a=1', g.__dict__)
-    return g.a
+    g = {}
+    exec('a=1', g)
+    return g['a']
 
 res = abc()
 assert (res==1), res
@@ -67,3 +65,12 @@ try:
     exit(1)
 except NameError:
     pass
+
+# https://github.com/pocketpy/pocketpy/issues/339
+res = []
+code = '\nres.append(x)\ndef f():\n  res.append(x)\nf()\n'
+x = 33
+exec(code, {'x': 42, 'res': res})
+assert res == [42, 42]
+assert x == 33
+

+ 41 - 0
tests/67_locals_vs_globals.py

@@ -0,0 +1,41 @@
+# https://gist.github.com/dean0x7d/df5ce97e4a1a05be4d56d1378726ff92
+
+a = 1
+my_locals = {"b": 2}
+
+# With user-defined locals:
+exec("""
+import sys
+assert "sys" in locals()
+assert "sys" not in globals()
+assert "a" not in locals()
+assert "a" in globals()
+# print(a)  # checks `locals()` first, fails, but finds it in `globals()`
+assert (a == 1), a
+assert "b" in locals()
+assert "b" not in globals()
+# print(b)
+assert (b == 2), b
+def main():
+    assert "sys" not in locals()   # not the same `locals()` as the outer scope
+    assert "sys" not in globals()  # and `sys` isn't in `globals()`, same as before
+    assert "b" not in locals() # again, not the same `locals()` as the outer scope
+main()
+""", globals(), my_locals)
+
+assert "sys" in my_locals  # side effect
+assert "sys" not in globals()
+
+
+# With default locals:
+exec("""
+import sys
+assert locals() == {}
+assert "sys" in globals()
+def main():
+    assert "sys" not in locals()  # not the same locals as the outer scope
+    assert "sys" in globals()     # but now be can access `sys` via `globals()`
+main()
+""", globals())
+
+assert "sys" in globals()