Optimized method invoke

This commit is contained in:
K. Lange 2022-05-05 09:10:54 +09:00
parent d947e1aba3
commit 5df1469ba1
4 changed files with 183 additions and 15 deletions

View File

@ -95,6 +95,7 @@ typedef enum {
EXPR_CAN_ASSIGN, /**< This expression may be an assignment target, check for assignment operators at the end. */
EXPR_ASSIGN_TARGET, /**< This expression is definitely an assignment target or chained to one. */
EXPR_DEL_TARGET, /**< This expression is in the target list of a 'del' statement. */
EXPR_METHOD_CALL, /**< This expression is the parameter list of a method call; only used by @ref dot and @ref call */
} ExpressionType;
/**
@ -367,6 +368,7 @@ static KrkToken decorator(size_t level, FunctionType type);
static void complexAssignment(ChunkRecorder before, KrkScanner oldScanner, Parser oldParser, size_t targetCount, int parenthesized);
static void complexAssignmentTargets(KrkScanner oldScanner, Parser oldParser, size_t targetCount, int parenthesized);
static int invalidTarget(int exprType, const char * description);
static void call(int exprType);
/* These are not the real parse functions. */
static void commaX(int exprType) { }
@ -1023,6 +1025,9 @@ _dotDone:
EMIT_OPERAND_OP(OP_SET_PROPERTY, ind);
} else if (exprType == EXPR_DEL_TARGET && checkEndOfDel()) {
EMIT_OPERAND_OP(OP_DEL_PROPERTY, ind);
} else if (match(TOKEN_LEFT_PAREN)) {
EMIT_OPERAND_OP(OP_GET_METHOD, ind);
call(EXPR_METHOD_CALL);
} else {
EMIT_OPERAND_OP(OP_GET_PROPERTY, ind);
}
@ -3256,7 +3261,12 @@ static void call(int exprType) {
*/
argCount += 1 /* for the sentinel */ + 2 * specialArgs;
}
EMIT_OPERAND_OP(OP_CALL, argCount);
if (exprType == EXPR_METHOD_CALL) {
EMIT_OPERAND_OP(OP_CALL_METHOD, argCount);
} else {
EMIT_OPERAND_OP(OP_CALL, argCount);
}
invalidTarget(exprType, "function call");
}

View File

@ -104,6 +104,8 @@ typedef enum {
OP_MAKE_SET,
OP_REVERSE,
OP_SLICE,
OP_GET_METHOD,
OP_CALL_METHOD,
/* Two opcode instructions */
OP_JUMP_IF_FALSE_OR_POP,
@ -148,6 +150,8 @@ typedef enum {
OP_MAKE_SET_LONG,
OP_REVERSE_LONG,
OP_SLICE_LONG,
OP_GET_METHOD_LONG,
OP_CALL_METHOD_LONG,
} KrkOpCode;
/**

View File

@ -58,6 +58,7 @@ CONSTANT(OP_CLOSURE, CLOSURE_MORE)
CONSTANT(OP_IMPORT, (void)0)
CONSTANT(OP_IMPORT_FROM, (void)0)
CONSTANT(OP_GET_SUPER, (void)0)
CONSTANT(OP_GET_METHOD, (void)0)
OPERAND(OP_KWARGS, (void)0)
OPERAND(OP_SET_LOCAL, LOCAL_MORE)
OPERAND(OP_GET_LOCAL, LOCAL_MORE)
@ -76,6 +77,7 @@ OPERAND(OP_MAKE_DICT, (void)0)
OPERAND(OP_MAKE_SET, (void)0)
OPERAND(OP_REVERSE, (void)0)
OPERAND(OP_SLICE, (void)0)
OPERAND(OP_CALL_METHOD, (void)0)
JUMP(OP_JUMP_IF_FALSE_OR_POP,+)
JUMP(OP_JUMP_IF_TRUE_OR_POP,+)
JUMP(OP_JUMP,+)

180
src/vm.c
View File

@ -584,7 +584,7 @@ int krk_processComplexArguments(int argCount, KrkValueArray * positionals, KrkTa
* `extra` is passed by `callValue` to tell us which case we have, and thus
* where we need to restore the stack to when we return from this call.
*/
static int call(KrkClosure * closure, int argCount, int callableOnStack) {
static int call(KrkClosure * closure, int argCount, int returnDepth) {
size_t potentialPositionalArgs = closure->function->requiredArgs + closure->function->keywordArgs;
size_t totalArguments = closure->function->requiredArgs + closure->function->keywordArgs + !!(closure->function->flags & KRK_CODEOBJECT_FLAGS_COLLECTS_ARGS) + !!(closure->function->flags & KRK_CODEOBJECT_FLAGS_COLLECTS_KWS);
size_t offsetOfExtraArgs = closure->function->requiredArgs + closure->function->keywordArgs;
@ -726,7 +726,7 @@ _finishKwarg:
if (unlikely(closure->function->flags & (KRK_CODEOBJECT_FLAGS_IS_GENERATOR | KRK_CODEOBJECT_FLAGS_IS_COROUTINE))) {
KrkInstance * gen = krk_buildGenerator(closure, krk_currentThread.stackTop - argCount, argCount);
krk_currentThread.stackTop = krk_currentThread.stackTop - argCount - callableOnStack;
krk_currentThread.stackTop = krk_currentThread.stackTop - argCount - returnDepth;
krk_push(OBJECT_VAL(gen));
return 2;
}
@ -740,7 +740,7 @@ _finishKwarg:
frame->closure = closure;
frame->ip = closure->function->chunk.code;
frame->slots = (krk_currentThread.stackTop - argCount) - krk_currentThread.stack;
frame->outSlots = (krk_currentThread.stackTop - argCount - callableOnStack) - krk_currentThread.stack;
frame->outSlots = (krk_currentThread.stackTop - argCount - returnDepth) - krk_currentThread.stack;
frame->globals = &closure->function->globalsContext->fields;
FRAME_IN(frame);
return 1;
@ -774,11 +774,11 @@ _errorAfterKeywords:
* If callValue returns 0, the VM should already be in the exception state
* and it is not necessary to raise another exception.
*/
int krk_callValue(KrkValue callee, int argCount, int callableOnStack) {
int krk_callValue(KrkValue callee, int argCount, int returnDepth) {
if (likely(IS_OBJECT(callee))) {
switch (OBJECT_TYPE(callee)) {
case KRK_OBJ_CLOSURE:
return call(AS_CLOSURE(callee), argCount, callableOnStack);
return call(AS_CLOSURE(callee), argCount, returnDepth);
case KRK_OBJ_NATIVE: {
NativeFn native = (NativeFn)AS_NATIVE(callee)->function;
if (unlikely(argCount && IS_KWARGS(krk_currentThread.stackTop[-1]))) {
@ -790,7 +790,7 @@ int krk_callValue(KrkValue callee, int argCount, int callableOnStack) {
return 0;
}
argCount--; /* Because that popped the kwargs value */
krk_currentThread.stackTop -= argCount + callableOnStack; /* We can just put the stack back to normal */
krk_currentThread.stackTop -= argCount + returnDepth; /* We can just put the stack back to normal */
krk_push(myList);
krk_push(myDict);
krk_currentThread.scratchSpace[0] = NONE_VAL();
@ -814,7 +814,7 @@ int krk_callValue(KrkValue callee, int argCount, int callableOnStack) {
free(stackCopy);
}
if (unlikely(krk_currentThread.stackTop == krk_currentThread.stack)) return 0;
krk_currentThread.stackTop -= argCount + callableOnStack;
krk_currentThread.stackTop -= argCount + returnDepth;
krk_push(result);
}
return 2;
@ -822,7 +822,7 @@ int krk_callValue(KrkValue callee, int argCount, int callableOnStack) {
case KRK_OBJ_INSTANCE: {
KrkClass * _class = AS_INSTANCE(callee)->_class;
if (likely(_class->_call != NULL)) {
return krk_callValue(OBJECT_VAL(_class->_call), argCount + 1, 0);
return krk_callValue(OBJECT_VAL(_class->_call), argCount + 1, returnDepth ? (returnDepth - 1) : 0);
} else {
krk_runtimeError(vm.exceptions->typeError, "'%s' object is not callable", krk_typeName(callee));
return 0;
@ -833,7 +833,7 @@ int krk_callValue(KrkValue callee, int argCount, int callableOnStack) {
KrkInstance * newInstance = krk_newInstance(_class);
krk_currentThread.stackTop[-argCount - 1] = OBJECT_VAL(newInstance);
if (likely(_class->_init != NULL)) {
return krk_callValue(OBJECT_VAL(_class->_init), argCount + 1, 0);
return krk_callValue(OBJECT_VAL(_class->_init), argCount + 1, returnDepth ? (returnDepth - 1) : 0);
} else if (unlikely(argCount != 0)) {
krk_runtimeError(vm.exceptions->typeError, "%s() takes no arguments (%d given)",
_class->name->chars, argCount);
@ -848,7 +848,7 @@ int krk_callValue(KrkValue callee, int argCount, int callableOnStack) {
krk_runtimeError(vm.exceptions->argumentError, "???");
return 0;
}
return krk_callValue(OBJECT_VAL(bound->method), argCount + 1, 0);
return krk_callValue(OBJECT_VAL(bound->method), argCount + 1, returnDepth ? (returnDepth - 1): 0);
}
default:
break;
@ -926,6 +926,61 @@ int krk_bindMethod(KrkClass * _class, KrkString * name) {
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)) {
if (((KrkNative*)AS_OBJECT(method))->flags & KRK_NATIVE_FLAGS_IS_DYNAMIC_PROPERTY) {
out = AS_NATIVE(method)->function(1, (KrkValue[]){krk_peek(0)}, 0);
} else if (((KrkNative*)AS_OBJECT(method))->flags & KRK_NATIVE_FLAGS_IS_CLASS_METHOD) {
krk_pop(); /* the object */
krk_push(OBJECT_VAL(originalClass));
krk_push(method);
return 1;
} else if (((KrkNative*)AS_OBJECT(method))->flags & KRK_NATIVE_FLAGS_IS_STATIC_METHOD) {
out = method;
} else {
krk_push(method);
return 1;
}
} else if (IS_CLOSURE(method)) {
if (AS_CLOSURE(method)->flags & KRK_FUNCTION_FLAGS_IS_CLASS_METHOD) {
krk_pop();
krk_push(OBJECT_VAL(originalClass));
krk_push(method);
return 1;
} else if (AS_CLOSURE(method)->flags & KRK_FUNCTION_FLAGS_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.
@ -1384,11 +1439,16 @@ static KrkValue tryBind(const char * name, KrkValue a, KrkValue b, const char *
krk_push(OBJECT_VAL(methodName));
/* Bind from a */
int res;
KrkClass * type = krk_getType(a);
krk_push(a);
if (krk_bindMethod(type, methodName)) {
if ((res = krk_unbindMethod(type, methodName))) {
krk_swap(1);
if (res == 2) {
krk_pop();
}
krk_push(b);
value = krk_callStack(1);
value = krk_callStack(res == 2 ? 1 : 2);
if (!IS_NOTIMPL(value)) goto _success;
krk_pop(); /* name */
} else {
@ -1401,9 +1461,13 @@ static KrkValue tryBind(const char * name, KrkValue a, KrkValue b, const char *
krk_push(OBJECT_VAL(methodName));
type = krk_getType(b);
krk_push(b);
if (krk_bindMethod(type, methodName)) {
if ((res = krk_unbindMethod(type, methodName))) {
krk_swap(1);
if (res == 2) {
krk_pop();
}
krk_push(a);
value = krk_callStack(1);
value = krk_callStack(res == 2 ? 1 : 2);
if (!IS_NOTIMPL(value)) goto _success;
krk_pop(); /* name */
} else {
@ -1907,6 +1971,64 @@ static int valueGetProperty(KrkString * name) {
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.
*/
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;
}
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) && (AS_CLOSURE(value)->flags & KRK_FUNCTION_FLAGS_IS_CLASS_METHOD)) ||
(IS_NATIVE(value) && (AS_NATIVE(value)->flags & KRK_NATIVE_FLAGS_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;
if (objectClass->_getattr) {
krk_push(krk_peek(0));
krk_push(OBJECT_VAL(name));
krk_push(krk_callDirect(objectClass->_getattr, 2));
return 2;
}
return 0;
}
KrkValue krk_valueGetAttribute(KrkValue value, char * name) {
krk_push(OBJECT_VAL(krk_copyString(name,strlen(name))));
krk_push(value);
@ -2576,6 +2698,18 @@ _finishReturn: (void)0;
frame = &krk_currentThread.frames[krk_currentThread.frameCount - 1];
break;
}
case OP_CALL_METHOD_LONG:
THREE_BYTE_OPERAND;
case OP_CALL_METHOD: {
ONE_BYTE_OPERAND;
if (IS_NONE(krk_peek(OPERAND+1))) {
if (unlikely(!krk_callValue(krk_peek(OPERAND), OPERAND, 2))) goto _finishException;
} else {
if (unlikely(!krk_callValue(krk_peek(OPERAND+1), OPERAND+1, 1))) goto _finishException;
}
frame = &krk_currentThread.frames[krk_currentThread.frameCount - 1];
break;
}
case OP_EXPAND_ARGS_LONG:
THREE_BYTE_OPERAND;
case OP_EXPAND_ARGS: {
@ -2737,6 +2871,24 @@ _finishReturn: (void)0;
krk_pop();
break;
}
case OP_GET_METHOD_LONG:
THREE_BYTE_OPERAND;
case OP_GET_METHOD: {
ONE_BYTE_OPERAND;
KrkString * name = READ_STRING(OPERAND);
int result = valueGetMethod(name);
if (result == 2) {
krk_push(NONE_VAL());
krk_swap(2);
krk_pop();
} else if (unlikely(!result)) {
krk_runtimeError(vm.exceptions->attributeError, "'%s' object has no attribute '%s'", krk_typeName(krk_peek(0)), name->chars);
goto _finishException;
} else {
krk_swap(1); /* unbound-method object */
}
break;
}
case OP_DUP_LONG:
THREE_BYTE_OPERAND;
case OP_DUP: