@@ -7,6 +7,9 @@ open System.Runtime.CompilerServices
77open Microsoft.FSharp .Core .CompilerServices
88open System.Collections .Generic
99open System.Threading
10+ open System
11+
12+ #nowarn " 42"
1013
1114module internal AsyncHelpers =
1215
@@ -19,6 +22,43 @@ module internal AsyncHelpers =
1922 let inline awaitAwaitable awaiter =
2023 awaitAwaiter ( Awaitable.GetAwaiter awaiter)
2124
25+ (*
26+ A big comment explaining the unsafe cast hack
27+
28+ Typically, F# would want to generate IL like this for returning a Task<int> from a method:
29+
30+ .method public static class [System.Runtime]System.Threading.Tasks.Task`1<int32>
31+ fsharp_tenBindAsync_TaskBuilderRuntime() cil managed async
32+
33+ ... stuff in between
34+
35+
36+ IL_01e2: call class [System.Runtime]System.Threading.Tasks.Task`1<!!0> [System.Runtime]System.Threading.Tasks.Task::FromResult<int32>(!!0)
37+ IL_01e7: ret
38+
39+ However as noted in https://github.com/dotnet/runtime/blob/main/docs/design/specs/runtime-async.md
40+
41+ > Async methods also do not have matching return type conventions as sync methods. For sync methods,
42+ > the stack should contain a value convertible to the stated return type before the ret instruction.
43+ > For async methods, the stack should be empty in the case of Task or ValueTask, or the type argument in the case of Task<T> or ValueTask<T>.
44+
45+ Which means we can't return a Task<int> in the IL, we need to return int directly from the async method.
46+ To achieve this, we use an unsafe cast hack to cast the int directly to Task<int> to make the compiler happy and emit the correct IL.
47+
48+ With this as the return value, the IL generated is:
49+
50+ IL_0044: call instance !0 valuetype [System.Runtime]System.Runtime.CompilerServices.ValueTaskAwaiter`1<int32>::GetResult()
51+ IL_0049: ret
52+
53+ We have the ValueTask because of either .Return or .Zero returning a ValueTask
54+
55+ *)
56+
57+ module internal Unsafe =
58+ let inline cast < 'a , 'b > ( a : 'a ) : 'b =
59+
60+ ( # " " a : 'b #)
61+
2262type TaskBuilderBaseRuntime () =
2363
2464 member inline _.Return ( value : 'T ) =
@@ -195,7 +235,7 @@ type TaskBuilderRuntime() =
195235
196236 member inline this.Run ( [<InlineIfLambda>] f : unit -> 'a ) : Task < 'T > =
197237 AsyncHelpers.awaitAwaiter ( f ())
198- |> Task.FromResult
238+ |> AsyncHelpers.Unsafe.cast
199239
200240 /// Used to force type inference to prefer Task<_> for parameters of functions using the build
201241 member inline _.Source ( task : Task < 'T >) = Awaitable.GetTaskAwaiter task
@@ -212,9 +252,11 @@ type BackgroundTaskBuilderRuntime() =
212252 && obj.ReferenceEquals( TaskScheduler.Current, TaskScheduler.Default)
213253 then
214254 AsyncHelpers.awaitAwaiter ( f ())
215- |> Task.FromResult
255+ |> AsyncHelpers.Unsafe.cast
216256 else
217- Task.Run< 'T>( fun () -> AsyncHelpers.awaitAwaiter ( f ()))
257+ Task.Run< 'T>( Func< 'T>( fun () -> AsyncHelpers.awaitAwaiter ( f ()))) .GetAwaiter()
258+ |> AsyncHelpers.awaitAwaiter
259+ |> AsyncHelpers.Unsafe.cast
218260
219261
220262 /// Used to force type inference to prefer Task<_> for parameters of functions using the build
@@ -226,7 +268,7 @@ type ValueTaskBuilderRuntime() =
226268
227269 member inline this.Run ( [<InlineIfLambda>] f ) : ValueTask < 'T > =
228270 AsyncHelpers.awaitAwaiter ( f ())
229- |> ValueTask.FromResult
271+ |> AsyncHelpers.Unsafe.cast
230272
231273
232274 /// Used to force type inference to prefer ValueTask<_> for parameters of functions using the build
0 commit comments