Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix EH handling in PInvoke stubs and remove workaround #327

Merged
merged 3 commits into from
Nov 27, 2019

Conversation

fadimounir
Copy link
Contributor

@fadimounir fadimounir commented Nov 27, 2019

Fix for #248
Reverts workaround from #249

The fix in the JIT is to inline the raw PInvoke call if:

  1. We are compiling a PInvoke IL Stub
  2. We are compiling the stub without a secret parameter

In crossgen1/jit, the raw pinvoke call is a calli operation that uses the secret parameter passed to the stub, and gets inlined by-design (we don't even call impCanPInvokeInlineCallSite for it).

With crossgen2, the secret parameter is not used and we have a separate pinvoke stub for each pinvoke. Each pinvoke IL stub generated by crossgen2 will have some marshaling operations if applicable, and a call to the raw pinvoke target (which gets wrapped by calls to JIT_PInvokeBegin/End helpers). This raw pinvoke call has to be inlined for IL stubs, even in the presence of EH blocks due to marshaling, otherwise we would get an IL stub that infinitely recursively calls itself because it can't inline the actual pinvoke call.

cc @dotnet/crossgen-contrib @dotnet/jit-contrib

The fix in the JIT is to inline the raw PInvoke call if:
1) We are compiling a PInvoke IL Stub
2) We are compiling the stub without a secret parameter

In crossgen1/jit, the raw pinvoke call is a calli operation that uses the secret parameter passed to the stub, but with crossgen2, that parameter is not used and we have a separate pinvoke stub for each pinvoke.
@@ -6393,6 +6393,15 @@ bool Compiler::impCanPInvokeInlineCallSite(BasicBlock* block)
// jit\jit64\ebvts\mcpp\sources2\ijw\__clrcall\vector_ctor_dtor.02\deldtor_clr.exe
if (block->hasTryIndex())
{
if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB) &&
opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PUBLISH_SECRET_PARAM))
Copy link
Member

Choose a reason for hiding this comment

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

Is this condition correct?

JIT_FLAG_PUBLISH_SECRET_PARAM is never set in crossgen2. It means that this if is going to be always false in crossgen2 and this change has no effect on what crossgen2 does.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops... Typo probably during the repro reforking

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Thanks for catching this Jan

Copy link
Member

Choose a reason for hiding this comment

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

Do we need the JIT_FLAG_PUBLISH_SECRET_PARAM part of the condition at all? What is not going to work if the condition is just if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB) ?

Copy link
Contributor Author

@fadimounir fadimounir Nov 27, 2019

Choose a reason for hiding this comment

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

What is not going to work if the condition is just if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB) ?

In my mind, i think it should work, but I put a more conservative check to avoid an unintentional break somewhere else. I don't know enough context outside of crossgen2 to be able to say with confidence that we should remove the check for JIT_FLAG_PUBLISH_SECRET_PARAM

Copy link
Member

Choose a reason for hiding this comment

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

Some interop stubs generated at runtime are not shared and do not have CORJIT_FLAG_PUBLISH_SECRET_PARAM. It is not reliable to use CORJIT_FLAG_PUBLISH_SECRET_PARAM to tell whether you are generating stub for crossgen2. (And even if it was reliable, it would be rather cryptic to use this flag for this purpose.)

It means that you are still potentially changing the behavior for the JIT case with this change. The potential bugs are just going to be more subtle and harder to find.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. Ok let me remove CORJIT_FLAG_PUBLISH_SECRET_PARAM and see if tests are going to start to fail in CI/outerloop. If all tests are passing, I'll merge without CORJIT_FLAG_PUBLISH_SECRET_PARAM.

Sounds good?

Copy link
Member

@jkotas jkotas Nov 27, 2019

Choose a reason for hiding this comment

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

I think it would be most appropriate to change the condition to use CORJIT_FLAG_USE_PINVOKE_HELPERS (see my other comment).

Copy link
Member

Choose a reason for hiding this comment

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

CORJIT_FLAG_USE_PINVOKE_HELPERS happens to be used by crossgen only, so you can be sure that you are not changing any of the JIT behavior.

Copy link
Member

@AndyAyersMS AndyAyersMS left a comment

Choose a reason for hiding this comment

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

Looks good.

It would be nice to verify that the within try check bailout is still really needed. Can you open an issue to look into that?

@fadimounir
Copy link
Contributor Author

verify that the within try check bailout is still really needed

What's that?

@AndyAyersMS
Copy link
Member

What's that?

Make sure we really understand why we can't just delete that whole ifdef'd block, and ideally, add a test case.

@fadimounir
Copy link
Contributor Author

Make sure we really understand why we can't just delete that whole ifdef'd block, and ideally, add a test case.

Ah, yes. Will do.

@jkotas
Copy link
Member

jkotas commented Nov 27, 2019

The problem described by the comment on the ifdefed block can't happen with CORJIT_FLAG_USE_PINVOKE_HELPERS because the InlinedFrame is not reused. I think the most appropriate way to change the condition would be:

if (block->hasTryIndex() && !opts.jitFlags->IsSet(JitFlags::CORJIT_FLAG_USE_PINVOKE_HELPERS))
{
    return false;
}

@fadimounir
Copy link
Contributor Author

fadimounir commented Nov 27, 2019

The problem described by the comment on the ifdefed block can't happen with CORJIT_FLAG_USE_PINVOKE_HELPERS. I think the most appropriate way to change the condition would be:

if (block->hasTryIndex() && !opts.jitFlags->IsSet(JitFlags::CORJIT_FLAG_USE_PINVOKE_HELPERS))
{
    return false;
}

That was my first fix attempt, and it wasn't working (see #248 for the error I got). Based on what I debugged in the EH handling, we need to teach the runtime to handle inlined pinvokes where the caller has EH blocks. The unwinding after executing the catch block wasn't working correctly.
Main issue is InlinedCallFrame::UpdateRegDisplay not filling all the registers in the context, because we don't save the state of all registers in the ICF.

This is a much more involved change

BTW, if we fix the EH handling, it will be general goodness because we'll be able to inline pinvokes with marshaling into callers.

@fadimounir
Copy link
Contributor Author

fadimounir commented Nov 27, 2019

Thinking about it more, I think that this fix will most likely work too, and look better (this is similar to my initial fix, which is along the line of what you're thinking too, but limited to the stubs only):

if (block->hasTryIndex())
{
    if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_IL_STUB) && opts.ShouldUsePInvokeHelpers())
        return true;

    return false;
}

I will try this out tomorrow

@fadimounir fadimounir added the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Nov 27, 2019
@fadimounir fadimounir changed the title Fix EH handling in PInvoke stubs and remove workaround [WIP] Fix EH handling in PInvoke stubs and remove workaround Nov 27, 2019
@jkotas
Copy link
Member

jkotas commented Nov 27, 2019

Yes, that would look better. Thanks!

@fadimounir fadimounir removed the NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) label Nov 27, 2019
@fadimounir fadimounir changed the title [WIP] Fix EH handling in PInvoke stubs and remove workaround Fix EH handling in PInvoke stubs and remove workaround Nov 27, 2019
@fadimounir fadimounir merged commit d47081a into dotnet:master Nov 27, 2019
@fadimounir fadimounir deleted the FixPinvokeEHIssue branch December 19, 2019 20:42
@ghost ghost locked as resolved and limited conversation to collaborators Dec 11, 2020
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.

4 participants