Skip to content

Add CALL_INTRINSIC instruction. #99005

Description

@markshannon

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

Activity

  1. ericsnowcurrently commented on Nov 2, 2022

    @ericsnowcurrently
    Member

    Related discussion: faster-cpython/ideas#202

  2. ericsnowcurrently commented on Nov 2, 2022

    @ericsnowcurrently
    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
  3. markshannon commented on Nov 3, 2022

    @markshannon
    MemberAuthor

    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)

  4. markshannon commented on Nov 3, 2022

    @markshannon
    MemberAuthor

    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.

  5. markshannon commented on Nov 3, 2022

    @markshannon
    MemberAuthor

    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.

  6. markshannon commented on Nov 3, 2022

    @markshannon
    MemberAuthor

    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
  7. markshannon commented on Jan 5, 2023

    @markshannon
    MemberAuthor

    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.

  8. added 2 commits that reference this issue on Jan 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)performancePerformance or resource usage

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions