-
Notifications
You must be signed in to change notification settings - Fork 5.3k
[mono][interp] Ensure call offset is greater than offset of other active calls #80728
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
[mono][interp] Ensure call offset is greater than offset of other active calls #80728
Conversation
… 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.
|
Tagging subscribers to this area: @BrzVlad Issue DetailsThis 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
|
| 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; |
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.
The call_args field was incorrectly set here. We should only init in places where INTERP_INST_FLAG_CALL is set, aka above
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.
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; |
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.
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); |
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.
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)); |
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.
wasn't call info already allocated when generating code ?
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.
You are right. It was an issue due to duplication of InterpCallInfo in InterpInst.
|
Once all issues are fixed I would be interested in some quick numbers for compilation time change, just in case. You could run |
| #endif | ||
| } | ||
| td->last_ins->flags |= INTERP_INST_FLAG_CALL; | ||
| init_last_ins_call (td); |
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.
we should set call_args here and not allocate the call info below, for non-call instructions
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.
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)); |
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.
this code seems accidental
Compilation time of
|
| 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; |
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 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.
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.
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. |
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 think this comment is outdated. You might want to double check the explanation for this method.
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.
Updated.
| } | ||
|
|
||
| static void | ||
| init_last_ins_call (TransformData *td) { |
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.
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. |
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 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.
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.
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.
…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.
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_callcomputes 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.