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

Generate an 'out' local variable for unmanaged to managed stubs when the value needs to be marshalled out to unmanaged #89139

Closed
wants to merge 31 commits into from

Conversation

jtschuster
Copy link
Member

Generates an 'out' local variable that will hold the marshalled value that will be passed back on a successful return. Right now it just generates the variable and discards the value immediately. The next step is to actually use that variable name in marshalling and copy the value to the parameter right before a successful return.

Using the variable just requires changing the MarshalToLocalContext to use the commented version of GetIdentifiers.

Copying from the local to the parameter is a little more complicated because of the ByValue [Out] case.

First step of #88438

jtschuster and others added 22 commits June 15, 2023 16:34
@ghost
Copy link

ghost commented Jul 18, 2023

Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.

Issue Details

Generates an 'out' local variable that will hold the marshalled value that will be passed back on a successful return. Right now it just generates the variable and discards the value immediately. The next step is to actually use that variable name in marshalling and copy the value to the parameter right before a successful return.

Using the variable just requires changing the MarshalToLocalContext to use the commented version of GetIdentifiers.

Copying from the local to the parameter is a little more complicated because of the ByValue [Out] case.

First step of #88438

Author: jtschuster
Assignees: -
Labels:

area-System.Runtime.InteropServices

Milestone: -

@jtschuster jtschuster mentioned this pull request Jul 21, 2023
Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT left a comment

Choose a reason for hiding this comment

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

Is this needed for a specific scenario or are we trying to be precise? I want to make sure we are weighting priorities correctly here.

@@ -110,6 +109,9 @@ public BlockSyntax GenerateStubBody(ExpressionSyntax methodToInvoke)
allStatements.AddRange(tryStatements);
}

allStatements.AddRange(statements.AssignOut);
allStatements.AddRange(statements.Cleanup);
Copy link
Member

Choose a reason for hiding this comment

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

This order concerns me. We added the finally staements above after we added the cleanup. Inverting this seems like a profound change.

@@ -22,6 +22,8 @@ public struct GeneratedStatements
public ImmutableArray<StatementSyntax> NotifyForSuccessfulInvoke { get; init; }
public ImmutableArray<StatementSyntax> GuaranteedUnmarshal { get; init; }
public ImmutableArray<StatementSyntax> Cleanup { get; init; }
//public ImmutableArray<StatementSyntax> CleanupNativeOut { get; init; }
Copy link
Member

Choose a reason for hiding this comment

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

What are the next steps for commented out code? At a minimum we need to have that captured here so we know how to uncomment or delete it.

@@ -39,7 +41,9 @@ public static GeneratedStatements Create(BoundGenerators marshallers, StubCodeCo
NotifyForSuccessfulInvoke = GenerateStatementsForStubContext(marshallers, context with { CurrentStage = StubCodeContext.Stage.NotifyForSuccessfulInvoke }),
GuaranteedUnmarshal = GenerateStatementsForStubContext(marshallers, context with { CurrentStage = StubCodeContext.Stage.GuaranteedUnmarshal }),
Cleanup = GenerateStatementsForStubContext(marshallers, context with { CurrentStage = StubCodeContext.Stage.Cleanup }),
ManagedExceptionCatchClauses = GenerateCatchClauseForManagedException(marshallers, context)
//CleanupNativeOut = GenerateStatementsForStubContext(marshallers, new MarshalToLocalContext(context with { CurrentStage = StubCodeContext.Stage.Cleanup })),
Copy link
Member

Choose a reason for hiding this comment

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

Again with commented out code.

Comment on lines +87 to +90
if (!MarshallerHelpers.MarshalsOutToLocal(marshaller.TypeInfo, context))
continue;
else
localContext = new AssignOutContext(context, marshaller.Generator.AsParameter(marshaller.TypeInfo, context).Identifier.ToString());
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (!MarshallerHelpers.MarshalsOutToLocal(marshaller.TypeInfo, context))
continue;
else
localContext = new AssignOutContext(context, marshaller.Generator.AsParameter(marshaller.TypeInfo, context).Identifier.ToString());
if (!MarshallerHelpers.MarshalsOutToLocal(marshaller.TypeInfo, context))
{
continue;
}
localContext = new AssignOutContext(context, marshaller.Generator.AsParameter(marshaller.TypeInfo, context).Identifier.ToString());

Comment on lines +281 to +284
//if (freeStrategy == FreeStrategy.FreeOriginal)
//{
// marshallingStrategy = new UnmanagedToManagedOwnershipTrackingStrategy(marshallingStrategy);
//}
Copy link
Member

Choose a reason for hiding this comment

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

I'll stop commenting, but undescribed commented out code is a red-flag in general.

Comment on lines +105 to +112
if (context is MarshalToLocalContext
&& (info.ByValueContentsMarshalKind.HasFlag(ByValueContentsMarshalKind.Out)
|| info.RefKind is RefKind.Out))
break;
else if (context.Direction is MarshalDirection.UnmanagedToManaged && info.RefKind is RefKind.Out)
break;
else
return _nativeTypeMarshaller.GenerateCleanupStatements(info, context);
Copy link
Member

Choose a reason for hiding this comment

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

If/else chains require braces.

public override bool AdditionalTemporaryStateLivesAcrossStages => InnerContext.AdditionalTemporaryStateLivesAcrossStages;

public override (string managed, string native) GetIdentifiers(TypePositionInfo info)
//=> InnerContext.GetIdentifiers(info);
Copy link
Member

Choose a reason for hiding this comment

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

?

Comment on lines +281 to +284
//if (freeStrategy == FreeStrategy.FreeOriginal)
//{
// marshallingStrategy = new UnmanagedToManagedOwnershipTrackingStrategy(marshallingStrategy);
//}
Copy link
Member

Choose a reason for hiding this comment

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

Please remove the commented-out code before merging

@@ -467,7 +467,8 @@ private enum FreeStrategy
/// <summary>
/// Do not free the unmanaged value, we don't own it.
/// </summary>
NoFree
NoFree,
FreeTemp
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a comment here about what this strategy means?

Comment on lines +103 to +104
// TODO: Correctly clean up the allocated contents that aren't transferred back to the caller
// We don't correctly clean up the [out] parameters
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 TODO still accurate?

statements.Add(numElementsAssignment);
if (MarshallerHelpers.MarshalsOutToLocal(info, context))
{
// #pragma warning disable CS9081
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't be suppressing this rule. This suppression likely covers up a use-out-of-scope bug. We should either declare native_out as scoped, or (if we can't), revisit how we do this. Can you share some example code that goes down this route?

if (MarshallerHelpers.MarshalsOutToLocal(info, context) && _isStateful)
{
var elementType = _parameterPointedToType;//info.IsByRef ? PointerType(_unmanagedElementType) : _unmanagedElementType;
// <value_native_out> = (<nativeType>*)Unsafe.AsPointer(ref <nativeSpan>.GetPinnableReference());
Copy link
Member

Choose a reason for hiding this comment

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

Similar question here around using a "pinnable reference" without pinning. Are we sure that this works? If this is only in the "stateful element" case, we shouldn't add code that will do bad things and instead we should gracefully fail earlier.

Comment on lines 194 to 197
// To simplify propogating back the value to the "byref" parameter,
// we'll just declare the native identifier as a ref to its type.
// The rest of the code we generate will work as expected, and we don't need
// to manually propogate back the updated values after the call.
Copy link
Member

Choose a reason for hiding this comment

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

Looks like this comment is out of date now. We should remove it if it's no longer accurate.

Comment on lines +80 to +81
// https://github.com/dotnet/runtime/issues/89265
//obj.Double(data, data.Length);
Copy link
Member

Choose a reason for hiding this comment

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

Can we split this case into a separate Xunit test and mark it with ActiveIssue instead of just commenting it out? Using ActiveIssue makes it easier to track/find the tests when we fix them.

@@ -281,6 +278,7 @@ public unsafe void ValidateArrayElementsByRefFreed_Stateful()
}

[Fact]
[ActiveIssue("Make issue: ByValueContents out do not get freed")]
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 a new issue introduced by this PR or is this an existing issue?

Comment on lines +782 to +787
// https://github.com/dotnet/runtime/issues/89265
//yield return new object[] {
// ID(),
// codeSnippets.ByValueMarshallingOfType(inAttribute + outAttribute + constElementCount, "int[]", paramNameWithLocation),
// new DiagnosticResult[] { inOutAttributeIsDefaultDiagnostic }
//};
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this test is passing in main. We're really late in the release cycle, so we shouldn't be disabling failing tests to get PRs in.

@@ -18,6 +18,13 @@
'$(TargetOS)' == 'tvossimulator'">true</_TargetsAppleOS>
</PropertyGroup>

<Target Name="DisableComInterfaceGenerator"
Copy link
Member

Choose a reason for hiding this comment

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

Let's add a comment here about this target if we're going to merge it into main.

@jtschuster
Copy link
Member Author

Is this needed for a specific scenario or are we trying to be precise? I want to make sure we are weighting priorities correctly here.

This is mainly to be precise and avoid memory leaks and potential double frees if marshalling fails.

@jtschuster
Copy link
Member Author

This PR requires a few test regressions that aren't palatable and isn't that much smaller than the full change, so I'll close this in favor of the complete change.

@jtschuster jtschuster closed this Jul 27, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Aug 26, 2023
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.

3 participants