GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-131798: JIT: Split `CALL_ISINSTANCE` into several uops by tomasr8 · Pull Request #133339 · python/cpython

Conversation

@tomasr8

@tomasr8 tomasr8 commented May 3, 2025 •
edited by bedevere-app Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Split CALL_ISINSTANCE into two guards and the uop itself.

It will be easier to implement #133172 with this in place.

  • Issue: Better uop coverage in the JIT optimizer #131798

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
tomasr8 added 3 commits May 3, 2025 13:34
@tomasr8
Split CALL_ISIINSTANCE into several uops
43ec167
@tomasr8
Add news entry
0ab70a9
@tomasr8
Close all stackrefs
900472a
@tomasr8
tomasr8 requested review from Fidget-Spinner and markshannon as code owners May 3, 2025 11:54
@bedevere-app bedevere-app Bot mentioned this pull request May 3, 2025
Better uop coverage in the JIT optimizer #131798
Open
@bedevere-app bedevere-app Bot added the awaiting review label May 3, 2025
@tomasr8 tomasr8 mentioned this pull request May 3, 2025
gh-131798: JIT: Narrow the return type of isinstance for some known arguments #133172
Merged
@tomasr8
tomasr8 requested a review from brandtbucher May 3, 2025 17:18
brandtbucher
brandtbucher requested changes May 3, 2025
View reviewed changes

@brandtbucher brandtbucher left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I just see an opportunity for further cleanup!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/bytecodes.c Outdated
Comment on lines +4352 to +4354
op(_GUARD_CALLABLE_ISINSTANCE_NULL, (callable, null, unused[oparg] -- callable, null, unused[oparg])) {
DEOPT_IF(!PyStackRef_IsNull(null));
}

@brandtbucher brandtbucher May 3, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since now we know that the oparg is two, we can split out the two args and get rid of the array. Also, let's give this a better name (since it's part of the same logical family as _GUARD_TOS_NULL and _GUARD_NOS_NULL).

Suggested change
op(_GUARD_CALLABLE_ISINSTANCE_NULL, (callable, null, unused[oparg] -- callable, null, unused[oparg])) {
DEOPT_IF(!PyStackRef_IsNull(null));
}
op(_GUARD_THIRD_NULL, (null, unused, unused -- null, unused, unused)) {
DEOPT_IF(!PyStackRef_IsNull(null));
}

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

tomasr8 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

@tomasr8 tomasr8 May 3, 2025

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I agree it makes sense to make it reusable. I also moved it just under _GUARD_NOS_NULL to help with discoverability

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

brandtbucher reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Python/bytecodes.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Python/bytecodes.c Outdated
DEOPT_IF(callable_o != interp->callable_cache.isinstance);
}

op(_CALL_ISINSTANCE, (callable, null, args[oparg] -- res)) {

@brandtbucher brandtbucher May 3, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here:

Suggested change
op(_CALL_ISINSTANCE, (callable, null, args[oparg] -- res)) {
op(_CALL_ISINSTANCE, (callable, null, inst, cls -- res)) {

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@tomasr8 tomasr8 May 3, 2025

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inst is a reserved keyword so I went with inst_ :)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/optimizer_bytecodes.c Resolved

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot added awaiting changes and removed awaiting review labels May 3, 2025
@bedevere-app

bedevere-app Bot commented May 3, 2025

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

tomasr8 added 2 commits May 3, 2025 22:22
@tomasr8
Rename to _GUARD_THIRD_NULL
6f49dca
@tomasr8
Unpack args array into separate stack variables
8ba53c3
tomasr8
tomasr8 commented May 3, 2025
View reviewed changes
Comment thread Python/bytecodes.c Outdated
}
(void)null; // Silence compiler warnings about unused variables
PyStackRef_CLOSE(cls);
PyStackRef_CLOSE(inst_);

@tomasr8 tomasr8 May 3, 2025

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting that with named stackrefs it won't let me do this:

DEAD(null);
PyStackRef_CLOSE(cls);
PyStackRef_CLOSE(inst_);

(the error is SyntaxError: Input 'null' is not live, but 'inst_' is)
While with the previous args version this was fine:

DEAD(null);
PyStackRef_CLOSE(args[0]);
PyStackRef_CLOSE(args[1]);

I guess the cases generator can't reason about arrays? Another reason to use named stackrefs instead :)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

brandtbucher reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
@tomasr8

tomasr8 commented May 3, 2025

Copy link
Copy Markdown
Member Author

CI looks good so: I have made the requested changes; please review again :)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot added awaiting change review and removed awaiting changes labels May 3, 2025
@bedevere-app

bedevere-app Bot commented May 3, 2025

Copy link
Copy Markdown

Thanks for making the requested changes!

@brandtbucher: please review the changes made to this pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app
bedevere-app Bot requested a review from brandtbucher May 3, 2025 21:10
brandtbucher
brandtbucher reviewed May 6, 2025
View reviewed changes

@brandtbucher brandtbucher left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One fix to the test:

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_capi/test_opt.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Python/bytecodes.c Outdated
DEOPT_IF(callable_o != interp->callable_cache.isinstance);
}

op(_CALL_ISINSTANCE, (callable, null, inst_, cls -- res)) {

@brandtbucher brandtbucher May 6, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love the trailing underscore. Not a huge deal, but maybe just rename to instance or obj or something ?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@tomasr8 tomasr8 May 8, 2025

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed it to instance in be50e24

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@brandtbucher brandtbucher changed the title gh-131798: JIT: Split CALL_ISINSTANCE into severeal uops gh-131798: JIT: Split CALL_ISINSTANCE into several uops May 6, 2025
tomasr8 and others added 4 commits May 8, 2025 18:54
@tomasr8 @brandtbucher
Fix tests
6e11442
Co-authored-by: Brandt Bucher <[email protected]>
@tomasr8
Rename parameter
be50e24
@tomasr8
Merge remote-tracking branch 'upstream/main' into jit-split-isinstance
ec61bc5
@tomasr8
Regen cases
b0b31dd
@Fidget-Spinner

Fidget-Spinner commented May 8, 2025

Copy link
Copy Markdown
Member

I think the tests could be more robust if there was a way to run the executors without optimizing the uops. Each test could then first use assertIn to ensure the relevant uops are in fact present and only then run with optimizations enabled. Though dynamically turning off optimizations would require some changes in the interpreter so I'm not sure if it's worth it.

You could do this if you want. Mark wanted to do this, but no one has time to implement it proper.

The steps to do this would be the following:

  1. Export some C API for optimize_uops in _textinternalcapi.c or something.
  2. The API should take a bytecode offset/slice to start trace projection from.
  3. Write our optimizer tests to just use that.
tomasr8 reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@tomasr8

tomasr8 commented May 8, 2025

Copy link
Copy Markdown
Member Author

Ok I'll give it a try! Seems like a nice project for the weekend (or for PyCon if I can't get it working by then 😄)

Thanks for writing down the individual steps for me, it's super helpful :)

Fidget-Spinner reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@tomasr8
tomasr8 requested a review from brandtbucher May 8, 2025 20:18
brandtbucher
brandtbucher approved these changes May 8, 2025
View reviewed changes
@bedevere-app bedevere-app Bot added awaiting merge and removed awaiting change review labels May 8, 2025
@brandtbucher
brandtbucher merged commit c492ac7 into python:main May 8, 2025
@bedevere-app bedevere-app Bot removed the awaiting merge label May 8, 2025
@tomasr8
tomasr8 deleted the jit-split-isinstance branch May 8, 2025 21:30
@tomasr8

tomasr8 commented May 9, 2025

Copy link
Copy Markdown
Member Author

I think the tests could be more robust if there was a way to run the executors without optimizing the uops. Each test could then first use assertIn to ensure the relevant uops are in fact present and only then run with optimizations enabled. Though dynamically turning off optimizations would require some changes in the interpreter so I'm not sure if it's worth it.

You could do this if you want. Mark wanted to do this, but no one has time to implement it proper.

The steps to do this would be the following:

1. Export some C API for `optimize_uops` in `_textinternalcapi.c` or something.

2. The API should take a bytecode offset/slice to start trace projection from.

3. Write our optimizer tests to just use that.

@Fidget-Spinner, I noticed that the optimizer can already be turned off by setting PYTHON_UOPS_OPTIMIZE to '0':

cpython/Python/optimizer.c

Lines 1280 to 1288 in 98e2c3a

char *env_var = Py_GETENV("PYTHON_UOPS_OPTIMIZE");
if (env_var == NULL || *env_var == '\0' || *env_var > '0') {
length = _Py_uop_analyze_and_optimize(frame, buffer,
length,
curr_stackentries, &dependencies);
if (length <= 0) {
return length;
}
}

Rather than exposing a new internal api, I think we could simply toggle this variable in the tests. I'm not sure if this is what Mark intended as this would still test more than just the optimizer proper. Though I like that these tests are more end-to-end.

Anyway, here's what it'd look like. Let me know if this is something we want to pursue, otherwise I'll go back to thinking how to expose just the optimizer :)

diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py
index 651148336f7..b82b36fa2f5 100644
--- a/Lib/test/test_capi/test_opt.py
+++ b/Lib/test/test_capi/test_opt.py
@@ -11,6 +11,7 @@
 from test.support import (script_helper, requires_specialization,
                           import_helper, Py_GIL_DISABLED, requires_jit_enabled,
                           reset_code)
+from test.support.os_helper import EnvironmentVarGuard
 
 _testinternalcapi = import_helper.import_module("_testinternalcapi")
 
@@ -458,6 +459,12 @@ def _run_with_optimizer(self, testfunc, arg):
         ex = get_first_executor(testfunc)
         return res, ex
 
+    def _run_without_optimizer(self, testfunc, arg):
+        with EnvironmentVarGuard() as env:
+            env["PYTHON_UOPS_OPTIMIZE"] = "0"
+            res = testfunc(arg)
+        ex = get_first_executor(testfunc)
+        return res, ex
 
     def test_int_type_propagation(self):
         def testfunc(loops):
@@ -1951,6 +1958,17 @@ def testfunc(n):
                     x += 1
             return x
 
+        res, ex = self._run_without_optimizer(testfunc, TIER2_THRESHOLD)
+        self.assertEqual(res, TIER2_THRESHOLD)
+        self.assertIsNotNone(ex)
+        uops = get_opnames(ex)
+        self.assertIn("_CALL_ISINSTANCE", uops)
+        self.assertIn("_GUARD_THIRD_NULL", uops)
+        self.assertIn("_GUARD_CALLABLE_ISINSTANCE", uops)
+
+        # Invalidate the executor to force a reoptimization
+        _testinternalcapi.invalidate_executors(testfunc.__code__)
+
         res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD)
         self.assertEqual(res, TIER2_THRESHOLD)
         self.assertIsNotNone(ex)
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@Fidget-Spinner

Fidget-Spinner commented May 9, 2025

Copy link
Copy Markdown
Member

@tomasr8 yeah I'm not advocating for removing the end-to-end tests altogether. Rather, the optimizer tests should have a mix of both end-to-end and unit.

For example, it would be nice if we could generate an optimized trace without having to even wrap it in a for loop and run till JIT threshold. This actually makes the tests really slow because JIT threshold is quite high right now.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@tomasr8

tomasr8 commented May 10, 2025

Copy link
Copy Markdown
Member Author

Got it! Yeah, being able to simplify the tests and make them run faster is definitely worth it :)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Pranjal095 pushed a commit to Pranjal095/cpython that referenced this pull request Jul 12, 2025
@tomasr8 @Pranjal095
pythonGH-131798: Split up and optimize CALL_ISINSTANCE (pythonGH-133339)
7e848f2
taegyunkim pushed a commit to taegyunkim/cpython that referenced this pull request Aug 4, 2025
@tomasr8 @taegyunkim
pythonGH-131798: Split up and optimize CALL_ISINSTANCE (pythonGH-133339)
203d5af
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@brandtbucher brandtbucher brandtbucher approved these changes
@Fidget-Spinner Fidget-Spinner Awaiting requested review from Fidget-Spinner Fidget-Spinner is a code owner
@markshannon markshannon Awaiting requested review from markshannon markshannon is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

3 participants

@tomasr8 @Fidget-Spinner @brandtbucher
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.