You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We 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.
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)
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.
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.
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.
Activity
ericsnowcurrently commentedon Nov 2, 2022
Related discussion: faster-cpython/ideas#202
ericsnowcurrently commentedon Nov 2, 2022
What about the other instructions mentioned in faster-cpython/ideas#202?
markshannon commentedon Nov 3, 2022
Those as well, with a few exceptions:
LOAD_ASSERTION_ERRORwill probably end up inLOAD_COMMON_CONSTor similar.RAISE_VARARGSis awkward as it takes a variable number of operands.BEFORE_ASYNC_WITHandBEFORE_WITHcan be lowered: faster-cpython/ideas#398 (comment)markshannon commentedon Nov 3, 2022
A tricky part is that not many of the above instruction take the form of a simple call.
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_VARARGSwe need to passargcountto the function. It is free and adds more flexibility.With that in mind,
CALL_INSTRINSICwould look something like:One downside of this is we can only determine the stack effect of
CALL_INSTRINSICwith a lookup table.markshannon commentedon Nov 3, 2022
IMPORT_NAMEandIMPORT_FROMtake an operand, so we need to be changed to take the name from the stack.So
IMPORT_NAME index_of_namewould need to becomeLOAD_CONST name; IMPORT_NAME.markshannon commentedon Nov 3, 2022
Some other instructions that are very rare, but take an operand, that might be nice to turn into intrinsic functions:
markshannon commentedon 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 aCALL_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_FROMMATCH_KEYS,CHECK_EG_MATCH,IMPORT_FROMhave 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_INTRINSICfor 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.
GH-99005: Add `CALL_INTRINSIC_1` instruction (GH-100771)
GH-99005: More intrinsics (GH-100774)