Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -5616,7 +5616,7 @@ class Compiler
GenTreeFieldList* fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl);
GenTreeCall* fgMorphArgs(GenTreeCall* call);

void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass);
void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg);

GenTree* fgMorphLocalVar(GenTree* tree, bool forceRemorph);

Expand Down
61 changes: 29 additions & 32 deletions src/coreclr/jit/morph.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3112,8 +3112,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

CORINFO_CLASS_HANDLE copyBlkClass = NO_CLASS_HANDLE;

// TODO-ARGS: Review this, is it really necessary to treat them specially here?
if (call->gtArgs.IsNonStandard(this, call, &arg) && arg.AbiInfo.IsPassedInRegisters())
{
Expand All@@ -3138,41 +3136,40 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// Struct arguments may be morphed into a node that is not a struct type.
// In such case the CallArgABIInformation keeps track of whether the original node (before morphing)
// was a struct and the struct classification.
bool isStructArg = arg.AbiInfo.IsStruct;
bool isStructArg = arg.AbiInfo.IsStruct;
GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
bool makeOutArgCopy = false;

GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
if (isStructArg && varTypeIsStruct(argObj) && !argObj->OperIs(GT_ASG, GT_MKREFANY, GT_FIELD_LIST))
{
CORINFO_CLASS_HANDLE objClass = gtGetStructHandle(argObj);
unsigned originalSize;
unsigned originalSize;
if (argObj->TypeGet() == TYP_STRUCT)
{
if (argObj->OperIs(GT_OBJ))
{
// Get the size off the OBJ node.
originalSize = argObj->AsObj()->GetLayout()->GetSize();
assert(originalSize == info.compCompHnd->getClassSize(objClass));
originalSize = argObj->AsObj()->Size();
}
else
{
// We have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
assert(argObj->OperIs(GT_LCL_VAR));
originalSize = lvaGetDesc(argObj->AsLclVarCommon())->lvExactSize;
// Must be LCL_VAR: we have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
originalSize = lvaGetDesc(argObj->AsLclVar())->lvExactSize;
}
}
else
{
originalSize = genTypeSize(argx);
assert(originalSize == info.compCompHnd->getClassSize(objClass));
}

assert(originalSize == info.compCompHnd->getClassSize(arg.GetSignatureClassHandle()));

unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE);
var_types structBaseType = arg.AbiInfo.ArgType;

// First, handle the case where the argument is passed by reference.
if (arg.AbiInfo.PassedByRef)
{
assert(arg.AbiInfo.ByteSize == TARGET_POINTER_SIZE);
copyBlkClass = objClass;
makeOutArgCopy = true;
#ifdef UNIX_AMD64_ABI
assert(!"Structs are not passed by reference on x64/ux");
#endif // UNIX_AMD64_ABI
Expand DownExpand Up@@ -3219,7 +3216,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// On Windows structs are always copied and passed by reference (handled above) unless they are
// passed by value in a single register.
assert(arg.AbiInfo.GetStackSlotsNumber() == 1);
copyBlkClass = objClass;
makeOutArgCopy = true;
#else // UNIX_AMD64_ABI
// On Unix, structs are always passed by value.
// We only need a copy if we have one of the following:
Expand All@@ -3233,7 +3230,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
if (passingSize != structSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (lclVar == nullptr)
Expand All@@ -3242,15 +3239,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
assert(argObj->TypeGet() != TYP_STRUCT);
if (arg.AbiInfo.NumRegs > 1)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
}
#endif // UNIX_AMD64_ABI
#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64)
if ((passingSize != structSize) && (lclVar == nullptr))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif

Expand All@@ -3260,12 +3257,12 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
(lvaGetPromotionType(lclVar->AsLclVarCommon()->GetLclNum()) == PROMOTION_TYPE_INDEPENDENT)) ||
((argObj->OperIs(GT_OBJ)) && (passingSize != structSize)))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}

if (structSize < TARGET_POINTER_SIZE)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif // TARGET_ARM
}
Expand DownExpand Up@@ -3328,7 +3325,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()));
assert(copyBlkClass == NO_CLASS_HANDLE);
assert(!makeOutArgCopy);
}
else
{
Expand All@@ -3342,7 +3339,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
// The struct fits into a single register, but it has been promoted into its
// constituent fields, and so we have to re-assemble it
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (genTypeSize(varDsc->TypeGet()) != genTypeSize(structBaseType))
Expand All@@ -3359,7 +3356,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()) ||
((copyBlkClass != NO_CLASS_HANDLE) && varTypeIsEnregisterable(structBaseType)));
(makeOutArgCopy && varTypeIsEnregisterable(structBaseType)));
}

#if !defined(UNIX_AMD64_ABI) && !defined(TARGET_ARMARCH) && !defined(TARGET_LOONGARCH64)
Expand All@@ -3377,15 +3374,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// the obj reading memory past the end of the valuetype
if (roundupSize > originalSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;

// There are a few special cases where we can omit using a CopyBlk
// where we normally would need to use one.

if (argObj->OperIs(GT_OBJ) &&
argObj->AsObj()->gtGetOp1()->IsLocalAddrExpr() != nullptr) // Is the source a LclVar?
{
copyBlkClass = NO_CLASS_HANDLE;
makeOutArgCopy = false;
}
}
}
Expand All@@ -3394,9 +3391,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

if (copyBlkClass != NO_CLASS_HANDLE)
if (makeOutArgCopy)
{
fgMakeOutgoingStructArgCopy(call, &arg, copyBlkClass);
fgMakeOutgoingStructArgCopy(call, &arg);
}

if (argx->gtOper == GT_MKREFANY)
Expand DownExpand Up@@ -4063,14 +4060,13 @@ GenTreeFieldList* Compiler::fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl)
// Arguments:
// call - call being processed
// arg - arg for the call
// copyBlkClass - class handle for the struct
//
// The arg is updated if necessary with the copy.
//
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass)
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg)
{
GenTree* argx = arg->GetEarlyNode();
noway_assert(argx->gtOper != GT_MKREFANY);
noway_assert(!argx->OperIs(GT_MKREFANY));

// If we're optimizing, see if we can avoid making a copy.
//
Expand DownExpand Up@@ -4121,8 +4117,9 @@ void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORI
fgOutgoingArgTemps = hashBv::Create(this);
}

unsigned tmp = 0;
bool found = false;
CORINFO_CLASS_HANDLE copyBlkClass = arg->GetSignatureClassHandle();
unsigned tmp = 0;
bool found = false;

// Attempt to find a local we have already used for an outgoing struct and reuse it.
// We do not reuse within a statement.
Expand Down
39 changes: 39 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_69965/Runtime_69965.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

public unsafe class Runtime_69965
{
public static int Main()
{
const int Value = 10;
var vtor = Vector128.Create(Value, Value, Value, Value);
var vtors = new StructWithOverlappedVtor128[] { new StructWithOverlappedVtor128 { Vtor = vtor } };

return Problem(vtors) != Value ? 101 : 100;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int Problem(StructWithOverlappedVtor128[] a)
{
static Vector128<int> Tunnel(StructWithOverlappedVtor128[] a) => a[0].Vtor;

return CallForVtor(Tunnel(a));
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int CallForVtor(Vector128<int> value) => value.GetElement(0);

[StructLayout(LayoutKind.Explicit)]
struct StructWithOverlappedVtor128
{
[FieldOffset(16)]
public Vector128<int> Vtor;
[FieldOffset(16)]
public Vector128<uint> AnotherVtor;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Optimize>True</Optimize>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Use the signature's struct handle for making outgoing arg copies by SingleAccretion · Pull Request #69971 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -5616,7 +5616,7 @@ class Compiler
GenTreeFieldList* fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl);
GenTreeCall* fgMorphArgs(GenTreeCall* call);

void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass);
void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg);

GenTree* fgMorphLocalVar(GenTree* tree, bool forceRemorph);

Expand Down
61 changes: 29 additions & 32 deletions src/coreclr/jit/morph.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3112,8 +3112,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

CORINFO_CLASS_HANDLE copyBlkClass = NO_CLASS_HANDLE;

// TODO-ARGS: Review this, is it really necessary to treat them specially here?
if (call->gtArgs.IsNonStandard(this, call, &arg) && arg.AbiInfo.IsPassedInRegisters())
{
Expand All@@ -3138,41 +3136,40 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// Struct arguments may be morphed into a node that is not a struct type.
// In such case the CallArgABIInformation keeps track of whether the original node (before morphing)
// was a struct and the struct classification.
bool isStructArg = arg.AbiInfo.IsStruct;
bool isStructArg = arg.AbiInfo.IsStruct;
GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
bool makeOutArgCopy = false;

GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
if (isStructArg && varTypeIsStruct(argObj) && !argObj->OperIs(GT_ASG, GT_MKREFANY, GT_FIELD_LIST))
{
CORINFO_CLASS_HANDLE objClass = gtGetStructHandle(argObj);
unsigned originalSize;
unsigned originalSize;
if (argObj->TypeGet() == TYP_STRUCT)
{
if (argObj->OperIs(GT_OBJ))
{
// Get the size off the OBJ node.
originalSize = argObj->AsObj()->GetLayout()->GetSize();
assert(originalSize == info.compCompHnd->getClassSize(objClass));
originalSize = argObj->AsObj()->Size();
}
else
{
// We have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
assert(argObj->OperIs(GT_LCL_VAR));
originalSize = lvaGetDesc(argObj->AsLclVarCommon())->lvExactSize;
// Must be LCL_VAR: we have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
originalSize = lvaGetDesc(argObj->AsLclVar())->lvExactSize;
}
}
else
{
originalSize = genTypeSize(argx);
assert(originalSize == info.compCompHnd->getClassSize(objClass));
}

assert(originalSize == info.compCompHnd->getClassSize(arg.GetSignatureClassHandle()));

unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE);
var_types structBaseType = arg.AbiInfo.ArgType;

// First, handle the case where the argument is passed by reference.
if (arg.AbiInfo.PassedByRef)
{
assert(arg.AbiInfo.ByteSize == TARGET_POINTER_SIZE);
copyBlkClass = objClass;
makeOutArgCopy = true;
#ifdef UNIX_AMD64_ABI
assert(!"Structs are not passed by reference on x64/ux");
#endif // UNIX_AMD64_ABI
Expand DownExpand Up@@ -3219,7 +3216,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// On Windows structs are always copied and passed by reference (handled above) unless they are
// passed by value in a single register.
assert(arg.AbiInfo.GetStackSlotsNumber() == 1);
copyBlkClass = objClass;
makeOutArgCopy = true;
#else // UNIX_AMD64_ABI
// On Unix, structs are always passed by value.
// We only need a copy if we have one of the following:
Expand All@@ -3233,7 +3230,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
if (passingSize != structSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (lclVar == nullptr)
Expand All@@ -3242,15 +3239,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
assert(argObj->TypeGet() != TYP_STRUCT);
if (arg.AbiInfo.NumRegs > 1)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
}
#endif // UNIX_AMD64_ABI
#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64)
if ((passingSize != structSize) && (lclVar == nullptr))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif

Expand All@@ -3260,12 +3257,12 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
(lvaGetPromotionType(lclVar->AsLclVarCommon()->GetLclNum()) == PROMOTION_TYPE_INDEPENDENT)) ||
((argObj->OperIs(GT_OBJ)) && (passingSize != structSize)))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}

if (structSize < TARGET_POINTER_SIZE)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif // TARGET_ARM
}
Expand DownExpand Up@@ -3328,7 +3325,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()));
assert(copyBlkClass == NO_CLASS_HANDLE);
assert(!makeOutArgCopy);
}
else
{
Expand All@@ -3342,7 +3339,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
// The struct fits into a single register, but it has been promoted into its
// constituent fields, and so we have to re-assemble it
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (genTypeSize(varDsc->TypeGet()) != genTypeSize(structBaseType))
Expand All@@ -3359,7 +3356,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()) ||
((copyBlkClass != NO_CLASS_HANDLE) && varTypeIsEnregisterable(structBaseType)));
(makeOutArgCopy && varTypeIsEnregisterable(structBaseType)));
}

#if !defined(UNIX_AMD64_ABI) && !defined(TARGET_ARMARCH) && !defined(TARGET_LOONGARCH64)
Expand All@@ -3377,15 +3374,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// the obj reading memory past the end of the valuetype
if (roundupSize > originalSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;

// There are a few special cases where we can omit using a CopyBlk
// where we normally would need to use one.

if (argObj->OperIs(GT_OBJ) &&
argObj->AsObj()->gtGetOp1()->IsLocalAddrExpr() != nullptr) // Is the source a LclVar?
{
copyBlkClass = NO_CLASS_HANDLE;
makeOutArgCopy = false;
}
}
}
Expand All@@ -3394,9 +3391,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

if (copyBlkClass != NO_CLASS_HANDLE)
if (makeOutArgCopy)
{
fgMakeOutgoingStructArgCopy(call, &arg, copyBlkClass);
fgMakeOutgoingStructArgCopy(call, &arg);
}

if (argx->gtOper == GT_MKREFANY)
Expand DownExpand Up@@ -4063,14 +4060,13 @@ GenTreeFieldList* Compiler::fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl)
// Arguments:
// call - call being processed
// arg - arg for the call
// copyBlkClass - class handle for the struct
//
// The arg is updated if necessary with the copy.
//
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass)
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg)
{
GenTree* argx = arg->GetEarlyNode();
noway_assert(argx->gtOper != GT_MKREFANY);
noway_assert(!argx->OperIs(GT_MKREFANY));

// If we're optimizing, see if we can avoid making a copy.
//
Expand DownExpand Up@@ -4121,8 +4117,9 @@ void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORI
fgOutgoingArgTemps = hashBv::Create(this);
}

unsigned tmp = 0;
bool found = false;
CORINFO_CLASS_HANDLE copyBlkClass = arg->GetSignatureClassHandle();
unsigned tmp = 0;
bool found = false;

// Attempt to find a local we have already used for an outgoing struct and reuse it.
// We do not reuse within a statement.
Expand Down
39 changes: 39 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_69965/Runtime_69965.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

public unsafe class Runtime_69965
{
public static int Main()
{
const int Value = 10;
var vtor = Vector128.Create(Value, Value, Value, Value);
var vtors = new StructWithOverlappedVtor128[] { new StructWithOverlappedVtor128 { Vtor = vtor } };

return Problem(vtors) != Value ? 101 : 100;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int Problem(StructWithOverlappedVtor128[] a)
{
static Vector128<int> Tunnel(StructWithOverlappedVtor128[] a) => a[0].Vtor;

return CallForVtor(Tunnel(a));
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int CallForVtor(Vector128<int> value) => value.GetElement(0);

[StructLayout(LayoutKind.Explicit)]
struct StructWithOverlappedVtor128
{
[FieldOffset(16)]
public Vector128<int> Vtor;
[FieldOffset(16)]
public Vector128<uint> AnotherVtor;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Optimize>True</Optimize>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Use the signature's struct handle for making outgoing arg copies by SingleAccretion · Pull Request #69971 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -5616,7 +5616,7 @@ class Compiler
GenTreeFieldList* fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl);
GenTreeCall* fgMorphArgs(GenTreeCall* call);

void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass);
void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg);

GenTree* fgMorphLocalVar(GenTree* tree, bool forceRemorph);

Expand Down
61 changes: 29 additions & 32 deletions src/coreclr/jit/morph.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3112,8 +3112,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

CORINFO_CLASS_HANDLE copyBlkClass = NO_CLASS_HANDLE;

// TODO-ARGS: Review this, is it really necessary to treat them specially here?
if (call->gtArgs.IsNonStandard(this, call, &arg) && arg.AbiInfo.IsPassedInRegisters())
{
Expand All@@ -3138,41 +3136,40 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// Struct arguments may be morphed into a node that is not a struct type.
// In such case the CallArgABIInformation keeps track of whether the original node (before morphing)
// was a struct and the struct classification.
bool isStructArg = arg.AbiInfo.IsStruct;
bool isStructArg = arg.AbiInfo.IsStruct;
GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
bool makeOutArgCopy = false;

GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
if (isStructArg && varTypeIsStruct(argObj) && !argObj->OperIs(GT_ASG, GT_MKREFANY, GT_FIELD_LIST))
{
CORINFO_CLASS_HANDLE objClass = gtGetStructHandle(argObj);
unsigned originalSize;
unsigned originalSize;
if (argObj->TypeGet() == TYP_STRUCT)
{
if (argObj->OperIs(GT_OBJ))
{
// Get the size off the OBJ node.
originalSize = argObj->AsObj()->GetLayout()->GetSize();
assert(originalSize == info.compCompHnd->getClassSize(objClass));
originalSize = argObj->AsObj()->Size();
}
else
{
// We have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
assert(argObj->OperIs(GT_LCL_VAR));
originalSize = lvaGetDesc(argObj->AsLclVarCommon())->lvExactSize;
// Must be LCL_VAR: we have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
originalSize = lvaGetDesc(argObj->AsLclVar())->lvExactSize;
}
}
else
{
originalSize = genTypeSize(argx);
assert(originalSize == info.compCompHnd->getClassSize(objClass));
}

assert(originalSize == info.compCompHnd->getClassSize(arg.GetSignatureClassHandle()));

unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE);
var_types structBaseType = arg.AbiInfo.ArgType;

// First, handle the case where the argument is passed by reference.
if (arg.AbiInfo.PassedByRef)
{
assert(arg.AbiInfo.ByteSize == TARGET_POINTER_SIZE);
copyBlkClass = objClass;
makeOutArgCopy = true;
#ifdef UNIX_AMD64_ABI
assert(!"Structs are not passed by reference on x64/ux");
#endif // UNIX_AMD64_ABI
Expand DownExpand Up@@ -3219,7 +3216,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// On Windows structs are always copied and passed by reference (handled above) unless they are
// passed by value in a single register.
assert(arg.AbiInfo.GetStackSlotsNumber() == 1);
copyBlkClass = objClass;
makeOutArgCopy = true;
#else // UNIX_AMD64_ABI
// On Unix, structs are always passed by value.
// We only need a copy if we have one of the following:
Expand All@@ -3233,7 +3230,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
if (passingSize != structSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (lclVar == nullptr)
Expand All@@ -3242,15 +3239,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
assert(argObj->TypeGet() != TYP_STRUCT);
if (arg.AbiInfo.NumRegs > 1)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
}
#endif // UNIX_AMD64_ABI
#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64)
if ((passingSize != structSize) && (lclVar == nullptr))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif

Expand All@@ -3260,12 +3257,12 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
(lvaGetPromotionType(lclVar->AsLclVarCommon()->GetLclNum()) == PROMOTION_TYPE_INDEPENDENT)) ||
((argObj->OperIs(GT_OBJ)) && (passingSize != structSize)))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}

if (structSize < TARGET_POINTER_SIZE)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif // TARGET_ARM
}
Expand DownExpand Up@@ -3328,7 +3325,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()));
assert(copyBlkClass == NO_CLASS_HANDLE);
assert(!makeOutArgCopy);
}
else
{
Expand All@@ -3342,7 +3339,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
// The struct fits into a single register, but it has been promoted into its
// constituent fields, and so we have to re-assemble it
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (genTypeSize(varDsc->TypeGet()) != genTypeSize(structBaseType))
Expand All@@ -3359,7 +3356,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()) ||
((copyBlkClass != NO_CLASS_HANDLE) && varTypeIsEnregisterable(structBaseType)));
(makeOutArgCopy && varTypeIsEnregisterable(structBaseType)));
}

#if !defined(UNIX_AMD64_ABI) && !defined(TARGET_ARMARCH) && !defined(TARGET_LOONGARCH64)
Expand All@@ -3377,15 +3374,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// the obj reading memory past the end of the valuetype
if (roundupSize > originalSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;

// There are a few special cases where we can omit using a CopyBlk
// where we normally would need to use one.

if (argObj->OperIs(GT_OBJ) &&
argObj->AsObj()->gtGetOp1()->IsLocalAddrExpr() != nullptr) // Is the source a LclVar?
{
copyBlkClass = NO_CLASS_HANDLE;
makeOutArgCopy = false;
}
}
}
Expand All@@ -3394,9 +3391,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

if (copyBlkClass != NO_CLASS_HANDLE)
if (makeOutArgCopy)
{
fgMakeOutgoingStructArgCopy(call, &arg, copyBlkClass);
fgMakeOutgoingStructArgCopy(call, &arg);
}

if (argx->gtOper == GT_MKREFANY)
Expand DownExpand Up@@ -4063,14 +4060,13 @@ GenTreeFieldList* Compiler::fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl)
// Arguments:
// call - call being processed
// arg - arg for the call
// copyBlkClass - class handle for the struct
//
// The arg is updated if necessary with the copy.
//
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass)
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg)
{
GenTree* argx = arg->GetEarlyNode();
noway_assert(argx->gtOper != GT_MKREFANY);
noway_assert(!argx->OperIs(GT_MKREFANY));

// If we're optimizing, see if we can avoid making a copy.
//
Expand DownExpand Up@@ -4121,8 +4117,9 @@ void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORI
fgOutgoingArgTemps = hashBv::Create(this);
}

unsigned tmp = 0;
bool found = false;
CORINFO_CLASS_HANDLE copyBlkClass = arg->GetSignatureClassHandle();
unsigned tmp = 0;
bool found = false;

// Attempt to find a local we have already used for an outgoing struct and reuse it.
// We do not reuse within a statement.
Expand Down
39 changes: 39 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_69965/Runtime_69965.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

public unsafe class Runtime_69965
{
public static int Main()
{
const int Value = 10;
var vtor = Vector128.Create(Value, Value, Value, Value);
var vtors = new StructWithOverlappedVtor128[] { new StructWithOverlappedVtor128 { Vtor = vtor } };

return Problem(vtors) != Value ? 101 : 100;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int Problem(StructWithOverlappedVtor128[] a)
{
static Vector128<int> Tunnel(StructWithOverlappedVtor128[] a) => a[0].Vtor;

return CallForVtor(Tunnel(a));
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int CallForVtor(Vector128<int> value) => value.GetElement(0);

[StructLayout(LayoutKind.Explicit)]
struct StructWithOverlappedVtor128
{
[FieldOffset(16)]
public Vector128<int> Vtor;
[FieldOffset(16)]
public Vector128<uint> AnotherVtor;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Optimize>True</Optimize>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Use the signature's struct handle for making outgoing arg copies by SingleAccretion · Pull Request #69971 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -5616,7 +5616,7 @@ class Compiler
GenTreeFieldList* fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl);
GenTreeCall* fgMorphArgs(GenTreeCall* call);

void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass);
void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg);

GenTree* fgMorphLocalVar(GenTree* tree, bool forceRemorph);

Expand Down
61 changes: 29 additions & 32 deletions src/coreclr/jit/morph.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3112,8 +3112,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

CORINFO_CLASS_HANDLE copyBlkClass = NO_CLASS_HANDLE;

// TODO-ARGS: Review this, is it really necessary to treat them specially here?
if (call->gtArgs.IsNonStandard(this, call, &arg) && arg.AbiInfo.IsPassedInRegisters())
{
Expand All@@ -3138,41 +3136,40 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// Struct arguments may be morphed into a node that is not a struct type.
// In such case the CallArgABIInformation keeps track of whether the original node (before morphing)
// was a struct and the struct classification.
bool isStructArg = arg.AbiInfo.IsStruct;
bool isStructArg = arg.AbiInfo.IsStruct;
GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
bool makeOutArgCopy = false;

GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
if (isStructArg && varTypeIsStruct(argObj) && !argObj->OperIs(GT_ASG, GT_MKREFANY, GT_FIELD_LIST))
{
CORINFO_CLASS_HANDLE objClass = gtGetStructHandle(argObj);
unsigned originalSize;
unsigned originalSize;
if (argObj->TypeGet() == TYP_STRUCT)
{
if (argObj->OperIs(GT_OBJ))
{
// Get the size off the OBJ node.
originalSize = argObj->AsObj()->GetLayout()->GetSize();
assert(originalSize == info.compCompHnd->getClassSize(objClass));
originalSize = argObj->AsObj()->Size();
}
else
{
// We have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
assert(argObj->OperIs(GT_LCL_VAR));
originalSize = lvaGetDesc(argObj->AsLclVarCommon())->lvExactSize;
// Must be LCL_VAR: we have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
originalSize = lvaGetDesc(argObj->AsLclVar())->lvExactSize;
}
}
else
{
originalSize = genTypeSize(argx);
assert(originalSize == info.compCompHnd->getClassSize(objClass));
}

assert(originalSize == info.compCompHnd->getClassSize(arg.GetSignatureClassHandle()));

unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE);
var_types structBaseType = arg.AbiInfo.ArgType;

// First, handle the case where the argument is passed by reference.
if (arg.AbiInfo.PassedByRef)
{
assert(arg.AbiInfo.ByteSize == TARGET_POINTER_SIZE);
copyBlkClass = objClass;
makeOutArgCopy = true;
#ifdef UNIX_AMD64_ABI
assert(!"Structs are not passed by reference on x64/ux");
#endif // UNIX_AMD64_ABI
Expand DownExpand Up@@ -3219,7 +3216,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// On Windows structs are always copied and passed by reference (handled above) unless they are
// passed by value in a single register.
assert(arg.AbiInfo.GetStackSlotsNumber() == 1);
copyBlkClass = objClass;
makeOutArgCopy = true;
#else // UNIX_AMD64_ABI
// On Unix, structs are always passed by value.
// We only need a copy if we have one of the following:
Expand All@@ -3233,7 +3230,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
if (passingSize != structSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (lclVar == nullptr)
Expand All@@ -3242,15 +3239,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
assert(argObj->TypeGet() != TYP_STRUCT);
if (arg.AbiInfo.NumRegs > 1)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
}
#endif // UNIX_AMD64_ABI
#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64)
if ((passingSize != structSize) && (lclVar == nullptr))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif

Expand All@@ -3260,12 +3257,12 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
(lvaGetPromotionType(lclVar->AsLclVarCommon()->GetLclNum()) == PROMOTION_TYPE_INDEPENDENT)) ||
((argObj->OperIs(GT_OBJ)) && (passingSize != structSize)))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}

if (structSize < TARGET_POINTER_SIZE)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif // TARGET_ARM
}
Expand DownExpand Up@@ -3328,7 +3325,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()));
assert(copyBlkClass == NO_CLASS_HANDLE);
assert(!makeOutArgCopy);
}
else
{
Expand All@@ -3342,7 +3339,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
// The struct fits into a single register, but it has been promoted into its
// constituent fields, and so we have to re-assemble it
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (genTypeSize(varDsc->TypeGet()) != genTypeSize(structBaseType))
Expand All@@ -3359,7 +3356,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()) ||
((copyBlkClass != NO_CLASS_HANDLE) && varTypeIsEnregisterable(structBaseType)));
(makeOutArgCopy && varTypeIsEnregisterable(structBaseType)));
}

#if !defined(UNIX_AMD64_ABI) && !defined(TARGET_ARMARCH) && !defined(TARGET_LOONGARCH64)
Expand All@@ -3377,15 +3374,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// the obj reading memory past the end of the valuetype
if (roundupSize > originalSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;

// There are a few special cases where we can omit using a CopyBlk
// where we normally would need to use one.

if (argObj->OperIs(GT_OBJ) &&
argObj->AsObj()->gtGetOp1()->IsLocalAddrExpr() != nullptr) // Is the source a LclVar?
{
copyBlkClass = NO_CLASS_HANDLE;
makeOutArgCopy = false;
}
}
}
Expand All@@ -3394,9 +3391,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

if (copyBlkClass != NO_CLASS_HANDLE)
if (makeOutArgCopy)
{
fgMakeOutgoingStructArgCopy(call, &arg, copyBlkClass);
fgMakeOutgoingStructArgCopy(call, &arg);
}

if (argx->gtOper == GT_MKREFANY)
Expand DownExpand Up@@ -4063,14 +4060,13 @@ GenTreeFieldList* Compiler::fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl)
// Arguments:
// call - call being processed
// arg - arg for the call
// copyBlkClass - class handle for the struct
//
// The arg is updated if necessary with the copy.
//
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass)
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg)
{
GenTree* argx = arg->GetEarlyNode();
noway_assert(argx->gtOper != GT_MKREFANY);
noway_assert(!argx->OperIs(GT_MKREFANY));

// If we're optimizing, see if we can avoid making a copy.
//
Expand DownExpand Up@@ -4121,8 +4117,9 @@ void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORI
fgOutgoingArgTemps = hashBv::Create(this);
}

unsigned tmp = 0;
bool found = false;
CORINFO_CLASS_HANDLE copyBlkClass = arg->GetSignatureClassHandle();
unsigned tmp = 0;
bool found = false;

// Attempt to find a local we have already used for an outgoing struct and reuse it.
// We do not reuse within a statement.
Expand Down
39 changes: 39 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_69965/Runtime_69965.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

public unsafe class Runtime_69965
{
public static int Main()
{
const int Value = 10;
var vtor = Vector128.Create(Value, Value, Value, Value);
var vtors = new StructWithOverlappedVtor128[] { new StructWithOverlappedVtor128 { Vtor = vtor } };

return Problem(vtors) != Value ? 101 : 100;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int Problem(StructWithOverlappedVtor128[] a)
{
static Vector128<int> Tunnel(StructWithOverlappedVtor128[] a) => a[0].Vtor;

return CallForVtor(Tunnel(a));
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int CallForVtor(Vector128<int> value) => value.GetElement(0);

[StructLayout(LayoutKind.Explicit)]
struct StructWithOverlappedVtor128
{
[FieldOffset(16)]
public Vector128<int> Vtor;
[FieldOffset(16)]
public Vector128<uint> AnotherVtor;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Optimize>True</Optimize>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Use the signature's struct handle for making outgoing arg copies by SingleAccretion · Pull Request #69971 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -5616,7 +5616,7 @@ class Compiler
GenTreeFieldList* fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl);
GenTreeCall* fgMorphArgs(GenTreeCall* call);

void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass);
void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg);

GenTree* fgMorphLocalVar(GenTree* tree, bool forceRemorph);

Expand Down
61 changes: 29 additions & 32 deletions src/coreclr/jit/morph.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3112,8 +3112,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

CORINFO_CLASS_HANDLE copyBlkClass = NO_CLASS_HANDLE;

// TODO-ARGS: Review this, is it really necessary to treat them specially here?
if (call->gtArgs.IsNonStandard(this, call, &arg) && arg.AbiInfo.IsPassedInRegisters())
{
Expand All@@ -3138,41 +3136,40 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// Struct arguments may be morphed into a node that is not a struct type.
// In such case the CallArgABIInformation keeps track of whether the original node (before morphing)
// was a struct and the struct classification.
bool isStructArg = arg.AbiInfo.IsStruct;
bool isStructArg = arg.AbiInfo.IsStruct;
GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
bool makeOutArgCopy = false;

GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
if (isStructArg && varTypeIsStruct(argObj) && !argObj->OperIs(GT_ASG, GT_MKREFANY, GT_FIELD_LIST))
{
CORINFO_CLASS_HANDLE objClass = gtGetStructHandle(argObj);
unsigned originalSize;
unsigned originalSize;
if (argObj->TypeGet() == TYP_STRUCT)
{
if (argObj->OperIs(GT_OBJ))
{
// Get the size off the OBJ node.
originalSize = argObj->AsObj()->GetLayout()->GetSize();
assert(originalSize == info.compCompHnd->getClassSize(objClass));
originalSize = argObj->AsObj()->Size();
}
else
{
// We have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
assert(argObj->OperIs(GT_LCL_VAR));
originalSize = lvaGetDesc(argObj->AsLclVarCommon())->lvExactSize;
// Must be LCL_VAR: we have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
originalSize = lvaGetDesc(argObj->AsLclVar())->lvExactSize;
}
}
else
{
originalSize = genTypeSize(argx);
assert(originalSize == info.compCompHnd->getClassSize(objClass));
}

assert(originalSize == info.compCompHnd->getClassSize(arg.GetSignatureClassHandle()));

unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE);
var_types structBaseType = arg.AbiInfo.ArgType;

// First, handle the case where the argument is passed by reference.
if (arg.AbiInfo.PassedByRef)
{
assert(arg.AbiInfo.ByteSize == TARGET_POINTER_SIZE);
copyBlkClass = objClass;
makeOutArgCopy = true;
#ifdef UNIX_AMD64_ABI
assert(!"Structs are not passed by reference on x64/ux");
#endif // UNIX_AMD64_ABI
Expand DownExpand Up@@ -3219,7 +3216,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// On Windows structs are always copied and passed by reference (handled above) unless they are
// passed by value in a single register.
assert(arg.AbiInfo.GetStackSlotsNumber() == 1);
copyBlkClass = objClass;
makeOutArgCopy = true;
#else // UNIX_AMD64_ABI
// On Unix, structs are always passed by value.
// We only need a copy if we have one of the following:
Expand All@@ -3233,7 +3230,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
if (passingSize != structSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (lclVar == nullptr)
Expand All@@ -3242,15 +3239,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
assert(argObj->TypeGet() != TYP_STRUCT);
if (arg.AbiInfo.NumRegs > 1)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
}
#endif // UNIX_AMD64_ABI
#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64)
if ((passingSize != structSize) && (lclVar == nullptr))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif

Expand All@@ -3260,12 +3257,12 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
(lvaGetPromotionType(lclVar->AsLclVarCommon()->GetLclNum()) == PROMOTION_TYPE_INDEPENDENT)) ||
((argObj->OperIs(GT_OBJ)) && (passingSize != structSize)))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}

if (structSize < TARGET_POINTER_SIZE)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif // TARGET_ARM
}
Expand DownExpand Up@@ -3328,7 +3325,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()));
assert(copyBlkClass == NO_CLASS_HANDLE);
assert(!makeOutArgCopy);
}
else
{
Expand All@@ -3342,7 +3339,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
// The struct fits into a single register, but it has been promoted into its
// constituent fields, and so we have to re-assemble it
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (genTypeSize(varDsc->TypeGet()) != genTypeSize(structBaseType))
Expand All@@ -3359,7 +3356,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()) ||
((copyBlkClass != NO_CLASS_HANDLE) && varTypeIsEnregisterable(structBaseType)));
(makeOutArgCopy && varTypeIsEnregisterable(structBaseType)));
}

#if !defined(UNIX_AMD64_ABI) && !defined(TARGET_ARMARCH) && !defined(TARGET_LOONGARCH64)
Expand All@@ -3377,15 +3374,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// the obj reading memory past the end of the valuetype
if (roundupSize > originalSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;

// There are a few special cases where we can omit using a CopyBlk
// where we normally would need to use one.

if (argObj->OperIs(GT_OBJ) &&
argObj->AsObj()->gtGetOp1()->IsLocalAddrExpr() != nullptr) // Is the source a LclVar?
{
copyBlkClass = NO_CLASS_HANDLE;
makeOutArgCopy = false;
}
}
}
Expand All@@ -3394,9 +3391,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

if (copyBlkClass != NO_CLASS_HANDLE)
if (makeOutArgCopy)
{
fgMakeOutgoingStructArgCopy(call, &arg, copyBlkClass);
fgMakeOutgoingStructArgCopy(call, &arg);
}

if (argx->gtOper == GT_MKREFANY)
Expand DownExpand Up@@ -4063,14 +4060,13 @@ GenTreeFieldList* Compiler::fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl)
// Arguments:
// call - call being processed
// arg - arg for the call
// copyBlkClass - class handle for the struct
//
// The arg is updated if necessary with the copy.
//
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass)
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg)
{
GenTree* argx = arg->GetEarlyNode();
noway_assert(argx->gtOper != GT_MKREFANY);
noway_assert(!argx->OperIs(GT_MKREFANY));

// If we're optimizing, see if we can avoid making a copy.
//
Expand DownExpand Up@@ -4121,8 +4117,9 @@ void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORI
fgOutgoingArgTemps = hashBv::Create(this);
}

unsigned tmp = 0;
bool found = false;
CORINFO_CLASS_HANDLE copyBlkClass = arg->GetSignatureClassHandle();
unsigned tmp = 0;
bool found = false;

// Attempt to find a local we have already used for an outgoing struct and reuse it.
// We do not reuse within a statement.
Expand Down
39 changes: 39 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_69965/Runtime_69965.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

public unsafe class Runtime_69965
{
public static int Main()
{
const int Value = 10;
var vtor = Vector128.Create(Value, Value, Value, Value);
var vtors = new StructWithOverlappedVtor128[] { new StructWithOverlappedVtor128 { Vtor = vtor } };

return Problem(vtors) != Value ? 101 : 100;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int Problem(StructWithOverlappedVtor128[] a)
{
static Vector128<int> Tunnel(StructWithOverlappedVtor128[] a) => a[0].Vtor;

return CallForVtor(Tunnel(a));
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int CallForVtor(Vector128<int> value) => value.GetElement(0);

[StructLayout(LayoutKind.Explicit)]
struct StructWithOverlappedVtor128
{
[FieldOffset(16)]
public Vector128<int> Vtor;
[FieldOffset(16)]
public Vector128<uint> AnotherVtor;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Optimize>True</Optimize>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Use the signature's struct handle for making outgoing arg copies by SingleAccretion · Pull Request #69971 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -5616,7 +5616,7 @@ class Compiler
GenTreeFieldList* fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl);
GenTreeCall* fgMorphArgs(GenTreeCall* call);

void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass);
void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg);

GenTree* fgMorphLocalVar(GenTree* tree, bool forceRemorph);

Expand Down
61 changes: 29 additions & 32 deletions src/coreclr/jit/morph.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3112,8 +3112,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

CORINFO_CLASS_HANDLE copyBlkClass = NO_CLASS_HANDLE;

// TODO-ARGS: Review this, is it really necessary to treat them specially here?
if (call->gtArgs.IsNonStandard(this, call, &arg) && arg.AbiInfo.IsPassedInRegisters())
{
Expand All@@ -3138,41 +3136,40 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// Struct arguments may be morphed into a node that is not a struct type.
// In such case the CallArgABIInformation keeps track of whether the original node (before morphing)
// was a struct and the struct classification.
bool isStructArg = arg.AbiInfo.IsStruct;
bool isStructArg = arg.AbiInfo.IsStruct;
GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
bool makeOutArgCopy = false;

GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
if (isStructArg && varTypeIsStruct(argObj) && !argObj->OperIs(GT_ASG, GT_MKREFANY, GT_FIELD_LIST))
{
CORINFO_CLASS_HANDLE objClass = gtGetStructHandle(argObj);
unsigned originalSize;
unsigned originalSize;
if (argObj->TypeGet() == TYP_STRUCT)
{
if (argObj->OperIs(GT_OBJ))
{
// Get the size off the OBJ node.
originalSize = argObj->AsObj()->GetLayout()->GetSize();
assert(originalSize == info.compCompHnd->getClassSize(objClass));
originalSize = argObj->AsObj()->Size();
}
else
{
// We have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
assert(argObj->OperIs(GT_LCL_VAR));
originalSize = lvaGetDesc(argObj->AsLclVarCommon())->lvExactSize;
// Must be LCL_VAR: we have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
originalSize = lvaGetDesc(argObj->AsLclVar())->lvExactSize;
}
}
else
{
originalSize = genTypeSize(argx);
assert(originalSize == info.compCompHnd->getClassSize(objClass));
}

assert(originalSize == info.compCompHnd->getClassSize(arg.GetSignatureClassHandle()));

unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE);
var_types structBaseType = arg.AbiInfo.ArgType;

// First, handle the case where the argument is passed by reference.
if (arg.AbiInfo.PassedByRef)
{
assert(arg.AbiInfo.ByteSize == TARGET_POINTER_SIZE);
copyBlkClass = objClass;
makeOutArgCopy = true;
#ifdef UNIX_AMD64_ABI
assert(!"Structs are not passed by reference on x64/ux");
#endif // UNIX_AMD64_ABI
Expand DownExpand Up@@ -3219,7 +3216,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// On Windows structs are always copied and passed by reference (handled above) unless they are
// passed by value in a single register.
assert(arg.AbiInfo.GetStackSlotsNumber() == 1);
copyBlkClass = objClass;
makeOutArgCopy = true;
#else // UNIX_AMD64_ABI
// On Unix, structs are always passed by value.
// We only need a copy if we have one of the following:
Expand All@@ -3233,7 +3230,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
if (passingSize != structSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (lclVar == nullptr)
Expand All@@ -3242,15 +3239,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
assert(argObj->TypeGet() != TYP_STRUCT);
if (arg.AbiInfo.NumRegs > 1)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
}
#endif // UNIX_AMD64_ABI
#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64)
if ((passingSize != structSize) && (lclVar == nullptr))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif

Expand All@@ -3260,12 +3257,12 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
(lvaGetPromotionType(lclVar->AsLclVarCommon()->GetLclNum()) == PROMOTION_TYPE_INDEPENDENT)) ||
((argObj->OperIs(GT_OBJ)) && (passingSize != structSize)))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}

if (structSize < TARGET_POINTER_SIZE)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif // TARGET_ARM
}
Expand DownExpand Up@@ -3328,7 +3325,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()));
assert(copyBlkClass == NO_CLASS_HANDLE);
assert(!makeOutArgCopy);
}
else
{
Expand All@@ -3342,7 +3339,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
// The struct fits into a single register, but it has been promoted into its
// constituent fields, and so we have to re-assemble it
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (genTypeSize(varDsc->TypeGet()) != genTypeSize(structBaseType))
Expand All@@ -3359,7 +3356,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()) ||
((copyBlkClass != NO_CLASS_HANDLE) && varTypeIsEnregisterable(structBaseType)));
(makeOutArgCopy && varTypeIsEnregisterable(structBaseType)));
}

#if !defined(UNIX_AMD64_ABI) && !defined(TARGET_ARMARCH) && !defined(TARGET_LOONGARCH64)
Expand All@@ -3377,15 +3374,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// the obj reading memory past the end of the valuetype
if (roundupSize > originalSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;

// There are a few special cases where we can omit using a CopyBlk
// where we normally would need to use one.

if (argObj->OperIs(GT_OBJ) &&
argObj->AsObj()->gtGetOp1()->IsLocalAddrExpr() != nullptr) // Is the source a LclVar?
{
copyBlkClass = NO_CLASS_HANDLE;
makeOutArgCopy = false;
}
}
}
Expand All@@ -3394,9 +3391,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

if (copyBlkClass != NO_CLASS_HANDLE)
if (makeOutArgCopy)
{
fgMakeOutgoingStructArgCopy(call, &arg, copyBlkClass);
fgMakeOutgoingStructArgCopy(call, &arg);
}

if (argx->gtOper == GT_MKREFANY)
Expand DownExpand Up@@ -4063,14 +4060,13 @@ GenTreeFieldList* Compiler::fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl)
// Arguments:
// call - call being processed
// arg - arg for the call
// copyBlkClass - class handle for the struct
//
// The arg is updated if necessary with the copy.
//
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass)
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg)
{
GenTree* argx = arg->GetEarlyNode();
noway_assert(argx->gtOper != GT_MKREFANY);
noway_assert(!argx->OperIs(GT_MKREFANY));

// If we're optimizing, see if we can avoid making a copy.
//
Expand DownExpand Up@@ -4121,8 +4117,9 @@ void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORI
fgOutgoingArgTemps = hashBv::Create(this);
}

unsigned tmp = 0;
bool found = false;
CORINFO_CLASS_HANDLE copyBlkClass = arg->GetSignatureClassHandle();
unsigned tmp = 0;
bool found = false;

// Attempt to find a local we have already used for an outgoing struct and reuse it.
// We do not reuse within a statement.
Expand Down
39 changes: 39 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_69965/Runtime_69965.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

public unsafe class Runtime_69965
{
public static int Main()
{
const int Value = 10;
var vtor = Vector128.Create(Value, Value, Value, Value);
var vtors = new StructWithOverlappedVtor128[] { new StructWithOverlappedVtor128 { Vtor = vtor } };

return Problem(vtors) != Value ? 101 : 100;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int Problem(StructWithOverlappedVtor128[] a)
{
static Vector128<int> Tunnel(StructWithOverlappedVtor128[] a) => a[0].Vtor;

return CallForVtor(Tunnel(a));
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int CallForVtor(Vector128<int> value) => value.GetElement(0);

[StructLayout(LayoutKind.Explicit)]
struct StructWithOverlappedVtor128
{
[FieldOffset(16)]
public Vector128<int> Vtor;
[FieldOffset(16)]
public Vector128<uint> AnotherVtor;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Optimize>True</Optimize>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Use the signature's struct handle for making outgoing arg copies by SingleAccretion · Pull Request #69971 · dotnet/runtime · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -5616,7 +5616,7 @@ class Compiler
GenTreeFieldList* fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl);
GenTreeCall* fgMorphArgs(GenTreeCall* call);

void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass);
void fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg);

GenTree* fgMorphLocalVar(GenTree* tree, bool forceRemorph);

Expand Down
61 changes: 29 additions & 32 deletions src/coreclr/jit/morph.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3112,8 +3112,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

CORINFO_CLASS_HANDLE copyBlkClass = NO_CLASS_HANDLE;

// TODO-ARGS: Review this, is it really necessary to treat them specially here?
if (call->gtArgs.IsNonStandard(this, call, &arg) && arg.AbiInfo.IsPassedInRegisters())
{
Expand All@@ -3138,41 +3136,40 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// Struct arguments may be morphed into a node that is not a struct type.
// In such case the CallArgABIInformation keeps track of whether the original node (before morphing)
// was a struct and the struct classification.
bool isStructArg = arg.AbiInfo.IsStruct;
bool isStructArg = arg.AbiInfo.IsStruct;
GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
bool makeOutArgCopy = false;

GenTree* argObj = argx->gtEffectiveVal(true /*commaOnly*/);
if (isStructArg && varTypeIsStruct(argObj) && !argObj->OperIs(GT_ASG, GT_MKREFANY, GT_FIELD_LIST))
{
CORINFO_CLASS_HANDLE objClass = gtGetStructHandle(argObj);
unsigned originalSize;
unsigned originalSize;
if (argObj->TypeGet() == TYP_STRUCT)
{
if (argObj->OperIs(GT_OBJ))
{
// Get the size off the OBJ node.
originalSize = argObj->AsObj()->GetLayout()->GetSize();
assert(originalSize == info.compCompHnd->getClassSize(objClass));
originalSize = argObj->AsObj()->Size();
}
else
{
// We have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
assert(argObj->OperIs(GT_LCL_VAR));
originalSize = lvaGetDesc(argObj->AsLclVarCommon())->lvExactSize;
// Must be LCL_VAR: we have a BADCODE assert for this in AddFinalArgsAndDetermineABIInfo.
originalSize = lvaGetDesc(argObj->AsLclVar())->lvExactSize;
}
}
else
{
originalSize = genTypeSize(argx);
assert(originalSize == info.compCompHnd->getClassSize(objClass));
}

assert(originalSize == info.compCompHnd->getClassSize(arg.GetSignatureClassHandle()));

unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE);
var_types structBaseType = arg.AbiInfo.ArgType;

// First, handle the case where the argument is passed by reference.
if (arg.AbiInfo.PassedByRef)
{
assert(arg.AbiInfo.ByteSize == TARGET_POINTER_SIZE);
copyBlkClass = objClass;
makeOutArgCopy = true;
#ifdef UNIX_AMD64_ABI
assert(!"Structs are not passed by reference on x64/ux");
#endif // UNIX_AMD64_ABI
Expand DownExpand Up@@ -3219,7 +3216,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// On Windows structs are always copied and passed by reference (handled above) unless they are
// passed by value in a single register.
assert(arg.AbiInfo.GetStackSlotsNumber() == 1);
copyBlkClass = objClass;
makeOutArgCopy = true;
#else // UNIX_AMD64_ABI
// On Unix, structs are always passed by value.
// We only need a copy if we have one of the following:
Expand All@@ -3233,7 +3230,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
if (passingSize != structSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (lclVar == nullptr)
Expand All@@ -3242,15 +3239,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
assert(argObj->TypeGet() != TYP_STRUCT);
if (arg.AbiInfo.NumRegs > 1)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
}
#endif // UNIX_AMD64_ABI
#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64)
if ((passingSize != structSize) && (lclVar == nullptr))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif

Expand All@@ -3260,12 +3257,12 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
(lvaGetPromotionType(lclVar->AsLclVarCommon()->GetLclNum()) == PROMOTION_TYPE_INDEPENDENT)) ||
((argObj->OperIs(GT_OBJ)) && (passingSize != structSize)))
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}

if (structSize < TARGET_POINTER_SIZE)
{
copyBlkClass = objClass;
makeOutArgCopy = true;
}
#endif // TARGET_ARM
}
Expand DownExpand Up@@ -3328,7 +3325,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()));
assert(copyBlkClass == NO_CLASS_HANDLE);
assert(!makeOutArgCopy);
}
else
{
Expand All@@ -3342,7 +3339,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
{
// The struct fits into a single register, but it has been promoted into its
// constituent fields, and so we have to re-assemble it
copyBlkClass = objClass;
makeOutArgCopy = true;
}
}
else if (genTypeSize(varDsc->TypeGet()) != genTypeSize(structBaseType))
Expand All@@ -3359,7 +3356,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
argObj->gtType = structBaseType;
}
assert(varTypeIsEnregisterable(argObj->TypeGet()) ||
((copyBlkClass != NO_CLASS_HANDLE) && varTypeIsEnregisterable(structBaseType)));
(makeOutArgCopy && varTypeIsEnregisterable(structBaseType)));
}

#if !defined(UNIX_AMD64_ABI) && !defined(TARGET_ARMARCH) && !defined(TARGET_LOONGARCH64)
Expand All@@ -3377,15 +3374,15 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
// the obj reading memory past the end of the valuetype
if (roundupSize > originalSize)
{
copyBlkClass = objClass;
makeOutArgCopy = true;

// There are a few special cases where we can omit using a CopyBlk
// where we normally would need to use one.

if (argObj->OperIs(GT_OBJ) &&
argObj->AsObj()->gtGetOp1()->IsLocalAddrExpr() != nullptr) // Is the source a LclVar?
{
copyBlkClass = NO_CLASS_HANDLE;
makeOutArgCopy = false;
}
}
}
Expand All@@ -3394,9 +3391,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
}
}

if (copyBlkClass != NO_CLASS_HANDLE)
if (makeOutArgCopy)
{
fgMakeOutgoingStructArgCopy(call, &arg, copyBlkClass);
fgMakeOutgoingStructArgCopy(call, &arg);
}

if (argx->gtOper == GT_MKREFANY)
Expand DownExpand Up@@ -4063,14 +4060,13 @@ GenTreeFieldList* Compiler::fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl)
// Arguments:
// call - call being processed
// arg - arg for the call
// copyBlkClass - class handle for the struct
//
// The arg is updated if necessary with the copy.
//
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORINFO_CLASS_HANDLE copyBlkClass)
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg)
{
GenTree* argx = arg->GetEarlyNode();
noway_assert(argx->gtOper != GT_MKREFANY);
noway_assert(!argx->OperIs(GT_MKREFANY));

// If we're optimizing, see if we can avoid making a copy.
//
Expand DownExpand Up@@ -4121,8 +4117,9 @@ void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg, CORI
fgOutgoingArgTemps = hashBv::Create(this);
}

unsigned tmp = 0;
bool found = false;
CORINFO_CLASS_HANDLE copyBlkClass = arg->GetSignatureClassHandle();
unsigned tmp = 0;
bool found = false;

// Attempt to find a local we have already used for an outgoing struct and reuse it.
// We do not reuse within a statement.
Expand Down
39 changes: 39 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_69965/Runtime_69965.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

public unsafe class Runtime_69965
{
public static int Main()
{
const int Value = 10;
var vtor = Vector128.Create(Value, Value, Value, Value);
var vtors = new StructWithOverlappedVtor128[] { new StructWithOverlappedVtor128 { Vtor = vtor } };

return Problem(vtors) != Value ? 101 : 100;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int Problem(StructWithOverlappedVtor128[] a)
{
static Vector128<int> Tunnel(StructWithOverlappedVtor128[] a) => a[0].Vtor;

return CallForVtor(Tunnel(a));
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static int CallForVtor(Vector128<int> value) => value.GetElement(0);

[StructLayout(LayoutKind.Explicit)]
struct StructWithOverlappedVtor128
{
[FieldOffset(16)]
public Vector128<int> Vtor;
[FieldOffset(16)]
public Vector128<uint> AnotherVtor;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Optimize>True</Optimize>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>