GeezerVison is showing github.com — Add `CALL_INTRINSIC` instruction. · Issue #99005 · python/cpython
Add CALL_INTRINSIC instruction. #99005DescriptionWe have a number of instructions that are complicated and executed fairly rarely. For example MAP_KEYS, CHECK_EG_MATCH, CLEANUP_THROW.
These bulk out the interpreter, possibly slowing things down.
We should move code from these into helper functions, which can be called though a table from CALL_INTRINSIC instruction.
The CALL_INTRINSIC instruction also provides a means for contributors to add new functionality without a deep understanding of the compiler.
Candidates for moving into CALL_INTRINSIC are:
- SETUP_ANNOTATIONS
- LOAD_BUILD_CLASS
- MATCH_KEYS
- CHECK_EG_MATCH
- CLEANUP_THROW
Linked PRs
Activityericsnowcurrently commented on Nov 2, 2022 ericsnowcurrently commented on Nov 2, 2022 What about the other instructions mentioned in faster-cpython/ideas#202?
- LOAD_ASSERTION_ERROR
- IMPORT_NAME
- IMPORT_FROM
- PRINT_EXPR
- PREP_RERAISE_STAR
- RAISE_VARARGS
- BEFORE_ASYNC_WITH
- BEFORE_WITH
markshannon commented on Nov 3, 2022 Those as well, with a few exceptions:
LOAD_ASSERTION_ERROR will probably end up in LOAD_COMMON_CONST or similar.
RAISE_VARARGS is awkward as it takes a variable number of operands.
BEFORE_ASYNC_WITH and BEFORE_WITH can be lowered: faster-cpython/ideas#398 (comment) markshannon commented on Nov 3, 2022 A tricky part is that not many of the above instruction take the form of a simple call.
| Instruction |
Stack effect |
Call-like |
| SETUP_ANNOTATIONS |
--- |
Yes, if we make it return a dummy value |
| LOAD_BUILD_CLASS |
--- cls (+1) |
Yes |
| MATCH_KEYS |
subject keys -- subject keys values (+1) |
No |
| CHECK_EG_MATCH |
exc type -- exc match (0) |
No |
| CLEANUP_THROW |
iter sent exc -- value (-1) |
Yes |
| IMPORT_NAME |
level fromlist --- res (-1) |
Yes |
| IMPORT_FROM |
from --- from res (+1) |
No |
| PRINT_EXPR |
value --- (-1) |
Yes, if we make it return a dummy value |
| PREP_RERAISE_STAR |
excs orig --- val (-1) |
Yes |
| RAISE_VARARGS |
(1 to 3) --- |
Maybe? |
| RERAISE |
exc --- |
Yes |
| STOPITERATION_ERROR |
exc -- exc (0) |
Yes |
So, instead of passing arguments, we could pass the stack. The instrinsic function would return how many values it left on the stack, or -1 for an error.
The instruction would need to encode the function to be called (4 bits), the arguments taken (2 bits), which fits easily into the one byte oparg.
To support RAISE_VARARGS we need to pass argcount to the function. It is free and adds more flexibility.
With that in mind, CALL_INSTRINSIC would look something like:
inst(CALL_INSTRINSIC) {
assert(oparg > 256);
int argcount = oparg & 15;
int func_id = oparg >> 4;
functpr func = IntrinsicFunctions[func_id];
int returned_args = func(sp-argcount, argcount);
if (returned_args < 0) {
/* In event of error, function should not modify stack depth */
goto error;
}
sp += returned_args - argcount;
}
One downside of this is we can only determine the stack effect of CALL_INSTRINSIC with a lookup table. markshannon commented on Nov 3, 2022 IMPORT_NAME and IMPORT_FROM take an operand, so we need to be changed to take the name from the stack.
So IMPORT_NAME index_of_name would need to become LOAD_CONST name; IMPORT_NAME.
markshannon commented on Nov 3, 2022 Some other instructions that are very rare, but take an operand, that might be nice to turn into intrinsic functions:
- DELETE_DEREF
- DELETE_NAME
- LOAD_CLASSDEREF
markshannon commented on Jan 5, 2023 Given that we plan to move to a register-based interpreter, the above scheme of adjusting the stack pointer no longer makes sense.
So, I'm going with a simpler approach of adding a CALL_INTRINSIC_1 (and maybe a CALL_INTRINSIC_2) instruction which will take 1 (or 2) value(s) from the stack and push one result.
All the instruction marked as "call-like" can be implemented this way, for either stack and register VM.
MATCH_KEYS, CHECK_EG_MATCH, IMPORT_FROM
MATCH_KEYS, CHECK_EG_MATCH, IMPORT_FROM have multiple outputs.
Although these produce multiple values, they leave their inputs unchanged.
With a register machine, the compiler handles liveness of the inputs, so we can use CALL_INTRINSIC for these as well.
CLEANUP_THROW
This takes three inputs, but it only uses one of those, so the compiler can just discard the other two values, and can thus be implemented with CALL_INTRINSIC_1.
It is nice to see the register VM making things simpler than the stack VM in some cases.
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|