Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CALL_INTRINSIC instruction. #99005

Open
markshannon opened this issue Nov 2, 2022 · 7 comments
Open

Add CALL_INTRINSIC instruction. #99005

markshannon opened this issue Nov 2, 2022 · 7 comments
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) performance Performance or resource usage

Comments

@markshannon
Copy link
Member

markshannon commented Nov 2, 2022

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.

Candidates for moving into CALL_INTRINSIC are:

  • SETUP_ANNOTATIONS
  • LOAD_BUILD_CLASS
  • MATCH_KEYS
  • CHECK_EG_MATCH
  • CLEANUP_THROW

Linked PRs

@markshannon markshannon added the performance Performance or resource usage label Nov 2, 2022
@ericsnowcurrently
Copy link
Member

ericsnowcurrently commented Nov 2, 2022

Related discussion: faster-cpython/ideas#202

@ericsnowcurrently
Copy link
Member

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
Copy link
Member Author

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
Copy link
Member Author

markshannon commented 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
Copy link
Member Author

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
Copy link
Member Author

markshannon commented 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
Copy link
Member Author

markshannon commented 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.

markshannon added a commit that referenced this issue Jan 5, 2023
* Remove PRINT_EXPR instruction

* Remove STOPITERATION_ERROR instruction

* Remove IMPORT_STAR instruction
markshannon added a commit that referenced this issue Jan 6, 2023
* Remove UNARY_POSITIVE, LIST_TO_TUPLE and ASYNC_GEN_WRAP, replacing them with intrinsics.
@iritkatriel iritkatriel added the interpreter-core (Objects, Python, Grammar, and Parser dirs) label Nov 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) performance Performance or resource usage
Projects
None yet
Development

No branches or pull requests

3 participants