diff --git a/src/builtins.c b/src/builtins.c index 05384ca..34fe882 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -88,6 +88,7 @@ KRK_Method(object,__dir__) { } KRK_Method(object,__class__) { + if (argc > 1) return krk_runtimeError(vm.exceptions->typeError, "__class__ can not be assigned"); return OBJECT_VAL(krk_getType(self)); } @@ -99,7 +100,7 @@ KRK_Method(object,__hash__) { } KrkObj * obj = AS_OBJECT(self); if (!(obj->flags & KRK_OBJ_FLAGS_VALID_HASH)) { - obj->hash = INTEGER_VAL((int)(intptr_t)self); + obj->hash = INTEGER_VAL((int)((intptr_t)self >> 3)); obj->flags |= KRK_OBJ_FLAGS_VALID_HASH; } return INTEGER_VAL(obj->hash); @@ -1146,7 +1147,6 @@ void _createAndBind_builtins(void) { krk_push(OBJECT_VAL(vm.baseClasses->objectClass)); KrkClass * object = vm.baseClasses->objectClass; - BIND_METHOD(object,__class__)->obj.flags = KRK_OBJ_FLAGS_FUNCTION_IS_DYNAMIC_PROPERTY; BIND_METHOD(object,__dir__); BIND_METHOD(object,__str__); BIND_METHOD(object,__hash__); @@ -1228,6 +1228,9 @@ void _createAndBind_builtins(void) { "different name will create a duplicate alias."); krk_finalizeClass(property); + /* Need to do this after creating 'property' */ + BIND_PROP(object,__class__); + KrkClass * Helper = ADD_BASE_CLASS(KRK_BASE_CLASS(Helper), "Helper", object); KRK_DOC(Helper, "@brief Special object that prints a helpeful message.\n\n" diff --git a/src/kuroko.c b/src/kuroko.c index a75ad75..e7f64c3 100644 --- a/src/kuroko.c +++ b/src/kuroko.c @@ -276,8 +276,7 @@ static void tab_complete_func(rline_context_t * c) { KrkToken asToken = {.start = s->chars, .literalWidth = s->length}; KrkValue thisValue = findFromProperty(root, asToken); krk_push(thisValue); - if (IS_CLOSURE(thisValue) || IS_BOUND_METHOD(thisValue) || - (IS_NATIVE(thisValue) && !((AS_OBJECT(thisValue)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_DYNAMIC_PROPERTY)))) { + if (IS_CLOSURE(thisValue) || IS_BOUND_METHOD(thisValue) || IS_NATIVE(thisValue)) { size_t allocSize = s->length + 2; char * tmp = malloc(allocSize); size_t len = snprintf(tmp, allocSize, "%s(", s->chars); diff --git a/src/kuroko/object.h b/src/kuroko/object.h index a0ebb1b..dc0feee 100644 --- a/src/kuroko/object.h +++ b/src/kuroko/object.h @@ -56,10 +56,9 @@ typedef struct KrkObj { #define KRK_OBJ_FLAGS_CODEOBJECT_IS_GENERATOR 0x0004 #define KRK_OBJ_FLAGS_CODEOBJECT_IS_COROUTINE 0x0008 -#define KRK_OBJ_FLAGS_FUNCTION_MASK 0x0007 +#define KRK_OBJ_FLAGS_FUNCTION_MASK 0x0003 #define KRK_OBJ_FLAGS_FUNCTION_IS_CLASS_METHOD 0x0001 #define KRK_OBJ_FLAGS_FUNCTION_IS_STATIC_METHOD 0x0002 -#define KRK_OBJ_FLAGS_FUNCTION_IS_DYNAMIC_PROPERTY 0x0004 #define KRK_OBJ_FLAGS_NO_INHERIT 0x0200 #define KRK_OBJ_FLAGS_SECOND_CHANCE 0x0100 @@ -239,6 +238,8 @@ typedef struct KrkClass { KrkObj * _pos; KrkObj * _setattr; KrkObj * _format; + + size_t cacheIndex; } KrkClass; /** diff --git a/src/obj_base.c b/src/obj_base.c index c6f0820..639aaa3 100644 --- a/src/obj_base.c +++ b/src/obj_base.c @@ -23,18 +23,31 @@ KRK_Method(type,__init__) { #define CURRENT_CTYPE KrkClass * KRK_Method(type,__base__) { + if (argc > 1) return krk_runtimeError(vm.exceptions->typeError, "__base__ can not be reassigned"); return self->base ? OBJECT_VAL(self->base) : NONE_VAL(); } KRK_Method(type,__name__) { + if (argc > 1) { + if (!IS_STRING(argv[1])) return TYPE_ERROR(str,argv[1]); + self->name = AS_STRING(argv[1]); + } return self->name ? OBJECT_VAL(self->name) : NONE_VAL(); } KRK_Method(type,__file__) { + if (argc > 1) { + if (!IS_STRING(argv[1])) return TYPE_ERROR(str,argv[1]); + self->filename = AS_STRING(argv[1]); + } return self->filename ? OBJECT_VAL(self->filename) : NONE_VAL(); } KRK_Method(type,__doc__) { + if (argc > 1) { + if (!IS_STRING(argv[1])) return TYPE_ERROR(str,argv[1]); + self->docstring = AS_STRING(argv[1]); + } return self->docstring ? OBJECT_VAL(self->docstring) : NONE_VAL(); } @@ -88,10 +101,10 @@ void _createAndBind_type(void) { KrkClass * type = ADD_BASE_CLASS(vm.baseClasses->typeClass, "type", vm.baseClasses->objectClass); type->obj.flags |= KRK_OBJ_FLAGS_NO_INHERIT; - BIND_METHOD(type,__base__)->obj.flags = KRK_OBJ_FLAGS_FUNCTION_IS_DYNAMIC_PROPERTY; - BIND_METHOD(type,__file__)->obj.flags = KRK_OBJ_FLAGS_FUNCTION_IS_DYNAMIC_PROPERTY; - BIND_METHOD(type,__doc__) ->obj.flags = KRK_OBJ_FLAGS_FUNCTION_IS_DYNAMIC_PROPERTY; - BIND_METHOD(type,__name__)->obj.flags = KRK_OBJ_FLAGS_FUNCTION_IS_DYNAMIC_PROPERTY; + BIND_PROP(type,__base__); + BIND_PROP(type,__file__); + BIND_PROP(type,__doc__); + BIND_PROP(type,__name__); BIND_METHOD(type,__init__); BIND_METHOD(type,__str__); diff --git a/src/vm.c b/src/vm.c index 818c309..cc6625b 100644 --- a/src/vm.c +++ b/src/vm.c @@ -257,6 +257,8 @@ void krk_finalizeClass(KrkClass * _class) { {NULL, 0}, }; + _class->cacheIndex = 0; + for (struct TypeMap * entry = specials; entry->method; ++entry) { *entry->method = NULL; KrkClass * _base = _class; @@ -845,87 +847,6 @@ KrkValue krk_callDirect(KrkObj * callable, int argCount) { return NONE_VAL(); } -/** - * Attach a method call to its callee and return a BoundMethod. - * Works for managed and native method calls. - */ -int krk_bindMethod(KrkClass * _class, KrkString * name) { - KrkClass * originalClass = _class; - KrkValue method, out; - while (_class) { - if (krk_tableGet_fast(&_class->methods, name, &method)) break; - _class = _class->base; - } - if (!_class) return 0; - if (IS_NATIVE(method)||IS_CLOSURE(method)) { - if (AS_OBJECT(method)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_CLASS_METHOD) { - out = OBJECT_VAL(krk_newBoundMethod(OBJECT_VAL(originalClass), AS_OBJECT(method))); - } else if (AS_OBJECT(method)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_STATIC_METHOD) { - out = method; - } else if (AS_OBJECT(method)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_DYNAMIC_PROPERTY) { - out = AS_NATIVE(method)->function(1, (KrkValue[]){krk_peek(0)}, 0); - } else { - out = OBJECT_VAL(krk_newBoundMethod(krk_peek(0), AS_OBJECT(method))); - } - } else { - /* Does it have a descriptor __get__? */ - KrkClass * type = krk_getType(method); - if (type->_descget) { - krk_push(method); - krk_swap(1); - krk_push(krk_callDirect(type->_descget, 2)); - return 1; - } - out = method; - } - krk_pop(); - krk_push(out); - return 1; -} - -/** - * Similar to @ref krk_bindMethod but does not create bound method objects. - * @returns 1 if the result was an (unbound) method, 2 if it was something else, 0 if it was not found. - */ -int krk_unbindMethod(KrkClass * _class, KrkString * name) { - KrkClass * originalClass = _class; - KrkValue method, out; - while (_class) { - if (krk_tableGet_fast(&_class->methods, name, &method)) break; - _class = _class->base; - } - if (!_class) return 0; - if (IS_NATIVE(method) || IS_CLOSURE(method)) { - if (AS_OBJECT(method)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_DYNAMIC_PROPERTY) { - out = AS_NATIVE(method)->function(1, (KrkValue[]){krk_peek(0)}, 0); - } else if (AS_CLOSURE(method)->obj.flags & KRK_OBJ_FLAGS_FUNCTION_IS_CLASS_METHOD) { - krk_pop(); - krk_push(OBJECT_VAL(originalClass)); - krk_push(method); - return 1; - } else if (AS_CLOSURE(method)->obj.flags & KRK_OBJ_FLAGS_FUNCTION_IS_STATIC_METHOD) { - out = method; - } else { - krk_push(method); - return 1; - } - } else { - /* Does it have a descriptor __get__? */ - KrkClass * type = krk_getType(method); - if (type->_descget) { - krk_push(krk_peek(0)); - krk_push(method); - krk_swap(1); - krk_push(krk_callDirect(type->_descget, 2)); - return 2; - } - out = method; - } - krk_push(out); - return 2; -} - - /** * Capture upvalues and mark them as open. Called upon closure creation to * mark stack slots used by a function. @@ -1739,115 +1660,171 @@ int krk_doRecursiveModuleLoad(KrkString * name) { return krk_importModule(name,name); } -/** - * Try to resolve and push [stack top].name. - * If [stack top] is an instance, scan fields first. - * Otherwise, scan for methods from [stack top].__class__. - * Returns 0 if nothing was found, 1 if something was - and that - * "something" will replace [stack top]. - */ -static int valueGetProperty(KrkString * name) { - KrkValue this = krk_peek(0); - KrkClass * objectClass; - KrkValue value; - if (IS_INSTANCE(this)) { - KrkInstance * instance = AS_INSTANCE(this); - if (krk_tableGet_fast(&instance->fields, name, &value)) { - krk_currentThread.stackTop[-1] = value; - return 1; +#define CACHE_SIZE 4096 +typedef struct { + KrkString * name; + struct KrkClass * owner; + KrkValue value; + size_t index; +} KrkClassCacheEntry; +static KrkClassCacheEntry cache[CACHE_SIZE] = {0}; +static size_t nextCount = 1; + +static KrkClass * checkCache(KrkClass * type, KrkString * name, KrkValue * method) { + size_t index = (name->obj.hash ^ (type->obj.hash << 4)) & (CACHE_SIZE-1); + KrkClassCacheEntry * entry = &cache[index]; + if (entry->name == name && entry->index == type->cacheIndex) { + *method = entry->value; + return entry->owner; + } + + KrkClass * _class = NULL; + if (krk_tableGet_fast(&type->methods, name, method)) { + _class = type; + } else if (type->base) { + _class = checkCache(type->base, name, method); + } + + if (!type->cacheIndex) { + type->cacheIndex = nextCount++; + } + entry->name = name; + entry->owner = _class; + entry->value = *method; + entry->index = type->cacheIndex; + return _class; +} + +static void clearCache(KrkClass * type) { + if (type->cacheIndex) { + type->cacheIndex = 0; + for (size_t i = 0; i < type->subclasses.capacity; ++i) { + KrkTableEntry * entry = &type->subclasses.entries[i]; + if (entry->key == KWARGS_VAL(0)) continue; + clearCache(AS_CLASS(entry->key)); } - objectClass = instance->_class; - } else if (IS_CLASS(this)) { - KrkClass * _class = AS_CLASS(this); - do { - if (krk_tableGet_fast(&_class->methods, name, &value)) { - if ((IS_NATIVE(value) || IS_CLOSURE(value)) && (AS_OBJECT(value)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_CLASS_METHOD)) { - value = OBJECT_VAL(krk_newBoundMethod(this, AS_OBJECT(value))); - } - krk_currentThread.stackTop[-1] = value; - return 1; - } - _class = _class->base; - } while (_class); - objectClass = vm.baseClasses->typeClass; - } else if (IS_CLOSURE(krk_peek(0))) { - KrkClosure * closure = AS_CLOSURE(this); - if (krk_tableGet_fast(&closure->fields, name, &value)) { - krk_currentThread.stackTop[-1] = value; - return 1; - } - objectClass = vm.baseClasses->functionClass; - } else { - objectClass = krk_getType(this); } - - /* See if the base class for this non-instance type has a method available */ - if (krk_bindMethod(objectClass, name)) { - return 1; - } - - if (objectClass->_getattr) { - krk_push(OBJECT_VAL(name)); - krk_push(krk_callDirect(objectClass->_getattr, 2)); - return 1; - } - - return 0; } /** - * Similar to the above, but specifically for getting properties that will be - * immediately called, eg. for GET_METHOD that is followed by argument pushing - * and then CALL_METHOD. + * Attach a method call to its callee and return a BoundMethod. + * Works for managed and native method calls. */ +int krk_bindMethod(KrkClass * originalClass, KrkString * name) { + KrkValue method, out; + KrkClass * _class = checkCache(originalClass, name, &method); + if (!_class) return 0; + if (IS_NATIVE(method)||IS_CLOSURE(method)) { + if (AS_OBJECT(method)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_CLASS_METHOD) { + out = OBJECT_VAL(krk_newBoundMethod(OBJECT_VAL(originalClass), AS_OBJECT(method))); + } else if (AS_OBJECT(method)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_STATIC_METHOD) { + out = method; + } else { + out = OBJECT_VAL(krk_newBoundMethod(krk_peek(0), AS_OBJECT(method))); + } + } else { + /* Does it have a descriptor __get__? */ + KrkClass * type = krk_getType(method); + if (type->_descget) { + krk_push(method); + krk_swap(1); + krk_push(krk_callDirect(type->_descget, 2)); + return 1; + } + out = method; + } + krk_pop(); + krk_push(out); + return 1; +} + + static int valueGetMethod(KrkString * name) { KrkValue this = krk_peek(0); - KrkClass * objectClass; - KrkValue value; - if (IS_INSTANCE(this)) { - KrkInstance * instance = AS_INSTANCE(this); - if (krk_tableGet_fast(&instance->fields, name, &value)) { - krk_push(value); - return 2; + KrkClass * myClass = krk_getType(this); + KrkValue value, method; + KrkClass * _class = checkCache(myClass, name, &method); + + /* Class descriptors */ + if (_class) { + KrkClass * valtype = krk_getType(method); + if (valtype->_descget) { + krk_push(method); + krk_push(this); + value = krk_callDirect(valtype->_descget, 2); + goto found; } - objectClass = instance->_class; - } else if (IS_CLASS(this)) { - KrkClass * _class = AS_CLASS(this); - do { - if (krk_tableGet_fast(&_class->methods, name, &value)) { - if ((IS_CLOSURE(value)||IS_NATIVE(value)) && (AS_OBJECT(value)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_CLASS_METHOD)) { - krk_push(value); - return 1; - } - krk_push(value); - return 2; - } - _class = _class->base; - } while (_class); - objectClass = vm.baseClasses->typeClass; - } else if (IS_CLOSURE(krk_peek(0))) { - KrkClosure * closure = AS_CLOSURE(this); - if (krk_tableGet_fast(&closure->fields, name, &value)) { - krk_push(value); - return 2; - } - objectClass = vm.baseClasses->functionClass; - } else { - objectClass = krk_getType(this); } - /* See if the base class for this non-instance type has a method available */ - int maybe = krk_unbindMethod(objectClass, name); - if (maybe) return maybe; + /* Fields */ + if (IS_INSTANCE(this)) { + if (krk_tableGet_fast(&AS_INSTANCE(this)->fields, name, &value)) goto found; + } else if (IS_CLASS(this)) { + KrkClass * type = AS_CLASS(this); + do { + if (krk_tableGet_fast(&type->methods, name, &value)) { + if ((IS_NATIVE(value) || IS_CLOSURE(value)) && (AS_OBJECT(value)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_CLASS_METHOD)) { + goto found_method; + } + goto found; + } + type = type->base; + } while (type); + } else if (IS_CLOSURE(this)) { + if (krk_tableGet_fast(&AS_CLOSURE(this)->fields, name, &value)) goto found; + } - if (objectClass->_getattr) { - krk_push(krk_peek(0)); + /* Method from type */ + if (_class) { + if (IS_NATIVE(method)||IS_CLOSURE(method)) { + if (AS_OBJECT(method)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_CLASS_METHOD) { + krk_currentThread.stackTop[-1] = OBJECT_VAL(myClass); + value = method; + goto found_method; + } else if (AS_OBJECT(method)->flags & KRK_OBJ_FLAGS_FUNCTION_IS_STATIC_METHOD) { + value = method; + } else { + value = method; + goto found_method; + } + } else { + value = method; + } + goto found; + } + + /* __getattr__ */ + if (myClass->_getattr) { + krk_push(this); krk_push(OBJECT_VAL(name)); - krk_push(krk_callDirect(objectClass->_getattr, 2)); - return 2; + value = krk_callDirect(myClass->_getattr, 2); + goto found; } return 0; + +found: + krk_push(value); + return 2; + +found_method: + krk_push(value); + return 1; +} + +static int valueGetProperty(KrkString * name) { + switch (valueGetMethod(name)) { + case 2: + krk_currentThread.stackTop[-2] = krk_currentThread.stackTop[-1]; + krk_currentThread.stackTop--; + return 1; + case 1: + krk_currentThread.stackTop[-2] = OBJECT_VAL(krk_newBoundMethod(krk_currentThread.stackTop[-2], AS_OBJECT(krk_currentThread.stackTop[-1]))); + krk_currentThread.stackTop--; + return 1; + default: + return 0; + } } int krk_getAttribute(KrkString * name) { @@ -1893,6 +1870,8 @@ static int valueDelProperty(KrkString * name) { } if (name->length > 1 && name->chars[0] == '_' && name->chars[1] == '_') { krk_finalizeClass(_class); + } else { + clearCache(_class); } krk_pop(); /* the original value */ return 1; @@ -1922,20 +1901,15 @@ KrkValue krk_valueDelAttribute(KrkValue owner, char * name) { return NONE_VAL(); } -static int trySetDescriptor(KrkValue owner, KrkString * name, KrkValue value) { - KrkClass * _class = krk_getType(owner); +static int _setDescriptor(KrkValue owner, KrkClass * _class, KrkString * name, KrkValue to) { KrkValue property; - while (_class) { - if (krk_tableGet_fast(&_class->methods, name, &property)) break; - _class = _class->base; - } + _class = checkCache(_class, name, &property); if (_class) { KrkClass * type = krk_getType(property); if (type->_descset) { - /* Need to rearrange arguments */ - krk_push(property); /* owner value property */ - krk_swap(2); /* property value owner */ - krk_swap(1); /* property owner value */ + krk_push(property); + krk_push(owner); + krk_push(to); krk_push(krk_callDirect(type->_descset, 3)); return 1; } @@ -1943,66 +1917,45 @@ static int trySetDescriptor(KrkValue owner, KrkString * name, KrkValue value) { return 0; } +static KrkValue setAttr_wrapper(KrkValue owner, KrkClass * _class, KrkTable * fields, KrkString * name, KrkValue to) { + if (_setDescriptor(owner,_class,name,to)) return krk_pop(); + krk_tableSet(fields, OBJECT_VAL(name), to); + return to; +} + _noexport KrkValue krk_instanceSetAttribute_wrapper(KrkValue owner, KrkString * name, KrkValue to) { - if (!krk_tableSetIfExists(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name), to)) { - /* That might have raised an exception. */ - if (unlikely(krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) return NONE_VAL(); - /* Entry did not exist, check for properties */ - KrkClass * _class = krk_getType(owner); - KrkValue property; - do { - if (krk_tableGet_fast(&_class->methods, name, &property)) break; - _class = _class->base; - } while (_class); - if (_class) { - KrkClass * type = krk_getType(property); - if (type->_descset) { - krk_push(property); - krk_push(owner); - krk_push(to); - return krk_callDirect(type->_descset, 3); - } - } - /* No descriptor, go ahead and set. */ - krk_tableSet(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name), to); - } - return to; + return setAttr_wrapper(owner, AS_INSTANCE(owner)->_class, &AS_INSTANCE(owner)->fields, name, to); } static int valueSetProperty(KrkString * name) { KrkValue owner = krk_peek(1); KrkValue value = krk_peek(0); + KrkClass * type = krk_getType(owner); + if (unlikely(type->_setattr != NULL)) { + krk_push(OBJECT_VAL(name)); + krk_swap(1); + krk_push(krk_callDirect(type->_setattr, 3)); + return 1; + } if (IS_INSTANCE(owner)) { - KrkClass * type = AS_INSTANCE(owner)->_class; - if (unlikely(type->_setattr != NULL)) { - krk_push(OBJECT_VAL(name)); - krk_swap(1); - krk_push(krk_callDirect(type->_setattr, 3)); - return 1; - } - if (krk_tableSet(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name), value)) { - if (trySetDescriptor(owner, name, value)) { - krk_tableDelete(&AS_INSTANCE(owner)->fields, OBJECT_VAL(name)); - return 1; - } - } + krk_currentThread.stackTop[-1] = setAttr_wrapper(owner,type,&AS_INSTANCE(owner)->fields, name, value); } else if (IS_CLASS(owner)) { - krk_tableSet(&AS_CLASS(owner)->methods, OBJECT_VAL(name), value); + krk_currentThread.stackTop[-1] = setAttr_wrapper(owner,type,&AS_CLASS(owner)->methods, name, value); if (name->length > 1 && name->chars[0] == '_' && name->chars[1] == '_') { - /* Quietly call finalizeClass to update special method table if this looks like it might be one */ krk_finalizeClass(AS_CLASS(owner)); + } else { + clearCache(AS_CLASS(owner)); } } else if (IS_CLOSURE(owner)) { - /* Closures shouldn't have descriptors, but let's let this happen anyway... */ - if (krk_tableSet(&AS_CLOSURE(owner)->fields, OBJECT_VAL(name), value)) { - if (trySetDescriptor(owner, name, value)) { - krk_tableDelete(&AS_CLOSURE(owner)->fields, OBJECT_VAL(name)); - return 1; - } - } + krk_currentThread.stackTop[-1] = setAttr_wrapper(owner,type,&AS_CLOSURE(owner)->fields, name, value); } else { - return (trySetDescriptor(owner,name,value)); + if (_setDescriptor(owner,type,name,value)) { + krk_swap(1); + krk_pop(); + } else { + return 0; + } } krk_swap(1); krk_pop();