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

GH-105848: Simplify the arrangement of CALL's stack #107788

Merged
merged 6 commits into from
Aug 9, 2023

Conversation

brandtbucher
Copy link
Member

@brandtbucher brandtbucher commented Aug 8, 2023

Instead of supporting both [callable, self, args...] and [NULL, callable, args...], change CALL's stack to be [callable, self_or_null, args...]. This always puts the desired callable at the same location on the stack, simplifying (or removing) the resulting shuffles and adjustments necessary for the shared code paths that follow.

This also changes CALL_FUNCTION_EX, DICT_MERGE, LOAD_ATTR, LOAD_GLOBAL, LOAD_SUPER_ATTR to support the new stack layout. In some cases, I've also changed the names of the stack items for consistency within a family.

As a side-effect of the new layout, LOAD_ATTR_PROPERTY and LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN (which are both implemented as inlined calls) don't support pushing an additional item to the stack for a following CALL (oparg & 1) anymore. I think this is fine, since it seems rare to call a "method" that's looked up in this manner.

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

I think this can go in just like this.

Comment on lines -1092 to +1093
if (oparg & 1) { stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = null; }
stack_pointer[-1] = v;
stack_pointer[-1 - (oparg & 1 ? 1 : 0)] = res;
if (oparg & 1) { stack_pointer[-(oparg & 1 ? 1 : 0)] = null; }
Copy link
Member

Choose a reason for hiding this comment

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

Theoretically this could be slightly less performant than the original (not that it matters, and the C compiler might see right through this).

Python/flowgraph.c Show resolved Hide resolved
@@ -1616,7 +1616,7 @@ dummy_func(
PyObject *dict = PEEK(oparg + 1); // update is still on the stack
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could make the DSL less of a lie, e.g. using

inst(DICT_MERGE, (dict, unused[oparg-1], update -- dict, unused[oparg-1])) {

?

(I guess the peeks in the error handling code suggest it's even more complicated.)

Copy link
Member Author

Choose a reason for hiding this comment

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

I figured the presence of the PEEK was an indicator that someone had tried that already. Maybe not, though... let me see!

Copy link
Member

Choose a reason for hiding this comment

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

Maybe it was an oversight. Maybe the code generator didn't use to handle this case.

I tried what I suggested and it seems to work. I didn't try to figure out what to do for the error handling (probably some more dummies below dict).

@@ -1644,7 +1644,7 @@ dummy_func(
LOAD_SUPER_ATTR_METHOD,
};

inst(LOAD_SUPER_ATTR, (unused/1, global_super, class, self -- res2 if (oparg & 1), res)) {
inst(LOAD_SUPER_ATTR, (unused/1, global_super, class, self -- attr, self_or_null if (oparg & 1))) {
Copy link
Member

Choose a reason for hiding this comment

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

self_or_null is unused, so replace with unused?

Copy link
Member Author

Choose a reason for hiding this comment

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

Putting unused here breaks things. Since this is named it's set to NULL in the generated code... if it's called unused, it just ignores it and now there's a garbage value there.

Copy link
Member

Choose a reason for hiding this comment

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

Oh sneaky. I think you found a hole in the code generator. If you want it to (conditionally) push NULL you should initialize it, not depend on the default initialization. IIRC the default initialization is there only to avoid C compiler warnings.

@@ -1705,39 +1705,38 @@ dummy_func(
LOAD_SUPER_ATTR,
};

inst(LOAD_SUPER_ATTR_ATTR, (unused/1, global_super, class, self -- res2 if (oparg & 1), res)) {
inst(LOAD_SUPER_ATTR_ATTR, (unused/1, global_super, class, self -- attr, self_or_null if (oparg & 1))) {
Copy link
Member

Choose a reason for hiding this comment

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

Ditto.

@@ -2795,9 +2792,9 @@ dummy_func(
}

inst(INSTRUMENTED_CALL, ( -- )) {
int is_meth = PEEK(oparg+2) != NULL;
int is_meth = PEEK(oparg + 1) != NULL;
Copy link
Member

Choose a reason for hiding this comment

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

Yes!

@gvanrossum
Copy link
Member

How about some buildbot runs?

@brandtbucher brandtbucher added 🔨 test-with-buildbots Test PR w/ buildbots; report in status section 🔨 test-with-refleak-buildbots Test PR w/ refleak buildbots; report in status section labels Aug 8, 2023
@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @brandtbucher for commit fd6607f 🤖

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @brandtbucher for commit fd6607f 🤖

If you want to schedule another build, you need to add the 🔨 test-with-refleak-buildbots label again.

@bedevere-bot bedevere-bot removed 🔨 test-with-refleak-buildbots Test PR w/ refleak buildbots; report in status section 🔨 test-with-buildbots Test PR w/ buildbots; report in status section labels Aug 8, 2023
@brandtbucher
Copy link
Member Author

Perf is neutral.

@brandtbucher brandtbucher added 🔨 test-with-buildbots Test PR w/ buildbots; report in status section 🔨 test-with-refleak-buildbots Test PR w/ refleak buildbots; report in status section labels Aug 9, 2023
@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @brandtbucher for commit a5a2d8f 🤖

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @brandtbucher for commit a5a2d8f 🤖

If you want to schedule another build, you need to add the 🔨 test-with-refleak-buildbots label again.

@bedevere-bot bedevere-bot removed 🔨 test-with-buildbots Test PR w/ buildbots; report in status section 🔨 test-with-refleak-buildbots Test PR w/ refleak buildbots; report in status section labels Aug 9, 2023
@markshannon
Copy link
Member

No new buildbot failures.

Copy link
Member

@markshannon markshannon left a comment

Choose a reason for hiding this comment

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

A bunch of minor style issues, but nothing blocking.

int total_args = oparg;
if (is_meth) {
callable = method;
if (self_or_null) {
Copy link
Member

Choose a reason for hiding this comment

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

This is perfectly correct, but could you make this consistent with the other NULL checks and use self_or_null != NULL to make it clear to the reader that this is a NULL check not a test of a boolean/int flag.

int argcount = oparg;
if (is_meth) {
callable = method;
if (self_or_null) {
Copy link
Member

Choose a reason for hiding this comment

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

ditto

int total_args = oparg;
if (is_meth) {
callable = method;
if (self_or_null) {
Copy link
Member

Choose a reason for hiding this comment

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

ditto

int total_args = oparg;
if (is_meth) {
callable = method;
if (self_or_null) {
Copy link
Member

Choose a reason for hiding this comment

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

likewise

int total_args = oparg;
if (is_meth) {
callable = method;
if (self_or_null) {
Copy link
Member

Choose a reason for hiding this comment

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

and here

int total_args = oparg;
if (is_meth) {
if (self_or_null) {
Copy link
Member

Choose a reason for hiding this comment

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

another

int total_args = oparg;
if (is_meth) {
if (self_or_null) {
Copy link
Member

Choose a reason for hiding this comment

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

almost the last one

int total_args = oparg;
if (is_meth) {
if (self_or_null) {
Copy link
Member

Choose a reason for hiding this comment

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

the last one

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, you missed one. But don't worry, I got it. ;)

@@ -2712,7 +2704,7 @@ dummy_func(
exc_info->exc_value = Py_NewRef(new_exc);
}

inst(LOAD_ATTR_METHOD_WITH_VALUES, (unused/1, type_version/2, keys_version/2, descr/4, self -- res2 if (1), res)) {
inst(LOAD_ATTR_METHOD_WITH_VALUES, (unused/1, type_version/2, keys_version/2, descr/4, self -- attr, self_or_null if (1))) {
Copy link
Member

Choose a reason for hiding this comment

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

This isn't self_or_null, it's just self.

@@ -1705,39 +1702,38 @@ dummy_func(
LOAD_SUPER_ATTR,
};

inst(LOAD_SUPER_ATTR_ATTR, (unused/1, global_super, class, self -- res2 if (oparg & 1), res)) {
inst(LOAD_SUPER_ATTR_ATTR, (unused/1, global_super, class, self -- attr, unused if (oparg & 1))) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
inst(LOAD_SUPER_ATTR_ATTR, (unused/1, global_super, class, self -- attr, unused if (oparg & 1))) {
inst(LOAD_SUPER_ATTR_ATTR, (unused/1, global_super, class, self -- attr, unused if (0))) {

DECREF_INPUTS();
}

inst(LOAD_ATTR_PROPERTY, (unused/1, type_version/2, func_version/2, fget/4, owner -- unused if (oparg & 1), unused)) {
inst(LOAD_ATTR_PROPERTY, (unused/1, type_version/2, func_version/2, fget/4, owner -- unused, unused if (oparg & 1))) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
inst(LOAD_ATTR_PROPERTY, (unused/1, type_version/2, func_version/2, fget/4, owner -- unused, unused if (oparg & 1))) {
inst(LOAD_ATTR_PROPERTY, (unused/1, type_version/2, func_version/2, fget/4, owner -- unused, unused if (0))) {

@brandtbucher brandtbucher enabled auto-merge (squash) August 9, 2023 17:54
@brandtbucher brandtbucher merged commit a9caf9c into python:main Aug 9, 2023
22 checks passed
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

Successfully merging this pull request may close these issues.

None yet

4 participants