-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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] Reduce false pinning from interp stack #100400
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -412,6 +412,9 @@ get_context (void) | |
if (context == NULL) { | ||
context = g_new0 (ThreadContext, 1); | ||
context->stack_start = (guchar*)mono_valloc_aligned (INTERP_STACK_SIZE, MINT_STACK_ALIGNMENT, MONO_MMAP_READ | MONO_MMAP_WRITE, MONO_MEM_ACCOUNT_INTERP_STACK); | ||
// A bit for every pointer sized slot in the stack. FIXME don't allocate whole bit array | ||
if (mono_interp_opt & INTERP_OPT_PRECISE_GC) | ||
context->no_ref_slots = (guchar*)mono_valloc (NULL, INTERP_STACK_SIZE / (8 * sizeof (gpointer)), MONO_MMAP_READ | MONO_MMAP_WRITE, MONO_MEM_ACCOUNT_INTERP_STACK); | ||
context->stack_end = context->stack_start + INTERP_STACK_SIZE - INTERP_REDZONE_SIZE; | ||
context->stack_real_end = context->stack_start + INTERP_STACK_SIZE; | ||
/* We reserve a stack slot at the top of the interp stack to make temp objects visible to GC */ | ||
|
@@ -8011,6 +8014,8 @@ interp_parse_options (const char *options) | |
#endif | ||
else if (strncmp (arg, "ssa", 3) == 0) | ||
opt = INTERP_OPT_SSA; | ||
else if (strncmp (arg, "precise", 7) == 0) | ||
opt = INTERP_OPT_PRECISE_GC; | ||
else if (strncmp (arg, "all", 3) == 0) | ||
opt = ~INTERP_OPT_NONE; | ||
|
||
|
@@ -8473,6 +8478,57 @@ interp_stop_single_stepping (void) | |
ss_enabled = FALSE; | ||
} | ||
|
||
|
||
static void | ||
interp_mark_frame_no_ref_slots (ThreadContext *context, InterpFrame *frame, gpointer *top_limit) | ||
{ | ||
InterpMethod *imethod = frame->imethod; | ||
gpointer *frame_stack = (gpointer*)frame->stack; | ||
gpointer *frame_stack_end = (gpointer*)((guchar*)frame->stack + imethod->alloca_size); | ||
// The way interpreter implements calls is by moving arguments to the param area, at the | ||
// top of the stack and then proceed with the call. Up to the moment of the call these slots | ||
// are owned by the calling frame. Once we do the call, the stack pointer of the called | ||
// frame will point inside the param area of the calling frame. | ||
// | ||
// We mark no ref slots from top to bottom and we use the top limit to ignore slots | ||
// that were already handled in the called frame. | ||
if (top_limit && top_limit < frame_stack_end) | ||
frame_stack_end = top_limit; | ||
|
||
for (gpointer *current = frame_stack; current < frame_stack_end; current++) { | ||
gsize slot_index = current - frame_stack; | ||
if (!mono_bitset_test_fast (imethod->ref_slots, slot_index)) { | ||
gsize global_slot_index = current - (gpointer*)context->stack_start; | ||
gsize table_index = global_slot_index / 8; | ||
int bit_index = global_slot_index % 8; | ||
context->no_ref_slots [table_index] |= 1 << bit_index; | ||
} | ||
} | ||
} | ||
|
||
static void | ||
interp_mark_no_ref_slots (ThreadContext *context, MonoLMF* lmf) | ||
{ | ||
memset (context->no_ref_slots, 0, (context->stack_pointer - context->stack_start) / (8 * sizeof (gpointer)) + 1); | ||
while (lmf) { | ||
if ((gsize)lmf->previous_lmf & 2) { | ||
MonoLMFExt *lmf_ext = (MonoLMFExt*) lmf; | ||
if (lmf_ext->kind == MONO_LMFEXT_INTERP_EXIT || lmf_ext->kind == MONO_LMFEXT_INTERP_EXIT_WITH_CTX) { | ||
InterpFrame *frame = (InterpFrame*)lmf_ext->interp_exit_data; | ||
gpointer *top_limit = NULL; | ||
while (frame) { | ||
if (frame->imethod) { | ||
interp_mark_frame_no_ref_slots (context, frame, top_limit); | ||
top_limit = (gpointer*)frame->stack; | ||
} | ||
frame = frame->parent; | ||
} | ||
} | ||
} | ||
lmf = (MonoLMF*)((gsize)lmf->previous_lmf & ~3); | ||
} | ||
} | ||
|
||
/* | ||
* interp_mark_stack: | ||
* | ||
|
@@ -8505,9 +8561,20 @@ interp_mark_stack (gpointer thread_data, GcScanFunc func, gpointer gc_data, gboo | |
if (!context || !context->stack_start) | ||
return; | ||
|
||
// FIXME: Scan the whole area with 1 call | ||
for (gpointer *p = (gpointer*)context->stack_start; p < (gpointer*)context->stack_pointer; p++) | ||
func (p, gc_data); | ||
if (mono_interp_opt & INTERP_OPT_PRECISE_GC) { | ||
MonoLMF **lmf_addr = (MonoLMF**)info->tls [TLS_KEY_LMF_ADDR]; | ||
if (lmf_addr) | ||
interp_mark_no_ref_slots (context, *lmf_addr); | ||
} | ||
|
||
int slot_index = 0; | ||
for (gpointer *p = (gpointer*)context->stack_start; p < (gpointer*)context->stack_pointer; p++) { | ||
if (context->no_ref_slots && (context->no_ref_slots [slot_index / 8] & (1 << (slot_index % 8)))) | ||
;// This slot is marked as no ref, we don't scan it | ||
else | ||
func (p, gc_data); | ||
slot_index++; | ||
Comment on lines
+8569
to
+8576
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just summarizing so I understand how this all works. The way this works is that sgen calls So if we're sure the slot isn't a pointer, we don't scan it at all. otherwise if we're not sure - we scan it, and possibly create false pinning. So it's not important that this PR precisely tracks every single managed pointer opcode. But if we see a slot that can't possibly contain a managed pointer, we may mark it and potentially avoid some false pinning. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. /cc @cshung FYI There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would add for clarity that the ambiguity of |
||
} | ||
|
||
FrameDataFragment *frag; | ||
for (frag = context->data_stack.first; frag; frag = frag->next) { | ||
|
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.
Can we use
mono_bitset_set_fast
here?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 could, but the use case was simple enough that I went with the manual implementation.