Skip to content

Instantly share code, notes, and snippets.

@dsyme
Created July 2, 2018 16:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dsyme/82c49d5cb63d04f3b2b3502d51e6277c to your computer and use it in GitHub Desktop.
Save dsyme/82c49d5cb63d04f3b2b3502d51e6277c to your computer and use it in GitHub Desktop.
(**************************************
F# tests for validating tail calls
are correctly generated by the
compiler and taken by the .NET
runtime.
Tested and passes when ran against
F# v1.9.6.16
Run this script via:
Chris Smith
chrsmith@microsoft.com
**************************************)
open System
// --------------------------------------------------
// Utilities
// --------------------------------------------------
// Check an expression against the result. If they do not
// match the script immediately terminates.
let RunTest msg actual expected =
if actual = expected then
Console.WriteLine(msg + ": YES")
else
Console.WriteLine("\n***** " + msg + ": FAIL\n")
exit 1
let HugeInt = 10000000
// --------------------------------------------------
// Tail Call Tests
// --------------------------------------------------
// Not a tail call - test that this overflows the stack
let rec nonTailCall x =
if x = 0
then 0
else 1 + nonTailCall (x - 1)
// Simple tail call, the compiler will convert to while loop
let rec simpleTail1 x acc =
if x = 0
then acc
else simpleTail1 (x - 1) (acc + 1)
RunTest "simpleTail1" (simpleTail1 HugeInt 0) HugeInt
// Another simple example
let rec simpleTail2 acc = function
|0 -> acc
|i -> simpleTail2 (acc+1) (i-1)
RunTest "simpleTail2" (simpleTail2 HugeInt 0) HugeInt
// Mutually recurisve tail call, compiler will emit tail instruction
let rec mutualTail1IsOdd x =
match x with
| 1 -> true
| n -> mutualTail1IsEven (x - 1)
and mutualTail1IsEven x =
match x with
| 1 -> false
| 0 -> true
| n -> mutualTail1IsOdd (x - 1)
RunTest "mutualTail1IsOdd" (mutualTail1IsOdd HugeInt) false
RunTest "mutualTail1IsEven" (mutualTail1IsEven HugeInt) true
// Interesting parameter types (large stack frame)
// Making mutually recurisve to ensure the .tail call
let rec mutualTail2IsOdd a b c d e f g h i x =
match x with
| 1 -> (true, a, b, c)
| n -> mutualTail2IsEven a b c d e f g h i 0L 0L 0L 0L (x - 1)
and mutualTail2IsEven a b c d e f g h i j k l m x =
match x with
| 1 -> (false, a, b, c)
| 0 -> (true, a, b, c)
| n -> mutualTail2IsOdd a b c d e f g h i (x - 1)
RunTest "mutualTail2IsOdd" (mutualTail2IsOdd 0.0M [| 1 .. 10 |] "str" 0L 0L 0L 0L 0L 0L HugeInt) (false, 0.0M, [| 1 .. 10 |], "str")
RunTest "mutualTail2IsEven" (mutualTail2IsEven 0.0M [| 1 .. 10 |] "str" 0L 0L 0L 0L 0L 0L 0L 0L 0L 0L HugeInt) (true, 0.0M, [| 1 .. 10 |], "str")
// Generic tail call within a type
type TailCallLoop<'T1>() =
let rec f x = if x = 0 then 1 else f (x - 1)
let v5 = f 10000000
member x.Result =
f 10000000 + v5
RunTest "TailCallLoop<int>" ((new TailCallLoop<int>()).Result) 2
RunTest "TailCallLoop<DateTime>" ((new TailCallLoop<DateTime>()).Result) 2
// Generic tail call within a type
type TailCallLoopGenericClassAndMethod<'T1>(resultA: 'T1) =
member this.Method1<'T2>(x:int, resultB: 'T2) =
if x = 0 then (resultA, resultB) else this.Method2 (x - 1, resultB)
member this.Method2<'T2>(x:int, resultB: 'T2) =
if x = 0 then (resultA, resultB) else this.Method1 (x - 1, resultB)
RunTest "TailCallLoopGenericClassAndMethod<byte>.Method1<byte>" ((new TailCallLoopGenericClassAndMethod<byte>(3uy)).Method1<byte>(10000000, 4uy)) (3uy, 4uy)
RunTest "TailCallLoopGenericClassAndMethod<byte>.Method1<int>" ((new TailCallLoopGenericClassAndMethod<byte>(3uy)).Method1<int>(10000000, 4)) (3uy, 4)
RunTest "TailCallLoopGenericClassAndMethod<byte>.Method1<DateTime>" ((new TailCallLoopGenericClassAndMethod<byte>(3uy)).Method1<DateTime>(10000000, DateTime.MinValue)) (3uy, DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethod<byte>.Method1<string>" ((new TailCallLoopGenericClassAndMethod<byte>(3uy)).Method1<string>(10000000, "abc")) (3uy, "abc")
RunTest "TailCallLoopGenericClassAndMethod<int>.Method1<byte>" ((new TailCallLoopGenericClassAndMethod<int>(3)).Method1<byte>(10000000, 4uy)) (3, 4uy)
RunTest "TailCallLoopGenericClassAndMethod<int>.Method1<int>" ((new TailCallLoopGenericClassAndMethod<int>(3)).Method1<int>(10000000, 4)) (3, 4)
RunTest "TailCallLoopGenericClassAndMethod<int>.Method1<DateTime>" ((new TailCallLoopGenericClassAndMethod<int>(3)).Method1<DateTime>(10000000, DateTime.MinValue)) (3, DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethod<int>.Method1<string>" ((new TailCallLoopGenericClassAndMethod<int>(3)).Method1<string>(10000000, "abc")) (3, "abc")
RunTest "TailCallLoopGenericClassAndMethod<DateTime>.Method1<byte>" ((new TailCallLoopGenericClassAndMethod<DateTime>(DateTime.MaxValue)).Method1<byte>(10000000, 4uy)) (DateTime.MaxValue, 4uy)
RunTest "TailCallLoopGenericClassAndMethod<DateTime>.Method1<int>" ((new TailCallLoopGenericClassAndMethod<DateTime>(DateTime.MaxValue)).Method1<int>(10000000, 4)) (DateTime.MaxValue, 4)
RunTest "TailCallLoopGenericClassAndMethod<DateTime>.Method1<DateTime>" ((new TailCallLoopGenericClassAndMethod<DateTime>(DateTime.MaxValue)).Method1<DateTime>(10000000, DateTime.MinValue)) (DateTime.MaxValue, DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethod<DateTime>.Method1<string>" ((new TailCallLoopGenericClassAndMethod<DateTime>(DateTime.MaxValue)).Method1<string>(10000000, "abc")) (DateTime.MaxValue, "abc")
RunTest "TailCallLoopGenericClassAndMethod<string>.Method1<byte>" ((new TailCallLoopGenericClassAndMethod<string>("qq")).Method1<byte>(10000000, 4uy)) ("qq", 4uy)
RunTest "TailCallLoopGenericClassAndMethod<string>.Method1<int>" ((new TailCallLoopGenericClassAndMethod<string>("qq")).Method1<int>(10000000, 4)) ("qq", 4)
RunTest "TailCallLoopGenericClassAndMethod<string>.Method1<DateTime>" ((new TailCallLoopGenericClassAndMethod<string>("qq")).Method1<DateTime>(10000000, DateTime.MinValue)) ("qq", DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethod<string>.Method1<string>" ((new TailCallLoopGenericClassAndMethod<string>("qq")).Method1<string>(10000000, "abc")) ("qq", "abc")
// Generic tail call within a type via virtual method in abstract class hierarchy
[<AbstractClass>]
type AbstractTailCallLoopGenericClassAndMethod<'T1>(resultA: 'T1) =
abstract Method1<'T2> : int * 'T2 -> ('T1 * 'T2)
abstract Method2<'T2> : int * 'T2 -> ('T1 * 'T2)
default this.Method2<'T2>(x:int, resultB: 'T2) =
if x = 0 then (resultA, resultB) else this.Method1 (x - 1, resultB)
type TailCallLoopGenericClassAndMethodAbstractClass<'T1>(resultA: 'T1) =
inherit AbstractTailCallLoopGenericClassAndMethod<'T1>(resultA)
override this.Method1<'T2>(x:int, resultB: 'T2) =
if x = 0 then (resultA, resultB) else this.Method2 (x - 1, resultB)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<byte>.Method1<byte>" ((new TailCallLoopGenericClassAndMethodAbstractClass<byte>(3uy)).Method1<byte>(10000000, 4uy)) (3uy, 4uy)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<byte>.Method1<int>" ((new TailCallLoopGenericClassAndMethodAbstractClass<byte>(3uy)).Method1<int>(10000000, 4)) (3uy, 4)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<byte>.Method1<DateTime>" ((new TailCallLoopGenericClassAndMethodAbstractClass<byte>(3uy)).Method1<DateTime>(10000000, DateTime.MinValue)) (3uy, DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<byte>.Method1<string>" ((new TailCallLoopGenericClassAndMethodAbstractClass<byte>(3uy)).Method1<string>(10000000, "abc")) (3uy, "abc")
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<int>.Method1<byte>" ((new TailCallLoopGenericClassAndMethodAbstractClass<int>(3)).Method1<byte>(10000000, 4uy)) (3, 4uy)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<int>.Method1<int>" ((new TailCallLoopGenericClassAndMethodAbstractClass<int>(3)).Method1<int>(10000000, 4)) (3, 4)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<int>.Method1<DateTime>" ((new TailCallLoopGenericClassAndMethodAbstractClass<int>(3)).Method1<DateTime>(10000000, DateTime.MinValue)) (3, DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<int>.Method1<string>" ((new TailCallLoopGenericClassAndMethodAbstractClass<int>(3)).Method1<string>(10000000, "abc")) (3, "abc")
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<DateTime>.Method1<byte>" ((new TailCallLoopGenericClassAndMethodAbstractClass<DateTime>(DateTime.MaxValue)).Method1<byte>(10000000, 4uy)) (DateTime.MaxValue, 4uy)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<DateTime>.Method1<int>" ((new TailCallLoopGenericClassAndMethodAbstractClass<DateTime>(DateTime.MaxValue)).Method1<int>(10000000, 4)) (DateTime.MaxValue, 4)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<DateTime>.Method1<DateTime>" ((new TailCallLoopGenericClassAndMethodAbstractClass<DateTime>(DateTime.MaxValue)).Method1<DateTime>(10000000, DateTime.MinValue)) (DateTime.MaxValue, DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<DateTime>.Method1<string>" ((new TailCallLoopGenericClassAndMethodAbstractClass<DateTime>(DateTime.MaxValue)).Method1<string>(10000000, "abc")) (DateTime.MaxValue, "abc")
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<string>.Method1<byte>" ((new TailCallLoopGenericClassAndMethodAbstractClass<string>("qq")).Method1<byte>(10000000, 4uy)) ("qq", 4uy)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<string>.Method1<int>" ((new TailCallLoopGenericClassAndMethodAbstractClass<string>("qq")).Method1<int>(10000000, 4)) ("qq", 4)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<string>.Method1<DateTime>" ((new TailCallLoopGenericClassAndMethodAbstractClass<string>("qq")).Method1<DateTime>(10000000, DateTime.MinValue)) ("qq", DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<DatstringeTime>.Method1<string>" ((new TailCallLoopGenericClassAndMethodAbstractClass<string>("qq")).Method1<string>(10000000, "abc")) ("qq", "abc")
// Generic tail call within a type via interface
type InterfaceTailCallLoopGenericInterface<'T1> =
interface
abstract Method1<'T2> : int * 'T2 -> ('T1 * 'T2)
abstract Method2<'T2> : int * 'T2 -> ('T1 * 'T2)
end
type TailCallLoopImplementGenericInterface<'T1>(resultA: 'T1) =
interface InterfaceTailCallLoopGenericInterface<'T1> with
member this.Method1<'T2>(x:int, resultB: 'T2) =
if x = 0 then (resultA, resultB) else (this :> InterfaceTailCallLoopGenericInterface<'T1>).Method2 (x - 1, resultB)
member this.Method2<'T2>(x:int, resultB: 'T2) =
if x = 0 then (resultA, resultB) else (this :> InterfaceTailCallLoopGenericInterface<'T1>).Method1 (x - 1, resultB)
let create<'T> result = TailCallLoopImplementGenericInterface<'T>(result) :> InterfaceTailCallLoopGenericInterface<'T>
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<byte>.Method1<byte>" ((create<byte>(3uy)).Method1<byte>(10000000, 4uy)) (3uy, 4uy)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<byte>.Method1<int>" ((create<byte>(3uy)).Method1<int>(10000000, 4)) (3uy, 4)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<byte>.Method1<DateTime>" ((create<byte>(3uy)).Method1<DateTime>(10000000, DateTime.MinValue)) (3uy, DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<byte>.Method1<string>" ((create<byte>(3uy)).Method1<string>(10000000, "abc")) (3uy, "abc")
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<int>.Method1<byte>" ((create<int>(3)).Method1<byte>(10000000, 4uy)) (3, 4uy)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<int>.Method1<int>" ((create<int>(3)).Method1<int>(10000000, 4)) (3, 4)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<int>.Method1<DateTime>" ((create<int>(3)).Method1<DateTime>(10000000, DateTime.MinValue)) (3, DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<int>.Method1<string>" ((create<int>(3)).Method1<string>(10000000, "abc")) (3, "abc")
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<DateTime>.Method1<byte>" ((create<DateTime>(DateTime.MaxValue)).Method1<byte>(10000000, 4uy)) (DateTime.MaxValue, 4uy)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<DateTime>.Method1<int>" ((create<DateTime>(DateTime.MaxValue)).Method1<int>(10000000, 4)) (DateTime.MaxValue, 4)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<DateTime>.Method1<DateTime>" ((create<DateTime>(DateTime.MaxValue)).Method1<DateTime>(10000000, DateTime.MinValue)) (DateTime.MaxValue, DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<DateTime>.Method1<string>" ((create<DateTime>(DateTime.MaxValue)).Method1<string>(10000000, "abc")) (DateTime.MaxValue, "abc")
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<string>.Method1<byte>" ((create<string>("qq")).Method1<byte>(10000000, 4uy)) ("qq", 4uy)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<string>.Method1<int>" ((create<string>("qq")).Method1<int>(10000000, 4)) ("qq", 4)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<string>.Method1<DateTime>" ((create<string>("qq")).Method1<DateTime>(10000000, DateTime.MinValue)) ("qq", DateTime.MinValue)
RunTest "TailCallLoopGenericClassAndMethodAbstractClass<DatstringeTime>.Method1<string>" ((create<string>("qq")).Method1<string>(10000000, "abc")) ("qq", "abc")
// Generic tail call within a type
type TailCallLoopGenericClass<'T1>(resultA: 'T1) =
member this.Method1(x:int) =
if x = 0 then resultA else this.Method2 (x - 1)
member this.Method2(x:int) =
if x = 0 then resultA else this.Method1 (x - 1)
member this.Result =
this.Method1 10000000
RunTest "TailCallLoopGenericClass<int>" ((new TailCallLoopGenericClass<int>(3)).Result) 3
RunTest "TailCallLoopGenericClass<DateTime>" ((new TailCallLoopGenericClass<DateTime>(DateTime.MinValue)).Result) DateTime.MinValue
RunTest "TailCallLoopGenericClass<string>" ((new TailCallLoopGenericClass<string>("abc")).Result) "abc"
// Generic tail call calling sizeof within a type
type StaticTailCallLoop<'T1>() =
static let rec f x = if x = 0 then sizeof<'T1> else f (x - 1)
static let v5 = f HugeInt
static member Result =
f HugeInt + v5
RunTest "StaticTailCallLoop<int>" (StaticTailCallLoop<int>.Result) 8
RunTest "StaticTailCallLoop<DateTime>" (StaticTailCallLoop<DateTime>.Result) 16
// --------------------------------------------------
// Some regression tests for F# related to tail calls
// --------------------------------------------------
do RunTest "Seq.filter-length1" ({ 1 .. 1000000 } |> Seq.filter (fun n -> n <> 1) |> Seq.length) 999999;;
do RunTest "Seq.filter-length2" ({ 1 .. 1000000 } |> Seq.filter (fun n -> n = 1) |> Seq.length) 1;;
do RunTest "Seq.filter-length3" ({ 1 .. 1000000 } |> Seq.filter (fun n -> n % 2 = 0) |> Seq.length) 500000;;
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment