Skip to content

[coop][interp] Fix GC transitions for thunk invoke wrappers - #81773

Merged
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper
Feb 13, 2023
Merged

[coop][interp] Fix GC transitions for thunk invoke wrappers#81773
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper

Conversation

@lambdageek

@lambdageeklambdageek commented Feb 7, 2023

Copy link
Copy Markdown
Member

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see mono_threads_enter_gc_safe_region_unbalanced / mono_threads_exit_gc_safe_region_unbalanced which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced / mono_threads_exit_gc_unsafe_region_unbalanced

Change the thunk invoke wrapper to also use attach_coop/detach_coop. This makes the thunks a bit more flexible (they can now be called from unattached threads), at the cost of a TLS lookup. Also fixes interpreter support since they use the existing attach/detach handling.

As an implementation detail, I added a GC Unsafe Transition Builder to the marshaling code.

Fixes a failure seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

The code that recognizes GC transition icalls in the interpreter was
only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced
which are used by managed-to-native wrappers.
In most cases for native-to-managed wrappers the marshaller emits
mono_threads_attach_coop / mono_threads_detach_coop and those are
handled elsewhere by setting the needs_thread_attach flag on the
InterpMethod.
However when the mono_marshal_get_thunk_invoke_wrapper API is used, we
emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced
Recognize those two icalls and set the needs_thread_attach
flag. (This is slightly more work than necessary -
mono_threads_attach_coop checks if the thread was previously attached
to the runtime and attaches it if it wasn't. The thunk invoke
wrappers seem to assume the API caller attaches the thread - so we pay
for an extra check. But on the other hand we don't need to change the
execution-time behavior of the interpreter by reusing existing mechanisms)
@ghost

ghost commented Feb 7, 2023

Copy link
Copy Markdown

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

Issue Details

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced

which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced

Recognize those two icalls and set the needs_thread_attach flag. (This is slightly more work than necessary - mono_threads_attach_coop checks if the thread was previously attached to the runtime and attaches it if it wasn't. The thunk invoke wrappers seem to assume the API caller attaches the thread - so we pay for an extra check. But on the other hand we don't need to change the execution-time behavior of the interpreter by reusing existing mechanisms)

Fixes a failures seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

Author:lambdageek
Assignees:lambdageek
Labels:

area-Codegen-Interpreter-mono

Milestone:-

@lambdageek

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@BrzVlad

Copy link
Copy Markdown
Member

I don't really understand how exactly your other PR induces this failure. Nevertheless, silently doing an attach behind the scenes seems like a hack to me. I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost. Otherwise it sounds like the test has a bug and it relies on some thread being attached. Or maybe the cctor invocation order is not really correct.

@lambdageek

Copy link
Copy Markdown
MemberAuthor

silently doing an attach behind the scenes seems like a hack to me.

My point is, there is no attach happening. Any user code that calls the thunk returned by mono_marshal_get_thunk_invoke_wrapper must already have done an attach. So mono_thread_attach_coop is just to do the GC Safe -> GC Unsafe transition.

I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost.

Yea I guess I could just change emit_thunk_invoke_wrapper to emit attach/detach pairs like all the other thunks. It would be a change of behavior for existing code, but correct code shouldn't notice. and incorrect code will be upgraded to be correct.

@lambdageek

lambdageek commented Feb 8, 2023

Copy link
Copy Markdown
MemberAuthor

I don't really understand how exactly your other PR induces this failure

It's the bit about not calling the managed ALC callback for the default ALC. Early during startup we used to end up in there and run the cctor for the default ALC. That would trigger a bunch of other early initialization. With my PR, we don't call that managed callback for the default ALC anymore because it is just the default implementation which always returns NULL. As a result a lot fewer cctors run at startup.

In the Thunks.cs test, the cctor for NotImplementedException is one of those cctors. After the PR, that cctor now runs when we call back into managed after the thunk_invoke_wrapper. And when cctors run they need to take a lock in mono - and attempt to do a GC Unsafe -> GC Safe transition. Under the interp, the thunk invoke wrapper was missing the GC Safe -> GC Unsafe transition on entry, so the cctor locking was doing GC Safe -> GC Safe, which is illegal. It worked on AOT and JIT because those had the correct transition in the wrapper.

Make a "GC Unsafe Transition Builder" - that always calls
mono_threads_attach_coop / mono_threads_detach_coop.
Use it in the native to managed wrappers:
emit_thunk_invoke_wrapper and emit_managed_wrapper
This is a change in behavior for emit_thunk_invoke_wrapper -
previously it directly called
mono_threads_enter_gc_unsafe_region_unbalanced.
That means that compared to the previous behavior, the thunk invoke
wrappers are now a bit more lax: they will be able to be called on
threads that aren't attached to the runtime and they will attach
automatically.
On the other hand existing code will continue to work, with the extra
cost of a check of a thread local var.
Using mono_thread_attach_coop also makes invoke wrappers work
correctly in the interpreter - it special cases
mono_thread_attach_coop but not enter_gc_unsafe.
@lambdageek

Copy link
Copy Markdown
MemberAuthor

@BrzVlad I updated the PR to change the thunk invoke wrappers to use attach_coop/detach_coop and fall into the existing attach/detach handling in the interpreter

@lambdageek

lambdageek commented Feb 10, 2023

Copy link
Copy Markdown
MemberAuthor

update oops, wrong PR

@lambdageek
lambdageek merged commit 9f8c009 into dotnet:mainFeb 13, 2023
@ghostghost locked as resolved and limited conversation to collaborators Mar 15, 2023
Sign up for freeto 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.

2 participants

@lambdageek@BrzVlad
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[coop][interp] Fix GC transitions for thunk invoke wrappers by lambdageek · Pull Request #81773 · dotnet/runtime · GitHub
Skip to content

[coop][interp] Fix GC transitions for thunk invoke wrappers - #81773

Merged
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper
Feb 13, 2023
Merged

[coop][interp] Fix GC transitions for thunk invoke wrappers#81773
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper

Conversation

@lambdageek

@lambdageeklambdageek commented Feb 7, 2023

Copy link
Copy Markdown
Member

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see mono_threads_enter_gc_safe_region_unbalanced / mono_threads_exit_gc_safe_region_unbalanced which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced / mono_threads_exit_gc_unsafe_region_unbalanced

Change the thunk invoke wrapper to also use attach_coop/detach_coop. This makes the thunks a bit more flexible (they can now be called from unattached threads), at the cost of a TLS lookup. Also fixes interpreter support since they use the existing attach/detach handling.

As an implementation detail, I added a GC Unsafe Transition Builder to the marshaling code.

Fixes a failure seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

The code that recognizes GC transition icalls in the interpreter was
only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced
which are used by managed-to-native wrappers.
In most cases for native-to-managed wrappers the marshaller emits
mono_threads_attach_coop / mono_threads_detach_coop and those are
handled elsewhere by setting the needs_thread_attach flag on the
InterpMethod.
However when the mono_marshal_get_thunk_invoke_wrapper API is used, we
emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced
Recognize those two icalls and set the needs_thread_attach
flag. (This is slightly more work than necessary -
mono_threads_attach_coop checks if the thread was previously attached
to the runtime and attaches it if it wasn't. The thunk invoke
wrappers seem to assume the API caller attaches the thread - so we pay
for an extra check. But on the other hand we don't need to change the
execution-time behavior of the interpreter by reusing existing mechanisms)
@ghost

ghost commented Feb 7, 2023

Copy link
Copy Markdown

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

Issue Details

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced

which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced

Recognize those two icalls and set the needs_thread_attach flag. (This is slightly more work than necessary - mono_threads_attach_coop checks if the thread was previously attached to the runtime and attaches it if it wasn't. The thunk invoke wrappers seem to assume the API caller attaches the thread - so we pay for an extra check. But on the other hand we don't need to change the execution-time behavior of the interpreter by reusing existing mechanisms)

Fixes a failures seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

Author:lambdageek
Assignees:lambdageek
Labels:

area-Codegen-Interpreter-mono

Milestone:-

@lambdageek

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@BrzVlad

Copy link
Copy Markdown
Member

I don't really understand how exactly your other PR induces this failure. Nevertheless, silently doing an attach behind the scenes seems like a hack to me. I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost. Otherwise it sounds like the test has a bug and it relies on some thread being attached. Or maybe the cctor invocation order is not really correct.

@lambdageek

Copy link
Copy Markdown
MemberAuthor

silently doing an attach behind the scenes seems like a hack to me.

My point is, there is no attach happening. Any user code that calls the thunk returned by mono_marshal_get_thunk_invoke_wrapper must already have done an attach. So mono_thread_attach_coop is just to do the GC Safe -> GC Unsafe transition.

I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost.

Yea I guess I could just change emit_thunk_invoke_wrapper to emit attach/detach pairs like all the other thunks. It would be a change of behavior for existing code, but correct code shouldn't notice. and incorrect code will be upgraded to be correct.

@lambdageek

lambdageek commented Feb 8, 2023

Copy link
Copy Markdown
MemberAuthor

I don't really understand how exactly your other PR induces this failure

It's the bit about not calling the managed ALC callback for the default ALC. Early during startup we used to end up in there and run the cctor for the default ALC. That would trigger a bunch of other early initialization. With my PR, we don't call that managed callback for the default ALC anymore because it is just the default implementation which always returns NULL. As a result a lot fewer cctors run at startup.

In the Thunks.cs test, the cctor for NotImplementedException is one of those cctors. After the PR, that cctor now runs when we call back into managed after the thunk_invoke_wrapper. And when cctors run they need to take a lock in mono - and attempt to do a GC Unsafe -> GC Safe transition. Under the interp, the thunk invoke wrapper was missing the GC Safe -> GC Unsafe transition on entry, so the cctor locking was doing GC Safe -> GC Safe, which is illegal. It worked on AOT and JIT because those had the correct transition in the wrapper.

Make a "GC Unsafe Transition Builder" - that always calls
mono_threads_attach_coop / mono_threads_detach_coop.
Use it in the native to managed wrappers:
emit_thunk_invoke_wrapper and emit_managed_wrapper
This is a change in behavior for emit_thunk_invoke_wrapper -
previously it directly called
mono_threads_enter_gc_unsafe_region_unbalanced.
That means that compared to the previous behavior, the thunk invoke
wrappers are now a bit more lax: they will be able to be called on
threads that aren't attached to the runtime and they will attach
automatically.
On the other hand existing code will continue to work, with the extra
cost of a check of a thread local var.
Using mono_thread_attach_coop also makes invoke wrappers work
correctly in the interpreter - it special cases
mono_thread_attach_coop but not enter_gc_unsafe.
@lambdageek

Copy link
Copy Markdown
MemberAuthor

@BrzVlad I updated the PR to change the thunk invoke wrappers to use attach_coop/detach_coop and fall into the existing attach/detach handling in the interpreter

@lambdageek

lambdageek commented Feb 10, 2023

Copy link
Copy Markdown
MemberAuthor

update oops, wrong PR

@lambdageek
lambdageek merged commit 9f8c009 into dotnet:mainFeb 13, 2023
@ghostghost locked as resolved and limited conversation to collaborators Mar 15, 2023
Sign up for freeto 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.

2 participants

@lambdageek@BrzVlad
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [coop][interp] Fix GC transitions for thunk invoke wrappers by lambdageek · Pull Request #81773 · dotnet/runtime · GitHub
Skip to content

[coop][interp] Fix GC transitions for thunk invoke wrappers - #81773

Merged
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper
Feb 13, 2023
Merged

[coop][interp] Fix GC transitions for thunk invoke wrappers#81773
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper

Conversation

@lambdageek

@lambdageeklambdageek commented Feb 7, 2023

Copy link
Copy Markdown
Member

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see mono_threads_enter_gc_safe_region_unbalanced / mono_threads_exit_gc_safe_region_unbalanced which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced / mono_threads_exit_gc_unsafe_region_unbalanced

Change the thunk invoke wrapper to also use attach_coop/detach_coop. This makes the thunks a bit more flexible (they can now be called from unattached threads), at the cost of a TLS lookup. Also fixes interpreter support since they use the existing attach/detach handling.

As an implementation detail, I added a GC Unsafe Transition Builder to the marshaling code.

Fixes a failure seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

The code that recognizes GC transition icalls in the interpreter was
only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced
which are used by managed-to-native wrappers.
In most cases for native-to-managed wrappers the marshaller emits
mono_threads_attach_coop / mono_threads_detach_coop and those are
handled elsewhere by setting the needs_thread_attach flag on the
InterpMethod.
However when the mono_marshal_get_thunk_invoke_wrapper API is used, we
emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced
Recognize those two icalls and set the needs_thread_attach
flag. (This is slightly more work than necessary -
mono_threads_attach_coop checks if the thread was previously attached
to the runtime and attaches it if it wasn't. The thunk invoke
wrappers seem to assume the API caller attaches the thread - so we pay
for an extra check. But on the other hand we don't need to change the
execution-time behavior of the interpreter by reusing existing mechanisms)
@ghost

ghost commented Feb 7, 2023

Copy link
Copy Markdown

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

Issue Details

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced

which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced

Recognize those two icalls and set the needs_thread_attach flag. (This is slightly more work than necessary - mono_threads_attach_coop checks if the thread was previously attached to the runtime and attaches it if it wasn't. The thunk invoke wrappers seem to assume the API caller attaches the thread - so we pay for an extra check. But on the other hand we don't need to change the execution-time behavior of the interpreter by reusing existing mechanisms)

Fixes a failures seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

Author:lambdageek
Assignees:lambdageek
Labels:

area-Codegen-Interpreter-mono

Milestone:-

@lambdageek

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@BrzVlad

Copy link
Copy Markdown
Member

I don't really understand how exactly your other PR induces this failure. Nevertheless, silently doing an attach behind the scenes seems like a hack to me. I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost. Otherwise it sounds like the test has a bug and it relies on some thread being attached. Or maybe the cctor invocation order is not really correct.

@lambdageek

Copy link
Copy Markdown
MemberAuthor

silently doing an attach behind the scenes seems like a hack to me.

My point is, there is no attach happening. Any user code that calls the thunk returned by mono_marshal_get_thunk_invoke_wrapper must already have done an attach. So mono_thread_attach_coop is just to do the GC Safe -> GC Unsafe transition.

I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost.

Yea I guess I could just change emit_thunk_invoke_wrapper to emit attach/detach pairs like all the other thunks. It would be a change of behavior for existing code, but correct code shouldn't notice. and incorrect code will be upgraded to be correct.

@lambdageek

lambdageek commented Feb 8, 2023

Copy link
Copy Markdown
MemberAuthor

I don't really understand how exactly your other PR induces this failure

It's the bit about not calling the managed ALC callback for the default ALC. Early during startup we used to end up in there and run the cctor for the default ALC. That would trigger a bunch of other early initialization. With my PR, we don't call that managed callback for the default ALC anymore because it is just the default implementation which always returns NULL. As a result a lot fewer cctors run at startup.

In the Thunks.cs test, the cctor for NotImplementedException is one of those cctors. After the PR, that cctor now runs when we call back into managed after the thunk_invoke_wrapper. And when cctors run they need to take a lock in mono - and attempt to do a GC Unsafe -> GC Safe transition. Under the interp, the thunk invoke wrapper was missing the GC Safe -> GC Unsafe transition on entry, so the cctor locking was doing GC Safe -> GC Safe, which is illegal. It worked on AOT and JIT because those had the correct transition in the wrapper.

Make a "GC Unsafe Transition Builder" - that always calls
mono_threads_attach_coop / mono_threads_detach_coop.
Use it in the native to managed wrappers:
emit_thunk_invoke_wrapper and emit_managed_wrapper
This is a change in behavior for emit_thunk_invoke_wrapper -
previously it directly called
mono_threads_enter_gc_unsafe_region_unbalanced.
That means that compared to the previous behavior, the thunk invoke
wrappers are now a bit more lax: they will be able to be called on
threads that aren't attached to the runtime and they will attach
automatically.
On the other hand existing code will continue to work, with the extra
cost of a check of a thread local var.
Using mono_thread_attach_coop also makes invoke wrappers work
correctly in the interpreter - it special cases
mono_thread_attach_coop but not enter_gc_unsafe.
@lambdageek

Copy link
Copy Markdown
MemberAuthor

@BrzVlad I updated the PR to change the thunk invoke wrappers to use attach_coop/detach_coop and fall into the existing attach/detach handling in the interpreter

@lambdageek

lambdageek commented Feb 10, 2023

Copy link
Copy Markdown
MemberAuthor

update oops, wrong PR

@lambdageek
lambdageek merged commit 9f8c009 into dotnet:mainFeb 13, 2023
@ghostghost locked as resolved and limited conversation to collaborators Mar 15, 2023
Sign up for freeto 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.

2 participants

@lambdageek@BrzVlad
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [coop][interp] Fix GC transitions for thunk invoke wrappers by lambdageek · Pull Request #81773 · dotnet/runtime · GitHub
Skip to content

[coop][interp] Fix GC transitions for thunk invoke wrappers - #81773

Merged
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper
Feb 13, 2023
Merged

[coop][interp] Fix GC transitions for thunk invoke wrappers#81773
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper

Conversation

@lambdageek

@lambdageeklambdageek commented Feb 7, 2023

Copy link
Copy Markdown
Member

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see mono_threads_enter_gc_safe_region_unbalanced / mono_threads_exit_gc_safe_region_unbalanced which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced / mono_threads_exit_gc_unsafe_region_unbalanced

Change the thunk invoke wrapper to also use attach_coop/detach_coop. This makes the thunks a bit more flexible (they can now be called from unattached threads), at the cost of a TLS lookup. Also fixes interpreter support since they use the existing attach/detach handling.

As an implementation detail, I added a GC Unsafe Transition Builder to the marshaling code.

Fixes a failure seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

The code that recognizes GC transition icalls in the interpreter was
only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced
which are used by managed-to-native wrappers.
In most cases for native-to-managed wrappers the marshaller emits
mono_threads_attach_coop / mono_threads_detach_coop and those are
handled elsewhere by setting the needs_thread_attach flag on the
InterpMethod.
However when the mono_marshal_get_thunk_invoke_wrapper API is used, we
emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced
Recognize those two icalls and set the needs_thread_attach
flag. (This is slightly more work than necessary -
mono_threads_attach_coop checks if the thread was previously attached
to the runtime and attaches it if it wasn't. The thunk invoke
wrappers seem to assume the API caller attaches the thread - so we pay
for an extra check. But on the other hand we don't need to change the
execution-time behavior of the interpreter by reusing existing mechanisms)
@ghost

ghost commented Feb 7, 2023

Copy link
Copy Markdown

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

Issue Details

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced

which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced

Recognize those two icalls and set the needs_thread_attach flag. (This is slightly more work than necessary - mono_threads_attach_coop checks if the thread was previously attached to the runtime and attaches it if it wasn't. The thunk invoke wrappers seem to assume the API caller attaches the thread - so we pay for an extra check. But on the other hand we don't need to change the execution-time behavior of the interpreter by reusing existing mechanisms)

Fixes a failures seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

Author:lambdageek
Assignees:lambdageek
Labels:

area-Codegen-Interpreter-mono

Milestone:-

@lambdageek

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@BrzVlad

Copy link
Copy Markdown
Member

I don't really understand how exactly your other PR induces this failure. Nevertheless, silently doing an attach behind the scenes seems like a hack to me. I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost. Otherwise it sounds like the test has a bug and it relies on some thread being attached. Or maybe the cctor invocation order is not really correct.

@lambdageek

Copy link
Copy Markdown
MemberAuthor

silently doing an attach behind the scenes seems like a hack to me.

My point is, there is no attach happening. Any user code that calls the thunk returned by mono_marshal_get_thunk_invoke_wrapper must already have done an attach. So mono_thread_attach_coop is just to do the GC Safe -> GC Unsafe transition.

I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost.

Yea I guess I could just change emit_thunk_invoke_wrapper to emit attach/detach pairs like all the other thunks. It would be a change of behavior for existing code, but correct code shouldn't notice. and incorrect code will be upgraded to be correct.

@lambdageek

lambdageek commented Feb 8, 2023

Copy link
Copy Markdown
MemberAuthor

I don't really understand how exactly your other PR induces this failure

It's the bit about not calling the managed ALC callback for the default ALC. Early during startup we used to end up in there and run the cctor for the default ALC. That would trigger a bunch of other early initialization. With my PR, we don't call that managed callback for the default ALC anymore because it is just the default implementation which always returns NULL. As a result a lot fewer cctors run at startup.

In the Thunks.cs test, the cctor for NotImplementedException is one of those cctors. After the PR, that cctor now runs when we call back into managed after the thunk_invoke_wrapper. And when cctors run they need to take a lock in mono - and attempt to do a GC Unsafe -> GC Safe transition. Under the interp, the thunk invoke wrapper was missing the GC Safe -> GC Unsafe transition on entry, so the cctor locking was doing GC Safe -> GC Safe, which is illegal. It worked on AOT and JIT because those had the correct transition in the wrapper.

Make a "GC Unsafe Transition Builder" - that always calls
mono_threads_attach_coop / mono_threads_detach_coop.
Use it in the native to managed wrappers:
emit_thunk_invoke_wrapper and emit_managed_wrapper
This is a change in behavior for emit_thunk_invoke_wrapper -
previously it directly called
mono_threads_enter_gc_unsafe_region_unbalanced.
That means that compared to the previous behavior, the thunk invoke
wrappers are now a bit more lax: they will be able to be called on
threads that aren't attached to the runtime and they will attach
automatically.
On the other hand existing code will continue to work, with the extra
cost of a check of a thread local var.
Using mono_thread_attach_coop also makes invoke wrappers work
correctly in the interpreter - it special cases
mono_thread_attach_coop but not enter_gc_unsafe.
@lambdageek

Copy link
Copy Markdown
MemberAuthor

@BrzVlad I updated the PR to change the thunk invoke wrappers to use attach_coop/detach_coop and fall into the existing attach/detach handling in the interpreter

@lambdageek

lambdageek commented Feb 10, 2023

Copy link
Copy Markdown
MemberAuthor

update oops, wrong PR

@lambdageek
lambdageek merged commit 9f8c009 into dotnet:mainFeb 13, 2023
@ghostghost locked as resolved and limited conversation to collaborators Mar 15, 2023
Sign up for freeto 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.

2 participants

@lambdageek@BrzVlad
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [coop][interp] Fix GC transitions for thunk invoke wrappers by lambdageek · Pull Request #81773 · dotnet/runtime · GitHub
Skip to content

[coop][interp] Fix GC transitions for thunk invoke wrappers - #81773

Merged
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper
Feb 13, 2023
Merged

[coop][interp] Fix GC transitions for thunk invoke wrappers#81773
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper

Conversation

@lambdageek

@lambdageeklambdageek commented Feb 7, 2023

Copy link
Copy Markdown
Member

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see mono_threads_enter_gc_safe_region_unbalanced / mono_threads_exit_gc_safe_region_unbalanced which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced / mono_threads_exit_gc_unsafe_region_unbalanced

Change the thunk invoke wrapper to also use attach_coop/detach_coop. This makes the thunks a bit more flexible (they can now be called from unattached threads), at the cost of a TLS lookup. Also fixes interpreter support since they use the existing attach/detach handling.

As an implementation detail, I added a GC Unsafe Transition Builder to the marshaling code.

Fixes a failure seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

The code that recognizes GC transition icalls in the interpreter was
only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced
which are used by managed-to-native wrappers.
In most cases for native-to-managed wrappers the marshaller emits
mono_threads_attach_coop / mono_threads_detach_coop and those are
handled elsewhere by setting the needs_thread_attach flag on the
InterpMethod.
However when the mono_marshal_get_thunk_invoke_wrapper API is used, we
emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced
Recognize those two icalls and set the needs_thread_attach
flag. (This is slightly more work than necessary -
mono_threads_attach_coop checks if the thread was previously attached
to the runtime and attaches it if it wasn't. The thunk invoke
wrappers seem to assume the API caller attaches the thread - so we pay
for an extra check. But on the other hand we don't need to change the
execution-time behavior of the interpreter by reusing existing mechanisms)
@ghost

ghost commented Feb 7, 2023

Copy link
Copy Markdown

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

Issue Details

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced

which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced

Recognize those two icalls and set the needs_thread_attach flag. (This is slightly more work than necessary - mono_threads_attach_coop checks if the thread was previously attached to the runtime and attaches it if it wasn't. The thunk invoke wrappers seem to assume the API caller attaches the thread - so we pay for an extra check. But on the other hand we don't need to change the execution-time behavior of the interpreter by reusing existing mechanisms)

Fixes a failures seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

Author:lambdageek
Assignees:lambdageek
Labels:

area-Codegen-Interpreter-mono

Milestone:-

@lambdageek

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@BrzVlad

Copy link
Copy Markdown
Member

I don't really understand how exactly your other PR induces this failure. Nevertheless, silently doing an attach behind the scenes seems like a hack to me. I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost. Otherwise it sounds like the test has a bug and it relies on some thread being attached. Or maybe the cctor invocation order is not really correct.

@lambdageek

Copy link
Copy Markdown
MemberAuthor

silently doing an attach behind the scenes seems like a hack to me.

My point is, there is no attach happening. Any user code that calls the thunk returned by mono_marshal_get_thunk_invoke_wrapper must already have done an attach. So mono_thread_attach_coop is just to do the GC Safe -> GC Unsafe transition.

I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost.

Yea I guess I could just change emit_thunk_invoke_wrapper to emit attach/detach pairs like all the other thunks. It would be a change of behavior for existing code, but correct code shouldn't notice. and incorrect code will be upgraded to be correct.

@lambdageek

lambdageek commented Feb 8, 2023

Copy link
Copy Markdown
MemberAuthor

I don't really understand how exactly your other PR induces this failure

It's the bit about not calling the managed ALC callback for the default ALC. Early during startup we used to end up in there and run the cctor for the default ALC. That would trigger a bunch of other early initialization. With my PR, we don't call that managed callback for the default ALC anymore because it is just the default implementation which always returns NULL. As a result a lot fewer cctors run at startup.

In the Thunks.cs test, the cctor for NotImplementedException is one of those cctors. After the PR, that cctor now runs when we call back into managed after the thunk_invoke_wrapper. And when cctors run they need to take a lock in mono - and attempt to do a GC Unsafe -> GC Safe transition. Under the interp, the thunk invoke wrapper was missing the GC Safe -> GC Unsafe transition on entry, so the cctor locking was doing GC Safe -> GC Safe, which is illegal. It worked on AOT and JIT because those had the correct transition in the wrapper.

Make a "GC Unsafe Transition Builder" - that always calls
mono_threads_attach_coop / mono_threads_detach_coop.
Use it in the native to managed wrappers:
emit_thunk_invoke_wrapper and emit_managed_wrapper
This is a change in behavior for emit_thunk_invoke_wrapper -
previously it directly called
mono_threads_enter_gc_unsafe_region_unbalanced.
That means that compared to the previous behavior, the thunk invoke
wrappers are now a bit more lax: they will be able to be called on
threads that aren't attached to the runtime and they will attach
automatically.
On the other hand existing code will continue to work, with the extra
cost of a check of a thread local var.
Using mono_thread_attach_coop also makes invoke wrappers work
correctly in the interpreter - it special cases
mono_thread_attach_coop but not enter_gc_unsafe.
@lambdageek

Copy link
Copy Markdown
MemberAuthor

@BrzVlad I updated the PR to change the thunk invoke wrappers to use attach_coop/detach_coop and fall into the existing attach/detach handling in the interpreter

@lambdageek

lambdageek commented Feb 10, 2023

Copy link
Copy Markdown
MemberAuthor

update oops, wrong PR

@lambdageek
lambdageek merged commit 9f8c009 into dotnet:mainFeb 13, 2023
@ghostghost locked as resolved and limited conversation to collaborators Mar 15, 2023
Sign up for freeto 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.

2 participants

@lambdageek@BrzVlad
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [coop][interp] Fix GC transitions for thunk invoke wrappers by lambdageek · Pull Request #81773 · dotnet/runtime · GitHub
Skip to content

[coop][interp] Fix GC transitions for thunk invoke wrappers - #81773

Merged
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper
Feb 13, 2023
Merged

[coop][interp] Fix GC transitions for thunk invoke wrappers#81773
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper

Conversation

@lambdageek

@lambdageeklambdageek commented Feb 7, 2023

Copy link
Copy Markdown
Member

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see mono_threads_enter_gc_safe_region_unbalanced / mono_threads_exit_gc_safe_region_unbalanced which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced / mono_threads_exit_gc_unsafe_region_unbalanced

Change the thunk invoke wrapper to also use attach_coop/detach_coop. This makes the thunks a bit more flexible (they can now be called from unattached threads), at the cost of a TLS lookup. Also fixes interpreter support since they use the existing attach/detach handling.

As an implementation detail, I added a GC Unsafe Transition Builder to the marshaling code.

Fixes a failure seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

The code that recognizes GC transition icalls in the interpreter was
only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced
which are used by managed-to-native wrappers.
In most cases for native-to-managed wrappers the marshaller emits
mono_threads_attach_coop / mono_threads_detach_coop and those are
handled elsewhere by setting the needs_thread_attach flag on the
InterpMethod.
However when the mono_marshal_get_thunk_invoke_wrapper API is used, we
emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced
Recognize those two icalls and set the needs_thread_attach
flag. (This is slightly more work than necessary -
mono_threads_attach_coop checks if the thread was previously attached
to the runtime and attaches it if it wasn't. The thunk invoke
wrappers seem to assume the API caller attaches the thread - so we pay
for an extra check. But on the other hand we don't need to change the
execution-time behavior of the interpreter by reusing existing mechanisms)
@ghost

ghost commented Feb 7, 2023

Copy link
Copy Markdown

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

Issue Details

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced

which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced

Recognize those two icalls and set the needs_thread_attach flag. (This is slightly more work than necessary - mono_threads_attach_coop checks if the thread was previously attached to the runtime and attaches it if it wasn't. The thunk invoke wrappers seem to assume the API caller attaches the thread - so we pay for an extra check. But on the other hand we don't need to change the execution-time behavior of the interpreter by reusing existing mechanisms)

Fixes a failures seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

Author:lambdageek
Assignees:lambdageek
Labels:

area-Codegen-Interpreter-mono

Milestone:-

@lambdageek

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@BrzVlad

Copy link
Copy Markdown
Member

I don't really understand how exactly your other PR induces this failure. Nevertheless, silently doing an attach behind the scenes seems like a hack to me. I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost. Otherwise it sounds like the test has a bug and it relies on some thread being attached. Or maybe the cctor invocation order is not really correct.

@lambdageek

Copy link
Copy Markdown
MemberAuthor

silently doing an attach behind the scenes seems like a hack to me.

My point is, there is no attach happening. Any user code that calls the thunk returned by mono_marshal_get_thunk_invoke_wrapper must already have done an attach. So mono_thread_attach_coop is just to do the GC Safe -> GC Unsafe transition.

I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost.

Yea I guess I could just change emit_thunk_invoke_wrapper to emit attach/detach pairs like all the other thunks. It would be a change of behavior for existing code, but correct code shouldn't notice. and incorrect code will be upgraded to be correct.

@lambdageek

lambdageek commented Feb 8, 2023

Copy link
Copy Markdown
MemberAuthor

I don't really understand how exactly your other PR induces this failure

It's the bit about not calling the managed ALC callback for the default ALC. Early during startup we used to end up in there and run the cctor for the default ALC. That would trigger a bunch of other early initialization. With my PR, we don't call that managed callback for the default ALC anymore because it is just the default implementation which always returns NULL. As a result a lot fewer cctors run at startup.

In the Thunks.cs test, the cctor for NotImplementedException is one of those cctors. After the PR, that cctor now runs when we call back into managed after the thunk_invoke_wrapper. And when cctors run they need to take a lock in mono - and attempt to do a GC Unsafe -> GC Safe transition. Under the interp, the thunk invoke wrapper was missing the GC Safe -> GC Unsafe transition on entry, so the cctor locking was doing GC Safe -> GC Safe, which is illegal. It worked on AOT and JIT because those had the correct transition in the wrapper.

Make a "GC Unsafe Transition Builder" - that always calls
mono_threads_attach_coop / mono_threads_detach_coop.
Use it in the native to managed wrappers:
emit_thunk_invoke_wrapper and emit_managed_wrapper
This is a change in behavior for emit_thunk_invoke_wrapper -
previously it directly called
mono_threads_enter_gc_unsafe_region_unbalanced.
That means that compared to the previous behavior, the thunk invoke
wrappers are now a bit more lax: they will be able to be called on
threads that aren't attached to the runtime and they will attach
automatically.
On the other hand existing code will continue to work, with the extra
cost of a check of a thread local var.
Using mono_thread_attach_coop also makes invoke wrappers work
correctly in the interpreter - it special cases
mono_thread_attach_coop but not enter_gc_unsafe.
@lambdageek

Copy link
Copy Markdown
MemberAuthor

@BrzVlad I updated the PR to change the thunk invoke wrappers to use attach_coop/detach_coop and fall into the existing attach/detach handling in the interpreter

@lambdageek

lambdageek commented Feb 10, 2023

Copy link
Copy Markdown
MemberAuthor

update oops, wrong PR

@lambdageek
lambdageek merged commit 9f8c009 into dotnet:mainFeb 13, 2023
@ghostghost locked as resolved and limited conversation to collaborators Mar 15, 2023
Sign up for freeto 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.

2 participants

@lambdageek@BrzVlad
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); [coop][interp] Fix GC transitions for thunk invoke wrappers by lambdageek · Pull Request #81773 · dotnet/runtime · GitHub
Skip to content

[coop][interp] Fix GC transitions for thunk invoke wrappers - #81773

Merged
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper
Feb 13, 2023
Merged

[coop][interp] Fix GC transitions for thunk invoke wrappers#81773
lambdageek merged 4 commits into
dotnet:mainfrom
lambdageek:fix-interp-thunk_invoke_wrapper

Conversation

@lambdageek

@lambdageeklambdageek commented Feb 7, 2023

Copy link
Copy Markdown
Member

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see mono_threads_enter_gc_safe_region_unbalanced / mono_threads_exit_gc_safe_region_unbalanced which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced / mono_threads_exit_gc_unsafe_region_unbalanced

Change the thunk invoke wrapper to also use attach_coop/detach_coop. This makes the thunks a bit more flexible (they can now be called from unattached threads), at the cost of a TLS lookup. Also fixes interpreter support since they use the existing attach/detach handling.

As an implementation detail, I added a GC Unsafe Transition Builder to the marshaling code.

Fixes a failure seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

The code that recognizes GC transition icalls in the interpreter was
only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced
which are used by managed-to-native wrappers.
In most cases for native-to-managed wrappers the marshaller emits
mono_threads_attach_coop / mono_threads_detach_coop and those are
handled elsewhere by setting the needs_thread_attach flag on the
InterpMethod.
However when the mono_marshal_get_thunk_invoke_wrapper API is used, we
emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced
Recognize those two icalls and set the needs_thread_attach
flag. (This is slightly more work than necessary -
mono_threads_attach_coop checks if the thread was previously attached
to the runtime and attaches it if it wasn't. The thunk invoke
wrappers seem to assume the API caller attaches the thread - so we pay
for an extra check. But on the other hand we don't need to change the
execution-time behavior of the interpreter by reusing existing mechanisms)
@ghost

ghost commented Feb 7, 2023

Copy link
Copy Markdown

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

Issue Details

The code that recognizes GC transition icalls in the interpreter was only assuming that it will see
mono_threads_enter_gc_safe_region_unbalanced /
mono_threads_exit_gc_safe_region_unbalanced

which are used by managed-to-native wrappers.

In most cases for native-to-managed wrappers the marshaller emits mono_threads_attach_coop / mono_threads_detach_coop and those are handled elsewhere by setting the needs_thread_attach flag on the InterpMethod.

However when the mono_marshal_get_thunk_invoke_wrapper API is used, we emit a thunk invoke wrapper which uses
mono_threads_enter_gc_unsafe_region_unbalanced /
mono_threads_exit_gc_unsafe_region_unbalanced

Recognize those two icalls and set the needs_thread_attach flag. (This is slightly more work than necessary - mono_threads_attach_coop checks if the thread was previously attached to the runtime and attaches it if it wasn't. The thunk invoke wrappers seem to assume the API caller attaches the thread - so we pay for an extra check. But on the other hand we don't need to change the execution-time behavior of the interpreter by reusing existing mechanisms)

Fixes a failures seen in #81380 in the MonoAPI/MonoMono/Thunks test (exposed by calling a cctor later than previously)

Author:lambdageek
Assignees:lambdageek
Labels:

area-Codegen-Interpreter-mono

Milestone:-

@lambdageek

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-extra-platforms

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@BrzVlad

Copy link
Copy Markdown
Member

I don't really understand how exactly your other PR induces this failure. Nevertheless, silently doing an attach behind the scenes seems like a hack to me. I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost. Otherwise it sounds like the test has a bug and it relies on some thread being attached. Or maybe the cctor invocation order is not really correct.

@lambdageek

Copy link
Copy Markdown
MemberAuthor

silently doing an attach behind the scenes seems like a hack to me.

My point is, there is no attach happening. Any user code that calls the thunk returned by mono_marshal_get_thunk_invoke_wrapper must already have done an attach. So mono_thread_attach_coop is just to do the GC Safe -> GC Unsafe transition.

I think we can either choose to perform explicit attach during thunk invoke wrappers, since it sounds like it doesn't really have a perf cost.

Yea I guess I could just change emit_thunk_invoke_wrapper to emit attach/detach pairs like all the other thunks. It would be a change of behavior for existing code, but correct code shouldn't notice. and incorrect code will be upgraded to be correct.

@lambdageek

lambdageek commented Feb 8, 2023

Copy link
Copy Markdown
MemberAuthor

I don't really understand how exactly your other PR induces this failure

It's the bit about not calling the managed ALC callback for the default ALC. Early during startup we used to end up in there and run the cctor for the default ALC. That would trigger a bunch of other early initialization. With my PR, we don't call that managed callback for the default ALC anymore because it is just the default implementation which always returns NULL. As a result a lot fewer cctors run at startup.

In the Thunks.cs test, the cctor for NotImplementedException is one of those cctors. After the PR, that cctor now runs when we call back into managed after the thunk_invoke_wrapper. And when cctors run they need to take a lock in mono - and attempt to do a GC Unsafe -> GC Safe transition. Under the interp, the thunk invoke wrapper was missing the GC Safe -> GC Unsafe transition on entry, so the cctor locking was doing GC Safe -> GC Safe, which is illegal. It worked on AOT and JIT because those had the correct transition in the wrapper.

Make a "GC Unsafe Transition Builder" - that always calls
mono_threads_attach_coop / mono_threads_detach_coop.
Use it in the native to managed wrappers:
emit_thunk_invoke_wrapper and emit_managed_wrapper
This is a change in behavior for emit_thunk_invoke_wrapper -
previously it directly called
mono_threads_enter_gc_unsafe_region_unbalanced.
That means that compared to the previous behavior, the thunk invoke
wrappers are now a bit more lax: they will be able to be called on
threads that aren't attached to the runtime and they will attach
automatically.
On the other hand existing code will continue to work, with the extra
cost of a check of a thread local var.
Using mono_thread_attach_coop also makes invoke wrappers work
correctly in the interpreter - it special cases
mono_thread_attach_coop but not enter_gc_unsafe.
@lambdageek

Copy link
Copy Markdown
MemberAuthor

@BrzVlad I updated the PR to change the thunk invoke wrappers to use attach_coop/detach_coop and fall into the existing attach/detach handling in the interpreter

@lambdageek

lambdageek commented Feb 10, 2023

Copy link
Copy Markdown
MemberAuthor

update oops, wrong PR

@lambdageek
lambdageek merged commit 9f8c009 into dotnet:mainFeb 13, 2023
@ghostghost locked as resolved and limited conversation to collaborators Mar 15, 2023
Sign up for freeto 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.

2 participants

@lambdageek@BrzVlad