Skip to content

Commit

Permalink
Add string marshallers for ANSI and platform-defined (dotnet/runtimel…
Browse files Browse the repository at this point in the history
…ab#288)

* Update to latest version of analyzer test package
* Add string marshaller for CharSet.Ansi
* Add string marshaller for CharSet.Auto
* Update compatibility doc

Commit migrated from dotnet/runtimelab@ebbd0b2
  • Loading branch information
elinor-fung authored Nov 5, 2020
1 parent d746fcb commit 01a1930
Show file tree
Hide file tree
Showing 15 changed files with 820 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ public override IEnumerable<StatementSyntax> Generate(TypePositionInfo info, Stu
VariableDeclaration(
AsNativeType(info),
SingletonSeparatedList(VariableDeclarator(nativeIdentifier))));

if (TryGenerateSetupSyntax(info, context, out StatementSyntax conditionalAllocSetup))
yield return conditionalAllocSetup;

break;
case StubCodeContext.Stage.Marshal:
if (info.RefKind != RefKind.Out)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,39 @@ namespace Microsoft.Interop
{
internal abstract class ConditionalStackallocMarshallingGenerator : IMarshallingGenerator
{
private static string GetAllocationMarkerIdentifier(string managedIdentifier) => $"{managedIdentifier}__allocated";
protected static string GetAllocationMarkerIdentifier(string managedIdentifier) => $"{managedIdentifier}__allocated";

private static string GetByteLengthIdentifier(string managedIdentifier) => $"{managedIdentifier}__bytelen";

private static string GetStackAllocIdentifier(string managedIdentifier) => $"{managedIdentifier}__stackptr";

protected bool UsesConditionalStackAlloc(TypePositionInfo info, StubCodeContext context)
{
return context.CanUseAdditionalTemporaryState
&& context.StackSpaceUsable
&& (!info.IsByRef || info.RefKind == RefKind.In)
&& !info.IsManagedReturnPosition;
}

protected bool TryGenerateSetupSyntax(TypePositionInfo info, StubCodeContext context, out StatementSyntax statement)
{
statement = EmptyStatement();

if (!UsesConditionalStackAlloc(info, context))
return false;

string allocationMarkerIdentifier = GetAllocationMarkerIdentifier(context.GetIdentifiers(info).managed);

// bool <allocationMarker> = false;
statement = LocalDeclarationStatement(
VariableDeclaration(
PredefinedType(Token(SyntaxKind.BoolKeyword)),
SingletonSeparatedList(
VariableDeclarator(allocationMarkerIdentifier)
.WithInitializer(EqualsValueClause(LiteralExpression(SyntaxKind.FalseLiteralExpression))))));
return true;
}

protected IEnumerable<StatementSyntax> GenerateConditionalAllocationSyntax(
TypePositionInfo info,
StubCodeContext context,
Expand All @@ -39,8 +66,8 @@ protected IEnumerable<StatementSyntax> GenerateConditionalAllocationSyntax(
VariableDeclarator(byteLenIdentifier)
.WithInitializer(EqualsValueClause(
GenerateByteLengthCalculationExpression(info, context))))));
if (!context.CanUseAdditionalTemporaryState || !context.StackSpaceUsable || (info.IsByRef && info.RefKind != RefKind.In))

if (!UsesConditionalStackAlloc(info, context))
{
List<StatementSyntax> statements = new List<StatementSyntax>();
if (allocationRequiresByteLength)
Expand All @@ -58,13 +85,6 @@ protected IEnumerable<StatementSyntax> GenerateConditionalAllocationSyntax(
Block(statements));
yield break;
}
// <allocationMarkerIdentifier> = false;
yield return LocalDeclarationStatement(
VariableDeclaration(
PredefinedType(Token(SyntaxKind.BoolKeyword)),
SingletonSeparatedList(
VariableDeclarator(allocationMarkerIdentifier)
.WithInitializer(EqualsValueClause(LiteralExpression(SyntaxKind.FalseLiteralExpression))))));

// Code block for stackalloc if number of bytes is below threshold size
var marshalOnStack = Block(
Expand Down Expand Up @@ -94,6 +114,7 @@ protected IEnumerable<StatementSyntax> GenerateConditionalAllocationSyntax(
// if (<byteLen> > <StackAllocBytesThreshold>)
// {
// <allocationStatement>;
// <allocationMarker> = true;
// }
// else
// {
Expand Down Expand Up @@ -135,7 +156,7 @@ protected StatementSyntax GenerateConditionalAllocationFreeSyntax(
{
(string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info);
string allocationMarkerIdentifier = GetAllocationMarkerIdentifier(managedIdentifier);
if (!context.CanUseAdditionalTemporaryState || (info.IsByRef && info.RefKind != RefKind.In))
if (!UsesConditionalStackAlloc(info, context))
{
return ExpressionStatement(GenerateFreeExpression(info, context));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ namespace Microsoft.Interop
{
internal static class MarshallerHelpers
{
public static readonly ExpressionSyntax IsWindows = InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
ParseTypeName("System.OperatingSystem"),
IdentifierName("IsWindows")));

public static readonly TypeSyntax InteropServicesMarshalType = ParseTypeName(TypeNames.System_Runtime_InteropServices_Marshal);

public static ForStatementSyntax GetForLoop(string collectionIdentifier, string indexerIdentifier)
{
// for(int <indexerIdentifier> = 0; <indexerIdentifier> < <collectionIdentifier>.Length; ++<indexerIdentifier>)
Expand Down Expand Up @@ -41,5 +49,48 @@ public static ForStatementSyntax GetForLoop(string collectionIdentifier, string
SyntaxKind.PreIncrementExpression,
IdentifierName(indexerIdentifier))));
}

public static class StringMarshaller
{
public static ExpressionSyntax AllocationExpression(CharEncoding encoding, string managedIdentifier)
{
string methodName = encoding switch
{
CharEncoding.Utf8 => "StringToCoTaskMemUTF8",
CharEncoding.Utf16 => "StringToCoTaskMemUni",
CharEncoding.Ansi => "StringToCoTaskMemAnsi",
_ => throw new System.ArgumentOutOfRangeException(nameof(encoding))
};

// Marshal.StringToCoTaskMemUTF8(<managed>)
// or
// Marshal.StringToCoTaskMemUni(<managed>)
// or
// Marshal.StringToCoTaskMemAnsi(<managed>)
return InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
InteropServicesMarshalType,
IdentifierName(methodName)),
ArgumentList(
SingletonSeparatedList<ArgumentSyntax>(
Argument(IdentifierName(managedIdentifier)))));
}

public static ExpressionSyntax FreeExpression(string nativeIdentifier)
{
// Marshal.FreeCoTaskMem((IntPtr)<nativeIdentifier>)
return InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
InteropServicesMarshalType,
IdentifierName("FreeCoTaskMem")),
ArgumentList(SingletonSeparatedList(
Argument(
CastExpression(
ParseTypeName("System.IntPtr"),
IdentifierName(nativeIdentifier))))));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,13 @@ internal class MarshallingGenerators
public static readonly ByteBoolMarshaller ByteBool = new ByteBoolMarshaller();
public static readonly WinBoolMarshaller WinBool = new WinBoolMarshaller();
public static readonly VariantBoolMarshaller VariantBool = new VariantBoolMarshaller();

public static readonly Utf16CharMarshaller Utf16Char = new Utf16CharMarshaller();
public static readonly Utf16StringMarshaller Utf16String = new Utf16StringMarshaller();
public static readonly Utf8StringMarshaller Utf8String = new Utf8StringMarshaller();
public static readonly AnsiStringMarshaller AnsiString = new AnsiStringMarshaller(Utf8String);
public static readonly PlatformDefinedStringMarshaller PlatformDefinedString = new PlatformDefinedStringMarshaller(Utf16String, Utf8String);

public static readonly Forwarder Forwarder = new Forwarder();
public static readonly BlittableMarshaller Blittable = new BlittableMarshaller();
public static readonly DelegateMarshaller Delegate = new DelegateMarshaller();
Expand Down Expand Up @@ -259,6 +263,8 @@ private static IMarshallingGenerator CreateStringMarshaller(TypePositionInfo inf
{
switch (marshalAsInfo.UnmanagedType)
{
case UnmanagedType.LPStr:
return AnsiString;
case UnmanagedType.LPTStr:
case UnmanagedType.LPWStr:
return Utf16String;
Expand All @@ -270,10 +276,14 @@ private static IMarshallingGenerator CreateStringMarshaller(TypePositionInfo inf
{
switch (marshalStringInfo.CharEncoding)
{
case CharEncoding.Ansi:
return AnsiString;
case CharEncoding.Utf16:
return Utf16String;
case CharEncoding.Utf8:
return Utf8String;
case CharEncoding.PlatformDefined:
return PlatformDefinedString;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ public override IEnumerable<StatementSyntax> Generate(TypePositionInfo info, Stu
VariableDeclaration(
AsNativeType(info),
SingletonSeparatedList(VariableDeclarator(nativeIdentifier))));

if (TryGenerateSetupSyntax(info, context, out StatementSyntax conditionalAllocSetup))
yield return conditionalAllocSetup;

break;
case StubCodeContext.Stage.Marshal:
if (info.RefKind != RefKind.Out)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

using static Microsoft.Interop.MarshallerHelpers;

namespace Microsoft.Interop
{
internal sealed class AnsiStringMarshaller : ConditionalStackallocMarshallingGenerator
{
private static readonly TypeSyntax NativeType = PointerType(PredefinedType(Token(SyntaxKind.ByteKeyword)));

private readonly Utf8StringMarshaller utf8StringMarshaller;

public AnsiStringMarshaller(Utf8StringMarshaller utf8StringMarshaller)
{
this.utf8StringMarshaller = utf8StringMarshaller;
}

public override ArgumentSyntax AsArgument(TypePositionInfo info, StubCodeContext context)
{
string identifier = context.GetIdentifiers(info).native;
if (info.IsByRef)
{
// &<nativeIdentifier>
return Argument(
PrefixUnaryExpression(
SyntaxKind.AddressOfExpression,
IdentifierName(identifier)));
}

// <nativeIdentifier>
return Argument(IdentifierName(identifier));
}

public override TypeSyntax AsNativeType(TypePositionInfo info)
{
// byte*
return NativeType;
}

public override ParameterSyntax AsParameter(TypePositionInfo info)
{
// byte**
// or
// byte*
var type = info.IsByRef
? PointerType(AsNativeType(info))
: AsNativeType(info);
return Parameter(Identifier(info.InstanceIdentifier))
.WithType(type);
}

public override IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeContext context)
{
(string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info);
switch (context.CurrentStage)
{
case StubCodeContext.Stage.Setup:
// byte* <native>;
yield return LocalDeclarationStatement(
VariableDeclaration(
AsNativeType(info),
SingletonSeparatedList(VariableDeclarator(nativeIdentifier))));

if (TryGenerateSetupSyntax(info, context, out StatementSyntax conditionalAllocSetup))
yield return conditionalAllocSetup;

break;
case StubCodeContext.Stage.Marshal:
if (info.RefKind != RefKind.Out)
{
// <native> = (byte*)Marshal.StringToCoTaskMemAnsi(<managed>);
var windowsBlock = Block(
ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName(nativeIdentifier),
CastExpression(
AsNativeType(info),
StringMarshaller.AllocationExpression(CharEncoding.Ansi, managedIdentifier)))));

// Set the allocation marker to true if it is being used
if (UsesConditionalStackAlloc(info, context))
{
// <allocationMarker> = true
windowsBlock.AddStatements(
ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName(GetAllocationMarkerIdentifier(managedIdentifier)),
LiteralExpression(SyntaxKind.TrueLiteralExpression))));
}

// [Compat] The generated source for ANSI string marshalling does not optimize for
// allocating on the stack based on the string length. It always uses AllocCoTaskMem.
// if (OperatingSystem.IsWindows())
// {
// <native> = (byte*)Marshal.StringToCoTaskMemAnsi(<managed>);
// }
// else
// {
// << marshal as UTF-8 >>
// }
yield return IfStatement(IsWindows,
windowsBlock,
ElseClause(
Block(this.utf8StringMarshaller.Generate(info, context))));
}
break;
case StubCodeContext.Stage.Unmarshal:
if (info.IsManagedReturnPosition || (info.IsByRef && info.RefKind != RefKind.In))
{
// if (OperatingSystem.IsWindows())
// {
// <managed> = <native> == null ? null : new string((sbyte*)<native>);
// }
// else
// {
// << unmarshal as UTF-8 >>
// }
yield return IfStatement(IsWindows,
Block(
ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName(managedIdentifier),
ConditionalExpression(
BinaryExpression(
SyntaxKind.EqualsExpression,
IdentifierName(nativeIdentifier),
LiteralExpression(SyntaxKind.NullLiteralExpression)),
LiteralExpression(SyntaxKind.NullLiteralExpression),
ObjectCreationExpression(
PredefinedType(Token(SyntaxKind.StringKeyword)),
ArgumentList(SingletonSeparatedList(
Argument(
CastExpression(
PointerType(PredefinedType(Token(SyntaxKind.SByteKeyword))),
IdentifierName(nativeIdentifier))))),
initializer: null))))),
ElseClause(
Block(this.utf8StringMarshaller.Generate(info, context))));
}
break;
case StubCodeContext.Stage.Cleanup:
yield return GenerateConditionalAllocationFreeSyntax(info, context);
break;
}
}

public override bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context) => true;

// This marshaller only uses the conditional allocaction base for setup and cleanup.
// It always allocates for ANSI (Windows) and relies on the UTF-8 (non-Windows) string marshaller for allocation/marshalling.
protected override ExpressionSyntax GenerateAllocationExpression(TypePositionInfo info, StubCodeContext context, SyntaxToken byteLengthIdentifier, out bool allocationRequiresByteLength) => throw new NotImplementedException();
protected override ExpressionSyntax GenerateByteLengthCalculationExpression(TypePositionInfo info, StubCodeContext context) => throw new NotImplementedException();
protected override StatementSyntax GenerateStackallocOnlyValueMarshalling(TypePositionInfo info, StubCodeContext context, SyntaxToken byteLengthIdentifier, SyntaxToken stackAllocPtrIdentifier) => throw new NotImplementedException();

protected override ExpressionSyntax GenerateFreeExpression(TypePositionInfo info, StubCodeContext context)
{
return StringMarshaller.FreeExpression(context.GetIdentifiers(info).native);
}
}
}
Loading

0 comments on commit 01a1930

Please sign in to comment.