gh-98831: Modernize CALL and family by gvanrossum · Pull Request #101508 · python/cpython
There are a few things about this family that make it unpleasant to convert.
First, the initial call stack can be either
[method, self, arg1, arg2, ...]
or
[NULL, callable, arg1, arg2, ...]
(This situation arises because the LOAD_ATTR instruction dynamically decides whether to produce one or the other.)
In the latter case we then introspect callable and if it is a bound method, we extract the im_func and im_self fields and convert to the former format -- but if the callable is not a bound method, we leave the NULL in place.
There is a macro is_method(stack_pointer, oparg) that, despite its lofty name, really just checks whether there's a NULL at the indicated position (using PEEK(oparg+1), it doesn't even use the stack_pointer argument). In the converted instructions I am modeling this as an input stack effect of the form thing1, thing2, args[oparg], and I replace the is_method() macro with thing1 != NULL. Everything else then gets a little awkward, because the thing we need to call in the end is either thing1 or thing2, and the number of arguments is either oparg or oparg+1. It also means that the first argument is not the start of the args array -- it is either that or one less.
Second, most specializations don't just call the function and produce a result. Because of Python function call inlining, most end up with DISPATCH_INLINE(), and because of that we can't leave the popping of the stack to the generated code (it would just be STACK_SHRINK(oparg+1)) -- we have to manually do this, and while we're at it we also either DECREF the arguments (including self, if present), or copy them into a new frame, depending on what we're calling.
All this leaves me with the feeling that the generator isn't a good match for the complexity of this instruction family, or I am missing a trick.
Maybe I should model the input stack effect as method, callable, args[oparg] and do something like
if (method != NULL) {
callable = method;
args -= 1;
oparg += 1;
}
so that we can always call callable with oparg arguments starting at args. But we still need to recall whether we did this, so we know where to push the result.