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

ComPtr<T> improvements, new __uuidof<T> macro #151

Merged
merged 22 commits into from
Nov 23, 2020

Conversation

Sergio0694
Copy link
Contributor

@Sergio0694 Sergio0694 commented Nov 15, 2020

This PR includes a few tweaks and improvements to the ComPtr<T> type, and a port of the __uuidof<T> macro.

🆕 Added a port of the __uuidof() macro, as a new static __uuidof<T>() API in the Windows class. This makes it easy to grab a Guid* value for a given type T, and similarly to the original macro it also can be used to assign a Guid value directly to a local variable. This works through a special UuidOfType type that wraps a Guid* pointer and adds implicit conversions when needed. This also indirectly speeds up a couple of ComPtr<T> APIs as the reflection done in typeof(T).GUID is no longer needed. Tier 1 JIT should also be able to completely replace the code with just a hardcoded address, but even without that it should be better than typeof(T).GUID every time. Also, this new API simplifies a lot having to grab a rIID value to use in various APIs.

🆕 Also added some utility As and CopyTo overloads.

Diff for the API surface:

 namespace TerraFX.Interop
 {
    public static class Windows
    {
+        public static unsafe UuidOfType __uuidof<T>() where T : unmanaged;

+        public readonly unsafe ref struct UuidOfType
+        {
+            public static implicit operator Guid(UuidOfType guid);
+            public static implicit operator Guid*(UuidOfType guid);
+        }
    }

     public unsafe struct ComPtr<T> : IDisposable
         where T : unmanaged
     {
         public ComPtr(T* other);
         public ComPtr(ComPtr<T> other);

         public static implicit operator ComPtr<T>(T* other);
         public static implicit operator T*(ComPtr<T> other);

         public readonly int As<U>(ComPtr<U>* p) where U : unmanaged;
+        public readonly int As<U>(ref ComPtr<U> other) where U : unmanaged;
         public readonly int AsIID(Guid* riid, ComPtr<IUnknown>* other);
+        public readonly int AsIID(Guid* riid, ref ComPtr<IUnknown> other);
         public void Attach(T* other);
         public T* Detach();
         public readonly int CopyTo(T** ptr);
+        public readonly int CopyTo(ComPtr<T>* p);
+        public readonly int CopyTo(ref ComPtr<T> other);
         public readonly int CopyTo<U>(U** ptr) where U : unmanaged;
+        public readonly int CopyTo<U>(ComPtr<U>* p) where U : unmanaged;
+        public readonly int CopyTo<U>(ref ComPtr<U> other) where U : unmanaged;
         public readonly int CopyTo(Guid* riid, void** ptr);
+        public readonly int CopyTo(Guid* riid, ComPtr<IUnknown>* p);
+        public readonly int CopyTo(Guid* riid, ref ComPtr<IUnknown> other);
         public void Dispose();
         public readonly T* Get();
         public readonly T** GetAddressOf();
         public readonly ref T* GetPinnableReference();
         public T** ReleaseAndGetAddressOf();
         public uint Reset();
+        public void Swap(ComPtr<T>* r);
         public void Swap(ref ComPtr<T> other);
     }
 }

🚀 Improved codegen to GetAddressOf and ReleaseAndGetAddressOf. The code was previously using a fixed block, which resulted in poor codegen as the JIT isn't currently able to optimize that away. Fixed that using Unsafe APIs:

; Old codegen
ComPtr`1[[System.Guid, System.Private.CoreLib]].GetAddressOf_Old()
    L0000: push rax
    L0001: xor eax, eax
    L0003: mov [rsp], rax
    L0007: mov [rsp], rcx
    L000b: mov rax, rcx
    L000e: add rsp, 8
    L0012: ret

; New codegen
ComPtr`1[[System.Guid, System.Private.CoreLib]].GetAddressOf_New()
    L0000: mov rax, rcx
    L0003: ret

📖 Minor code tweaks, added complete XML docs to the type

@CLAassistant
Copy link

CLAassistant commented Nov 15, 2020

CLA assistant check
All committers have signed the CLA.

@Sergio0694 Sergio0694 changed the title ComPtr<T> improvements ComPtr<T> improvements, new __uuidof<T> macro Nov 17, 2020
/// </summary>
/// <param name="guid">The input <see cref="GUID"/> instance to read data for.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Guid*(GUID guid) => guid.riid;
Copy link
Member

Choose a reason for hiding this comment

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

What does the codegen look like for this and the other implicit operator?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd say it looks quite nice 😄
Plus there's the fact that the tiered JIT could even elide the static constructor check entirely.
But other than this there's basically just the call to the static constructor (which we would've had anyway since Windows has statically initialized fields as well), and then all the rest is completely elided by the JIT.

Guid* converter (click to expand):
public static unsafe Guid* GetAddr()
{
    return __uuidof<ID3D12SomeType>();
}
sub      rsp, 40
mov      rcx, 0xD1FFAB1E
xor      edx, edx
call     CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS
mov      rax, 0xD1FFAB1E
mov      rax, qword ptr [rax]
add      rsp, 40
ret
Guid converter (click to expand):
public static unsafe Guid GetGuid()
{
    return Windows.__uuidof<ID3D12Test>();
}
push     rsi
sub      rsp, 32
vzeroupper 
mov      rsi, rcx
mov      rcx, 0xD1FFAB1E
xor      edx, edx
call     CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS
mov      rax, 0xD1FFAB1E
mov      rax, qword ptr [rax]
vmovdqu  xmm0, xmmword ptr [rax]
vmovdqu  xmmword ptr [rsi], xmm0
mov      rax, rsi
add      rsp, 32
pop      rsi
ret  

In particular for this as we have a direct comparison, the codegen is slightly smaller than just suing static GUIDs from the Windows class, but of course this approach is still much easier to use and less error prone.

Accessing Windows.IID_Foo (click to expand):
public static unsafe Guid GetGuid()
{
    return Windows.IID_Foo;
}
push     rsi
sub      rsp, 32
vzeroupper 
mov      rsi, rcx
mov      rcx, 0xD1FFAB1E
mov      edx, 5
call     CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE
mov      rax, 0xD1FFAB1E
mov      rax, gword ptr [rax]
vmovdqu  xmm0, xmmword ptr [rax+8]
vmovdqu  xmmword ptr [rsi], xmm0
mov      rax, rsi
add      rsp, 32
pop      rsi
ret   

Considering that here we also have the prologue/epilogue that'd just go away once this method is just inlined 🚀

/// <summary>
/// A proxy type that wraps a pointer to GUID data. Values of this type can be implicitly
/// converted to and assigned to <see cref="Guid"/>* or <see cref="Guid"/> parameters.
/// </summary>
Copy link
Member

Choose a reason for hiding this comment

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

I had only called out the doc comment syntax mismatch on the type, but it applies to all added doc comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, right. Fixed in b358ae9 🙂

{
private readonly Guid* riid;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
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 need AggressiveInlining on these. They are extremely small and the JIT should handle it already.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed in 65b7f00.

public ComPtr(T* other)
{
ptr_ = other;

Copy link
Member

Choose a reason for hiding this comment

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

Unnecessary newline was inserted here and a few other places in the file.

Suggested change

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But they looked so nice 😛
Fixed in b2a4be6!

public readonly int As<U>(ref ComPtr<U> other)
where U : unmanaged
{
fixed (ComPtr<U>* p = &other)
Copy link
Member

Choose a reason for hiding this comment

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

The pin could be avoided here by using a local and then internally assigning other.ptr_.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as below, removed in e10f572.

{
InternalAddRef();

fixed (ComPtr<T>* p = &other)
Copy link
Member

@tannergooding tannergooding Nov 22, 2020

Choose a reason for hiding this comment

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

This pin could also be elided with the right helper. The address isn't actually being used in way that requires it to be fixed, so releasing the value and assigning the field directly is probably better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed all extra fixed blocks in e10f572 🚀

public void Dispose()
{
InternalRelease();
T* pointer = ptr_;
Copy link
Member

Choose a reason for hiding this comment

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

Why was this changed from using InternalRelease()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here we don't return the ref count so thought doing this manually and simplified could help the JIT with inlining?

private uint InternalRelease()
{
uint @ref = 0;
uint referenceCount = 0;
Copy link
Member

@tannergooding tannergooding Nov 22, 2020

Choose a reason for hiding this comment

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

The names here were matching the ones used in native code. It would be good to keep them where possible. Same for other places as well where changes have been made to the code.

The patterns and names were kept to help do visual validation and ease future ports.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see, that makes perfect sense 😄
Reverted those changes in 3f93d6e.

@tannergooding tannergooding merged commit b5d348f into terrafx:main Nov 23, 2020
@Sergio0694 Sergio0694 deleted the tweaks/comptr branch November 23, 2020 21:00
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.

3 participants