Skip to content

Conversation

@kotlarmilos
Copy link
Member

This PR fixes #80175 by introducing deferred calls resolution. The algorithm for allocating offsets for the arguments of a call must ensure that the base offset of the call must be greater than the offset of any argument of other active call args.

For each call, function end_active_call computes the call offset of each call argument starting from a base offset. The base offset is computed as max offset of all call offsets on which the call depends. Deferred call stack ensures that all call offsets on which the call depends are calculated before the call resolution.

… call must be greater than the offset of any argument of other active call args.

Function allocates an offset of resolved calls following the constraint.
…call instruction. Use glist data structure allocated from the td->mempool.
@kotlarmilos kotlarmilos requested a review from BrzVlad as a code owner January 17, 2023 10:08
@kotlarmilos kotlarmilos self-assigned this Jan 17, 2023
@kotlarmilos kotlarmilos requested a review from vargaz as a code owner January 17, 2023 10:08
@ghost
Copy link

ghost commented Jan 17, 2023

Tagging subscribers to this area: @BrzVlad
See info in area-owners.md if you want to be subscribed.

Issue Details

This PR fixes #80175 by introducing deferred calls resolution. The algorithm for allocating offsets for the arguments of a call must ensure that the base offset of the call must be greater than the offset of any argument of other active call args.

For each call, function end_active_call computes the call offset of each call argument starting from a base offset. The base offset is computed as max offset of all call offsets on which the call depends. Deferred call stack ensures that all call offsets on which the call depends are calculated before the call resolution.

Author: kotlarmilos
Assignees: kotlarmilos
Labels:

area-Codegen-Interpreter-mono

Milestone: -

@kotlarmilos kotlarmilos added this to the 8.0.0 milestone Jan 17, 2023
td->ip += 5;
td->last_ins->info.call_args = call_args;
init_last_ins_call (td);
td->last_ins->info.call_info->call_args = call_args;
Copy link
Member

Choose a reason for hiding this comment

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

The call_args field was incorrectly set here. We should only init in places where INTERP_INST_FLAG_CALL is set, aka above

Copy link
Member Author

@kotlarmilos kotlarmilos Jan 17, 2023

Choose a reason for hiding this comment

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

In interp_transform_call there is https://github.com/dotnet/runtime/blob/main/src/mono/mono/mini/interp/transform.c#L3556-L3577 that doesn't set INTERP_INST_FLAG_CALL which is probably correct. I will add there allocation of InterpCallInfo structure if it doesn't exist.

int *call_args;
InterpCallInfo *call_info;
} info;
InterpCallInfo *call_info;
Copy link
Member

Choose a reason for hiding this comment

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

duplicate addition

call->call_info->call_deps = NULL;
for (int i = 0; i < ac->active_calls_count; i++)
call->call_info->call_deps = g_slist_prepend_mempool (td->mempool, call->call_info->call_deps, ac->active_calls [i]);
ac->deferred_calls = g_slist_prepend_mempool (td->mempool, ac->deferred_calls, call);
Copy link
Member

Choose a reason for hiding this comment

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

We seem to be allocating a list entry for the most common case of no active calls / resolve arg offsets immediately.

call_args [0] = new_var;
call_args [1] = -1;
// Push active call that should be resolved onto the stack
call->call_info = (InterpCallInfo*)mono_mempool_alloc (td->mempool, sizeof (InterpCallInfo));
Copy link
Member

Choose a reason for hiding this comment

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

wasn't call info already allocated when generating code ?

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right. It was an issue due to duplication of InterpCallInfo in InterpInst.

@BrzVlad
Copy link
Member

BrzVlad commented Jan 17, 2023

Once all issues are fixed I would be interested in some quick numbers for compilation time change, just in case. You could run System.Runtime.Tests with tiering disabled, and use MONO_TIME_TRACK on interp_alloc_offsets, with and without your change.

#endif
}
td->last_ins->flags |= INTERP_INST_FLAG_CALL;
init_last_ins_call (td);
Copy link
Member

Choose a reason for hiding this comment

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

we should set call_args here and not allocate the call info below, for non-call instructions

Copy link
Member Author

Choose a reason for hiding this comment

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

Now, there is a check if (td->last_ins->flags & INTERP_INST_FLAG_CALL) { so I haven't set call_args here. Let me know if it should be updated.

ac->active_calls [ac->active_calls_count].call = call;
ac->active_calls [ac->active_calls_count].param_size = get_call_param_size (td, call);
ac->param_size += ac->active_calls [ac->active_calls_count].param_size;
ac->active_calls [ac->active_calls_count] = (InterpInst*)mono_mempool_alloc (td->mempool, sizeof (InterpInst));
Copy link
Member

Choose a reason for hiding this comment

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

this code seems accidental

@kotlarmilos
Copy link
Member Author

@BrzVlad good catch in #80795, it fixed the failing tests.

@kotlarmilos
Copy link
Member Author

kotlarmilos commented Jan 19, 2023

Once all issues are fixed I would be interested in some quick numbers for compilation time change, just in case. You could run System.Runtime.Tests with tiering disabled, and use MONO_TIME_TRACK on interp_alloc_offsets, with and without your change.

Compilation time of interp_alloc_offsets in μs for System.Runtime.Tests with tiering disabled.

Run Incorrect offset allocation fix branch Main branch
1 90438 89927
2 90884 89905
3 92977 97611
4 90264 92051
5 94591 91173
6 90077 89698
7 89900 91660
8 89433 89643
9 90259 93393
10 94498 89805
-- -- --
AVG 91332,10 91486,60
STDEV 1940,06 2493,31

InterpInst *deferred_call = call;
while (deferred_call) {
// `base_offset` is a relative offset (to the start of the call args stack) where the args for this call reside.
int base_offset = 0;
Copy link
Member

Choose a reason for hiding this comment

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

I think a comment here explaining why call_offset is guaranteed to be set for all deps wouldn't hurt. Something like: The deps for a call represent the list of active calls at the moment when the call ends. This means that all deps for a call end after the call in question. Given we iterate over the list of deferred calls from the last to the first one to end, all deps of a call are guaranteed to have been processed at this point.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added.

* Function first removes the call from an array of active calls. If a match is found,
* the call is removed from the array by moving the last entry into its place. Otherwise, it is a call without arguments.
*
* Call is push onto the stack with an array of other active calls that need to be resolved first.
Copy link
Member

Choose a reason for hiding this comment

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

I think this comment is outdated. You might want to double check the explanation for this method.

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated.

}

static void
init_last_ins_call (TransformData *td) {
Copy link
Member

Choose a reason for hiding this comment

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

newline here

* Function first removes the call from an array of active calls. If a match is found,
* the call is removed from the array by moving the last entry into its place. Otherwise, it is a call without arguments.
*
* If there are active calls, the call is push onto the stack with an array of other active calls on which the call in question depends.
Copy link
Member

Choose a reason for hiding this comment

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

I was referring to

call is push onto the stack with an array of other active calls

Seemed like old comment from your original implementation where we were pushing the current call together with all active calls. Which we no longer do now.

Copy link
Member Author

Choose a reason for hiding this comment

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

If there are active calls, it is pushed onto the stack as a deferred call. It also contains an array of other active calls. Let me change it to be clear that the array is not pushed onto the stack but exists as a part of InterpInst.

@kotlarmilos kotlarmilos merged commit 7ec3634 into dotnet:main Jan 21, 2023
@kotlarmilos kotlarmilos deleted the bugfix/incorrect-offset-allocation branch January 21, 2023 08:03
mdh1418 pushed a commit to mdh1418/runtime that referenced this pull request Jan 24, 2023
…ive calls (dotnet#80728)

* Add deferred call resolution for active calls. The base offset of the call must be greater than the offset of any argument of other active call args. The base offset is computed as max offset of all call offsets on which the call depends. Stack ensures that all call offsets on which the call depends are calculated before the call in question, by deferring calls from the last to the first one.

* Move call_args to InterpCallInfo struct. Allocate InterpCallInfo per call instruction.
@ghost ghost locked as resolved and limited conversation to collaborators Feb 20, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[mono][interp] Offset allocation of call args is incorrect

2 participants