Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Fix x86 stack probing #23881

Merged
merged 3 commits into from
Apr 12, 2019
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
8 changes: 8 additions & 0 deletions src/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,14 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

void genReturn(GenTree* treeNode);

#if defined(_TARGET_XARCH_)
void genStackPointerConstantAdjustmentWithProbe(ssize_t spDelta, bool hideSpChangeFromEmitter, regNumber regTmp);
void genStackPointerConstantAdjustmentLoopWithProbe(ssize_t spDelta,
bool hideSpChangeFromEmitter,
regNumber regTmp);
void genStackPointerDynamicAdjustmentWithProbe(regNumber regSpDelta, regNumber regTmp);
#endif // defined(_TARGET_XARCH_)

void genLclHeap(GenTree* tree);

bool genIsRegCandidateLocal(GenTree* tree)
Expand Down
2 changes: 1 addition & 1 deletion src/jit/codegenlinear.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1646,7 +1646,7 @@ void CodeGen::genPutArgStkFieldList(GenTreePutArgStk* putArgStk, unsigned outArg
unsigned thisFieldOffset = argOffset + fieldListPtr->gtFieldOffset;
getEmitter()->emitIns_S_R(ins_Store(type), attr, reg, outArgVarNum, thisFieldOffset);

// We can't write beyound the arg area
// We can't write beyond the arg area
assert((thisFieldOffset + EA_SIZE_IN_BYTES(attr)) <= compiler->lvaLclSize(outArgVarNum));
}
}
Expand Down
303 changes: 226 additions & 77 deletions src/jit/codegenxarch.cpp

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions src/jit/target.h
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,15 @@ typedef unsigned char regNumberSmall;
#define INS_stosp INS_stosd
#define INS_r_stosp INS_r_stosd

// Any stack pointer adjustments larger than this (in bytes) when setting up outgoing call arguments
// requires a stack probe. Set it large enough so all normal stack arguments don't get a probe.
#define ARG_STACK_PROBE_THRESHOLD_BYTES 1024

// The number of bytes from the end the last probed page that must also be probed, to allow for some
// small SP adjustments without probes. If zero, then the stack pointer can point to the last byte/word
// on the stack guard page, and must be touched before any further "SUB SP".
#define STACK_PROBE_BOUNDARY_THRESHOLD_BYTES ARG_STACK_PROBE_THRESHOLD_BYTES

#elif defined(_TARGET_AMD64_)
// TODO-AMD64-CQ: Fine tune the following xxBlk threshold values:

Expand Down Expand Up @@ -889,6 +898,9 @@ typedef unsigned char regNumberSmall;
#define INS_stosp INS_stosq
#define INS_r_stosp INS_r_stosq

// AMD64 uses FEATURE_FIXED_OUT_ARGS so this can be zero.
#define STACK_PROBE_BOUNDARY_THRESHOLD_BYTES 0

#elif defined(_TARGET_ARM_)

// TODO-ARM-CQ: Use shift for division by power of 2
Expand Down
88 changes: 88 additions & 0 deletions tests/src/JIT/Methodical/largeframes/skip3/skippage3.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// Passing a very large struct by value on the stack, on arm32 and x86,
// can cause it to be copied from a temp to the outgoing space without
// probing the stack.

using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

namespace BigFrames
{

[StructLayout(LayoutKind.Explicit)]
public struct LargeStruct
{
[FieldOffset(0)]
public int i1;
[FieldOffset(65512)]
public int i2;
}

public class Test
{
public static int iret = 1;

[MethodImplAttribute(MethodImplOptions.NoInlining)]
public static void TestWrite(int i1, int i2, int i3, int i4, LargeStruct s)
{
Console.Write("Enter TestWrite: ");
Console.WriteLine(i1 + i2 + i3 + i4 + s.i2);
iret = 100;
}

[MethodImplAttribute(MethodImplOptions.NoInlining)]
public static void Test1()
{
Console.WriteLine("Enter Test1");
LargeStruct s = new LargeStruct();
s.i2 = 5;
TestWrite(1, 2, 3, 4, s); // 4 int reg args, then struct stack arg
}

[MethodImplAttribute(MethodImplOptions.NoInlining)]
public static void Escape(ref LargeStruct s)
{
}

// A lot of time the stack when we are called has a bunch of committed pages
// before the guard page. So eat up a bunch of stack before doing our test,
// where we want to be near the guard page.
[MethodImplAttribute(MethodImplOptions.NoInlining)]
public static void EatStackThenTest1(int level = 0)
{
LargeStruct s = new LargeStruct();
s.i2 = level;
Escape(ref s);

if (level < 20)
{
EatStackThenTest1(level + 1);
}
else
{
Test1();
}
}

public static int Main()
{
Test1(); // force JIT of this

EatStackThenTest1(); // If that didn't fail, eat stack then try again.

if (iret == 100)
{
Console.WriteLine("TEST PASSED");
}
else
{
Console.WriteLine("TEST FAILED");
}
return iret;
}
}
}
35 changes: 35 additions & 0 deletions tests/src/JIT/Methodical/largeframes/skip3/skippage3.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<AssemblyName>$(MSBuildProjectName)</AssemblyName>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{43F24741-6FD9-4593-92FA-D3252B540A92}</ProjectGuid>
<OutputType>Exe</OutputType>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<CLRTestPriority>1</CLRTestPriority>
</PropertyGroup>
<!-- Default configurations to help VS understand the configurations -->
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
<ItemGroup>
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
<Visible>False</Visible>
</CodeAnalysisDependentAssemblyPaths>
</ItemGroup>
<PropertyGroup>
<DebugType>PdbOnly</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="skippage3.cs" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
<PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
</Project>
61 changes: 61 additions & 0 deletions tests/src/JIT/Methodical/largeframes/skip4/skippage4.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// Passing a very large struct by value on the stack, on arm32 and x86,
// can cause it to be copied from a temp to the outgoing space without
// probing the stack.

using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

namespace BigFrames
{

[StructLayout(LayoutKind.Explicit)]
public struct LargeStructWithRef
{
[FieldOffset(0)]
public int i1;
[FieldOffset(65500)]
public Object o1;
}

public class Test
{
public static int iret = 1;

[MethodImplAttribute(MethodImplOptions.NoInlining)]
public static void TestWrite(LargeStructWithRef s)
{
Console.Write("Enter TestWrite: ");
Console.WriteLine(s.o1.GetHashCode());
iret = 100;
}

[MethodImplAttribute(MethodImplOptions.NoInlining)]
public static void Test1()
{
Console.WriteLine("Enter Test1");
LargeStructWithRef s = new LargeStructWithRef();
s.o1 = new Object();
TestWrite(s);
}

public static int Main()
{
Test1();

if (iret == 100)
{
Console.WriteLine("TEST PASSED");
}
else
{
Console.WriteLine("TEST FAILED");
}
return iret;
}
}
}
35 changes: 35 additions & 0 deletions tests/src/JIT/Methodical/largeframes/skip4/skippage4.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<AssemblyName>$(MSBuildProjectName)</AssemblyName>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{43F24741-6FD9-4593-92FA-D3252B540A92}</ProjectGuid>
<OutputType>Exe</OutputType>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<CLRTestPriority>1</CLRTestPriority>
</PropertyGroup>
<!-- Default configurations to help VS understand the configurations -->
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
<ItemGroup>
<CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
<Visible>False</Visible>
</CodeAnalysisDependentAssemblyPaths>
</ItemGroup>
<PropertyGroup>
<DebugType>PdbOnly</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="skippage4.cs" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
<PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
</Project>
62 changes: 62 additions & 0 deletions tests/src/JIT/Methodical/largeframes/skip4/skippage4_save.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// Passing a very large struct by value on the stack, on arm32 and x86,
// can cause it to be copied from a temp to the outgoing space without
// probing the stack.

using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

namespace BigFrames
{

[StructLayout(LayoutKind.Explicit)]
public struct Struct65500ref
{
[FieldOffset(0)]
public int i1;
[FieldOffset(65496)]
public Object o1;
}

public class Test
{
public static int iret = 1;

[MethodImplAttribute(MethodImplOptions.NoInlining)]
public static void TestWrite(int i1, int i2, int i3, int i4, Struct65500ref s)
{
Console.Write("Enter TestWrite: ");
Console.WriteLine(i1 + i2 + i3 + i4 + s.o1.GetHashCode());
iret = 100;
// Test1(); // recurse
}

[MethodImplAttribute(MethodImplOptions.NoInlining)]
public static void Test1()
{
Console.WriteLine("Enter Test1");
Struct65500ref s = new Struct65500ref();
s.o1 = new Object();
TestWrite(1, 2, 3, 4, s); // 4 int reg args, then struct stack arg
}

public static int Main()
{
Test1();

if (iret == 100)
{
Console.WriteLine("TEST PASSED");
}
else
{
Console.WriteLine("TEST FAILED");
}
return iret;
}
}
}
36 changes: 36 additions & 0 deletions tests/src/JIT/Methodical/largeframes/skip5/skippage5.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

class Program
{
[StructLayout(LayoutKind.Sequential)]
unsafe struct S
{
fixed byte x[65500];
}

class C
{
public S s;
}

static int Main() => Test(new C());

[MethodImpl(MethodImplOptions.NoInlining)]
static void Call(int r0, int r1, int r2, int r3, int r4, int r5, int r6, S s)
{
}

[MethodImpl(MethodImplOptions.NoInlining)]
static int Test(C c)
{
Call(0, 1, 2, 3, 4, 5, 42, c.s);
Console.WriteLine("TEST PASSED");
return 100; // If we don't crash, we pass
}
}
Loading