Răsfoiți Sursa

add method cache

blueloveTH 12 ore în urmă
părinte
comite
f080c07abc

+ 8 - 0
include/pocketpy/interpreter/typeinfo.h

@@ -21,6 +21,12 @@ typedef struct py_TypeInfo {
     bool (*delattribute)(py_Ref self, py_Name name) PY_RAISE;
     bool (*getunboundmethod)(py_Ref self, py_Name name) PY_RETURN;
 
+    // Resolved `__new__`/`__init__`, valid while `magics_version` matches
+    // `vm->type_version`. `cached_init` is nil when the type has no `__init__`.
+    py_TValue cached_new;
+    py_TValue cached_init;
+    uint64_t magics_version;
+
     py_TValue annotations;
     py_Dtor dtor;  // destructor for this type, NULL if no dtor
     void (*on_end_subclass)(struct py_TypeInfo*);  // backdoor for enum module
@@ -28,6 +34,8 @@ typedef struct py_TypeInfo {
 
 py_TypeInfo* pk_typeinfo(py_Type type);
 py_ItemRef pk_tpfindname(py_TypeInfo* ti, py_Name name);
+/// Re-resolve `cached_new`/`cached_init` against the current `type_version`.
+void pk_tpresolvemagics(py_TypeInfo* ti);
 #define pk_tpfindmagic pk_tpfindname
 
 py_Type pk_newtype(const char* name,

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

@@ -45,6 +45,9 @@ typedef struct VM {
 
     BinTree modules;
     c11_vector /*TypePointer*/ types;
+    // Bumped on every write to a type's `__dict__`. `py_TypeInfo` caches the
+    // resolution of `__new__`/`__init__` and re-resolves when this moves.
+    uint64_t type_version;
 
     py_GlobalRef builtins;  // builtins module
     py_GlobalRef main;      // __main__ module

+ 1 - 2
src/bindings/py_mappingproxy.c

@@ -75,8 +75,7 @@ static bool namedict_items(int argc, py_Ref argv) {
 static bool namedict_clear(int argc, py_Ref argv) {
     PY_CHECK_ARGC(1);
     py_Ref object = py_getslot(argv, 0);
-    NameDict* dict = PyObject__dict(object->_obj);
-    NameDict__clear(dict);
+    py_cleardict(object);
     py_newnone(py_retval());
     return true;
 }

+ 12 - 0
src/interpreter/typeinfo.c

@@ -1,6 +1,15 @@
 #include "pocketpy/interpreter/vm.h"
 #include <assert.h>
 
+void pk_tpresolvemagics(py_TypeInfo* ti) {
+    py_Ref f = pk_tpfindname(ti, __new__);
+    assert(f != NULL);  // `object.__new__` is always reachable
+    ti->cached_new = *f;
+    py_Ref g = pk_tpfindname(ti, __init__);
+    ti->cached_init = g ? *g : *py_NIL();
+    ti->magics_version = pk_current_vm->type_version;
+}
+
 py_ItemRef pk_tpfindname(py_TypeInfo* ti, py_Name name) {
     assert(ti != NULL);
     do {
@@ -52,6 +61,9 @@ static void py_TypeInfo__common_init(py_Name name,
     self->delattribute = NULL;
     self->getunboundmethod = NULL;
 
+    self->cached_new = *py_NIL();
+    self->cached_init = *py_NIL();
+    self->magics_version = 0;  // never resolved
     self->annotations = *py_NIL();
     self->dtor = dtor;
     self->on_end_subclass = NULL;

+ 8 - 3
src/interpreter/vm.c

@@ -95,6 +95,7 @@ void VM__ctor(VM* self) {
 
     self->last_retval = *py_NIL();
     self->unhandled_exc = *py_NIL();
+    self->type_version = 1;  // 0 means "never resolved" in py_TypeInfo
 
     self->recursion_depth = 0;
     self->max_recursion_depth = 1000;
@@ -581,9 +582,11 @@ FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall
 
     if(p0->type == tp_type) {
         py_Type p0_type = py_totype(p0);
+        py_TypeInfo* p0_ti = pk_typeinfo(p0_type);
+        if(p0_ti->magics_version != self->type_version) pk_tpresolvemagics(p0_ti);
         // [cls, NULL, args..., kwargs...]
-        py_Ref new_f = py_tpfindmagic(p0_type, __new__);
-        assert(new_f && py_isnil(p0 + 1));
+        py_Ref new_f = &p0_ti->cached_new;
+        assert(py_isnil(p0 + 1));
         bool is_default_new = new_f->type == tp_nativefunc && new_f->_cfunc == pk__object_new;
 
         // prepare a copy of args and kwargs
@@ -600,7 +603,7 @@ FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall
         // NOTE: previously we use `get_unbound_method` but here we just use `tpfindmagic`
         // >> [cls, NULL, args..., kwargs...]
         // >> py_retval() is the new instance
-        py_Ref init_f = py_tpfindmagic(p0_type, __init__);
+        py_Ref init_f = py_isnil(&p0_ti->cached_init) ? NULL : &p0_ti->cached_init;
         if(init_f) {
             if(py_isinstance(py_retval(), p0_type)) {
                 // do an inplace patch
@@ -681,6 +684,8 @@ void ManagedHeap__mark(ManagedHeap* self) {
     for(py_Type i = 1; i < types_length; i++) {
         py_TypeInfo* ti = c11__getitem(TypePointer, &vm->types, i).ti;
         pk__mark_value(&ti->self);
+        pk__mark_value(&ti->cached_new);
+        pk__mark_value(&ti->cached_init);
         pk__mark_value(&ti->annotations);
     }
     // mark frame

+ 5 - 0
src/public/DictSlots.c

@@ -13,11 +13,15 @@ PK_INLINE py_Ref py_getdict(py_Ref self, py_Name name) {
 
 PK_INLINE void py_setdict(py_Ref self, py_Name name, py_Ref val) {
     assert(self && self->is_ptr);
+    // writing to a type's dict may change what `__new__`/`__init__` resolve to,
+    // for this type and for every subclass of it
+    if(self->type == tp_type) pk_current_vm->type_version++;
     NameDict__set(PyObject__dict(self->_obj), name, val);
 }
 
 bool py_deldict(py_Ref self, py_Name name) {
     assert(self && self->is_ptr);
+    if(self->type == tp_type) pk_current_vm->type_version++;
     return NameDict__del(PyObject__dict(self->_obj), name);
 }
 
@@ -40,6 +44,7 @@ bool py_applydict(py_Ref self, bool (*f)(py_Name, py_Ref, void*), void* ctx) {
 
 void py_cleardict(py_Ref self) {
     assert(self && self->is_ptr);
+    if(self->type == tp_type) pk_current_vm->type_version++;
     NameDict* dict = PyObject__dict(self->_obj);
     NameDict__clear(dict);
 }

+ 37 - 0
tests/400_class.py

@@ -156,3 +156,40 @@ class E1:
 
 e1 = E1(3,4)
 assert e1.sum() == 7
+
+# `__new__`/`__init__` are resolved once and cached on the type, so rebinding
+# them later has to invalidate that cache -- for the type and for its subclasses.
+class CacheA:
+    def __init__(self):
+        self.tag = 'old'
+
+assert CacheA().tag == 'old'
+
+def _new_init(self):
+    self.tag = 'new'
+
+CacheA.__init__ = _new_init
+assert CacheA().tag == 'new'
+
+class CacheBase: pass
+class CacheDerived(CacheBase): pass
+
+CacheDerived()  # populate the cache before the base is touched
+
+def _base_init(self):
+    self.tag = 'base'
+
+CacheBase.__init__ = _base_init
+assert CacheDerived().tag == 'base'
+
+class CacheC:
+    def __init__(self):
+        self.tag = 'c'
+
+assert CacheC().tag == 'c'
+del CacheC.__init__
+try:
+    CacheC().tag
+    exit(1)
+except AttributeError:
+    pass