Restore StubSecretArg from stack - #100428

Merged
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack
Apr 10, 2024
Merged

Restore StubSecretArg from stack#100428
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack

Conversation

@t-mustafin

Copy link
Copy Markdown
Contributor

Issue #100301

@jakobbotsch @dotnet/samsung
Part of #84834

Draft to check CI test results.

@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 28, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 28, 2024
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment on lines +5613 to +5614
if (comp->lvaStubArgumentVar == BAD_VAR_NUM)
comp->lvaStubArgumentVar = comp->lvaInlinedPInvokeFrameVar;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Without this (comp->lvaStubArgumentVar == BAD_VAR_NUM) cases lead to assert:

Assert failure(PID 11606 [0x00002d56], Thread: 11606 [0x2d56]): Assertion failed 'lclNum < lvaCount' in 'System.Buffer:_Memmove(byref,byref,ulong)' during 'Lowering nodeinfo' (IL size 25; hash 0xd7d18401; FullOpts)
File: /home/runtime/src/coreclr/jit/compiler.h:4008
Image: /home/tmustafin/main_26mar_checkedO0_7f555e0_StubSecretArgRestored68d8aa/coreroot/corerun
Thread 1"corerun" received signal SIGTRAP, Trace/breakpoint trap.
DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:66 /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S: No such file or directory.
(gdb) bt
#0 DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:6#1 0x0000003f74b129ae in DebugBreak () at /home/runtime/src/coreclr/pal/src/debug/debug.cpp:406#2 0x0000003f7481c69a in assertAbort (why=0x3f74bc0f17 "lclNum < lvaCount", file=0x3f74bc17b0 "/home/runtime/src/coreclr/jit/compiler.h", line=4008) at /home/runtime/src/coreclr/jit/error.cpp:286#3 0x0000003f7479cf10 in Compiler::lvaGetDesc (this=0x2aaac75278, lclNum=4294967295) at /home/runtime/src/coreclr/jit/compiler.h:4008#4 0x0000003f7479cff2 in Compiler::lvaGetDesc (this=0x2aaac75278, lclVar=0x2aaac832d8) at /home/runtime/src/coreclr/jit/compiler.h:4020#5 0x0000003f749e314c in Compiler::fgMorphLeafLocal (this=0x2aaac75278, lclNode=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:4577#6 0x0000003f749ef45a in Compiler::fgMorphLeaf (this=0x2aaac75278, tree=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:8098#7 0x0000003f749d9134 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac832d8, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12721#8 0x0000003f749da318 in Compiler::fgMorphArgs (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:3171#9 0x0000003f749ebee0 in Compiler::fgMorphCall (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:7760#10 0x0000003f749d9248 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac831b0, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12746#11 0x0000003f749e6052 in Compiler::fgMorphSmpOp (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0, optAssertionPropDone=0x3fffff9f47) at /home/runtime/src/coreclr/jit/morph.cpp:8950#12 0x0000003f749d9164 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12729#13 0x0000003f749a51d8 in Lowering::InsertPInvokeMethodProlog (this=0x2aaac830c8) at /home/runtime/src/coreclr/jit/lower.cpp:5630

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you double check why lvaStubArgumentVar is BAD_VAR_NUM even if we are making use of this register? Does it mean we are adding garbage to this method call? It would be better to pass 0 as the argument in those cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC there are several different flavors of how the secret stub argument gets consumed:

  • Through System.StubHelpers.GetStubContext() on all architectures. The runtime passes CORJIT_FLAG_PUBLISH_SECRET_PARAM for functions that may use this intrinsic.
  • As an argument to CORINFO_HELP_INIT_PINVOKE_FRAME outside x86/arm32. The runtime seemingly doesn't always pass CORJIT_FLAG_PUBLISH_SECRET_PARAM in the cases where we need to call this JIT helper, which is confusing to me. Does it mean we can pass nullptr in these cases? Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?
  • Through known stack frame layout on x86/arm32:
    // Given a methodDesc representing an ILStub for a pinvoke call,
    // this method will return the MethodDesc for the actual interop
    // method if the current InlinedCallFrame is inactive.
    PTR_MethodDesc GetActualInteropMethodDesc()
    {
    #if defined(TARGET_X86) || defined(TARGET_ARM)
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
    #elif defined(HOST_64BIT)
    // On 64bit, the actual interop MethodDesc is saved off in a field off the InlinedCrawlFrame
    // which is populated by the JIT. Refer to JIT_InitPInvokeFrame for details.
    returnPTR_MethodDesc(m_StubSecretArg);
    #else
    _ASSERTE(!"NYI - Interop method reporting for this architecture!");
    returnNULL;
    #endif// defined(TARGET_X86) || defined(TARGET_ARM)
    }
    . Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

cc @jkotas, do you know the answer to some of these questions?

@jkotasjkotasApr 4, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?

Yes, the arg is only used for IL stubs.

Does it mean we can pass nullptr in these cases?

The value is unused. It does not matter what gets passed in.

Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

The JIT is aware of InlinedFrame layout and semantics. It may be better to switch 64-bit to the same scheme as x86/arm32: Get rid of the argument and store it in the InlinedFrame instead - on all platforms and only when it is needed. It will save a memory store for inlined PInvokes on 64-bit platforms.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! I opened #100662 about unifying this.

@t-mustafin can you please update the fix to be something like

 GenTree* argNode;
if (comp->info.compPublishStubParam)
{
argNode = comp->gtNewLclvNode(comp->lvaStubArgumentVar, TYP_I_IMPL);
}
else
{
argNode = comp->gtNewIconNode(0, TYP_I_IMPL);
}
NewCallArg stubParamArg =
NewCallArg::Primitive(argNode).WellKnown(WellKnownArg::SecretStubParam);
call->gtArgs.PushBack(comp, stubParamArg);

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I opened #100662 about unifying this.

@t-mustafin In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

  • Delete code under the ifdef that you are fixing
  • Delete StubSecretArg here:
    Thread * __stdcall JIT_InitPInvokeFrame(InlinedCallFrame *pFrame, PTR_VOID StubSecretArg)
  • Delete
    #ifdef HOST_64BIT
    // IL stubs fill this field with the incoming secret argument when they erect
    // InlinedCallFrame so we know which interop method was invoked even if the frame
    // is not active at the moment.
    PTR_VOID m_StubSecretArg;
    #endif// HOST_64BIT
  • Enable this for all architectures
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
  • Rev the JIT-EE interface GUID: https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/jiteeversionguid.h

@jakobbotsch Does this sound about right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was hoping we would be able to get rid of the "known stack layout" contract between the VM and JIT as part of this unification. It results in a number of special casing around this particular value in the JIT and has a side effect that we cannot keep the value enregistered.
My thought was:

  • Make InlinedCallFrame::m_StubSecretArg available on all platforms. Make it the JIT's responsibility to store this field in the frame on x64 (instead of doing it from JIT_InitPInvokeFrame). Store the value on x86 as well (it will change a spill to stack at the known stack frame location to a store to the InlinedCallFrame)
  • Remove all special handling in frame layout around lvaStubArgumentVar
  • Eventually enable full enregistration of lvaStubArgumentVar and remove all remaining special handling around it in the JIT. Coincidentally I am doing work in JIT: Rewrite register parameter homing #100572 that should make this simple.

Do you see any potential issues around doing it this way?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Make InlinedCallFrame::m_StubSecretArg available on all platforms

It makes InlinedCallFrame one extra word larger than necessary in most cases. It should not be a big deal, there are other similar inefficiencies in InlinedCallFrame. We should be able to do that to make things cleaner.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

can you please update the fix to be something like

@jakobbotsch thanks, it works. Updated PR.

rv64 asm for IL_STUB_PInvoke(byref) [MinOpts, IL size=98, code size=920, hash=0x5d63dafb]
G_M9476_IG01: ; offs=0x000000, size=0x0068, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IGIN0086: 000000 addi sp,sp,-576IN0087: 000004 sd s1,0(sp)IN0088: 000008 sd s2,8(sp)IN0089: 00000C sd s3,16(sp)IN008a: 000010 sd s4,24(sp)IN008b: 000014 sd s5,32(sp)IN008c: 000018 sd s6,40(sp)IN008d: 00001C sd s7,48(sp)IN008e: 000020 sd s8,56(sp)IN008f: 000024 sd s9,64(sp) IN0090: 000028 sd s10,72(sp)IN0091: 00002C sd s11,80(sp)IN0092: 000030 sd ra,88(sp)IN0093: 000034 sd fp,96(sp)IN0094: 000038 addi fp,sp,96IN0095: 00003C sd t2,80(fp)IN0096: 000040 addi s1, fp,104IN0097: 000044 addi t0, zero,22IN0098: 000048 sd zero,8(s1) IN0099: 00004C sd zero,0(s1) IN009a: 000050 addi t0, t0,-1IN009b: 000054 addi s1, s1,16IN009c: 000058 bne t0, zero, pc-16 (-4 instructions)IN009d: 00005C addi s1,sp,576IN009e: 000060 sd s1,472(fp)IN009f: 000064 sd a0,464(fp) ;; size=104 bbWeight=1 PerfScore 0.00G_M9476_IG02: ; offs=0x000068, size=0x006C, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB09 [0008], BB01 [0000], byrefIN0001: 000068 addi a0, fp,112IN0002: 00006C addi a1, zero,-51IN0003: 000070 addi a2, zero,296IN0004: 000074 lui t2,0000078 addi t2, t2,63 00007C slli t2, t2,11000080 addi t2, t2,1401000084 slli t2, t2,11000088 addi t2, t2,1785 00008C slli t2, t2,10000090 jalr ra,748(t2) // CORINFO_HELP_NATIVE_MEMSETIN0005: 000094 addi a0, fp,16IN0006: 000098 ld a1,80(fp)IN0007: 00009C lui a2,521704IN0008: 0000A0 addiw a2, a2,-1570IN0009: 0000A4 slli a2, a2,7IN000a: 0000A8 addi a2, a2,58IN000b: 0000AC jalr ra,0(a2) // CORINFO_HELP_INIT_PINVOKE_FRAME

In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

@jkotas Thanks for detailed instruction, unfortunately I don't have plan to fix #100662 for near future.

@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from ae76e4a to f37d25eCompareMarch 28, 2024 23:34
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch 2 times, most recently from 1c7c97f to 232ee6fCompareMarch 30, 2024 01:29
@t-mustafin
t-mustafin marked this pull request as ready for review March 30, 2024 01:45
Issue dotnet#100301
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from 232ee6f to 321559aCompareApril 9, 2024 18:17

@jakobbotschjakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@gbalykov

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

@jakobbotsch

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

I don't think so. The CORINFO_HELP_MEMSET call that trashes the register in #100301 is coming from poisoning that is inserted by the JIT for address taken locals in unoptimized codegen. This means that the demonstrated case requires an IL stub compiled without optimizations. However, I believe that IL stubs under normal operations will always be compiled with optimizations enabled, so the call will not be there.

In theory nothing stops the register from being trashed through other means during prolog codegen, but this theoretical issue exists in RyuJIT since forever and generally we wouldn't try to backport a fix for a theoretical issue like that.

@jkotas

Copy link
Copy Markdown
Member

I believe that IL stubs under normal operations will always be compiled with optimizations enabled

I do not think that it is the case. The IL stubs have the same optimization settings as the module that they are attached to.

I agree that this is a theoretical issue in the shipping runtime version and it does not meet the bar for backport without a repro.

matouskozak pushed a commit to matouskozak/runtime that referenced this pull request Apr 30, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 16, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@t-mustafin@gbalykov@jakobbotsch@jkotas@clamp03@ashaurtaev@sirntar@Bajtazar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Restore StubSecretArg from stack - #100428

Merged
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack
Apr 10, 2024
Merged

Restore StubSecretArg from stack#100428
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack

Conversation

@t-mustafin

Copy link
Copy Markdown
Contributor

Issue #100301

@jakobbotsch @dotnet/samsung
Part of #84834

Draft to check CI test results.

@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 28, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 28, 2024
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment on lines +5613 to +5614
if (comp->lvaStubArgumentVar == BAD_VAR_NUM)
comp->lvaStubArgumentVar = comp->lvaInlinedPInvokeFrameVar;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Without this (comp->lvaStubArgumentVar == BAD_VAR_NUM) cases lead to assert:

Assert failure(PID 11606 [0x00002d56], Thread: 11606 [0x2d56]): Assertion failed 'lclNum < lvaCount' in 'System.Buffer:_Memmove(byref,byref,ulong)' during 'Lowering nodeinfo' (IL size 25; hash 0xd7d18401; FullOpts)
File: /home/runtime/src/coreclr/jit/compiler.h:4008
Image: /home/tmustafin/main_26mar_checkedO0_7f555e0_StubSecretArgRestored68d8aa/coreroot/corerun
Thread 1"corerun" received signal SIGTRAP, Trace/breakpoint trap.
DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:66 /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S: No such file or directory.
(gdb) bt
#0 DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:6#1 0x0000003f74b129ae in DebugBreak () at /home/runtime/src/coreclr/pal/src/debug/debug.cpp:406#2 0x0000003f7481c69a in assertAbort (why=0x3f74bc0f17 "lclNum < lvaCount", file=0x3f74bc17b0 "/home/runtime/src/coreclr/jit/compiler.h", line=4008) at /home/runtime/src/coreclr/jit/error.cpp:286#3 0x0000003f7479cf10 in Compiler::lvaGetDesc (this=0x2aaac75278, lclNum=4294967295) at /home/runtime/src/coreclr/jit/compiler.h:4008#4 0x0000003f7479cff2 in Compiler::lvaGetDesc (this=0x2aaac75278, lclVar=0x2aaac832d8) at /home/runtime/src/coreclr/jit/compiler.h:4020#5 0x0000003f749e314c in Compiler::fgMorphLeafLocal (this=0x2aaac75278, lclNode=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:4577#6 0x0000003f749ef45a in Compiler::fgMorphLeaf (this=0x2aaac75278, tree=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:8098#7 0x0000003f749d9134 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac832d8, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12721#8 0x0000003f749da318 in Compiler::fgMorphArgs (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:3171#9 0x0000003f749ebee0 in Compiler::fgMorphCall (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:7760#10 0x0000003f749d9248 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac831b0, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12746#11 0x0000003f749e6052 in Compiler::fgMorphSmpOp (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0, optAssertionPropDone=0x3fffff9f47) at /home/runtime/src/coreclr/jit/morph.cpp:8950#12 0x0000003f749d9164 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12729#13 0x0000003f749a51d8 in Lowering::InsertPInvokeMethodProlog (this=0x2aaac830c8) at /home/runtime/src/coreclr/jit/lower.cpp:5630

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you double check why lvaStubArgumentVar is BAD_VAR_NUM even if we are making use of this register? Does it mean we are adding garbage to this method call? It would be better to pass 0 as the argument in those cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC there are several different flavors of how the secret stub argument gets consumed:

  • Through System.StubHelpers.GetStubContext() on all architectures. The runtime passes CORJIT_FLAG_PUBLISH_SECRET_PARAM for functions that may use this intrinsic.
  • As an argument to CORINFO_HELP_INIT_PINVOKE_FRAME outside x86/arm32. The runtime seemingly doesn't always pass CORJIT_FLAG_PUBLISH_SECRET_PARAM in the cases where we need to call this JIT helper, which is confusing to me. Does it mean we can pass nullptr in these cases? Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?
  • Through known stack frame layout on x86/arm32:
    // Given a methodDesc representing an ILStub for a pinvoke call,
    // this method will return the MethodDesc for the actual interop
    // method if the current InlinedCallFrame is inactive.
    PTR_MethodDesc GetActualInteropMethodDesc()
    {
    #if defined(TARGET_X86) || defined(TARGET_ARM)
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
    #elif defined(HOST_64BIT)
    // On 64bit, the actual interop MethodDesc is saved off in a field off the InlinedCrawlFrame
    // which is populated by the JIT. Refer to JIT_InitPInvokeFrame for details.
    returnPTR_MethodDesc(m_StubSecretArg);
    #else
    _ASSERTE(!"NYI - Interop method reporting for this architecture!");
    returnNULL;
    #endif// defined(TARGET_X86) || defined(TARGET_ARM)
    }
    . Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

cc @jkotas, do you know the answer to some of these questions?

@jkotasjkotasApr 4, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?

Yes, the arg is only used for IL stubs.

Does it mean we can pass nullptr in these cases?

The value is unused. It does not matter what gets passed in.

Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

The JIT is aware of InlinedFrame layout and semantics. It may be better to switch 64-bit to the same scheme as x86/arm32: Get rid of the argument and store it in the InlinedFrame instead - on all platforms and only when it is needed. It will save a memory store for inlined PInvokes on 64-bit platforms.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! I opened #100662 about unifying this.

@t-mustafin can you please update the fix to be something like

 GenTree* argNode;
if (comp->info.compPublishStubParam)
{
argNode = comp->gtNewLclvNode(comp->lvaStubArgumentVar, TYP_I_IMPL);
}
else
{
argNode = comp->gtNewIconNode(0, TYP_I_IMPL);
}
NewCallArg stubParamArg =
NewCallArg::Primitive(argNode).WellKnown(WellKnownArg::SecretStubParam);
call->gtArgs.PushBack(comp, stubParamArg);

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I opened #100662 about unifying this.

@t-mustafin In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

  • Delete code under the ifdef that you are fixing
  • Delete StubSecretArg here:
    Thread * __stdcall JIT_InitPInvokeFrame(InlinedCallFrame *pFrame, PTR_VOID StubSecretArg)
  • Delete
    #ifdef HOST_64BIT
    // IL stubs fill this field with the incoming secret argument when they erect
    // InlinedCallFrame so we know which interop method was invoked even if the frame
    // is not active at the moment.
    PTR_VOID m_StubSecretArg;
    #endif// HOST_64BIT
  • Enable this for all architectures
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
  • Rev the JIT-EE interface GUID: https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/jiteeversionguid.h

@jakobbotsch Does this sound about right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was hoping we would be able to get rid of the "known stack layout" contract between the VM and JIT as part of this unification. It results in a number of special casing around this particular value in the JIT and has a side effect that we cannot keep the value enregistered.
My thought was:

  • Make InlinedCallFrame::m_StubSecretArg available on all platforms. Make it the JIT's responsibility to store this field in the frame on x64 (instead of doing it from JIT_InitPInvokeFrame). Store the value on x86 as well (it will change a spill to stack at the known stack frame location to a store to the InlinedCallFrame)
  • Remove all special handling in frame layout around lvaStubArgumentVar
  • Eventually enable full enregistration of lvaStubArgumentVar and remove all remaining special handling around it in the JIT. Coincidentally I am doing work in JIT: Rewrite register parameter homing #100572 that should make this simple.

Do you see any potential issues around doing it this way?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Make InlinedCallFrame::m_StubSecretArg available on all platforms

It makes InlinedCallFrame one extra word larger than necessary in most cases. It should not be a big deal, there are other similar inefficiencies in InlinedCallFrame. We should be able to do that to make things cleaner.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

can you please update the fix to be something like

@jakobbotsch thanks, it works. Updated PR.

rv64 asm for IL_STUB_PInvoke(byref) [MinOpts, IL size=98, code size=920, hash=0x5d63dafb]
G_M9476_IG01: ; offs=0x000000, size=0x0068, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IGIN0086: 000000 addi sp,sp,-576IN0087: 000004 sd s1,0(sp)IN0088: 000008 sd s2,8(sp)IN0089: 00000C sd s3,16(sp)IN008a: 000010 sd s4,24(sp)IN008b: 000014 sd s5,32(sp)IN008c: 000018 sd s6,40(sp)IN008d: 00001C sd s7,48(sp)IN008e: 000020 sd s8,56(sp)IN008f: 000024 sd s9,64(sp) IN0090: 000028 sd s10,72(sp)IN0091: 00002C sd s11,80(sp)IN0092: 000030 sd ra,88(sp)IN0093: 000034 sd fp,96(sp)IN0094: 000038 addi fp,sp,96IN0095: 00003C sd t2,80(fp)IN0096: 000040 addi s1, fp,104IN0097: 000044 addi t0, zero,22IN0098: 000048 sd zero,8(s1) IN0099: 00004C sd zero,0(s1) IN009a: 000050 addi t0, t0,-1IN009b: 000054 addi s1, s1,16IN009c: 000058 bne t0, zero, pc-16 (-4 instructions)IN009d: 00005C addi s1,sp,576IN009e: 000060 sd s1,472(fp)IN009f: 000064 sd a0,464(fp) ;; size=104 bbWeight=1 PerfScore 0.00G_M9476_IG02: ; offs=0x000068, size=0x006C, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB09 [0008], BB01 [0000], byrefIN0001: 000068 addi a0, fp,112IN0002: 00006C addi a1, zero,-51IN0003: 000070 addi a2, zero,296IN0004: 000074 lui t2,0000078 addi t2, t2,63 00007C slli t2, t2,11000080 addi t2, t2,1401000084 slli t2, t2,11000088 addi t2, t2,1785 00008C slli t2, t2,10000090 jalr ra,748(t2) // CORINFO_HELP_NATIVE_MEMSETIN0005: 000094 addi a0, fp,16IN0006: 000098 ld a1,80(fp)IN0007: 00009C lui a2,521704IN0008: 0000A0 addiw a2, a2,-1570IN0009: 0000A4 slli a2, a2,7IN000a: 0000A8 addi a2, a2,58IN000b: 0000AC jalr ra,0(a2) // CORINFO_HELP_INIT_PINVOKE_FRAME

In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

@jkotas Thanks for detailed instruction, unfortunately I don't have plan to fix #100662 for near future.

@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from ae76e4a to f37d25eCompareMarch 28, 2024 23:34
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch 2 times, most recently from 1c7c97f to 232ee6fCompareMarch 30, 2024 01:29
@t-mustafin
t-mustafin marked this pull request as ready for review March 30, 2024 01:45
Issue dotnet#100301
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from 232ee6f to 321559aCompareApril 9, 2024 18:17

@jakobbotschjakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@gbalykov

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

@jakobbotsch

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

I don't think so. The CORINFO_HELP_MEMSET call that trashes the register in #100301 is coming from poisoning that is inserted by the JIT for address taken locals in unoptimized codegen. This means that the demonstrated case requires an IL stub compiled without optimizations. However, I believe that IL stubs under normal operations will always be compiled with optimizations enabled, so the call will not be there.

In theory nothing stops the register from being trashed through other means during prolog codegen, but this theoretical issue exists in RyuJIT since forever and generally we wouldn't try to backport a fix for a theoretical issue like that.

@jkotas

Copy link
Copy Markdown
Member

I believe that IL stubs under normal operations will always be compiled with optimizations enabled

I do not think that it is the case. The IL stubs have the same optimization settings as the module that they are attached to.

I agree that this is a theoretical issue in the shipping runtime version and it does not meet the bar for backport without a repro.

matouskozak pushed a commit to matouskozak/runtime that referenced this pull request Apr 30, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 16, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@t-mustafin@gbalykov@jakobbotsch@jkotas@clamp03@ashaurtaev@sirntar@Bajtazar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Restore StubSecretArg from stack - #100428

Merged
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack
Apr 10, 2024
Merged

Restore StubSecretArg from stack#100428
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack

Conversation

@t-mustafin

Copy link
Copy Markdown
Contributor

Issue #100301

@jakobbotsch @dotnet/samsung
Part of #84834

Draft to check CI test results.

@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 28, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 28, 2024
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment on lines +5613 to +5614
if (comp->lvaStubArgumentVar == BAD_VAR_NUM)
comp->lvaStubArgumentVar = comp->lvaInlinedPInvokeFrameVar;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Without this (comp->lvaStubArgumentVar == BAD_VAR_NUM) cases lead to assert:

Assert failure(PID 11606 [0x00002d56], Thread: 11606 [0x2d56]): Assertion failed 'lclNum < lvaCount' in 'System.Buffer:_Memmove(byref,byref,ulong)' during 'Lowering nodeinfo' (IL size 25; hash 0xd7d18401; FullOpts)
File: /home/runtime/src/coreclr/jit/compiler.h:4008
Image: /home/tmustafin/main_26mar_checkedO0_7f555e0_StubSecretArgRestored68d8aa/coreroot/corerun
Thread 1"corerun" received signal SIGTRAP, Trace/breakpoint trap.
DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:66 /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S: No such file or directory.
(gdb) bt
#0 DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:6#1 0x0000003f74b129ae in DebugBreak () at /home/runtime/src/coreclr/pal/src/debug/debug.cpp:406#2 0x0000003f7481c69a in assertAbort (why=0x3f74bc0f17 "lclNum < lvaCount", file=0x3f74bc17b0 "/home/runtime/src/coreclr/jit/compiler.h", line=4008) at /home/runtime/src/coreclr/jit/error.cpp:286#3 0x0000003f7479cf10 in Compiler::lvaGetDesc (this=0x2aaac75278, lclNum=4294967295) at /home/runtime/src/coreclr/jit/compiler.h:4008#4 0x0000003f7479cff2 in Compiler::lvaGetDesc (this=0x2aaac75278, lclVar=0x2aaac832d8) at /home/runtime/src/coreclr/jit/compiler.h:4020#5 0x0000003f749e314c in Compiler::fgMorphLeafLocal (this=0x2aaac75278, lclNode=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:4577#6 0x0000003f749ef45a in Compiler::fgMorphLeaf (this=0x2aaac75278, tree=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:8098#7 0x0000003f749d9134 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac832d8, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12721#8 0x0000003f749da318 in Compiler::fgMorphArgs (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:3171#9 0x0000003f749ebee0 in Compiler::fgMorphCall (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:7760#10 0x0000003f749d9248 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac831b0, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12746#11 0x0000003f749e6052 in Compiler::fgMorphSmpOp (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0, optAssertionPropDone=0x3fffff9f47) at /home/runtime/src/coreclr/jit/morph.cpp:8950#12 0x0000003f749d9164 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12729#13 0x0000003f749a51d8 in Lowering::InsertPInvokeMethodProlog (this=0x2aaac830c8) at /home/runtime/src/coreclr/jit/lower.cpp:5630

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you double check why lvaStubArgumentVar is BAD_VAR_NUM even if we are making use of this register? Does it mean we are adding garbage to this method call? It would be better to pass 0 as the argument in those cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC there are several different flavors of how the secret stub argument gets consumed:

  • Through System.StubHelpers.GetStubContext() on all architectures. The runtime passes CORJIT_FLAG_PUBLISH_SECRET_PARAM for functions that may use this intrinsic.
  • As an argument to CORINFO_HELP_INIT_PINVOKE_FRAME outside x86/arm32. The runtime seemingly doesn't always pass CORJIT_FLAG_PUBLISH_SECRET_PARAM in the cases where we need to call this JIT helper, which is confusing to me. Does it mean we can pass nullptr in these cases? Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?
  • Through known stack frame layout on x86/arm32:
    // Given a methodDesc representing an ILStub for a pinvoke call,
    // this method will return the MethodDesc for the actual interop
    // method if the current InlinedCallFrame is inactive.
    PTR_MethodDesc GetActualInteropMethodDesc()
    {
    #if defined(TARGET_X86) || defined(TARGET_ARM)
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
    #elif defined(HOST_64BIT)
    // On 64bit, the actual interop MethodDesc is saved off in a field off the InlinedCrawlFrame
    // which is populated by the JIT. Refer to JIT_InitPInvokeFrame for details.
    returnPTR_MethodDesc(m_StubSecretArg);
    #else
    _ASSERTE(!"NYI - Interop method reporting for this architecture!");
    returnNULL;
    #endif// defined(TARGET_X86) || defined(TARGET_ARM)
    }
    . Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

cc @jkotas, do you know the answer to some of these questions?

@jkotasjkotasApr 4, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?

Yes, the arg is only used for IL stubs.

Does it mean we can pass nullptr in these cases?

The value is unused. It does not matter what gets passed in.

Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

The JIT is aware of InlinedFrame layout and semantics. It may be better to switch 64-bit to the same scheme as x86/arm32: Get rid of the argument and store it in the InlinedFrame instead - on all platforms and only when it is needed. It will save a memory store for inlined PInvokes on 64-bit platforms.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! I opened #100662 about unifying this.

@t-mustafin can you please update the fix to be something like

 GenTree* argNode;
if (comp->info.compPublishStubParam)
{
argNode = comp->gtNewLclvNode(comp->lvaStubArgumentVar, TYP_I_IMPL);
}
else
{
argNode = comp->gtNewIconNode(0, TYP_I_IMPL);
}
NewCallArg stubParamArg =
NewCallArg::Primitive(argNode).WellKnown(WellKnownArg::SecretStubParam);
call->gtArgs.PushBack(comp, stubParamArg);

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I opened #100662 about unifying this.

@t-mustafin In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

  • Delete code under the ifdef that you are fixing
  • Delete StubSecretArg here:
    Thread * __stdcall JIT_InitPInvokeFrame(InlinedCallFrame *pFrame, PTR_VOID StubSecretArg)
  • Delete
    #ifdef HOST_64BIT
    // IL stubs fill this field with the incoming secret argument when they erect
    // InlinedCallFrame so we know which interop method was invoked even if the frame
    // is not active at the moment.
    PTR_VOID m_StubSecretArg;
    #endif// HOST_64BIT
  • Enable this for all architectures
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
  • Rev the JIT-EE interface GUID: https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/jiteeversionguid.h

@jakobbotsch Does this sound about right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was hoping we would be able to get rid of the "known stack layout" contract between the VM and JIT as part of this unification. It results in a number of special casing around this particular value in the JIT and has a side effect that we cannot keep the value enregistered.
My thought was:

  • Make InlinedCallFrame::m_StubSecretArg available on all platforms. Make it the JIT's responsibility to store this field in the frame on x64 (instead of doing it from JIT_InitPInvokeFrame). Store the value on x86 as well (it will change a spill to stack at the known stack frame location to a store to the InlinedCallFrame)
  • Remove all special handling in frame layout around lvaStubArgumentVar
  • Eventually enable full enregistration of lvaStubArgumentVar and remove all remaining special handling around it in the JIT. Coincidentally I am doing work in JIT: Rewrite register parameter homing #100572 that should make this simple.

Do you see any potential issues around doing it this way?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Make InlinedCallFrame::m_StubSecretArg available on all platforms

It makes InlinedCallFrame one extra word larger than necessary in most cases. It should not be a big deal, there are other similar inefficiencies in InlinedCallFrame. We should be able to do that to make things cleaner.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

can you please update the fix to be something like

@jakobbotsch thanks, it works. Updated PR.

rv64 asm for IL_STUB_PInvoke(byref) [MinOpts, IL size=98, code size=920, hash=0x5d63dafb]
G_M9476_IG01: ; offs=0x000000, size=0x0068, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IGIN0086: 000000 addi sp,sp,-576IN0087: 000004 sd s1,0(sp)IN0088: 000008 sd s2,8(sp)IN0089: 00000C sd s3,16(sp)IN008a: 000010 sd s4,24(sp)IN008b: 000014 sd s5,32(sp)IN008c: 000018 sd s6,40(sp)IN008d: 00001C sd s7,48(sp)IN008e: 000020 sd s8,56(sp)IN008f: 000024 sd s9,64(sp) IN0090: 000028 sd s10,72(sp)IN0091: 00002C sd s11,80(sp)IN0092: 000030 sd ra,88(sp)IN0093: 000034 sd fp,96(sp)IN0094: 000038 addi fp,sp,96IN0095: 00003C sd t2,80(fp)IN0096: 000040 addi s1, fp,104IN0097: 000044 addi t0, zero,22IN0098: 000048 sd zero,8(s1) IN0099: 00004C sd zero,0(s1) IN009a: 000050 addi t0, t0,-1IN009b: 000054 addi s1, s1,16IN009c: 000058 bne t0, zero, pc-16 (-4 instructions)IN009d: 00005C addi s1,sp,576IN009e: 000060 sd s1,472(fp)IN009f: 000064 sd a0,464(fp) ;; size=104 bbWeight=1 PerfScore 0.00G_M9476_IG02: ; offs=0x000068, size=0x006C, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB09 [0008], BB01 [0000], byrefIN0001: 000068 addi a0, fp,112IN0002: 00006C addi a1, zero,-51IN0003: 000070 addi a2, zero,296IN0004: 000074 lui t2,0000078 addi t2, t2,63 00007C slli t2, t2,11000080 addi t2, t2,1401000084 slli t2, t2,11000088 addi t2, t2,1785 00008C slli t2, t2,10000090 jalr ra,748(t2) // CORINFO_HELP_NATIVE_MEMSETIN0005: 000094 addi a0, fp,16IN0006: 000098 ld a1,80(fp)IN0007: 00009C lui a2,521704IN0008: 0000A0 addiw a2, a2,-1570IN0009: 0000A4 slli a2, a2,7IN000a: 0000A8 addi a2, a2,58IN000b: 0000AC jalr ra,0(a2) // CORINFO_HELP_INIT_PINVOKE_FRAME

In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

@jkotas Thanks for detailed instruction, unfortunately I don't have plan to fix #100662 for near future.

@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from ae76e4a to f37d25eCompareMarch 28, 2024 23:34
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch 2 times, most recently from 1c7c97f to 232ee6fCompareMarch 30, 2024 01:29
@t-mustafin
t-mustafin marked this pull request as ready for review March 30, 2024 01:45
Issue dotnet#100301
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from 232ee6f to 321559aCompareApril 9, 2024 18:17

@jakobbotschjakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@gbalykov

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

@jakobbotsch

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

I don't think so. The CORINFO_HELP_MEMSET call that trashes the register in #100301 is coming from poisoning that is inserted by the JIT for address taken locals in unoptimized codegen. This means that the demonstrated case requires an IL stub compiled without optimizations. However, I believe that IL stubs under normal operations will always be compiled with optimizations enabled, so the call will not be there.

In theory nothing stops the register from being trashed through other means during prolog codegen, but this theoretical issue exists in RyuJIT since forever and generally we wouldn't try to backport a fix for a theoretical issue like that.

@jkotas

Copy link
Copy Markdown
Member

I believe that IL stubs under normal operations will always be compiled with optimizations enabled

I do not think that it is the case. The IL stubs have the same optimization settings as the module that they are attached to.

I agree that this is a theoretical issue in the shipping runtime version and it does not meet the bar for backport without a repro.

matouskozak pushed a commit to matouskozak/runtime that referenced this pull request Apr 30, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 16, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@t-mustafin@gbalykov@jakobbotsch@jkotas@clamp03@ashaurtaev@sirntar@Bajtazar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Restore StubSecretArg from stack - #100428

Merged
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack
Apr 10, 2024
Merged

Restore StubSecretArg from stack#100428
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack

Conversation

@t-mustafin

Copy link
Copy Markdown
Contributor

Issue #100301

@jakobbotsch @dotnet/samsung
Part of #84834

Draft to check CI test results.

@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 28, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 28, 2024
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment on lines +5613 to +5614
if (comp->lvaStubArgumentVar == BAD_VAR_NUM)
comp->lvaStubArgumentVar = comp->lvaInlinedPInvokeFrameVar;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Without this (comp->lvaStubArgumentVar == BAD_VAR_NUM) cases lead to assert:

Assert failure(PID 11606 [0x00002d56], Thread: 11606 [0x2d56]): Assertion failed 'lclNum < lvaCount' in 'System.Buffer:_Memmove(byref,byref,ulong)' during 'Lowering nodeinfo' (IL size 25; hash 0xd7d18401; FullOpts)
File: /home/runtime/src/coreclr/jit/compiler.h:4008
Image: /home/tmustafin/main_26mar_checkedO0_7f555e0_StubSecretArgRestored68d8aa/coreroot/corerun
Thread 1"corerun" received signal SIGTRAP, Trace/breakpoint trap.
DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:66 /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S: No such file or directory.
(gdb) bt
#0 DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:6#1 0x0000003f74b129ae in DebugBreak () at /home/runtime/src/coreclr/pal/src/debug/debug.cpp:406#2 0x0000003f7481c69a in assertAbort (why=0x3f74bc0f17 "lclNum < lvaCount", file=0x3f74bc17b0 "/home/runtime/src/coreclr/jit/compiler.h", line=4008) at /home/runtime/src/coreclr/jit/error.cpp:286#3 0x0000003f7479cf10 in Compiler::lvaGetDesc (this=0x2aaac75278, lclNum=4294967295) at /home/runtime/src/coreclr/jit/compiler.h:4008#4 0x0000003f7479cff2 in Compiler::lvaGetDesc (this=0x2aaac75278, lclVar=0x2aaac832d8) at /home/runtime/src/coreclr/jit/compiler.h:4020#5 0x0000003f749e314c in Compiler::fgMorphLeafLocal (this=0x2aaac75278, lclNode=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:4577#6 0x0000003f749ef45a in Compiler::fgMorphLeaf (this=0x2aaac75278, tree=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:8098#7 0x0000003f749d9134 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac832d8, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12721#8 0x0000003f749da318 in Compiler::fgMorphArgs (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:3171#9 0x0000003f749ebee0 in Compiler::fgMorphCall (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:7760#10 0x0000003f749d9248 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac831b0, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12746#11 0x0000003f749e6052 in Compiler::fgMorphSmpOp (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0, optAssertionPropDone=0x3fffff9f47) at /home/runtime/src/coreclr/jit/morph.cpp:8950#12 0x0000003f749d9164 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12729#13 0x0000003f749a51d8 in Lowering::InsertPInvokeMethodProlog (this=0x2aaac830c8) at /home/runtime/src/coreclr/jit/lower.cpp:5630

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you double check why lvaStubArgumentVar is BAD_VAR_NUM even if we are making use of this register? Does it mean we are adding garbage to this method call? It would be better to pass 0 as the argument in those cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC there are several different flavors of how the secret stub argument gets consumed:

  • Through System.StubHelpers.GetStubContext() on all architectures. The runtime passes CORJIT_FLAG_PUBLISH_SECRET_PARAM for functions that may use this intrinsic.
  • As an argument to CORINFO_HELP_INIT_PINVOKE_FRAME outside x86/arm32. The runtime seemingly doesn't always pass CORJIT_FLAG_PUBLISH_SECRET_PARAM in the cases where we need to call this JIT helper, which is confusing to me. Does it mean we can pass nullptr in these cases? Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?
  • Through known stack frame layout on x86/arm32:
    // Given a methodDesc representing an ILStub for a pinvoke call,
    // this method will return the MethodDesc for the actual interop
    // method if the current InlinedCallFrame is inactive.
    PTR_MethodDesc GetActualInteropMethodDesc()
    {
    #if defined(TARGET_X86) || defined(TARGET_ARM)
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
    #elif defined(HOST_64BIT)
    // On 64bit, the actual interop MethodDesc is saved off in a field off the InlinedCrawlFrame
    // which is populated by the JIT. Refer to JIT_InitPInvokeFrame for details.
    returnPTR_MethodDesc(m_StubSecretArg);
    #else
    _ASSERTE(!"NYI - Interop method reporting for this architecture!");
    returnNULL;
    #endif// defined(TARGET_X86) || defined(TARGET_ARM)
    }
    . Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

cc @jkotas, do you know the answer to some of these questions?

@jkotasjkotasApr 4, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?

Yes, the arg is only used for IL stubs.

Does it mean we can pass nullptr in these cases?

The value is unused. It does not matter what gets passed in.

Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

The JIT is aware of InlinedFrame layout and semantics. It may be better to switch 64-bit to the same scheme as x86/arm32: Get rid of the argument and store it in the InlinedFrame instead - on all platforms and only when it is needed. It will save a memory store for inlined PInvokes on 64-bit platforms.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! I opened #100662 about unifying this.

@t-mustafin can you please update the fix to be something like

 GenTree* argNode;
if (comp->info.compPublishStubParam)
{
argNode = comp->gtNewLclvNode(comp->lvaStubArgumentVar, TYP_I_IMPL);
}
else
{
argNode = comp->gtNewIconNode(0, TYP_I_IMPL);
}
NewCallArg stubParamArg =
NewCallArg::Primitive(argNode).WellKnown(WellKnownArg::SecretStubParam);
call->gtArgs.PushBack(comp, stubParamArg);

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I opened #100662 about unifying this.

@t-mustafin In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

  • Delete code under the ifdef that you are fixing
  • Delete StubSecretArg here:
    Thread * __stdcall JIT_InitPInvokeFrame(InlinedCallFrame *pFrame, PTR_VOID StubSecretArg)
  • Delete
    #ifdef HOST_64BIT
    // IL stubs fill this field with the incoming secret argument when they erect
    // InlinedCallFrame so we know which interop method was invoked even if the frame
    // is not active at the moment.
    PTR_VOID m_StubSecretArg;
    #endif// HOST_64BIT
  • Enable this for all architectures
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
  • Rev the JIT-EE interface GUID: https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/jiteeversionguid.h

@jakobbotsch Does this sound about right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was hoping we would be able to get rid of the "known stack layout" contract between the VM and JIT as part of this unification. It results in a number of special casing around this particular value in the JIT and has a side effect that we cannot keep the value enregistered.
My thought was:

  • Make InlinedCallFrame::m_StubSecretArg available on all platforms. Make it the JIT's responsibility to store this field in the frame on x64 (instead of doing it from JIT_InitPInvokeFrame). Store the value on x86 as well (it will change a spill to stack at the known stack frame location to a store to the InlinedCallFrame)
  • Remove all special handling in frame layout around lvaStubArgumentVar
  • Eventually enable full enregistration of lvaStubArgumentVar and remove all remaining special handling around it in the JIT. Coincidentally I am doing work in JIT: Rewrite register parameter homing #100572 that should make this simple.

Do you see any potential issues around doing it this way?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Make InlinedCallFrame::m_StubSecretArg available on all platforms

It makes InlinedCallFrame one extra word larger than necessary in most cases. It should not be a big deal, there are other similar inefficiencies in InlinedCallFrame. We should be able to do that to make things cleaner.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

can you please update the fix to be something like

@jakobbotsch thanks, it works. Updated PR.

rv64 asm for IL_STUB_PInvoke(byref) [MinOpts, IL size=98, code size=920, hash=0x5d63dafb]
G_M9476_IG01: ; offs=0x000000, size=0x0068, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IGIN0086: 000000 addi sp,sp,-576IN0087: 000004 sd s1,0(sp)IN0088: 000008 sd s2,8(sp)IN0089: 00000C sd s3,16(sp)IN008a: 000010 sd s4,24(sp)IN008b: 000014 sd s5,32(sp)IN008c: 000018 sd s6,40(sp)IN008d: 00001C sd s7,48(sp)IN008e: 000020 sd s8,56(sp)IN008f: 000024 sd s9,64(sp) IN0090: 000028 sd s10,72(sp)IN0091: 00002C sd s11,80(sp)IN0092: 000030 sd ra,88(sp)IN0093: 000034 sd fp,96(sp)IN0094: 000038 addi fp,sp,96IN0095: 00003C sd t2,80(fp)IN0096: 000040 addi s1, fp,104IN0097: 000044 addi t0, zero,22IN0098: 000048 sd zero,8(s1) IN0099: 00004C sd zero,0(s1) IN009a: 000050 addi t0, t0,-1IN009b: 000054 addi s1, s1,16IN009c: 000058 bne t0, zero, pc-16 (-4 instructions)IN009d: 00005C addi s1,sp,576IN009e: 000060 sd s1,472(fp)IN009f: 000064 sd a0,464(fp) ;; size=104 bbWeight=1 PerfScore 0.00G_M9476_IG02: ; offs=0x000068, size=0x006C, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB09 [0008], BB01 [0000], byrefIN0001: 000068 addi a0, fp,112IN0002: 00006C addi a1, zero,-51IN0003: 000070 addi a2, zero,296IN0004: 000074 lui t2,0000078 addi t2, t2,63 00007C slli t2, t2,11000080 addi t2, t2,1401000084 slli t2, t2,11000088 addi t2, t2,1785 00008C slli t2, t2,10000090 jalr ra,748(t2) // CORINFO_HELP_NATIVE_MEMSETIN0005: 000094 addi a0, fp,16IN0006: 000098 ld a1,80(fp)IN0007: 00009C lui a2,521704IN0008: 0000A0 addiw a2, a2,-1570IN0009: 0000A4 slli a2, a2,7IN000a: 0000A8 addi a2, a2,58IN000b: 0000AC jalr ra,0(a2) // CORINFO_HELP_INIT_PINVOKE_FRAME

In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

@jkotas Thanks for detailed instruction, unfortunately I don't have plan to fix #100662 for near future.

@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from ae76e4a to f37d25eCompareMarch 28, 2024 23:34
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch 2 times, most recently from 1c7c97f to 232ee6fCompareMarch 30, 2024 01:29
@t-mustafin
t-mustafin marked this pull request as ready for review March 30, 2024 01:45
Issue dotnet#100301
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from 232ee6f to 321559aCompareApril 9, 2024 18:17

@jakobbotschjakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@gbalykov

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

@jakobbotsch

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

I don't think so. The CORINFO_HELP_MEMSET call that trashes the register in #100301 is coming from poisoning that is inserted by the JIT for address taken locals in unoptimized codegen. This means that the demonstrated case requires an IL stub compiled without optimizations. However, I believe that IL stubs under normal operations will always be compiled with optimizations enabled, so the call will not be there.

In theory nothing stops the register from being trashed through other means during prolog codegen, but this theoretical issue exists in RyuJIT since forever and generally we wouldn't try to backport a fix for a theoretical issue like that.

@jkotas

Copy link
Copy Markdown
Member

I believe that IL stubs under normal operations will always be compiled with optimizations enabled

I do not think that it is the case. The IL stubs have the same optimization settings as the module that they are attached to.

I agree that this is a theoretical issue in the shipping runtime version and it does not meet the bar for backport without a repro.

matouskozak pushed a commit to matouskozak/runtime that referenced this pull request Apr 30, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 16, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@t-mustafin@gbalykov@jakobbotsch@jkotas@clamp03@ashaurtaev@sirntar@Bajtazar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Restore StubSecretArg from stack - #100428

Merged
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack
Apr 10, 2024
Merged

Restore StubSecretArg from stack#100428
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack

Conversation

@t-mustafin

Copy link
Copy Markdown
Contributor

Issue #100301

@jakobbotsch @dotnet/samsung
Part of #84834

Draft to check CI test results.

@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 28, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 28, 2024
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment on lines +5613 to +5614
if (comp->lvaStubArgumentVar == BAD_VAR_NUM)
comp->lvaStubArgumentVar = comp->lvaInlinedPInvokeFrameVar;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Without this (comp->lvaStubArgumentVar == BAD_VAR_NUM) cases lead to assert:

Assert failure(PID 11606 [0x00002d56], Thread: 11606 [0x2d56]): Assertion failed 'lclNum < lvaCount' in 'System.Buffer:_Memmove(byref,byref,ulong)' during 'Lowering nodeinfo' (IL size 25; hash 0xd7d18401; FullOpts)
File: /home/runtime/src/coreclr/jit/compiler.h:4008
Image: /home/tmustafin/main_26mar_checkedO0_7f555e0_StubSecretArgRestored68d8aa/coreroot/corerun
Thread 1"corerun" received signal SIGTRAP, Trace/breakpoint trap.
DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:66 /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S: No such file or directory.
(gdb) bt
#0 DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:6#1 0x0000003f74b129ae in DebugBreak () at /home/runtime/src/coreclr/pal/src/debug/debug.cpp:406#2 0x0000003f7481c69a in assertAbort (why=0x3f74bc0f17 "lclNum < lvaCount", file=0x3f74bc17b0 "/home/runtime/src/coreclr/jit/compiler.h", line=4008) at /home/runtime/src/coreclr/jit/error.cpp:286#3 0x0000003f7479cf10 in Compiler::lvaGetDesc (this=0x2aaac75278, lclNum=4294967295) at /home/runtime/src/coreclr/jit/compiler.h:4008#4 0x0000003f7479cff2 in Compiler::lvaGetDesc (this=0x2aaac75278, lclVar=0x2aaac832d8) at /home/runtime/src/coreclr/jit/compiler.h:4020#5 0x0000003f749e314c in Compiler::fgMorphLeafLocal (this=0x2aaac75278, lclNode=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:4577#6 0x0000003f749ef45a in Compiler::fgMorphLeaf (this=0x2aaac75278, tree=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:8098#7 0x0000003f749d9134 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac832d8, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12721#8 0x0000003f749da318 in Compiler::fgMorphArgs (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:3171#9 0x0000003f749ebee0 in Compiler::fgMorphCall (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:7760#10 0x0000003f749d9248 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac831b0, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12746#11 0x0000003f749e6052 in Compiler::fgMorphSmpOp (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0, optAssertionPropDone=0x3fffff9f47) at /home/runtime/src/coreclr/jit/morph.cpp:8950#12 0x0000003f749d9164 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12729#13 0x0000003f749a51d8 in Lowering::InsertPInvokeMethodProlog (this=0x2aaac830c8) at /home/runtime/src/coreclr/jit/lower.cpp:5630

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you double check why lvaStubArgumentVar is BAD_VAR_NUM even if we are making use of this register? Does it mean we are adding garbage to this method call? It would be better to pass 0 as the argument in those cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC there are several different flavors of how the secret stub argument gets consumed:

  • Through System.StubHelpers.GetStubContext() on all architectures. The runtime passes CORJIT_FLAG_PUBLISH_SECRET_PARAM for functions that may use this intrinsic.
  • As an argument to CORINFO_HELP_INIT_PINVOKE_FRAME outside x86/arm32. The runtime seemingly doesn't always pass CORJIT_FLAG_PUBLISH_SECRET_PARAM in the cases where we need to call this JIT helper, which is confusing to me. Does it mean we can pass nullptr in these cases? Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?
  • Through known stack frame layout on x86/arm32:
    // Given a methodDesc representing an ILStub for a pinvoke call,
    // this method will return the MethodDesc for the actual interop
    // method if the current InlinedCallFrame is inactive.
    PTR_MethodDesc GetActualInteropMethodDesc()
    {
    #if defined(TARGET_X86) || defined(TARGET_ARM)
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
    #elif defined(HOST_64BIT)
    // On 64bit, the actual interop MethodDesc is saved off in a field off the InlinedCrawlFrame
    // which is populated by the JIT. Refer to JIT_InitPInvokeFrame for details.
    returnPTR_MethodDesc(m_StubSecretArg);
    #else
    _ASSERTE(!"NYI - Interop method reporting for this architecture!");
    returnNULL;
    #endif// defined(TARGET_X86) || defined(TARGET_ARM)
    }
    . Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

cc @jkotas, do you know the answer to some of these questions?

@jkotasjkotasApr 4, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?

Yes, the arg is only used for IL stubs.

Does it mean we can pass nullptr in these cases?

The value is unused. It does not matter what gets passed in.

Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

The JIT is aware of InlinedFrame layout and semantics. It may be better to switch 64-bit to the same scheme as x86/arm32: Get rid of the argument and store it in the InlinedFrame instead - on all platforms and only when it is needed. It will save a memory store for inlined PInvokes on 64-bit platforms.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! I opened #100662 about unifying this.

@t-mustafin can you please update the fix to be something like

 GenTree* argNode;
if (comp->info.compPublishStubParam)
{
argNode = comp->gtNewLclvNode(comp->lvaStubArgumentVar, TYP_I_IMPL);
}
else
{
argNode = comp->gtNewIconNode(0, TYP_I_IMPL);
}
NewCallArg stubParamArg =
NewCallArg::Primitive(argNode).WellKnown(WellKnownArg::SecretStubParam);
call->gtArgs.PushBack(comp, stubParamArg);

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I opened #100662 about unifying this.

@t-mustafin In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

  • Delete code under the ifdef that you are fixing
  • Delete StubSecretArg here:
    Thread * __stdcall JIT_InitPInvokeFrame(InlinedCallFrame *pFrame, PTR_VOID StubSecretArg)
  • Delete
    #ifdef HOST_64BIT
    // IL stubs fill this field with the incoming secret argument when they erect
    // InlinedCallFrame so we know which interop method was invoked even if the frame
    // is not active at the moment.
    PTR_VOID m_StubSecretArg;
    #endif// HOST_64BIT
  • Enable this for all architectures
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
  • Rev the JIT-EE interface GUID: https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/jiteeversionguid.h

@jakobbotsch Does this sound about right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was hoping we would be able to get rid of the "known stack layout" contract between the VM and JIT as part of this unification. It results in a number of special casing around this particular value in the JIT and has a side effect that we cannot keep the value enregistered.
My thought was:

  • Make InlinedCallFrame::m_StubSecretArg available on all platforms. Make it the JIT's responsibility to store this field in the frame on x64 (instead of doing it from JIT_InitPInvokeFrame). Store the value on x86 as well (it will change a spill to stack at the known stack frame location to a store to the InlinedCallFrame)
  • Remove all special handling in frame layout around lvaStubArgumentVar
  • Eventually enable full enregistration of lvaStubArgumentVar and remove all remaining special handling around it in the JIT. Coincidentally I am doing work in JIT: Rewrite register parameter homing #100572 that should make this simple.

Do you see any potential issues around doing it this way?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Make InlinedCallFrame::m_StubSecretArg available on all platforms

It makes InlinedCallFrame one extra word larger than necessary in most cases. It should not be a big deal, there are other similar inefficiencies in InlinedCallFrame. We should be able to do that to make things cleaner.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

can you please update the fix to be something like

@jakobbotsch thanks, it works. Updated PR.

rv64 asm for IL_STUB_PInvoke(byref) [MinOpts, IL size=98, code size=920, hash=0x5d63dafb]
G_M9476_IG01: ; offs=0x000000, size=0x0068, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IGIN0086: 000000 addi sp,sp,-576IN0087: 000004 sd s1,0(sp)IN0088: 000008 sd s2,8(sp)IN0089: 00000C sd s3,16(sp)IN008a: 000010 sd s4,24(sp)IN008b: 000014 sd s5,32(sp)IN008c: 000018 sd s6,40(sp)IN008d: 00001C sd s7,48(sp)IN008e: 000020 sd s8,56(sp)IN008f: 000024 sd s9,64(sp) IN0090: 000028 sd s10,72(sp)IN0091: 00002C sd s11,80(sp)IN0092: 000030 sd ra,88(sp)IN0093: 000034 sd fp,96(sp)IN0094: 000038 addi fp,sp,96IN0095: 00003C sd t2,80(fp)IN0096: 000040 addi s1, fp,104IN0097: 000044 addi t0, zero,22IN0098: 000048 sd zero,8(s1) IN0099: 00004C sd zero,0(s1) IN009a: 000050 addi t0, t0,-1IN009b: 000054 addi s1, s1,16IN009c: 000058 bne t0, zero, pc-16 (-4 instructions)IN009d: 00005C addi s1,sp,576IN009e: 000060 sd s1,472(fp)IN009f: 000064 sd a0,464(fp) ;; size=104 bbWeight=1 PerfScore 0.00G_M9476_IG02: ; offs=0x000068, size=0x006C, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB09 [0008], BB01 [0000], byrefIN0001: 000068 addi a0, fp,112IN0002: 00006C addi a1, zero,-51IN0003: 000070 addi a2, zero,296IN0004: 000074 lui t2,0000078 addi t2, t2,63 00007C slli t2, t2,11000080 addi t2, t2,1401000084 slli t2, t2,11000088 addi t2, t2,1785 00008C slli t2, t2,10000090 jalr ra,748(t2) // CORINFO_HELP_NATIVE_MEMSETIN0005: 000094 addi a0, fp,16IN0006: 000098 ld a1,80(fp)IN0007: 00009C lui a2,521704IN0008: 0000A0 addiw a2, a2,-1570IN0009: 0000A4 slli a2, a2,7IN000a: 0000A8 addi a2, a2,58IN000b: 0000AC jalr ra,0(a2) // CORINFO_HELP_INIT_PINVOKE_FRAME

In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

@jkotas Thanks for detailed instruction, unfortunately I don't have plan to fix #100662 for near future.

@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from ae76e4a to f37d25eCompareMarch 28, 2024 23:34
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch 2 times, most recently from 1c7c97f to 232ee6fCompareMarch 30, 2024 01:29
@t-mustafin
t-mustafin marked this pull request as ready for review March 30, 2024 01:45
Issue dotnet#100301
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from 232ee6f to 321559aCompareApril 9, 2024 18:17

@jakobbotschjakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@gbalykov

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

@jakobbotsch

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

I don't think so. The CORINFO_HELP_MEMSET call that trashes the register in #100301 is coming from poisoning that is inserted by the JIT for address taken locals in unoptimized codegen. This means that the demonstrated case requires an IL stub compiled without optimizations. However, I believe that IL stubs under normal operations will always be compiled with optimizations enabled, so the call will not be there.

In theory nothing stops the register from being trashed through other means during prolog codegen, but this theoretical issue exists in RyuJIT since forever and generally we wouldn't try to backport a fix for a theoretical issue like that.

@jkotas

Copy link
Copy Markdown
Member

I believe that IL stubs under normal operations will always be compiled with optimizations enabled

I do not think that it is the case. The IL stubs have the same optimization settings as the module that they are attached to.

I agree that this is a theoretical issue in the shipping runtime version and it does not meet the bar for backport without a repro.

matouskozak pushed a commit to matouskozak/runtime that referenced this pull request Apr 30, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 16, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@t-mustafin@gbalykov@jakobbotsch@jkotas@clamp03@ashaurtaev@sirntar@Bajtazar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Restore StubSecretArg from stack - #100428

Merged
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack
Apr 10, 2024
Merged

Restore StubSecretArg from stack#100428
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack

Conversation

@t-mustafin

Copy link
Copy Markdown
Contributor

Issue #100301

@jakobbotsch @dotnet/samsung
Part of #84834

Draft to check CI test results.

@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 28, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 28, 2024
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment on lines +5613 to +5614
if (comp->lvaStubArgumentVar == BAD_VAR_NUM)
comp->lvaStubArgumentVar = comp->lvaInlinedPInvokeFrameVar;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Without this (comp->lvaStubArgumentVar == BAD_VAR_NUM) cases lead to assert:

Assert failure(PID 11606 [0x00002d56], Thread: 11606 [0x2d56]): Assertion failed 'lclNum < lvaCount' in 'System.Buffer:_Memmove(byref,byref,ulong)' during 'Lowering nodeinfo' (IL size 25; hash 0xd7d18401; FullOpts)
File: /home/runtime/src/coreclr/jit/compiler.h:4008
Image: /home/tmustafin/main_26mar_checkedO0_7f555e0_StubSecretArgRestored68d8aa/coreroot/corerun
Thread 1"corerun" received signal SIGTRAP, Trace/breakpoint trap.
DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:66 /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S: No such file or directory.
(gdb) bt
#0 DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:6#1 0x0000003f74b129ae in DebugBreak () at /home/runtime/src/coreclr/pal/src/debug/debug.cpp:406#2 0x0000003f7481c69a in assertAbort (why=0x3f74bc0f17 "lclNum < lvaCount", file=0x3f74bc17b0 "/home/runtime/src/coreclr/jit/compiler.h", line=4008) at /home/runtime/src/coreclr/jit/error.cpp:286#3 0x0000003f7479cf10 in Compiler::lvaGetDesc (this=0x2aaac75278, lclNum=4294967295) at /home/runtime/src/coreclr/jit/compiler.h:4008#4 0x0000003f7479cff2 in Compiler::lvaGetDesc (this=0x2aaac75278, lclVar=0x2aaac832d8) at /home/runtime/src/coreclr/jit/compiler.h:4020#5 0x0000003f749e314c in Compiler::fgMorphLeafLocal (this=0x2aaac75278, lclNode=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:4577#6 0x0000003f749ef45a in Compiler::fgMorphLeaf (this=0x2aaac75278, tree=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:8098#7 0x0000003f749d9134 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac832d8, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12721#8 0x0000003f749da318 in Compiler::fgMorphArgs (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:3171#9 0x0000003f749ebee0 in Compiler::fgMorphCall (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:7760#10 0x0000003f749d9248 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac831b0, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12746#11 0x0000003f749e6052 in Compiler::fgMorphSmpOp (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0, optAssertionPropDone=0x3fffff9f47) at /home/runtime/src/coreclr/jit/morph.cpp:8950#12 0x0000003f749d9164 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12729#13 0x0000003f749a51d8 in Lowering::InsertPInvokeMethodProlog (this=0x2aaac830c8) at /home/runtime/src/coreclr/jit/lower.cpp:5630

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you double check why lvaStubArgumentVar is BAD_VAR_NUM even if we are making use of this register? Does it mean we are adding garbage to this method call? It would be better to pass 0 as the argument in those cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC there are several different flavors of how the secret stub argument gets consumed:

  • Through System.StubHelpers.GetStubContext() on all architectures. The runtime passes CORJIT_FLAG_PUBLISH_SECRET_PARAM for functions that may use this intrinsic.
  • As an argument to CORINFO_HELP_INIT_PINVOKE_FRAME outside x86/arm32. The runtime seemingly doesn't always pass CORJIT_FLAG_PUBLISH_SECRET_PARAM in the cases where we need to call this JIT helper, which is confusing to me. Does it mean we can pass nullptr in these cases? Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?
  • Through known stack frame layout on x86/arm32:
    // Given a methodDesc representing an ILStub for a pinvoke call,
    // this method will return the MethodDesc for the actual interop
    // method if the current InlinedCallFrame is inactive.
    PTR_MethodDesc GetActualInteropMethodDesc()
    {
    #if defined(TARGET_X86) || defined(TARGET_ARM)
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
    #elif defined(HOST_64BIT)
    // On 64bit, the actual interop MethodDesc is saved off in a field off the InlinedCrawlFrame
    // which is populated by the JIT. Refer to JIT_InitPInvokeFrame for details.
    returnPTR_MethodDesc(m_StubSecretArg);
    #else
    _ASSERTE(!"NYI - Interop method reporting for this architecture!");
    returnNULL;
    #endif// defined(TARGET_X86) || defined(TARGET_ARM)
    }
    . Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

cc @jkotas, do you know the answer to some of these questions?

@jkotasjkotasApr 4, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?

Yes, the arg is only used for IL stubs.

Does it mean we can pass nullptr in these cases?

The value is unused. It does not matter what gets passed in.

Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

The JIT is aware of InlinedFrame layout and semantics. It may be better to switch 64-bit to the same scheme as x86/arm32: Get rid of the argument and store it in the InlinedFrame instead - on all platforms and only when it is needed. It will save a memory store for inlined PInvokes on 64-bit platforms.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! I opened #100662 about unifying this.

@t-mustafin can you please update the fix to be something like

 GenTree* argNode;
if (comp->info.compPublishStubParam)
{
argNode = comp->gtNewLclvNode(comp->lvaStubArgumentVar, TYP_I_IMPL);
}
else
{
argNode = comp->gtNewIconNode(0, TYP_I_IMPL);
}
NewCallArg stubParamArg =
NewCallArg::Primitive(argNode).WellKnown(WellKnownArg::SecretStubParam);
call->gtArgs.PushBack(comp, stubParamArg);

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I opened #100662 about unifying this.

@t-mustafin In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

  • Delete code under the ifdef that you are fixing
  • Delete StubSecretArg here:
    Thread * __stdcall JIT_InitPInvokeFrame(InlinedCallFrame *pFrame, PTR_VOID StubSecretArg)
  • Delete
    #ifdef HOST_64BIT
    // IL stubs fill this field with the incoming secret argument when they erect
    // InlinedCallFrame so we know which interop method was invoked even if the frame
    // is not active at the moment.
    PTR_VOID m_StubSecretArg;
    #endif// HOST_64BIT
  • Enable this for all architectures
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
  • Rev the JIT-EE interface GUID: https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/jiteeversionguid.h

@jakobbotsch Does this sound about right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was hoping we would be able to get rid of the "known stack layout" contract between the VM and JIT as part of this unification. It results in a number of special casing around this particular value in the JIT and has a side effect that we cannot keep the value enregistered.
My thought was:

  • Make InlinedCallFrame::m_StubSecretArg available on all platforms. Make it the JIT's responsibility to store this field in the frame on x64 (instead of doing it from JIT_InitPInvokeFrame). Store the value on x86 as well (it will change a spill to stack at the known stack frame location to a store to the InlinedCallFrame)
  • Remove all special handling in frame layout around lvaStubArgumentVar
  • Eventually enable full enregistration of lvaStubArgumentVar and remove all remaining special handling around it in the JIT. Coincidentally I am doing work in JIT: Rewrite register parameter homing #100572 that should make this simple.

Do you see any potential issues around doing it this way?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Make InlinedCallFrame::m_StubSecretArg available on all platforms

It makes InlinedCallFrame one extra word larger than necessary in most cases. It should not be a big deal, there are other similar inefficiencies in InlinedCallFrame. We should be able to do that to make things cleaner.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

can you please update the fix to be something like

@jakobbotsch thanks, it works. Updated PR.

rv64 asm for IL_STUB_PInvoke(byref) [MinOpts, IL size=98, code size=920, hash=0x5d63dafb]
G_M9476_IG01: ; offs=0x000000, size=0x0068, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IGIN0086: 000000 addi sp,sp,-576IN0087: 000004 sd s1,0(sp)IN0088: 000008 sd s2,8(sp)IN0089: 00000C sd s3,16(sp)IN008a: 000010 sd s4,24(sp)IN008b: 000014 sd s5,32(sp)IN008c: 000018 sd s6,40(sp)IN008d: 00001C sd s7,48(sp)IN008e: 000020 sd s8,56(sp)IN008f: 000024 sd s9,64(sp) IN0090: 000028 sd s10,72(sp)IN0091: 00002C sd s11,80(sp)IN0092: 000030 sd ra,88(sp)IN0093: 000034 sd fp,96(sp)IN0094: 000038 addi fp,sp,96IN0095: 00003C sd t2,80(fp)IN0096: 000040 addi s1, fp,104IN0097: 000044 addi t0, zero,22IN0098: 000048 sd zero,8(s1) IN0099: 00004C sd zero,0(s1) IN009a: 000050 addi t0, t0,-1IN009b: 000054 addi s1, s1,16IN009c: 000058 bne t0, zero, pc-16 (-4 instructions)IN009d: 00005C addi s1,sp,576IN009e: 000060 sd s1,472(fp)IN009f: 000064 sd a0,464(fp) ;; size=104 bbWeight=1 PerfScore 0.00G_M9476_IG02: ; offs=0x000068, size=0x006C, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB09 [0008], BB01 [0000], byrefIN0001: 000068 addi a0, fp,112IN0002: 00006C addi a1, zero,-51IN0003: 000070 addi a2, zero,296IN0004: 000074 lui t2,0000078 addi t2, t2,63 00007C slli t2, t2,11000080 addi t2, t2,1401000084 slli t2, t2,11000088 addi t2, t2,1785 00008C slli t2, t2,10000090 jalr ra,748(t2) // CORINFO_HELP_NATIVE_MEMSETIN0005: 000094 addi a0, fp,16IN0006: 000098 ld a1,80(fp)IN0007: 00009C lui a2,521704IN0008: 0000A0 addiw a2, a2,-1570IN0009: 0000A4 slli a2, a2,7IN000a: 0000A8 addi a2, a2,58IN000b: 0000AC jalr ra,0(a2) // CORINFO_HELP_INIT_PINVOKE_FRAME

In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

@jkotas Thanks for detailed instruction, unfortunately I don't have plan to fix #100662 for near future.

@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from ae76e4a to f37d25eCompareMarch 28, 2024 23:34
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch 2 times, most recently from 1c7c97f to 232ee6fCompareMarch 30, 2024 01:29
@t-mustafin
t-mustafin marked this pull request as ready for review March 30, 2024 01:45
Issue dotnet#100301
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from 232ee6f to 321559aCompareApril 9, 2024 18:17

@jakobbotschjakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@gbalykov

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

@jakobbotsch

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

I don't think so. The CORINFO_HELP_MEMSET call that trashes the register in #100301 is coming from poisoning that is inserted by the JIT for address taken locals in unoptimized codegen. This means that the demonstrated case requires an IL stub compiled without optimizations. However, I believe that IL stubs under normal operations will always be compiled with optimizations enabled, so the call will not be there.

In theory nothing stops the register from being trashed through other means during prolog codegen, but this theoretical issue exists in RyuJIT since forever and generally we wouldn't try to backport a fix for a theoretical issue like that.

@jkotas

Copy link
Copy Markdown
Member

I believe that IL stubs under normal operations will always be compiled with optimizations enabled

I do not think that it is the case. The IL stubs have the same optimization settings as the module that they are attached to.

I agree that this is a theoretical issue in the shipping runtime version and it does not meet the bar for backport without a repro.

matouskozak pushed a commit to matouskozak/runtime that referenced this pull request Apr 30, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 16, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@t-mustafin@gbalykov@jakobbotsch@jkotas@clamp03@ashaurtaev@sirntar@Bajtazar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Restore StubSecretArg from stack - #100428

Merged
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack
Apr 10, 2024
Merged

Restore StubSecretArg from stack#100428
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack

Conversation

@t-mustafin

Copy link
Copy Markdown
Contributor

Issue #100301

@jakobbotsch @dotnet/samsung
Part of #84834

Draft to check CI test results.

@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 28, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 28, 2024
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment on lines +5613 to +5614
if (comp->lvaStubArgumentVar == BAD_VAR_NUM)
comp->lvaStubArgumentVar = comp->lvaInlinedPInvokeFrameVar;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Without this (comp->lvaStubArgumentVar == BAD_VAR_NUM) cases lead to assert:

Assert failure(PID 11606 [0x00002d56], Thread: 11606 [0x2d56]): Assertion failed 'lclNum < lvaCount' in 'System.Buffer:_Memmove(byref,byref,ulong)' during 'Lowering nodeinfo' (IL size 25; hash 0xd7d18401; FullOpts)
File: /home/runtime/src/coreclr/jit/compiler.h:4008
Image: /home/tmustafin/main_26mar_checkedO0_7f555e0_StubSecretArgRestored68d8aa/coreroot/corerun
Thread 1"corerun" received signal SIGTRAP, Trace/breakpoint trap.
DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:66 /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S: No such file or directory.
(gdb) bt
#0 DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:6#1 0x0000003f74b129ae in DebugBreak () at /home/runtime/src/coreclr/pal/src/debug/debug.cpp:406#2 0x0000003f7481c69a in assertAbort (why=0x3f74bc0f17 "lclNum < lvaCount", file=0x3f74bc17b0 "/home/runtime/src/coreclr/jit/compiler.h", line=4008) at /home/runtime/src/coreclr/jit/error.cpp:286#3 0x0000003f7479cf10 in Compiler::lvaGetDesc (this=0x2aaac75278, lclNum=4294967295) at /home/runtime/src/coreclr/jit/compiler.h:4008#4 0x0000003f7479cff2 in Compiler::lvaGetDesc (this=0x2aaac75278, lclVar=0x2aaac832d8) at /home/runtime/src/coreclr/jit/compiler.h:4020#5 0x0000003f749e314c in Compiler::fgMorphLeafLocal (this=0x2aaac75278, lclNode=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:4577#6 0x0000003f749ef45a in Compiler::fgMorphLeaf (this=0x2aaac75278, tree=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:8098#7 0x0000003f749d9134 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac832d8, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12721#8 0x0000003f749da318 in Compiler::fgMorphArgs (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:3171#9 0x0000003f749ebee0 in Compiler::fgMorphCall (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:7760#10 0x0000003f749d9248 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac831b0, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12746#11 0x0000003f749e6052 in Compiler::fgMorphSmpOp (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0, optAssertionPropDone=0x3fffff9f47) at /home/runtime/src/coreclr/jit/morph.cpp:8950#12 0x0000003f749d9164 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12729#13 0x0000003f749a51d8 in Lowering::InsertPInvokeMethodProlog (this=0x2aaac830c8) at /home/runtime/src/coreclr/jit/lower.cpp:5630

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you double check why lvaStubArgumentVar is BAD_VAR_NUM even if we are making use of this register? Does it mean we are adding garbage to this method call? It would be better to pass 0 as the argument in those cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC there are several different flavors of how the secret stub argument gets consumed:

  • Through System.StubHelpers.GetStubContext() on all architectures. The runtime passes CORJIT_FLAG_PUBLISH_SECRET_PARAM for functions that may use this intrinsic.
  • As an argument to CORINFO_HELP_INIT_PINVOKE_FRAME outside x86/arm32. The runtime seemingly doesn't always pass CORJIT_FLAG_PUBLISH_SECRET_PARAM in the cases where we need to call this JIT helper, which is confusing to me. Does it mean we can pass nullptr in these cases? Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?
  • Through known stack frame layout on x86/arm32:
    // Given a methodDesc representing an ILStub for a pinvoke call,
    // this method will return the MethodDesc for the actual interop
    // method if the current InlinedCallFrame is inactive.
    PTR_MethodDesc GetActualInteropMethodDesc()
    {
    #if defined(TARGET_X86) || defined(TARGET_ARM)
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
    #elif defined(HOST_64BIT)
    // On 64bit, the actual interop MethodDesc is saved off in a field off the InlinedCrawlFrame
    // which is populated by the JIT. Refer to JIT_InitPInvokeFrame for details.
    returnPTR_MethodDesc(m_StubSecretArg);
    #else
    _ASSERTE(!"NYI - Interop method reporting for this architecture!");
    returnNULL;
    #endif// defined(TARGET_X86) || defined(TARGET_ARM)
    }
    . Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

cc @jkotas, do you know the answer to some of these questions?

@jkotasjkotasApr 4, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?

Yes, the arg is only used for IL stubs.

Does it mean we can pass nullptr in these cases?

The value is unused. It does not matter what gets passed in.

Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

The JIT is aware of InlinedFrame layout and semantics. It may be better to switch 64-bit to the same scheme as x86/arm32: Get rid of the argument and store it in the InlinedFrame instead - on all platforms and only when it is needed. It will save a memory store for inlined PInvokes on 64-bit platforms.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! I opened #100662 about unifying this.

@t-mustafin can you please update the fix to be something like

 GenTree* argNode;
if (comp->info.compPublishStubParam)
{
argNode = comp->gtNewLclvNode(comp->lvaStubArgumentVar, TYP_I_IMPL);
}
else
{
argNode = comp->gtNewIconNode(0, TYP_I_IMPL);
}
NewCallArg stubParamArg =
NewCallArg::Primitive(argNode).WellKnown(WellKnownArg::SecretStubParam);
call->gtArgs.PushBack(comp, stubParamArg);

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I opened #100662 about unifying this.

@t-mustafin In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

  • Delete code under the ifdef that you are fixing
  • Delete StubSecretArg here:
    Thread * __stdcall JIT_InitPInvokeFrame(InlinedCallFrame *pFrame, PTR_VOID StubSecretArg)
  • Delete
    #ifdef HOST_64BIT
    // IL stubs fill this field with the incoming secret argument when they erect
    // InlinedCallFrame so we know which interop method was invoked even if the frame
    // is not active at the moment.
    PTR_VOID m_StubSecretArg;
    #endif// HOST_64BIT
  • Enable this for all architectures
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
  • Rev the JIT-EE interface GUID: https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/jiteeversionguid.h

@jakobbotsch Does this sound about right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was hoping we would be able to get rid of the "known stack layout" contract between the VM and JIT as part of this unification. It results in a number of special casing around this particular value in the JIT and has a side effect that we cannot keep the value enregistered.
My thought was:

  • Make InlinedCallFrame::m_StubSecretArg available on all platforms. Make it the JIT's responsibility to store this field in the frame on x64 (instead of doing it from JIT_InitPInvokeFrame). Store the value on x86 as well (it will change a spill to stack at the known stack frame location to a store to the InlinedCallFrame)
  • Remove all special handling in frame layout around lvaStubArgumentVar
  • Eventually enable full enregistration of lvaStubArgumentVar and remove all remaining special handling around it in the JIT. Coincidentally I am doing work in JIT: Rewrite register parameter homing #100572 that should make this simple.

Do you see any potential issues around doing it this way?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Make InlinedCallFrame::m_StubSecretArg available on all platforms

It makes InlinedCallFrame one extra word larger than necessary in most cases. It should not be a big deal, there are other similar inefficiencies in InlinedCallFrame. We should be able to do that to make things cleaner.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

can you please update the fix to be something like

@jakobbotsch thanks, it works. Updated PR.

rv64 asm for IL_STUB_PInvoke(byref) [MinOpts, IL size=98, code size=920, hash=0x5d63dafb]
G_M9476_IG01: ; offs=0x000000, size=0x0068, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IGIN0086: 000000 addi sp,sp,-576IN0087: 000004 sd s1,0(sp)IN0088: 000008 sd s2,8(sp)IN0089: 00000C sd s3,16(sp)IN008a: 000010 sd s4,24(sp)IN008b: 000014 sd s5,32(sp)IN008c: 000018 sd s6,40(sp)IN008d: 00001C sd s7,48(sp)IN008e: 000020 sd s8,56(sp)IN008f: 000024 sd s9,64(sp) IN0090: 000028 sd s10,72(sp)IN0091: 00002C sd s11,80(sp)IN0092: 000030 sd ra,88(sp)IN0093: 000034 sd fp,96(sp)IN0094: 000038 addi fp,sp,96IN0095: 00003C sd t2,80(fp)IN0096: 000040 addi s1, fp,104IN0097: 000044 addi t0, zero,22IN0098: 000048 sd zero,8(s1) IN0099: 00004C sd zero,0(s1) IN009a: 000050 addi t0, t0,-1IN009b: 000054 addi s1, s1,16IN009c: 000058 bne t0, zero, pc-16 (-4 instructions)IN009d: 00005C addi s1,sp,576IN009e: 000060 sd s1,472(fp)IN009f: 000064 sd a0,464(fp) ;; size=104 bbWeight=1 PerfScore 0.00G_M9476_IG02: ; offs=0x000068, size=0x006C, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB09 [0008], BB01 [0000], byrefIN0001: 000068 addi a0, fp,112IN0002: 00006C addi a1, zero,-51IN0003: 000070 addi a2, zero,296IN0004: 000074 lui t2,0000078 addi t2, t2,63 00007C slli t2, t2,11000080 addi t2, t2,1401000084 slli t2, t2,11000088 addi t2, t2,1785 00008C slli t2, t2,10000090 jalr ra,748(t2) // CORINFO_HELP_NATIVE_MEMSETIN0005: 000094 addi a0, fp,16IN0006: 000098 ld a1,80(fp)IN0007: 00009C lui a2,521704IN0008: 0000A0 addiw a2, a2,-1570IN0009: 0000A4 slli a2, a2,7IN000a: 0000A8 addi a2, a2,58IN000b: 0000AC jalr ra,0(a2) // CORINFO_HELP_INIT_PINVOKE_FRAME

In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

@jkotas Thanks for detailed instruction, unfortunately I don't have plan to fix #100662 for near future.

@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from ae76e4a to f37d25eCompareMarch 28, 2024 23:34
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch 2 times, most recently from 1c7c97f to 232ee6fCompareMarch 30, 2024 01:29
@t-mustafin
t-mustafin marked this pull request as ready for review March 30, 2024 01:45
Issue dotnet#100301
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from 232ee6f to 321559aCompareApril 9, 2024 18:17

@jakobbotschjakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@gbalykov

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

@jakobbotsch

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

I don't think so. The CORINFO_HELP_MEMSET call that trashes the register in #100301 is coming from poisoning that is inserted by the JIT for address taken locals in unoptimized codegen. This means that the demonstrated case requires an IL stub compiled without optimizations. However, I believe that IL stubs under normal operations will always be compiled with optimizations enabled, so the call will not be there.

In theory nothing stops the register from being trashed through other means during prolog codegen, but this theoretical issue exists in RyuJIT since forever and generally we wouldn't try to backport a fix for a theoretical issue like that.

@jkotas

Copy link
Copy Markdown
Member

I believe that IL stubs under normal operations will always be compiled with optimizations enabled

I do not think that it is the case. The IL stubs have the same optimization settings as the module that they are attached to.

I agree that this is a theoretical issue in the shipping runtime version and it does not meet the bar for backport without a repro.

matouskozak pushed a commit to matouskozak/runtime that referenced this pull request Apr 30, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 16, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@t-mustafin@gbalykov@jakobbotsch@jkotas@clamp03@ashaurtaev@sirntar@Bajtazar
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Restore StubSecretArg from stack - #100428

Merged
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack
Apr 10, 2024
Merged

Restore StubSecretArg from stack#100428
jakobbotsch merged 3 commits into
dotnet:mainfrom
t-mustafin:StubSecretArg_restore_from_stack

Conversation

@t-mustafin

Copy link
Copy Markdown
Contributor

Issue #100301

@jakobbotsch @dotnet/samsung
Part of #84834

Draft to check CI test results.

@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 28, 2024
@dotnet-policy-servicedotnet-policy-serviceBot added the community-contribution Indicates that the PR has been added by a community member label Mar 28, 2024
Comment threadsrc/coreclr/jit/lower.cpp Outdated
Comment on lines +5613 to +5614
if (comp->lvaStubArgumentVar == BAD_VAR_NUM)
comp->lvaStubArgumentVar = comp->lvaInlinedPInvokeFrameVar;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Without this (comp->lvaStubArgumentVar == BAD_VAR_NUM) cases lead to assert:

Assert failure(PID 11606 [0x00002d56], Thread: 11606 [0x2d56]): Assertion failed 'lclNum < lvaCount' in 'System.Buffer:_Memmove(byref,byref,ulong)' during 'Lowering nodeinfo' (IL size 25; hash 0xd7d18401; FullOpts)
File: /home/runtime/src/coreclr/jit/compiler.h:4008
Image: /home/tmustafin/main_26mar_checkedO0_7f555e0_StubSecretArgRestored68d8aa/coreroot/corerun
Thread 1"corerun" received signal SIGTRAP, Trace/breakpoint trap.
DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:66 /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S: No such file or directory.
(gdb) bt
#0 DBG_DebugBreak () at /home/runtime/src/coreclr/pal/src/arch/riscv64/debugbreak.S:6#1 0x0000003f74b129ae in DebugBreak () at /home/runtime/src/coreclr/pal/src/debug/debug.cpp:406#2 0x0000003f7481c69a in assertAbort (why=0x3f74bc0f17 "lclNum < lvaCount", file=0x3f74bc17b0 "/home/runtime/src/coreclr/jit/compiler.h", line=4008) at /home/runtime/src/coreclr/jit/error.cpp:286#3 0x0000003f7479cf10 in Compiler::lvaGetDesc (this=0x2aaac75278, lclNum=4294967295) at /home/runtime/src/coreclr/jit/compiler.h:4008#4 0x0000003f7479cff2 in Compiler::lvaGetDesc (this=0x2aaac75278, lclVar=0x2aaac832d8) at /home/runtime/src/coreclr/jit/compiler.h:4020#5 0x0000003f749e314c in Compiler::fgMorphLeafLocal (this=0x2aaac75278, lclNode=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:4577#6 0x0000003f749ef45a in Compiler::fgMorphLeaf (this=0x2aaac75278, tree=0x2aaac832d8) at /home/runtime/src/coreclr/jit/morph.cpp:8098#7 0x0000003f749d9134 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac832d8, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12721#8 0x0000003f749da318 in Compiler::fgMorphArgs (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:3171#9 0x0000003f749ebee0 in Compiler::fgMorphCall (this=0x2aaac75278, call=0x2aaac831b0) at /home/runtime/src/coreclr/jit/morph.cpp:7760#10 0x0000003f749d9248 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac831b0, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12746#11 0x0000003f749e6052 in Compiler::fgMorphSmpOp (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0, optAssertionPropDone=0x3fffff9f47) at /home/runtime/src/coreclr/jit/morph.cpp:8950#12 0x0000003f749d9164 in Compiler::fgMorphTree (this=0x2aaac75278, tree=0x2aaac83398, mac=0x0) at /home/runtime/src/coreclr/jit/morph.cpp:12729#13 0x0000003f749a51d8 in Lowering::InsertPInvokeMethodProlog (this=0x2aaac830c8) at /home/runtime/src/coreclr/jit/lower.cpp:5630

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you double check why lvaStubArgumentVar is BAD_VAR_NUM even if we are making use of this register? Does it mean we are adding garbage to this method call? It would be better to pass 0 as the argument in those cases.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC there are several different flavors of how the secret stub argument gets consumed:

  • Through System.StubHelpers.GetStubContext() on all architectures. The runtime passes CORJIT_FLAG_PUBLISH_SECRET_PARAM for functions that may use this intrinsic.
  • As an argument to CORINFO_HELP_INIT_PINVOKE_FRAME outside x86/arm32. The runtime seemingly doesn't always pass CORJIT_FLAG_PUBLISH_SECRET_PARAM in the cases where we need to call this JIT helper, which is confusing to me. Does it mean we can pass nullptr in these cases? Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?
  • Through known stack frame layout on x86/arm32:
    // Given a methodDesc representing an ILStub for a pinvoke call,
    // this method will return the MethodDesc for the actual interop
    // method if the current InlinedCallFrame is inactive.
    PTR_MethodDesc GetActualInteropMethodDesc()
    {
    #if defined(TARGET_X86) || defined(TARGET_ARM)
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
    #elif defined(HOST_64BIT)
    // On 64bit, the actual interop MethodDesc is saved off in a field off the InlinedCrawlFrame
    // which is populated by the JIT. Refer to JIT_InitPInvokeFrame for details.
    returnPTR_MethodDesc(m_StubSecretArg);
    #else
    _ASSERTE(!"NYI - Interop method reporting for this architecture!");
    returnNULL;
    #endif// defined(TARGET_X86) || defined(TARGET_ARM)
    }
    . Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

cc @jkotas, do you know the answer to some of these questions?

@jkotasjkotasApr 4, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is the arg only needed for the pinvoke IL stubs, and not for normal functions with pinvokes in them?

Yes, the arg is only used for IL stubs.

Does it mean we can pass nullptr in these cases?

The value is unused. It does not matter what gets passed in.

Does anything stop us from unifying x86/arm32 with the rest of our targets and always passing it as an argument to the helper?

The JIT is aware of InlinedFrame layout and semantics. It may be better to switch 64-bit to the same scheme as x86/arm32: Get rid of the argument and store it in the InlinedFrame instead - on all platforms and only when it is needed. It will save a memory store for inlined PInvokes on 64-bit platforms.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks! I opened #100662 about unifying this.

@t-mustafin can you please update the fix to be something like

 GenTree* argNode;
if (comp->info.compPublishStubParam)
{
argNode = comp->gtNewLclvNode(comp->lvaStubArgumentVar, TYP_I_IMPL);
}
else
{
argNode = comp->gtNewIconNode(0, TYP_I_IMPL);
}
NewCallArg stubParamArg =
NewCallArg::Primitive(argNode).WellKnown(WellKnownArg::SecretStubParam);
call->gtArgs.PushBack(comp, stubParamArg);

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I opened #100662 about unifying this.

@t-mustafin In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

  • Delete code under the ifdef that you are fixing
  • Delete StubSecretArg here:
    Thread * __stdcall JIT_InitPInvokeFrame(InlinedCallFrame *pFrame, PTR_VOID StubSecretArg)
  • Delete
    #ifdef HOST_64BIT
    // IL stubs fill this field with the incoming secret argument when they erect
    // InlinedCallFrame so we know which interop method was invoked even if the frame
    // is not active at the moment.
    PTR_VOID m_StubSecretArg;
    #endif// HOST_64BIT
  • Enable this for all architectures
    // Important: This code relies on the way JIT lays out frames. Keep it in sync
    // with code:Compiler.lvaAssignFrameOffsets.
    //
    // | ... |
    // +--------------------+
    // | lvaStubArgumentVar | <= filled with EAX in prolog |
    // +--------------------+ |
    // | | |
    // | InlinedCallFrame | |
    // | | <= m_pCrawl.pFrame | to lower addresses
    // +--------------------+ V
    // | ... |
    //
    // Extract the actual MethodDesc to report from the InlinedCallFrame.
    TADDR addr = dac_cast<TADDR>(this) + sizeof(InlinedCallFrame);
    returnPTR_MethodDesc(*PTR_TADDR(addr));
  • Rev the JIT-EE interface GUID: https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/jiteeversionguid.h

@jakobbotsch Does this sound about right?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was hoping we would be able to get rid of the "known stack layout" contract between the VM and JIT as part of this unification. It results in a number of special casing around this particular value in the JIT and has a side effect that we cannot keep the value enregistered.
My thought was:

  • Make InlinedCallFrame::m_StubSecretArg available on all platforms. Make it the JIT's responsibility to store this field in the frame on x64 (instead of doing it from JIT_InitPInvokeFrame). Store the value on x86 as well (it will change a spill to stack at the known stack frame location to a store to the InlinedCallFrame)
  • Remove all special handling in frame layout around lvaStubArgumentVar
  • Eventually enable full enregistration of lvaStubArgumentVar and remove all remaining special handling around it in the JIT. Coincidentally I am doing work in JIT: Rewrite register parameter homing #100572 that should make this simple.

Do you see any potential issues around doing it this way?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Make InlinedCallFrame::m_StubSecretArg available on all platforms

It makes InlinedCallFrame one extra word larger than necessary in most cases. It should not be a big deal, there are other similar inefficiencies in InlinedCallFrame. We should be able to do that to make things cleaner.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

can you please update the fix to be something like

@jakobbotsch thanks, it works. Updated PR.

rv64 asm for IL_STUB_PInvoke(byref) [MinOpts, IL size=98, code size=920, hash=0x5d63dafb]
G_M9476_IG01: ; offs=0x000000, size=0x0068, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, byref, nogc <-- Prolog IGIN0086: 000000 addi sp,sp,-576IN0087: 000004 sd s1,0(sp)IN0088: 000008 sd s2,8(sp)IN0089: 00000C sd s3,16(sp)IN008a: 000010 sd s4,24(sp)IN008b: 000014 sd s5,32(sp)IN008c: 000018 sd s6,40(sp)IN008d: 00001C sd s7,48(sp)IN008e: 000020 sd s8,56(sp)IN008f: 000024 sd s9,64(sp) IN0090: 000028 sd s10,72(sp)IN0091: 00002C sd s11,80(sp)IN0092: 000030 sd ra,88(sp)IN0093: 000034 sd fp,96(sp)IN0094: 000038 addi fp,sp,96IN0095: 00003C sd t2,80(fp)IN0096: 000040 addi s1, fp,104IN0097: 000044 addi t0, zero,22IN0098: 000048 sd zero,8(s1) IN0099: 00004C sd zero,0(s1) IN009a: 000050 addi t0, t0,-1IN009b: 000054 addi s1, s1,16IN009c: 000058 bne t0, zero, pc-16 (-4 instructions)IN009d: 00005C addi s1,sp,576IN009e: 000060 sd s1,472(fp)IN009f: 000064 sd a0,464(fp) ;; size=104 bbWeight=1 PerfScore 0.00G_M9476_IG02: ; offs=0x000068, size=0x006C, bbWeight=1, gcrefRegs=0000 {}, byrefRegs=0000 {}, BB09 [0008], BB01 [0000], byrefIN0001: 000068 addi a0, fp,112IN0002: 00006C addi a1, zero,-51IN0003: 000070 addi a2, zero,296IN0004: 000074 lui t2,0000078 addi t2, t2,63 00007C slli t2, t2,11000080 addi t2, t2,1401000084 slli t2, t2,11000088 addi t2, t2,1785 00008C slli t2, t2,10000090 jalr ra,748(t2) // CORINFO_HELP_NATIVE_MEMSETIN0005: 000094 addi a0, fp,16IN0006: 000098 ld a1,80(fp)IN0007: 00009C lui a2,521704IN0008: 0000A0 addiw a2, a2,-1570IN0009: 0000A4 slli a2, a2,7IN000a: 0000A8 addi a2, a2,58IN000b: 0000AC jalr ra,0(a2) // CORINFO_HELP_INIT_PINVOKE_FRAME

In case you would like to fix #100662 and clean this up in this PR, here is the set of places:

@jkotas Thanks for detailed instruction, unfortunately I don't have plan to fix #100662 for near future.

@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from ae76e4a to f37d25eCompareMarch 28, 2024 23:34
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch 2 times, most recently from 1c7c97f to 232ee6fCompareMarch 30, 2024 01:29
@t-mustafin
t-mustafin marked this pull request as ready for review March 30, 2024 01:45
Issue dotnet#100301
Co-authored-by: Jakob Botsch Nielsen <Jakob.botsch.nielsen@gmail.com>
@t-mustafin
t-mustafinforce-pushed the StubSecretArg_restore_from_stack branch from 232ee6f to 321559aCompareApril 9, 2024 18:17

@jakobbotschjakobbotsch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@gbalykov

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

@jakobbotsch

Copy link
Copy Markdown
Member

@jakobbotsch thanks! By the way, we first observed this on .net 8, does this need to be backported there?

I don't think so. The CORINFO_HELP_MEMSET call that trashes the register in #100301 is coming from poisoning that is inserted by the JIT for address taken locals in unoptimized codegen. This means that the demonstrated case requires an IL stub compiled without optimizations. However, I believe that IL stubs under normal operations will always be compiled with optimizations enabled, so the call will not be there.

In theory nothing stops the register from being trashed through other means during prolog codegen, but this theoretical issue exists in RyuJIT since forever and generally we wouldn't try to backport a fix for a theoretical issue like that.

@jkotas

Copy link
Copy Markdown
Member

I believe that IL stubs under normal operations will always be compiled with optimizations enabled

I do not think that it is the case. The IL stubs have the same optimization settings as the module that they are attached to.

I agree that this is a theoretical issue in the shipping runtime version and it does not meet the bar for backport without a repro.

matouskozak pushed a commit to matouskozak/runtime that referenced this pull request Apr 30, 2024
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 16, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@t-mustafin@gbalykov@jakobbotsch@jkotas@clamp03@ashaurtaev@sirntar@Bajtazar