-
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
Inject IJW Copy Constructor calls in the JIT instead of in the IL stubs #106000
Conversation
…these copies." This reverts commit 79b1cd2.
…he tests in JitStress (as they won't be fixed when the JIT introduces these extra checks when not asking the VM)" This reverts commit 35d0de9.
…d creating shadow vars
…to do the generic method instantiation on every call (only when jitting)
…which the P/Invoke scenario will use)
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
I'm somewhat skeptical that annotating lcl vars in the jit will be sufficient. Isn't the need to invoke a copy ctor a property of the type and not of the variable? |
In our case, it actually is a property of the variable. In this particular scenario, the modifier means "this variable needs a copy constructor to be used whenever the value is copied, but the C++ compiler is not generating the body of this method. The runtime needs to emit copy constructor calls in this method." |
This is a bit too viral for my taste. It is basically adding a new type of local in the JIT that cannot be bitwise copied. All current and future transformations within the JIT need to account for this kind of local with its special rules. Also, it does not seem right to me that internal copies for this type of local results in insertion of user visible calls. For example, simple primitive functions like I think instead we should restrict the insertion of the copy constructors to the few places we expect the copy to be user visible. Particularly we know that if the address of an IL local is taken, then it is going to be stable throughout the method. Because of that I do not think that internal copies made by the JIT need to call the copy constructor. Rather, we need to call the copy constructor at just the boundaries, before the address is stable and at the copy that the IL stub is inserting in the IL:
Of course this works based on the fact that the IL patterns here are very restricted, but I really would like to avoid that we try to teach the current JIT and all future changes to the JIT about the fact that types with copy constructors exist. Also, what about the destructor? When we insert one of these copy constructors it seems we inevitably also need to insert a destructor somewhere. |
If I revert 390185c then that moves the handling for shadow parameters and spills into the GS check/importer level. However, that still leaves the If you have a recommendation for a better way to handle that case (and get it out of lowering) than how I'm currently doing it, I'm open for suggestions.
I'm fine with only supporting restricted IL patterns, but I'd like suggestions of where to put asserts to ensure that any other IL patterns cause JIT asserts.
In this case (at least the way that it's traditionally been structured), when the IL stub does a copy, it actually does "copy constructor of new + destructor of old", which is what I implemented This provides us with the expected behavior as per our implicit contract with the C++/CLI compiler's codegen. |
I don't think we will be able to get it out of lowering. But what I'm after is that we make use of the fact that the only copies we allow inserting the copy constructor call for are the shadow params one and the stack pushed x86 one to the specific pinvoke call that we expect to see in this IL.
I'm specifically after allowing the JIT to make (bitwise) copies of these locals as long as it does not alter user visible behavior. Fundamentally once the address is taken of the IL local the JIT has to guarantee that it remains stable, and the transformations it makes cannot alter that visible behavior. Here is an example: say that your IL stub for some reason is passing a static field as the value of an argument that comes after the IL local with the copy constructor. The JIT is going to change your var copy = vectorWithCopyConstructor;
if (!class.IsInitialized)
class.Cctor();
M(copy, class.SomeStaticField) The same form of change can happen for a multitude of reasons internally within the JIT. I would expect that you can hit this particular introduction of a copy today when the JIT runs with I don't think it is simple to assert anything here, but I don't think the introducing the copy constructor calls more liberally improves the situation. You can still create IL that does not properly maintain the "move" semantics you are implementing here (e.g. two copies from the same IL local would cause problems that would not result in any asserts here). |
I think I understand better now. I'll try to adjust the implementation implement it with a table in the JIT to do the copy/move from the "original" local into the arg slot for the specific GT_PUTARG_STK case and do the copy/move into the shadow arg for the Reverse P/Invoke case. |
…gher layers" This reverts commit 390185c.
…he original arg and don't do intermediate copy-constructor calls. The bitwise copies are invalid, but there's no possible way for the user to see them. They'll always get passed a correctly copy-constructed value.
@jkoritzinsky please check test failures. |
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.
JIT changes LGTM, with a few minor things.
…ructor scenarios (by counting args in the correct direction)
@jakobbotsch I had to make a few changes in |
src/coreclr/jit/lower.cpp
Outdated
if (call->GetUnmanagedCallConv() == CorInfoCallConvExtension::Thiscall && | ||
argIndex == call->gtArgs.CountUserArgs() - 1) | ||
{ | ||
assert(arg.GetNode()->OperIs(GT_PUTARG_REG)); | ||
continue; | ||
} | ||
|
||
if (argIndex >= comp->info.compILargsCount) | ||
{ | ||
argIndex--; | ||
continue; | ||
} |
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 you need some handling for retbufs as well.
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.
Ah, I guess not... Should be skipped by IsUserArg
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.
When is this argIndex >= comp->info.compILargsCount
true?
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 fails on the LCID interop tests, in which we introduce an additional argument into the native call that isn't present in the managed P/Invoke signature.
The C++/CLI compiler will never emit a P/Invoke that uses both copy constructors and introduces the LCID argument.
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 guess one way to be completely explicit about it would be to check m_specialCopyArgs != nullptr
above and add an assert(call.CountUserArgs() == comp->info.compILargsCount)
, but this looks ok to me as well.
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'll add the assert. Better to be explicit about it, and adding that check will probably help jit throughput (as we won't iterate through the args unless we actually are in a scenario where we may do something).
/azp run runtime, runtime-dev-innerloop |
Azure Pipelines successfully started running 2 pipeline(s). |
…e IL stubs (dotnet#106000)" This reverts commit ec067c8. Just being cautions -- the PR was merged before all tests had run.
Fixes Issue #100751 and #100033 by injecting copy constructor calls in the JIT instead of injecting them in the IL and then using special hooks/interceptors to handle non-IL-supported/accessible scenarios.
Re-enable GS checks in Reverse P/Invokes now that we handel them correctly.