Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1e23a06

Browse files
authored
Consider that retbuf arg can point to GC heap in fgCreateCallDispatcherAndGetResult() (dotnet#39815)
Caller return buffer argument can point to GC heap while DispatchTailCalls expects the return value argument to point to the stack. We should use a temporary stack allocated return buffer to hold the value during the dispatcher call and copy the value back to the caller return buffer after that.
1 parent 65c9be6 commit 1e23a06

File tree

15 files changed

+239
-35
lines changed

15 files changed

+239
-35
lines changed

src/coreclr/src/jit/morph.cpp

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7901,21 +7901,54 @@ GenTree* Compiler::fgCreateCallDispatcherAndGetResult(GenTreeCall* orig
79017901

79027902
// Add return value arg.
79037903
GenTree* retValArg;
7904-
GenTree* retVal = nullptr;
7905-
unsigned int newRetLcl = BAD_VAR_NUM;
7904+
GenTree* retVal = nullptr;
7905+
unsigned int newRetLcl = BAD_VAR_NUM;
7906+
GenTree* copyToRetBufNode = nullptr;
79067907

7907-
// Use existing retbuf if there is one.
79087908
if (origCall->HasRetBufArg())
79097909
{
79107910
JITDUMP("Transferring retbuf\n");
79117911
GenTree* retBufArg = origCall->gtCallArgs->GetNode();
7912-
assert((info.compRetBuffArg != BAD_VAR_NUM) && retBufArg->OperIsLocal() &&
7913-
(retBufArg->AsLclVarCommon()->GetLclNum() == info.compRetBuffArg));
79147912

7915-
retValArg = retBufArg;
7913+
assert(info.compRetBuffArg != BAD_VAR_NUM);
7914+
assert(retBufArg->OperIsLocal());
7915+
assert(retBufArg->AsLclVarCommon()->GetLclNum() == info.compRetBuffArg);
7916+
7917+
if (info.compRetBuffDefStack)
7918+
{
7919+
// Use existing retbuf.
7920+
retValArg = retBufArg;
7921+
}
7922+
else
7923+
{
7924+
// Caller return buffer argument retBufArg can point to GC heap while the dispatcher expects
7925+
// the return value argument retValArg to point to the stack.
7926+
// We use a temporary stack allocated return buffer to hold the value during the dispatcher call
7927+
// and copy the value back to the caller return buffer after that.
7928+
unsigned int tmpRetBufNum = lvaGrabTemp(true DEBUGARG("substitute local for return buffer"));
7929+
7930+
constexpr bool unsafeValueClsCheck = false;
7931+
lvaSetStruct(tmpRetBufNum, origCall->gtRetClsHnd, unsafeValueClsCheck);
7932+
lvaSetVarAddrExposed(tmpRetBufNum);
7933+
7934+
var_types tmpRetBufType = lvaGetDesc(tmpRetBufNum)->TypeGet();
7935+
7936+
retValArg = gtNewOperNode(GT_ADDR, TYP_I_IMPL, gtNewLclvNode(tmpRetBufNum, tmpRetBufType));
7937+
7938+
var_types callerRetBufType = lvaGetDesc(info.compRetBuffArg)->TypeGet();
7939+
7940+
GenTree* dstAddr = gtNewLclvNode(info.compRetBuffArg, callerRetBufType);
7941+
GenTree* dst = gtNewObjNode(info.compMethodInfo->args.retTypeClass, dstAddr);
7942+
GenTree* src = gtNewLclvNode(tmpRetBufNum, tmpRetBufType);
7943+
7944+
constexpr bool isVolatile = false;
7945+
constexpr bool isCopyBlock = true;
7946+
copyToRetBufNode = gtNewBlkOpNode(dst, src, isVolatile, isCopyBlock);
7947+
}
7948+
79167949
if (origCall->gtType != TYP_VOID)
79177950
{
7918-
retVal = gtClone(retValArg);
7951+
retVal = gtClone(retBufArg);
79197952
}
79207953
}
79217954
else if (origCall->gtType != TYP_VOID)
@@ -7969,22 +8002,30 @@ GenTree* Compiler::fgCreateCallDispatcherAndGetResult(GenTreeCall* orig
79698002
GenTree* retAddrSlot = gtNewOperNode(GT_ADDR, TYP_I_IMPL, gtNewLclvNode(lvaRetAddrVar, TYP_I_IMPL));
79708003
callDispatcherNode->gtCallArgs = gtPrependNewCallArg(retAddrSlot, callDispatcherNode->gtCallArgs);
79718004

8005+
GenTree* finalTree = callDispatcherNode;
8006+
8007+
if (copyToRetBufNode != nullptr)
8008+
{
8009+
finalTree = gtNewOperNode(GT_COMMA, TYP_VOID, callDispatcherNode, copyToRetBufNode);
8010+
}
8011+
79728012
if (origCall->gtType == TYP_VOID)
79738013
{
7974-
return callDispatcherNode;
8014+
return finalTree;
79758015
}
79768016

79778017
assert(retVal != nullptr);
7978-
GenTree* comma = gtNewOperNode(GT_COMMA, origCall->TypeGet(), callDispatcherNode, retVal);
8018+
finalTree = gtNewOperNode(GT_COMMA, origCall->TypeGet(), finalTree, retVal);
8019+
79798020
// The JIT seems to want to CSE this comma and messes up multi-reg ret
79808021
// values in the process. Just avoid CSE'ing this tree entirely in that
79818022
// case.
79828023
if (origCall->HasMultiRegRetVal())
79838024
{
7984-
comma->gtFlags |= GTF_DONT_CSE;
8025+
finalTree->gtFlags |= GTF_DONT_CSE;
79858026
}
79868027

7987-
return comma;
8028+
return finalTree;
79888029
}
79898030

79908031
//------------------------------------------------------------------------

src/tests/JIT/HardwareIntrinsics/General/Vector128/Vector128_r.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39576, https://github.com/dotnet/runtime/issues/39579 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>

src/tests/JIT/HardwareIntrinsics/General/Vector128/Vector128_ro.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39576, https://github.com/dotnet/runtime/issues/39579 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>

src/tests/JIT/HardwareIntrinsics/General/Vector128_1/Vector128_1_r.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39576, https://github.com/dotnet/runtime/issues/39579 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>

src/tests/JIT/HardwareIntrinsics/General/Vector128_1/Vector128_1_ro.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39576, https://github.com/dotnet/runtime/issues/39579 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>

src/tests/JIT/HardwareIntrinsics/General/Vector256/Vector256_r.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39576, https://github.com/dotnet/runtime/issues/39579 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>

src/tests/JIT/HardwareIntrinsics/General/Vector256/Vector256_ro.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39576, https://github.com/dotnet/runtime/issues/39579 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>

src/tests/JIT/HardwareIntrinsics/General/Vector256_1/Vector256_1_r.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39581 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>

src/tests/JIT/HardwareIntrinsics/General/Vector256_1/Vector256_1_ro.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39581 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>

src/tests/JIT/HardwareIntrinsics/General/Vector64/Vector64_r.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39576, https://github.com/dotnet/runtime/issues/39579 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>

src/tests/JIT/HardwareIntrinsics/General/Vector64/Vector64_ro.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39576, https://github.com/dotnet/runtime/issues/39579 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>

src/tests/JIT/HardwareIntrinsics/General/Vector64_1/Vector64_1_r.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39576, https://github.com/dotnet/runtime/issues/39579 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>

src/tests/JIT/HardwareIntrinsics/General/Vector64_1/Vector64_1_ro.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<!-- Disabled under GCStress due to https://github.com/dotnet/runtime/issues/39576, https://github.com/dotnet/runtime/issues/39579 -->
6-
<GCStressIncompatible>true</GCStressIncompatible>
75
</PropertyGroup>
86
<PropertyGroup>
97
<DebugType>Embedded</DebugType>
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
//
4+
// The test exposes the issue when:
5+
//
6+
// 1) methods Callee and Caller have the following structure:
7+
//
8+
// S Callee() { produce and return an instance of S }
9+
//
10+
// S Caller() { return Callee(); }
11+
//
12+
// 2) S is a value type with non-GC fields and has such size that it
13+
// must be passed via return buffer;
14+
//
15+
// 3) call to Callee is transformed to tail call via helper:
16+
//
17+
// S result;
18+
// DispatchTailCalls(&IL_STUB_CallTailCallTarget, (IntPtr)&result, _AddressOfReturnAddress());
19+
// return result;
20+
//
21+
// 4) Caller is invoked via Reflection.
22+
//
23+
// During morph phase the JIT discovers that both Caller and Callee have the return buffer arguments and
24+
// falsely assumes that the value can be directly passed from Caller to Callee.
25+
// Here is a problem: DispatchTailCalls helper expects the RetValue argument (in this case, return buffer argument)
26+
// to always point to the stack. However, during a reflection call the return buffer for S value type can be placed on the GC heap (see condition 2 above).
27+
// As a consequence, when GC is called at DispatchTailCalls the object corresponding to the return value buffer can be moved,
28+
// but the pointers passed to DispatchTailCalls will not be updated that leads to Callee using the non-updated location when writing its return value.
29+
//
30+
// Note: you will find below that Caller uses localloc - this is done in order to prevent a call to Caller to be transformed into fast tail call.
31+
// Note: COMPlus_GCStress=3 or COMPlus_GCStress=C are required in order to reliably expose this issue.
32+
33+
.assembly extern System.Runtime
34+
{
35+
}
36+
37+
.assembly Runtime_39581
38+
{
39+
}
40+
41+
.class private sequential ansi sealed beforefieldinit int32x8
42+
extends [System.Runtime]System.ValueType
43+
{
44+
.field public int32 a
45+
.field public int32 b
46+
.field public int32 c
47+
.field public int32 d
48+
.field public int32 e
49+
.field public int32 f
50+
.field public int32 g
51+
.field public int32 h
52+
}
53+
54+
.class private auto ansi beforefieldinit Runtime_39581.Program
55+
extends [System.Runtime]System.Object
56+
{
57+
.method private hidebysig static valuetype int32x8 Callee(uint8* arg0) cil managed noinlining
58+
{
59+
.maxstack 2
60+
.locals init (valuetype int32x8 V_0)
61+
IL_0000: ldloca.s V_0
62+
IL_0002: initobj int32x8
63+
IL_0008: ldloca.s V_0
64+
IL_000a: ldc.i4.0
65+
IL_000b: stfld int32 int32x8::a
66+
IL_0010: ldloca.s V_0
67+
IL_0012: ldc.i4.1
68+
IL_0013: stfld int32 int32x8::b
69+
IL_0018: ldloca.s V_0
70+
IL_001a: ldc.i4.2
71+
IL_001b: stfld int32 int32x8::c
72+
IL_0020: ldloca.s V_0
73+
IL_0022: ldc.i4.3
74+
IL_0023: stfld int32 int32x8::d
75+
IL_0028: ldloca.s V_0
76+
IL_002a: ldc.i4.4
77+
IL_002b: stfld int32 int32x8::e
78+
IL_0030: ldloca.s V_0
79+
IL_0032: ldc.i4.5
80+
IL_0033: stfld int32 int32x8::f
81+
IL_0038: ldloca.s V_0
82+
IL_003a: ldc.i4.6
83+
IL_003b: stfld int32 int32x8::g
84+
IL_0040: ldloca.s V_0
85+
IL_0042: ldc.i4.7
86+
IL_0043: stfld int32 int32x8::h
87+
IL_0048: ldloc.0
88+
IL_0049: ret
89+
}
90+
91+
.method public hidebysig static valuetype int32x8 Caller(int32 arg0) cil managed noinlining
92+
{
93+
.maxstack 1
94+
IL_0000: ldarg.0
95+
IL_0001: conv.u
96+
IL_0002: localloc
97+
IL_0004: tail.
98+
IL_0006: call valuetype int32x8 Runtime_39581.Program::Callee(uint8*)
99+
IL_000b: ret
100+
}
101+
102+
.method private hidebysig static int32 Main(string[] args) cil managed
103+
{
104+
.entrypoint
105+
.maxstack 6
106+
.locals init (valuetype int32x8 V_0)
107+
IL_0000: ldtoken Runtime_39581.Program
108+
IL_0005: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
109+
IL_000a: ldstr "Caller"
110+
IL_000f: ldc.i4.1
111+
IL_0010: newarr [System.Runtime]System.Type
112+
IL_0015: dup
113+
IL_0016: ldc.i4.0
114+
IL_0017: ldtoken [System.Runtime]System.Int32
115+
IL_001c: call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
116+
IL_0021: stelem.ref
117+
IL_0022: call instance class [System.Runtime]System.Reflection.MethodInfo [System.Runtime]System.Type::GetMethod(string, class [System.Runtime]System.Type[])
118+
IL_0027: ldnull
119+
IL_0028: ldc.i4.1
120+
IL_0029: newarr [System.Runtime]System.Object
121+
IL_002e: dup
122+
IL_002f: ldc.i4.0
123+
IL_0030: ldc.i4.1
124+
IL_0031: box [System.Runtime]System.Int32
125+
IL_0036: stelem.ref
126+
IL_0037: callvirt instance object [System.Runtime]System.Reflection.MethodBase::Invoke(object, object[])
127+
IL_003c: unbox.any int32x8
128+
IL_0041: stloc.0
129+
IL_0042: ldloc.0
130+
IL_0043: ldfld int32 int32x8::a
131+
IL_0048: brtrue.s IL_008c
132+
133+
IL_004a: ldloc.0
134+
IL_004b: ldfld int32 int32x8::b
135+
IL_0050: ldc.i4.1
136+
IL_0051: bne.un.s IL_008c
137+
138+
IL_0053: ldloc.0
139+
IL_0054: ldfld int32 int32x8::c
140+
IL_0059: ldc.i4.2
141+
IL_005a: bne.un.s IL_008c
142+
143+
IL_005c: ldloc.0
144+
IL_005d: ldfld int32 int32x8::d
145+
IL_0062: ldc.i4.3
146+
IL_0063: bne.un.s IL_008c
147+
148+
IL_0065: ldloc.0
149+
IL_0066: ldfld int32 int32x8::e
150+
IL_006b: ldc.i4.4
151+
IL_006c: bne.un.s IL_008c
152+
153+
IL_006e: ldloc.0
154+
IL_006f: ldfld int32 int32x8::f
155+
IL_0074: ldc.i4.5
156+
IL_0075: bne.un.s IL_008c
157+
158+
IL_0077: ldloc.0
159+
IL_0078: ldfld int32 int32x8::g
160+
IL_007d: ldc.i4.6
161+
IL_007e: bne.un.s IL_008c
162+
163+
IL_0080: ldloc.0
164+
IL_0081: ldfld int32 int32x8::h
165+
IL_0086: ldc.i4.7
166+
IL_0087: bne.un.s IL_008c
167+
168+
IL_0089: ldc.i4.s 100
169+
IL_008b: ret
170+
171+
IL_008c: ldc.i4.0
172+
IL_008d: ret
173+
}
174+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk.IL">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
</PropertyGroup>
5+
<PropertyGroup>
6+
<DebugType>None</DebugType>
7+
<Optimize>True</Optimize>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9+
</PropertyGroup>
10+
<ItemGroup>
11+
<Compile Include="$(MSBuildProjectName).il" />
12+
</ItemGroup>
13+
</Project>

0 commit comments

Comments
 (0)