Add first fsharp test (#39871)
authorJarret Shook <jashoo@microsoft.com>
Thu, 30 Jul 2020 15:27:26 +0000 (08:27 -0700)
committerGitHub <noreply@github.com>
Thu, 30 Jul 2020 15:27:26 +0000 (08:27 -0700)
* Add our first fsharp test

This will test some newly supported codepaths in .Net 5.0

* Working test

* Small change

* Addressed feedback and resolve fsharp dependencies

* Add a separate dependency to be restored and copied

* Fix typo

src/coreclr/tests/build.proj
src/coreclr/tests/publishdependency.targets
src/coreclr/tests/src/Common/test_dependencies_fs/test_dependencies.fsproj [new file with mode: 0644]
src/coreclr/tests/src/Directory.Build.props
src/coreclr/tests/src/dirs.proj
src/coreclr/tests/src/runtest.proj
src/tests/JIT/Directed/tailcall/mutual_recursion.fs [new file with mode: 0644]
src/tests/JIT/Directed/tailcall/mutual_recursion.fsproj [new file with mode: 0644]

index 424cd25..083ab26 100644 (file)
@@ -19,6 +19,7 @@
 
   <ItemGroup>
     <RestoreProjects Include="$(MSBuildThisFileDirectory)scripts\scripts.csproj" />
+    <RestoreProjects Include="$(MSBuildThisFileDirectory)src\Common\test_dependencies_fs\test_dependencies.fsproj" />
     <RestoreProjects Include="$(MSBuildThisFileDirectory)src\Common\test_dependencies\test_dependencies.csproj" />
     <RestoreProjects Include="$(RepoRoot)src\tests\Common\CoreCLRTestLibrary\CoreCLRTestLibrary.csproj" />
     <RestoreProjects Include="$(MSBuildThisFileDirectory)external\external.csproj" />
index 1442ad0..2fb2482 100644 (file)
@@ -16,6 +16,7 @@
     <!-- These projects are individually restored in order. Each
          subsequent restore only copies files that don't already exist
          in CORE_ROOT, so assets from the first project file win. -->
+    <CoreRootProjectFiles Include="$(RepoRoot)src\coreclr\tests\src\Common\test_dependencies_fs\test_dependencies.fsproj" />
     <CoreRootProjectFiles Include="$(RepoRoot)src\coreclr\tests\src\Common\test_dependencies\test_dependencies.csproj" />
   </ItemGroup>
 
diff --git a/src/coreclr/tests/src/Common/test_dependencies_fs/test_dependencies.fsproj b/src/coreclr/tests/src/Common/test_dependencies_fs/test_dependencies.fsproj
new file mode 100644 (file)
index 0000000..ce4a2c2
--- /dev/null
@@ -0,0 +1,26 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <CLRTestKind>BuildOnly</CLRTestKind>
+    <IsTestProject>false</IsTestProject>
+    <TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
+    <DisableRarCache>true</DisableRarCache>
+    <DisablePackageAssetsCache>true</DisablePackageAssetsCache>
+    <RuntimeIdentifiers>win-arm;win-arm64;win-x64;win-x86;$(TargetRid)</RuntimeIdentifiers>
+    <IncludeOOBLibraries>true</IncludeOOBLibraries>
+    <LibrariesConfiguration>Release</LibrariesConfiguration>
+  </PropertyGroup>
+
+  <Target Name="Build" DependsOnTargets="$(TraversalBuildDependsOn)" />
+
+  <PropertyGroup>
+    <ProjectAssetsFile>$(RepoRoot)src\coreclr\tests\src\Common\test_dependencies_fs\obj\project.assets.json</ProjectAssetsFile>
+  </PropertyGroup>
+
+  <Import Project="$(RepositoryEngineeringDir)liveBuilds.targets" />
+
+  <Target Name="AddLibrariesToCoreRootFS" BeforeTargets="AddLibrariesToCoreRoot" DependsOnTargets="ResolveLibrariesFromLocalBuild">
+    <ItemGroup>
+      <RuntimeCopyLocalItems Include="@(LibrariesRuntimeFiles)" />
+    </ItemGroup>
+  </Target>
+</Project>
index 73cb625..4722ae5 100644 (file)
   <PropertyGroup>
     <TargetsUnknownUnix Condition="'$(TargetsUnix)' == 'true' AND '$(TargetOS)' != 'FreeBSD' AND '$(TargetOS)' != 'Linux' AND '$(TargetOS)' != 'NetBSD' AND '$(TargetOS)' != 'OSX' AND '$(TargetOS)' != 'illumos' AND '$(TargetOS)' != 'Solaris'">true</TargetsUnknownUnix>
     <Language Condition="'$(Language)' == '' and  '$(MSBuildProjectExtension)' == '.csproj'">C#</Language>
+    <Language Condition="'$(Language)' == '' and  '$(MSBuildProjectExtension)' == '.fsproj'">F#</Language>
     <Language Condition="'$(Language)' == '' and  '$(MSBuildProjectExtension)' == '.ilproj'">IL</Language>
   </PropertyGroup>
 
index 253aea5..9656ac5 100644 (file)
@@ -24,6 +24,7 @@
 
     <ItemGroup>
       <AllProjects Include="$(TestRoot)**\*.csproj" Exclude="@(DisabledProjects)" />
+      <AllProjects Include="$(TestRoot)**\*.fsproj" Exclude="@(DisabledProjects)" />
       <AllProjects Include="$(TestRoot)**\*.ilproj" Exclude="@(DisabledProjects)" />
     </ItemGroup>
 
index f4d3e4b..1cb606e 100644 (file)
@@ -405,6 +405,9 @@ namespace $([System.String]::Copy($(Category)).Replace(".","_").Replace("\","").
     <MSBuild Projects="$(MSBuildThisFileDirectory)Common\test_dependencies\test_dependencies.csproj"
              Condition=" '$(BuildWrappers)'=='true' " />
 
+    <MSBuild Projects="$(MSBuildThisFileDirectory)Common\test_dependencies_fs\test_dependencies.fsproj"
+             Condition=" '$(BuildWrappers)'=='true' " />
+
     <MSBuild Projects="$(MSBuildProjectFile)"
              Targets="MonoAotCompileTests"
              Properties=""
diff --git a/src/tests/JIT/Directed/tailcall/mutual_recursion.fs b/src/tests/JIT/Directed/tailcall/mutual_recursion.fs
new file mode 100644 (file)
index 0000000..5a06798
--- /dev/null
@@ -0,0 +1,561 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Notes:
+//
+// This test will Stack overflow when built in debug. It relies on the FSharp
+// compiler optimizing the tail calls to loops.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+open System
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+// 16 byte struct
+[<Struct>]
+type Point2D(x: double, y: double) =
+    member _.X = x
+    member _.Y = y
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+// F# compiler will optimize the call away
+let first() =
+    let callee firstArg secondArg thirdArg =
+        if firstArg = 10 then firstArg * secondArg * thirdArg
+        else firstArg + secondArg + thirdArg
+
+    let retVal = callee 10 20 30
+
+    if retVal <> 6000 then
+        printfn "Method -- Failed, expected result: 6000, calculated: %d" retVal
+
+        -1
+    else
+        0
+
+// F# Compiler will optimize the call away and treat this as a loop
+let second() =
+    let rec secondCallee(iteration, firstArg, secondArg, thirdArg) = 
+        if iteration = 0 then
+            firstArg
+        else
+            let mutable retVal =
+                if firstArg = 10 then 
+                    firstArg * secondArg * thirdArg
+                else 
+                    firstArg + secondArg + thirdArg
+
+            secondCallee(iteration - 1, retVal, secondArg, thirdArg)
+
+    let retVal = secondCallee(100, 10, 20, 30)
+
+    if retVal <> 10950 then
+        printfn "Method -- Failed, expected result: 10950, calculated: %d" retVal
+        -2
+    else
+        0
+
+// F# Compiler will optimize the call away and treat this as a loop
+let third() = 
+    let rec thirdCallee(iteration, firstArg, secondArg, thirdArg, point: Point2D, secondPoint: Point2D, thirdPoint: Point2D) =
+        if point.X <> 10.0 then -100
+        else if point.Y <> 20.0 then -101
+        else if secondPoint.X <> 30.0 then -102
+        else if secondPoint.Y <> 40.0 then -102
+        else if thirdPoint.X <> 30.0 then -103
+        else if thirdPoint.Y <> 40.0 then -103
+        else if iteration = 0 then
+            firstArg
+        else
+            let mutable retVal =
+                if firstArg = 10 then 
+                    firstArg * secondArg * thirdArg
+                else 
+                    firstArg + secondArg + thirdArg
+                    
+            if retVal > 5000 then
+                thirdCallee(iteration - 1, retVal, secondArg, thirdArg, point, secondPoint, thirdPoint)
+            else
+                thirdCallee(iteration - 1, secondArg, thirdArg, thirdArg, point, secondPoint, thirdPoint)
+
+    let point = Point2D(10.0, 20.0)
+    let secondPoint = Point2D(30.0, 40.0)
+
+    let retVal = thirdCallee(100, 10, 20, 30, point, secondPoint, secondPoint)
+
+    if retVal <> 10950 then
+        printfn "Method -- Failed, expected result: 10950, calculated: %d" retVal
+        -3
+    else
+        0
+
+// Will create a tail il instruction and force a tail call. This is will become
+// a fast tail call on unix x64 as there is no stack usage
+let fourth() =
+    let rec fourthMethodFirstCallee(iterationCount, firstArg: Point2D, secondArg: Point2D, thirdArg: Point2D, fourthArg: Point2D) = 
+        if firstArg.X <> 10.0 then -100
+        else if firstArg.Y <> 20.0 then -101
+        else if secondArg.X <> 30.0 then -102
+        else if secondArg.Y <> 40.0 then -103
+        else if thirdArg.X <> 10.0 then -104
+        else if thirdArg.Y <> 20.0 then -105
+        else if fourthArg.X <> 30.0 then -106
+        else if fourthArg.Y <> 40.0 then -107
+        else if iterationCount = 0 then
+            100
+        else if iterationCount % 2 = 0 then
+            fourthMethodSecondCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg)
+        else
+            fourthMethodFirstCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg)
+
+    and fourthMethodSecondCallee(iterationCount, firstArg, secondArg, thirdArg, fourthArg) =
+        if firstArg.X <> 10.0 then -150
+        else if firstArg.Y <> 20.0 then -151
+        else if secondArg.X <> 30.0 then -152
+        else if secondArg.Y <> 40.0 then -153
+        else if thirdArg.X <> 10.0 then -154
+        else if thirdArg.Y <> 20.0 then -155
+        else if fourthArg.X <> 30.0 then -156
+        else if fourthArg.Y <> 40.0 then -157
+        else if iterationCount = 0 then
+            101
+        else if iterationCount % 2 = 0 then
+            fourthMethodSecondCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg)
+        else
+            fourthMethodFirstCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg)
+    
+    let point = Point2D(10.0, 20.0)
+    let secondPoint = Point2D(30.0, 40.0)
+
+    let retVal = fourthMethodFirstCallee(1000000, point, secondPoint, point, secondPoint)
+
+    if retVal <> 100 && retVal <> 101 then
+        printfn "Method -- Failed, expected result: 100 or 101, calculated: %d" retVal
+        -4
+    else
+        0
+    
+// Will create a tail il instruction and force a tail call. This is will become
+// a fast tail call on unix x64 as the caller and callee have equal stack size
+let fifth() =
+    let rec fifthMethodFirstCallee(iterationCount, firstArg: Point2D, secondArg: Point2D, thirdArg: Point2D, fourthArg: Point2D, fifthArg: Point2D) = 
+        if firstArg.X <> 10.0 then -100
+        else if firstArg.Y <> 20.0 then -101
+        else if secondArg.X <> 30.0 then -102
+        else if secondArg.Y <> 40.0 then -103
+        else if thirdArg.X <> 10.0 then -104
+        else if thirdArg.Y <> 20.0 then -105
+        else if fourthArg.X <> 30.0 then -106
+        else if fourthArg.Y <> 40.0 then -107
+        else if fifthArg.X <> 10.0 then -108
+        else if fifthArg.Y <> 20.0 then -109
+        else if iterationCount = 0 then
+            100
+        else if iterationCount % 2 = 0 then
+            fifthMethodSecondCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg)
+        else
+            fifthMethodFirstCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg)
+
+    and fifthMethodSecondCallee(iterationCount, firstArg, secondArg, thirdArg, fourthArg, fifthArg) =
+        if firstArg.X <> 10.0 then -150
+        else if firstArg.Y <> 20.0 then -151
+        else if secondArg.X <> 30.0 then -152
+        else if secondArg.Y <> 40.0 then -153
+        else if thirdArg.X <> 10.0 then -154
+        else if thirdArg.Y <> 20.0 then -155
+        else if fourthArg.X <> 30.0 then -156
+        else if fourthArg.Y <> 40.0 then -157
+        else if fifthArg.X <> 10.0 then -158
+        else if fifthArg.Y <> 20.0 then -159
+        else if iterationCount = 0 then
+            101
+        else if iterationCount % 2 = 0 then
+            fifthMethodSecondCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg)
+        else
+            fifthMethodFirstCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg)
+
+    let point = Point2D(10.0, 20.0)
+    let secondPoint = Point2D(30.0, 40.0)
+
+    let retVal = fifthMethodFirstCallee(1000000, point, secondPoint, point, secondPoint, point)
+
+    if retVal <> 100 && retVal <> 101 then
+        printfn "Method -- Failed, expected result: 100 or 101, calculated: %d" retVal
+        -5
+    else
+        0
+
+// Will create a tail il instruction and force a tail call. This is will become
+// a tail call via helper on unix x64 as the caller has less available incoming
+// arg size than the callee
+let sixth() =
+    let rec sixthMethodFirstCallee(iterationCount, firstArg: Point2D, secondArg: Point2D, thirdArg: Point2D, fourthArg: Point2D, fifthArg: Point2D) = 
+        if firstArg.X <> 10.0 then -100
+        else if firstArg.Y <> 20.0 then -101
+        else if secondArg.X <> 30.0 then -102
+        else if secondArg.Y <> 40.0 then -103
+        else if thirdArg.X <> 10.0 then -104
+        else if thirdArg.Y <> 20.0 then -105
+        else if fourthArg.X <> 30.0 then -106
+        else if fourthArg.Y <> 40.0 then -107
+        else if fifthArg.X <> 10.0 then -108
+        else if fifthArg.Y <> 20.0 then -109
+        else if iterationCount = 0 then
+            100
+        else if iterationCount % 2 = 0 then
+            sixthMethodSecondCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg)
+        else
+            sixthMethodFirstCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg)
+
+    and sixthMethodSecondCallee(iterationCount, firstArg, secondArg, thirdArg, fourthArg) =
+        if firstArg.X <> 10.0 then -150
+        else if firstArg.Y <> 20.0 then -151
+        else if secondArg.X <> 30.0 then -152
+        else if secondArg.Y <> 40.0 then -153
+        else if thirdArg.X <> 10.0 then -154
+        else if thirdArg.Y <> 20.0 then -155
+        else if fourthArg.X <> 30.0 then -156
+        else if fourthArg.Y <> 40.0 then -157
+        else if iterationCount = 0 then
+            101
+        else if iterationCount % 2 = 0 then
+            sixthMethodSecondCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg)
+        else
+            sixthMethodFirstCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, thirdArg)
+
+    let point = new Point2D(10.0, 20.0)
+    let secondPoint = new Point2D(30.0, 40.0)
+
+    let retVal = sixthMethodFirstCallee(1000000, point, secondPoint, point, secondPoint, point)
+
+    if retVal <> 100 && retVal <> 101 then
+        printfn "Method -- Failed, expected result: 100 or 101, calculated: %d" retVal
+        -6
+    else
+        0
+
+// Will create a tail il instruction and force a tail call. This is will become
+// a tail call via helper on unix x64 as the caller has less available incoming
+// arg size than the callee
+let seventh() =
+    let rec seventhMethodFirstCallee(iterationCount, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg, seventhArg, eighthArg, ninethArg, tenthArg) =
+        if firstArg <> 1 then -100
+        else if secondArg <> 2 then -101
+        else if thirdArg <> 3 then -102
+        else if fourthArg <> 4 then -103
+        else if fifthArg <> 5 then -104
+        else if sixthArg <> 6 then -105
+        else if seventhArg <> 7 then -106
+        else if eighthArg <> 8 then -107
+        else if ninethArg <> 9 then -108
+        else if tenthArg <> 10 then -109
+        else if iterationCount = 0 then
+            100
+        else if iterationCount % 2 = 0 then
+            seventhMethodSecondCallee(iterationCount)
+        else
+            seventhMethodFirstCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg, seventhArg, eighthArg, ninethArg, tenthArg)
+
+    and seventhMethodSecondCallee(iterationCount) = 
+        if iterationCount = 0 then
+            101
+        else if iterationCount % 2 = 0 then
+            seventhMethodSecondCallee(iterationCount - 1)
+        else
+            seventhMethodFirstCallee(iterationCount - 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+    let retVal = seventhMethodFirstCallee(1000000, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+    if retVal <> 100 && retVal <> 101 then
+        printfn "Method -- Failed, expected result: 100 or 101, calculated: %d" retVal
+        -7
+    else
+        0
+
+// Will create a tail il instruction and force a tail call. This is will become
+// a fast tail call as the caller and callee have the incoming arg size
+let seventhFastTailCall() =
+    let rec seventhMethodFirstCalleeFastTailCall(iterationCount, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg, seventhArg, eighthArg, ninethArg, tenthArg) =
+        if firstArg <> 1 then -100
+        else if secondArg <> 2 then -101
+        else if thirdArg <> 3 then -102
+        else if fourthArg <> 4 then -103
+        else if fifthArg <> 5 then -104
+        else if sixthArg <> 6 then -105
+        else if seventhArg <> 7 then -106
+        else if eighthArg <> 8 then -107
+        else if ninethArg <> 9 then -108
+        else if tenthArg <> 10 then -109
+        else if iterationCount = 0 then
+            100
+        else if iterationCount % 2 = 0 then
+            seventhMethodSecondCalleeFastTailCall(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg, seventhArg, eighthArg, ninethArg, tenthArg)
+        else
+            seventhMethodFirstCalleeFastTailCall(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg, seventhArg, eighthArg, ninethArg, tenthArg)
+
+    and seventhMethodSecondCalleeFastTailCall(iterationCount, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg, seventhArg, eighthArg, ninethArg, tenthArg) = 
+        if firstArg <> 1 then -110
+        else if secondArg <> 2 then -111
+        else if thirdArg <> 3 then -112
+        else if fourthArg <> 4 then -113
+        else if fifthArg <> 5 then -114
+        else if sixthArg <> 6 then -115
+        else if seventhArg <> 7 then -116
+        else if eighthArg <> 8 then -117
+        else if ninethArg <> 9 then -118
+        else if tenthArg <> 10 then -119
+        else if iterationCount = 0 then
+            101
+        else if iterationCount % 2 = 0 then
+            seventhMethodSecondCalleeFastTailCall(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg, seventhArg, eighthArg, ninethArg, tenthArg)
+        else
+            seventhMethodFirstCalleeFastTailCall(iterationCount - 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+    let retVal = seventhMethodFirstCalleeFastTailCall(1000000, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+    if retVal <> 100 && retVal <> 101 then
+        printfn "Method -- Failed, expected result: 100 or 101, calculated: %d" retVal
+        -8
+    else
+        0
+
+// Will create a tail il instruction and force a tail call. This is will become
+// a fast tail call as the caller and callee have the incoming arg size
+let seventhFastTailCallReversed() =
+    let rec seventhMethodFirstCalleeFastTailCallReversed(iterationCount, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg, seventhArg, eighthArg, ninethArg, tenthArg) =
+        if firstArg <> 1 then -100
+        else if secondArg <> 2 then -101
+        else if thirdArg <> 3 then -102
+        else if fourthArg <> 4 then -103
+        else if fifthArg <> 5 then -104
+        else if sixthArg <> 6 then -105
+        else if seventhArg <> 7 then -106
+        else if eighthArg <> 8 then -107
+        else if ninethArg <> 9 then -108
+        else if tenthArg <> 10 then -109
+        else if iterationCount = 0 then
+            100
+        else if iterationCount % 2 = 0 then
+            seventhMethodSecondCalleeFastTailCallReversed(iterationCount - 1, tenthArg, ninethArg, eighthArg, seventhArg, sixthArg, fifthArg, fourthArg, thirdArg, secondArg, firstArg)
+        else
+            seventhMethodFirstCalleeFastTailCallReversed(iterationCount - 1, tenthArg, ninethArg, eighthArg, seventhArg, sixthArg, fifthArg, fourthArg, thirdArg, secondArg, firstArg)
+
+    and seventhMethodSecondCalleeFastTailCallReversed(iterationCount, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg, seventhArg, eighthArg, ninethArg, tenthArg) = 
+        if firstArg <> 10 then -110
+        else if secondArg <> 9 then -111
+        else if thirdArg <> 8 then -112
+        else if fourthArg <> 7 then -113
+        else if fifthArg <> 6 then -114
+        else if sixthArg <> 5 then -115
+        else if seventhArg <> 4 then -116
+        else if eighthArg <> 3 then -117
+        else if ninethArg <> 2 then -118
+        else if tenthArg <> 1 then -119
+        else if iterationCount = 0 then
+            101
+        else if iterationCount % 2 = 0 then
+            seventhMethodSecondCalleeFastTailCallReversed(iterationCount - 1, tenthArg, ninethArg, eighthArg, seventhArg, sixthArg, fifthArg, fourthArg, thirdArg, secondArg, firstArg)
+        else
+            seventhMethodFirstCalleeFastTailCallReversed(iterationCount - 1, tenthArg, ninethArg, eighthArg, seventhArg, sixthArg, fifthArg, fourthArg, thirdArg, secondArg, firstArg)
+
+    let retVal = seventhMethodFirstCalleeFastTailCallReversed(1000000, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+
+    if retVal <> 100 && retVal <> 101 then
+        printfn "Method -- Failed, expected result: 100 or 101, calculated: %d" retVal
+        -8
+    else
+        0
+
+// Will create a tail il instruction and force a tail call. This is will become
+// a tail call via helper on unix x64 as the caller has less available incoming
+// arg size than the callee
+let eight() =
+    let rec eightMethodFirstCallee(iterationCount, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg) =
+        if firstArg <> 1 then -100
+        else if secondArg <> 2 then -101
+        else if thirdArg <> 3 then -102
+        else if fourthArg <> 4 then -103
+        else if fifthArg <> 5 then -104
+        else if sixthArg <> 6 then -105
+        else if iterationCount = 0 then
+            100
+        else if iterationCount % 2 = 0 then
+            eightMethodSecondCallee(iterationCount)
+        else
+            eightMethodFirstCallee(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg)
+
+    and eightMethodSecondCallee(iterationCount) = 
+        if iterationCount = 0 then
+            101
+        else if iterationCount % 2 = 0 then
+            eightMethodSecondCallee(iterationCount - 1)
+        else
+            eightMethodFirstCallee(iterationCount - 1, 1, 2, 3, 4, 5, 6)
+
+    let retVal = eightMethodFirstCallee(1000000, 1, 2, 3, 4, 5, 6)
+
+    if retVal <> 100 && retVal <> 101 then
+        printfn "Method -- Failed, expected result: 100 or 101, calculated: %d" retVal
+        -9
+    else
+        0
+
+// Will create a tail il instruction and force a tail call. This is will become
+// a tail call via helper on unix x64 as the caller has less available incoming
+// arg size than the callee
+let eightFastTailCall() =
+    let rec eightMethodFirstCalleeFastTailCall(iterationCount, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg) =
+        if firstArg <> 1 then -100
+        else if secondArg <> 2 then -101
+        else if thirdArg <> 3 then -102
+        else if fourthArg <> 4 then -103
+        else if fifthArg <> 5 then -104
+        else if sixthArg <> 6 then -105
+        else if iterationCount = 0 then
+            100
+        else if iterationCount % 2 = 0 then
+            eightMethodSecondCalleeFastTailCall(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg)
+        else
+            eightMethodFirstCalleeFastTailCall(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg)
+
+    and eightMethodSecondCalleeFastTailCall(iterationCount, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg) = 
+        if firstArg <> 1 then -100
+        else if secondArg <> 2 then -101
+        else if thirdArg <> 3 then -102
+        else if fourthArg <> 4 then -103
+        else if fifthArg <> 5 then -104
+        else if sixthArg <> 6 then -105
+        else if iterationCount = 0 then
+            101
+        else if iterationCount % 2 = 0 then
+            eightMethodSecondCalleeFastTailCall(iterationCount - 1, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg)
+        else
+            eightMethodFirstCalleeFastTailCall(iterationCount - 1, 1, 2, 3, 4, 5, 6)
+
+    let retVal = eightMethodFirstCalleeFastTailCall(1000000, 1, 2, 3, 4, 5, 6)
+
+    if retVal <> 100 && retVal <> 101 then
+        printfn "Method -- Failed, expected result: 100 or 101, calculated: %d" retVal
+        -10
+    else
+        0
+
+// Will create a tail il instruction and force a tail call. This is will become
+// a tail call via helper on unix x64 as the caller has less available incoming
+// arg size than the callee
+//
+// Reversing the call arguments is a simple way to stress LowerFastTailCall
+let eightFastTailCallReversed() =
+    let rec eightMethodFirstCalleeFastTailCallReversed(iterationCount, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg) =
+        if firstArg <> 1 then -100
+        else if secondArg <> 2 then -101
+        else if thirdArg <> 3 then -102
+        else if fourthArg <> 4 then -103
+        else if fifthArg <> 5 then -104
+        else if sixthArg <> 6 then -105
+        else if iterationCount = 0 then
+            100
+        else if iterationCount % 2 = 0 then
+            eightMethodSecondCalleeFastTailCallReversed(iterationCount - 1, sixthArg, fifthArg, fourthArg, thirdArg, secondArg, firstArg)
+        else
+            eightMethodFirstCalleeFastTailCallReversed(iterationCount - 1, sixthArg, fifthArg, fourthArg, thirdArg, secondArg, firstArg)
+
+    and eightMethodSecondCalleeFastTailCallReversed(iterationCount, firstArg, secondArg, thirdArg, fourthArg, fifthArg, sixthArg) = 
+        if firstArg <> 6 then -100
+        else if secondArg <> 5 then -101
+        else if thirdArg <> 4 then -102
+        else if fourthArg <> 3 then -103
+        else if fifthArg <> 2 then -104
+        else if sixthArg <> 1 then -105
+        else if iterationCount = 0 then
+            101
+        else if iterationCount % 2 = 0 then
+            eightMethodSecondCalleeFastTailCallReversed(iterationCount - 1, sixthArg, fifthArg, fourthArg, thirdArg, secondArg, firstArg)
+        else
+            eightMethodFirstCalleeFastTailCallReversed(iterationCount - 1, sixthArg, fifthArg, fourthArg, thirdArg, secondArg, firstArg)
+
+    let retVal = eightMethodFirstCalleeFastTailCallReversed(1000000, 1, 2, 3, 4, 5, 6)
+
+    if retVal <> 100 && retVal <> 101 then
+        printfn "Method -- Failed, expected result: 100 or 101, calculated: %d" retVal
+        -11
+    else
+        0
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+type Driver() = 
+    // Notes:
+    //
+    // Drive all of the different methods that will be called and account for
+    // timing.
+    member _.Start() =
+        let rec runMethod(iterationCount: int, cb: _ -> int) = 
+            if iterationCount > 0 then
+                let retVal = cb()
+
+                if retVal <> 0 then
+                    retVal
+                else
+                    runMethod(iterationCount - 1, cb)
+            else
+                0
+
+        let runMethodWithTiming(methodName: string, worksOnlyOnNetCore5: bool, iterationCount: int, cb: _ -> int) = 
+            let canRun = (worksOnlyOnNetCore5 && System.Environment.Version.Major > 3) || worksOnlyOnNetCore5 = false
+
+            if canRun then
+                let startTime = DateTime.Now
+                let retVal = runMethod(iterationCount, cb)
+                let endTime = DateTime.Now
+
+                let elapsedTime = (endTime - startTime).TotalMilliseconds
+
+                if retVal <> 0 then
+                    failwith "Incorrect method passed"
+
+                printfn "[%s] - %fms" methodName elapsedTime
+            else
+                printfn "[%s] - skipped. Only works on .Net Core 5.0 and above" methodName
+
+        runMethodWithTiming("FirstMethod", false, 100000, first)
+        runMethodWithTiming("SecondMethod", false, 100000, second)
+
+        // ThirdMethod will SO in debug
+        runMethodWithTiming("ThirdMethod", false, 100000, third)
+
+        // FourthMethod will SO in debug
+        runMethodWithTiming("FourthMethod", false, 10, fourth)
+
+        // The rest of the methods require generic tailcall helper therefore
+        // will only work on >3.1
+
+        // All the following SO in debug
+
+        runMethodWithTiming("FifthMethod", true, 10, fifth)
+        runMethodWithTiming("SixthMethod", true, 10, sixth)
+        runMethodWithTiming("SeventhMethod", true, 10, seventh)
+        runMethodWithTiming("SeventhMethodFastTailCall", false, 10, seventhFastTailCall)
+        runMethodWithTiming("SeventhMethodFastTailCallReversed", false, 10, seventhFastTailCallReversed)
+        runMethodWithTiming("EigthhMethod", true, 10, eight)
+        runMethodWithTiming("EigthMethodFastTailCall", false, 10, eightFastTailCall)
+        runMethodWithTiming("EigthMethodFastTailCallReversed", false, 10, eightFastTailCallReversed)
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+[<EntryPoint>]
+let main argv =
+    let driver = Driver()
+    driver.Start()
+
+    // If we have gotten to this point we have not StackOverflowed. Therefore 
+    // consider this a passing test
+    100
diff --git a/src/tests/JIT/Directed/tailcall/mutual_recursion.fsproj b/src/tests/JIT/Directed/tailcall/mutual_recursion.fsproj
new file mode 100644 (file)
index 0000000..0a9d97a
--- /dev/null
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+  <PropertyGroup>
+    <NoStandardLib>True</NoStandardLib>
+    <Noconfig>True</Noconfig>
+    <Optimize>True</Optimize>
+    <JitOptimizationSensitive>True</JitOptimizationSensitive>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="mutual_recursion.fs" />
+  </ItemGroup>
+</Project>