Skip to content

State machines: low-level resumable code not always expanded correctly, without warning #19296

@majocha

Description

@majocha

This is esoteric, I know.

Execute in interactive

open FSharp.Core.CompilerServices
open FSharp.Core.CompilerServices.StateMachineHelpers
open System.Runtime.CompilerServices

let inline MoveOnce(x: byref<'T> when 'T :> IAsyncStateMachine and 'T :> IResumableStateMachine<'Data>) =
    x.MoveNext()
    x.Data

let inline helper x =
    ResumableCode<int, int>(fun sm ->
        if __useResumableCode then
            sm.Data <- x
            true
        else
            failwith "unexpected dynamic branch at runtime")

let inline repro x =
    if __useResumableCode then
        __stateMachine<int, int>
            (MoveNextMethodImpl<_>(fun sm ->
 
                // helper function is not expanded correctly
                (helper x).Invoke(&sm) |> ignore))

            (SetStateMachineMethodImpl<_>(fun _ _ -> ()))
            (AfterCode<_, _>(fun sm -> MoveOnce(&sm)))
    else
        failwith "overall state machine should be static"

repro 42

expected result 42, actual: "unexpected dynamic branch at runtime"

The overall state machine compiles statically, no warning here. Yet at runtime the inlined helper function enters the dynamic branch which by then should have been erased but somehow survives the expansion.

The workaround is to bind the result to a __stack_ value:

                let __stack_result = (helper x).Invoke(&sm)
                ignore __stack_result

The problem is:

It is not mentioned in the RFC.
Gives no warning at compile time.
At runtime can fail in mysterious ways, including but not limited to NREs.

Impact is low, but a fix should be uncomplicated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    Status

    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions