Skip to content

Drop support for generating "safe" code#69

Merged
tannergooding merged 15 commits intodotnet:masterfrom
tannergooding:regenerate
Jun 11, 2019
Merged

Drop support for generating "safe" code#69
tannergooding merged 15 commits intodotnet:masterfrom
tannergooding:regenerate

Conversation

@tannergooding
Copy link
Copy Markdown
Member

As called out here, the current support for generating "safe" code is very error prone and isn't going to be easy to maintain long term.

Namely, almost all of the support is a "best" guess heuristic where the heuristics can vary wildly from header file to header file and we frequently get it wrong, especially when dealing with bool, out, ref, or arrays.

This removes that support and switches to only generating unsafe code. As part of that, it updates the logic of when the unsafe keyword is emitted to ensure it is only emitted when actually required. This also adds NativeTypeName attribute metadata to members where the native type and managed type do not line up (this could do with some tuning in the future, as it is currently overly conservative). The attribute is marked as [Conditional("DEBUG")] so it doesn't bloat the release binaries.

This then regenerates ClangSharp and fixes up the higher level bindings where required.

@tannergooding
Copy link
Copy Markdown
Member Author

Sorry for the "big" PR. LLVMSharp is going to be just as bad.

public TranslationUnit TranslationUnit { get; }

public CXToken[] Tokenize(Cursor cursor)
public Span<CXToken> Tokenize(Cursor cursor)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Took a dependency on System.Memory to avoid creating arrays and copying overhead where possible.

return EscapeName(name);
}

private string GetAccessSpecifierName(NamedDecl namedDecl)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Also added support for matching the native access specifier.

if (type is ArrayType arrayType)
{
name = GetTypeName(namedDecl, arrayType.ElementType);
name = GetTypeName(namedDecl, arrayType.ElementType, out var nativeElementTypeName);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The secondary variables here are only used for debugging purposes. In some future PR, they might be used to drop more NativeTypeNameAttributes when possible (but the logic for treating unsigned int * as equal to uint* or transforming const unsigned int * const * to be const uint * const * is a bit tricky).


switch (name)
{
case "int8_t":
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

All of these "just work" now.

public CXString GetParent(ref CXCursorKind kind) => clang.getCompletionParent(this, ref kind);
public CXString GetParent(out CXCursorKind kind)
{
fixed (CXCursorKind* pKind = &kind)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The downside (or upside, depending on how you look at it) to having unsafe bindings is you get to explicitly see all the "hidden" overhead that the marshaler was adding (such as implicitly pinning a value).


namespace ClangSharp
{
public unsafe partial struct CXModuleMapDescriptor : IDisposable
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Had to add a couple more "safe" wrappers so the non-PInvokeGenerator unit tests would pass.


public CXErrorCode SetFrameworkModuleName(string name)
{
using (var marshaledName = new MarshaledString(name))
Copy link
Copy Markdown
Member Author

@tannergooding tannergooding Jun 11, 2019

Choose a reason for hiding this comment

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

This is how I opted to do the string marshaling (and cleanup) currently.

Ideally the "safe" API would take a UTF8String rather than string, but that type doesn't exist yet (and support for treating ReadOnlySpan<byte> as a utf8string is fairly limited in netstandard2.0).

}

var span = new ReadOnlySpan<byte>(pCString, int.MaxValue);
return span.Slice(0, span.IndexOf((byte)'\0')).AsString();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Marshaling from native to string just utilizes ROSpan<byte> and System.Text.Encoding.UTF8.GetBytes

using (var marshaledCommandLineArgs = new MarshaledStringArray(commandLineArgs))
fixed (CXUnsavedFile* pUnsavedFiles = unsavedFiles)
{
var pCommandLineArgs = stackalloc sbyte*[marshaledCommandLineArgs.Count];
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Marshaling string arrays is currently a bit more complex. I had tried to allocate a sbyte** and then fill it directly (to avoid needing to allocate a sbyte*[]), but that was having problems and I decided to resolve it later.

public void Dispose() => clang.disposeTranslationUnit(this);

public void DisposeTokens(CXToken[] tokens) => clang.disposeTokens(this, tokens, (uint)tokens.Length);
public void AnnotateTokens(CXToken[] tokens, CXCursor[] cursors)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It would be nice if most of these were updated to take a ROSpan<T> or Span<T>, as appropriate. So you can avoid allocations where appropriate, but I left that to a future PR.

@@ -1,325 +0,0 @@
namespace ClangSharp
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I was able to get rid of all method/type exclusions and just generate the bindings directly instead.

public unsafe partial struct CXCodeCompleteResults
{
public IntPtr Results;
[NativeTypeName("CXCompletionResult *")]
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Here is an example of where, if we "normalized" the native type name when checking for "matches managed type name", we could drop the attribute.

public partial struct CXCompletionResult
public unsafe partial struct CXCompletionResult
{
[NativeTypeName("enum CXCursorKind")]
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Another example of where "normalizing" the native type name would allow us to drop the attribute.

public IntPtr data0; public IntPtr data1; public IntPtr data2;

[NativeTypeName("const void *[3]")]
public _data_e__FixedBuffer data;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I find the attributes to, at a glance, make it much easier to determine the original signature. While still allowing you to use the least expensive managed signature. Here is a good example.

public CXIdxObjCContainerDeclInfo* containerInfo;

[NativeTypeName("const CXIdxBaseClassInfo *")]
public CXIdxBaseClassInfo* superInfo;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It also allows you to see the "const" info, which can be very useful for exposing the right managed API (and knowing things like: "should this be span or rospan").


namespace ClangSharp
{
[Conditional("DEBUG")]
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Conditional("DEBUG") will cause the attribute to be stripped from release binaries, avoiding bloat.


public IntPtr Pointer;

public static implicit operator CXCursorSet(CXCursorSetImpl* value)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Using implicit operators to convert from the native type to the wrapper type when it is an "exact" match.


public IntPtr Pointer;

public static explicit operator CXDiagnostic(void* value)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Using explicit operators to convert from the native type to the wrapper type when it is an "ambiguous" match.

@tannergooding tannergooding merged commit 01e36c8 into dotnet:master Jun 11, 2019
@tannergooding tannergooding deleted the regenerate branch June 12, 2019 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants