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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
dc6356f
Fully qualify return type on UnreachableException methods
jtschuster Jun 15, 2023
0cc937e
Merge branch 'main' of https://github.com/dotnet/runtime
jtschuster Jun 16, 2023
a16a7d6
Merge branch 'main' of https://github.com/dotnet/runtime
jtschuster Jun 19, 2023
cca57d3
Merge branch 'main' of https://github.com/dotnet/runtime
jtschuster Jun 28, 2023
ed7cd71
wip
jtschuster Jul 5, 2023
744a39b
Merge branch 'main' of https://github.com/dotnet/runtime into Marshal…
jtschuster Jul 5, 2023
788c862
wip
jtschuster Jul 13, 2023
d4aaad5
Merge branch 'main' of https://github.com/dotnet/runtime into Marshal…
jtschuster Jul 13, 2023
7363299
Merge branch 'main' of https://github.com/dotnet/runtime into Marshal…
jtschuster Jul 14, 2023
1e1345b
wip
jtschuster Jul 17, 2023
5c0ba6a
builds the interop sln. repo build failing
jtschuster Jul 18, 2023
203807e
Merge branch 'main' of https://github.com/dotnet/runtime into Marshal…
jtschuster Jul 18, 2023
f2d7921
Merge branch 'main' of https://github.com/dotnet/runtime into Marshal…
jtschuster Jul 18, 2023
2b99032
Merge branch 'main' of https://github.com/dotnet/runtime into Marshal…
jtschuster Jul 18, 2023
0a532ad
Revert "Update dependencies from https://github.com/dotnet/roslyn bui…
jtschuster Jul 18, 2023
8128579
Revert "Revert "Update dependencies from https://github.com/dotnet/ro…
jtschuster Jul 18, 2023
acf29cc
Merge branch 'main' of https://github.com/dotnet/runtime into Marshal…
jtschuster Jul 18, 2023
f8da5a1
Fix changes to other files
jtschuster Jul 18, 2023
8a8f573
fix changes within interop sln
jtschuster Jul 18, 2023
86099a2
Tests pass
jtschuster Jul 18, 2023
8b14784
Remove trailing space
jtschuster Jul 18, 2023
ee7de7f
Remove commented code
jtschuster Jul 18, 2023
cf38596
Refactor com interfaces and rename isMidlOut
jtschuster Jul 18, 2023
29a6bad
PR feedback: rename _guid, be consistent in local vs property use
jtschuster Jul 18, 2023
1627cc9
Remove temp test project
jtschuster Jul 18, 2023
c25b5d7
wip
jtschuster Jul 19, 2023
31d5f1b
wip
jtschuster Jul 19, 2023
5370aa4
Merge branch 'main' of https://github.com/dotnet/runtime into Marshal…
jtschuster Jul 19, 2023
c615eb2
Almost Passing tests, skipping [out] collections cleanup
jtschuster Jul 21, 2023
525f58c
Passing tests
jtschuster Jul 21, 2023
0fccde6
Remove extraneous changes
jtschuster Jul 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ public BlockSyntax GenerateStubBody(int index, ImmutableArray<FunctionPointerUnm
allStatements.Add(MarshallerHelpers.CreateSetLastPInvokeErrorStatement(LastErrorIdentifier));
}

allStatements.AddRange(statements.AssignOut);

// Return
if (!_marshallers.IsManagedVoidReturn)
allStatements.Add(ReturnStatement(IdentifierName(_context.GetIdentifiers(_marshallers.ManagedReturnMarshaller.TypeInfo).managed)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,22 @@ public ManagedTypeInfo AsNativeType(TypePositionInfo info) =>
IsFunctionPointer: false);
public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeContext context)
{
if (context.CurrentStage == StubCodeContext.Stage.AssignOut)
{
var assignOut = this.GeneratePointerAssignOut(info, context);
if (assignOut != null)
{
yield return assignOut;
}
yield break;
}

if (context.CurrentStage != StubCodeContext.Stage.Unmarshal)
{
yield break;
}

var (managed, native) = context.GetIdentifiers(info);

// <managed> = ComWrappers.ComInterfaceDispatch.GetInstance<<managedType>>(<native>);
yield return ExpressionStatement(
AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ public BlockSyntax GenerateStubBody(ExpressionSyntax methodToInvoke)
setupStatements.Add(MarshallerHelpers.Declare(PredefinedType(Token(SyntaxKind.BoolKeyword)), InvokeSucceededIdentifier, initializeToDefault: true));
}

setupStatements.AddRange(declarations.Initializations);
setupStatements.AddRange(declarations.Variables);
setupStatements.AddRange(declarations.Initializations);
setupStatements.AddRange(statements.Setup);

List<StatementSyntax> tryStatements = new();
Expand Down Expand Up @@ -94,7 +94,6 @@ public BlockSyntax GenerateStubBody(ExpressionSyntax methodToInvoke)

SyntaxList<CatchClauseSyntax> catchClauses = List(statements.ManagedExceptionCatchClauses);

finallyStatements.AddRange(statements.Cleanup);
if (finallyStatements.Count > 0)
{
allStatements.Add(
Expand All @@ -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.


// Return
if (!_marshallers.IsUnmanagedVoidReturn)
allStatements.Add(ReturnStatement(IdentifierName(_context.GetIdentifiers(_marshallers.NativeReturnMarshaller.TypeInfo).native)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ public BlockSyntax GeneratePInvokeBody(string dllImportName)
allStatements.Add(MarshallerHelpers.CreateSetLastPInvokeErrorStatement(LastErrorIdentifier));
}

allStatements.AddRange(statements.AssignOut);

// Return
if (!_marshallers.IsManagedVoidReturn)
allStatements.Add(ReturnStatement(IdentifierName(_context.GetIdentifiers(_marshallers.ManagedReturnMarshaller.TypeInfo).managed)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.

public ImmutableArray<StatementSyntax> AssignOut { get; init; }

public ImmutableArray<CatchClauseSyntax> ManagedExceptionCatchClauses { get; init; }

Expand All @@ -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.

ManagedExceptionCatchClauses = GenerateCatchClauseForManagedException(marshallers, context),
AssignOut = GenerateStatementsForStubContext(marshallers, context with { CurrentStage = StubCodeContext.Stage.AssignOut })
};
}
public static GeneratedStatements Create(BoundGenerators marshallers, StubCodeContext context, ExpressionSyntax expressionToInvoke)
Expand Down Expand Up @@ -71,7 +75,26 @@ private static ImmutableArray<StatementSyntax> GenerateStatementsForStubContext(
ImmutableArray<StatementSyntax>.Builder statementsToUpdate = ImmutableArray.CreateBuilder<StatementSyntax>();
foreach (BoundGenerator marshaller in marshallers.SignatureMarshallers)
{
statementsToUpdate.AddRange(marshaller.Generator.Generate(marshaller.TypeInfo, context));
StubCodeContext localContext = context;
if (context.CurrentStage is StubCodeContext.Stage.Marshal or StubCodeContext.Stage.AssignOut
&& MarshallerHelpers.MarshalsOutToLocal(marshaller.TypeInfo, context))
{
localContext = new MarshalToLocalContext(context);
}
// Right now, MarshalsOutToLocal is the only way we determine if we assign out, so we can return early here for perf
if (context.CurrentStage is StubCodeContext.Stage.AssignOut)
{
if (!MarshallerHelpers.MarshalsOutToLocal(marshaller.TypeInfo, context))
continue;
else
localContext = new AssignOutContext(context, marshaller.Generator.AsParameter(marshaller.TypeInfo, context).Identifier.ToString());
Comment on lines +87 to +90
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());

}
if (context is MarshalToLocalContext && context.CurrentStage == StubCodeContext.Stage.Cleanup)
{
if (!MarshallerHelpers.MarshalsOutToLocal(marshaller.TypeInfo, context))
continue;
}
statementsToUpdate.AddRange(marshaller.Generator.Generate(marshaller.TypeInfo, localContext));
}

if (statementsToUpdate.Count > 0)
Expand Down Expand Up @@ -164,6 +187,11 @@ private static ImmutableArray<CatchClauseSyntax> GenerateCatchClauseForManagedEx
catchClauseBuilder.AddRange(
managedExceptionMarshaller.Generator.Generate(
managedExceptionMarshaller.TypeInfo, context with { CurrentStage = StubCodeContext.Stage.PinnedMarshal }));
catchClauseBuilder.AddRange(GenerateStatementsForStubContext(marshallers, new MarshalToLocalContext(context with { CurrentStage = StubCodeContext.Stage.Cleanup })));
if (!marshallers.IsUnmanagedVoidReturn)
{
catchClauseBuilder.Add(ReturnStatement(IdentifierName(context.GetIdentifiers(marshallers.NativeReturnMarshaller.TypeInfo).native)));
}
return ImmutableArray.Create(
CatchClause(
CatchDeclaration(ParseTypeName(TypeNames.System_Exception), Identifier(managed)),
Expand All @@ -185,6 +213,7 @@ private static SyntaxTriviaList GenerateStageTrivia(StubCodeContext.Stage stage)
StubCodeContext.Stage.Cleanup => "Perform required cleanup.",
StubCodeContext.Stage.NotifyForSuccessfulInvoke => "Keep alive any managed objects that need to stay alive across the call.",
StubCodeContext.Stage.GuaranteedUnmarshal => "Convert native data to managed data even in the case of an exception during the non-cleanup phases.",
StubCodeContext.Stage.AssignOut => "Assign to parameters",
_ => throw new ArgumentOutOfRangeException(nameof(stage))
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Microsoft.Interop
{
internal static class ICustomTypeMarshallingStrategyExtensions
{
public static IEnumerable<ExpressionStatementSyntax> GenerateDefaultAssignOutStatement(this ICustomTypeMarshallingStrategy _, TypePositionInfo info, AssignOutContext context)
{
if (MarshallerHelpers.MarshalsOutToLocal(info, context))
{
var (_, native) = context.GetIdentifiers(info);
return ImmutableArray.Create(MarshallerHelpers.GenerateAssignmentToPointerValue(context.ParameterIdentifier, native));
}
return ImmutableArray<ExpressionStatementSyntax>.Empty;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;

namespace Microsoft.Interop
{
public sealed record AssignOutContext : StubCodeContext
{
internal StubCodeContext InnerContext { get; init; }
public string ParameterIdentifier { get; init; }

internal AssignOutContext(StubCodeContext inner, string parameterIdentifier)
{
InnerContext = inner;
Debug.Assert(inner.CurrentStage == Stage.AssignOut);
CurrentStage = Stage.AssignOut;
Direction = inner.Direction;
ParentContext = inner.ParentContext;
ParameterIdentifier = parameterIdentifier;
}

public override (TargetFramework framework, Version version) GetTargetFramework() => InnerContext.GetTargetFramework();

public override bool SingleFrameSpansNativeContext => InnerContext.SingleFrameSpansNativeContext;

public override bool AdditionalTemporaryStateLivesAcrossStages => InnerContext.AdditionalTemporaryStateLivesAcrossStages;

public override (string managed, string native) GetIdentifiers(TypePositionInfo info)
=> (InnerContext.GetIdentifiers(info).managed, InnerContext.GetAdditionalIdentifier(info, "out"));

public override string GetAdditionalIdentifier(TypePositionInfo info, string name) => InnerContext.GetAdditionalIdentifier(info, name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,20 +278,20 @@ private ResolvedGenerator CreateCustomNativeTypeMarshaller(TypePositionInfo info

FreeStrategy freeStrategy = GetFreeStrategy(info, context);

if (freeStrategy == FreeStrategy.FreeOriginal)
{
marshallingStrategy = new UnmanagedToManagedOwnershipTrackingStrategy(marshallingStrategy);
}
//if (freeStrategy == FreeStrategy.FreeOriginal)
//{
// marshallingStrategy = new UnmanagedToManagedOwnershipTrackingStrategy(marshallingStrategy);
//}
Comment on lines +281 to +284
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

Comment on lines +281 to +284
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.


if (freeStrategy != FreeStrategy.NoFree && marshallerData.Shape.HasFlag(MarshallerShape.Free))
{
marshallingStrategy = new StatelessFreeMarshalling(marshallingStrategy, marshallerData.MarshallerType.Syntax);
}

if (freeStrategy == FreeStrategy.FreeOriginal)
{
marshallingStrategy = new CleanupOwnedOriginalValueMarshalling(marshallingStrategy);
}
//if (freeStrategy == FreeStrategy.FreeOriginal)
//{
// marshallingStrategy = new CleanupOwnedOriginalValueMarshalling(marshallingStrategy);
//}
}

IMarshallingGenerator marshallingGenerator = new CustomTypeMarshallingGenerator(marshallingStrategy, ByValueMarshalKindSupportDescriptor.Default, marshallerData.Shape.HasFlag(MarshallerShape.StatelessPinnableReference));
Expand Down Expand Up @@ -371,19 +371,19 @@ private ResolvedGenerator CreateNativeCollectionMarshaller(

var freeStrategy = GetFreeStrategy(info, context);
IElementsMarshallingCollectionSource collectionSource = new StatefulLinearCollectionSource();
ElementsMarshalling elementsMarshalling = CreateElementsMarshalling(marshallerData, elementInfo, elementMarshaller, unmanagedElementType, collectionSource);
ElementsMarshalling elementsMarshalling = CreateElementsMarshalling(marshallerData, elementInfo, elementMarshaller, unmanagedElementType, collectionSource, nativeType.Syntax, true);

if (freeStrategy == FreeStrategy.FreeOriginal)
{
marshallingStrategy = new UnmanagedToManagedOwnershipTrackingStrategy(marshallingStrategy);
}
//if (freeStrategy == FreeStrategy.FreeOriginal)
//{
// marshallingStrategy = new UnmanagedToManagedOwnershipTrackingStrategy(marshallingStrategy);
//}

marshallingStrategy = new StatefulLinearCollectionMarshalling(marshallingStrategy, marshallerData.Shape, numElementsExpression, elementsMarshalling, freeStrategy != FreeStrategy.NoFree);
marshallingStrategy = new StatefulLinearCollectionMarshalling(marshallingStrategy, marshallerData.Shape, numElementsExpression, elementsMarshalling, freeStrategy is not FreeStrategy.NoFree or FreeStrategy.FreeTemp);

if (freeStrategy == FreeStrategy.FreeOriginal)
{
marshallingStrategy = new CleanupOwnedOriginalValueMarshalling(marshallingStrategy);
}
//if (freeStrategy == FreeStrategy.FreeOriginal)
//{
// marshallingStrategy = new CleanupOwnedOriginalValueMarshalling(marshallingStrategy);
//}

if (marshallerData.Shape.HasFlag(MarshallerShape.Free))
{
Expand All @@ -397,14 +397,14 @@ private ResolvedGenerator CreateNativeCollectionMarshaller(
var freeStrategy = GetFreeStrategy(info, context);

IElementsMarshallingCollectionSource collectionSource = new StatelessLinearCollectionSource(marshallerTypeSyntax);
if (freeStrategy == FreeStrategy.FreeOriginal)
{
marshallingStrategy = new UnmanagedToManagedOwnershipTrackingStrategy(marshallingStrategy);
}
//if (freeStrategy == FreeStrategy.FreeOriginal)
//{
// marshallingStrategy = new UnmanagedToManagedOwnershipTrackingStrategy(marshallingStrategy);
//}

ElementsMarshalling elementsMarshalling = CreateElementsMarshalling(marshallerData, elementInfo, elementMarshaller, unmanagedElementType, collectionSource);
ElementsMarshalling elementsMarshalling = CreateElementsMarshalling(marshallerData, elementInfo, elementMarshaller, unmanagedElementType, collectionSource, nativeType.Syntax, false);

marshallingStrategy = new StatelessLinearCollectionMarshalling(marshallingStrategy, elementsMarshalling, nativeType, marshallerData.Shape, numElementsExpression, freeStrategy != FreeStrategy.NoFree);
marshallingStrategy = new StatelessLinearCollectionMarshalling(marshallingStrategy, elementsMarshalling, nativeType, marshallerData.Shape, numElementsExpression, !(freeStrategy is FreeStrategy.NoFree or FreeStrategy.FreeTemp));

if (marshallerData.Shape.HasFlag(MarshallerShape.CallerAllocatedBuffer))
{
Expand All @@ -420,10 +420,10 @@ private ResolvedGenerator CreateNativeCollectionMarshaller(
marshallingStrategy = new StatelessFreeMarshalling(marshallingStrategy, marshallerTypeSyntax);
}

if (freeStrategy == FreeStrategy.FreeOriginal)
{
marshallingStrategy = new CleanupOwnedOriginalValueMarshalling(marshallingStrategy);
}
//if (freeStrategy == FreeStrategy.FreeOriginal)
//{
// marshallingStrategy = new CleanupOwnedOriginalValueMarshalling(marshallingStrategy);
//}
}

ByValueMarshalKindSupportDescriptor byValueMarshalKindSupport;
Expand Down Expand Up @@ -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?

}

private static FreeStrategy GetFreeStrategy(TypePositionInfo info, StubCodeContext context)
Expand All @@ -491,12 +492,16 @@ private static FreeStrategy GetFreeStrategy(TypePositionInfo info, StubCodeConte
{
return FreeStrategy.FreeOriginal;
}
if (MarshallerHelpers.MarshalsOutToLocal(info, context))
{
return FreeStrategy.FreeTemp;
}

// In an unmanaged-to-managed stub, we don't take ownership of the value when it isn't passed by 'ref'.
return FreeStrategy.NoFree;
}

private static ElementsMarshalling CreateElementsMarshalling(CustomTypeMarshallerData marshallerData, TypePositionInfo elementInfo, IMarshallingGenerator elementMarshaller, TypeSyntax unmanagedElementType, IElementsMarshallingCollectionSource collectionSource)
private static ElementsMarshalling CreateElementsMarshalling(CustomTypeMarshallerData marshallerData, TypePositionInfo elementInfo, IMarshallingGenerator elementMarshaller, TypeSyntax unmanagedElementType, IElementsMarshallingCollectionSource collectionSource, TypeSyntax parameterPointedToType, bool isStateful)
{
ElementsMarshalling elementsMarshalling;

Expand All @@ -507,7 +512,7 @@ private static ElementsMarshalling CreateElementsMarshalling(CustomTypeMarshalle
}
else
{
elementsMarshalling = new NonBlittableElementsMarshalling(unmanagedElementType, elementMarshaller, elementInfo, collectionSource);
elementsMarshalling = new NonBlittableElementsMarshalling(unmanagedElementType, elementMarshaller, elementInfo, collectionSource, parameterPointedToType, isStateful);
}

return elementsMarshalling;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeCont
IdentifierName(nativeIdentifier)));
}
break;
case StubCodeContext.Stage.AssignOut:
if (this.GeneratePointerAssignOut(info, context) is { } assignOut)
yield return assignOut;
break;
default:
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ public IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeCont
LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(comparand)))));
}
break;
case StubCodeContext.Stage.AssignOut:
if (this.GeneratePointerAssignOut(info, context) is { } assignOut)
yield return assignOut;
break;
default:
break;
}
Expand Down
Loading
Loading