Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
* Reference assembly MVIDs are now deterministic across compiler invocations. Previously, `--refout` / `<ProduceReferenceAssembly>true</ProduceReferenceAssembly>` produced a different MVID every build because the implied signature hash used .NET's randomized `String.GetHashCode()`. ([Issue #19751](https://github.com/dotnet/fsharp/issues/19751), [PR #19801](https://github.com/dotnet/fsharp/pull/19801))
* Parser: recover on unfinished if and binary expressions
([PR #19724](https://github.com/dotnet/fsharp/pull/19724))
* Fix recursive inline-member optimization dependencies so inline consumers in recursive groups are resolved reliably without changing static initialization order. ([Issue #20085](https://github.com/dotnet/fsharp/issues/20085), [PR #20111](https://github.com/dotnet/fsharp/pull/20111))
* Fix `SynExpr.shouldBeParenthesizedInContext` to report parentheses as required around `SynExpr.Sequential` expressions used as record or anonymous-record field values, so the IDE "remove unnecessary parentheses" analyzer no longer breaks code like `{| A = ((); B = 3) |}`. ([Issue #17826](https://github.com/dotnet/fsharp/issues/17826), [PR #19850](https://github.com/dotnet/fsharp/pull/19850))
* Fix semantic classification of `IDisposable` and other interface types in type-occurrence positions being incorrectly classified as `DisposableType` instead of `Interface`. ([Issue #16268](https://github.com/dotnet/fsharp/issues/16268), [PR #19809](https://github.com/dotnet/fsharp/pull/19809))
* Fix missing semantic classification on second and later type qualifiers in nested copy-and-update expressions like `{ p with Person.Info.X = 1; Person.Info.Y = 2 }`. ([Issue #17428](https://github.com/dotnet/fsharp/issues/17428), [PR #19878](https://github.com/dotnet/fsharp/pull/19878))
Expand Down
154 changes: 146 additions & 8 deletions src/Compiler/Optimize/Optimizer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ let GetInfoForLocalValue cenv env (v: Val) m =
match env.localExternalVals.TryFind v.Stamp with
| Some vval -> vval
| None ->
if v.ShouldInline then
if cenv.optimizing && v.ShouldInline then
errorR(Error(FSComp.SR.optValueMarkedInlineButWasNotBoundInTheOptEnv(fullDisplayTextOfValRef (mkLocalValRef v)), m))
UnknownValInfo

Expand Down Expand Up @@ -3191,11 +3191,11 @@ and TryOptimizeVal cenv env (vOpt: ValRef option, shouldInline, inlineIfLambda,
| TupleValue _ | UnionCaseValue _ | RecdValue _ when shouldInline ->
failwith "tuple, union and record values cannot be marked 'inline'"

| UnknownValue when shouldInline && cenv.settings.alwaysInline ->
| UnknownValue when shouldInline && cenv.settings.alwaysInline && cenv.optimizing ->
warning(Error(FSComp.SR.optValueMarkedInlineHasUnexpectedValue(), m))
None

| _ when shouldInline && cenv.settings.alwaysInline ->
| _ when shouldInline && cenv.settings.alwaysInline && cenv.optimizing ->
warning(Error(FSComp.SR.optValueMarkedInlineCouldNotBeInlined(), m))
None

Expand Down Expand Up @@ -3241,7 +3241,7 @@ and OptimizeVal cenv env expr (v: ValRef, m) =
e, AddValEqualityInfo g m v einfo

| None ->
if cenv.settings.alwaysInline then
if cenv.optimizing && cenv.settings.alwaysInline then
if v.ShouldInline then
match valInfoForVal.ValExprInfo with
| UnknownValue -> error(Error(FSComp.SR.optFailedToInlineValue(v.DisplayName), m))
Expand Down Expand Up @@ -4491,7 +4491,20 @@ and OptimizeBinding cenv isRec env (TBind(vref, expr, spBind)) =
raise (ReportedError (Some exn))

and OptimizeBindings cenv isRec env xs =
List.mapFold (OptimizeBinding cenv isRec) env xs
if isRec then
let xsArray = xs |> List.toArray
let order = GetBindingOptimizationOrder cenv false true xs

let results, env =
(env, order)
||> List.mapFold (fun env idx ->
let result, env = OptimizeBinding cenv isRec env xsArray[idx]
(idx, result), env)

let resultsByIndex = results |> Map.ofList
[ for idx in 0 .. xsArray.Length - 1 -> resultsByIndex[idx] ], env
else
List.mapFold (OptimizeBinding cenv isRec) env xs

and OptimizeModuleExprWithSig cenv env mty def =
let g = cenv.g
Expand Down Expand Up @@ -4581,11 +4594,109 @@ and OptimizeModuleExprWithSig cenv env mty def =
and mkValBind (bind: Binding) info =
(mkLocalValRef bind.Var, info)

and GetBindingOptimizationOrder cenv inlineDependenciesOnly preferLowArity (binds: Binding list) =
// Recursive binding groups are published to the optimizer incrementally as each binding is
// processed. If a caller is optimized before a later sibling it depends on, inline lookup can
// observe an incomplete optimization environment. Compute a dependency-first schedule for the
// recursive group, then restore source order after optimization.
let bindsArray = binds |> List.toArray

let bindIndexByStamp =
binds
|> List.mapi (fun idx bind -> bind.Var.Stamp, idx)
|> Map.ofList

let addDependency depIdxs stamp =
match bindIndexByStamp |> Map.tryFind stamp with
| Some depIdx when not inlineDependenciesOnly || bindsArray[depIdx].Var.ShouldInline ->
Set.add depIdx depIdxs
| None -> depIdxs
| Some _ -> depIdxs

let rec addBindingDependencies depIdxs expr =
let addVals depIdxs vals =
vals
|> Seq.fold (fun depIdxs (v: Val) -> addDependency depIdxs v.Stamp) depIdxs

let rec addTraitSolutionDependencies depIdxs (traitInfo: TraitConstraintInfo) =
match traitInfo.Solution with
| Some(FSMethSln(_, vref, _, _)) -> addDependency depIdxs vref.Deref.Stamp
| Some(ClosedExprSln witnessExpr) -> addBindingDependencies depIdxs witnessExpr
| _ -> depIdxs

let fvs = freeInExpr CollectLocalsNoCaching expr

let depIdxs =
let depIdxs = addVals depIdxs (fvs.FreeLocals |> Zset.elements)
addVals depIdxs (fvs.FreeTyvars.FreeTraitSolutions |> Zset.elements)

let folder =
{ ExprFolder0 with
exprIntercept =
(fun _exprF noInterceptF depIdxs expr ->
let depIdxs =
match expr with
| Expr.Val(vref, _, _) -> addDependency depIdxs vref.Deref.Stamp
// Member-constraint calls can hide the real sibling dependency behind
// a witness expression, so fold over the resolved witness as well.
| Expr.Op(TOp.TraitCall traitInfo, _, args, m) ->
let depIdxs = addTraitSolutionDependencies depIdxs traitInfo

match ConstraintSolver.CodegenWitnessExprForTraitConstraint cenv.TcVal cenv.g cenv.amap m traitInfo args with
| OkResult (_, Some witnessExpr) -> addBindingDependencies depIdxs witnessExpr
| _ -> depIdxs
| _ -> depIdxs

noInterceptF depIdxs expr) }

FoldExpr folder depIdxs expr

let dependencyIndexes =
binds
|> List.map (fun (TBind(_, expr, _)) ->
addBindingDependencies Set.empty expr |> Set.toArray)
|> List.toArray

let ordered = ResizeArray()
let visiting = HashSet<int>()
let visited = HashSet<int>()

let rec visit idx =
if not (visited.Contains idx) then
if not (visiting.Contains idx) then
visiting.Add idx |> ignore

for depIdx in dependencyIndexes[idx] do
if depIdx <> idx then
visit depIdx

visiting.Remove idx |> ignore
visited.Add idx |> ignore
ordered.Add idx

let rootOrder =
[ 0 .. binds.Length - 1 ]
|> (if preferLowArity then
List.sortBy (fun idx ->
let arity =
bindsArray[idx].Var.ValReprInfo
|> Option.map (fun repr -> repr.TotalArgCount)
|> Option.defaultValue 0

arity, -idx)
else
id)

for idx in rootOrder do
visit idx

ordered |> Seq.toList

and OptimizeModuleContents cenv (env, bindInfosColl) input =
match input with
| TMDefRec(isRec, opens, tycons, mbinds, m) ->
let env = if isRec then BindInternalValsToUnknown cenv (allValsOfModDef input) env else env
let mbindInfos, (env, bindInfosColl) = OptimizeModuleBindings cenv (env, bindInfosColl) mbinds
let mbindInfos, (env, bindInfosColl) = OptimizeModuleBindings cenv isRec (env, bindInfosColl) mbinds
let mbinds, minfos = List.unzip mbindInfos
let binds = minfos |> List.choose (function Choice1Of2 (x, _) -> Some x | _ -> None)
let binfos = minfos |> List.choose (function Choice1Of2 (_, x) -> Some x | _ -> None)
Expand Down Expand Up @@ -4615,8 +4726,35 @@ and OptimizeModuleContents cenv (env, bindInfosColl) input =
let (defs, info), (env, bindInfosColl) = OptimizeModuleDefs cenv (env, bindInfosColl) defs
(TMDefs defs, info), (env, bindInfosColl)

and OptimizeModuleBindings cenv (env, bindInfosColl) xs =
List.mapFold (OptimizeModuleBinding cenv) (env, bindInfosColl) xs
and OptimizeModuleBindings cenv isRec (env, bindInfosColl) xs =
let bindingGroup =
xs
|> List.map (function
| ModuleOrNamespaceBinding.Binding bind -> Some bind
| _ -> None)

let binds = bindingGroup |> List.choose id

if
isRec
&& (bindingGroup |> List.forall Option.isSome)
&& (binds |> List.exists (fun bind -> bind.Var.ShouldInline))
then
let xsArray = xs |> List.toArray
let preferLowArity = binds |> List.forall (fun bind -> bind.Var.IsMember)
let order = GetBindingOptimizationOrder cenv true preferLowArity binds

let results, (env, bindInfosColl) =
((env, bindInfosColl), order)
||> List.mapFold (fun state idx ->
let result, state = OptimizeModuleBinding cenv state xsArray[idx]
(idx, result), state)

let resultsByIndex = results |> Map.ofList
// Keep the emitted binding list in source order; only the optimization schedule changes.
[ for idx in 0 .. xsArray.Length - 1 -> resultsByIndex[idx] ], (env, bindInfosColl)
else
List.mapFold (OptimizeModuleBinding cenv) (env, bindInfosColl) xs

and OptimizeModuleBinding cenv (env, bindInfosColl) x =
match x with
Expand Down
4 changes: 2 additions & 2 deletions tests/AheadOfTime/Trimming/check.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ function CheckTrim($root, $tfm, $outputfile, $expected_len, $callerLineNumber) {
$allErrors = @()

# Check net9.0 trimmed assemblies.
$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 311296 -callerLineNumber 66
$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 310272 -callerLineNumber 66

# Check net9.0 trimmed assemblies with static linked FSharpCore.
# Statically links FSharp.Compiler.Service; the size is stable now that its codegen is
# deterministic (#19928/#19929). Update if compiler/trimming output intentionally changes.
$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9174016 -callerLineNumber 71
$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9172992 -callerLineNumber 71

# Check net9.0 trimmed assemblies with F# metadata resources removed
$allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7613440 -callerLineNumber 74
Expand Down
Loading
Loading