Add profiler ELT test (#39550)
authorDavid Mason <davmason@microsoft.com>
Wed, 29 Jul 2020 21:53:07 +0000 (14:53 -0700)
committerGitHub <noreply@github.com>
Wed, 29 Jul 2020 21:53:07 +0000 (14:53 -0700)
Fix the following issues:

    On amd64 linux we didn't save and restore the xmm registers, and didn't handle enregistered 16 bytes structs as return values
    On arm we didn't save and restore the floating point registers (I made the linux arm helpers match the windows arm helpers)
    On arm64 we didn't handle 16 byte enregistered structs as return values

And add tests

23 files changed:
src/coreclr/src/vm/amd64/asmhelpers.S
src/coreclr/src/vm/amd64/profiler.cpp
src/coreclr/src/vm/arm/asmhelpers.S
src/coreclr/src/vm/arm/profiler.cpp
src/coreclr/src/vm/arm64/asmhelpers.S
src/coreclr/src/vm/arm64/asmhelpers.asm
src/coreclr/src/vm/arm64/profiler.cpp
src/coreclr/src/vm/proftoeeinterfaceimpl.h
src/coreclr/tests/issues.targets
src/tests/profiler/common/ProfilerTestRunner.cs
src/tests/profiler/elt/slowpathcommon.cs [new file with mode: 0644]
src/tests/profiler/elt/slowpathcommon.csproj [new file with mode: 0644]
src/tests/profiler/elt/slowpatheltenter.cs [new file with mode: 0644]
src/tests/profiler/elt/slowpatheltenter.csproj [new file with mode: 0644]
src/tests/profiler/elt/slowpatheltleave.cs [new file with mode: 0644]
src/tests/profiler/elt/slowpatheltleave.csproj [new file with mode: 0644]
src/tests/profiler/native/CMakeLists.txt
src/tests/profiler/native/classfactory.cpp
src/tests/profiler/native/eltprofiler/slowpatheltprofiler.cpp [new file with mode: 0644]
src/tests/profiler/native/eltprofiler/slowpatheltprofiler.h [new file with mode: 0644]
src/tests/profiler/native/profiler.cpp
src/tests/profiler/native/profiler.h
src/tests/profiler/native/profilerstring.h

index 82d6984ab545dfc868023784726deb994c186b47..7a70334d6383b33c9a5d9afd3b1a4ea5d77d6e12 100644 (file)
 # we can align to 16 and be guaranteed to not exceed the frame size
 .equ STACK_FUDGE_FACTOR, 0x8
 
-# Space to keep xmm0 and xmm1
-.equ SIZEOF_FP_ARG_SPILL, 0x10*2
-
-.equ OFFSETOF_FP_ARG_SPILL, SIZEOF_PROFILE_PLATFORM_SPECIFIC_DATA + STACK_FUDGE_FACTOR
-
 # SIZEOF_STACK_FRAME is how many bytes we reserve in our ELT helpers below
 # There are three components, the first is space for profiler platform specific
 # data struct that we spill the general purpose registers to, then space to
 # spill xmm0 and xmm1, then finally 8 bytes of padding to ensure that the xmm
 # register reads/writes are aligned on 16 bytes.
-.equ SIZEOF_STACK_FRAME, SIZEOF_PROFILE_PLATFORM_SPECIFIC_DATA + SIZEOF_FP_ARG_SPILL + STACK_FUDGE_FACTOR
+.equ SIZEOF_STACK_FRAME, SIZEOF_PROFILE_PLATFORM_SPECIFIC_DATA + STACK_FUDGE_FACTOR
 
 .equ PROFILE_ENTER, 0x1
 .equ PROFILE_LEAVE, 0x2
@@ -131,15 +126,6 @@ NESTED_ENTRY ProfileEnterNaked, _TEXT, NoHandler
   mov                     r10, 0x1 # PROFILE_ENTER
   mov                     [rsp + 0xa8], r10d              #      -- struct flags field
 
-  # get aligned stack ptr (rsp + OFFSETOF_FP_ARG_SPILL) & (-16)
-  lea                     rax, [rsp + OFFSETOF_FP_ARG_SPILL]
-  and                     rax, -16
-
-  # we need to be able to restore the fp return register
-  # save fp return registers
-  movdqa                  [rax + 0x00], xmm0
-  movdqa                  [rax + 0x10], xmm1
-
   END_PROLOGUE
 
   # rdi already contains the clientInfo
@@ -148,10 +134,14 @@ NESTED_ENTRY ProfileEnterNaked, _TEXT, NoHandler
   call                    C_FUNC(ProfileEnter)
 
   # restore fp return registers
-  lea                     rax, [rsp + OFFSETOF_FP_ARG_SPILL]
-  and                     rax, -16
-  movdqa                  xmm0, [rax + 0x00]
-  movdqa                  xmm1, [rax + 0x10]
+  movsd                   xmm0, real8 ptr [rsp + 0x38]    #      -- struct flt0 field
+  movsd                   xmm1, real8 ptr [rsp + 0x40]    #      -- struct flt1 field
+  movsd                   xmm2, real8 ptr [rsp + 0x48]    #      -- struct flt2 field
+  movsd                   xmm3, real8 ptr [rsp + 0x50]    #      -- struct flt3 field
+  movsd                   xmm4, real8 ptr [rsp + 0x58]    #      -- struct flt4 field
+  movsd                   xmm5, real8 ptr [rsp + 0x60]    #      -- struct flt5 field
+  movsd                   xmm6, real8 ptr [rsp + 0x68]    #      -- struct flt6 field
+  movsd                   xmm7, real8 ptr [rsp + 0x70]    #      -- struct flt7 field
 
   # restore arg registers
   mov                     rdi, [rsp + 0x78]
@@ -216,15 +206,6 @@ NESTED_ENTRY ProfileLeaveNaked, _TEXT, NoHandler
   mov                     r10, 0x2  # PROFILE_LEAVE
   mov                     [rsp + 0xa8], r10d   # flags           -- struct flags field
 
-  # get aligned stack ptr (rsp + OFFSETOF_FP_ARG_SPILL) & (-16)
-  lea                     rax, [rsp + OFFSETOF_FP_ARG_SPILL]
-  and                     rax, -16
-
-  # we need to be able to restore the fp return register
-  # save fp return registers
-  movdqa                  [rax + 0x00], xmm0
-  movdqa                  [rax + 0x10], xmm1
-
   END_PROLOGUE
 
   # rdi already contains the clientInfo
@@ -232,10 +213,14 @@ NESTED_ENTRY ProfileLeaveNaked, _TEXT, NoHandler
   call                    C_FUNC(ProfileLeave)
 
   # restore fp return registers
-  lea                     rax, [rsp + OFFSETOF_FP_ARG_SPILL]
-  and                     rax, -16
-  movdqa                  xmm0, [rax + 0x00]
-  movdqa                  xmm1, [rax + 0x10]
+  movsd                   xmm0, real8 ptr [rsp + 0x38]    #      -- struct flt0 field
+  movsd                   xmm1, real8 ptr [rsp + 0x40]    #      -- struct flt1 field
+  movsd                   xmm2, real8 ptr [rsp + 0x48]    #      -- struct flt2 field
+  movsd                   xmm3, real8 ptr [rsp + 0x50]    #      -- struct flt3 field
+  movsd                   xmm4, real8 ptr [rsp + 0x58]    #      -- struct flt4 field
+  movsd                   xmm5, real8 ptr [rsp + 0x60]    #      -- struct flt5 field
+  movsd                   xmm6, real8 ptr [rsp + 0x68]    #      -- struct flt6 field
+  movsd                   xmm7, real8 ptr [rsp + 0x70]    #      -- struct flt7 field
 
   # restore int return register
   mov                     rax, [rsp + 0x28]
@@ -295,15 +280,6 @@ NESTED_ENTRY ProfileTailcallNaked, _TEXT, NoHandler
   mov                     r10, 0x2  # PROFILE_LEAVE
   mov                     [rsp + 0xa8], r10d   # flags           -- struct flags field
 
-  # get aligned stack ptr (rsp + OFFSETOF_FP_ARG_SPILL) & (-16)
-  lea                     rax, [rsp + OFFSETOF_FP_ARG_SPILL]
-  and                     rax, -16
-
-  # we need to be able to restore the fp return register
-  # save fp return registers
-  movdqa                  [rax + 0x00], xmm0
-  movdqa                  [rax + 0x10], xmm1
-
   END_PROLOGUE
 
   # rdi already contains the clientInfo
@@ -311,10 +287,14 @@ NESTED_ENTRY ProfileTailcallNaked, _TEXT, NoHandler
   call                    C_FUNC(ProfileTailcall)
 
   # restore fp return registers
-  lea                     rax, [rsp + OFFSETOF_FP_ARG_SPILL]
-  and                     rax, -16
-  movdqa                  xmm0, [rax + 0x00]
-  movdqa                  xmm1, [rax + 0x10]
+  movsd                   xmm0, real8 ptr [rsp + 0x38]    #      -- struct flt0 field
+  movsd                   xmm1, real8 ptr [rsp + 0x40]    #      -- struct flt1 field
+  movsd                   xmm2, real8 ptr [rsp + 0x48]    #      -- struct flt2 field
+  movsd                   xmm3, real8 ptr [rsp + 0x50]    #      -- struct flt3 field
+  movsd                   xmm4, real8 ptr [rsp + 0x58]    #      -- struct flt4 field
+  movsd                   xmm5, real8 ptr [rsp + 0x60]    #      -- struct flt5 field
+  movsd                   xmm6, real8 ptr [rsp + 0x68]    #      -- struct flt6 field
+  movsd                   xmm7, real8 ptr [rsp + 0x70]    #      -- struct flt7 field
 
   # restore int return register
   mov                     rax, [rsp + 0x28]
index afe9685cda845b45aa650a7c611dd62b3114a34d..025bfab6f56408cd408acb267127aa1d84a6cd24 100644 (file)
@@ -126,6 +126,7 @@ ProfileArgIterator::ProfileArgIterator(MetaSig * pSig, void * platformSpecificHa
     PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle;
 #ifdef UNIX_AMD64_ABI
     m_bufferPos = 0;
+    ZeroMemory(pData->buffer, PROFILE_PLATFORM_SPECIFIC_DATA_BUFFER_SIZE * sizeof(UINT64)); 
 #endif // UNIX_AMD64_ABI
 
     // unwind a frame and get the Rsp for the profiled method to make sure it matches
@@ -483,17 +484,46 @@ LPVOID ProfileArgIterator::GetReturnBufferAddr(void)
         // by our calling convention, but is required by our profiler spec.
         return (LPVOID)pData->rax;
     }
-
+    
     CorElementType t = m_argIterator.GetSig()->GetReturnType();
-    if (ELEMENT_TYPE_VOID != t)
+    if (ELEMENT_TYPE_VOID == t)
+    {
+        return NULL;
+    }
+
+#ifdef UNIX_AMD64_ABI
+    if (m_argIterator.GetSig()->GetReturnTypeSize() == 16)
     {
-        if (ELEMENT_TYPE_R4 == t || ELEMENT_TYPE_R8 == t)
-            pData->rax = pData->flt0;
+        _ASSERTE(m_bufferPos == 0 && "Nothing else should be using the scratch space during a return");
+
+        // The unix x64 ABI has a special case where a 16 byte struct will be passed in registers
+        // and if there are integer and float args it will be passed in rax/etc and xmm/etc, respectively
+        // which means the values are noncontiguous. Just like the argument passing above
+        // we copy it in to the buffer to fake it being contiguous.
+        UINT flags = m_argIterator.GetFPReturnSize();
 
-        return &(pData->rax);
+        // The lower two bits are used to indicate whether struct args are floating point or integer
+        if (flags & 1)
+        {
+            pData->buffer[0] = pData->flt0;
+            pData->buffer[1] = (flags & 2) ? pData->flt1 : pData->rax;
+        }
+        else
+        {
+            pData->buffer[0] = pData->rax;
+            pData->buffer[1] = (flags & 2) ? pData->flt0 : pData->rdx;
+        }
+
+        return pData->buffer;
     }
-    else
-        return NULL;
+#endif // UNIX_AMD64_ABI
+
+    if (ELEMENT_TYPE_R4 == t || ELEMENT_TYPE_R8 == t)
+    {
+        pData->rax = pData->flt0;
+    }
+    
+    return &(pData->rax);
 }
 
 #undef PROFILE_ENTER
index dbc7baa9a175c78d891fcafabed2b413dd803aca..dcdfda4df350d6056db1b9e40aed9c43d237977e 100644 (file)
@@ -366,134 +366,87 @@ LEAF_ENTRY JIT_ProfilerEnterLeaveTailcallStub, _TEXT
     bx lr
 LEAF_END JIT_ProfilerEnterLeaveTailcallStub, _TEXT
 
-//
-// EXTERN_C void ProfileEnterNaked(FunctionIDOrClientID functionIDOrClientID);
-//
-NESTED_ENTRY ProfileEnterNaked, _TEXT, NoHandler
-    PROLOG_PUSH "{r4, r5, r7, r11, lr}"
-    PROLOG_STACK_SAVE_OFFSET r7, #8
-
-    // fields of PROFILE_PLATFORM_SPECIFIC_DATA, in reverse order
-
-    // UINT32      r0;         // Keep r0 & r1 contiguous to make returning 64-bit results easier
-    // UINT32      r1;
-    // void       *r11;
-    // void       *Pc;
-    // union                   // Float arg registers as 32-bit (s0-s15) and 64-bit (d0-d7)
-    // {
-    //     UINT32  s[16];
-    //     UINT64  d[8];
-    // };
-    // FunctionID  functionId;
-    // void       *probeSp;    // stack pointer of managed function
-    // void       *profiledSp; // location of arguments on stack
-    // LPVOID      hiddenArg;
-    // UINT32      flags;
-    movw        r4, #1
-    push        { /* flags      */ r4 }
-    movw        r4, #0
-    push        { /* hiddenArg  */ r4 }
-    add         r5, r11, #8
-    push        { /* profiledSp */ r5 }
-    add         r5, sp, #32
-    push        { /* probeSp    */ r5 }
-    push        { /* functionId */ r0 }
+#define PROFILE_ENTER    1
+#define PROFILE_LEAVE    2
+#define PROFILE_TAILCALL 4
+// size of profiler data structure plus alignment padding
+#define SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA 104+4
+
+// typedef struct _PROFILE_PLATFORM_SPECIFIC_DATA
+// {
+//     UINT32      r0;         // Keep r0 & r1 contiguous to make returning 64-bit results easier
+//     UINT32      r1;
+//     void       *R11;
+//     void       *Pc;
+//     union                   // Float arg registers as 32-bit (s0-s15) and 64-bit (d0-d7)
+//     {
+//         UINT32  s[16];
+//         UINT64  d[8];
+//     };
+//     FunctionID  functionId;
+//     void       *probeSp;    // stack pointer of managed function
+//     void       *profiledSp; // location of arguments on stack
+//     LPVOID      hiddenArg;
+//     UINT32      flags;
+// } PROFILE_PLATFORM_SPECIFIC_DATA, *PPROFILE_PLATFORM_SPECIFIC_DATA;
+
+.macro GenerateProfileHelper helper, flags
+NESTED_ENTRY \helper\()Naked, _TEXT, NoHandler
+    PROLOG_PUSH "{r0,r3,r9,r12}"
+
+    // for the 5 arguments that do not need popped plus 4 bytes of alignment 
+    alloc_stack 6*4
+
+    // push fp regs
     vpush.64    { d0 - d7 }
-    push        { lr }
-    push        { r11 }
-    push        { /* return value, r4 is NULL */ r4 }
-    push        { /* return value, r4 is NULL */ r4 }
-    mov         r1, sp
-    bl          C_FUNC(ProfileEnter)
-    EPILOG_STACK_RESTORE_OFFSET r7, #8
-    EPILOG_POP "{r4, r5, r7, r11, pc}"
-NESTED_END ProfileEnterNaked, _TEXT
 
-//
-// EXTERN_C void ProfileLeaveNaked(FunctionIDOrClientID functionIDOrClientID);
-//
-NESTED_ENTRY ProfileLeaveNaked, _TEXT, NoHandler
-    PROLOG_PUSH "{r1, r2, r4, r5, r7, r11, lr}"
-    PROLOG_STACK_SAVE_OFFSET r7, #16
-
-    // fields of PROFILE_PLATFORM_SPECIFIC_DATA, in reverse order
-
-    // UINT32      r0;         // Keep r0 & r1 contiguous to make returning 64-bit results easier
-    // UINT32      r1;
-    // void       *r11;
-    // void       *Pc;
-    // union                   // Float arg registers as 32-bit (s0-s15) and 64-bit (d0-d7)
-    // {
-    //     UINT32  s[16];
-    //     UINT64  d[8];
-    // };
-    // FunctionID  functionId;
-    // void       *probeSp;    // stack pointer of managed function
-    // void       *profiledSp; // location of arguments on stack
-    // LPVOID      hiddenArg;
-    // UINT32      flags;
-    movw        r4, #2
-    push        { /* flags      */ r4 }
-    movw        r4, #0
-    push        { /* hiddenArg  */ r4 }
-    add         r5, r11, #8
-    push        { /* profiledSp */ r5 }
-    add         r5, sp, #40
-    push        { /* probeSp    */ r5 }
-    push        { /* functionId */ r0 }
-    vpush.64    { d0 - d7 }
-    push        { lr }
-    push        { r11 }
-    push        { r1 }
-    push        { r0 }
-    mov         r1, sp
-    bl          C_FUNC(ProfileLeave)
-    EPILOG_STACK_RESTORE_OFFSET r7, #16
-    EPILOG_POP "{r1, r2, r4, r5, r7, r11, pc}"
-NESTED_END ProfileLeaveNaked, _TEXT
+    // next three fields pc, r11, r1
+    push        { r1, r11, lr}
 
-//
-// EXTERN_C void ProfileTailcallNaked(FunctionIDOrClientID functionIDOrClientID);
-//
-NESTED_ENTRY ProfileTailcallNaked, _TEXT, NoHandler
-    PROLOG_PUSH "{r1, r2, r4, r5, r7, r11, lr}"
-    PROLOG_STACK_SAVE_OFFSET r7, #16
-
-    // fields of PROFILE_PLATFORM_SPECIFIC_DATA, in reverse order
-
-    // UINT32      r0;         // Keep r0 & r1 contiguous to make returning 64-bit results easier
-    // UINT32      r1;
-    // void       *r11;
-    // void       *Pc;
-    // union                   // Float arg registers as 32-bit (s0-s15) and 64-bit (d0-d7)
-    // {
-    //     UINT32  s[16];
-    //     UINT64  d[8];
-    // };
-    // FunctionID  functionId;
-    // void       *probeSp;    // stack pointer of managed function
-    // void       *profiledSp; // location of arguments on stack
-    // LPVOID      hiddenArg;
-    // UINT32      flags;
-    movw        r4, #2
-    push        { /* flags      */ r4 }
-    movw        r4, #0
-    push        { /* hiddenArg  */ r4 }
-    add         r5, r11, #8
-    push        { /* profiledSp */ r5 }
-    add         r5, sp, #40
-    push        { /* probeSp    */ r5 }
-    push        { /* functionId */ r0 }
-    vpush.64    { d0 - d7 }
-    push        { lr }
-    push        { r11 }
-    push        { r1 }
-    push        { r0 }
+    // return value is in r2 instead of r0 because functionID is passed in r0
+    push        { r2 }
+
+    CHECK_STACK_ALIGNMENT
+
+    // set the other args, starting with functionID
+    str         r0, [sp, #80]
+
+    // probeSp is the original sp when this stub was called
+    add         r2, sp, SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA+20
+    str         r2, [sp, #84] 
+
+    // get the address of the arguments from the frame pointer, store in profiledSp
+    add         r2, r11, #8
+    str         r2, [sp, #88]
+
+    // clear hiddenArg
+    movw        r2, #0
+    str         r2, [sp, #92]
+    
+    // set the flag to indicate what hook this is
+    movw        r2, \flags
+    str         r2, [sp, #96]
+
+    // sp is the address of PROFILE_PLATFORM_SPECIFIC_DATA, then call to C++
     mov         r1, sp
-    bl          C_FUNC(ProfileTailcall)
-    EPILOG_STACK_RESTORE_OFFSET r7, #16
-    EPILOG_POP "{r1, r2, r4, r5, r7, r11, pc}"
-NESTED_END ProfileTailcallNaked, _TEXT
+    bl          C_FUNC(\helper)
+
+    // restore all our regs
+    pop         { r2 }
+    pop         { r1, r11, lr}
+    vpop.64     { d0 - d7 }
+
+    free_stack 6*4
+
+    EPILOG_POP "{r0,r3,r9,r12}"
+
+    bx          lr
+NESTED_END \helper\()Naked, _TEXT
+.endm
+
+GenerateProfileHelper ProfileEnter, PROFILE_ENTER
+GenerateProfileHelper ProfileLeave, PROFILE_LEAVE
+GenerateProfileHelper ProfileTailcall, PROFILE_TAILCALL
 
 #endif
 
index 8bcd075fe986c4b41caa7d3786ae195100728fcb..670dedb8ca8ca12238a16158568b54beca4ed1d5 100644 (file)
@@ -144,7 +144,7 @@ Stack for the above call will look as follows (stack growing downwards):
         Thread::VirtualUnwindCallFrame(&ctx);
 
         // add the prespill register(r0-r3) size to get the stack pointer of previous function
-        _ASSERTE(pData->profiledSp == (void*)(ctx.Sp - 4*4));
+        _ASSERTE(pData->profiledSp == (void*)(ctx.Sp - 4*4) || pData->profiledSp == (void*)(ctx.Sp - 6*4));
     }
 #endif // _DEBUG
 
index 9ff8203f22f4bfa8b4df50dd6dc6bdad83c342f6..a8b0a7c07873a40f79ba14a6b21269d391cb0fce 100644 (file)
@@ -1200,7 +1200,7 @@ LEAF_END JIT_ProfilerEnterLeaveTailcallStub, _TEXT
 #define PROFILE_ENTER    1
 #define PROFILE_LEAVE    2
 #define PROFILE_TAILCALL 4
-#define SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA 256
+#define SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA 272
 
 // ------------------------------------------------------------------
 .macro GenerateProfileHelper helper, flags
index 1250bd32652004194f42eb1fa6ec91e2e635adea..2f9227b1d80df6c86c9024ad8126ed3a65f0f56a 100644 (file)
@@ -1427,7 +1427,7 @@ CallHelper2
  #define PROFILE_ENTER    1
  #define PROFILE_LEAVE    2
  #define PROFILE_TAILCALL 4
- #define SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA 256
+ #define SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA 272
 
 ; ------------------------------------------------------------------
     MACRO
index 64bd9603c877b3b82e3a7ac63033a96767f115b3..ba35eb103eec3050029784f5afa3f9261071ce3d 100644 (file)
@@ -10,6 +10,9 @@
 #define PROFILE_LEAVE    2
 #define PROFILE_TAILCALL 4
 
+// Scratch space to store HFA return values (max 16 bytes)
+#define PROFILE_PLATFORM_SPECIFIC_DATA_BUFFER_SIZE 16
+
 typedef struct _PROFILE_PLATFORM_SPECIFIC_DATA
 {
     void*                  Fp;
@@ -23,6 +26,7 @@ typedef struct _PROFILE_PLATFORM_SPECIFIC_DATA
     void*                  hiddenArg;
     UINT32                 flags;
     UINT32                 unused;
+    BYTE                   buffer[PROFILE_PLATFORM_SPECIFIC_DATA_BUFFER_SIZE];
 } PROFILE_PLATFORM_SPECIFIC_DATA, *PPROFILE_PLATFORM_SPECIFIC_DATA;
 
 UINT_PTR ProfileGetIPFromPlatformSpecificHandle(void* pPlatformSpecificHandle)
@@ -45,7 +49,8 @@ void ProfileSetFunctionIDInPlatformSpecificHandle(void* pPlatformSpecificHandle,
 }
 
 ProfileArgIterator::ProfileArgIterator(MetaSig* pSig, void* pPlatformSpecificHandle)
-    : m_argIterator(pSig)
+    : m_argIterator(pSig),
+    m_bufferPos(0)
 {
     WRAPPER_NO_CONTRACT;
 
@@ -235,8 +240,44 @@ LPVOID ProfileArgIterator::GetReturnBufferAddr(void)
         }
     }
 
-    if (m_argIterator.GetFPReturnSize() != 0)
-    {
+    UINT fpReturnSize = m_argIterator.GetFPReturnSize();
+    if (fpReturnSize != 0)
+    {    
+        TypeHandle thReturnValueType;
+        m_argIterator.GetSig()->GetReturnTypeNormalized(&thReturnValueType);
+        if (!thReturnValueType.IsNull() && thReturnValueType.IsHFA())
+        {
+            UINT hfaFieldSize = fpReturnSize / 4;
+            UINT totalSize = m_argIterator.GetSig()->GetReturnTypeSize();
+            _ASSERTE(totalSize % hfaFieldSize == 0);
+            _ASSERTE(totalSize <= 16);
+
+            BYTE *dest = pData->buffer;
+            for (UINT floatRegIdx = 0; floatRegIdx < totalSize / hfaFieldSize; ++floatRegIdx)
+            {
+                if (hfaFieldSize == 4)
+                {
+                    *(UINT32*)dest = *(UINT32*)&pData->floatArgumentRegisters.q[floatRegIdx];
+                    dest += 4;
+                }
+                else
+                {
+                    _ASSERTE(hfaFieldSize == 8);
+                    *(UINT64*)dest = *(UINT64*)&pData->floatArgumentRegisters.q[floatRegIdx];
+                    dest += 8;
+                }
+
+                if (floatRegIdx > 8)
+                {
+                    // There's only space for 8 arguments in buffer
+                    _ASSERTE(FALSE);
+                    break;
+                }
+            }
+
+            return pData->buffer;
+        }
+
         return &pData->floatArgumentRegisters.q[0];
     }
 
index 2fbfb8e80376c7b4d4594b39656f49b9dafd957b..de5b75a3434d664df4d1664d4e6fa9e992056ce2 100644 (file)
@@ -56,9 +56,9 @@ class ProfileArgIterator
 private:
     void        *m_handle;
     ArgIterator  m_argIterator;
-#ifdef UNIX_AMD64_ABI
+#if defined(UNIX_AMD64_ABI) || defined(TARGET_ARM64)
     UINT64       m_bufferPos;
-#endif // UNIX_AMD64_ABI
+#endif // defined(UNIX_AMD64_ABI) || defined(TARGET_ARM64)
 
 public:
     ProfileArgIterator(MetaSig * pMetaSig, void* platformSpecificHandle);
@@ -74,9 +74,12 @@ public:
         return m_argIterator.NumFixedArgs();
     }
 
-#ifdef UNIX_AMD64_ABI
+#if defined(UNIX_AMD64_ABI)
+    // On certain architectures we can pass args in non-sequential registers,
+    // this function will copy the struct so it is laid out as it would be in memory
+    // so it can be passed to the profiler
     LPVOID CopyStructFromRegisters();
-#endif // UNIX_AMD64_ABI
+#endif // defined(UNIX_AMD64_ABI)
 
     //
     // After initialization, this method is called repeatedly until it
index dc65eef1590d6f31f0ed118d02e10b9cbdf7d515..e21987966398fb4822af92e97e06838eaf008260 100644 (file)
         <ExcludeList Include="$(XunitTestBinBase)/profiler/gc/gc/**">
             <Issue>needs triage</Issue>
         </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)/profiler/elt/slowpatheltenter/*">
+            <Issue>needs triage</Issue>
+        </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)/profiler/elt/slowpatheltleave/*">
+            <Issue>needs triage</Issue>
+        </ExcludeList>
         <ExcludeList Include="$(XunitTestBinBase)/profiler/unittest/metadatagetdispenser/**">
             <Issue>needs triage</Issue>
         </ExcludeList>
index 4600f1f1f2d7223d552e4aee2f7f948d386e3671..bb5bae9d90924d6f912850cd153f6c7b2f8a9c9a 100644 (file)
@@ -33,10 +33,11 @@ namespace Profiler.Tests
 
             arguments = profileePath + " RunTest " + profileeArguments;
             program = GetCorerunPath();
+            string profilerPath = GetProfilerPath();
             if (!profileeOptions.HasFlag(ProfileeOptions.NoStartupAttach))
             {
                 envVars.Add("CORECLR_ENABLE_PROFILING", "1");
-                envVars.Add("CORECLR_PROFILER_PATH", GetProfilerPath());
+                envVars.Add("CORECLR_PROFILER_PATH", profilerPath);
                 envVars.Add("CORECLR_PROFILER", "{" + profilerClsid + "}");
             }
 
@@ -48,7 +49,8 @@ namespace Profiler.Tests
                 envVars.Add("COMPlus_JITMinOpts", "0");
             }
 
-            string profilerPath = GetProfilerPath();
+            envVars.Add("Profiler_Test_Name", testName);
+
             if(!File.Exists(profilerPath))
             {
                 LogTestFailure("Profiler library not found at expected path: " + profilerPath);
diff --git a/src/tests/profiler/elt/slowpathcommon.cs b/src/tests/profiler/elt/slowpathcommon.cs
new file mode 100644 (file)
index 0000000..eab383d
--- /dev/null
@@ -0,0 +1,169 @@
+// 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.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics.Tracing;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace SlowPathELTTests
+{
+    [StructLayout(LayoutKind.Sequential)]
+    public struct IntegerStruct
+    {
+        public int x;
+        public int y;
+
+        public IntegerStruct(int x, int y)
+        {
+            this.x = x;
+            this.y = y;
+        }
+
+        public override String ToString()
+        {
+            return $"x={x} y={y}";
+        }
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    public struct FloatingPointStruct
+    {
+        public double d1;
+        public double d2;
+
+        public FloatingPointStruct(double d1, double d2)
+        {
+            this.d1 = d1;
+            this.d2 = d2;
+        }
+
+        public override String ToString()
+        {
+            return $"d1={d1} d2={d2}";
+        }
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    public struct MixedStruct
+    {
+        public int x;
+        public double d;
+
+        public MixedStruct(int x, double d)
+        {
+            this.x = x;
+            this.d = d;
+        }
+
+        public override String ToString()
+        {
+            return $"x={x} d={d}";
+        }
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    public struct LargeStruct
+    {
+        public int x0;
+        public double d0;
+        public int x1;
+        public double d1;
+        public int x2;
+        public double d2;
+        public int x3;
+        public double d3;
+
+        public LargeStruct(int x0, 
+                           double d0,
+                           int x1,
+                           double d1,
+                           int x2,
+                           double d2,
+                           int x3,
+                           double d3)
+        {
+            this. x0 = x0;
+            this.d0 = d0;
+            this.x1 = x1;
+            this.d1 = d1;
+            this.x2 = x2;
+            this.d2 = d2;
+            this.x3 = x3;
+            this.d3 = d3;
+        }
+
+        public override String ToString()
+        {
+            return $"x0={x0} d0={d0} x1={x1} d1={d1} x2={x2} d2={d2} x3={x3} d3={d3}";
+        }
+    }
+
+    public class SlowPathELTHelpers
+    {
+        public static int RunTest()
+        {
+            Console.WriteLine($"SimpleArgsFunc returned {SimpleArgsFunc(-123, -4.3f, "Hello, test!")}");
+
+            Console.WriteLine($"MixedStructFunc returned {MixedStructFunc(new MixedStruct(1, 1))}");
+
+            Console.WriteLine($"LargeStructFunc returned {LargeStructFunc(new LargeStruct(0, 0, 1, 1, 2, 2, 3, 3))}");
+
+            Console.WriteLine($"IntegerStructFunc returned {IntegerStructFunc(new IntegerStruct(14, 256))}");
+
+            Console.WriteLine($"FloatingPointStructFunc returned {FloatingPointStructFunc(new FloatingPointStruct(13.0, 145.2))}");
+
+            Console.WriteLine($"DoubleRetFunc returned {DoubleRetFunc()}");
+
+            return 100;
+        }
+
+        [MethodImplAttribute(MethodImplOptions.NoInlining)]
+        public static string SimpleArgsFunc(int x, float y, String str)
+        {
+            Console.WriteLine($"x={x} y={y} str={str}");
+            return "Hello from SimpleArgsFunc!";
+        }
+
+        [MethodImplAttribute(MethodImplOptions.NoInlining)]
+        public static MixedStruct MixedStructFunc(MixedStruct ss)
+        {
+            Console.WriteLine($"ss={ss}");
+            ss.x = 4;
+            return ss;
+        }
+
+        [MethodImplAttribute(MethodImplOptions.NoInlining)]
+        public static int LargeStructFunc(LargeStruct ls)
+        {
+            Console.WriteLine($"ls={ls}");
+            return 3;
+        }
+
+        [MethodImplAttribute(MethodImplOptions.NoInlining)]
+        public static IntegerStruct IntegerStructFunc(IntegerStruct its)
+        {
+            its.x = 21;
+            return its;
+        }
+
+        [MethodImplAttribute(MethodImplOptions.NoInlining)]
+        public static FloatingPointStruct FloatingPointStructFunc(FloatingPointStruct fps)
+        {
+            fps.d2 = 256.8;
+            return fps;
+        }
+
+        [MethodImplAttribute(MethodImplOptions.NoInlining)]
+        public static double DoubleRetFunc()
+        {
+            return 13.0;
+        }
+    }
+}
diff --git a/src/tests/profiler/elt/slowpathcommon.csproj b/src/tests/profiler/elt/slowpathcommon.csproj
new file mode 100644 (file)
index 0000000..869ebc2
--- /dev/null
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Library</OutputType>
+    <CLRTestKind>BuildOnly</CLRTestKind>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <CLRTestPriority>0</CLRTestPriority>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/profiler/elt/slowpatheltenter.cs b/src/tests/profiler/elt/slowpatheltenter.cs
new file mode 100644 (file)
index 0000000..83d6bb5
--- /dev/null
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Profiler.Tests;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics.Tracing;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace SlowPathELTTests
+{
+    class SlowPathELTEnter
+    {
+        static readonly Guid EventPipeWritingProfilerGuid = new Guid("0B36296B-EC47-44DA-8320-DC5E3071DD06");
+
+        public static int Main(string[] args)
+        {
+            if (args.Length > 0 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase))
+            {
+                return SlowPathELTHelpers.RunTest();
+            }
+
+            return ProfilerTestRunner.Run(profileePath: System.Reflection.Assembly.GetExecutingAssembly().Location,
+                                          testName: "ELTSlowPathEnter",
+                                          profilerClsid: EventPipeWritingProfilerGuid);
+        }
+    }
+}
diff --git a/src/tests/profiler/elt/slowpatheltenter.csproj b/src/tests/profiler/elt/slowpatheltenter.csproj
new file mode 100644 (file)
index 0000000..8d2ac30
--- /dev/null
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+    <OutputType>exe</OutputType>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <CLRTestPriority>0</CLRTestPriority>
+    <Optimize>true</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+    <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+    <ProjectReference Include="../common/profiler_common.csproj" />
+    <ProjectReference Include="slowpathcommon.csproj" />
+    <ProjectReference Include="$(MSBuildThisFileDirectory)/../native/CMakeLists.txt" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/profiler/elt/slowpatheltleave.cs b/src/tests/profiler/elt/slowpatheltleave.cs
new file mode 100644 (file)
index 0000000..da8fd03
--- /dev/null
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Profiler.Tests;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics.Tracing;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace SlowPathELTTests
+{
+    class SlowPathELTLeave
+    {
+        static readonly Guid EventPipeWritingProfilerGuid = new Guid("0B36296B-EC47-44DA-8320-DC5E3071DD06");
+
+        public static int Main(string[] args)
+        {
+            if (args.Length > 0 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase))
+            {
+                return SlowPathELTHelpers.RunTest();
+            }
+
+            return ProfilerTestRunner.Run(profileePath: System.Reflection.Assembly.GetExecutingAssembly().Location,
+                                          testName: "ELTSlowPathLeave",
+                                          profilerClsid: EventPipeWritingProfilerGuid);
+        }
+    }
+}
diff --git a/src/tests/profiler/elt/slowpatheltleave.csproj b/src/tests/profiler/elt/slowpatheltleave.csproj
new file mode 100644 (file)
index 0000000..8d2ac30
--- /dev/null
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+    <OutputType>exe</OutputType>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <CLRTestPriority>0</CLRTestPriority>
+    <Optimize>true</Optimize>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+    <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+    <ProjectReference Include="../common/profiler_common.csproj" />
+    <ProjectReference Include="slowpathcommon.csproj" />
+    <ProjectReference Include="$(MSBuildThisFileDirectory)/../native/CMakeLists.txt" />
+  </ItemGroup>
+</Project>
index 3af1b28a4fbf77ead5b4197cfa23b5e8d69a2cda..a742c928bfc30684a6308071f70efe2305f1d8bb 100644 (file)
@@ -10,8 +10,16 @@ set(EVENTPIPE_SOURCES
     eventpipeprofiler/eventpipemetadatareader.cpp)
 set(METADATAGETDISPENSER_SOURCES metadatagetdispenser/metadatagetdispenser.cpp)
 set(GETAPPDOMAINSTATICADDRESS_SOURCES getappdomainstaticaddress/getappdomainstaticaddress.cpp)
-
-set(SOURCES ${GCBASIC_SOURCES} ${REJIT_SOURCES} ${EVENTPIPE_SOURCES} ${METADATAGETDISPENSER_SOURCES} ${GETAPPDOMAINSTATICADDRESS_SOURCES} profiler.def profiler.cpp classfactory.cpp dllmain.cpp guids.cpp)
+set(ELT_SOURCES eltprofiler/slowpatheltprofiler.cpp)
+
+set(SOURCES
+    ${GCBASIC_SOURCES}
+    ${REJIT_SOURCES}
+    ${EVENTPIPE_SOURCES}
+    ${METADATAGETDISPENSER_SOURCES}
+    ${GETAPPDOMAINSTATICADDRESS_SOURCES}
+    ${ELT_SOURCES}
+    profiler.def profiler.cpp classfactory.cpp dllmain.cpp guids.cpp)
 
 include_directories(../../../coreclr/src/pal/prebuilt/inc)
 
index dc5cc2feea0251eae78606329da2377fe59db2c5..301e91dde442c47e948d6a3d3ec8f76a2af1fa1b 100644 (file)
@@ -8,6 +8,7 @@
 #include "eventpipeprofiler/eventpipewritingprofiler.h"
 #include "metadatagetdispenser/metadatagetdispenser.h"
 #include "getappdomainstaticaddress/getappdomainstaticaddress.h"
+#include "eltprofiler/slowpatheltprofiler.h"
 
 ClassFactory::ClassFactory(REFCLSID clsid) : refCount(0), clsid(clsid)
 {
@@ -61,7 +62,8 @@ HRESULT STDMETHODCALLTYPE ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFI
         new EventPipeReadingProfiler(),
         new EventPipeWritingProfiler(),
         new MetaDataGetDispenser(),
-        new GetAppDomainStaticAddress()
+        new GetAppDomainStaticAddress(),
+        new SlowPathELTProfiler()
                // add new profilers here
        };
 
diff --git a/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.cpp b/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.cpp
new file mode 100644 (file)
index 0000000..8f858cd
--- /dev/null
@@ -0,0 +1,499 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#define NOMINMAX
+
+#include "slowpatheltprofiler.h"
+#include <iostream>
+#include <cctype>
+#include <iomanip>
+#include <algorithm>
+
+using std::shared_ptr;
+using std::vector;
+using std::wcout;
+using std::endl;
+
+shared_ptr<SlowPathELTProfiler> SlowPathELTProfiler::s_profiler;
+
+#ifndef WIN32
+#define UINT_PTR_FORMAT "lx"
+#define PROFILER_STUB EXTERN_C __attribute__((visibility("hidden"))) void STDMETHODCALLTYPE
+#else // WIN32
+#define UINT_PTR_FORMAT "llx"
+#define PROFILER_STUB EXTERN_C void STDMETHODCALLTYPE
+#endif // WIN32
+
+PROFILER_STUB EnterStub(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo)
+{
+    SlowPathELTProfiler::s_profiler->EnterCallback(functionId, eltInfo);
+}
+
+PROFILER_STUB LeaveStub(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo)
+{
+    SlowPathELTProfiler::s_profiler->LeaveCallback(functionId, eltInfo);
+}
+
+PROFILER_STUB TailcallStub(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo)
+{
+    SlowPathELTProfiler::s_profiler->TailcallCallback(functionId, eltInfo);
+}
+
+GUID SlowPathELTProfiler::GetClsid()
+{
+    // {0B36296B-EC47-44DA-8320-DC5E3071DD06}
+    GUID clsid = { 0x0B36296B, 0xEC47, 0x44DA, { 0x83, 0x20, 0xDC, 0x5E, 0x30, 0x71, 0xDD, 0x06 } };
+    return clsid;
+}
+
+HRESULT SlowPathELTProfiler::Initialize(IUnknown* pICorProfilerInfoUnk)
+{
+    Profiler::Initialize(pICorProfilerInfoUnk);
+
+    HRESULT hr = S_OK;
+    constexpr ULONG bufferSize = 1024;
+    ULONG envVarLen = 0;
+    WCHAR envVar[bufferSize];
+    if (FAILED(hr = pCorProfilerInfo->GetEnvironmentVariable(WCHAR("Profiler_Test_Name"),
+                                                             bufferSize,
+                                                             &envVarLen,
+                                                             envVar)))
+    {
+        wcout << L"Failed to get test name hr=" << std::hex << hr << endl;
+        _failures++;
+        return hr;
+    }
+
+    size_t nullCharPos = std::min(bufferSize - 1, envVarLen);
+    envVar[nullCharPos] = 0;
+    if (wcscmp(envVar, WCHAR("ELTSlowPathEnter")) == 0)
+    {
+        wcout << L"Testing enter hooks" << endl;
+        _testType = TestType::EnterHooks;
+    }
+    else if (wcscmp(envVar, WCHAR("ELTSlowPathLeave")) == 0)
+    {
+        wcout << L"Testing leave hooks" << endl;
+        _testType = TestType::LeaveHooks;
+    }
+    else
+    {
+        wcout << L"Unknown test type" << endl;
+        _failures++;
+        return E_FAIL;
+    }
+
+    SlowPathELTProfiler::s_profiler = shared_ptr<SlowPathELTProfiler>(this);
+
+    if (FAILED(hr = pCorProfilerInfo->SetEventMask2(COR_PRF_MONITOR_ENTERLEAVE 
+                                                    | COR_PRF_ENABLE_FUNCTION_ARGS
+                                                    | COR_PRF_ENABLE_FUNCTION_RETVAL
+                                                    | COR_PRF_ENABLE_FRAME_INFO, 
+                                                    0)))
+    {
+        wcout << L"FAIL: IpCorProfilerInfo::SetEventMask2() failed hr=0x" << std::hex << hr << endl;
+        _failures++;
+        return hr;
+    }
+
+    hr = this->pCorProfilerInfo->SetEnterLeaveFunctionHooks3WithInfo(EnterStub, LeaveStub, TailcallStub);
+    if (hr != S_OK)
+    {
+        wcout << L"SetEnterLeaveFunctionHooks3WithInfo failed with hr=0x" << std::hex << hr << endl;
+        _failures++;
+        return hr;
+    }
+
+    return S_OK;
+}
+
+HRESULT SlowPathELTProfiler::Shutdown()
+{
+    Profiler::Shutdown();
+
+    if (_testType == TestType::EnterHooks)
+    {
+        if (_failures == 0 
+             && _testType == TestType::EnterHooks
+             && _sawSimpleFuncEnter
+             && _sawMixedStructFuncEnter
+             && _sawLargeStructFuncEnter)
+        {
+            wcout << L"PROFILER TEST PASSES" << endl;
+        }
+        else
+        {
+            wcout << L"TEST FAILED _failures=" << _failures.load() << L", _sawSimpleFuncEnter=" << _sawSimpleFuncEnter 
+                    << L", _sawMixedStructFuncEnter=" << _sawMixedStructFuncEnter << L", _sawLargeStructFuncEnter=" 
+                    << _sawLargeStructFuncEnter << endl;
+        }    
+    }
+    else if (_testType == TestType::LeaveHooks)
+    {
+        if (_failures == 0
+              && _testType == TestType::LeaveHooks
+              && _sawSimpleFuncLeave
+              && _sawMixedStructFuncLeave
+              && _sawLargeStructFuncLeave
+              && _sawIntegerStructFuncLeave
+              && _sawFloatingPointStructFuncLeave
+              && _sawDoubleRetFuncLeave)
+        {
+            wcout << L"PROFILER TEST PASSES" << endl;
+        }
+        else
+        {
+            wcout << L"TEST FAILED _failures=" << _failures.load() << L", _sawSimpleFuncLeave=" << _sawSimpleFuncLeave 
+                    << L", _sawMixedStructFuncLeave=" << _sawMixedStructFuncLeave << L", _sawLargeStructFuncLeave=" 
+                    << _sawLargeStructFuncLeave << L"_sawIntegerStructFuncLeave=" << _sawIntegerStructFuncLeave 
+                    << L"_sawFloatingPointStructFuncLeave=" << _sawFloatingPointStructFuncLeave 
+                    << L"_sawDoubleRetFuncLeave=" << _sawDoubleRetFuncLeave << endl;
+        }
+    }
+
+    return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE SlowPathELTProfiler::EnterCallback(FunctionIDOrClientID functionIdOrClientID, COR_PRF_ELT_INFO eltInfo)
+{
+    if (_testType != TestType::EnterHooks)
+    {
+        return S_OK;
+    }
+
+    COR_PRF_FRAME_INFO frameInfo;
+    ULONG pcbArgumentInfo = 0;
+    NewArrayHolder<BYTE> pArgumentInfoBytes;
+    COR_PRF_FUNCTION_ARGUMENT_INFO *pArgumentInfo = NULL;
+
+    HRESULT hr = pCorProfilerInfo->GetFunctionEnter3Info(functionIdOrClientID.functionID, eltInfo, &frameInfo, &pcbArgumentInfo, NULL);
+    if (FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
+    {
+        wcout << L"GetFunctionEnter3Info 1 failed with hr=0x" << std::hex << hr << endl;
+        _failures++;
+        return hr;
+    }
+    else if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
+    {
+        pArgumentInfoBytes = new BYTE[pcbArgumentInfo];
+        pArgumentInfo = reinterpret_cast<COR_PRF_FUNCTION_ARGUMENT_INFO *>((BYTE *)pArgumentInfoBytes);
+        hr = pCorProfilerInfo->GetFunctionEnter3Info(functionIdOrClientID.functionID, eltInfo, &frameInfo, &pcbArgumentInfo, pArgumentInfo);
+        if(FAILED(hr))
+        {
+            wcout << L"GetFunctionEnter3Info 2 failed with hr=0x" << std::hex << hr << endl;
+            _failures++;
+            return hr;
+        }
+    }
+
+    String functionName = GetFunctionIDName(functionIdOrClientID.functionID);
+    if (functionName == WCHAR("SimpleArgsFunc"))
+    {
+        _sawSimpleFuncEnter = true;
+
+        int x = -123;
+        float f = -4.3f;
+        const WCHAR *str = WCHAR("Hello, test!");
+
+        vector<ExpectedArgValue> expectedValues = { { sizeof(int), (void *)&x, [&](UINT_PTR ptr){ return ValidateInt(ptr, x); } },
+                                                    { sizeof(float), (void *)&f, [&](UINT_PTR ptr){ return ValidateFloat(ptr, f); }  },
+                                                    { sizeof(UINT_PTR), (void *)str, [&](UINT_PTR ptr){ return ValidateString(ptr, str); } } };
+
+        hr = ValidateFunctionArgs(pArgumentInfo, functionName, expectedValues);
+    }
+    else if (functionName == WCHAR("MixedStructFunc"))
+    {
+        _sawMixedStructFuncEnter = true;
+
+        // On linux structs can be split with some in int registers and some in float registers
+        // so a struct with interleaved ints/doubles is interesting.
+        MixedStruct ss = { 1, 1.0 };
+        vector<ExpectedArgValue> expectedValues = { { sizeof(MixedStruct), (void *)&ss, [&](UINT_PTR ptr){ return ValidateMixedStruct(ptr, ss); } } };
+        
+        hr = ValidateFunctionArgs(pArgumentInfo, functionName, expectedValues);
+    }
+    else if (functionName == WCHAR("LargeStructFunc"))
+    {
+        _sawLargeStructFuncEnter = true;
+
+        LargeStruct ls = { 0, 0.0, 1, 1.0, 2, 2.0, 3, 3.0 };
+        vector<ExpectedArgValue> expectedValues = { { sizeof(LargeStruct), (void *)&ls, [&](UINT_PTR ptr){ return ValidateLargeStruct(ptr, ls); } } };;
+
+        hr = ValidateFunctionArgs(pArgumentInfo, functionName, expectedValues);
+    }
+
+    return hr;
+}
+
+HRESULT STDMETHODCALLTYPE SlowPathELTProfiler::LeaveCallback(FunctionIDOrClientID functionIdOrClientID, COR_PRF_ELT_INFO eltInfo)
+{
+    if (_testType != TestType::LeaveHooks)
+    {
+        return S_OK;
+    }
+    
+    COR_PRF_FRAME_INFO frameInfo;
+    COR_PRF_FUNCTION_ARGUMENT_RANGE * pRetvalRange = new COR_PRF_FUNCTION_ARGUMENT_RANGE;
+    HRESULT hr = pCorProfilerInfo->GetFunctionLeave3Info(functionIdOrClientID.functionID, eltInfo, &frameInfo, pRetvalRange);
+    if (FAILED(hr))
+    {
+        wcout << L"GetFunctionLeave3Info failed hr=0x" << std::hex << hr << endl;
+        _failures++;
+        return hr;
+    }
+
+    String functionName = GetFunctionIDName(functionIdOrClientID.functionID);
+    if (functionName == WCHAR("SimpleArgsFunc"))
+    {
+        _sawSimpleFuncLeave = true;
+
+        const WCHAR *str = WCHAR("Hello from SimpleArgsFunc!");
+
+        ExpectedArgValue simpleRetValue = { sizeof(UINT_PTR), (void *)str, [&](UINT_PTR ptr){ return ValidateString(ptr, str); } };
+        hr = ValidateOneArgument(pRetvalRange, functionName, 0, simpleRetValue);
+    }
+    else if (functionName == WCHAR("MixedStructFunc"))
+    {
+        _sawMixedStructFuncLeave = true;
+
+        MixedStruct ss = { 4, 1.0 };
+        ExpectedArgValue MixedStructRetValue = { sizeof(MixedStruct), (void *)&ss, [&](UINT_PTR ptr){ return ValidateMixedStruct(ptr, ss); } };
+        hr = ValidateOneArgument(pRetvalRange, functionName, 0, MixedStructRetValue);
+    }
+    else if (functionName == WCHAR("LargeStructFunc"))
+    {
+        _sawLargeStructFuncLeave = true;
+
+        int32_t val = 3;
+        ExpectedArgValue largeStructRetValue = { sizeof(int32_t), (void *)&val, [&](UINT_PTR ptr){ return ValidateInt(ptr, val); } };
+        hr = ValidateOneArgument(pRetvalRange, functionName, 0, largeStructRetValue);
+    }
+    else if (functionName == WCHAR("IntegerStructFunc"))
+    {
+        _sawIntegerStructFuncLeave = true;
+
+        IntegerStruct is = { 21, 256 };
+        ExpectedArgValue integerStructRetValue = { sizeof(IntegerStruct), (void *)&is, [&](UINT_PTR ptr){ return ValidateIntegerStruct(ptr, is); } };
+        hr = ValidateOneArgument(pRetvalRange, functionName, 0, integerStructRetValue);
+    }
+    else if (functionName == WCHAR("FloatingPointStructFunc"))
+    {
+        _sawFloatingPointStructFuncLeave = true;
+
+        FloatingPointStruct fps = { 13.0, 256.8 };
+        ExpectedArgValue floatingPointStructRetValue = { sizeof(FloatingPointStruct), (void *)&fps, [&](UINT_PTR ptr){ return ValidateFloatingPointStruct(ptr, fps); } };
+        hr = ValidateOneArgument(pRetvalRange, functionName, 0, floatingPointStructRetValue);
+    }
+    else if (functionName == WCHAR("DoubleRetFunc"))
+    {
+        _sawDoubleRetFuncLeave = true;
+
+        double d = 13.0;
+        ExpectedArgValue doubleRetValue = { sizeof(double), (void *)&d, [&](UINT_PTR ptr){ return ValidateDouble(ptr, d); } };
+        hr = ValidateOneArgument(pRetvalRange, functionName, 0, doubleRetValue);
+    }
+
+    return hr;   
+}
+
+HRESULT STDMETHODCALLTYPE SlowPathELTProfiler::TailcallCallback(FunctionIDOrClientID functionIdOrClientID, COR_PRF_ELT_INFO eltInfo)
+{
+    COR_PRF_FRAME_INFO frameInfo;
+    HRESULT hr = pCorProfilerInfo->GetFunctionTailcall3Info(functionIdOrClientID.functionID, eltInfo, &frameInfo);
+    if (FAILED(hr))
+    {
+        wcout << L"GetFunctionTailcall3Info failed hr=0x" << std::hex << hr << endl;
+        _failures++;
+        return hr;
+    }
+
+    // Tailcalls don't happen on debug builds, and there's no arguments to verify from GetFunctionTailcallinfo3
+
+    return hr;    
+}
+
+void SlowPathELTProfiler::PrintBytes(const BYTE *bytes, size_t length)
+{
+    for (size_t i = 0; i < length; ++i)
+    {
+        wcout << std::setfill(L'0') << std::setw(2) << std::uppercase << std::hex << bytes[i];
+
+        if (i > 1 && (i + 1) % 4 == 0)
+        {
+            wcout << " ";
+        }
+    }
+
+    wcout << endl;
+}
+
+bool SlowPathELTProfiler::ValidateInt(UINT_PTR ptr, int expected)
+{
+    if (ptr == NULL)
+    {
+        return false;
+    }
+
+    return *(int *)ptr == expected;
+}
+
+bool SlowPathELTProfiler::ValidateFloat(UINT_PTR ptr, float expected)
+{
+    if (ptr == NULL)
+    {
+        return false;
+    }
+
+    return *(float *)ptr == expected;
+}
+
+bool SlowPathELTProfiler::ValidateDouble(UINT_PTR ptr, double expected)
+{
+    if (ptr == NULL)
+    {
+        return false;
+    }
+
+    return *(double *)ptr == expected;
+}
+
+bool SlowPathELTProfiler::ValidateString(UINT_PTR ptr, const WCHAR *expected)
+{
+    if (ptr == NULL || *(void **)ptr == NULL)
+    {
+        return false;
+    }
+
+    ULONG lengthOffset = 0;
+    ULONG bufferOffset = 0;
+    HRESULT hr = pCorProfilerInfo->GetStringLayout2(&lengthOffset, &bufferOffset);
+    if (FAILED(hr))
+    {
+        wcout << L"GetStringLayout2 failed hr=0x" << std::hex << hr << endl;
+        _failures++;
+        return hr;
+    }
+
+    UINT_PTR strReference = *((UINT_PTR *)ptr) + bufferOffset;
+    WCHAR *strPtr = (WCHAR *)strReference;
+    if (wcscmp(strPtr, expected) != 0)
+    {
+        _failures++;
+        return false;
+    }
+
+    return true;
+}
+
+bool SlowPathELTProfiler::ValidateMixedStruct(UINT_PTR ptr, MixedStruct expected)
+{
+    if (ptr == NULL)
+    {
+        return false;
+    }
+
+    MixedStruct lhs = *(MixedStruct *)ptr;
+    return lhs.x == expected.x && lhs.d == expected.d;
+}
+
+bool SlowPathELTProfiler::ValidateLargeStruct(UINT_PTR ptr, LargeStruct expected)
+{
+    if (ptr == NULL)
+    {
+        return false;
+    }
+
+    LargeStruct lhs = *(LargeStruct *)ptr;
+    return lhs.x0 == expected.x0
+            && lhs.x1 == expected.x1
+            && lhs.x2 == expected.x2 
+            && lhs.x3 == expected.x3
+            && lhs.d0 == expected.d0
+            && lhs.d1 == expected.d1
+            && lhs.d2 == expected.d2 
+            && lhs.d3 == expected.d3;
+}
+
+bool SlowPathELTProfiler::ValidateFloatingPointStruct(UINT_PTR ptr, FloatingPointStruct expected)
+{
+    if (ptr == NULL)
+    {
+        return false;
+    }
+
+    FloatingPointStruct lhs = *(FloatingPointStruct *)ptr;
+    return lhs.d1 == expected.d1 && lhs.d2 == expected.d2;
+}
+
+bool SlowPathELTProfiler::ValidateIntegerStruct(UINT_PTR ptr, IntegerStruct expected)
+{
+    if (ptr == NULL)
+    {
+        return false;
+    }
+
+    IntegerStruct lhs = *(IntegerStruct *)ptr;
+    return lhs.x == expected.x && lhs.y == expected.y;
+}
+
+
+HRESULT SlowPathELTProfiler::ValidateOneArgument(COR_PRF_FUNCTION_ARGUMENT_RANGE *pArgRange,
+                                        String functionName,
+                                        size_t argPos,
+                                        ExpectedArgValue expectedValue)
+{
+    if (pArgRange->length != expectedValue.length)
+    {
+        wcout << L"Argument " << argPos << L" for function " << functionName << " expected length " << expectedValue.length 
+                << L" but got length " << pArgRange->length << endl;
+        _failures++;
+        return E_FAIL;
+    }
+
+    if (!expectedValue.func(pArgRange->startAddress))
+    {
+        wcout << L"Argument " << argPos << L" for function " << functionName << L" did not match." << endl;
+        _failures++;
+
+        // Print out the bytes so you don't have to debug if something mismatches
+        BYTE *expectedBytes = (BYTE *)expectedValue.value;
+        wcout << L"Expected bytes: "; 
+        PrintBytes(expectedBytes, expectedValue.length);
+
+        BYTE *actualBytes = (BYTE *)pArgRange->startAddress;
+        wcout << L"Actual bytes  : "; 
+        PrintBytes(actualBytes, pArgRange->length);
+
+        return E_FAIL;
+    }
+
+    return S_OK;
+}
+
+HRESULT SlowPathELTProfiler::ValidateFunctionArgs(COR_PRF_FUNCTION_ARGUMENT_INFO *pArgInfo,
+                                          String functionName,
+                                          vector<ExpectedArgValue> expectedArgValues)
+{
+    size_t expectedArgCount = expectedArgValues.size();
+
+    if (pArgInfo->numRanges != expectedArgCount)
+    {
+        wcout << L"Expected " << expectedArgCount << L" args for " << functionName << L" but got " << pArgInfo->numRanges << endl;
+        _failures++;
+        return E_FAIL;
+    }
+
+    for (size_t i = 0; i < expectedArgCount; ++i)
+    {
+        ExpectedArgValue expectedValue = expectedArgValues[i];
+        COR_PRF_FUNCTION_ARGUMENT_RANGE *pArgRange = &(pArgInfo->ranges[i]);
+
+        HRESULT hr = ValidateOneArgument(pArgRange, functionName, i, expectedValue);
+        if (FAILED(hr))
+        {
+            return hr;
+        }
+    }
+
+    return S_OK;
+}
diff --git a/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.h b/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.h
new file mode 100644 (file)
index 0000000..254ba12
--- /dev/null
@@ -0,0 +1,115 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <functional>
+#include "../profiler.h"
+
+typedef bool (*validateFunc)(void *pMem);
+
+typedef struct
+{
+    size_t length;
+    void *value;
+    std::function<bool(UINT_PTR)> func;
+} ExpectedArgValue;
+
+typedef struct
+{
+    int x;
+    double d;
+} MixedStruct;
+
+typedef struct
+{
+    int x0;
+    double d0;
+    int x1;
+    double d1;
+    int x2;
+    double d2;
+    int x3;
+    double d3;
+} LargeStruct;
+
+typedef struct
+{
+    int x;
+    int y;
+} IntegerStruct;
+
+typedef struct
+{
+    double d1;
+    double d2;
+} FloatingPointStruct;
+
+class SlowPathELTProfiler : public Profiler
+{
+public:
+    static std::shared_ptr<SlowPathELTProfiler> s_profiler;
+
+    SlowPathELTProfiler() : Profiler(),
+        _failures(0),
+        _sawSimpleFuncEnter(false),
+        _sawMixedStructFuncEnter(false),
+        _sawLargeStructFuncEnter(false),
+        _sawSimpleFuncLeave(false),
+        _sawMixedStructFuncLeave(false),
+        _sawLargeStructFuncLeave(false),
+        _testType(TestType::Unknown)
+    {}
+
+    virtual GUID GetClsid();
+    virtual HRESULT STDMETHODCALLTYPE Initialize(IUnknown* pICorProfilerInfoUnk);
+    virtual HRESULT STDMETHODCALLTYPE Shutdown();
+
+
+    HRESULT STDMETHODCALLTYPE EnterCallback(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo);
+    HRESULT STDMETHODCALLTYPE LeaveCallback(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo);
+    HRESULT STDMETHODCALLTYPE TailcallCallback(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo);
+
+private:
+    enum class TestType
+    {
+        EnterHooks,
+        LeaveHooks,
+        Unknown
+    };
+
+    std::atomic<int> _failures;
+    bool _sawSimpleFuncEnter;
+    bool _sawMixedStructFuncEnter;
+    bool _sawLargeStructFuncEnter;
+    bool _sawSimpleFuncLeave;
+    bool _sawMixedStructFuncLeave;
+    bool _sawLargeStructFuncLeave;
+    bool _sawIntegerStructFuncLeave;
+    bool _sawFloatingPointStructFuncLeave;
+    bool _sawDoubleRetFuncLeave;
+
+    TestType _testType;
+    
+    void PrintBytes(const BYTE *bytes, size_t length);
+    
+    bool ValidateInt(UINT_PTR ptr, int expected);
+    bool ValidateFloat(UINT_PTR ptr, float expected);
+    bool ValidateDouble(UINT_PTR ptr, double expected);
+    bool ValidateString(UINT_PTR ptr, const WCHAR *expected);
+    bool ValidateMixedStruct(UINT_PTR ptr, MixedStruct expected);
+    bool ValidateLargeStruct(UINT_PTR ptr, LargeStruct expected);
+    bool ValidateFloatingPointStruct(UINT_PTR ptr, FloatingPointStruct expected);
+    bool ValidateIntegerStruct(UINT_PTR ptr, IntegerStruct expected);
+
+    HRESULT ValidateOneArgument(COR_PRF_FUNCTION_ARGUMENT_RANGE *pArgRange,
+                                String functionName,
+                                size_t argPos,
+                                ExpectedArgValue expectedValue);
+
+    HRESULT ValidateFunctionArgs(COR_PRF_FUNCTION_ARGUMENT_INFO *pArgInfo,
+                                 String name,
+                                 std::vector<ExpectedArgValue> expectedArgValues);
+};
index 3600788ccdf85ee8f296409cb8d45ba5ff1ae81d..2dae0d1e7d4f9bb3b5476a260b0b5faaa4c18466 100644 (file)
@@ -21,12 +21,13 @@ HRESULT STDMETHODCALLTYPE Profiler::Initialize(IUnknown *pICorProfilerInfoUnk)
     printf("Profiler.dll!Profiler::Initialize\n");
     fflush(stdout);
 
-    HRESULT queryInterfaceResult = pICorProfilerInfoUnk->QueryInterface(__uuidof(ICorProfilerInfo9), reinterpret_cast<void **>(&this->pCorProfilerInfo));
+    HRESULT queryInterfaceResult = pICorProfilerInfoUnk->QueryInterface(__uuidof(ICorProfilerInfo11), reinterpret_cast<void **>(&this->pCorProfilerInfo));
     if (FAILED(queryInterfaceResult))
     {
         printf("Profiler.dll!Profiler::Initialize failed to QI for ICorProfilerInfo.\n");
         pICorProfilerInfoUnk = NULL;
     }
+    
     return S_OK;
 }
 
index 490e9c9ced09a0c73f3bf01aac1b3c92e049f2fd..bb7651d018d7b23a0ee5b6e24221a8c5b52efb22 100644 (file)
@@ -3,6 +3,8 @@
 
 #pragma once
 
+#define NOMINMAX
+
 #include <atomic>
 #include <cstdio>
 #include "cor.h"
@@ -108,7 +110,7 @@ protected:
     String GetModuleIDName(ModuleID modId);
 
 public:
-    ICorProfilerInfo9* pCorProfilerInfo;
+    ICorProfilerInfo11* pCorProfilerInfo;
 
     Profiler();
     virtual ~Profiler();
index a45edbd05626f13483788f5e8af502d43e91f730..d7b63758369f5bc879effb1d58e71ddcb414f12a 100644 (file)
@@ -7,6 +7,7 @@
 #include <assert.h>
 #include <cstring>
 #include <string>
+#include <algorithm>
 
 #ifdef _WIN32
 #define WCHAR(str) L##str
@@ -23,7 +24,6 @@
 // here is to provide the easy ones to avoid all the copying and transforming. If more complex
 // string operations become necessary we should either write them in C++ or convert the string to
 // 32 bit and call the c runtime ones.
-using std::max;
 
 #define WCHAR(str) u##str
 inline size_t wcslen(const char16_t *str)
@@ -80,7 +80,7 @@ private:
         size_t otherLen = wcslen(other) + 1;
         if (buffer == nullptr || otherLen > bufferLen)
         {
-            bufferLen = max(DefaultStringLength, otherLen);
+            bufferLen = std::max(DefaultStringLength, otherLen);
             if (buffer != nullptr)
             {
                 delete[] buffer;
@@ -225,21 +225,23 @@ public:
 
         if (bufferLen > printBufferLen)
         {
-            delete[] printBuffer;
-            printBuffer = nullptr;
-            printBufferLen = 0;
-        }
+            if (printBuffer != nullptr)
+            {
+                delete[] printBuffer;
+            }
 
-        if (printBuffer == nullptr)
-        {
             printBuffer = new wchar_t[bufferLen];
+            printBufferLen = bufferLen;
         }
 
         for (size_t i = 0; i < bufferLen; ++i)
         {
-            printBuffer[i] = (wchar_t)buffer[i];
+            printBuffer[i] = CAST_CHAR(buffer[i]);
         }
 
+        // Make sure it's null terminated
+        printBuffer[bufferLen - 1] = '\0';
+
         return printBuffer;
     }