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
98 changes: 96 additions & 2 deletions src/coreclr/jit/codegencommon.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -2919,6 +2919,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
bool writeThru; // true if the argument gets homed to both stack and register
bool processed; // true after we've processed the argument (and it is in its final location)
bool circular; // true if this register participates in a circular dependency loop.
bool hfaConflict; // arg is part of an HFA that will end up in the same register
// but in a different slot (eg arg in s3 = v3.s[0], needs to end up in v3.s[3])
} regArgTab[max(MAX_REG_ARG + 1, MAX_FLOAT_REG_ARG)] = {};

unsigned varNum;
Expand DownExpand Up@@ -3284,7 +3286,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
* A circular dependency is a set of registers R1, R2, ..., Rn
* such that R1->R2 (that is, R1 needs to be moved to R2), R2->R3, ..., Rn->R1 */

bool change = true;
bool change = true;
bool hasHfaConflict = false;
if (regArgMaskLive)
{
/* Possible circular dependencies still exist; the previous pass was not enough
Expand DownExpand Up@@ -3337,10 +3340,32 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
// This must be a SIMD type that's fully enregistered, but is passed as an HFA.
// Each field will be inserted into the same destination register.
//
assert(varTypeIsSIMD(varDsc));
assert(regArgTab[argNum].slot <= (int)varDsc->lvHfaSlots());
assert(argNum > 0);
assert(regArgTab[argNum - 1].varNum == varNum);

// If the field is passed in the same register as the destination,
// but is in the wrong part of the register, mark it specially so later
// we make sure to move it to the right spot before "freeing" the destination.
//
destRegNum = varDsc->GetRegNum();
if (regNum == destRegNum)
{
// We only get here if the HFA part is not already in the right slot in
// the destination. That is, it is not slot-1.
//
const int slot = regArgTab[argNum].slot;
assert(slot != 1);
JITDUMP("HFA conflict; arg num %u needs to move from %s[%u] to %s[%u]\n", argNum,
getRegName(regNum), 0, getRegName(destRegNum), slot - 1);
regArgTab[argNum].hfaConflict = true;

// We'll need to do a special pass later to resolve these
//
hasHfaConflict = true;
}
regArgMaskLive &= ~genRegMask(regNum);
regArgTab[argNum].circular = false;
change = true;
Expand DownExpand Up@@ -3736,13 +3761,13 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
size = EA_4BYTE;
}
// HVA types...?

/* move the dest reg (begReg) in the extra reg */

assert(xtraReg != REG_NA);

regNumber begRegNum = genMapRegArgNumToRegNum(begReg, destMemType);

GetEmitter()->emitIns_Mov(insCopy, size, xtraReg, begRegNum, /* canSkip */ false);

regSet.verifyRegUsed(xtraReg);
Expand DownExpand Up@@ -3823,6 +3848,75 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
}
}

#if defined(TARGET_ARM64) && defined(FEATURE_SIMD)
// If we saw any hfa conflicts, handle those now.
//
if (hasHfaConflict)
{
// Up above we noticed that there was at least one non-slot-1 HFA arg whose
// destination register was the same as the arg register.
//
// For example, say an HFA was passed as s0-s3 and the destination was v3.
// s3 is in the right register, but not in the right slot in the register.
//
// We handle this by first moving the conflicting part to the right slot
// in the destination (via pass 0 below), and then moving the remaining parts
// to their respective slots (via pass 1).
//
// Note the slot index in the register is one less than value of
// regArgTab[argNum].slot, so a slot-1 hfa arg goes into slot 0 of the destination).
//
// So for the above example, we'd first move the "slot-4" s3 (== v3.s[0]) to v3.s[3].
// Then we can insert s0 to v3.s[0]) and so on.
//
// We can exempt slot-1 cases as the conflicting part is already in the
// right slot, and code lower down correctly handles populating the remaining slots.
//
for (argNum = 0; argNum < argMax; argNum++)
{
if (!regArgTab[argNum].hfaConflict)
{
continue;
}

varNum = regArgTab[argNum].varNum;
varDsc = compiler->lvaGetDesc(varNum);
const regNumber destRegNum = varDsc->GetRegNum();
const var_types regType = regArgTab[argNum].type;
const unsigned firstArgNum = argNum - (regArgTab[argNum].slot - 1);
const unsigned lastArgNum = firstArgNum + varDsc->lvHfaSlots() - 1;

assert(varDsc->lvIsHfa());
assert((argNum >= firstArgNum) && (argNum <= lastArgNum));
assert(destRegNum == genMapRegArgNumToRegNum(argNum, regType));

// Pass 0: move the conflicting part; Pass1: insert everything else
//
for (int pass = 0; pass <= 1; pass++)
{
for (unsigned currentArgNum = firstArgNum; currentArgNum <= lastArgNum; currentArgNum++)
{
const regNumber regNum = genMapRegArgNumToRegNum(currentArgNum, regType);
bool insertArg =
((pass == 0) && (currentArgNum == argNum)) || ((pass == 1) && (currentArgNum != argNum));

if (insertArg)
{
assert(!regArgTab[currentArgNum].processed);

// EA_4BYTE is probably wrong here (and below)
// todo -- suppress self move
GetEmitter()->emitIns_R_R_I_I(INS_mov, EA_4BYTE, destRegNum, regNum,
regArgTab[currentArgNum].slot - 1, 0);
regArgTab[currentArgNum].processed = true;
regArgMaskLive &= ~genRegMask(regNum);
}
}
}
}
}
#endif // defined(TARGET_ARM64) && defined(FEATURE_SIMD)

/* Finally take care of the remaining arguments that must be enregistered */
while (regArgMaskLive)
{
Expand Down
4 changes: 0 additions & 4 deletions src/libraries/tests.proj
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,10 +417,6 @@
</ItemGroup>

<ItemGroup Condition="'$(TestNativeAot)' == 'true' and '$(RunDisabledNativeAotTests)' != 'true'">
<!-- https://github.com/dotnet/runtime/issues/83167 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Numerics.Vectors\tests\System.Numerics.Vectors.Tests.csproj"
Condition="'$(TargetArchitecture)' == 'arm64'" />

<!-- https://github.com/dotnet/runtime/issues/72908 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Reflection.MetadataLoadContext\tests\System.Reflection.MetadataLoadContext.Tests.csproj" />

Expand Down
27 changes: 27 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_83167/Runtime_83167.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Xunit;

public class Runtime_83167
{
[MethodImpl(MethodImplOptions.NoOptimization)]
[Fact]
public static int Problem()
{
Plane p = new Plane (new Vector3(2.0f, 3.0f, 4.0f), 1.0f);
int pH = p.GetHashCode();
EqualityComparer<Plane> c = EqualityComparer<Plane>.Default;
int cH = c.GetHashCode(p);
if (pH != cH)
{
Console.WriteLine($"Failed: {pH:X8} != {cH:X8}");
return 101;
}
return 100;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Optimize>True</Optimize>
<DebugType>None</DebugType>
</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" + '
JIT: fix self-conflicting HFA arg prolog handling for arm64 by AndyAyersMS · Pull Request #92355 · 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
98 changes: 96 additions & 2 deletions src/coreclr/jit/codegencommon.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -2919,6 +2919,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
bool writeThru; // true if the argument gets homed to both stack and register
bool processed; // true after we've processed the argument (and it is in its final location)
bool circular; // true if this register participates in a circular dependency loop.
bool hfaConflict; // arg is part of an HFA that will end up in the same register
// but in a different slot (eg arg in s3 = v3.s[0], needs to end up in v3.s[3])
} regArgTab[max(MAX_REG_ARG + 1, MAX_FLOAT_REG_ARG)] = {};

unsigned varNum;
Expand DownExpand Up@@ -3284,7 +3286,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
* A circular dependency is a set of registers R1, R2, ..., Rn
* such that R1->R2 (that is, R1 needs to be moved to R2), R2->R3, ..., Rn->R1 */

bool change = true;
bool change = true;
bool hasHfaConflict = false;
if (regArgMaskLive)
{
/* Possible circular dependencies still exist; the previous pass was not enough
Expand DownExpand Up@@ -3337,10 +3340,32 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
// This must be a SIMD type that's fully enregistered, but is passed as an HFA.
// Each field will be inserted into the same destination register.
//
assert(varTypeIsSIMD(varDsc));
assert(regArgTab[argNum].slot <= (int)varDsc->lvHfaSlots());
assert(argNum > 0);
assert(regArgTab[argNum - 1].varNum == varNum);

// If the field is passed in the same register as the destination,
// but is in the wrong part of the register, mark it specially so later
// we make sure to move it to the right spot before "freeing" the destination.
//
destRegNum = varDsc->GetRegNum();
if (regNum == destRegNum)
{
// We only get here if the HFA part is not already in the right slot in
// the destination. That is, it is not slot-1.
//
const int slot = regArgTab[argNum].slot;
assert(slot != 1);
JITDUMP("HFA conflict; arg num %u needs to move from %s[%u] to %s[%u]\n", argNum,
getRegName(regNum), 0, getRegName(destRegNum), slot - 1);
regArgTab[argNum].hfaConflict = true;

// We'll need to do a special pass later to resolve these
//
hasHfaConflict = true;
}
regArgMaskLive &= ~genRegMask(regNum);
regArgTab[argNum].circular = false;
change = true;
Expand DownExpand Up@@ -3736,13 +3761,13 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
size = EA_4BYTE;
}
// HVA types...?

/* move the dest reg (begReg) in the extra reg */

assert(xtraReg != REG_NA);

regNumber begRegNum = genMapRegArgNumToRegNum(begReg, destMemType);

GetEmitter()->emitIns_Mov(insCopy, size, xtraReg, begRegNum, /* canSkip */ false);

regSet.verifyRegUsed(xtraReg);
Expand DownExpand Up@@ -3823,6 +3848,75 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
}
}

#if defined(TARGET_ARM64) && defined(FEATURE_SIMD)
// If we saw any hfa conflicts, handle those now.
//
if (hasHfaConflict)
{
// Up above we noticed that there was at least one non-slot-1 HFA arg whose
// destination register was the same as the arg register.
//
// For example, say an HFA was passed as s0-s3 and the destination was v3.
// s3 is in the right register, but not in the right slot in the register.
//
// We handle this by first moving the conflicting part to the right slot
// in the destination (via pass 0 below), and then moving the remaining parts
// to their respective slots (via pass 1).
//
// Note the slot index in the register is one less than value of
// regArgTab[argNum].slot, so a slot-1 hfa arg goes into slot 0 of the destination).
//
// So for the above example, we'd first move the "slot-4" s3 (== v3.s[0]) to v3.s[3].
// Then we can insert s0 to v3.s[0]) and so on.
//
// We can exempt slot-1 cases as the conflicting part is already in the
// right slot, and code lower down correctly handles populating the remaining slots.
//
for (argNum = 0; argNum < argMax; argNum++)
{
if (!regArgTab[argNum].hfaConflict)
{
continue;
}

varNum = regArgTab[argNum].varNum;
varDsc = compiler->lvaGetDesc(varNum);
const regNumber destRegNum = varDsc->GetRegNum();
const var_types regType = regArgTab[argNum].type;
const unsigned firstArgNum = argNum - (regArgTab[argNum].slot - 1);
const unsigned lastArgNum = firstArgNum + varDsc->lvHfaSlots() - 1;

assert(varDsc->lvIsHfa());
assert((argNum >= firstArgNum) && (argNum <= lastArgNum));
assert(destRegNum == genMapRegArgNumToRegNum(argNum, regType));

// Pass 0: move the conflicting part; Pass1: insert everything else
//
for (int pass = 0; pass <= 1; pass++)
{
for (unsigned currentArgNum = firstArgNum; currentArgNum <= lastArgNum; currentArgNum++)
{
const regNumber regNum = genMapRegArgNumToRegNum(currentArgNum, regType);
bool insertArg =
((pass == 0) && (currentArgNum == argNum)) || ((pass == 1) && (currentArgNum != argNum));

if (insertArg)
{
assert(!regArgTab[currentArgNum].processed);

// EA_4BYTE is probably wrong here (and below)
// todo -- suppress self move
GetEmitter()->emitIns_R_R_I_I(INS_mov, EA_4BYTE, destRegNum, regNum,
regArgTab[currentArgNum].slot - 1, 0);
regArgTab[currentArgNum].processed = true;
regArgMaskLive &= ~genRegMask(regNum);
}
}
}
}
}
#endif // defined(TARGET_ARM64) && defined(FEATURE_SIMD)

/* Finally take care of the remaining arguments that must be enregistered */
while (regArgMaskLive)
{
Expand Down
4 changes: 0 additions & 4 deletions src/libraries/tests.proj
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,10 +417,6 @@
</ItemGroup>

<ItemGroup Condition="'$(TestNativeAot)' == 'true' and '$(RunDisabledNativeAotTests)' != 'true'">
<!-- https://github.com/dotnet/runtime/issues/83167 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Numerics.Vectors\tests\System.Numerics.Vectors.Tests.csproj"
Condition="'$(TargetArchitecture)' == 'arm64'" />

<!-- https://github.com/dotnet/runtime/issues/72908 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Reflection.MetadataLoadContext\tests\System.Reflection.MetadataLoadContext.Tests.csproj" />

Expand Down
27 changes: 27 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_83167/Runtime_83167.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Xunit;

public class Runtime_83167
{
[MethodImpl(MethodImplOptions.NoOptimization)]
[Fact]
public static int Problem()
{
Plane p = new Plane (new Vector3(2.0f, 3.0f, 4.0f), 1.0f);
int pH = p.GetHashCode();
EqualityComparer<Plane> c = EqualityComparer<Plane>.Default;
int cH = c.GetHashCode(p);
if (pH != cH)
{
Console.WriteLine($"Failed: {pH:X8} != {cH:X8}");
return 101;
}
return 100;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Optimize>True</Optimize>
<DebugType>None</DebugType>
</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('^' + ".*" + ' JIT: fix self-conflicting HFA arg prolog handling for arm64 by AndyAyersMS · Pull Request #92355 · 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
98 changes: 96 additions & 2 deletions src/coreclr/jit/codegencommon.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -2919,6 +2919,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
bool writeThru; // true if the argument gets homed to both stack and register
bool processed; // true after we've processed the argument (and it is in its final location)
bool circular; // true if this register participates in a circular dependency loop.
bool hfaConflict; // arg is part of an HFA that will end up in the same register
// but in a different slot (eg arg in s3 = v3.s[0], needs to end up in v3.s[3])
} regArgTab[max(MAX_REG_ARG + 1, MAX_FLOAT_REG_ARG)] = {};

unsigned varNum;
Expand DownExpand Up@@ -3284,7 +3286,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
* A circular dependency is a set of registers R1, R2, ..., Rn
* such that R1->R2 (that is, R1 needs to be moved to R2), R2->R3, ..., Rn->R1 */

bool change = true;
bool change = true;
bool hasHfaConflict = false;
if (regArgMaskLive)
{
/* Possible circular dependencies still exist; the previous pass was not enough
Expand DownExpand Up@@ -3337,10 +3340,32 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
// This must be a SIMD type that's fully enregistered, but is passed as an HFA.
// Each field will be inserted into the same destination register.
//
assert(varTypeIsSIMD(varDsc));
assert(regArgTab[argNum].slot <= (int)varDsc->lvHfaSlots());
assert(argNum > 0);
assert(regArgTab[argNum - 1].varNum == varNum);

// If the field is passed in the same register as the destination,
// but is in the wrong part of the register, mark it specially so later
// we make sure to move it to the right spot before "freeing" the destination.
//
destRegNum = varDsc->GetRegNum();
if (regNum == destRegNum)
{
// We only get here if the HFA part is not already in the right slot in
// the destination. That is, it is not slot-1.
//
const int slot = regArgTab[argNum].slot;
assert(slot != 1);
JITDUMP("HFA conflict; arg num %u needs to move from %s[%u] to %s[%u]\n", argNum,
getRegName(regNum), 0, getRegName(destRegNum), slot - 1);
regArgTab[argNum].hfaConflict = true;

// We'll need to do a special pass later to resolve these
//
hasHfaConflict = true;
}
regArgMaskLive &= ~genRegMask(regNum);
regArgTab[argNum].circular = false;
change = true;
Expand DownExpand Up@@ -3736,13 +3761,13 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
size = EA_4BYTE;
}
// HVA types...?

/* move the dest reg (begReg) in the extra reg */

assert(xtraReg != REG_NA);

regNumber begRegNum = genMapRegArgNumToRegNum(begReg, destMemType);

GetEmitter()->emitIns_Mov(insCopy, size, xtraReg, begRegNum, /* canSkip */ false);

regSet.verifyRegUsed(xtraReg);
Expand DownExpand Up@@ -3823,6 +3848,75 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
}
}

#if defined(TARGET_ARM64) && defined(FEATURE_SIMD)
// If we saw any hfa conflicts, handle those now.
//
if (hasHfaConflict)
{
// Up above we noticed that there was at least one non-slot-1 HFA arg whose
// destination register was the same as the arg register.
//
// For example, say an HFA was passed as s0-s3 and the destination was v3.
// s3 is in the right register, but not in the right slot in the register.
//
// We handle this by first moving the conflicting part to the right slot
// in the destination (via pass 0 below), and then moving the remaining parts
// to their respective slots (via pass 1).
//
// Note the slot index in the register is one less than value of
// regArgTab[argNum].slot, so a slot-1 hfa arg goes into slot 0 of the destination).
//
// So for the above example, we'd first move the "slot-4" s3 (== v3.s[0]) to v3.s[3].
// Then we can insert s0 to v3.s[0]) and so on.
//
// We can exempt slot-1 cases as the conflicting part is already in the
// right slot, and code lower down correctly handles populating the remaining slots.
//
for (argNum = 0; argNum < argMax; argNum++)
{
if (!regArgTab[argNum].hfaConflict)
{
continue;
}

varNum = regArgTab[argNum].varNum;
varDsc = compiler->lvaGetDesc(varNum);
const regNumber destRegNum = varDsc->GetRegNum();
const var_types regType = regArgTab[argNum].type;
const unsigned firstArgNum = argNum - (regArgTab[argNum].slot - 1);
const unsigned lastArgNum = firstArgNum + varDsc->lvHfaSlots() - 1;

assert(varDsc->lvIsHfa());
assert((argNum >= firstArgNum) && (argNum <= lastArgNum));
assert(destRegNum == genMapRegArgNumToRegNum(argNum, regType));

// Pass 0: move the conflicting part; Pass1: insert everything else
//
for (int pass = 0; pass <= 1; pass++)
{
for (unsigned currentArgNum = firstArgNum; currentArgNum <= lastArgNum; currentArgNum++)
{
const regNumber regNum = genMapRegArgNumToRegNum(currentArgNum, regType);
bool insertArg =
((pass == 0) && (currentArgNum == argNum)) || ((pass == 1) && (currentArgNum != argNum));

if (insertArg)
{
assert(!regArgTab[currentArgNum].processed);

// EA_4BYTE is probably wrong here (and below)
// todo -- suppress self move
GetEmitter()->emitIns_R_R_I_I(INS_mov, EA_4BYTE, destRegNum, regNum,
regArgTab[currentArgNum].slot - 1, 0);
regArgTab[currentArgNum].processed = true;
regArgMaskLive &= ~genRegMask(regNum);
}
}
}
}
}
#endif // defined(TARGET_ARM64) && defined(FEATURE_SIMD)

/* Finally take care of the remaining arguments that must be enregistered */
while (regArgMaskLive)
{
Expand Down
4 changes: 0 additions & 4 deletions src/libraries/tests.proj
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,10 +417,6 @@
</ItemGroup>

<ItemGroup Condition="'$(TestNativeAot)' == 'true' and '$(RunDisabledNativeAotTests)' != 'true'">
<!-- https://github.com/dotnet/runtime/issues/83167 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Numerics.Vectors\tests\System.Numerics.Vectors.Tests.csproj"
Condition="'$(TargetArchitecture)' == 'arm64'" />

<!-- https://github.com/dotnet/runtime/issues/72908 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Reflection.MetadataLoadContext\tests\System.Reflection.MetadataLoadContext.Tests.csproj" />

Expand Down
27 changes: 27 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_83167/Runtime_83167.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Xunit;

public class Runtime_83167
{
[MethodImpl(MethodImplOptions.NoOptimization)]
[Fact]
public static int Problem()
{
Plane p = new Plane (new Vector3(2.0f, 3.0f, 4.0f), 1.0f);
int pH = p.GetHashCode();
EqualityComparer<Plane> c = EqualityComparer<Plane>.Default;
int cH = c.GetHashCode(p);
if (pH != cH)
{
Console.WriteLine($"Failed: {pH:X8} != {cH:X8}");
return 101;
}
return 100;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Optimize>True</Optimize>
<DebugType>None</DebugType>
</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('^' + ".*" + ' JIT: fix self-conflicting HFA arg prolog handling for arm64 by AndyAyersMS · Pull Request #92355 · 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
98 changes: 96 additions & 2 deletions src/coreclr/jit/codegencommon.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -2919,6 +2919,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
bool writeThru; // true if the argument gets homed to both stack and register
bool processed; // true after we've processed the argument (and it is in its final location)
bool circular; // true if this register participates in a circular dependency loop.
bool hfaConflict; // arg is part of an HFA that will end up in the same register
// but in a different slot (eg arg in s3 = v3.s[0], needs to end up in v3.s[3])
} regArgTab[max(MAX_REG_ARG + 1, MAX_FLOAT_REG_ARG)] = {};

unsigned varNum;
Expand DownExpand Up@@ -3284,7 +3286,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
* A circular dependency is a set of registers R1, R2, ..., Rn
* such that R1->R2 (that is, R1 needs to be moved to R2), R2->R3, ..., Rn->R1 */

bool change = true;
bool change = true;
bool hasHfaConflict = false;
if (regArgMaskLive)
{
/* Possible circular dependencies still exist; the previous pass was not enough
Expand DownExpand Up@@ -3337,10 +3340,32 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
// This must be a SIMD type that's fully enregistered, but is passed as an HFA.
// Each field will be inserted into the same destination register.
//
assert(varTypeIsSIMD(varDsc));
assert(regArgTab[argNum].slot <= (int)varDsc->lvHfaSlots());
assert(argNum > 0);
assert(regArgTab[argNum - 1].varNum == varNum);

// If the field is passed in the same register as the destination,
// but is in the wrong part of the register, mark it specially so later
// we make sure to move it to the right spot before "freeing" the destination.
//
destRegNum = varDsc->GetRegNum();
if (regNum == destRegNum)
{
// We only get here if the HFA part is not already in the right slot in
// the destination. That is, it is not slot-1.
//
const int slot = regArgTab[argNum].slot;
assert(slot != 1);
JITDUMP("HFA conflict; arg num %u needs to move from %s[%u] to %s[%u]\n", argNum,
getRegName(regNum), 0, getRegName(destRegNum), slot - 1);
regArgTab[argNum].hfaConflict = true;

// We'll need to do a special pass later to resolve these
//
hasHfaConflict = true;
}
regArgMaskLive &= ~genRegMask(regNum);
regArgTab[argNum].circular = false;
change = true;
Expand DownExpand Up@@ -3736,13 +3761,13 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
size = EA_4BYTE;
}
// HVA types...?

/* move the dest reg (begReg) in the extra reg */

assert(xtraReg != REG_NA);

regNumber begRegNum = genMapRegArgNumToRegNum(begReg, destMemType);

GetEmitter()->emitIns_Mov(insCopy, size, xtraReg, begRegNum, /* canSkip */ false);

regSet.verifyRegUsed(xtraReg);
Expand DownExpand Up@@ -3823,6 +3848,75 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
}
}

#if defined(TARGET_ARM64) && defined(FEATURE_SIMD)
// If we saw any hfa conflicts, handle those now.
//
if (hasHfaConflict)
{
// Up above we noticed that there was at least one non-slot-1 HFA arg whose
// destination register was the same as the arg register.
//
// For example, say an HFA was passed as s0-s3 and the destination was v3.
// s3 is in the right register, but not in the right slot in the register.
//
// We handle this by first moving the conflicting part to the right slot
// in the destination (via pass 0 below), and then moving the remaining parts
// to their respective slots (via pass 1).
//
// Note the slot index in the register is one less than value of
// regArgTab[argNum].slot, so a slot-1 hfa arg goes into slot 0 of the destination).
//
// So for the above example, we'd first move the "slot-4" s3 (== v3.s[0]) to v3.s[3].
// Then we can insert s0 to v3.s[0]) and so on.
//
// We can exempt slot-1 cases as the conflicting part is already in the
// right slot, and code lower down correctly handles populating the remaining slots.
//
for (argNum = 0; argNum < argMax; argNum++)
{
if (!regArgTab[argNum].hfaConflict)
{
continue;
}

varNum = regArgTab[argNum].varNum;
varDsc = compiler->lvaGetDesc(varNum);
const regNumber destRegNum = varDsc->GetRegNum();
const var_types regType = regArgTab[argNum].type;
const unsigned firstArgNum = argNum - (regArgTab[argNum].slot - 1);
const unsigned lastArgNum = firstArgNum + varDsc->lvHfaSlots() - 1;

assert(varDsc->lvIsHfa());
assert((argNum >= firstArgNum) && (argNum <= lastArgNum));
assert(destRegNum == genMapRegArgNumToRegNum(argNum, regType));

// Pass 0: move the conflicting part; Pass1: insert everything else
//
for (int pass = 0; pass <= 1; pass++)
{
for (unsigned currentArgNum = firstArgNum; currentArgNum <= lastArgNum; currentArgNum++)
{
const regNumber regNum = genMapRegArgNumToRegNum(currentArgNum, regType);
bool insertArg =
((pass == 0) && (currentArgNum == argNum)) || ((pass == 1) && (currentArgNum != argNum));

if (insertArg)
{
assert(!regArgTab[currentArgNum].processed);

// EA_4BYTE is probably wrong here (and below)
// todo -- suppress self move
GetEmitter()->emitIns_R_R_I_I(INS_mov, EA_4BYTE, destRegNum, regNum,
regArgTab[currentArgNum].slot - 1, 0);
regArgTab[currentArgNum].processed = true;
regArgMaskLive &= ~genRegMask(regNum);
}
}
}
}
}
#endif // defined(TARGET_ARM64) && defined(FEATURE_SIMD)

/* Finally take care of the remaining arguments that must be enregistered */
while (regArgMaskLive)
{
Expand Down
4 changes: 0 additions & 4 deletions src/libraries/tests.proj
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,10 +417,6 @@
</ItemGroup>

<ItemGroup Condition="'$(TestNativeAot)' == 'true' and '$(RunDisabledNativeAotTests)' != 'true'">
<!-- https://github.com/dotnet/runtime/issues/83167 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Numerics.Vectors\tests\System.Numerics.Vectors.Tests.csproj"
Condition="'$(TargetArchitecture)' == 'arm64'" />

<!-- https://github.com/dotnet/runtime/issues/72908 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Reflection.MetadataLoadContext\tests\System.Reflection.MetadataLoadContext.Tests.csproj" />

Expand Down
27 changes: 27 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_83167/Runtime_83167.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Xunit;

public class Runtime_83167
{
[MethodImpl(MethodImplOptions.NoOptimization)]
[Fact]
public static int Problem()
{
Plane p = new Plane (new Vector3(2.0f, 3.0f, 4.0f), 1.0f);
int pH = p.GetHashCode();
EqualityComparer<Plane> c = EqualityComparer<Plane>.Default;
int cH = c.GetHashCode(p);
if (pH != cH)
{
Console.WriteLine($"Failed: {pH:X8} != {cH:X8}");
return 101;
}
return 100;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Optimize>True</Optimize>
<DebugType>None</DebugType>
</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" + ' JIT: fix self-conflicting HFA arg prolog handling for arm64 by AndyAyersMS · Pull Request #92355 · 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
98 changes: 96 additions & 2 deletions src/coreclr/jit/codegencommon.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -2919,6 +2919,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
bool writeThru; // true if the argument gets homed to both stack and register
bool processed; // true after we've processed the argument (and it is in its final location)
bool circular; // true if this register participates in a circular dependency loop.
bool hfaConflict; // arg is part of an HFA that will end up in the same register
// but in a different slot (eg arg in s3 = v3.s[0], needs to end up in v3.s[3])
} regArgTab[max(MAX_REG_ARG + 1, MAX_FLOAT_REG_ARG)] = {};

unsigned varNum;
Expand DownExpand Up@@ -3284,7 +3286,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
* A circular dependency is a set of registers R1, R2, ..., Rn
* such that R1->R2 (that is, R1 needs to be moved to R2), R2->R3, ..., Rn->R1 */

bool change = true;
bool change = true;
bool hasHfaConflict = false;
if (regArgMaskLive)
{
/* Possible circular dependencies still exist; the previous pass was not enough
Expand DownExpand Up@@ -3337,10 +3340,32 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
// This must be a SIMD type that's fully enregistered, but is passed as an HFA.
// Each field will be inserted into the same destination register.
//
assert(varTypeIsSIMD(varDsc));
assert(regArgTab[argNum].slot <= (int)varDsc->lvHfaSlots());
assert(argNum > 0);
assert(regArgTab[argNum - 1].varNum == varNum);

// If the field is passed in the same register as the destination,
// but is in the wrong part of the register, mark it specially so later
// we make sure to move it to the right spot before "freeing" the destination.
//
destRegNum = varDsc->GetRegNum();
if (regNum == destRegNum)
{
// We only get here if the HFA part is not already in the right slot in
// the destination. That is, it is not slot-1.
//
const int slot = regArgTab[argNum].slot;
assert(slot != 1);
JITDUMP("HFA conflict; arg num %u needs to move from %s[%u] to %s[%u]\n", argNum,
getRegName(regNum), 0, getRegName(destRegNum), slot - 1);
regArgTab[argNum].hfaConflict = true;

// We'll need to do a special pass later to resolve these
//
hasHfaConflict = true;
}
regArgMaskLive &= ~genRegMask(regNum);
regArgTab[argNum].circular = false;
change = true;
Expand DownExpand Up@@ -3736,13 +3761,13 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
size = EA_4BYTE;
}
// HVA types...?

/* move the dest reg (begReg) in the extra reg */

assert(xtraReg != REG_NA);

regNumber begRegNum = genMapRegArgNumToRegNum(begReg, destMemType);

GetEmitter()->emitIns_Mov(insCopy, size, xtraReg, begRegNum, /* canSkip */ false);

regSet.verifyRegUsed(xtraReg);
Expand DownExpand Up@@ -3823,6 +3848,75 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
}
}

#if defined(TARGET_ARM64) && defined(FEATURE_SIMD)
// If we saw any hfa conflicts, handle those now.
//
if (hasHfaConflict)
{
// Up above we noticed that there was at least one non-slot-1 HFA arg whose
// destination register was the same as the arg register.
//
// For example, say an HFA was passed as s0-s3 and the destination was v3.
// s3 is in the right register, but not in the right slot in the register.
//
// We handle this by first moving the conflicting part to the right slot
// in the destination (via pass 0 below), and then moving the remaining parts
// to their respective slots (via pass 1).
//
// Note the slot index in the register is one less than value of
// regArgTab[argNum].slot, so a slot-1 hfa arg goes into slot 0 of the destination).
//
// So for the above example, we'd first move the "slot-4" s3 (== v3.s[0]) to v3.s[3].
// Then we can insert s0 to v3.s[0]) and so on.
//
// We can exempt slot-1 cases as the conflicting part is already in the
// right slot, and code lower down correctly handles populating the remaining slots.
//
for (argNum = 0; argNum < argMax; argNum++)
{
if (!regArgTab[argNum].hfaConflict)
{
continue;
}

varNum = regArgTab[argNum].varNum;
varDsc = compiler->lvaGetDesc(varNum);
const regNumber destRegNum = varDsc->GetRegNum();
const var_types regType = regArgTab[argNum].type;
const unsigned firstArgNum = argNum - (regArgTab[argNum].slot - 1);
const unsigned lastArgNum = firstArgNum + varDsc->lvHfaSlots() - 1;

assert(varDsc->lvIsHfa());
assert((argNum >= firstArgNum) && (argNum <= lastArgNum));
assert(destRegNum == genMapRegArgNumToRegNum(argNum, regType));

// Pass 0: move the conflicting part; Pass1: insert everything else
//
for (int pass = 0; pass <= 1; pass++)
{
for (unsigned currentArgNum = firstArgNum; currentArgNum <= lastArgNum; currentArgNum++)
{
const regNumber regNum = genMapRegArgNumToRegNum(currentArgNum, regType);
bool insertArg =
((pass == 0) && (currentArgNum == argNum)) || ((pass == 1) && (currentArgNum != argNum));

if (insertArg)
{
assert(!regArgTab[currentArgNum].processed);

// EA_4BYTE is probably wrong here (and below)
// todo -- suppress self move
GetEmitter()->emitIns_R_R_I_I(INS_mov, EA_4BYTE, destRegNum, regNum,
regArgTab[currentArgNum].slot - 1, 0);
regArgTab[currentArgNum].processed = true;
regArgMaskLive &= ~genRegMask(regNum);
}
}
}
}
}
#endif // defined(TARGET_ARM64) && defined(FEATURE_SIMD)

/* Finally take care of the remaining arguments that must be enregistered */
while (regArgMaskLive)
{
Expand Down
4 changes: 0 additions & 4 deletions src/libraries/tests.proj
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,10 +417,6 @@
</ItemGroup>

<ItemGroup Condition="'$(TestNativeAot)' == 'true' and '$(RunDisabledNativeAotTests)' != 'true'">
<!-- https://github.com/dotnet/runtime/issues/83167 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Numerics.Vectors\tests\System.Numerics.Vectors.Tests.csproj"
Condition="'$(TargetArchitecture)' == 'arm64'" />

<!-- https://github.com/dotnet/runtime/issues/72908 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Reflection.MetadataLoadContext\tests\System.Reflection.MetadataLoadContext.Tests.csproj" />

Expand Down
27 changes: 27 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_83167/Runtime_83167.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Xunit;

public class Runtime_83167
{
[MethodImpl(MethodImplOptions.NoOptimization)]
[Fact]
public static int Problem()
{
Plane p = new Plane (new Vector3(2.0f, 3.0f, 4.0f), 1.0f);
int pH = p.GetHashCode();
EqualityComparer<Plane> c = EqualityComparer<Plane>.Default;
int cH = c.GetHashCode(p);
if (pH != cH)
{
Console.WriteLine($"Failed: {pH:X8} != {cH:X8}");
return 101;
}
return 100;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Optimize>True</Optimize>
<DebugType>None</DebugType>
</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('^' + ".*" + ' JIT: fix self-conflicting HFA arg prolog handling for arm64 by AndyAyersMS · Pull Request #92355 · 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
98 changes: 96 additions & 2 deletions src/coreclr/jit/codegencommon.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -2919,6 +2919,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
bool writeThru; // true if the argument gets homed to both stack and register
bool processed; // true after we've processed the argument (and it is in its final location)
bool circular; // true if this register participates in a circular dependency loop.
bool hfaConflict; // arg is part of an HFA that will end up in the same register
// but in a different slot (eg arg in s3 = v3.s[0], needs to end up in v3.s[3])
} regArgTab[max(MAX_REG_ARG + 1, MAX_FLOAT_REG_ARG)] = {};

unsigned varNum;
Expand DownExpand Up@@ -3284,7 +3286,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
* A circular dependency is a set of registers R1, R2, ..., Rn
* such that R1->R2 (that is, R1 needs to be moved to R2), R2->R3, ..., Rn->R1 */

bool change = true;
bool change = true;
bool hasHfaConflict = false;
if (regArgMaskLive)
{
/* Possible circular dependencies still exist; the previous pass was not enough
Expand DownExpand Up@@ -3337,10 +3340,32 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
// This must be a SIMD type that's fully enregistered, but is passed as an HFA.
// Each field will be inserted into the same destination register.
//
assert(varTypeIsSIMD(varDsc));
assert(regArgTab[argNum].slot <= (int)varDsc->lvHfaSlots());
assert(argNum > 0);
assert(regArgTab[argNum - 1].varNum == varNum);

// If the field is passed in the same register as the destination,
// but is in the wrong part of the register, mark it specially so later
// we make sure to move it to the right spot before "freeing" the destination.
//
destRegNum = varDsc->GetRegNum();
if (regNum == destRegNum)
{
// We only get here if the HFA part is not already in the right slot in
// the destination. That is, it is not slot-1.
//
const int slot = regArgTab[argNum].slot;
assert(slot != 1);
JITDUMP("HFA conflict; arg num %u needs to move from %s[%u] to %s[%u]\n", argNum,
getRegName(regNum), 0, getRegName(destRegNum), slot - 1);
regArgTab[argNum].hfaConflict = true;

// We'll need to do a special pass later to resolve these
//
hasHfaConflict = true;
}
regArgMaskLive &= ~genRegMask(regNum);
regArgTab[argNum].circular = false;
change = true;
Expand DownExpand Up@@ -3736,13 +3761,13 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
size = EA_4BYTE;
}
// HVA types...?

/* move the dest reg (begReg) in the extra reg */

assert(xtraReg != REG_NA);

regNumber begRegNum = genMapRegArgNumToRegNum(begReg, destMemType);

GetEmitter()->emitIns_Mov(insCopy, size, xtraReg, begRegNum, /* canSkip */ false);

regSet.verifyRegUsed(xtraReg);
Expand DownExpand Up@@ -3823,6 +3848,75 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
}
}

#if defined(TARGET_ARM64) && defined(FEATURE_SIMD)
// If we saw any hfa conflicts, handle those now.
//
if (hasHfaConflict)
{
// Up above we noticed that there was at least one non-slot-1 HFA arg whose
// destination register was the same as the arg register.
//
// For example, say an HFA was passed as s0-s3 and the destination was v3.
// s3 is in the right register, but not in the right slot in the register.
//
// We handle this by first moving the conflicting part to the right slot
// in the destination (via pass 0 below), and then moving the remaining parts
// to their respective slots (via pass 1).
//
// Note the slot index in the register is one less than value of
// regArgTab[argNum].slot, so a slot-1 hfa arg goes into slot 0 of the destination).
//
// So for the above example, we'd first move the "slot-4" s3 (== v3.s[0]) to v3.s[3].
// Then we can insert s0 to v3.s[0]) and so on.
//
// We can exempt slot-1 cases as the conflicting part is already in the
// right slot, and code lower down correctly handles populating the remaining slots.
//
for (argNum = 0; argNum < argMax; argNum++)
{
if (!regArgTab[argNum].hfaConflict)
{
continue;
}

varNum = regArgTab[argNum].varNum;
varDsc = compiler->lvaGetDesc(varNum);
const regNumber destRegNum = varDsc->GetRegNum();
const var_types regType = regArgTab[argNum].type;
const unsigned firstArgNum = argNum - (regArgTab[argNum].slot - 1);
const unsigned lastArgNum = firstArgNum + varDsc->lvHfaSlots() - 1;

assert(varDsc->lvIsHfa());
assert((argNum >= firstArgNum) && (argNum <= lastArgNum));
assert(destRegNum == genMapRegArgNumToRegNum(argNum, regType));

// Pass 0: move the conflicting part; Pass1: insert everything else
//
for (int pass = 0; pass <= 1; pass++)
{
for (unsigned currentArgNum = firstArgNum; currentArgNum <= lastArgNum; currentArgNum++)
{
const regNumber regNum = genMapRegArgNumToRegNum(currentArgNum, regType);
bool insertArg =
((pass == 0) && (currentArgNum == argNum)) || ((pass == 1) && (currentArgNum != argNum));

if (insertArg)
{
assert(!regArgTab[currentArgNum].processed);

// EA_4BYTE is probably wrong here (and below)
// todo -- suppress self move
GetEmitter()->emitIns_R_R_I_I(INS_mov, EA_4BYTE, destRegNum, regNum,
regArgTab[currentArgNum].slot - 1, 0);
regArgTab[currentArgNum].processed = true;
regArgMaskLive &= ~genRegMask(regNum);
}
}
}
}
}
#endif // defined(TARGET_ARM64) && defined(FEATURE_SIMD)

/* Finally take care of the remaining arguments that must be enregistered */
while (regArgMaskLive)
{
Expand Down
4 changes: 0 additions & 4 deletions src/libraries/tests.proj
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,10 +417,6 @@
</ItemGroup>

<ItemGroup Condition="'$(TestNativeAot)' == 'true' and '$(RunDisabledNativeAotTests)' != 'true'">
<!-- https://github.com/dotnet/runtime/issues/83167 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Numerics.Vectors\tests\System.Numerics.Vectors.Tests.csproj"
Condition="'$(TargetArchitecture)' == 'arm64'" />

<!-- https://github.com/dotnet/runtime/issues/72908 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Reflection.MetadataLoadContext\tests\System.Reflection.MetadataLoadContext.Tests.csproj" />

Expand Down
27 changes: 27 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_83167/Runtime_83167.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Xunit;

public class Runtime_83167
{
[MethodImpl(MethodImplOptions.NoOptimization)]
[Fact]
public static int Problem()
{
Plane p = new Plane (new Vector3(2.0f, 3.0f, 4.0f), 1.0f);
int pH = p.GetHashCode();
EqualityComparer<Plane> c = EqualityComparer<Plane>.Default;
int cH = c.GetHashCode(p);
if (pH != cH)
{
Console.WriteLine($"Failed: {pH:X8} != {cH:X8}");
return 101;
}
return 100;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Optimize>True</Optimize>
<DebugType>None</DebugType>
</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); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' JIT: fix self-conflicting HFA arg prolog handling for arm64 by AndyAyersMS · Pull Request #92355 · 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
98 changes: 96 additions & 2 deletions src/coreclr/jit/codegencommon.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -2919,6 +2919,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
bool writeThru; // true if the argument gets homed to both stack and register
bool processed; // true after we've processed the argument (and it is in its final location)
bool circular; // true if this register participates in a circular dependency loop.
bool hfaConflict; // arg is part of an HFA that will end up in the same register
// but in a different slot (eg arg in s3 = v3.s[0], needs to end up in v3.s[3])
} regArgTab[max(MAX_REG_ARG + 1, MAX_FLOAT_REG_ARG)] = {};

unsigned varNum;
Expand DownExpand Up@@ -3284,7 +3286,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
* A circular dependency is a set of registers R1, R2, ..., Rn
* such that R1->R2 (that is, R1 needs to be moved to R2), R2->R3, ..., Rn->R1 */

bool change = true;
bool change = true;
bool hasHfaConflict = false;
if (regArgMaskLive)
{
/* Possible circular dependencies still exist; the previous pass was not enough
Expand DownExpand Up@@ -3337,10 +3340,32 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
// This must be a SIMD type that's fully enregistered, but is passed as an HFA.
// Each field will be inserted into the same destination register.
//
assert(varTypeIsSIMD(varDsc));
assert(regArgTab[argNum].slot <= (int)varDsc->lvHfaSlots());
assert(argNum > 0);
assert(regArgTab[argNum - 1].varNum == varNum);

// If the field is passed in the same register as the destination,
// but is in the wrong part of the register, mark it specially so later
// we make sure to move it to the right spot before "freeing" the destination.
//
destRegNum = varDsc->GetRegNum();
if (regNum == destRegNum)
{
// We only get here if the HFA part is not already in the right slot in
// the destination. That is, it is not slot-1.
//
const int slot = regArgTab[argNum].slot;
assert(slot != 1);
JITDUMP("HFA conflict; arg num %u needs to move from %s[%u] to %s[%u]\n", argNum,
getRegName(regNum), 0, getRegName(destRegNum), slot - 1);
regArgTab[argNum].hfaConflict = true;

// We'll need to do a special pass later to resolve these
//
hasHfaConflict = true;
}
regArgMaskLive &= ~genRegMask(regNum);
regArgTab[argNum].circular = false;
change = true;
Expand DownExpand Up@@ -3736,13 +3761,13 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
size = EA_4BYTE;
}
// HVA types...?

/* move the dest reg (begReg) in the extra reg */

assert(xtraReg != REG_NA);

regNumber begRegNum = genMapRegArgNumToRegNum(begReg, destMemType);

GetEmitter()->emitIns_Mov(insCopy, size, xtraReg, begRegNum, /* canSkip */ false);

regSet.verifyRegUsed(xtraReg);
Expand DownExpand Up@@ -3823,6 +3848,75 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
}
}

#if defined(TARGET_ARM64) && defined(FEATURE_SIMD)
// If we saw any hfa conflicts, handle those now.
//
if (hasHfaConflict)
{
// Up above we noticed that there was at least one non-slot-1 HFA arg whose
// destination register was the same as the arg register.
//
// For example, say an HFA was passed as s0-s3 and the destination was v3.
// s3 is in the right register, but not in the right slot in the register.
//
// We handle this by first moving the conflicting part to the right slot
// in the destination (via pass 0 below), and then moving the remaining parts
// to their respective slots (via pass 1).
//
// Note the slot index in the register is one less than value of
// regArgTab[argNum].slot, so a slot-1 hfa arg goes into slot 0 of the destination).
//
// So for the above example, we'd first move the "slot-4" s3 (== v3.s[0]) to v3.s[3].
// Then we can insert s0 to v3.s[0]) and so on.
//
// We can exempt slot-1 cases as the conflicting part is already in the
// right slot, and code lower down correctly handles populating the remaining slots.
//
for (argNum = 0; argNum < argMax; argNum++)
{
if (!regArgTab[argNum].hfaConflict)
{
continue;
}

varNum = regArgTab[argNum].varNum;
varDsc = compiler->lvaGetDesc(varNum);
const regNumber destRegNum = varDsc->GetRegNum();
const var_types regType = regArgTab[argNum].type;
const unsigned firstArgNum = argNum - (regArgTab[argNum].slot - 1);
const unsigned lastArgNum = firstArgNum + varDsc->lvHfaSlots() - 1;

assert(varDsc->lvIsHfa());
assert((argNum >= firstArgNum) && (argNum <= lastArgNum));
assert(destRegNum == genMapRegArgNumToRegNum(argNum, regType));

// Pass 0: move the conflicting part; Pass1: insert everything else
//
for (int pass = 0; pass <= 1; pass++)
{
for (unsigned currentArgNum = firstArgNum; currentArgNum <= lastArgNum; currentArgNum++)
{
const regNumber regNum = genMapRegArgNumToRegNum(currentArgNum, regType);
bool insertArg =
((pass == 0) && (currentArgNum == argNum)) || ((pass == 1) && (currentArgNum != argNum));

if (insertArg)
{
assert(!regArgTab[currentArgNum].processed);

// EA_4BYTE is probably wrong here (and below)
// todo -- suppress self move
GetEmitter()->emitIns_R_R_I_I(INS_mov, EA_4BYTE, destRegNum, regNum,
regArgTab[currentArgNum].slot - 1, 0);
regArgTab[currentArgNum].processed = true;
regArgMaskLive &= ~genRegMask(regNum);
}
}
}
}
}
#endif // defined(TARGET_ARM64) && defined(FEATURE_SIMD)

/* Finally take care of the remaining arguments that must be enregistered */
while (regArgMaskLive)
{
Expand Down
4 changes: 0 additions & 4 deletions src/libraries/tests.proj
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,10 +417,6 @@
</ItemGroup>

<ItemGroup Condition="'$(TestNativeAot)' == 'true' and '$(RunDisabledNativeAotTests)' != 'true'">
<!-- https://github.com/dotnet/runtime/issues/83167 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Numerics.Vectors\tests\System.Numerics.Vectors.Tests.csproj"
Condition="'$(TargetArchitecture)' == 'arm64'" />

<!-- https://github.com/dotnet/runtime/issues/72908 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Reflection.MetadataLoadContext\tests\System.Reflection.MetadataLoadContext.Tests.csproj" />

Expand Down
27 changes: 27 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_83167/Runtime_83167.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Xunit;

public class Runtime_83167
{
[MethodImpl(MethodImplOptions.NoOptimization)]
[Fact]
public static int Problem()
{
Plane p = new Plane (new Vector3(2.0f, 3.0f, 4.0f), 1.0f);
int pH = p.GetHashCode();
EqualityComparer<Plane> c = EqualityComparer<Plane>.Default;
int cH = c.GetHashCode(p);
if (pH != cH)
{
Console.WriteLine($"Failed: {pH:X8} != {cH:X8}");
return 101;
}
return 100;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Optimize>True</Optimize>
<DebugType>None</DebugType>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); JIT: fix self-conflicting HFA arg prolog handling for arm64 by AndyAyersMS · Pull Request #92355 · 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
98 changes: 96 additions & 2 deletions src/coreclr/jit/codegencommon.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -2919,6 +2919,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
bool writeThru; // true if the argument gets homed to both stack and register
bool processed; // true after we've processed the argument (and it is in its final location)
bool circular; // true if this register participates in a circular dependency loop.
bool hfaConflict; // arg is part of an HFA that will end up in the same register
// but in a different slot (eg arg in s3 = v3.s[0], needs to end up in v3.s[3])
} regArgTab[max(MAX_REG_ARG + 1, MAX_FLOAT_REG_ARG)] = {};

unsigned varNum;
Expand DownExpand Up@@ -3284,7 +3286,8 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
* A circular dependency is a set of registers R1, R2, ..., Rn
* such that R1->R2 (that is, R1 needs to be moved to R2), R2->R3, ..., Rn->R1 */

bool change = true;
bool change = true;
bool hasHfaConflict = false;
if (regArgMaskLive)
{
/* Possible circular dependencies still exist; the previous pass was not enough
Expand DownExpand Up@@ -3337,10 +3340,32 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
// This must be a SIMD type that's fully enregistered, but is passed as an HFA.
// Each field will be inserted into the same destination register.
//
assert(varTypeIsSIMD(varDsc));
assert(regArgTab[argNum].slot <= (int)varDsc->lvHfaSlots());
assert(argNum > 0);
assert(regArgTab[argNum - 1].varNum == varNum);

// If the field is passed in the same register as the destination,
// but is in the wrong part of the register, mark it specially so later
// we make sure to move it to the right spot before "freeing" the destination.
//
destRegNum = varDsc->GetRegNum();
if (regNum == destRegNum)
{
// We only get here if the HFA part is not already in the right slot in
// the destination. That is, it is not slot-1.
//
const int slot = regArgTab[argNum].slot;
assert(slot != 1);
JITDUMP("HFA conflict; arg num %u needs to move from %s[%u] to %s[%u]\n", argNum,
getRegName(regNum), 0, getRegName(destRegNum), slot - 1);
regArgTab[argNum].hfaConflict = true;

// We'll need to do a special pass later to resolve these
//
hasHfaConflict = true;
}
regArgMaskLive &= ~genRegMask(regNum);
regArgTab[argNum].circular = false;
change = true;
Expand DownExpand Up@@ -3736,13 +3761,13 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
{
size = EA_4BYTE;
}
// HVA types...?

/* move the dest reg (begReg) in the extra reg */

assert(xtraReg != REG_NA);

regNumber begRegNum = genMapRegArgNumToRegNum(begReg, destMemType);

GetEmitter()->emitIns_Mov(insCopy, size, xtraReg, begRegNum, /* canSkip */ false);

regSet.verifyRegUsed(xtraReg);
Expand DownExpand Up@@ -3823,6 +3848,75 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
}
}

#if defined(TARGET_ARM64) && defined(FEATURE_SIMD)
// If we saw any hfa conflicts, handle those now.
//
if (hasHfaConflict)
{
// Up above we noticed that there was at least one non-slot-1 HFA arg whose
// destination register was the same as the arg register.
//
// For example, say an HFA was passed as s0-s3 and the destination was v3.
// s3 is in the right register, but not in the right slot in the register.
//
// We handle this by first moving the conflicting part to the right slot
// in the destination (via pass 0 below), and then moving the remaining parts
// to their respective slots (via pass 1).
//
// Note the slot index in the register is one less than value of
// regArgTab[argNum].slot, so a slot-1 hfa arg goes into slot 0 of the destination).
//
// So for the above example, we'd first move the "slot-4" s3 (== v3.s[0]) to v3.s[3].
// Then we can insert s0 to v3.s[0]) and so on.
//
// We can exempt slot-1 cases as the conflicting part is already in the
// right slot, and code lower down correctly handles populating the remaining slots.
//
for (argNum = 0; argNum < argMax; argNum++)
{
if (!regArgTab[argNum].hfaConflict)
{
continue;
}

varNum = regArgTab[argNum].varNum;
varDsc = compiler->lvaGetDesc(varNum);
const regNumber destRegNum = varDsc->GetRegNum();
const var_types regType = regArgTab[argNum].type;
const unsigned firstArgNum = argNum - (regArgTab[argNum].slot - 1);
const unsigned lastArgNum = firstArgNum + varDsc->lvHfaSlots() - 1;

assert(varDsc->lvIsHfa());
assert((argNum >= firstArgNum) && (argNum <= lastArgNum));
assert(destRegNum == genMapRegArgNumToRegNum(argNum, regType));

// Pass 0: move the conflicting part; Pass1: insert everything else
//
for (int pass = 0; pass <= 1; pass++)
{
for (unsigned currentArgNum = firstArgNum; currentArgNum <= lastArgNum; currentArgNum++)
{
const regNumber regNum = genMapRegArgNumToRegNum(currentArgNum, regType);
bool insertArg =
((pass == 0) && (currentArgNum == argNum)) || ((pass == 1) && (currentArgNum != argNum));

if (insertArg)
{
assert(!regArgTab[currentArgNum].processed);

// EA_4BYTE is probably wrong here (and below)
// todo -- suppress self move
GetEmitter()->emitIns_R_R_I_I(INS_mov, EA_4BYTE, destRegNum, regNum,
regArgTab[currentArgNum].slot - 1, 0);
regArgTab[currentArgNum].processed = true;
regArgMaskLive &= ~genRegMask(regNum);
}
}
}
}
}
#endif // defined(TARGET_ARM64) && defined(FEATURE_SIMD)

/* Finally take care of the remaining arguments that must be enregistered */
while (regArgMaskLive)
{
Expand Down
4 changes: 0 additions & 4 deletions src/libraries/tests.proj
Original file line numberDiff line numberDiff line change
Expand Up@@ -417,10 +417,6 @@
</ItemGroup>

<ItemGroup Condition="'$(TestNativeAot)' == 'true' and '$(RunDisabledNativeAotTests)' != 'true'">
<!-- https://github.com/dotnet/runtime/issues/83167 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Numerics.Vectors\tests\System.Numerics.Vectors.Tests.csproj"
Condition="'$(TargetArchitecture)' == 'arm64'" />

<!-- https://github.com/dotnet/runtime/issues/72908 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Reflection.MetadataLoadContext\tests\System.Reflection.MetadataLoadContext.Tests.csproj" />

Expand Down
27 changes: 27 additions & 0 deletions src/tests/JIT/Regression/JitBlue/Runtime_83167/Runtime_83167.cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using Xunit;

public class Runtime_83167
{
[MethodImpl(MethodImplOptions.NoOptimization)]
[Fact]
public static int Problem()
{
Plane p = new Plane (new Vector3(2.0f, 3.0f, 4.0f), 1.0f);
int pH = p.GetHashCode();
EqualityComparer<Plane> c = EqualityComparer<Plane>.Default;
int cH = c.GetHashCode(p);
if (pH != cH)
{
Console.WriteLine($"Failed: {pH:X8} != {cH:X8}");
return 101;
}
return 100;
}
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Optimize>True</Optimize>
<DebugType>None</DebugType>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>