From 68fa7ed3dc3f28b9571201878a6005f31a260dd9 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 11:45:32 +0200
Subject: [PATCH 01/48] wip
---
src/Compiler/FSharp.Compiler.Service.fsproj | 1 +
src/Compiler/Utilities/Async2.fs | 373 ++++++++++++++++++++
2 files changed, 374 insertions(+)
create mode 100644 src/Compiler/Utilities/Async2.fs
diff --git a/src/Compiler/FSharp.Compiler.Service.fsproj b/src/Compiler/FSharp.Compiler.Service.fsproj
index 5db9b2e1b2..c130e15933 100644
--- a/src/Compiler/FSharp.Compiler.Service.fsproj
+++ b/src/Compiler/FSharp.Compiler.Service.fsproj
@@ -128,6 +128,7 @@
+
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
new file mode 100644
index 0000000000..67be864a3a
--- /dev/null
+++ b/src/Compiler/Utilities/Async2.fs
@@ -0,0 +1,373 @@
+namespace Internal.Utilities.Async2
+
+module internal Implementation =
+
+ open System
+ open System.Threading
+ open System.Threading.Tasks
+
+ open FSharp.Core.CompilerServices.StateMachineHelpers
+
+ open Microsoft.FSharp.Core.CompilerServices
+ open System.Runtime.CompilerServices
+ open System.Runtime.ExceptionServices
+
+ /// A structure that looks like an Awaiter
+ type Awaiter<'Awaiter, 'TResult
+ when 'Awaiter :> ICriticalNotifyCompletion
+ and 'Awaiter: (member get_IsCompleted: unit -> bool)
+ and 'Awaiter: (member GetResult: unit -> 'TResult)> = 'Awaiter
+
+ type Awaitable<'Awaitable, 'Awaiter, 'TResult
+ when 'Awaitable: (member GetAwaiter: unit -> Awaiter<'Awaiter, 'TResult>)> = 'Awaitable
+
+ module Awaiter =
+ let inline isCompleted (awaiter: ^Awaiter) : bool
+ when ^Awaiter : (member get_IsCompleted : unit -> bool) =
+ awaiter.get_IsCompleted()
+
+ let inline getResult (awaiter: ^Awaiter) : ^TResult
+ when ^Awaiter : (member GetResult : unit -> ^TResult) =
+ awaiter.GetResult()
+
+ let inline onCompleted (awaiter: ^Awaiter) (continuation: Action) : unit
+ when ^Awaiter :> INotifyCompletion =
+ awaiter.OnCompleted continuation
+
+ let inline unsafeOnCompleted (awaiter: ^Awaiter) (continuation: Action) : unit
+ when ^Awaiter :> ICriticalNotifyCompletion =
+ awaiter.UnsafeOnCompleted continuation
+
+ type Trampoline private () =
+
+ let ownerThreadId = Thread.CurrentThread.ManagedThreadId
+
+ static let holder = new ThreadLocal<_>(fun () -> Trampoline())
+
+ let mutable pending : Action voption = ValueNone
+ let mutable running = false
+
+ let start (action: Action) =
+ try
+ running <- true
+ action.Invoke()
+ while pending.IsSome do
+ let next = pending.Value
+ pending <- ValueNone
+ next.Invoke()
+ finally
+ running <- false
+
+ let set action =
+ assert (Thread.CurrentThread.ManagedThreadId = ownerThreadId)
+ assert pending.IsNone
+ if running then
+ pending <- ValueSome action
+ else
+ start action
+
+ interface ICriticalNotifyCompletion with
+ member _.OnCompleted(continuation) = set continuation
+ member _.UnsafeOnCompleted(continuation) = set continuation
+
+ static member Current = holder.Value
+
+ module Trampoline =
+ let Awaiter : ICriticalNotifyCompletion = Trampoline.Current
+ let AwaiterRef = ref Awaiter
+
+ module BindContext =
+ []
+ let bindLimit = 100
+
+ let bindCount = new ThreadLocal()
+
+ let inline IncrementBindCount () =
+ bindCount.Value <- bindCount.Value + 1
+ bindCount.Value % bindLimit = 0
+
+ module ExceptionCache =
+ let store = ConditionalWeakTable()
+
+ let inline CaptureOrRetrieve (exn: exn) =
+ match store.TryGetValue exn with
+ | true, edi when edi.SourceException = exn -> edi
+ | _ ->
+ let edi = ExceptionDispatchInfo.Capture exn
+
+ try store.Add(exn, edi) with _ -> ()
+
+ edi
+
+ let inline Throw(exn: exn) =
+ let edi = CaptureOrRetrieve exn
+ edi.Throw()
+ Unchecked.defaultof<_>
+
+ let inline GetResultOrThrow awaiter = try Awaiter.getResult awaiter with exn -> Throw exn
+
+ type Async2 =
+ static let token = AsyncLocal()
+ static member UseToken ct = token.Value <- ct
+ static member val Token = token.Value
+
+ []
+ type Async2Data<'t> =
+ []
+ val mutable Result: 't
+
+ []
+ val mutable MethodBuilder: AsyncTaskMethodBuilder<'t>
+
+ []
+ val mutable Hijack: bool
+
+ type Async2StateMachine<'TOverall> = ResumableStateMachine>
+ type IAsync2StateMachine<'TOverall> = IResumableStateMachine>
+ type Async2ResumptionFunc<'TOverall> = ResumptionFunc>
+ type Async2ResumptionDynamicInfo<'TOverall> = ResumptionDynamicInfo>
+ type Async2Code<'TOverall, 'T> = ResumableCode, 'T>
+
+ []
+ type Async2<'T>(start: bool -> Task<'T>) =
+
+ member internal _.Start() = start false
+
+ member internal _.StartBound() = start true
+
+ []
+ module Async2Code =
+ let inline filterCancellation (catch: exn -> Async2Code<_, _>) (exn: exn) =
+ Async2Code(fun sm ->
+ match exn with
+ | :? OperationCanceledException as oce when oce.CancellationToken = Async2.Token -> raise exn
+ | _ -> (catch exn).Invoke(&sm))
+
+ let inline throwIfCancellationRequested (code: Async2Code<_, _>) =
+ Async2Code(fun sm ->
+ Async2.Token.ThrowIfCancellationRequested()
+ code.Invoke(&sm))
+
+ []
+ type DynamicState =
+ | InitialYield
+ | Running
+ | SetResult
+ | SetException of ExceptionDispatchInfo
+
+ []
+ type DynamicContinuation =
+ | Stop
+ | Immediate
+ | Await of ICriticalNotifyCompletion
+
+ let inline yieldOnBindLimit () =
+ Async2Code<_, _>(fun sm ->
+ if sm.Data.Hijack then
+ let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm)
+ if not __stack_yield_fin then
+ sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(Trampoline.AwaiterRef, &sm)
+ __stack_yield_fin
+ else
+ true
+ )
+
+ type Async2Builder() =
+
+ member inline _.Delay(generator: unit -> Async2Code<'TOverall, 'T>) : Async2Code<'TOverall, 'T> =
+ ResumableCode.Delay(fun () -> generator () |> throwIfCancellationRequested)
+
+ []
+ member inline _.Zero() : Async2Code<'TOverall, unit> = ResumableCode.Zero()
+
+ member inline _.Return(value: 'T) = Async2Code(fun sm -> sm.Data.Result <- value; true)
+
+ member inline _.Combine
+ (code1: Async2Code<'TOverall, unit>, code2: Async2Code<'TOverall, 'T>)
+ : Async2Code<'TOverall, 'T> =
+ ResumableCode.Combine(code1, code2)
+
+ member inline _.While
+ ([] condition: unit -> bool, body: Async2Code<'TOverall, unit>)
+ : Async2Code<'TOverall, unit> =
+ ResumableCode.While(condition, throwIfCancellationRequested body)
+
+ member inline _.TryWith
+ (body: Async2Code<'TOverall, 'T>, catch: exn -> Async2Code<'TOverall, 'T>)
+ : Async2Code<'TOverall, 'T> =
+ ResumableCode.TryWith(body, filterCancellation catch)
+
+ member inline _.TryFinally
+ (body: Async2Code<'TOverall, 'T>, [] compensation: unit -> unit)
+ : Async2Code<'TOverall, 'T> =
+ ResumableCode.TryFinally(
+ body,
+ ResumableCode<_, _>(fun _sm ->
+ compensation ()
+ true)
+ )
+
+ member inline _.Using<'Resource, 'TOverall, 'T when 'Resource :> IDisposable | null>
+ (resource: 'Resource, body: 'Resource -> Async2Code<'TOverall, 'T>)
+ : Async2Code<'TOverall, 'T> =
+ ResumableCode.Using(resource, body)
+
+ member inline _.For(sequence: seq<'T>, body: 'T -> Async2Code<'TOverall, unit>) : Async2Code<'TOverall, unit> =
+ ResumableCode.For(sequence, fun x -> body x |> throwIfCancellationRequested)
+
+ []
+ static member inline BindDynamic(sm: byref>, awaiter, continuation: _ -> Async2Code<_, _>) =
+ if Awaiter.isCompleted awaiter then
+ (Awaiter.getResult awaiter |> continuation).Invoke(&sm)
+ else
+ let resumptionFunc = Async2ResumptionFunc(fun sm ->
+ let result = ExceptionCache.GetResultOrThrow awaiter
+ (continuation result).Invoke(&sm))
+ sm.ResumptionDynamicInfo.ResumptionFunc <- resumptionFunc
+ sm.ResumptionDynamicInfo.ResumptionData <- awaiter :> ICriticalNotifyCompletion
+ false
+
+ []
+ member inline _.Bind
+ (awaiter, continuation: 'U -> Async2Code<'Data, 'T>)
+ : Async2Code<'Data, 'T> =
+ Async2Code(fun sm ->
+ if __useResumableCode then
+ if Awaiter.isCompleted awaiter then
+ continuation(ExceptionCache.GetResultOrThrow awaiter).Invoke(&sm)
+ else
+ let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm)
+ if __stack_yield_fin then
+ continuation(ExceptionCache.GetResultOrThrow awaiter).Invoke(&sm)
+ else
+ let mutable __stack_awaiter = awaiter
+ sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&__stack_awaiter, &sm)
+ false
+ else
+ Async2Builder.BindDynamic(&sm, awaiter, continuation)
+ )
+
+ []
+ member inline this.ReturnFrom(awaiter) : Async2Code<'T, 'T> = this.Bind(awaiter, this.Return)
+
+ static member inline RunDynamic(code: Async2Code<'T, 'T>) : Async2<'T> =
+ let initialResumptionFunc = Async2ResumptionFunc<'T>(fun sm -> code.Invoke &sm)
+
+ let resumptionInfo () =
+ let mutable state = InitialYield
+ { new Async2ResumptionDynamicInfo<'T>(initialResumptionFunc) with
+ member info.MoveNext(sm) =
+ let mutable continuation = Stop
+
+ let hijackCheck = if sm.Data.Hijack then Await Trampoline.Awaiter else Immediate
+
+ let current = state
+ match current with
+ | InitialYield ->
+ state <- Running
+ continuation <- hijackCheck
+ | Running ->
+ try
+ let step = info.ResumptionFunc.Invoke(&sm)
+ if step then
+ state <- SetResult
+ continuation <- hijackCheck
+ else
+ continuation <- Await (info.ResumptionData :?> ICriticalNotifyCompletion)
+ with exn ->
+ state <- SetException (ExceptionCache.CaptureOrRetrieve exn)
+ continuation <- hijackCheck
+ | SetResult -> sm.Data.MethodBuilder.SetResult sm.Data.Result
+ | SetException edi -> sm.Data.MethodBuilder.SetException(edi.SourceException)
+
+ match continuation with
+ | Await awaiter ->
+ sm.ResumptionDynamicInfo.ResumptionData <- null
+ let mutable awaiter = awaiter
+ sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm)
+ | Immediate -> info.MoveNext &sm
+ | Stop -> ()
+
+ member _.SetStateMachine(sm, state) =
+ sm.Data.MethodBuilder.SetStateMachine(state)
+ }
+
+ Async2(fun bound ->
+ let mutable copy = Async2StateMachine()
+ copy.ResumptionDynamicInfo <- resumptionInfo ()
+ copy.Data <- Async2Data()
+ copy.Data.Hijack <- bound && BindContext.IncrementBindCount()
+ copy.Data.MethodBuilder <- AsyncTaskMethodBuilder<'T>.Create()
+ copy.Data.MethodBuilder.Start(©)
+ copy.Data.MethodBuilder.Task)
+
+ member inline _.Run(code: Async2Code<'T, 'T>) : Async2<'T> =
+ if __useResumableCode then
+ __stateMachine, _>
+
+ (MoveNextMethodImpl<_>(fun sm ->
+ __resumeAt sm.ResumptionPoint
+ let mutable error = null
+
+ let __stack_go1 = yieldOnBindLimit().Invoke(&sm)
+ if __stack_go1 then
+ try
+ let __stack_code_fin = code.Invoke(&sm)
+ if __stack_code_fin then
+ let __stack_go2 = yieldOnBindLimit().Invoke(&sm)
+ if __stack_go2 then
+ sm.Data.MethodBuilder.SetResult(sm.Data.Result)
+ with exn ->
+ error <- ExceptionCache.CaptureOrRetrieve exn
+
+ if error <> null then
+ let __stack_go2 = yieldOnBindLimit().Invoke(&sm)
+ if __stack_go2 then
+ sm.Data.MethodBuilder.SetException(error.SourceException)
+ ))
+
+ (SetStateMachineMethodImpl<_>(fun sm state -> sm.Data.MethodBuilder.SetStateMachine state))
+
+ (AfterCode<_, _>(fun sm ->
+ let mutable copy = sm
+ Async2(fun bound ->
+ copy.Data <- Async2Data()
+ copy.Data.Hijack <- bound && BindContext.IncrementBindCount()
+ copy.Data.MethodBuilder <- AsyncTaskMethodBuilder<'T>.Create()
+ copy.Data.MethodBuilder.Start(©)
+ copy.Data.MethodBuilder.Task)
+ ))
+ else
+ Async2Builder.RunDynamic(code)
+
+ member inline _.Source(code: Async2<_>) = code.StartBound() |> _.GetAwaiter()
+
+ []
+ module Async2AutoOpens =
+
+ let async2 = Async2Builder()
+
+ []
+ module SourceExtensions =
+ type Async2Builder with
+ member inline _.Source(awaitable: Awaitable<_, _, _>) = awaitable.GetAwaiter()
+ member inline _.Source(task: Task) = task.GetAwaiter()
+ member inline _.Source(items: #seq<_>) : seq<_> = upcast items
+
+ module Async2 =
+
+ let run (code: Async2<'t>) =
+ if isNull SynchronizationContext.Current && TaskScheduler.Current = TaskScheduler.Default then
+ code.Start().GetAwaiter().GetResult()
+ else
+ Task.Run<'t>(code.Start).GetAwaiter().GetResult()
+
+ let startAsTask (code: Async2<'t>) = code.Start()
+
+ let runWithoutCancellation code = run code
+
+
+
+
+
+
From b79b3bfec448bb3049c2ca21c9e25627552859f5 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 11:56:35 +0200
Subject: [PATCH 02/48] wip
---
src/Compiler/Utilities/Async2.fs | 63 +++++++++++++++-----------------
1 file changed, 29 insertions(+), 34 deletions(-)
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 67be864a3a..7bfde0a064 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -1,10 +1,28 @@
-namespace Internal.Utilities.Async2
+namespace Internal.Utilities
-module internal Implementation =
+open System
+open System.Threading
+open System.Threading.Tasks
- open System
- open System.Threading
- open System.Threading.Tasks
+[]
+type internal Async2<'T> (start: bool -> Task<'T>) =
+
+ member _.Start() = start false
+ member _.StartBound() = start true
+
+module internal Async2 =
+
+ let run (code: Async2<'t>) =
+ if isNull SynchronizationContext.Current && TaskScheduler.Current = TaskScheduler.Default then
+ code.Start().GetAwaiter().GetResult()
+ else
+ Task.Run<'t>(code.Start).GetAwaiter().GetResult()
+
+ let startAsTask (code: Async2<'t>) = code.Start()
+
+ let runWithoutCancellation code = run code
+
+module internal Async2Implementation =
open FSharp.Core.CompilerServices.StateMachineHelpers
@@ -128,13 +146,6 @@ module internal Implementation =
type Async2ResumptionDynamicInfo<'TOverall> = ResumptionDynamicInfo>
type Async2Code<'TOverall, 'T> = ResumableCode, 'T>
- []
- type Async2<'T>(start: bool -> Task<'T>) =
-
- member internal _.Start() = start false
-
- member internal _.StartBound() = start true
-
[]
module Async2Code =
let inline filterCancellation (catch: exn -> Async2Code<_, _>) (exn: exn) =
@@ -341,33 +352,17 @@ module internal Implementation =
Async2Builder.RunDynamic(code)
member inline _.Source(code: Async2<_>) = code.StartBound() |> _.GetAwaiter()
-
- []
- module Async2AutoOpens =
-
- let async2 = Async2Builder()
-
+
[]
module SourceExtensions =
type Async2Builder with
member inline _.Source(awaitable: Awaitable<_, _, _>) = awaitable.GetAwaiter()
member inline _.Source(task: Task) = task.GetAwaiter()
- member inline _.Source(items: #seq<_>) : seq<_> = upcast items
-
- module Async2 =
-
- let run (code: Async2<'t>) =
- if isNull SynchronizationContext.Current && TaskScheduler.Current = TaskScheduler.Default then
- code.Start().GetAwaiter().GetResult()
- else
- Task.Run<'t>(code.Start).GetAwaiter().GetResult()
+ member inline _.Source(items: #seq<_>) : seq<_> = upcast items
- let startAsTask (code: Async2<'t>) = code.Start()
- let runWithoutCancellation code = run code
-
-
-
-
-
+[]
+module internal Async2AutoOpens =
+ open Async2Implementation
+ let async2 = Async2Builder()
From 7955b652853c4739681a0dd7693455cc0edc0cb8 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 12:05:49 +0200
Subject: [PATCH 03/48] wip
---
src/Compiler/Utilities/Async2.fs | 49 ++++++++++++++++----------------
1 file changed, 25 insertions(+), 24 deletions(-)
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 7bfde0a064..dcb1a6467a 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -4,24 +4,6 @@ open System
open System.Threading
open System.Threading.Tasks
-[]
-type internal Async2<'T> (start: bool -> Task<'T>) =
-
- member _.Start() = start false
- member _.StartBound() = start true
-
-module internal Async2 =
-
- let run (code: Async2<'t>) =
- if isNull SynchronizationContext.Current && TaskScheduler.Current = TaskScheduler.Default then
- code.Start().GetAwaiter().GetResult()
- else
- Task.Run<'t>(code.Start).GetAwaiter().GetResult()
-
- let startAsTask (code: Async2<'t>) = code.Start()
-
- let runWithoutCancellation code = run code
-
module internal Async2Implementation =
open FSharp.Core.CompilerServices.StateMachineHelpers
@@ -128,6 +110,14 @@ module internal Async2Implementation =
static let token = AsyncLocal()
static member UseToken ct = token.Value <- ct
static member val Token = token.Value
+
+ []
+ type internal Async2<'T> (start: bool -> Task<'T>) =
+
+ member _.Start() = start false
+ member _.GetAwaiter() =
+ let hijack = BindContext.IncrementBindCount()
+ (start hijack).GetAwaiter()
[]
type Async2Data<'t> =
@@ -303,11 +293,11 @@ module internal Async2Implementation =
sm.Data.MethodBuilder.SetStateMachine(state)
}
- Async2(fun bound ->
+ Async2(fun hijack ->
let mutable copy = Async2StateMachine()
copy.ResumptionDynamicInfo <- resumptionInfo ()
copy.Data <- Async2Data()
- copy.Data.Hijack <- bound && BindContext.IncrementBindCount()
+ copy.Data.Hijack <- hijack
copy.Data.MethodBuilder <- AsyncTaskMethodBuilder<'T>.Create()
copy.Data.MethodBuilder.Start(©)
copy.Data.MethodBuilder.Task)
@@ -341,9 +331,9 @@ module internal Async2Implementation =
(AfterCode<_, _>(fun sm ->
let mutable copy = sm
- Async2(fun bound ->
+ Async2(fun hijack ->
copy.Data <- Async2Data()
- copy.Data.Hijack <- bound && BindContext.IncrementBindCount()
+ copy.Data.Hijack <- hijack
copy.Data.MethodBuilder <- AsyncTaskMethodBuilder<'T>.Create()
copy.Data.MethodBuilder.Start(©)
copy.Data.MethodBuilder.Task)
@@ -351,8 +341,6 @@ module internal Async2Implementation =
else
Async2Builder.RunDynamic(code)
- member inline _.Source(code: Async2<_>) = code.StartBound() |> _.GetAwaiter()
-
[]
module SourceExtensions =
type Async2Builder with
@@ -366,3 +354,16 @@ module internal Async2AutoOpens =
open Async2Implementation
let async2 = Async2Builder()
+
+module internal Async2 =
+ open Async2Implementation
+
+ let run (code: Async2<'t>) =
+ if isNull SynchronizationContext.Current && TaskScheduler.Current = TaskScheduler.Default then
+ code.Start().GetAwaiter().GetResult()
+ else
+ Task.Run<'t>(code.Start).GetAwaiter().GetResult()
+
+ let startAsTask (code: Async2<'t>) = code.Start()
+
+ let runWithoutCancellation code = run code
From 0c9863c96116c950e4b8c61a30fe9c99b94163ea Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 12:56:34 +0200
Subject: [PATCH 04/48] wip
---
src/Compiler/Utilities/Async2.fs | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index dcb1a6467a..4d4cf566e4 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -4,6 +4,8 @@ open System
open System.Threading
open System.Threading.Tasks
+#nowarn 3513
+
module internal Async2Implementation =
open FSharp.Core.CompilerServices.StateMachineHelpers
@@ -274,7 +276,9 @@ module internal Async2Implementation =
state <- SetResult
continuation <- hijackCheck
else
- continuation <- Await (info.ResumptionData :?> ICriticalNotifyCompletion)
+ match info.ResumptionData with
+ | :? ICriticalNotifyCompletion as awaiter -> continuation <- Await awaiter
+ | _ -> failwith "invalid awaiter"
with exn ->
state <- SetException (ExceptionCache.CaptureOrRetrieve exn)
continuation <- hijackCheck
@@ -308,7 +312,7 @@ module internal Async2Implementation =
(MoveNextMethodImpl<_>(fun sm ->
__resumeAt sm.ResumptionPoint
- let mutable error = null
+ let mutable error = ValueNone
let __stack_go1 = yieldOnBindLimit().Invoke(&sm)
if __stack_go1 then
@@ -319,12 +323,12 @@ module internal Async2Implementation =
if __stack_go2 then
sm.Data.MethodBuilder.SetResult(sm.Data.Result)
with exn ->
- error <- ExceptionCache.CaptureOrRetrieve exn
+ error <- ValueSome (ExceptionCache.CaptureOrRetrieve exn)
- if error <> null then
+ if error.IsSome then
let __stack_go2 = yieldOnBindLimit().Invoke(&sm)
if __stack_go2 then
- sm.Data.MethodBuilder.SetException(error.SourceException)
+ sm.Data.MethodBuilder.SetException(error.Value.SourceException)
))
(SetStateMachineMethodImpl<_>(fun sm state -> sm.Data.MethodBuilder.SetStateMachine state))
From 5719e57568bef7da60b31af69b02ed8f5631a456 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 13:25:12 +0200
Subject: [PATCH 05/48] wip
---
src/Compiler/Utilities/Async2.fs | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 4d4cf566e4..3870cbef71 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -6,6 +6,11 @@ open System.Threading.Tasks
#nowarn 3513
+type Async2 =
+ static let token = AsyncLocal()
+ static member UseToken ct = token.Value <- ct
+ static member val Token = token.Value
+
module internal Async2Implementation =
open FSharp.Core.CompilerServices.StateMachineHelpers
@@ -107,11 +112,6 @@ module internal Async2Implementation =
Unchecked.defaultof<_>
let inline GetResultOrThrow awaiter = try Awaiter.getResult awaiter with exn -> Throw exn
-
- type Async2 =
- static let token = AsyncLocal()
- static member UseToken ct = token.Value <- ct
- static member val Token = token.Value
[]
type internal Async2<'T> (start: bool -> Task<'T>) =
@@ -362,7 +362,8 @@ module internal Async2AutoOpens =
module internal Async2 =
open Async2Implementation
- let run (code: Async2<'t>) =
+ let run ct (code: Async2<'t>) =
+ Async2.UseToken ct
if isNull SynchronizationContext.Current && TaskScheduler.Current = TaskScheduler.Default then
code.Start().GetAwaiter().GetResult()
else
@@ -370,4 +371,4 @@ module internal Async2 =
let startAsTask (code: Async2<'t>) = code.Start()
- let runWithoutCancellation code = run code
+ let runWithoutCancellation code = run CancellationToken.None code
From 35cad0fc39965b053665ca3af475b661befe9202 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 13:32:17 +0200
Subject: [PATCH 06/48] wip
---
src/Compiler/Checking/CheckDeclarations.fs | 18 +-
src/Compiler/Checking/CheckDeclarations.fsi | 4 +-
src/Compiler/Driver/ParseAndCheckInputs.fs | 14 +-
src/Compiler/Driver/ParseAndCheckInputs.fsi | 6 +-
src/Compiler/Facilities/DiagnosticsLogger.fs | 2 +-
src/Compiler/Facilities/DiagnosticsLogger.fsi | 2 +-
src/Compiler/Service/BackgroundCompiler.fs | 6 +-
src/Compiler/Service/FSharpCheckerResults.fs | 10 +-
src/Compiler/Service/FSharpCheckerResults.fsi | 6 +-
src/Compiler/Service/TransparentCompiler.fs | 4 +-
src/Compiler/Utilities/Async2.fs | 7 +-
src/Compiler/Utilities/Cancellable.fs | 174 ------------------
src/Compiler/Utilities/Cancellable.fsi | 65 -------
13 files changed, 41 insertions(+), 277 deletions(-)
diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs
index 07b40b6b11..9f8d035128 100644
--- a/src/Compiler/Checking/CheckDeclarations.fs
+++ b/src/Compiler/Checking/CheckDeclarations.fs
@@ -4871,8 +4871,8 @@ module TcDeclarations =
// Bind module types
//-------------------------------------------------------------------------
-let rec TcSignatureElementNonMutRec (cenv: cenv) parent typeNames endm (env: TcEnv) synSigDecl: Cancellable =
- cancellable {
+let rec TcSignatureElementNonMutRec (cenv: cenv) parent typeNames endm (env: TcEnv) synSigDecl: Async2 =
+ async2 {
let g = cenv.g
try
match synSigDecl with
@@ -5028,7 +5028,7 @@ let rec TcSignatureElementNonMutRec (cenv: cenv) parent typeNames endm (env: TcE
and TcSignatureElements cenv parent endm env xml mutRecNSInfo defs =
- cancellable {
+ async2 {
// Ensure the .Deref call in UpdateAccModuleOrNamespaceType succeeds
if cenv.compilingCanonicalFslibModuleType then
let checkXmlDocs = cenv.diagnosticOptions.CheckXmlDocs
@@ -5047,7 +5047,7 @@ and TcSignatureElementsNonMutRec cenv parent typeNames endm env defs =
Cancellable.fold (TcSignatureElementNonMutRec cenv parent typeNames endm) env defs
and TcSignatureElementsMutRec cenv parent typeNames m mutRecNSInfo envInitial (defs: SynModuleSigDecl list) =
- cancellable {
+ async2 {
let m = match defs with [] -> m | _ -> defs |> List.map (fun d -> d.Range) |> List.reduce unionRanges
let scopem = (defs, m) ||> List.foldBack (fun h m -> unionRanges h.Range m)
@@ -5102,7 +5102,7 @@ and TcSignatureElementsMutRec cenv parent typeNames m mutRecNSInfo envInitial (d
and TcModuleOrNamespaceSignatureElementsNonMutRec cenv parent env (id, moduleKind, defs, m: range, xml) =
- cancellable {
+ async2 {
let endm = m.EndRange // use end of range for errors
// Create the module type that will hold the results of type checking....
@@ -5260,7 +5260,7 @@ let TcModuleOrNamespaceElementsMutRec (cenv: cenv) parent typeNames m envInitial
/// The non-mutually recursive case for a declaration
let rec TcModuleOrNamespaceElementNonMutRec (cenv: cenv) parent typeNames scopem env synDecl =
- cancellable {
+ async2 {
let g = cenv.g
cenv.synArgNameGenerator.Reset()
let tpenv = emptyUnscopedTyparEnv
@@ -5525,7 +5525,7 @@ and [] TcModuleOrNamespaceElementsNonMutRec cenv parent typeNames endm
and TcModuleOrNamespaceElements cenv parent endm env xml mutRecNSInfo openDecls0 synModuleDecls =
- cancellable {
+ async2 {
// Ensure the deref_nlpath call in UpdateAccModuleOrNamespaceType succeeds
if cenv.compilingCanonicalFslibModuleType then
let checkXmlDocs = cenv.diagnosticOptions.CheckXmlDocs
@@ -5773,7 +5773,7 @@ let CheckOneImplFile
let (ParsedImplFileInput (fileName, isScript, qualNameOfFile, _, implFileFrags, isLastCompiland, _, _)) = synImplFile
let infoReader = InfoReader(g, amap)
- cancellable {
+ async2 {
use _ =
Activity.start "CheckDeclarations.CheckOneImplFile"
[|
@@ -5918,7 +5918,7 @@ let CheckOneImplFile
/// Check an entire signature file
let CheckOneSigFile (g, amap, thisCcu, checkForErrors, conditionalDefines, tcSink, isInternalTestSpanStackReferring, diagnosticOptions) tcEnv (sigFile: ParsedSigFileInput) =
- cancellable {
+ async2 {
use _ =
Activity.start "CheckDeclarations.CheckOneSigFile"
[|
diff --git a/src/Compiler/Checking/CheckDeclarations.fsi b/src/Compiler/Checking/CheckDeclarations.fsi
index 9b06fcc828..1a2be70f80 100644
--- a/src/Compiler/Checking/CheckDeclarations.fsi
+++ b/src/Compiler/Checking/CheckDeclarations.fsi
@@ -60,7 +60,7 @@ val CheckOneImplFile:
ModuleOrNamespaceType option *
ParsedImplFileInput *
FSharpDiagnosticOptions ->
- Cancellable
+ Async2
val CheckOneSigFile:
TcGlobals *
@@ -73,7 +73,7 @@ val CheckOneSigFile:
FSharpDiagnosticOptions ->
TcEnv ->
ParsedSigFileInput ->
- Cancellable
+ Async2
exception NotUpperCaseConstructor of range: range
diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs
index fd6eabbf0f..5b9a156ec6 100644
--- a/src/Compiler/Driver/ParseAndCheckInputs.fs
+++ b/src/Compiler/Driver/ParseAndCheckInputs.fs
@@ -1225,8 +1225,8 @@ let CheckOneInput
tcSink: TcResultsSink,
tcState: TcState,
input: ParsedInput
- ) : Cancellable =
- cancellable {
+ ) : Async2 =
+ async2 {
try
use _ =
Activity.start "ParseAndCheckInputs.CheckOneInput" [| Activity.Tags.fileName, input.FileName |]
@@ -1344,7 +1344,7 @@ let DiagnosticsLoggerForInput (tcConfig: TcConfig, oldLogger) =
/// Typecheck a single file (or interactive entry into F# Interactive)
let CheckOneInputEntry (ctok, checkForErrors, tcConfig: TcConfig, tcImports, tcGlobals, prefixPathOpt) tcState input =
- cancellable {
+ async2 {
// Equip loggers to locally filter w.r.t. scope pragmas in each input
use _ =
UseTransformedDiagnosticsLogger(fun oldLogger -> DiagnosticsLoggerForInput(tcConfig, oldLogger))
@@ -1371,7 +1371,7 @@ let CheckMultipleInputsFinish (results, tcState: TcState) =
(tcEnvAtEndOfLastFile, topAttrs, implFiles, ccuSigsForFiles), tcState
let CheckOneInputAndFinish (checkForErrors, tcConfig: TcConfig, tcImports, tcGlobals, prefixPathOpt, tcSink, tcState, input) =
- cancellable {
+ async2 {
let! result, tcState = CheckOneInput(checkForErrors, tcConfig, tcImports, tcGlobals, prefixPathOpt, tcSink, tcState, input)
let finishedResult = CheckMultipleInputsFinish([ result ], tcState)
return finishedResult
@@ -1445,8 +1445,8 @@ let CheckOneInputWithCallback
_skipImplIfSigExists: bool):
(unit -> bool) * TcConfig * TcImports * TcGlobals * LongIdent option * TcResultsSink * TcState * ParsedInput * bool
)
- : Cancellable> =
- cancellable {
+ : Async2> =
+ async2 {
try
CheckSimulateException tcConfig
@@ -1820,7 +1820,7 @@ let CheckMultipleInputsUsingGraphMode
: Finisher =
let (Finisher(finisher = finisher)) =
- cancellable {
+ async2 {
use _ = UseDiagnosticsLogger logger
let checkForErrors2 () =
diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fsi b/src/Compiler/Driver/ParseAndCheckInputs.fsi
index 281638b568..6233cd17c1 100644
--- a/src/Compiler/Driver/ParseAndCheckInputs.fsi
+++ b/src/Compiler/Driver/ParseAndCheckInputs.fsi
@@ -180,7 +180,7 @@ val CheckOneInput:
tcSink: NameResolution.TcResultsSink *
tcState: TcState *
input: ParsedInput ->
- Cancellable<(TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType) * TcState>
+ Async2<(TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType) * TcState>
val CheckOneInputWithCallback:
node: NodeToTypeCheck ->
@@ -193,7 +193,7 @@ val CheckOneInputWithCallback:
tcState: TcState *
input: ParsedInput *
_skipImplIfSigExists: bool ->
- Cancellable>
+ Async2>
val AddCheckResultsToTcState:
tcGlobals: TcGlobals *
@@ -248,4 +248,4 @@ val CheckOneInputAndFinish:
tcSink: NameResolution.TcResultsSink *
tcState: TcState *
input: ParsedInput ->
- Cancellable<(TcEnv * TopAttribs * CheckedImplFile list * ModuleOrNamespaceType list) * TcState>
+ Async2<(TcEnv * TopAttribs * CheckedImplFile list * ModuleOrNamespaceType list) * TcState>
diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs
index eb96a5f6e0..a7316ab972 100644
--- a/src/Compiler/Facilities/DiagnosticsLogger.fs
+++ b/src/Compiler/Facilities/DiagnosticsLogger.fs
@@ -910,7 +910,7 @@ type StackGuard(maxDepth: int, name: string) =
depth <- depth - 1
[]
- member x.GuardCancellable(original: Cancellable<'T>) =
+ member x.GuardCancellable(original: Async2<'T>) =
Cancellable(fun ct -> x.Guard(fun () -> Cancellable.run ct original))
static member val DefaultDepth =
diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi
index 02471dd383..2a7b041119 100644
--- a/src/Compiler/Facilities/DiagnosticsLogger.fsi
+++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi
@@ -470,7 +470,7 @@ type StackGuard =
[] line: int ->
'T
- member GuardCancellable: Internal.Utilities.Library.Cancellable<'T> -> Internal.Utilities.Library.Cancellable<'T>
+ member GuardCancellable: Internal.Utilities.Library.Async2<'T> -> Internal.Utilities.Library.Async2<'T>
static member GetDepthOption: string -> int
diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs
index 1b00bcb1ff..cf6cd09931 100644
--- a/src/Compiler/Service/BackgroundCompiler.fs
+++ b/src/Compiler/Service/BackgroundCompiler.fs
@@ -330,7 +330,7 @@ type internal BackgroundCompiler
| FSharpReferencedProject.PEReference(getStamp, delayedReader) ->
{ new IProjectReference with
member x.EvaluateRawContents() =
- cancellable {
+ async2 {
let! ilReaderOpt = delayedReader.TryGetILModuleReader()
match ilReaderOpt with
@@ -352,7 +352,7 @@ type internal BackgroundCompiler
| FSharpReferencedProject.ILModuleReference(nm, getStamp, getReader) ->
{ new IProjectReference with
member x.EvaluateRawContents() =
- cancellable {
+ async2 {
let ilReader = getReader ()
let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs
let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
@@ -1294,7 +1294,7 @@ type internal BackgroundCompiler
"BackgroundCompiler.GetProjectOptionsFromScript"
[| Activity.Tags.fileName, fileName; Activity.Tags.userOpName, _userOpName |]
- cancellable {
+ async2 {
// Do we add a reference to FSharp.Compiler.Interactive.Settings by default?
let useFsiAuxLib = defaultArg useFsiAuxLib true
let useSdkRefs = defaultArg useSdkRefs true
diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs
index aa23d1534a..df8538562b 100644
--- a/src/Compiler/Service/FSharpCheckerResults.fs
+++ b/src/Compiler/Service/FSharpCheckerResults.fs
@@ -87,7 +87,7 @@ type DelayedILModuleReader =
// fast path
match box this.result with
| null ->
- cancellable {
+ async2 {
let! ct = Cancellable.token ()
return
@@ -3209,7 +3209,7 @@ module internal ParseAndCheckFile =
suggestNamesForErrors: bool
) =
- cancellable {
+ async2 {
use _ =
Activity.start
"ParseAndCheckFile.CheckOneFile"
@@ -3235,7 +3235,7 @@ module internal ParseAndCheckFile =
let sink = TcResultsSinkImpl(tcGlobals, sourceText = sourceText)
let! resOpt =
- cancellable {
+ async2 {
try
let checkForErrors () =
(parseResults.ParseHadErrors || errHandler.ErrorCount > 0)
@@ -3683,7 +3683,7 @@ type FSharpCheckFileResults
keepAssemblyContents: bool,
suggestNamesForErrors: bool
) =
- cancellable {
+ async2 {
let! tcErrors, tcFileInfo =
ParseAndCheckFile.CheckOneFile(
parseResults,
@@ -3923,7 +3923,7 @@ type FsiInteractiveChecker(legacyReferenceResolver, tcConfig: TcConfig, tcGlobal
let keepAssemblyContents = false
member _.ParseAndCheckInteraction(sourceText: ISourceText, ?userOpName: string) =
- cancellable {
+ async2 {
let userOpName = defaultArg userOpName "Unknown"
let fileName = Path.Combine(tcConfig.implicitIncludeDir, "stdin.fsx")
let suggestNamesForErrors = true // Will always be true, this is just for readability
diff --git a/src/Compiler/Service/FSharpCheckerResults.fsi b/src/Compiler/Service/FSharpCheckerResults.fsi
index 9418d9a4f3..8aefb7f825 100644
--- a/src/Compiler/Service/FSharpCheckerResults.fsi
+++ b/src/Compiler/Service/FSharpCheckerResults.fsi
@@ -46,7 +46,7 @@ type DelayedILModuleReader =
/// Will lazily create the ILModuleReader.
/// Is only evaluated once and can be called by multiple threads.
- member internal TryGetILModuleReader: unit -> Cancellable
+ member internal TryGetILModuleReader: unit -> Async2
/// Unused in this API
type public FSharpUnresolvedReferencesSet = internal FSharpUnresolvedReferencesSet of UnresolvedAssemblyReference list
@@ -501,7 +501,7 @@ type public FSharpCheckFileResults =
parseErrors: FSharpDiagnostic[] *
keepAssemblyContents: bool *
suggestNamesForErrors: bool ->
- Cancellable
+ Async2
/// The result of calling TypeCheckResult including the possibility of abort and background compiler not caught up.
and [] public FSharpCheckFileAnswer =
@@ -618,7 +618,7 @@ type internal FsiInteractiveChecker =
member internal ParseAndCheckInteraction:
sourceText: ISourceText * ?userOpName: string ->
- Cancellable
+ Async2
module internal FSharpCheckerResultsSettings =
val defaultFSharpBinariesDir: string
diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs
index 0c3f01d4a3..1525e75e2f 100644
--- a/src/Compiler/Service/TransparentCompiler.fs
+++ b/src/Compiler/Service/TransparentCompiler.fs
@@ -771,7 +771,7 @@ type internal TransparentCompiler
| FSharpReferencedProjectSnapshot.PEReference(getStamp, delayedReader) ->
{ new IProjectReference with
member x.EvaluateRawContents() =
- cancellable {
+ async2 {
let! ilReaderOpt = delayedReader.TryGetILModuleReader()
match ilReaderOpt with
@@ -793,7 +793,7 @@ type internal TransparentCompiler
| FSharpReferencedProjectSnapshot.ILModuleReference(nm, getStamp, getReader) ->
{ new IProjectReference with
member x.EvaluateRawContents() =
- cancellable {
+ async2 {
let ilReader = getReader ()
let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs
let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 3870cbef71..29a3a3cdc0 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -1,4 +1,4 @@
-namespace Internal.Utilities
+namespace Internal.Utilities.Library
open System
open System.Threading
@@ -344,6 +344,8 @@ module internal Async2Implementation =
))
else
Async2Builder.RunDynamic(code)
+
+ member inline _.Source(code: Async2<_>) = code.GetAwaiter()
[]
module SourceExtensions =
@@ -352,13 +354,14 @@ module internal Async2Implementation =
member inline _.Source(task: Task) = task.GetAwaiter()
member inline _.Source(items: #seq<_>) : seq<_> = upcast items
-
[]
module internal Async2AutoOpens =
open Async2Implementation
let async2 = Async2Builder()
+type Async2<'t> = Async2Implementation.Async2<'t>
+
module internal Async2 =
open Async2Implementation
diff --git a/src/Compiler/Utilities/Cancellable.fs b/src/Compiler/Utilities/Cancellable.fs
index bae9c6f829..f1788f6aa1 100644
--- a/src/Compiler/Utilities/Cancellable.fs
+++ b/src/Compiler/Utilities/Cancellable.fs
@@ -50,177 +50,3 @@ type Cancellable =
match tokenHolder.Value with
| ValueNone -> ()
| ValueSome token -> token.ThrowIfCancellationRequested()
-
-namespace Internal.Utilities.Library
-
-open System
-open System.Threading
-open FSharp.Compiler
-
-open FSharp.Core.CompilerServices.StateMachineHelpers
-
-[]
-type ValueOrCancelled<'TResult> =
- | Value of result: 'TResult
- | Cancelled of ``exception``: OperationCanceledException
-
-[]
-type Cancellable<'T> = Cancellable of (CancellationToken -> ValueOrCancelled<'T>)
-
-module Cancellable =
-
- let inline run (ct: CancellationToken) (Cancellable oper) =
- if ct.IsCancellationRequested then
- ValueOrCancelled.Cancelled(OperationCanceledException ct)
- else
- try
- oper ct
- with
- | :? OperationCanceledException as e when ct.IsCancellationRequested -> ValueOrCancelled.Cancelled e
- | :? OperationCanceledException as e -> InvalidOperationException("Wrong cancellation token", e) |> raise
-
- let fold f acc seq =
- Cancellable(fun ct ->
- let mutable acc = ValueOrCancelled.Value acc
-
- for x in seq do
- match acc with
- | ValueOrCancelled.Value accv -> acc <- run ct (f accv x)
- | ValueOrCancelled.Cancelled _ -> ()
-
- acc)
-
- let runWithoutCancellation comp =
- use _ = Cancellable.UsingToken CancellationToken.None
- let res = run CancellationToken.None comp
-
- match res with
- | ValueOrCancelled.Cancelled _ -> failwith "unexpected cancellation"
- | ValueOrCancelled.Value r -> r
-
- let toAsync c =
- async {
- use! _holder = Cancellable.UseToken()
-
- let! ct = Async.CancellationToken
-
- return!
- Async.FromContinuations(fun (cont, _econt, ccont) ->
- match run ct c with
- | ValueOrCancelled.Value v -> cont v
- | ValueOrCancelled.Cancelled ce -> ccont ce)
- }
-
- let token () = Cancellable(ValueOrCancelled.Value)
-
-type CancellableBuilder() =
-
- member inline _.Delay([] f) =
- Cancellable(fun ct ->
- let (Cancellable g) = f ()
- g ct)
-
- member inline _.Bind(comp, [] k) =
- Cancellable(fun ct ->
-
- __debugPoint ""
-
- match Cancellable.run ct comp with
- | ValueOrCancelled.Value v1 -> Cancellable.run ct (k v1)
- | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1)
-
- member inline _.BindReturn(comp, [] k) =
- Cancellable(fun ct ->
-
- __debugPoint ""
-
- match Cancellable.run ct comp with
- | ValueOrCancelled.Value v1 -> ValueOrCancelled.Value(k v1)
- | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1)
-
- member inline _.Combine(comp1, comp2) =
- Cancellable(fun ct ->
-
- __debugPoint ""
-
- match Cancellable.run ct comp1 with
- | ValueOrCancelled.Value() -> Cancellable.run ct comp2
- | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1)
-
- member inline _.TryWith(comp, [] handler) =
- Cancellable(fun ct ->
-
- __debugPoint ""
-
- let compRes =
- try
- match Cancellable.run ct comp with
- | ValueOrCancelled.Value res -> ValueOrCancelled.Value(Choice1Of2 res)
- | ValueOrCancelled.Cancelled exn -> ValueOrCancelled.Cancelled exn
- with err ->
- ValueOrCancelled.Value(Choice2Of2 err)
-
- match compRes with
- | ValueOrCancelled.Value res ->
- match res with
- | Choice1Of2 r -> ValueOrCancelled.Value r
- | Choice2Of2 err -> Cancellable.run ct (handler err)
- | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1)
-
- member inline _.Using(resource: _ MaybeNull, [] comp) =
- Cancellable(fun ct ->
-
- __debugPoint ""
-
- let body = comp resource
-
- let compRes =
- try
- match Cancellable.run ct body with
- | ValueOrCancelled.Value res -> ValueOrCancelled.Value(Choice1Of2 res)
- | ValueOrCancelled.Cancelled exn -> ValueOrCancelled.Cancelled exn
- with err ->
- ValueOrCancelled.Value(Choice2Of2 err)
-
- match compRes with
- | ValueOrCancelled.Value res ->
- Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicFunctions.Dispose resource
-
- match res with
- | Choice1Of2 r -> ValueOrCancelled.Value r
- | Choice2Of2 err -> raise err
- | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1)
-
- member inline _.TryFinally(comp, [] compensation) =
- Cancellable(fun ct ->
-
- __debugPoint ""
-
- let compRes =
- try
- match Cancellable.run ct comp with
- | ValueOrCancelled.Value res -> ValueOrCancelled.Value(Choice1Of2 res)
- | ValueOrCancelled.Cancelled exn -> ValueOrCancelled.Cancelled exn
- with err ->
- ValueOrCancelled.Value(Choice2Of2 err)
-
- match compRes with
- | ValueOrCancelled.Value res ->
- compensation ()
-
- match res with
- | Choice1Of2 r -> ValueOrCancelled.Value r
- | Choice2Of2 err -> raise err
- | ValueOrCancelled.Cancelled err1 -> ValueOrCancelled.Cancelled err1)
-
- member inline _.Return v =
- Cancellable(fun _ -> ValueOrCancelled.Value v)
-
- member inline _.ReturnFrom(v: Cancellable<'T>) = v
-
- member inline _.Zero() =
- Cancellable(fun _ -> ValueOrCancelled.Value())
-
-[]
-module CancellableAutoOpens =
- let cancellable = CancellableBuilder()
diff --git a/src/Compiler/Utilities/Cancellable.fsi b/src/Compiler/Utilities/Cancellable.fsi
index 0d82faa68c..880a31199f 100644
--- a/src/Compiler/Utilities/Cancellable.fsi
+++ b/src/Compiler/Utilities/Cancellable.fsi
@@ -12,68 +12,3 @@ type Cancellable =
static member CheckAndThrow: unit -> unit
static member TryCheckAndThrow: unit -> unit
-
-namespace Internal.Utilities.Library
-
-open System
-open System.Threading
-
-[]
-type internal ValueOrCancelled<'TResult> =
- | Value of result: 'TResult
- | Cancelled of ``exception``: OperationCanceledException
-
-/// Represents a synchronous, cold-start, cancellable computation with explicit representation of a cancelled result.
-///
-/// A cancellable computation may be cancelled via a CancellationToken, which is propagated implicitly.
-/// If cancellation occurs, it is propagated as data rather than by raising an OperationCanceledException.
-[]
-type internal Cancellable<'T> = Cancellable of (CancellationToken -> ValueOrCancelled<'T>)
-
-module internal Cancellable =
-
- /// Run a cancellable computation using the given cancellation token
- val inline run: ct: CancellationToken -> Cancellable<'T> -> ValueOrCancelled<'T>
-
- val fold: f: ('State -> 'T -> Cancellable<'State>) -> acc: 'State -> seq: seq<'T> -> Cancellable<'State>
-
- /// Run the computation in a mode where it may not be cancelled. The computation never results in a
- /// ValueOrCancelled.Cancelled.
- val runWithoutCancellation: comp: Cancellable<'T> -> 'T
-
- /// Bind the cancellation token associated with the computation
- val token: unit -> Cancellable
-
- val toAsync: Cancellable<'T> -> Async<'T>
-
-type internal CancellableBuilder =
-
- new: unit -> CancellableBuilder
-
- member inline BindReturn: comp: Cancellable<'T> * [] k: ('T -> 'U) -> Cancellable<'U>
-
- member inline Bind: comp: Cancellable<'T> * [] k: ('T -> Cancellable<'U>) -> Cancellable<'U>
-
- member inline Combine: comp1: Cancellable * comp2: Cancellable<'T> -> Cancellable<'T>
-
- member inline Delay: [] f: (unit -> Cancellable<'T>) -> Cancellable<'T>
-
- member inline Return: v: 'T -> Cancellable<'T>
-
- member inline ReturnFrom: v: Cancellable<'T> -> Cancellable<'T>
-
- member inline TryFinally: comp: Cancellable<'T> * [] compensation: (unit -> unit) -> Cancellable<'T>
-
- member inline TryWith:
- comp: Cancellable<'T> * [] handler: (exn -> Cancellable<'T>) -> Cancellable<'T>
-
- member inline Using:
- resource: 'Resource MaybeNull * [] comp: ('Resource MaybeNull -> Cancellable<'T>) ->
- Cancellable<'T>
- when 'Resource :> IDisposable and 'Resource: not struct and 'Resource: not null
-
- member inline Zero: unit -> Cancellable
-
-[]
-module internal CancellableAutoOpens =
- val cancellable: CancellableBuilder
From fecd921f056cd4532daa4a55c132d86d0da952d6 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 13:53:18 +0200
Subject: [PATCH 07/48] wip
---
src/Compiler/Checking/CheckDeclarations.fs | 58 ++++++++++------------
src/Compiler/Driver/ParseAndCheckInputs.fs | 4 +-
2 files changed, 27 insertions(+), 35 deletions(-)
diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs
index 9f8d035128..719063033b 100644
--- a/src/Compiler/Checking/CheckDeclarations.fs
+++ b/src/Compiler/Checking/CheckDeclarations.fs
@@ -5021,7 +5021,7 @@ let rec TcSignatureElementNonMutRec (cenv: cenv) parent typeNames endm (env: TcE
return env
- with RecoverableException exn ->
+ with exn ->
errorRecovery exn endm
return env
}
@@ -5044,7 +5044,13 @@ and TcSignatureElements cenv parent endm env xml mutRecNSInfo defs =
}
and TcSignatureElementsNonMutRec cenv parent typeNames endm env defs =
- Cancellable.fold (TcSignatureElementNonMutRec cenv parent typeNames endm) env defs
+ async2 {
+ match defs with
+ | [] -> return env
+ | def :: rest ->
+ let! env = TcSignatureElementNonMutRec cenv parent typeNames endm env def
+ return! TcSignatureElementsNonMutRec cenv parent typeNames endm env rest
+ }
and TcSignatureElementsMutRec cenv parent typeNames m mutRecNSInfo envInitial (defs: SynModuleSigDecl list) =
async2 {
@@ -5493,20 +5499,17 @@ let rec TcModuleOrNamespaceElementNonMutRec (cenv: cenv) parent typeNames scopem
return
(defns, [], topAttrs), env, envAtEnd
- with RecoverableException exn ->
+ with exn ->
errorRecovery exn synDecl.Range
return ([], [], []), env, env
}
/// The non-mutually recursive case for a sequence of declarations
-and [] TcModuleOrNamespaceElementsNonMutRec cenv parent typeNames endm (defsSoFar, env, envAtEnd) (moreDefs: SynModuleDecl list) (ct: CancellationToken) =
-
- if ct.IsCancellationRequested then
- ValueOrCancelled.Cancelled(OperationCanceledException ct)
- else
+and TcModuleOrNamespaceElementsNonMutRec cenv parent typeNames endm (defsSoFar, env, envAtEnd) (moreDefs: SynModuleDecl list) =
+ async2 {
match moreDefs with
| [] ->
- ValueOrCancelled.Value (List.rev defsSoFar, envAtEnd)
+ return List.rev defsSoFar, envAtEnd
| firstDef :: otherDefs ->
// Lookahead one to find out the scope of the next declaration.
let scopem =
@@ -5515,14 +5518,9 @@ and [] TcModuleOrNamespaceElementsNonMutRec cenv parent typeNames endm
else
unionRanges (List.head otherDefs).Range endm
- let result = Cancellable.run ct (TcModuleOrNamespaceElementNonMutRec cenv parent typeNames scopem env firstDef |> cenv.stackGuard.GuardCancellable)
-
- match result with
- | ValueOrCancelled.Cancelled x ->
- ValueOrCancelled.Cancelled x
- | ValueOrCancelled.Value(firstDef, env, envAtEnd) ->
- TcModuleOrNamespaceElementsNonMutRec cenv parent typeNames endm ((firstDef :: defsSoFar), env, envAtEnd) otherDefs ct
-
+ let! firstDef, env, envAtEnd = TcModuleOrNamespaceElementNonMutRec cenv parent typeNames scopem env firstDef
+ return! TcModuleOrNamespaceElementsNonMutRec cenv parent typeNames endm ((firstDef :: defsSoFar), env, envAtEnd) otherDefs
+ }
and TcModuleOrNamespaceElements cenv parent endm env xml mutRecNSInfo openDecls0 synModuleDecls =
async2 {
@@ -5547,21 +5545,15 @@ and TcModuleOrNamespaceElements cenv parent endm env xml mutRecNSInfo openDecls0
return (moduleContents, topAttrsNew, envAtEnd)
| None ->
- let! ct = Cancellable.token ()
- let result = TcModuleOrNamespaceElementsNonMutRec cenv parent typeNames endm ([], env, env) synModuleDecls ct
-
- match result with
- | ValueOrCancelled.Value(compiledDefs, envAtEnd) ->
- // Apply the functions for each declaration to build the overall expression-builder
- let moduleDefs = List.collect p13 compiledDefs
- let moduleDefs = match openDecls0 with [] -> moduleDefs | _ -> TMDefOpens openDecls0 :: moduleDefs
- let moduleContents = TMDefs moduleDefs
-
- // Collect up the attributes that are global to the file
- let topAttrsNew = List.collect p33 compiledDefs
- return (moduleContents, topAttrsNew, envAtEnd)
- | ValueOrCancelled.Cancelled x ->
- return! Cancellable(fun _ -> ValueOrCancelled.Cancelled x)
+ let! compiledDefs, envAtEnd = TcModuleOrNamespaceElementsNonMutRec cenv parent typeNames endm ([], env, env) synModuleDecls
+ // Apply the functions for each declaration to build the overall expression-builder
+ let moduleDefs = List.collect p13 compiledDefs
+ let moduleDefs = match openDecls0 with [] -> moduleDefs | _ -> TMDefOpens openDecls0 :: moduleDefs
+ let moduleContents = TMDefs moduleDefs
+
+ // Collect up the attributes that are global to the file
+ let topAttrsNew = List.collect p33 compiledDefs
+ return (moduleContents, topAttrsNew, envAtEnd)
}
@@ -5949,7 +5941,7 @@ let CheckOneSigFile (g, amap, thisCcu, checkForErrors, conditionalDefines, tcSin
try
sigFileType |> IterTyconsOfModuleOrNamespaceType (fun tycon ->
FinalTypeDefinitionChecksAtEndOfInferenceScope(cenv.infoReader, tcEnv.NameEnv, cenv.tcSink, false, tcEnv.DisplayEnv, tycon))
- with RecoverableException exn -> errorRecovery exn sigFile.QualifiedName.Range
+ with exn -> errorRecovery exn sigFile.QualifiedName.Range
UpdatePrettyTyparNames.updateModuleOrNamespaceType sigFileType
diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs
index 5b9a156ec6..ab15cee94a 100644
--- a/src/Compiler/Driver/ParseAndCheckInputs.fs
+++ b/src/Compiler/Driver/ParseAndCheckInputs.fs
@@ -1355,7 +1355,7 @@ let CheckOneInputEntry (ctok, checkForErrors, tcConfig: TcConfig, tcImports, tcG
return! CheckOneInput(checkForErrors, tcConfig, tcImports, tcGlobals, prefixPathOpt, TcResultsSink.NoSink, tcState, input)
}
- |> Cancellable.runWithoutCancellation
+ |> Async2.runWithoutCancellation
/// Finish checking multiple files (or one interactive entry into F# Interactive)
let CheckMultipleInputsFinish (results, tcState: TcState) =
@@ -1833,7 +1833,7 @@ let CheckMultipleInputsUsingGraphMode
node
(checkForErrors2, tcConfig, tcImports, tcGlobals, prefixPathOpt, tcSink, currentTcState, input, false)
}
- |> Cancellable.runWithoutCancellation
+ |> Async2.runWithoutCancellation
Finisher(
node,
From f14b126af0c0ed0aa0bdc69baa5e42ea8218ad4a Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 13:54:48 +0200
Subject: [PATCH 08/48] wip
---
src/Compiler/Checking/CheckDeclarations.fs | 3 ---
src/Compiler/Facilities/DiagnosticsLogger.fs | 4 ----
src/Compiler/Facilities/DiagnosticsLogger.fsi | 2 --
3 files changed, 9 deletions(-)
diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs
index 719063033b..fe45d5f8ab 100644
--- a/src/Compiler/Checking/CheckDeclarations.fs
+++ b/src/Compiler/Checking/CheckDeclarations.fs
@@ -5377,7 +5377,6 @@ let rec TcModuleOrNamespaceElementNonMutRec (cenv: cenv) parent typeNames scopem
// Now typecheck.
let! moduleContents, topAttrsNew, envAtEnd =
TcModuleOrNamespaceElements cenv (Parent (mkLocalModuleRef moduleEntity)) endm envForModule xml None [] moduleDefs
- |> cenv.stackGuard.GuardCancellable
// Get the inferred type of the decls and record it in the modul.
moduleEntity.entity_modul_type <- MaybeLazy.Strict moduleTyAcc.Value
@@ -5469,7 +5468,6 @@ let rec TcModuleOrNamespaceElementNonMutRec (cenv: cenv) parent typeNames scopem
let! moduleContents, topAttrs, envAtEnd =
TcModuleOrNamespaceElements cenv parent endm envNS xml mutRecNSInfo [] defs
- |> cenv.stackGuard.GuardCancellable
MutRecBindingChecking.TcMutRecDefns_UpdateNSContents nsInfo
let env, openDecls =
@@ -5790,7 +5788,6 @@ let CheckOneImplFile
let defs = [ for x in implFileFrags -> SynModuleDecl.NamespaceFragment x ]
let! moduleContents, topAttrs, envAtEnd =
TcModuleOrNamespaceElements cenv ParentNone qualNameOfFile.Range envinner PreXmlDoc.Empty None openDecls0 defs
- |> cenv.stackGuard.GuardCancellable
let implFileTypePriorToSig = moduleTyAcc.Value
diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs
index a7316ab972..1ef603a2a9 100644
--- a/src/Compiler/Facilities/DiagnosticsLogger.fs
+++ b/src/Compiler/Facilities/DiagnosticsLogger.fs
@@ -909,10 +909,6 @@ type StackGuard(maxDepth: int, name: string) =
finally
depth <- depth - 1
- []
- member x.GuardCancellable(original: Async2<'T>) =
- Cancellable(fun ct -> x.Guard(fun () -> Cancellable.run ct original))
-
static member val DefaultDepth =
#if DEBUG
GetEnvInteger "FSHARP_DefaultStackGuardDepth" 50
diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi
index 2a7b041119..f545babce8 100644
--- a/src/Compiler/Facilities/DiagnosticsLogger.fsi
+++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi
@@ -470,8 +470,6 @@ type StackGuard =
[] line: int ->
'T
- member GuardCancellable: Internal.Utilities.Library.Async2<'T> -> Internal.Utilities.Library.Async2<'T>
-
static member GetDepthOption: string -> int
/// This represents the global state established as each task function runs as part of the build.
From 9127783e3bcae7bfbd5b332762944827faa6d891 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 16:28:43 +0200
Subject: [PATCH 09/48] fix callsites
---
src/Compiler/Checking/CheckDeclarations.fs | 2 +-
src/Compiler/Interactive/fsi.fs | 2 +-
src/Compiler/Service/BackgroundCompiler.fs | 8 ++++----
src/Compiler/Service/FSharpCheckerResults.fs | 6 +++---
src/Compiler/Service/IncrementalBuild.fs | 2 +-
src/Compiler/Service/TransparentCompiler.fs | 6 +++---
6 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs
index fe45d5f8ab..e6525223fc 100644
--- a/src/Compiler/Checking/CheckDeclarations.fs
+++ b/src/Compiler/Checking/CheckDeclarations.fs
@@ -4870,7 +4870,7 @@ module TcDeclarations =
//-------------------------------------------------------------------------
// Bind module types
//-------------------------------------------------------------------------
-
+#nowarn 3511
let rec TcSignatureElementNonMutRec (cenv: cenv) parent typeNames endm (env: TcEnv) synSigDecl: Async2 =
async2 {
let g = cenv.g
diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs
index 8b09f86fb4..ed1143d386 100644
--- a/src/Compiler/Interactive/fsi.fs
+++ b/src/Compiler/Interactive/fsi.fs
@@ -4797,7 +4797,7 @@ type FsiEvaluationSession
member _.ParseAndCheckInteraction(code) =
fsiInteractionProcessor.ParseAndCheckInteraction(legacyReferenceResolver, fsiInteractionProcessor.CurrentState, code)
- |> Cancellable.runWithoutCancellation
+ |> Async2.runWithoutCancellation
member _.InteractiveChecker = checker
diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs
index cf6cd09931..a728821051 100644
--- a/src/Compiler/Service/BackgroundCompiler.fs
+++ b/src/Compiler/Service/BackgroundCompiler.fs
@@ -343,7 +343,7 @@ type internal BackgroundCompiler
// continue to try to use an on-disk DLL
return ProjectAssemblyDataResult.Unavailable false
}
- |> Cancellable.toAsync
+ |> Async2.toAsync
member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
member x.FileName = delayedReader.OutputFile
@@ -358,7 +358,7 @@ type internal BackgroundCompiler
let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
return ProjectAssemblyDataResult.Available data
}
- |> Cancellable.toAsync
+ |> Async2.toAsync
member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
member x.FileName = nm
@@ -745,7 +745,7 @@ type internal BackgroundCompiler
keepAssemblyContents,
suggestNamesForErrors
)
- |> Cancellable.toAsync
+ |> Async2.toAsync
GraphNode.SetPreferredUILang tcConfig.preferredUiLang
return (parseResults, checkAnswer, sourceText.GetHashCode() |> int64, tcPrior.ProjectTimeStamp)
@@ -1377,7 +1377,7 @@ type internal BackgroundCompiler
return options, (diags @ diagnostics.Diagnostics)
}
- |> Cancellable.toAsync
+ |> Async2.toAsync
member bc.InvalidateConfiguration(options: FSharpProjectOptions, userOpName) =
use _ =
diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs
index df8538562b..de6c9f4801 100644
--- a/src/Compiler/Service/FSharpCheckerResults.fs
+++ b/src/Compiler/Service/FSharpCheckerResults.fs
@@ -88,7 +88,7 @@ type DelayedILModuleReader =
match box this.result with
| null ->
async2 {
- let! ct = Cancellable.token ()
+ let ct = Async2.Token
return
lock this.gate (fun () ->
@@ -118,7 +118,7 @@ type DelayedILModuleReader =
None
| _ -> Some this.result)
}
- | _ -> cancellable.Return(Some this.result)
+ | _ -> async2 { return Some this.result }
[]
type FSharpReferencedProject =
@@ -3931,7 +3931,7 @@ type FsiInteractiveChecker(legacyReferenceResolver, tcConfig: TcConfig, tcGlobal
let parsingOptions =
FSharpParsingOptions.FromTcConfig(tcConfig, [| fileName |], true)
- let! ct = Cancellable.token ()
+ let ct = Async2.Token
let parseErrors, parsedInput, anyErrors =
ParseAndCheckFile.parseFile (
diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs
index 6db19653f9..cf171e78f0 100644
--- a/src/Compiler/Service/IncrementalBuild.fs
+++ b/src/Compiler/Service/IncrementalBuild.fs
@@ -272,7 +272,7 @@ type BoundModel private (
None,
TcResultsSink.WithSink sink,
prevTcInfo.tcState, input )
- |> Cancellable.toAsync
+ |> Async2.toAsync
fileChecked.Trigger fileName
diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs
index 1525e75e2f..f50262a27d 100644
--- a/src/Compiler/Service/TransparentCompiler.fs
+++ b/src/Compiler/Service/TransparentCompiler.fs
@@ -784,7 +784,7 @@ type internal TransparentCompiler
// continue to try to use an on-disk DLL
return ProjectAssemblyDataResult.Unavailable false
}
- |> Cancellable.toAsync
+ |> Async2.toAsync
member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
member x.FileName = delayedReader.OutputFile
@@ -799,7 +799,7 @@ type internal TransparentCompiler
let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData
return ProjectAssemblyDataResult.Available data
}
- |> Cancellable.toAsync
+ |> Async2.toAsync
member x.TryGetLogicalTimeStamp _ = getStamp () |> Some
member x.FileName = nm
@@ -1431,7 +1431,7 @@ type internal TransparentCompiler
prevTcInfo.tcState,
input,
true)
- |> Cancellable.toAsync
+ |> Async2.toAsync
//fileChecked.Trigger fileName
From 9ae962ad8361a48682413a2ddf13e7d6b0322509 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 16:29:56 +0200
Subject: [PATCH 10/48] add InlineIfLambda to fix pass2 duplicate method error
during writing binary (FS2014)
---
src/Compiler/Utilities/Async2.fs | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 29a3a3cdc0..1b0b7a4615 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -116,8 +116,8 @@ module internal Async2Implementation =
[]
type internal Async2<'T> (start: bool -> Task<'T>) =
- member _.Start() = start false
- member _.GetAwaiter() =
+ member inline _.Start() = start false
+ member inline _.GetAwaiter() =
let hijack = BindContext.IncrementBindCount()
(start hijack).GetAwaiter()
@@ -196,7 +196,7 @@ module internal Async2Implementation =
ResumableCode.While(condition, throwIfCancellationRequested body)
member inline _.TryWith
- (body: Async2Code<'TOverall, 'T>, catch: exn -> Async2Code<'TOverall, 'T>)
+ (body: Async2Code<'TOverall, 'T>, [] catch: exn -> Async2Code<'TOverall, 'T>)
: Async2Code<'TOverall, 'T> =
ResumableCode.TryWith(body, filterCancellation catch)
@@ -215,11 +215,11 @@ module internal Async2Implementation =
: Async2Code<'TOverall, 'T> =
ResumableCode.Using(resource, body)
- member inline _.For(sequence: seq<'T>, body: 'T -> Async2Code<'TOverall, unit>) : Async2Code<'TOverall, unit> =
+ member inline _.For(sequence: seq<'T>, [] body: 'T -> Async2Code<'TOverall, unit>) : Async2Code<'TOverall, unit> =
ResumableCode.For(sequence, fun x -> body x |> throwIfCancellationRequested)
[]
- static member inline BindDynamic(sm: byref>, awaiter, continuation: _ -> Async2Code<_, _>) =
+ static member inline BindDynamic(sm: byref>, awaiter, [] continuation: _ -> Async2Code<_, _>) =
if Awaiter.isCompleted awaiter then
(Awaiter.getResult awaiter |> continuation).Invoke(&sm)
else
@@ -232,7 +232,7 @@ module internal Async2Implementation =
[]
member inline _.Bind
- (awaiter, continuation: 'U -> Async2Code<'Data, 'T>)
+ (awaiter, [] continuation: 'U -> Async2Code<'Data, 'T>)
: Async2Code<'Data, 'T> =
Async2Code(fun sm ->
if __useResumableCode then
@@ -375,3 +375,5 @@ module internal Async2 =
let startAsTask (code: Async2<'t>) = code.Start()
let runWithoutCancellation code = run CancellationToken.None code
+
+ let toAsync (code: Async2<_>) = startAsTask code |> Async.AwaitTask
From 4ebec4777bbcd8c05273aabd64beeb4c64a2984f Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 19:11:04 +0200
Subject: [PATCH 11/48] wip
---
src/Compiler/Service/BackgroundCompiler.fs | 2 +-
src/Compiler/Service/ServiceAnalysis.fs | 2 +-
src/Compiler/Service/TransparentCompiler.fs | 10 ++--
src/Compiler/Utilities/Async2.fs | 21 ++++++--
src/Compiler/Utilities/Cancellable.fs | 49 ++-----------------
src/Compiler/Utilities/Cancellable.fsi | 9 ----
.../ModuleReaderCancellationTests.fs | 2 +-
7 files changed, 29 insertions(+), 66 deletions(-)
diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs
index a728821051..867f4219d6 100644
--- a/src/Compiler/Service/BackgroundCompiler.fs
+++ b/src/Compiler/Service/BackgroundCompiler.fs
@@ -503,7 +503,7 @@ type internal BackgroundCompiler
let getOrCreateBuilder (options, userOpName) : Async =
async {
- use! _holder = Cancellable.UseToken()
+ use! _holder = Async2.UseTokenAsync ()
match tryGetBuilder options with
| Some getBuilder ->
diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs
index 6455d9f0ff..4ed47613e5 100644
--- a/src/Compiler/Service/ServiceAnalysis.fs
+++ b/src/Compiler/Service/ServiceAnalysis.fs
@@ -302,7 +302,7 @@ module UnusedOpens =
/// Async to allow cancellation.
let getUnusedOpens (checkFileResults: FSharpCheckFileResults, getSourceLineStr: int -> string) : Async =
async {
- use! _holder = Cancellable.UseToken()
+ use! _holder = Async2.UseTokenAsync ()
if checkFileResults.OpenDeclarations.Length = 0 then
return []
diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs
index f50262a27d..fea4a02f62 100644
--- a/src/Compiler/Service/TransparentCompiler.fs
+++ b/src/Compiler/Service/TransparentCompiler.fs
@@ -1608,7 +1608,7 @@ type internal TransparentCompiler
caches.ParseAndCheckFileInProject.Get(
projectSnapshot.FileKeyWithExtraFileSnapshotVersion fileName,
async {
- use! _holder = Cancellable.UseToken()
+ use! _holder = Async2.UseTokenAsync ()
use _ =
Activity.start "ComputeParseAndCheckFileInProject" [| Activity.Tags.fileName, fileName |> Path.GetFileName |> (!!) |]
@@ -1876,7 +1876,7 @@ type internal TransparentCompiler
Activity.Tags.project, projectSnapshot.ProjectFileName |> Path.GetFileName |> (!!)
|]
- use! _holder = Cancellable.UseToken()
+ use! _holder = Async2.UseTokenAsync ()
try
@@ -1924,7 +1924,7 @@ type internal TransparentCompiler
caches.ParseAndCheckProject.Get(
projectSnapshot.FullKey,
async {
- use! _holder = Cancellable.UseToken()
+ use! _holder = Async2.UseTokenAsync ()
match! ComputeBootstrapInfo projectSnapshot with
| None, creationDiags ->
@@ -1998,8 +1998,8 @@ type internal TransparentCompiler
let tryGetSink (fileName: string) (projectSnapshot: ProjectSnapshot) =
async {
- use! _holder = Cancellable.UseToken()
-
+ use! _holder = Async2.UseTokenAsync ()
+
match! ComputeBootstrapInfo projectSnapshot with
| None, _ -> return None
| Some bootstrapInfo, _creationDiags ->
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 1b0b7a4615..9e09016ac8 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -6,9 +6,22 @@ open System.Threading.Tasks
#nowarn 3513
-type Async2 =
+type internal Async2 =
static let token = AsyncLocal()
- static member UseToken ct = token.Value <- ct
+ static member UseToken ct =
+ let old = token.Value
+ token.Value <- ct
+ { new IDisposable with
+ member _.Dispose() = token.Value <- old }
+
+
+ static member UseTokenAsync () =
+ async {
+ let! ct = Async.CancellationToken
+ let old = token.Value
+ token.Value <- ct
+ return { new IDisposable with member _.Dispose() = token.Value <- old }
+ }
static member val Token = token.Value
module internal Async2Implementation =
@@ -85,7 +98,7 @@ module internal Async2Implementation =
module BindContext =
[]
- let bindLimit = 100
+ let bindLimit = 50
let bindCount = new ThreadLocal()
@@ -366,7 +379,7 @@ module internal Async2 =
open Async2Implementation
let run ct (code: Async2<'t>) =
- Async2.UseToken ct
+ use _ = Async2.UseToken ct
if isNull SynchronizationContext.Current && TaskScheduler.Current = TaskScheduler.Default then
code.Start().GetAwaiter().GetResult()
else
diff --git a/src/Compiler/Utilities/Cancellable.fs b/src/Compiler/Utilities/Cancellable.fs
index f1788f6aa1..33b8448688 100644
--- a/src/Compiler/Utilities/Cancellable.fs
+++ b/src/Compiler/Utilities/Cancellable.fs
@@ -1,52 +1,11 @@
namespace FSharp.Compiler
-open System
+open Internal.Utilities.Library
open System.Threading
-// This code provides two methods for handling cancellation in synchronous code:
-// 1. Explicitly, by calling Cancellable.CheckAndThrow().
-// 2. Implicitly, by wrapping the code in a cancellable computation.
-// The cancellable computation propagates the CancellationToken and checks for cancellation implicitly.
-// When it is impractical to use the cancellable computation, such as in deeply nested functions, Cancellable.CheckAndThrow() can be used.
-// It checks a CancellationToken local to the current async execution context, held in AsyncLocal.
-// Before calling Cancellable.CheckAndThrow(), this token must be set.
-// The token is guaranteed to be set during execution of cancellable computation.
-// Otherwise, it can be passed explicitly from the ambient async computation using Cancellable.UseToken().
-
[]
type Cancellable =
- static let tokenHolder = AsyncLocal()
-
- static let guard =
- String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("DISABLE_CHECKANDTHROW_ASSERT"))
-
- static let ensureToken msg =
- tokenHolder.Value
- |> ValueOption.defaultWith (fun () -> if guard then failwith msg else CancellationToken.None)
-
- static member HasCancellationToken = tokenHolder.Value.IsSome
-
- static member Token = ensureToken "Token not available outside of Cancellable computation."
-
- static member UseToken() =
- async {
- let! ct = Async.CancellationToken
- return Cancellable.UsingToken ct
- }
-
- static member UsingToken(ct) =
- let oldCt = tokenHolder.Value
- tokenHolder.Value <- ValueSome ct
-
- { new IDisposable with
- member _.Dispose() = tokenHolder.Value <- oldCt
- }
-
static member CheckAndThrow() =
- let token = ensureToken "CheckAndThrow invoked outside of Cancellable computation."
- token.ThrowIfCancellationRequested()
-
- static member TryCheckAndThrow() =
- match tokenHolder.Value with
- | ValueNone -> ()
- | ValueSome token -> token.ThrowIfCancellationRequested()
+ // If we're not inside an async computation, the ambient cancellation token will be CancellationToken.None and nothing will happen
+ // otherwise, if we are inside an async computation, this will throw.
+ Async2.Token.ThrowIfCancellationRequested()
diff --git a/src/Compiler/Utilities/Cancellable.fsi b/src/Compiler/Utilities/Cancellable.fsi
index 880a31199f..ea928cd075 100644
--- a/src/Compiler/Utilities/Cancellable.fsi
+++ b/src/Compiler/Utilities/Cancellable.fsi
@@ -1,14 +1,5 @@
namespace FSharp.Compiler
-open System
-open System.Threading
-
[]
type Cancellable =
- static member internal UseToken: unit -> Async
-
- static member HasCancellationToken: bool
- static member Token: CancellationToken
-
static member CheckAndThrow: unit -> unit
- static member TryCheckAndThrow: unit -> unit
diff --git a/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs b/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs
index f307586cd5..e76761f01e 100644
--- a/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs
+++ b/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs
@@ -150,7 +150,7 @@ let parseAndCheck path source options =
| _, FSharpCheckFileAnswer.Aborted -> None
| _, FSharpCheckFileAnswer.Succeeded results -> Some results
- Cancellable.HasCancellationToken |> shouldEqual false
+ Async2.Token |> shouldEqual CancellationToken.None
result
with :? OperationCanceledException ->
From 097c0a3c77dc7ee796d5f012e49f04e750645547 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 20:51:27 +0200
Subject: [PATCH 12/48] fix
---
src/Compiler/Utilities/Async2.fs | 28 ++++++++++---------
.../ModuleReaderCancellationTests.fs | 5 ----
2 files changed, 15 insertions(+), 18 deletions(-)
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 9e09016ac8..26cff28ca5 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -89,13 +89,11 @@ module internal Async2Implementation =
interface ICriticalNotifyCompletion with
member _.OnCompleted(continuation) = set continuation
member _.UnsafeOnCompleted(continuation) = set continuation
+
+ member this.Ref : ICriticalNotifyCompletion ref = ref this
static member Current = holder.Value
- module Trampoline =
- let Awaiter : ICriticalNotifyCompletion = Trampoline.Current
- let AwaiterRef = ref Awaiter
-
module BindContext =
[]
let bindLimit = 50
@@ -175,6 +173,7 @@ module internal Async2Implementation =
type DynamicContinuation =
| Stop
| Immediate
+ | Bounce
| Await of ICriticalNotifyCompletion
let inline yieldOnBindLimit () =
@@ -182,7 +181,7 @@ module internal Async2Implementation =
if sm.Data.Hijack then
let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm)
if not __stack_yield_fin then
- sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(Trampoline.AwaiterRef, &sm)
+ sm.Data.MethodBuilder.AwaitOnCompleted(Trampoline.Current.Ref, &sm)
__stack_yield_fin
else
true
@@ -269,32 +268,31 @@ module internal Async2Implementation =
static member inline RunDynamic(code: Async2Code<'T, 'T>) : Async2<'T> =
let initialResumptionFunc = Async2ResumptionFunc<'T>(fun sm -> code.Invoke &sm)
- let resumptionInfo () =
+ let resumptionInfo hijack =
let mutable state = InitialYield
+ let bounceOrImmediate = if hijack then Bounce else Immediate
{ new Async2ResumptionDynamicInfo<'T>(initialResumptionFunc) with
member info.MoveNext(sm) =
let mutable continuation = Stop
- let hijackCheck = if sm.Data.Hijack then Await Trampoline.Awaiter else Immediate
-
let current = state
match current with
| InitialYield ->
state <- Running
- continuation <- hijackCheck
+ continuation <- bounceOrImmediate
| Running ->
try
let step = info.ResumptionFunc.Invoke(&sm)
if step then
state <- SetResult
- continuation <- hijackCheck
+ continuation <- bounceOrImmediate
else
match info.ResumptionData with
| :? ICriticalNotifyCompletion as awaiter -> continuation <- Await awaiter
| _ -> failwith "invalid awaiter"
with exn ->
state <- SetException (ExceptionCache.CaptureOrRetrieve exn)
- continuation <- hijackCheck
+ continuation <- bounceOrImmediate
| SetResult -> sm.Data.MethodBuilder.SetResult sm.Data.Result
| SetException edi -> sm.Data.MethodBuilder.SetException(edi.SourceException)
@@ -303,6 +301,11 @@ module internal Async2Implementation =
sm.ResumptionDynamicInfo.ResumptionData <- null
let mutable awaiter = awaiter
sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm)
+ | Bounce ->
+ sm.Data.MethodBuilder.AwaitOnCompleted(
+ Trampoline.Current.Ref,
+ &sm
+ )
| Immediate -> info.MoveNext &sm
| Stop -> ()
@@ -312,9 +315,8 @@ module internal Async2Implementation =
Async2(fun hijack ->
let mutable copy = Async2StateMachine()
- copy.ResumptionDynamicInfo <- resumptionInfo ()
+ copy.ResumptionDynamicInfo <- resumptionInfo hijack
copy.Data <- Async2Data()
- copy.Data.Hijack <- hijack
copy.Data.MethodBuilder <- AsyncTaskMethodBuilder<'T>.Create()
copy.Data.MethodBuilder.Start(©)
copy.Data.MethodBuilder.Task)
diff --git a/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs b/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs
index e76761f01e..d8988c1099 100644
--- a/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs
+++ b/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs
@@ -173,11 +173,6 @@ open Ns1.Ns2
let t: T = T()
"""
-
-[]
-let ``CheckAndThrow is not allowed to throw outside of cancellable`` () =
- Assert.Throws(fun () -> Cancellable.CheckAndThrow())
-
[]
let ``Type defs 01 - assembly import`` () =
let source = source1
From f985734f32f7a520bfb39435a87f14823bebf196 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 22:26:35 +0200
Subject: [PATCH 13/48] wip
---
src/Compiler/Utilities/Async2.fs | 14 ++++++++++----
...r.Service.SurfaceArea.netstandard20.release.bsl | 5 -----
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 26cff28ca5..e0893e64d8 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -59,6 +59,9 @@ module internal Async2Implementation =
awaiter.UnsafeOnCompleted continuation
type Trampoline private () =
+
+ let failIfNot condition message =
+ if not condition then failwith message
let ownerThreadId = Thread.CurrentThread.ManagedThreadId
@@ -79,8 +82,8 @@ module internal Async2Implementation =
running <- false
let set action =
- assert (Thread.CurrentThread.ManagedThreadId = ownerThreadId)
- assert pending.IsNone
+ failIfNot (Thread.CurrentThread.ManagedThreadId = ownerThreadId) "Trampoline used from wrong thread"
+ failIfNot pending.IsNone "Trampoline used while already pending"
if running then
pending <- ValueSome action
else
@@ -96,7 +99,7 @@ module internal Async2Implementation =
module BindContext =
[]
- let bindLimit = 50
+ let bindLimit = 25
let bindCount = new ThreadLocal()
@@ -127,7 +130,10 @@ module internal Async2Implementation =
[]
type internal Async2<'T> (start: bool -> Task<'T>) =
- member inline _.Start() = start false
+ member inline _.Start() =
+ BindContext.IncrementBindCount() |> ignore
+ start false
+
member inline _.GetAwaiter() =
let hijack = BindContext.IncrementBindCount()
(start hijack).GetAwaiter()
diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl
index bc08c7fd0b..4a60d33db6 100644
--- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl
+++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl
@@ -2011,12 +2011,7 @@ FSharp.Compiler.AbstractIL.ILBinaryReader: FSharp.Compiler.AbstractIL.ILBinaryRe
FSharp.Compiler.AbstractIL.ILBinaryReader: FSharp.Compiler.AbstractIL.ILBinaryReader+MetadataOnlyFlag
FSharp.Compiler.AbstractIL.ILBinaryReader: FSharp.Compiler.AbstractIL.ILBinaryReader+ReduceMemoryFlag
FSharp.Compiler.AbstractIL.ILBinaryReader: FSharp.Compiler.AbstractIL.ILBinaryReader+Shim
-FSharp.Compiler.Cancellable: Boolean HasCancellationToken
-FSharp.Compiler.Cancellable: Boolean get_HasCancellationToken()
-FSharp.Compiler.Cancellable: System.Threading.CancellationToken Token
-FSharp.Compiler.Cancellable: System.Threading.CancellationToken get_Token()
FSharp.Compiler.Cancellable: Void CheckAndThrow()
-FSharp.Compiler.Cancellable: Void TryCheckAndThrow()
FSharp.Compiler.CodeAnalysis.DelayedILModuleReader: System.String OutputFile
FSharp.Compiler.CodeAnalysis.DelayedILModuleReader: System.String get_OutputFile()
FSharp.Compiler.CodeAnalysis.DelayedILModuleReader: Void .ctor(System.String, Microsoft.FSharp.Core.FSharpFunc`2[System.Threading.CancellationToken,Microsoft.FSharp.Core.FSharpOption`1[System.IO.Stream]])
From 4f816637e3edf71bfa7d942815ce409fb183e5ea Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 28 Aug 2025 23:24:01 +0200
Subject: [PATCH 14/48] wip
---
src/Compiler/Service/BackgroundCompiler.fs | 2 +-
src/Compiler/Service/ServiceAnalysis.fs | 2 +-
src/Compiler/Service/TransparentCompiler.fs | 10 +-
src/Compiler/Utilities/Async2.fs | 320 +++++++++++---------
4 files changed, 180 insertions(+), 154 deletions(-)
diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs
index 867f4219d6..b6ffa72ea9 100644
--- a/src/Compiler/Service/BackgroundCompiler.fs
+++ b/src/Compiler/Service/BackgroundCompiler.fs
@@ -503,7 +503,7 @@ type internal BackgroundCompiler
let getOrCreateBuilder (options, userOpName) : Async =
async {
- use! _holder = Async2.UseTokenAsync ()
+ use! _holder = Async2.UseTokenAsync()
match tryGetBuilder options with
| Some getBuilder ->
diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs
index 4ed47613e5..43f9110085 100644
--- a/src/Compiler/Service/ServiceAnalysis.fs
+++ b/src/Compiler/Service/ServiceAnalysis.fs
@@ -302,7 +302,7 @@ module UnusedOpens =
/// Async to allow cancellation.
let getUnusedOpens (checkFileResults: FSharpCheckFileResults, getSourceLineStr: int -> string) : Async =
async {
- use! _holder = Async2.UseTokenAsync ()
+ use! _holder = Async2.UseTokenAsync()
if checkFileResults.OpenDeclarations.Length = 0 then
return []
diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs
index fea4a02f62..8472c47af7 100644
--- a/src/Compiler/Service/TransparentCompiler.fs
+++ b/src/Compiler/Service/TransparentCompiler.fs
@@ -1608,7 +1608,7 @@ type internal TransparentCompiler
caches.ParseAndCheckFileInProject.Get(
projectSnapshot.FileKeyWithExtraFileSnapshotVersion fileName,
async {
- use! _holder = Async2.UseTokenAsync ()
+ use! _holder = Async2.UseTokenAsync()
use _ =
Activity.start "ComputeParseAndCheckFileInProject" [| Activity.Tags.fileName, fileName |> Path.GetFileName |> (!!) |]
@@ -1876,7 +1876,7 @@ type internal TransparentCompiler
Activity.Tags.project, projectSnapshot.ProjectFileName |> Path.GetFileName |> (!!)
|]
- use! _holder = Async2.UseTokenAsync ()
+ use! _holder = Async2.UseTokenAsync()
try
@@ -1924,7 +1924,7 @@ type internal TransparentCompiler
caches.ParseAndCheckProject.Get(
projectSnapshot.FullKey,
async {
- use! _holder = Async2.UseTokenAsync ()
+ use! _holder = Async2.UseTokenAsync()
match! ComputeBootstrapInfo projectSnapshot with
| None, creationDiags ->
@@ -1998,8 +1998,8 @@ type internal TransparentCompiler
let tryGetSink (fileName: string) (projectSnapshot: ProjectSnapshot) =
async {
- use! _holder = Async2.UseTokenAsync ()
-
+ use! _holder = Async2.UseTokenAsync()
+
match! ComputeBootstrapInfo projectSnapshot with
| None, _ -> return None
| Some bootstrapInfo, _creationDiags ->
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index e0893e64d8..b9c8e6bc9d 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -8,20 +8,27 @@ open System.Threading.Tasks
type internal Async2 =
static let token = AsyncLocal()
+
static member UseToken ct =
let old = token.Value
token.Value <- ct
+
{ new IDisposable with
- member _.Dispose() = token.Value <- old }
+ member _.Dispose() = token.Value <- old
+ }
-
- static member UseTokenAsync () =
+ static member UseTokenAsync() =
async {
let! ct = Async.CancellationToken
let old = token.Value
token.Value <- ct
- return { new IDisposable with member _.Dispose() = token.Value <- old }
+
+ return
+ { new IDisposable with
+ member _.Dispose() = token.Value <- old
+ }
}
+
static member val Token = token.Value
module internal Async2Implementation =
@@ -38,123 +45,122 @@ module internal Async2Implementation =
and 'Awaiter: (member get_IsCompleted: unit -> bool)
and 'Awaiter: (member GetResult: unit -> 'TResult)> = 'Awaiter
- type Awaitable<'Awaitable, 'Awaiter, 'TResult
- when 'Awaitable: (member GetAwaiter: unit -> Awaiter<'Awaiter, 'TResult>)> = 'Awaitable
+ type Awaitable<'Awaitable, 'Awaiter, 'TResult when 'Awaitable: (member GetAwaiter: unit -> Awaiter<'Awaiter, 'TResult>)> = 'Awaitable
module Awaiter =
- let inline isCompleted (awaiter: ^Awaiter) : bool
- when ^Awaiter : (member get_IsCompleted : unit -> bool) =
- awaiter.get_IsCompleted()
+ let inline isCompleted (awaiter: ^Awaiter) : bool when ^Awaiter: (member get_IsCompleted: unit -> bool) = awaiter.get_IsCompleted ()
- let inline getResult (awaiter: ^Awaiter) : ^TResult
- when ^Awaiter : (member GetResult : unit -> ^TResult) =
- awaiter.GetResult()
+ let inline getResult (awaiter: ^Awaiter) : ^TResult when ^Awaiter: (member GetResult: unit -> ^TResult) = awaiter.GetResult()
- let inline onCompleted (awaiter: ^Awaiter) (continuation: Action) : unit
- when ^Awaiter :> INotifyCompletion =
- awaiter.OnCompleted continuation
+ let inline onCompleted (awaiter: ^Awaiter) (continuation: Action) : unit when ^Awaiter :> INotifyCompletion =
+ awaiter.OnCompleted continuation
- let inline unsafeOnCompleted (awaiter: ^Awaiter) (continuation: Action) : unit
- when ^Awaiter :> ICriticalNotifyCompletion =
- awaiter.UnsafeOnCompleted continuation
+ let inline unsafeOnCompleted (awaiter: ^Awaiter) (continuation: Action) : unit when ^Awaiter :> ICriticalNotifyCompletion =
+ awaiter.UnsafeOnCompleted continuation
type Trampoline private () =
let failIfNot condition message =
- if not condition then failwith message
-
+ if not condition then
+ failwith message
+
let ownerThreadId = Thread.CurrentThread.ManagedThreadId
-
+
static let holder = new ThreadLocal<_>(fun () -> Trampoline())
-
- let mutable pending : Action voption = ValueNone
+
+ let mutable pending: Action voption = ValueNone
let mutable running = false
-
+
let start (action: Action) =
- try
+ try
running <- true
action.Invoke()
+
while pending.IsSome do
let next = pending.Value
pending <- ValueNone
next.Invoke()
finally
running <- false
-
+
let set action =
failIfNot (Thread.CurrentThread.ManagedThreadId = ownerThreadId) "Trampoline used from wrong thread"
failIfNot pending.IsNone "Trampoline used while already pending"
+
if running then
pending <- ValueSome action
else
start action
-
+
interface ICriticalNotifyCompletion with
member _.OnCompleted(continuation) = set continuation
member _.UnsafeOnCompleted(continuation) = set continuation
- member this.Ref : ICriticalNotifyCompletion ref = ref this
-
+ member this.Ref: ICriticalNotifyCompletion ref = ref this
+
static member Current = holder.Value
-
+
module BindContext =
[]
- let bindLimit = 25
-
+ let bindLimit = 200
+
let bindCount = new ThreadLocal()
-
+
+ let inline ResetBindCount () = bindCount.Value <- 0
+
let inline IncrementBindCount () =
bindCount.Value <- bindCount.Value + 1
bindCount.Value % bindLimit = 0
-
+
module ExceptionCache =
let store = ConditionalWeakTable()
-
+
let inline CaptureOrRetrieve (exn: exn) =
match store.TryGetValue exn with
| true, edi when edi.SourceException = exn -> edi
| _ ->
let edi = ExceptionDispatchInfo.Capture exn
-
- try store.Add(exn, edi) with _ -> ()
-
+
+ try
+ store.Add(exn, edi)
+ with _ ->
+ ()
+
edi
-
- let inline Throw(exn: exn) =
+
+ let inline Throw (exn: exn) =
let edi = CaptureOrRetrieve exn
edi.Throw()
Unchecked.defaultof<_>
-
- let inline GetResultOrThrow awaiter = try Awaiter.getResult awaiter with exn -> Throw exn
+
+ let inline GetResultOrThrow awaiter =
+ try
+ Awaiter.getResult awaiter
+ with exn ->
+ Throw exn
[]
- type internal Async2<'T> (start: bool -> Task<'T>) =
-
- member inline _.Start() =
- BindContext.IncrementBindCount() |> ignore
- start false
-
- member inline _.GetAwaiter() =
- let hijack = BindContext.IncrementBindCount()
- (start hijack).GetAwaiter()
-
+ type internal Async2<'T>(start: unit -> Task<'T>) =
+
+ member inline _.Start() = start ()
+
[]
type Async2Data<'t> =
[]
val mutable Result: 't
-
+
[]
val mutable MethodBuilder: AsyncTaskMethodBuilder<'t>
-
+
[]
val mutable Hijack: bool
-
+
type Async2StateMachine<'TOverall> = ResumableStateMachine>
type IAsync2StateMachine<'TOverall> = IResumableStateMachine>
type Async2ResumptionFunc<'TOverall> = ResumptionFunc>
type Async2ResumptionDynamicInfo<'TOverall> = ResumptionDynamicInfo>
- type Async2Code<'TOverall, 'T> = ResumableCode, 'T>
-
+ type Async2Code<'TOverall, 'T> = ResumableCode, 'T>
+
[]
module Async2Code =
let inline filterCancellation (catch: exn -> Async2Code<_, _>) (exn: exn) =
@@ -162,62 +168,62 @@ module internal Async2Implementation =
match exn with
| :? OperationCanceledException as oce when oce.CancellationToken = Async2.Token -> raise exn
| _ -> (catch exn).Invoke(&sm))
-
+
let inline throwIfCancellationRequested (code: Async2Code<_, _>) =
Async2Code(fun sm ->
Async2.Token.ThrowIfCancellationRequested()
code.Invoke(&sm))
-
+
[]
type DynamicState =
| InitialYield
| Running
| SetResult
| SetException of ExceptionDispatchInfo
-
+
[]
type DynamicContinuation =
| Stop
| Immediate
| Bounce
| Await of ICriticalNotifyCompletion
-
+
let inline yieldOnBindLimit () =
Async2Code<_, _>(fun sm ->
- if sm.Data.Hijack then
+ if BindContext.IncrementBindCount() then
let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm)
+
if not __stack_yield_fin then
sm.Data.MethodBuilder.AwaitOnCompleted(Trampoline.Current.Ref, &sm)
+
__stack_yield_fin
else
- true
- )
-
+ true)
+
type Async2Builder() =
-
+
member inline _.Delay(generator: unit -> Async2Code<'TOverall, 'T>) : Async2Code<'TOverall, 'T> =
ResumableCode.Delay(fun () -> generator () |> throwIfCancellationRequested)
-
+
[]
member inline _.Zero() : Async2Code<'TOverall, unit> = ResumableCode.Zero()
-
- member inline _.Return(value: 'T) = Async2Code(fun sm -> sm.Data.Result <- value; true)
-
- member inline _.Combine
- (code1: Async2Code<'TOverall, unit>, code2: Async2Code<'TOverall, 'T>)
- : Async2Code<'TOverall, 'T> =
+
+ member inline _.Return(value: 'T) =
+ Async2Code(fun sm ->
+ sm.Data.Result <- value
+ true)
+
+ member inline _.Combine(code1: Async2Code<'TOverall, unit>, code2: Async2Code<'TOverall, 'T>) : Async2Code<'TOverall, 'T> =
ResumableCode.Combine(code1, code2)
-
- member inline _.While
- ([] condition: unit -> bool, body: Async2Code<'TOverall, unit>)
- : Async2Code<'TOverall, unit> =
+
+ member inline _.While([] condition: unit -> bool, body: Async2Code<'TOverall, unit>) : Async2Code<'TOverall, unit> =
ResumableCode.While(condition, throwIfCancellationRequested body)
-
+
member inline _.TryWith
(body: Async2Code<'TOverall, 'T>, [] catch: exn -> Async2Code<'TOverall, 'T>)
: Async2Code<'TOverall, 'T> =
ResumableCode.TryWith(body, filterCancellation catch)
-
+
member inline _.TryFinally
(body: Async2Code<'TOverall, 'T>, [] compensation: unit -> unit)
: Async2Code<'TOverall, 'T> =
@@ -227,154 +233,162 @@ module internal Async2Implementation =
compensation ()
true)
)
-
+
member inline _.Using<'Resource, 'TOverall, 'T when 'Resource :> IDisposable | null>
(resource: 'Resource, body: 'Resource -> Async2Code<'TOverall, 'T>)
: Async2Code<'TOverall, 'T> =
ResumableCode.Using(resource, body)
-
+
member inline _.For(sequence: seq<'T>, [] body: 'T -> Async2Code<'TOverall, unit>) : Async2Code<'TOverall, unit> =
ResumableCode.For(sequence, fun x -> body x |> throwIfCancellationRequested)
-
+
[]
- static member inline BindDynamic(sm: byref>, awaiter, [] continuation: _ -> Async2Code<_, _>) =
+ static member inline BindDynamic
+ (sm: byref>, awaiter, [] continuation: _ -> Async2Code<_, _>)
+ =
if Awaiter.isCompleted awaiter then
(Awaiter.getResult awaiter |> continuation).Invoke(&sm)
else
- let resumptionFunc = Async2ResumptionFunc(fun sm ->
- let result = ExceptionCache.GetResultOrThrow awaiter
- (continuation result).Invoke(&sm))
+ let resumptionFunc =
+ Async2ResumptionFunc(fun sm ->
+ let result = ExceptionCache.GetResultOrThrow awaiter
+ (continuation result).Invoke(&sm))
+
sm.ResumptionDynamicInfo.ResumptionFunc <- resumptionFunc
sm.ResumptionDynamicInfo.ResumptionData <- awaiter :> ICriticalNotifyCompletion
false
-
+
[]
- member inline _.Bind
- (awaiter, [] continuation: 'U -> Async2Code<'Data, 'T>)
- : Async2Code<'Data, 'T> =
- Async2Code(fun sm ->
- if __useResumableCode then
- if Awaiter.isCompleted awaiter then
+ member inline _.Bind(awaiter, [] continuation: 'U -> Async2Code<'Data, 'T>) : Async2Code<'Data, 'T> =
+ Async2Code(fun sm ->
+ if __useResumableCode then
+ if Awaiter.isCompleted awaiter then
+ continuation(ExceptionCache.GetResultOrThrow awaiter).Invoke(&sm)
+ else
+ let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm)
+
+ if __stack_yield_fin then
continuation(ExceptionCache.GetResultOrThrow awaiter).Invoke(&sm)
else
- let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm)
- if __stack_yield_fin then
- continuation(ExceptionCache.GetResultOrThrow awaiter).Invoke(&sm)
- else
- let mutable __stack_awaiter = awaiter
- sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&__stack_awaiter, &sm)
- false
- else
- Async2Builder.BindDynamic(&sm, awaiter, continuation)
- )
-
+ let mutable __stack_awaiter = awaiter
+ sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&__stack_awaiter, &sm)
+ false
+ else
+ Async2Builder.BindDynamic(&sm, awaiter, continuation))
+
[]
member inline this.ReturnFrom(awaiter) : Async2Code<'T, 'T> = this.Bind(awaiter, this.Return)
-
+
static member inline RunDynamic(code: Async2Code<'T, 'T>) : Async2<'T> =
let initialResumptionFunc = Async2ResumptionFunc<'T>(fun sm -> code.Invoke &sm)
-
- let resumptionInfo hijack =
+
+ let resumptionInfo () =
let mutable state = InitialYield
- let bounceOrImmediate = if hijack then Bounce else Immediate
+
+ let bounceOrImmediate () =
+ if BindContext.IncrementBindCount() then
+ Bounce
+ else
+ Immediate
+
{ new Async2ResumptionDynamicInfo<'T>(initialResumptionFunc) with
member info.MoveNext(sm) =
let mutable continuation = Stop
-
+
let current = state
+
match current with
| InitialYield ->
state <- Running
- continuation <- bounceOrImmediate
+ continuation <- bounceOrImmediate ()
| Running ->
try
- let step = info.ResumptionFunc.Invoke(&sm)
+ let step = info.ResumptionFunc.Invoke(&sm)
+
if step then
- state <- SetResult
- continuation <- bounceOrImmediate
+ state <- SetResult
+ continuation <- bounceOrImmediate ()
else
match info.ResumptionData with
| :? ICriticalNotifyCompletion as awaiter -> continuation <- Await awaiter
| _ -> failwith "invalid awaiter"
with exn ->
- state <- SetException (ExceptionCache.CaptureOrRetrieve exn)
- continuation <- bounceOrImmediate
+ state <- SetException(ExceptionCache.CaptureOrRetrieve exn)
+ continuation <- bounceOrImmediate ()
| SetResult -> sm.Data.MethodBuilder.SetResult sm.Data.Result
| SetException edi -> sm.Data.MethodBuilder.SetException(edi.SourceException)
-
+
match continuation with
| Await awaiter ->
sm.ResumptionDynamicInfo.ResumptionData <- null
let mutable awaiter = awaiter
sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm)
- | Bounce ->
- sm.Data.MethodBuilder.AwaitOnCompleted(
- Trampoline.Current.Ref,
- &sm
- )
+ | Bounce -> sm.Data.MethodBuilder.AwaitOnCompleted(Trampoline.Current.Ref, &sm)
| Immediate -> info.MoveNext &sm
| Stop -> ()
-
+
member _.SetStateMachine(sm, state) =
sm.Data.MethodBuilder.SetStateMachine(state)
}
- Async2(fun hijack ->
+ Async2(fun () ->
let mutable copy = Async2StateMachine()
- copy.ResumptionDynamicInfo <- resumptionInfo hijack
+ copy.ResumptionDynamicInfo <- resumptionInfo ()
copy.Data <- Async2Data()
copy.Data.MethodBuilder <- AsyncTaskMethodBuilder<'T>.Create()
copy.Data.MethodBuilder.Start(©)
copy.Data.MethodBuilder.Task)
-
+
member inline _.Run(code: Async2Code<'T, 'T>) : Async2<'T> =
if __useResumableCode then
__stateMachine, _>
-
+
(MoveNextMethodImpl<_>(fun sm ->
__resumeAt sm.ResumptionPoint
let mutable error = ValueNone
-
+
let __stack_go1 = yieldOnBindLimit().Invoke(&sm)
+
if __stack_go1 then
try
let __stack_code_fin = code.Invoke(&sm)
+
if __stack_code_fin then
let __stack_go2 = yieldOnBindLimit().Invoke(&sm)
+
if __stack_go2 then
sm.Data.MethodBuilder.SetResult(sm.Data.Result)
with exn ->
- error <- ValueSome (ExceptionCache.CaptureOrRetrieve exn)
-
+ error <- ValueSome(ExceptionCache.CaptureOrRetrieve exn)
+
if error.IsSome then
- let __stack_go2 = yieldOnBindLimit().Invoke(&sm)
+ let __stack_go2 = yieldOnBindLimit().Invoke(&sm)
+
if __stack_go2 then
- sm.Data.MethodBuilder.SetException(error.Value.SourceException)
- ))
-
+ sm.Data.MethodBuilder.SetException(error.Value.SourceException)))
+
(SetStateMachineMethodImpl<_>(fun sm state -> sm.Data.MethodBuilder.SetStateMachine state))
-
+
(AfterCode<_, _>(fun sm ->
let mutable copy = sm
- Async2(fun hijack ->
+
+ Async2(fun () ->
copy.Data <- Async2Data()
- copy.Data.Hijack <- hijack
copy.Data.MethodBuilder <- AsyncTaskMethodBuilder<'T>.Create()
copy.Data.MethodBuilder.Start(©)
- copy.Data.MethodBuilder.Task)
- ))
+ copy.Data.MethodBuilder.Task)))
else
Async2Builder.RunDynamic(code)
- member inline _.Source(code: Async2<_>) = code.GetAwaiter()
-
+ member inline _.Source(code: Async2<_>) = code.Start().GetAwaiter()
+
[]
module SourceExtensions =
- type Async2Builder with
+ type Async2Builder with
member inline _.Source(awaitable: Awaitable<_, _, _>) = awaitable.GetAwaiter()
member inline _.Source(task: Task) = task.GetAwaiter()
- member inline _.Source(items: #seq<_>) : seq<_> = upcast items
-
+ member inline _.Source(items: #seq<_>) : seq<_> = upcast items
+
[]
module internal Async2AutoOpens =
open Async2Implementation
@@ -388,12 +402,24 @@ module internal Async2 =
let run ct (code: Async2<'t>) =
use _ = Async2.UseToken ct
- if isNull SynchronizationContext.Current && TaskScheduler.Current = TaskScheduler.Default then
+
+ if
+ isNull SynchronizationContext.Current
+ && TaskScheduler.Current = TaskScheduler.Default
+ then
+ BindContext.ResetBindCount()
code.Start().GetAwaiter().GetResult()
- else
- Task.Run<'t>(code.Start).GetAwaiter().GetResult()
+ else
+ Task
+ .Run<'t>(fun () ->
+ BindContext.ResetBindCount()
+ code.Start())
+ .GetAwaiter()
+ .GetResult()
- let startAsTask (code: Async2<'t>) = code.Start()
+ let startAsTask (code: Async2<'t>) =
+ BindContext.ResetBindCount()
+ code.Start()
let runWithoutCancellation code = run CancellationToken.None code
From c15cafed473c33689a4fab31adb1b02090ab35b8 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Fri, 29 Aug 2025 13:10:51 +0200
Subject: [PATCH 15/48] wip
---
src/Compiler/Utilities/Async2.fs | 47 ++++++++++++++++----------------
1 file changed, 23 insertions(+), 24 deletions(-)
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index b9c8e6bc9d..85428bff01 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -100,9 +100,23 @@ module internal Async2Implementation =
static member Current = holder.Value
+ []
+ type DynamicContinuation =
+ | Stop
+ | Immediate
+ | Bounce
+ | Await of ICriticalNotifyCompletion
+
+ []
+ type DynamicState =
+ | InitialYield
+ | Running
+ | SetResult
+ | SetException of ExceptionDispatchInfo
+
module BindContext =
[]
- let bindLimit = 200
+ let bindLimit = 100
let bindCount = new ThreadLocal()
@@ -112,6 +126,9 @@ module internal Async2Implementation =
bindCount.Value <- bindCount.Value + 1
bindCount.Value % bindLimit = 0
+ let inline IncrementBindCountDynamic () =
+ if IncrementBindCount() then Bounce else Immediate
+
module ExceptionCache =
let store = ConditionalWeakTable()
@@ -174,20 +191,6 @@ module internal Async2Implementation =
Async2.Token.ThrowIfCancellationRequested()
code.Invoke(&sm))
- []
- type DynamicState =
- | InitialYield
- | Running
- | SetResult
- | SetException of ExceptionDispatchInfo
-
- []
- type DynamicContinuation =
- | Stop
- | Immediate
- | Bounce
- | Await of ICriticalNotifyCompletion
-
let inline yieldOnBindLimit () =
Async2Code<_, _>(fun sm ->
if BindContext.IncrementBindCount() then
@@ -285,12 +288,6 @@ module internal Async2Implementation =
let resumptionInfo () =
let mutable state = InitialYield
- let bounceOrImmediate () =
- if BindContext.IncrementBindCount() then
- Bounce
- else
- Immediate
-
{ new Async2ResumptionDynamicInfo<'T>(initialResumptionFunc) with
member info.MoveNext(sm) =
let mutable continuation = Stop
@@ -300,24 +297,26 @@ module internal Async2Implementation =
match current with
| InitialYield ->
state <- Running
- continuation <- bounceOrImmediate ()
+ continuation <- BindContext.IncrementBindCountDynamic()
| Running ->
try
let step = info.ResumptionFunc.Invoke(&sm)
if step then
state <- SetResult
- continuation <- bounceOrImmediate ()
+ continuation <- BindContext.IncrementBindCountDynamic()
else
match info.ResumptionData with
| :? ICriticalNotifyCompletion as awaiter -> continuation <- Await awaiter
| _ -> failwith "invalid awaiter"
with exn ->
state <- SetException(ExceptionCache.CaptureOrRetrieve exn)
- continuation <- bounceOrImmediate ()
+ continuation <- BindContext.IncrementBindCountDynamic()
| SetResult -> sm.Data.MethodBuilder.SetResult sm.Data.Result
| SetException edi -> sm.Data.MethodBuilder.SetException(edi.SourceException)
+ let continuation = continuation
+
match continuation with
| Await awaiter ->
sm.ResumptionDynamicInfo.ResumptionData <- null
From 0982cf8bf7e461b104e8ec04ba7d4fe104121875 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Mon, 1 Sep 2025 22:28:03 +0200
Subject: [PATCH 16/48] fix Source extensions
---
src/Compiler/Utilities/Async2.fs | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 85428bff01..357459e027 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -381,19 +381,17 @@ module internal Async2Implementation =
member inline _.Source(code: Async2<_>) = code.Start().GetAwaiter()
- []
- module SourceExtensions =
- type Async2Builder with
- member inline _.Source(awaitable: Awaitable<_, _, _>) = awaitable.GetAwaiter()
- member inline _.Source(task: Task) = task.GetAwaiter()
- member inline _.Source(items: #seq<_>) : seq<_> = upcast items
-
[]
module internal Async2AutoOpens =
open Async2Implementation
let async2 = Async2Builder()
+ type Async2Builder with
+ member inline _.Source(awaitable: Awaitable<_, _, _>) = awaitable.GetAwaiter()
+ member inline _.Source(task: Task) = task.GetAwaiter()
+ member inline _.Source(items: #seq<_>) : seq<_> = upcast items
+
type Async2<'t> = Async2Implementation.Async2<'t>
module internal Async2 =
From da319d400464dc5a0e0ffbb91c1437831098e43e Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Tue, 2 Sep 2025 13:12:28 +0200
Subject: [PATCH 17/48] wip
---
src/Compiler/Utilities/Async2.fs | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 357459e027..8fcf7d96ff 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -387,15 +387,27 @@ module internal Async2AutoOpens =
let async2 = Async2Builder()
+[]
+module internal Async2LowPriority =
+ open Async2Implementation
+
type Async2Builder with
member inline _.Source(awaitable: Awaitable<_, _, _>) = awaitable.GetAwaiter()
- member inline _.Source(task: Task) = task.GetAwaiter()
+ member inline _.Source(expr: Async<_>) = Async.StartAsTask(expr, cancellationToken = Async2.Token).GetAwaiter()
member inline _.Source(items: #seq<_>) : seq<_> = upcast items
-type Async2<'t> = Async2Implementation.Async2<'t>
+[]
+module internal Async2MediumPriority =
+ open Async2Implementation
+ type Async2Builder with
+ member inline _.Source(task: Task) = task.GetAwaiter()
+ member inline _.Source(task: Task<_>) = task.GetAwaiter()
+
+open Async2Implementation
+
+type internal Async2<'t> = Async2Implementation.Async2<'t>
module internal Async2 =
- open Async2Implementation
let run ct (code: Async2<'t>) =
use _ = Async2.UseToken ct
From eb52129b45c25e4d539a1c7bd9ce3a2d3478a4c1 Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 4 Sep 2025 10:08:14 +0200
Subject: [PATCH 18/48] wip
---
src/Compiler/Service/FSharpCheckerResults.fs | 4 +-
src/Compiler/Utilities/Async2.fs | 131 +++++++++++-------
src/Compiler/Utilities/Cancellable.fs | 3 +-
.../ModuleReaderCancellationTests.fs | 2 +-
4 files changed, 83 insertions(+), 57 deletions(-)
diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs
index 732ce24bcc..7e46455a76 100644
--- a/src/Compiler/Service/FSharpCheckerResults.fs
+++ b/src/Compiler/Service/FSharpCheckerResults.fs
@@ -88,7 +88,7 @@ type DelayedILModuleReader =
match box this.result with
| null ->
async2 {
- let ct = Async2.Token
+ let ct = Async2.CancellationToken
return
lock this.gate (fun () ->
@@ -3931,7 +3931,7 @@ type FsiInteractiveChecker(legacyReferenceResolver, tcConfig: TcConfig, tcGlobal
let parsingOptions =
FSharpParsingOptions.FromTcConfig(tcConfig, [| fileName |], true)
- let ct = Async2.Token
+ let ct = Async2.CancellationToken
let parseErrors, parsedInput, anyErrors =
ParseAndCheckFile.parseFile (
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 8fcf7d96ff..73e96a40bf 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -6,31 +6,6 @@ open System.Threading.Tasks
#nowarn 3513
-type internal Async2 =
- static let token = AsyncLocal()
-
- static member UseToken ct =
- let old = token.Value
- token.Value <- ct
-
- { new IDisposable with
- member _.Dispose() = token.Value <- old
- }
-
- static member UseTokenAsync() =
- async {
- let! ct = Async.CancellationToken
- let old = token.Value
- token.Value <- ct
-
- return
- { new IDisposable with
- member _.Dispose() = token.Value <- old
- }
- }
-
- static member val Token = token.Value
-
module internal Async2Implementation =
open FSharp.Core.CompilerServices.StateMachineHelpers
@@ -39,6 +14,19 @@ module internal Async2Implementation =
open System.Runtime.CompilerServices
open System.Runtime.ExceptionServices
+ let failIfNot condition message =
+ if not condition then
+ failwith message
+
+ []
+ type Context =
+ {
+ Token: CancellationToken
+ IsNested: bool
+ }
+
+ let currentContext = AsyncLocal()
+
/// A structure that looks like an Awaiter
type Awaiter<'Awaiter, 'TResult
when 'Awaiter :> ICriticalNotifyCompletion
@@ -60,10 +48,6 @@ module internal Async2Implementation =
type Trampoline private () =
- let failIfNot condition message =
- if not condition then
- failwith message
-
let ownerThreadId = Thread.CurrentThread.ManagedThreadId
static let holder = new ThreadLocal<_>(fun () -> Trampoline())
@@ -124,7 +108,12 @@ module internal Async2Implementation =
let inline IncrementBindCount () =
bindCount.Value <- bindCount.Value + 1
- bindCount.Value % bindLimit = 0
+
+ if bindCount.Value >= bindLimit then
+ ResetBindCount()
+ true
+ else
+ false
let inline IncrementBindCountDynamic () =
if IncrementBindCount() then Bounce else Immediate
@@ -157,9 +146,23 @@ module internal Async2Implementation =
Throw exn
[]
- type internal Async2<'T>(start: unit -> Task<'T>) =
+ type Async2<'T>(start: unit -> Task<'T>) =
+
+ //static let tailCallSource = AsyncLocal voption>()
- member inline _.Start() = start ()
+ member _.startWithContext context =
+ let old = currentContext.Value
+ currentContext.Value <- context
+
+ try
+ BindContext.ResetBindCount()
+ start ()
+ finally
+ currentContext.Value <- old
+
+ member _.StartBound() =
+ failIfNot currentContext.Value.IsNested "StartBound requires a nested context"
+ start ()
[]
type Async2Data<'t> =
@@ -169,9 +172,6 @@ module internal Async2Implementation =
[]
val mutable MethodBuilder: AsyncTaskMethodBuilder<'t>
- []
- val mutable Hijack: bool
-
type Async2StateMachine<'TOverall> = ResumableStateMachine>
type IAsync2StateMachine<'TOverall> = IResumableStateMachine>
type Async2ResumptionFunc<'TOverall> = ResumptionFunc>
@@ -183,12 +183,12 @@ module internal Async2Implementation =
let inline filterCancellation (catch: exn -> Async2Code<_, _>) (exn: exn) =
Async2Code(fun sm ->
match exn with
- | :? OperationCanceledException as oce when oce.CancellationToken = Async2.Token -> raise exn
+ | :? OperationCanceledException as oce when oce.CancellationToken = currentContext.Value.Token -> raise exn
| _ -> (catch exn).Invoke(&sm))
let inline throwIfCancellationRequested (code: Async2Code<_, _>) =
Async2Code(fun sm ->
- Async2.Token.ThrowIfCancellationRequested()
+ currentContext.Value.Token.ThrowIfCancellationRequested()
code.Invoke(&sm))
let inline yieldOnBindLimit () =
@@ -271,6 +271,7 @@ module internal Async2Implementation =
let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm)
if __stack_yield_fin then
+ BindContext.ResetBindCount()
continuation(ExceptionCache.GetResultOrThrow awaiter).Invoke(&sm)
else
let mutable __stack_awaiter = awaiter
@@ -379,7 +380,7 @@ module internal Async2Implementation =
else
Async2Builder.RunDynamic(code)
- member inline _.Source(code: Async2<_>) = code.Start().GetAwaiter()
+ member inline _.Source(code: Async2<_>) = code.StartBound().GetAwaiter()
[]
module internal Async2AutoOpens =
@@ -393,43 +394,69 @@ module internal Async2LowPriority =
type Async2Builder with
member inline _.Source(awaitable: Awaitable<_, _, _>) = awaitable.GetAwaiter()
- member inline _.Source(expr: Async<_>) = Async.StartAsTask(expr, cancellationToken = Async2.Token).GetAwaiter()
+
+ member inline _.Source(expr: Async<_>) =
+ Async.StartAsTask(expr, cancellationToken = currentContext.Value.Token).GetAwaiter()
+
member inline _.Source(items: #seq<_>) : seq<_> = upcast items
[]
module internal Async2MediumPriority =
open Async2Implementation
+
type Async2Builder with
- member inline _.Source(task: Task) = task.GetAwaiter()
+ member inline _.Source(task: Task) = task.GetAwaiter()
member inline _.Source(task: Task<_>) = task.GetAwaiter()
open Async2Implementation
type internal Async2<'t> = Async2Implementation.Async2<'t>
+type internal Async2 =
+ static member CancellationToken = currentContext.Value.Token
+
+ static member UseTokenAsync() =
+ async {
+ let! ct = Async.CancellationToken
+ let old = currentContext.Value.Token
+ currentContext.Value <- { currentContext.Value with Token = ct }
+
+ return
+ { new IDisposable with
+ member _.Dispose() =
+ currentContext.Value <-
+ { currentContext.Value with
+ Token = old
+ }
+ }
+ }
+
module internal Async2 =
let run ct (code: Async2<'t>) =
- use _ = Async2.UseToken ct
+ let context = { Token = ct; IsNested = true }
if
isNull SynchronizationContext.Current
&& TaskScheduler.Current = TaskScheduler.Default
then
- BindContext.ResetBindCount()
- code.Start().GetAwaiter().GetResult()
+ code.startWithContext(context).GetAwaiter().GetResult()
else
- Task
- .Run<'t>(fun () ->
- BindContext.ResetBindCount()
- code.Start())
- .GetAwaiter()
- .GetResult()
+ Task.Run<'t>(fun () -> code.startWithContext (context)).GetAwaiter().GetResult()
let startAsTask (code: Async2<'t>) =
- BindContext.ResetBindCount()
- code.Start()
+ let context =
+ {
+ Token = CancellationToken.None
+ IsNested = true
+ }
+
+ code.startWithContext context
let runWithoutCancellation code = run CancellationToken.None code
- let toAsync (code: Async2<_>) = startAsTask code |> Async.AwaitTask
+ let toAsync (code: Async2<'t>) =
+ async {
+ let! ct = Async.CancellationToken
+ return run ct code
+ }
diff --git a/src/Compiler/Utilities/Cancellable.fs b/src/Compiler/Utilities/Cancellable.fs
index 33b8448688..812db9e369 100644
--- a/src/Compiler/Utilities/Cancellable.fs
+++ b/src/Compiler/Utilities/Cancellable.fs
@@ -1,11 +1,10 @@
namespace FSharp.Compiler
open Internal.Utilities.Library
-open System.Threading
[]
type Cancellable =
static member CheckAndThrow() =
// If we're not inside an async computation, the ambient cancellation token will be CancellationToken.None and nothing will happen
// otherwise, if we are inside an async computation, this will throw.
- Async2.Token.ThrowIfCancellationRequested()
+ Async2.CancellationToken.ThrowIfCancellationRequested()
diff --git a/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs b/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs
index d8988c1099..9b04ad0984 100644
--- a/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs
+++ b/tests/FSharp.Compiler.Service.Tests/ModuleReaderCancellationTests.fs
@@ -150,7 +150,7 @@ let parseAndCheck path source options =
| _, FSharpCheckFileAnswer.Aborted -> None
| _, FSharpCheckFileAnswer.Succeeded results -> Some results
- Async2.Token |> shouldEqual CancellationToken.None
+ Async2.CancellationToken |> shouldEqual CancellationToken.None
result
with :? OperationCanceledException ->
From fb0d318fbe65f54db5dda7897e0ef57f5eb8419a Mon Sep 17 00:00:00 2001
From: Jakub Majocha <1760221+majocha@users.noreply.github.com>
Date: Thu, 4 Sep 2025 18:26:16 +0200
Subject: [PATCH 19/48] wip
---
src/Compiler/Checking/CheckDeclarations.fs | 2 +-
src/Compiler/Checking/CheckDeclarations.fsi | 4 +-
src/Compiler/Driver/ParseAndCheckInputs.fs | 4 +-
src/Compiler/Driver/ParseAndCheckInputs.fsi | 6 +-
src/Compiler/Facilities/BuildGraph.fs | 70 +-
src/Compiler/Facilities/BuildGraph.fsi | 6 +-
src/Compiler/Facilities/DiagnosticsLogger.fs | 3 +
src/Compiler/Facilities/DiagnosticsLogger.fsi | 4 +
src/Compiler/Service/BackgroundCompiler.fs | 74 +-
src/Compiler/Service/FSharpCheckerResults.fsi | 6 +-
src/Compiler/Service/IncrementalBuild.fs | 103 +-
src/Compiler/Utilities/Async2.fs | 71 +-
.../BuildGraphTests.fs | 902 +++++++++---------
13 files changed, 635 insertions(+), 620 deletions(-)
diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs
index e6525223fc..eae9f43014 100644
--- a/src/Compiler/Checking/CheckDeclarations.fs
+++ b/src/Compiler/Checking/CheckDeclarations.fs
@@ -4871,7 +4871,7 @@ module TcDeclarations =
// Bind module types
//-------------------------------------------------------------------------
#nowarn 3511
-let rec TcSignatureElementNonMutRec (cenv: cenv) parent typeNames endm (env: TcEnv) synSigDecl: Async2 =
+let rec TcSignatureElementNonMutRec (cenv: cenv) parent typeNames endm (env: TcEnv) synSigDecl: IAsync2 =
async2 {
let g = cenv.g
try
diff --git a/src/Compiler/Checking/CheckDeclarations.fsi b/src/Compiler/Checking/CheckDeclarations.fsi
index 1a2be70f80..05f0a6f05b 100644
--- a/src/Compiler/Checking/CheckDeclarations.fsi
+++ b/src/Compiler/Checking/CheckDeclarations.fsi
@@ -60,7 +60,7 @@ val CheckOneImplFile:
ModuleOrNamespaceType option *
ParsedImplFileInput *
FSharpDiagnosticOptions ->
- Async2
+ IAsync2
val CheckOneSigFile:
TcGlobals *
@@ -73,7 +73,7 @@ val CheckOneSigFile:
FSharpDiagnosticOptions ->
TcEnv ->
ParsedSigFileInput ->
- Async2
+ IAsync2
exception NotUpperCaseConstructor of range: range
diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs
index ab15cee94a..a395f4a3cc 100644
--- a/src/Compiler/Driver/ParseAndCheckInputs.fs
+++ b/src/Compiler/Driver/ParseAndCheckInputs.fs
@@ -1225,7 +1225,7 @@ let CheckOneInput
tcSink: TcResultsSink,
tcState: TcState,
input: ParsedInput
- ) : Async2 =
+ ) : IAsync2 =
async2 {
try
use _ =
@@ -1445,7 +1445,7 @@ let CheckOneInputWithCallback
_skipImplIfSigExists: bool):
(unit -> bool) * TcConfig * TcImports * TcGlobals * LongIdent option * TcResultsSink * TcState * ParsedInput * bool
)
- : Async2> =
+ : IAsync2> =
async2 {
try
CheckSimulateException tcConfig
diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fsi b/src/Compiler/Driver/ParseAndCheckInputs.fsi
index 6233cd17c1..d22b6d40d2 100644
--- a/src/Compiler/Driver/ParseAndCheckInputs.fsi
+++ b/src/Compiler/Driver/ParseAndCheckInputs.fsi
@@ -180,7 +180,7 @@ val CheckOneInput:
tcSink: NameResolution.TcResultsSink *
tcState: TcState *
input: ParsedInput ->
- Async2<(TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType) * TcState>
+ IAsync2<(TcEnv * TopAttribs * CheckedImplFile option * ModuleOrNamespaceType) * TcState>
val CheckOneInputWithCallback:
node: NodeToTypeCheck ->
@@ -193,7 +193,7 @@ val CheckOneInputWithCallback:
tcState: TcState *
input: ParsedInput *
_skipImplIfSigExists: bool ->
- Async2>
+ IAsync2>
val AddCheckResultsToTcState:
tcGlobals: TcGlobals *
@@ -248,4 +248,4 @@ val CheckOneInputAndFinish:
tcSink: NameResolution.TcResultsSink *
tcState: TcState *
input: ParsedInput ->
- Async2<(TcEnv * TopAttribs * CheckedImplFile list * ModuleOrNamespaceType list) * TcState>
+ IAsync2<(TcEnv * TopAttribs * CheckedImplFile list * ModuleOrNamespaceType list) * TcState>
diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs
index db77f52ea1..f2b5284b8e 100644
--- a/src/Compiler/Facilities/BuildGraph.fs
+++ b/src/Compiler/Facilities/BuildGraph.fs
@@ -5,6 +5,8 @@ module FSharp.Compiler.BuildGraph
open System.Threading
open System.Globalization
+open Internal.Utilities.Library
+
[]
module GraphNode =
@@ -20,51 +22,42 @@ module GraphNode =
| None -> ()
[]
-type GraphNode<'T> private (computation: Async<'T>, cachedResult: ValueOption<'T>, cachedResultNode: Async<'T>) =
-
- let mutable computation = computation
+type GraphNode<'T> private (computation: IAsync2<'T>, cachedResult: ValueOption<'T>) =
let mutable requestCount = 0
let mutable cachedResult = cachedResult
- let mutable cachedResultNode: Async<'T> = cachedResultNode
-
- let isCachedResultNodeNotNull () =
- not (obj.ReferenceEquals(cachedResultNode, null))
let semaphore = new SemaphoreSlim(1, 1)
member _.GetOrComputeValue() =
- // fast path
- if isCachedResultNodeNotNull () then
- cachedResultNode
+ async2 {
+ if cachedResult.IsSome then
+ return cachedResult.Value
else
- async {
- let! ct = Async.CancellationToken
- Interlocked.Increment(&requestCount) |> ignore
- let enter = semaphore.WaitAsync(ct)
-
+ let! ct = Async.CancellationToken
+ Interlocked.Increment(&requestCount) |> ignore
+ let enter = semaphore.WaitAsync(ct)
+
+ try
+ do! enter |> Async.AwaitTask
+
+ match cachedResult with
+ | ValueSome value -> return value
+ | _ ->
+ Thread.CurrentThread.CurrentUICulture <- GraphNode.culture
+ let! result = computation
+ cachedResult <- ValueSome result
+ return result
+ finally
+ // At this point, the semaphore awaiter is either already completed or about to get canceled.
+ // If calling Wait() does not throw an exception it means the semaphore was successfully taken and needs to be released.
try
- do! enter |> Async.AwaitTask
-
- match cachedResult with
- | ValueSome value -> return value
- | _ ->
- Thread.CurrentThread.CurrentUICulture <- GraphNode.culture
- let! result = computation
- cachedResult <- ValueSome result
- cachedResultNode <- async.Return result
- computation <- Unchecked.defaultof<_>
- return result
- finally
- // At this point, the semaphore awaiter is either already completed or about to get canceled.
- // If calling Wait() does not throw an exception it means the semaphore was successfully taken and needs to be released.
- try
- enter.Wait()
- semaphore.Release() |> ignore
- with _ ->
- ()
-
- Interlocked.Decrement(&requestCount) |> ignore
+ enter.Wait()
+ semaphore.Release() |> ignore
+ with _ ->
+ ()
+
+ Interlocked.Decrement(&requestCount) |> ignore
}
member _.TryPeekValue() = cachedResult
@@ -74,7 +67,6 @@ type GraphNode<'T> private (computation: Async<'T>, cachedResult: ValueOption<'T
member _.IsComputing = requestCount > 0
static member FromResult(result: 'T) =
- let nodeResult = async.Return result
- GraphNode(nodeResult, ValueSome result, nodeResult)
+ GraphNode(async2 { return result }, ValueSome result)
- new(computation) = GraphNode(computation, ValueNone, Unchecked.defaultof<_>)
+ new(computation) = GraphNode(computation, ValueNone)
diff --git a/src/Compiler/Facilities/BuildGraph.fsi b/src/Compiler/Facilities/BuildGraph.fsi
index 2b3016bf99..44101ffad8 100644
--- a/src/Compiler/Facilities/BuildGraph.fsi
+++ b/src/Compiler/Facilities/BuildGraph.fsi
@@ -2,6 +2,8 @@
module internal FSharp.Compiler.BuildGraph
+open Internal.Utilities.Library
+
/// Contains helpers related to the build graph
[]
module internal GraphNode =
@@ -18,7 +20,7 @@ module internal GraphNode =
type internal GraphNode<'T> =
/// - computation - The computation code to run.
- new: computation: Async<'T> -> GraphNode<'T>
+ new: computation: IAsync2<'T> -> GraphNode<'T>
/// Creates a GraphNode with given result already cached.
static member FromResult: 'T -> GraphNode<'T>
@@ -26,7 +28,7 @@ type internal GraphNode<'T> =
/// Return NodeCode which, when executed, will get the value of the computation if already computed, or
/// await an existing in-progress computation for the node if one exists, or else will synchronously
/// start the computation on the current thread.
- member GetOrComputeValue: unit -> Async<'T>
+ member GetOrComputeValue: unit -> IAsync2<'T>
/// Return 'Some' if the computation has already been computed, else None if
/// the computation is in-progress or has not yet been started.
diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs
index 1ef603a2a9..4be3a5903c 100644
--- a/src/Compiler/Facilities/DiagnosticsLogger.fs
+++ b/src/Compiler/Facilities/DiagnosticsLogger.fs
@@ -984,3 +984,6 @@ module MultipleDiagnosticsLoggers =
return results.ToArray()
}
+
+ let Sequential2 computations = computations |> Seq.map Async2.toAsync |> Sequential
+ let Parallel2 computations = computations |> Seq.map Async2.toAsync |> Parallel
diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi
index f545babce8..6237cb89f2 100644
--- a/src/Compiler/Facilities/DiagnosticsLogger.fsi
+++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi
@@ -9,6 +9,8 @@ open FSharp.Compiler.Text
open System.Runtime.CompilerServices
open System.Runtime.InteropServices
+open Internal.Utilities.Library
+
/// Represents the style being used to format errors
[]
type DiagnosticStyle =
@@ -493,6 +495,8 @@ module MultipleDiagnosticsLoggers =
/// Captures the diagnostics from each computation and commits them to the caller's logger preserving their order.
/// When done, restores caller's build phase and diagnostics logger.
val Parallel: computations: Async<'T> seq -> Async<'T array>
+ val Parallel2: computations: #IAsync2<'T> seq -> Async<'T array>
/// Run computations sequentially starting immediately on the current thread.
val Sequential: computations: Async<'T> seq -> Async<'T array>
+ val Sequential2: computations: #IAsync2<'T> seq -> Async<'T array>
diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs
index b6ffa72ea9..e272b50e6d 100644
--- a/src/Compiler/Service/BackgroundCompiler.fs
+++ b/src/Compiler/Service/BackgroundCompiler.fs
@@ -368,7 +368,7 @@ type internal BackgroundCompiler
/// CreateOneIncrementalBuilder (for background type checking). Note that fsc.fs also
/// creates an incremental builder used by the command line compiler.
let CreateOneIncrementalBuilder (options: FSharpProjectOptions, userOpName) =
- async {
+ async2 {
use _ =
Activity.start "BackgroundCompiler.CreateOneIncrementalBuilder" [| Activity.Tags.project, options.ProjectFileName |]
@@ -474,14 +474,14 @@ type internal BackgroundCompiler
let tryGetBuilderNode options =
incrementalBuildersCache.TryGet(AnyCallerThread, options)
- let tryGetBuilder options : Async option =
+ let tryGetBuilder options : IAsync2 option =
tryGetBuilderNode options |> Option.map (fun x -> x.GetOrComputeValue())
- let tryGetSimilarBuilder options : Async option =
+ let tryGetSimilarBuilder options : IAsync2 option =
incrementalBuildersCache.TryGetSimilar(AnyCallerThread, options)
|> Option.map (fun x -> x.GetOrComputeValue())
- let tryGetAnyBuilder options : Async option =
+ let tryGetAnyBuilder options : IAsync2 option =
incrementalBuildersCache.TryGetAny(AnyCallerThread, options)
|> Option.map (fun x -> x.GetOrComputeValue())
@@ -495,14 +495,14 @@ type internal BackgroundCompiler
getBuilderNode)
let createAndGetBuilder (options, userOpName) =
- async {
+ async2 {
let! ct = Async.CancellationToken
let getBuilderNode = createBuilderNode (options, userOpName, ct)
return! getBuilderNode.GetOrComputeValue()
}
- let getOrCreateBuilder (options, userOpName) : Async =
- async {
+ let getOrCreateBuilder (options, userOpName) : IAsync2 =
+ async2 {
use! _holder = Async2.UseTokenAsync()
match tryGetBuilder options with
@@ -558,7 +558,7 @@ type internal BackgroundCompiler
| _ ->
let res =
GraphNode(
- async {
+ async2 {
let! res =
self.CheckOneFileImplAux(
parseResults,
@@ -638,7 +638,7 @@ type internal BackgroundCompiler
/// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API)
member _.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) =
- async {
+ async2 {
use _ =
Activity.start
"BackgroundCompiler.GetBackgroundParseResultsForFileInProject"
@@ -678,7 +678,7 @@ type internal BackgroundCompiler
}
member _.GetCachedCheckFileResult(builder: IncrementalBuilder, fileName, sourceText: ISourceText, options) =
- async {
+ async2 {
use _ =
Activity.start "BackgroundCompiler.GetCachedCheckFileResult" [| Activity.Tags.fileName, fileName |]
@@ -764,7 +764,7 @@ type internal BackgroundCompiler
creationDiags: FSharpDiagnostic[]
) =
- async {
+ async2 {
match! bc.GetCachedCheckFileResult(builder, fileName, sourceText, options) with
| Some(_, results) -> return FSharpCheckFileAnswer.Succeeded results
| _ ->
@@ -779,7 +779,7 @@ type internal BackgroundCompiler
member bc.CheckFileInProjectAllowingStaleCachedResults
(parseResults: FSharpParseFileResults, fileName, fileVersion, sourceText: ISourceText, options, userOpName)
=
- async {
+ async2 {
use _ =
Activity.start
"BackgroundCompiler.CheckFileInProjectAllowingStaleCachedResults"
@@ -790,7 +790,7 @@ type internal BackgroundCompiler
|]
let! cachedResults =
- async {
+ async2 {
let! builderOpt, creationDiags = getAnyBuilder (options, userOpName)
match builderOpt with
@@ -833,7 +833,7 @@ type internal BackgroundCompiler
member bc.CheckFileInProject
(parseResults: FSharpParseFileResults, fileName, fileVersion, sourceText: ISourceText, options, userOpName)
=
- async {
+ async2 {
use _ =
Activity.start
"BackgroundCompiler.CheckFileInProject"
@@ -876,7 +876,7 @@ type internal BackgroundCompiler
member bc.ParseAndCheckFileInProject
(fileName: string, fileVersion, sourceText: ISourceText, options: FSharpProjectOptions, userOpName)
=
- async {
+ async2 {
use _ =
Activity.start
"BackgroundCompiler.ParseAndCheckFileInProject"
@@ -945,7 +945,7 @@ type internal BackgroundCompiler
}
member _.NotifyFileChanged(fileName, options, userOpName) =
- async {
+ async2 {
use _ =
Activity.start
"BackgroundCompiler.NotifyFileChanged"
@@ -964,7 +964,7 @@ type internal BackgroundCompiler
/// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API)
member _.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName) =
- async {
+ async2 {
use _ =
Activity.start
"BackgroundCompiler.ParseAndCheckFileInProject"
@@ -1071,7 +1071,7 @@ type internal BackgroundCompiler
member _.FindReferencesInFile
(fileName: string, options: FSharpProjectOptions, symbol: FSharpSymbol, canInvalidateProject: bool, userOpName: string)
=
- async {
+ async2 {
use _ =
Activity.start
"BackgroundCompiler.FindReferencesInFile"
@@ -1099,7 +1099,7 @@ type internal BackgroundCompiler
}
member _.GetSemanticClassificationForFile(fileName: string, options: FSharpProjectOptions, userOpName: string) =
- async {
+ async2 {
use _ =
Activity.start
"BackgroundCompiler.GetSemanticClassificationForFile"
@@ -1162,7 +1162,7 @@ type internal BackgroundCompiler
/// Parse and typecheck the whole project (the implementation, called recursively as project graph is evaluated)
member private _.ParseAndCheckProjectImpl(options, userOpName) =
- async {
+ async2 {
let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName)
@@ -1235,7 +1235,7 @@ type internal BackgroundCompiler
}
member _.GetAssemblyData(options, userOpName) =
- async {
+ async2 {
use _ =
Activity.start
"BackgroundCompiler.GetAssemblyData"
@@ -1252,6 +1252,7 @@ type internal BackgroundCompiler
let! _, _, tcAssemblyDataOpt, _ = builder.GetCheckResultsAndImplementationsForProject()
return tcAssemblyDataOpt
}
+ |> Async2.toAsync
/// Get the timestamp that would be on the output if fully built immediately
member private _.TryGetLogicalTimeStampForProject(cache, options) =
@@ -1480,13 +1481,14 @@ type internal BackgroundCompiler
options: FSharpProjectOptions,
userOpName: string
) : Async =
- async {
+ async2 {
ignore parseResults
let! _, result = this.ParseAndCheckFileInProject(fileName, fileVersion, sourceText, options, userOpName)
return result
}
+ |> Async2.toAsync
member _.CheckFileInProjectAllowingStaleCachedResults
(
@@ -1497,7 +1499,7 @@ type internal BackgroundCompiler
options: FSharpProjectOptions,
userOpName: string
) : Async =
- self.CheckFileInProjectAllowingStaleCachedResults(parseResults, fileName, fileVersion, sourceText, options, userOpName)
+ self.CheckFileInProjectAllowingStaleCachedResults(parseResults, fileName, fileVersion, sourceText, options, userOpName) |> Async2.toAsync
member _.ClearCache(options: seq, userOpName: string) : unit = self.ClearCache(options, userOpName)
@@ -1511,10 +1513,10 @@ type internal BackgroundCompiler
member _.FindReferencesInFile
(fileName: string, options: FSharpProjectOptions, symbol: FSharpSymbol, canInvalidateProject: bool, userOpName: string)
: Async> =
- self.FindReferencesInFile(fileName, options, symbol, canInvalidateProject, userOpName)
+ self.FindReferencesInFile(fileName, options, symbol, canInvalidateProject, userOpName) |> Async2.toAsync
member this.FindReferencesInFile(fileName, projectSnapshot, symbol, userOpName) =
- this.FindReferencesInFile(fileName, projectSnapshot.ToOptions(), symbol, true, userOpName)
+ this.FindReferencesInFile(fileName, projectSnapshot.ToOptions(), symbol, true, userOpName) |> Async2.toAsync
member _.FrameworkImportsCache: FrameworkImportsCache = self.FrameworkImportsCache
@@ -1529,17 +1531,17 @@ type internal BackgroundCompiler
member _.GetBackgroundCheckResultsForFileInProject
(fileName: string, options: FSharpProjectOptions, userOpName: string)
: Async =
- self.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName)
+ self.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName) |> Async2.toAsync
member _.GetBackgroundParseResultsForFileInProject
(fileName: string, options: FSharpProjectOptions, userOpName: string)
: Async =
- self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName)
+ self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) |> Async2.toAsync
member _.GetCachedCheckFileResult
(builder: IncrementalBuilder, fileName: string, sourceText: ISourceText, options: FSharpProjectOptions)
: Async<(FSharpParseFileResults * FSharpCheckFileResults) option> =
- self.GetCachedCheckFileResult(builder, fileName, sourceText, options)
+ self.GetCachedCheckFileResult(builder, fileName, sourceText, options) |> Async2.toAsync
member _.GetProjectOptionsFromScript
(
@@ -1611,12 +1613,12 @@ type internal BackgroundCompiler
member _.GetSemanticClassificationForFile
(fileName: string, options: FSharpProjectOptions, userOpName: string)
: Async =
- self.GetSemanticClassificationForFile(fileName, options, userOpName)
+ self.GetSemanticClassificationForFile(fileName, options, userOpName) |> Async2.toAsync
member _.GetSemanticClassificationForFile
(fileName: string, snapshot: FSharpProjectSnapshot, userOpName: string)
: Async =
- self.GetSemanticClassificationForFile(fileName, snapshot.ToOptions(), userOpName)
+ self.GetSemanticClassificationForFile(fileName, snapshot.ToOptions(), userOpName) |> Async2.toAsync
member _.InvalidateConfiguration(options: FSharpProjectOptions, userOpName: string) : unit =
self.InvalidateConfiguration(options, userOpName)
@@ -1626,7 +1628,7 @@ type internal BackgroundCompiler
self.InvalidateConfiguration(options, userOpName)
member _.NotifyFileChanged(fileName: string, options: FSharpProjectOptions, userOpName: string) : Async =
- self.NotifyFileChanged(fileName, options, userOpName)
+ self.NotifyFileChanged(fileName, options, userOpName) |> Async2.toAsync
member _.NotifyProjectCleaned(options: FSharpProjectOptions, userOpName: string) : Async =
self.NotifyProjectCleaned(options, userOpName)
@@ -1634,7 +1636,7 @@ type internal BackgroundCompiler
member _.ParseAndCheckFileInProject
(fileName: string, fileVersion: int, sourceText: ISourceText, options: FSharpProjectOptions, userOpName: string)
: Async =
- self.ParseAndCheckFileInProject(fileName, fileVersion, sourceText, options, userOpName)
+ self.ParseAndCheckFileInProject(fileName, fileVersion, sourceText, options, userOpName) |> Async2.toAsync
member _.ParseAndCheckFileInProject
(fileName: string, projectSnapshot: FSharpProjectSnapshot, userOpName: string)
@@ -1647,14 +1649,14 @@ type internal BackgroundCompiler
let! sourceText = fileSnapshot.GetSource() |> Async.AwaitTask
let options = projectSnapshot.ToOptions()
- return! self.ParseAndCheckFileInProject(fileName, 0, sourceText, options, userOpName)
+ return! self.ParseAndCheckFileInProject(fileName, 0, sourceText, options, userOpName) |> Async2.toAsync
}
member _.ParseAndCheckProject(options: FSharpProjectOptions, userOpName: string) : Async =
- self.ParseAndCheckProject(options, userOpName)
+ self.ParseAndCheckProject(options, userOpName) |> Async2.toAsync
member _.ParseAndCheckProject(projectSnapshot: FSharpProjectSnapshot, userOpName: string) : Async =
- self.ParseAndCheckProject(projectSnapshot.ToOptions(), userOpName)
+ self.ParseAndCheckProject(projectSnapshot.ToOptions(), userOpName) |> Async2.toAsync
member _.ParseFile
(fileName: string, sourceText: ISourceText, options: FSharpParsingOptions, cache: bool, flatErrors: bool, userOpName: string)
@@ -1664,7 +1666,7 @@ type internal BackgroundCompiler
member _.ParseFile(fileName: string, projectSnapshot: FSharpProjectSnapshot, userOpName: string) =
let options = projectSnapshot.ToOptions()
- self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName)
+ self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) |> Async2.toAsync
member _.ProjectChecked: IEvent = self.ProjectChecked
diff --git a/src/Compiler/Service/FSharpCheckerResults.fsi b/src/Compiler/Service/FSharpCheckerResults.fsi
index 8aefb7f825..9589916a22 100644
--- a/src/Compiler/Service/FSharpCheckerResults.fsi
+++ b/src/Compiler/Service/FSharpCheckerResults.fsi
@@ -46,7 +46,7 @@ type DelayedILModuleReader =
/// Will lazily create the ILModuleReader.
/// Is only evaluated once and can be called by multiple threads.
- member internal TryGetILModuleReader: unit -> Async2
+ member internal TryGetILModuleReader: unit -> IAsync2
/// Unused in this API
type public FSharpUnresolvedReferencesSet = internal FSharpUnresolvedReferencesSet of UnresolvedAssemblyReference list
@@ -501,7 +501,7 @@ type public FSharpCheckFileResults =
parseErrors: FSharpDiagnostic[] *
keepAssemblyContents: bool *
suggestNamesForErrors: bool ->
- Async2
+ IAsync2
/// The result of calling TypeCheckResult including the possibility of abort and background compiler not caught up.
and [] public FSharpCheckFileAnswer =
@@ -618,7 +618,7 @@ type internal FsiInteractiveChecker =
member internal ParseAndCheckInteraction:
sourceText: ISourceText * ?userOpName: string ->
- Async2
+ IAsync2
module internal FSharpCheckerResultsSettings =
val defaultFSharpBinariesDir: string
diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs
index cf171e78f0..82d5fd0a70 100644
--- a/src/Compiler/Service/IncrementalBuild.fs
+++ b/src/Compiler/Service/IncrementalBuild.fs
@@ -136,7 +136,7 @@ module IncrementalBuildSyntaxTree =
), sourceRange, fileName, [||]
let parse (source: FSharpSource) =
- async {
+ async2 {
IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBEParsed fileName)
use _ =
Activity.start "IncrementalBuildSyntaxTree.parse"
@@ -247,8 +247,8 @@ type BoundModel private (
?tcStateOpt: GraphNode * GraphNode
) =
- let getTypeCheck (syntaxTree: SyntaxTree) : Async =
- async {
+ let getTypeCheck (syntaxTree: SyntaxTree) : IAsync2 =
+ async2 {
let! input, _sourceRange, fileName, parseErrors = syntaxTree.ParseNode.GetOrComputeValue()
use _ = Activity.start "BoundModel.TypeCheck" [|Activity.Tags.fileName, fileName|]
@@ -317,13 +317,13 @@ type BoundModel private (
| _ -> None
let getTcInfo (typeCheck: GraphNode) =
- async {
+ async2 {
let! tcInfo , _, _, _, _ = typeCheck.GetOrComputeValue()
return tcInfo
} |> GraphNode
let getTcInfoExtras (typeCheck: GraphNode) =
- async {
+ async2 {
let! _ , sink, implFile, fileName, _ = typeCheck.GetOrComputeValue()
// Build symbol keys
let itemKeyStore, semanticClassification =
@@ -361,17 +361,17 @@ type BoundModel private (
}
} |> GraphNode
- let defaultTypeCheck = async { return prevTcInfo, TcResultsSinkImpl(tcGlobals), None, "default typecheck - no syntaxTree", [||] }
+ let defaultTypeCheck = async2 { return prevTcInfo, TcResultsSinkImpl(tcGlobals), None, "default typecheck - no syntaxTree", [||] }
let typeCheckNode = syntaxTreeOpt |> Option.map getTypeCheck |> Option.defaultValue defaultTypeCheck |> GraphNode
let tcInfoExtras = getTcInfoExtras typeCheckNode
let diagnostics =
- async {
+ async2 {
let! _, _, _, _, diags = typeCheckNode.GetOrComputeValue()
return diags
} |> GraphNode
let startComputingFullTypeCheck =
- async {
+ async2 {
let! _ = tcInfoExtras.GetOrComputeValue()
return! diagnostics.GetOrComputeValue()
}
@@ -386,7 +386,7 @@ type BoundModel private (
GraphNode.FromResult tcInfo, tcInfoExtras
| _ ->
// start computing extras, so that typeCheckNode can be GC'd quickly
- startComputingFullTypeCheck |> Async.Catch |> Async.Ignore |> Async.Start
+ startComputingFullTypeCheck |> Async2.toAsync |> Async.Catch |> Async.Ignore |> Async.Start
getTcInfo typeCheckNode, tcInfoExtras
member val Diagnostics = diagnostics
@@ -412,13 +412,13 @@ type BoundModel private (
member this.GetOrComputeTcInfoExtras = this.TcInfoExtras.GetOrComputeValue
- member this.GetOrComputeTcInfoWithExtras() = async {
+ member this.GetOrComputeTcInfoWithExtras() = async2 {
let! tcInfo = this.TcInfo.GetOrComputeValue()
let! tcInfoExtras = this.TcInfoExtras.GetOrComputeValue()
return tcInfo, tcInfoExtras
}
- member this.Next(syntaxTree) = async {
+ member this.Next(syntaxTree) = async2 {
let! tcInfo = this.TcInfo.GetOrComputeValue()
return
BoundModel(
@@ -437,7 +437,7 @@ type BoundModel private (
}
member this.Finish(finalTcDiagnosticsRev, finalTopAttribs) =
- async {
+ async2 {
let! tcInfo = this.TcInfo.GetOrComputeValue()
let finishState = { tcInfo with tcDiagnosticsRev = finalTcDiagnosticsRev; topAttribs = finalTopAttribs }
return
@@ -534,7 +534,7 @@ type FrameworkImportsCache(size) =
match frameworkTcImportsCache.TryGet (AnyCallerThread, key) with
| Some lazyWork -> lazyWork
| None ->
- let lazyWork = GraphNode(async {
+ let lazyWork = GraphNode(async2 {
let tcConfigP = TcConfigProvider.Constant tcConfig
return! TcImports.BuildFrameworkTcImports (tcConfigP, frameworkDLLs, nonFrameworkResolutions)
})
@@ -546,7 +546,7 @@ type FrameworkImportsCache(size) =
/// This function strips the "System" assemblies from the tcConfig and returns a age-cached TcImports for them.
member this.Get(tcConfig: TcConfig) =
- async {
+ async2 {
// Split into installed and not installed.
let frameworkDLLs, nonFrameworkResolutions, unresolved = TcAssemblyResolutions.SplitNonFoundationalResolutions(tcConfig)
let node = this.GetNode(tcConfig, frameworkDLLs, nonFrameworkResolutions)
@@ -581,6 +581,7 @@ type FrameworkImportsCache(size) =
return tcGlobals, frameworkTcImports, nonFrameworkResolutions, unresolved
}
+ |> Async2.toAsync
/// Represents the interim state of checking an assembly
[]
@@ -600,21 +601,23 @@ type PartialCheckResults (boundModel: BoundModel, timeStamp: DateTime, projectTi
member _.TryPeekTcInfoWithExtras() = boundModel.TryPeekTcInfoWithExtras()
- member _.GetOrComputeTcInfo() = boundModel.GetOrComputeTcInfo()
+ member _.GetOrComputeTcInfo() = boundModel.GetOrComputeTcInfo() |> Async2.toAsync
- member _.GetOrComputeTcInfoWithExtras() = boundModel.GetOrComputeTcInfoWithExtras()
+ member _.GetOrComputeTcInfoWithExtras() = boundModel.GetOrComputeTcInfoWithExtras() |> Async2.toAsync
member _.GetOrComputeItemKeyStoreIfEnabled() =
- async {
+ async2 {
let! info = boundModel.GetOrComputeTcInfoExtras()
return info.itemKeyStore
}
+ |> Async2.toAsync
member _.GetOrComputeSemanticClassificationIfEnabled() =
- async {
+ async2 {
let! info = boundModel.GetOrComputeTcInfoExtras()
return info.semanticClassificationKeyStore
}
+ |> Async2.toAsync
[]
module Utilities =
@@ -760,28 +763,28 @@ module IncrementalBuilderHelpers =
/// Finish up the typechecking to produce outputs for the rest of the compilation process
let FinalizeTypeCheckTask (tcConfig: TcConfig) tcGlobals partialCheck assemblyName outfile (boundModels: GraphNode seq) =
- async {
+ async2 {
let diagnosticsLogger = CompilationDiagnosticLogger("FinalizeTypeCheckTask", tcConfig.diagnosticsOptions)
use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.TypeCheck)
- let! computedBoundModels = boundModels |> Seq.map (fun g -> g.GetOrComputeValue()) |> MultipleDiagnosticsLoggers.Sequential
+ let! computedBoundModels = boundModels |> Seq.map (fun g -> g.GetOrComputeValue()) |> MultipleDiagnosticsLoggers.Sequential2
let! tcInfos =
computedBoundModels
- |> Seq.map (fun boundModel -> async { return! boundModel.GetOrComputeTcInfo() })
- |> MultipleDiagnosticsLoggers.Sequential
+ |> Seq.map (fun boundModel -> async2 { return! boundModel.GetOrComputeTcInfo() })
+ |> MultipleDiagnosticsLoggers.Sequential2
// tcInfoExtras can be computed in parallel. This will check any previously skipped implementation files in parallel, too.
let! latestImplFiles =
computedBoundModels
- |> Seq.map (fun boundModel -> async {
+ |> Seq.map (fun boundModel -> async2 {
if partialCheck then
return None
else
let! tcInfoExtras = boundModel.GetOrComputeTcInfoExtras()
return tcInfoExtras.latestImplFile
})
- |> MultipleDiagnosticsLoggers.Parallel
+ |> MultipleDiagnosticsLoggers.Parallel2
let results = [
for tcInfo, latestImplFile in Seq.zip tcInfos latestImplFiles ->
@@ -850,7 +853,7 @@ module IncrementalBuilderHelpers =
let! partialDiagnostics =
computedBoundModels
|> Seq.map (fun m -> m.Diagnostics.GetOrComputeValue())
- |> MultipleDiagnosticsLoggers.Parallel
+ |> MultipleDiagnosticsLoggers.Parallel2
let diagnostics = [
diagnosticsLogger.GetDiagnostics()
yield! partialDiagnostics |> Seq.rev
@@ -970,13 +973,13 @@ module IncrementalBuilderStateHelpers =
type BuildStatus = Invalidated | Good
let createBoundModelGraphNode (prevBoundModel: GraphNode) syntaxTree =
- GraphNode(async {
+ GraphNode(async2 {
let! prevBoundModel = prevBoundModel.GetOrComputeValue()
return! prevBoundModel.Next(syntaxTree)
})
let createFinalizeBoundModelGraphNode (initialState: IncrementalBuilderInitialState) (boundModels: GraphNode seq) =
- GraphNode(async {
+ GraphNode(async2 {
use _ = Activity.start "GetCheckResultsAndImplementationsForProject" [|Activity.Tags.project, initialState.outfile|]
let! result =
FinalizeTypeCheckTask
@@ -1144,7 +1147,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
tryGetSlot state (slot - 1)
let evalUpToTargetSlot (state: IncrementalBuilderState) targetSlot =
- async {
+ async2 {
if targetSlot < 0 then
return Some(initialBoundModel, defaultTimeStamp)
else
@@ -1175,9 +1178,10 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
let mutable currentState = state
- let setCurrentState state cache (ct: CancellationToken) =
- async {
- do! semaphore.WaitAsync(ct) |> Async.AwaitTask
+ let setCurrentState state cache =
+ async2 {
+ let ct = Async2.CancellationToken
+ do! semaphore.WaitAsync(ct)
try
ct.ThrowIfCancellationRequested()
currentState <- computeStampedFileNames initialState state cache
@@ -1185,11 +1189,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
semaphore.Release() |> ignore
}
- let checkFileTimeStamps (cache: TimeStampCache) =
- async {
- let! ct = Async.CancellationToken
- do! setCurrentState currentState cache ct
- }
+ let checkFileTimeStamps (cache: TimeStampCache) = setCurrentState currentState cache
do IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBECreated)
@@ -1217,12 +1217,13 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
member _.AllDependenciesDeprecated = allDependencies
member _.PopulatePartialCheckingResults () =
- async {
+ async2 {
let cache = TimeStampCache defaultTimeStamp // One per step
do! checkFileTimeStamps cache
let! _ = currentState.finalizedBoundModel.GetOrComputeValue()
projectChecked.Trigger()
}
+ |> Async2.toAsync
member builder.GetCheckResultsBeforeFileInProjectEvenIfStale fileName: PartialCheckResults option =
let slotOfFile = builder.GetSlotOfFileName fileName
@@ -1259,7 +1260,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
(builder.TryGetCheckResultsBeforeFileInProject fileName).IsSome
member builder.GetCheckResultsBeforeSlotInProject slotOfFile =
- async {
+ async2 {
let cache = TimeStampCache defaultTimeStamp
do! checkFileTimeStamps cache
let! result = evalUpToTargetSlot currentState (slotOfFile - 1)
@@ -1269,9 +1270,10 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
return PartialCheckResults(boundModel, timestamp, projectTimeStamp)
| None -> return! failwith "Expected results to be ready. (GetCheckResultsBeforeSlotInProject)."
}
+ |> Async2.toAsync
member builder.GetFullCheckResultsBeforeSlotInProject slotOfFile =
- async {
+ async2 {
let cache = TimeStampCache defaultTimeStamp
do! checkFileTimeStamps cache
let! result = evalUpToTargetSlot currentState (slotOfFile - 1)
@@ -1282,6 +1284,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
return PartialCheckResults(boundModel, timestamp, projectTimeStamp)
| None -> return! failwith "Expected results to be ready. (GetFullCheckResultsBeforeSlotInProject)."
}
+ |> Async2.toAsync
member builder.GetCheckResultsBeforeFileInProject fileName =
let slotOfFile = builder.GetSlotOfFileName fileName
@@ -1296,17 +1299,18 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
builder.GetFullCheckResultsBeforeSlotInProject slotOfFile
member builder.GetFullCheckResultsAfterFileInProject fileName =
- async {
+ async2 {
let slotOfFile = builder.GetSlotOfFileName fileName + 1
let! result = builder.GetFullCheckResultsBeforeSlotInProject(slotOfFile)
return result
}
+ |> Async2.toAsync
member builder.GetCheckResultsAfterLastFileInProject () =
builder.GetCheckResultsBeforeSlotInProject(builder.GetSlotsCount())
member builder.GetCheckResultsAndImplementationsForProject() =
- async {
+ async2 {
let cache = TimeStampCache(defaultTimeStamp)
do! checkFileTimeStamps cache
let! result = currentState.finalizedBoundModel.GetOrComputeValue()
@@ -1316,14 +1320,16 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
let projectTimeStamp = builder.GetLogicalTimeStampForProject(cache)
return PartialCheckResults (boundModel, timestamp, projectTimeStamp), ilAssemRef, tcAssemblyDataOpt, tcAssemblyExprOpt
}
+ |> Async2.toAsync
member builder.GetFullCheckResultsAndImplementationsForProject() =
- async {
+ async2 {
let! result = builder.GetCheckResultsAndImplementationsForProject()
let results, _, _, _ = result
let! _ = results.GetOrComputeTcInfoWithExtras() // Make sure we forcefully evaluate the info
return result
}
+ |> Async2.toAsync
member builder.GetLogicalTimeStampForFileInProject(filename: string) =
let slot = builder.GetSlotOfFileName(filename)
@@ -1363,18 +1369,18 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
let slotOfFile = builder.GetSlotOfFileName fileName
let syntaxTree = currentState.slots[slotOfFile].SyntaxTree
syntaxTree.ParseNode.GetOrComputeValue()
- |> Async.RunSynchronously
+ |> Async2.runWithoutCancellation
member builder.NotifyFileChanged(fileName, timeStamp) =
- async {
+ async2 {
let slotOfFile = builder.GetSlotOfFileName fileName
let cache = TimeStampCache defaultTimeStamp
- let! ct = Async.CancellationToken
do! setCurrentState
{ currentState with
slots = currentState.slots |> List.updateAt slotOfFile (currentState.slots[slotOfFile].Notify timeStamp) }
- cache ct
+ cache
}
+ |> Async2.toAsync
member _.SourceFiles = fileNames |> Seq.map (fun f -> f.Source.FilePath) |> List.ofSeq
@@ -1407,14 +1413,14 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
let useSimpleResolutionSwitch = "--simpleresolution"
- async {
+ async2 {
// Trap and report diagnostics from creation.
let delayedLogger = CapturingDiagnosticsLogger("IncrementalBuilderCreation")
use _ = new CompilationGlobalsScope(delayedLogger, BuildPhase.Parameter)
let! builderOpt =
- async {
+ async2 {
try
// Create the builder.
@@ -1670,3 +1676,4 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc
return builderOpt, diagnostics
}
+ |> Async2.toAsync
diff --git a/src/Compiler/Utilities/Async2.fs b/src/Compiler/Utilities/Async2.fs
index 73e96a40bf..76ff8cb5a3 100644
--- a/src/Compiler/Utilities/Async2.fs
+++ b/src/Compiler/Utilities/Async2.fs
@@ -6,6 +6,9 @@ open System.Threading.Tasks
#nowarn 3513
+type internal IAsync2<'t> =
+ abstract Start: unit -> Task<'t>
+
module internal Async2Implementation =
open FSharp.Core.CompilerServices.StateMachineHelpers
@@ -27,6 +30,9 @@ module internal Async2Implementation =
let currentContext = AsyncLocal()
+ type Invokable<'Async2, 'TResult
+ when 'Async2: (member Start: unit -> Task<'TResult>)> = 'Async2
+
/// A structure that looks like an Awaiter
type Awaiter<'Awaiter, 'TResult
when 'Awaiter :> ICriticalNotifyCompletion
@@ -148,21 +154,11 @@ module internal Async2Implementation =
[]
type Async2<'T>(start: unit -> Task<'T>) =
- //static let tailCallSource = AsyncLocal voption>()
-
- member _.startWithContext context =
- let old = currentContext.Value
- currentContext.Value <- context
+ interface IAsync2<'T> with
- try
- BindContext.ResetBindCount()
- start ()
- finally
- currentContext.Value <- old
+ member _.Start() = start ()
- member _.StartBound() =
- failIfNot currentContext.Value.IsNested "StartBound requires a nested context"
- start ()
+ //static let tailCallSource = AsyncLocal voption>()
[]
type Async2Data<'t> =
@@ -183,11 +179,13 @@ module internal Async2Implementation =
let inline filterCancellation (catch: exn -> Async2Code<_, _>) (exn: exn) =
Async2Code(fun sm ->
match exn with
- | :? OperationCanceledException as oce when oce.CancellationToken = currentContext.Value.Token -> raise exn
+ | :? OperationCanceledException as oce when oce.CancellationToken = currentContext.Value.Token ->
+ raise exn
| _ -> (catch exn).Invoke(&sm))
let inline throwIfCancellationRequested (code: Async2Code<_, _>) =
Async2Code(fun sm ->
+ if currentContext.Value.Token.IsCancellationRequested then printfn "throwing cancellation"
currentContext.Value.Token.ThrowIfCancellationRequested()
code.Invoke(&sm))
@@ -283,7 +281,7 @@ module internal Async2Implementation =
[]
member inline this.ReturnFrom(awaiter) : Async2Code<'T, 'T> = this.Bind(awaiter, this.Return)
- static member inline RunDynamic(code: Async2Code<'T, 'T>) : Async2<'T> =
+ static member inline RunDynamic(code: Async2Code<'T, 'T>) : IAsync2<'T> =
let initialResumptionFunc = Async2ResumptionFunc<'T>(fun sm -> code.Invoke &sm)
let resumptionInfo () =
@@ -339,7 +337,7 @@ module internal Async2Implementation =
copy.Data.MethodBuilder.Start(©)
copy.Data.MethodBuilder.Task)
- member inline _.Run(code: Async2Code<'T, 'T>) : Async2<'T> =
+ member inline _.Run(code: Async2Code<'T, 'T>) : IAsync2<'T> =
if __useResumableCode then
__stateMachine, _>
@@ -380,7 +378,7 @@ module internal Async2Implementation =
else
Async2Builder.RunDynamic(code)
- member inline _.Source(code: Async2<_>) = code.StartBound().GetAwaiter()
+ member inline _.Source(code: IAsync2<_>) = code.Start().GetAwaiter()
[]
module internal Async2AutoOpens =
@@ -410,8 +408,6 @@ module internal Async2MediumPriority =
open Async2Implementation
-type internal Async2<'t> = Async2Implementation.Async2<'t>
-
type internal Async2 =
static member CancellationToken = currentContext.Value.Token
@@ -433,30 +429,39 @@ type internal Async2 =
module internal Async2 =
- let run ct (code: Async2<'t>) =
+ let inline start (code: IAsync2<_>) = code.Start()
+
+ let inline startWithContext context code =
+ let old = currentContext.Value
+ currentContext.Value <- context
+
+ try
+ BindContext.ResetBindCount()
+ start code
+ finally
+ currentContext.Value <- old
+
+ let run ct (code: IAsync2<'t>) =
let context = { Token = ct; IsNested = true }
if
isNull SynchronizationContext.Current
&& TaskScheduler.Current = TaskScheduler.Default
then
- code.startWithContext(context).GetAwaiter().GetResult()
+ (code |> startWithContext context).GetAwaiter().GetResult()
else
- Task.Run<'t>(fun () -> code.startWithContext (context)).GetAwaiter().GetResult()
-
- let startAsTask (code: Async2<'t>) =
- let context =
- {
- Token = CancellationToken.None
- IsNested = true
- }
-
- code.startWithContext context
+ Task.Run<'t>(fun () -> code |> startWithContext context).GetAwaiter().GetResult()
let runWithoutCancellation code = run CancellationToken.None code
- let toAsync (code: Async2<'t>) =
+ let toAsync (code: IAsync2<'t>) =
+
async {
let! ct = Async.CancellationToken
- return run ct code
+ return! Async.FromContinuations <| fun (cont, econt, ccont) ->
+ try
+ cont (run ct code)
+ with
+ | :? OperationCanceledException as oce when oce.CancellationToken = ct -> ccont oce
+ | exn -> econt exn
}
diff --git a/tests/FSharp.Compiler.Service.Tests/BuildGraphTests.fs b/tests/FSharp.Compiler.Service.Tests/BuildGraphTests.fs
index 4769b4c322..9f6e95ae95 100644
--- a/tests/FSharp.Compiler.Service.Tests/BuildGraphTests.fs
+++ b/tests/FSharp.Compiler.Service.Tests/BuildGraphTests.fs
@@ -12,534 +12,534 @@ open FSharp.Compiler.DiagnosticsLogger
open Internal.Utilities.Library
open FSharp.Compiler.Diagnostics
-module BuildGraphTests =
+//module BuildGraphTests =
- []
- let private createNode () =
- let o = obj ()
- GraphNode(async {
- Assert.shouldBeTrue (o <> null)
- return 1
- }), WeakReference(o)
+// []
+// let private createNode () =
+// let o = obj ()
+// GraphNode(async {
+// Assert.shouldBeTrue (o <> null)
+// return 1
+// }), WeakReference(o)
- []
- let ``Initialization of graph node should not have a computed value``() =
- let node = GraphNode(async { return 1 })
- Assert.shouldBeTrue(node.TryPeekValue().IsNone)
- Assert.shouldBeFalse(node.HasValue)
+// []
+// let ``Initialization of graph node should not have a computed value``() =
+// let node = GraphNode(async { return 1 })
+// Assert.shouldBeTrue(node.TryPeekValue().IsNone)
+// Assert.shouldBeFalse(node.HasValue)
- []
- let ``Two requests to get a value asynchronously should be successful``() =
- let resetEvent = new ManualResetEvent(false)
- let resetEventInAsync = new ManualResetEvent(false)
+// []
+// let ``Two requests to get a value asynchronously should be successful``() =
+// let resetEvent = new ManualResetEvent(false)
+// let resetEventInAsync = new ManualResetEvent(false)
- let graphNode =
- GraphNode(async {
- resetEventInAsync.Set() |> ignore
- let! _ = Async.AwaitWaitHandle(resetEvent)
- return 1
- })
+// let graphNode =
+// GraphNode(async {
+// resetEventInAsync.Set() |> ignore
+// let! _ = Async.AwaitWaitHandle(resetEvent)
+// return 1
+// })
- let task1 =
- async {
- let! _ = graphNode.GetOrComputeValue()
- ()
- } |> Async.StartAsTask
+// let task1 =
+// async {
+// let! _ = graphNode.GetOrComputeValue()
+// ()
+// } |> Async.StartAsTask
- let task2 =
- async {
- let! _ = graphNode.GetOrComputeValue()
- ()
- } |> Async.StartAsTask
+// let task2 =
+// async {
+// let! _ = graphNode.GetOrComputeValue()
+// ()
+// } |> Async.StartAsTask
- resetEventInAsync.WaitOne() |> ignore
- resetEvent.Set() |> ignore
- try
- task1.Wait(1000) |> ignore
- task2.Wait() |> ignore
- with
- | :? TimeoutException -> reraise()
- | _ -> ()
+// resetEventInAsync.WaitOne() |> ignore
+// resetEvent.Set() |> ignore
+// try
+// task1.Wait(1000) |> ignore
+// task2.Wait() |> ignore
+// with
+// | :? TimeoutException -> reraise()
+// | _ -> ()
- []
- let ``Many requests to get a value asynchronously should only evaluate the computation once``() =
- let requests = 10000
- let mutable computationCount = 0
+// [