Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/10.0.402.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BLOCKER before servicing approval


* Preserve static state-machine lowering for resumable builders in Debug. ([Issue #20466](https://github.com/dotnet/fsharp/issues/20466), [PR #20692](https://github.com/dotnet/fsharp/pull/20692))
90 changes: 74 additions & 16 deletions src/Compiler/Optimize/Optimizer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,8 @@ type cenv =

specializedInlineVals: HashMultiMap<Stamp, TType * Expr>

forcedInlineVals: Dictionary<Stamp, bool>

signatureHidingInfo: SignatureHidingInfo
}

Expand Down Expand Up @@ -621,20 +623,21 @@ let BindTyparsToUnknown (tps: Typar list) env =
let BindCcu (ccu: CcuThunk) mval env (_g: TcGlobals) =
{ env with globalModuleInfos=env.globalModuleInfos.Add(ccu.AssemblyName, mval) }

/// Lookup information about values
let GetInfoForLocalValue cenv env (v: Val) m =
// Abstract slots do not have values
if v.IsDispatchSlot then UnknownValInfo
let TryGetInfoForLocalValue cenv env (v: Val) =
if v.IsDispatchSlot then None
else
match cenv.localInternalVals.TryGetValue v.Stamp with
| true, res -> res
| _ ->
match env.localExternalVals.TryFind v.Stamp with
| Some vval -> vval
| None ->
if v.ShouldInline then
errorR(Error(FSComp.SR.optValueMarkedInlineButWasNotBoundInTheOptEnv(fullDisplayTextOfValRef (mkLocalValRef v)), m))
UnknownValInfo
| true, res -> Some res
| _ -> env.localExternalVals.TryFind v.Stamp

/// Lookup information about values
let GetInfoForLocalValue cenv env (v: Val) m =
match TryGetInfoForLocalValue cenv env v with
| Some vval -> vval
| None ->
if not v.IsDispatchSlot && v.ShouldInline then
errorR(Error(FSComp.SR.optValueMarkedInlineButWasNotBoundInTheOptEnv(fullDisplayTextOfValRef (mkLocalValRef v)), m))
UnknownValInfo

let TryGetInfoForCcu env (ccu: CcuThunk) = env.globalModuleInfos.TryFind(ccu.AssemblyName)

Expand Down Expand Up @@ -2388,11 +2391,53 @@ let shouldForceInlineMembersInDebug (g: TcGlobals) (tcref: EntityRef) =
| true, modRef -> tyconRefEq g tcref modRef
| _ -> false

let shouldForceInlineInDebug (g: TcGlobals) (vref: ValRef) : bool =
// Resumable templates and their code arguments must remain in the caller's method.
let rec HasForcedInlineBody cenv env (vref: ValRef) =
let stamp = vref.Stamp

match cenv.forcedInlineVals.TryGetValue stamp with
| true, res -> res
| _ ->
// The expression walk also visits local bindings whose optimization info is not available yet.
let info =
if vref.IsLocalRef then
TryGetInfoForLocalValue cenv env vref.binding
else
Some(GetInfoForNonLocalVal cenv env vref)

match info |> Option.map (fun info -> stripValue info.ValExprInfo) with
| Some(CurriedLambdaValue (_, _, _, body, _)) ->
cenv.forcedInlineVals[stamp] <- false // Break cycles while the body is inspected
let res = ExprNeedsForcedInlining cenv env body
cenv.forcedInlineVals[stamp] <- res
res
| _ -> false

and ExprNeedsForcedInlining cenv env expr =
let folder =
{ ExprFolder0 with
exprIntercept =
fun _ noInterceptF acc expr ->
if acc then true else

match expr with
| StructStateMachineExpr cenv.g _ -> true
| Expr.Val (vref, _, _) when vref.ShouldInline -> HasForcedInlineBody cenv env vref
| _ -> noInterceptF acc expr }

FoldExpr folder false expr

let shouldForceInlineInDebug cenv env (vref: ValRef) : bool =
let g = cenv.g

ValHasWellKnownAttribute g WellKnownValAttributes.NoDynamicInvocationAttribute_True vref.Deref ||
ValHasWellKnownAttribute g WellKnownValAttributes.NoDynamicInvocationAttribute_False vref.Deref ||

vref.HasDeclaringEntity && shouldForceInlineMembersInDebug g vref.DeclaringEntity
(vref.HasDeclaringEntity && shouldForceInlineMembersInDebug g vref.DeclaringEntity) ||

isReturnsResumableCodeTy g vref.TauType ||

HasForcedInlineBody cenv env vref

/// Optimize/analyze an expression
let rec OptimizeExpr cenv (env: IncrementalOptimizationEnv) expr =
Expand Down Expand Up @@ -3148,7 +3193,7 @@ and TryOptimizeVal cenv env (vOpt: ValRef option, shouldInline, inlineIfLambda,
Some (remarkExpr m (copyExpr g CloneAllAndMarkExprValsAsCompilerGenerated expr))

| CurriedLambdaValue (_, _, _, expr, _) when
shouldInline && (cenv.settings.alwaysInline || Option.exists (shouldForceInlineInDebug cenv.g) vOpt) ||
shouldInline && (cenv.settings.alwaysInline || Option.exists (shouldForceInlineInDebug cenv env) vOpt) ||
inlineIfLambda && cenv.settings.alwaysInline ->
let fvs = freeInExpr CollectLocals expr
if usesMethodLocalConstructsOrProtectedField cenv fvs expr then
Expand Down Expand Up @@ -3503,7 +3548,7 @@ and TryInlineApplication cenv env finfo (valExpr: Expr) (tyargs: TType list, arg
let g = cenv.g

match cenv.settings.alwaysInline, stripExpr valExpr with
| false, Expr.Val(vref, _, _) when vref.ShouldInline && not (shouldForceInlineInDebug cenv.g vref) ->
| false, Expr.Val(vref, _, _) when vref.ShouldInline && not (shouldForceInlineInDebug cenv env vref) ->
let hasNoTraits =
let tps, _ = tryDestForallTy g vref.Type
GetTraitConstraintInfosOfTypars g tps |> List.isEmpty
Expand Down Expand Up @@ -3539,6 +3584,18 @@ and TryInlineApplication cenv env finfo (valExpr: Expr) (tyargs: TType list, arg
let specLambda = MakeApplicationAndBetaReduce g (f2R, origLambdaTy, [tyargs], [], m)
let specLambdaTy = tyOfExpr g specLambda

let hasStateMachineTemplate =
(false, specLambdaTy)
||> SimplifyTypes.foldTypeButNotConstraints (stripTyEqns g) (fun found ty ->
found ||
(tryTcrefOfAppTy g ty |> ValueOption.exists (tyconRefEq g g.ResumableStateMachine_tcr)))

// A separate helper loses type parameters of the struct that replaces this template during lowering.
if hasStateMachineTemplate then
let cenv = { cenv with settings = { cenv.settings with alwaysInline = true } }
Some(OptimizeApplication cenv env (valExpr, vref.Type, tyargs, argsR, m))
else

// Typars that flow in from the enclosing scope when tyargs are non-concrete. A tyarg can reach
// only the body, and typars left unabstracted below are erased to 'object'.
let freeTypars =
Expand Down Expand Up @@ -4671,6 +4728,7 @@ let OptimizeImplFile (settings, ccu, tcGlobals, tcVal, importMap, optEnv, isIncr
stackGuard = StackGuard("OptimizerStackGuardDepth")
realsig = tcGlobals.realsig
specializedInlineVals = HashMultiMap(HashIdentity.Structural, true)
forcedInlineVals = Dictionary<Stamp, bool>()
signatureHidingInfo = SignatureHidingInfo.Empty
}

Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/TypedTree/TcGlobals.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ type internal TcGlobals =

member ResumableCode_tcr: TypedTree.EntityRef

member ResumableStateMachine_tcr: TypedTree.EntityRef

member System_Runtime_CompilerServices_RuntimeFeature_ty: TypedTree.TType option

member addrof2_vref: TypedTree.ValRef
Expand Down
13 changes: 7 additions & 6 deletions src/Compiler/TypedTree/TypedTreeOps.FreeVars.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1082,19 +1082,20 @@ module internal MemberRepresentation =
module SimplifyTypes =

// CAREFUL! This function does NOT walk constraints
let rec foldTypeButNotConstraints f z ty =
let ty = stripTyparEqns ty
let rec foldTypeButNotConstraints normalizeType f z ty =
let ty = normalizeType ty
let z = f z ty

match ty with
| TType_forall(_, bodyTy) -> foldTypeButNotConstraints f z bodyTy
| TType_forall(_, bodyTy) -> foldTypeButNotConstraints normalizeType f z bodyTy

| TType_app(_, tys, _)
| TType_ucase(_, tys)
| TType_anon(_, tys)
| TType_tuple(_, tys) -> List.fold (foldTypeButNotConstraints f) z tys
| TType_tuple(_, tys) -> List.fold (foldTypeButNotConstraints normalizeType f) z tys

| TType_fun(domainTy, rangeTy, _) -> foldTypeButNotConstraints f (foldTypeButNotConstraints f z domainTy) rangeTy
| TType_fun(domainTy, rangeTy, _) ->
foldTypeButNotConstraints normalizeType f (foldTypeButNotConstraints normalizeType f z domainTy) rangeTy

| TType_var _ -> z

Expand All @@ -1109,7 +1110,7 @@ module internal MemberRepresentation =
let accTyparCounts z ty =
// Walk type to determine typars and their counts (for pprinting decisions)
(z, ty)
||> foldTypeButNotConstraints (fun z ty ->
||> foldTypeButNotConstraints stripTyparEqns (fun z ty ->
match ty with
| TType_var(tp, _) when tp.Rigidity = TyparRigidity.Rigid -> incM tp z
| _ -> z)
Expand Down
5 changes: 4 additions & 1 deletion src/Compiler/TypedTree/TypedTreeOps.FreeVars.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,12 @@ module internal MemberRepresentation =

val prefixOfInferenceTypar: Typar -> string

/// Utilities used in simplifying types for visual presentation
/// Utilities for traversing and simplifying types
module SimplifyTypes =

/// Fold normalized type structure without following type-parameter constraints.
val foldTypeButNotConstraints: (TType -> TType) -> ('State -> TType -> 'State) -> 'State -> TType -> 'State

type TypeSimplificationInfo =
{ singletons: Typar Zset
inplaceConstraints: Zmap<Typar, TType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1738,6 +1738,41 @@ let main _ =
|> compileAndRun
|> verifySequencePoints

[<Fact>]
let ``Resumable 04 - Builder Run is inlined`` () =
FSharp """
open Microsoft.FSharp.Core.CompilerServices
open Microsoft.FSharp.Core.CompilerServices.StateMachineHelpers

#nowarn "3501"
#nowarn "3513"

type Builder() =
member inline _.Run(code: ResumableCode<unit, int>) =
if __useResumableCode then
__stateMachine<unit, int>
(MoveNextMethodImpl<_>(fun sm -> code.Invoke(&sm) |> ignore))
(SetStateMachineMethodImpl<_>(fun _ _ -> ()))
(AfterCode<_, _>(fun _ -> 42))
else
0

let builder = Builder()

[<EntryPoint>]
let main _ =
let code = ResumableCode<unit, int>(fun _ -> true)
let result = builder.Run code
if result = 42 then 0 else 1
"""
|> withDebug
|> withNoOptimize
|> asExe
|> compileAndRun
|> shouldSucceed
|> withExitCode 0
|> verifySequencePoints

[<Fact>]
let ``InlineIfLambda 01 - Debug`` () =
FSharp """
Expand Down Expand Up @@ -1770,4 +1805,3 @@ let main _ =
|> compile
|> shouldSucceed
|> verifyILNotPresent ["call int32 Test::apply(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<int32,int32>,"]

Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,88 @@ let main _ =

Test::main
(6,13-6,17) task
IL_0000: call TaskBuilderModule::get_task
IL_0005: stloc.1
IL_0006: ldloc.1
IL_0007: ldloc.1
IL_0008: ldloc.1
IL_0009: newobj t@6::.ctor
IL_000e: callvirt TaskBuilderBase::Delay
IL_0013: callvirt TaskBuilder::Run
IL_0018: stloc.0
IL_0000: ldloca.s 1
IL_0002: initobj t@6
IL_0008: ldloca.s 1
IL_000a: stloc.2
IL_000b: ldloc.2
IL_000c: ldflda t@6::Data
IL_0011: call Create
IL_0016: stfld MethodBuilder
IL_001b: ldloc.2
IL_001c: ldflda t@6::Data
IL_0021: ldflda MethodBuilder
IL_0026: ldloc.2
IL_0027: call Start
IL_002c: ldloc.2
IL_002d: ldflda t@6::Data
IL_0032: ldflda MethodBuilder
IL_0037: call get_Task
IL_003c: stloc.0

(7,5-7,25) if t.Result = 1 then
IL_0019: ldloc.0
IL_001a: callvirt get_Result
IL_001f: ldc.i4.1
IL_0020: bne.un.s IL_0024
IL_003d: ldloc.0
IL_003e: callvirt get_Result
IL_0043: ldc.i4.1
IL_0044: bne.un.s IL_0048

(7,26-7,27) 0
IL_0022: ldc.i4.0
IL_0023: ret
IL_0046: ldc.i4.0
IL_0047: ret

(7,33-7,34) 1
IL_0024: ldc.i4.1
IL_0025: ret
IL_0048: ldc.i4.1
IL_0049: ret

t@6::Invoke
(6,20-6,28) return 1
t@6::MoveNext
<hidden>
IL_0000: ldarg.0
IL_0001: ldfld t@6::builder@
IL_0006: ldc.i4.1
IL_0007: tail.
IL_0009: callvirt TaskBuilderBase::Return
IL_000e: ret
IL_0001: ldfld t@6::ResumptionPoint
IL_0006: stloc.0

(6,20-6,28) return 1
IL_0007: ldc.i4.1
IL_0008: stloc.3
IL_0009: ldarg.0
IL_000a: ldflda t@6::Data
IL_000f: ldloc.3
IL_0010: stfld Result
IL_0015: ldc.i4.1
IL_0016: stloc.2
IL_0017: ldloc.2
IL_0018: brfalse.s IL_0037

<hidden>
IL_001a: ldarg.0
IL_001b: ldflda t@6::Data
IL_0020: ldflda MethodBuilder
IL_0025: ldarg.0
IL_0026: ldflda t@6::Data
IL_002b: ldfld Result
IL_0030: call SetResult
IL_0035: leave.s IL_0045

<hidden>
IL_0037: leave.s IL_0045
IL_0039: castclass Exception
IL_003e: stloc.s 4
IL_0040: ldloc.s 4
IL_0042: stloc.1
IL_0043: leave.s IL_0045

<hidden>
IL_0045: ldloc.1
IL_0046: stloc.s 5
IL_0048: ldloc.s 5
IL_004a: brtrue.s IL_004d

<hidden>
IL_004c: ret

<hidden>
IL_004d: ldarg.0
IL_004e: ldflda t@6::Data
IL_0053: ldflda MethodBuilder
IL_0058: ldloc.s 5
IL_005a: call SetException
IL_005f: ret
Loading
Loading