Skip to content

Commit 53ba0b4

Browse files
authored
Debugger: prefer no inlining in non-optimized builds (#19548)
1 parent 95ff08f commit 53ba0b4

121 files changed

Lines changed: 9416 additions & 1883 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

azure-pipelines-PR.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,6 @@ stages:
898898
buildScript: dotnet build Prime.sln --configuration Release
899899
displayName: Prime_Build
900900
- repo: bryanedds/Nu
901-
commit: b321cb41a0bea0dab6b4509f895e6cd4d024e9e5
901+
commit: e81e00a464b9d35d272f61708a1a0bfbf487b6d5
902902
buildScript: dotnet build Nu.sln --configuration Release
903903
displayName: Nu_Build

docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,4 @@
130130
* Exception field serialization (`GetObjectData` and field-restoring constructor) is now gated behind `langversion:11` (`LanguageFeature.ExceptionFieldSerializationSupport`). With langversion ≤10, exception codegen is unchanged from pre-#19342 behavior. ([PR #19746](https://github.com/dotnet/fsharp/pull/19746))
131131

132132
### Breaking Changes
133+
* Optimizer: don't inline named functions in debug builds ([PR #19548](https://github.com/dotnet/fsharp/pull/19548)

src/Compiler/Checking/PostInferenceChecks.fs

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,9 @@ type cenv =
223223
mutable entryPointGiven: bool
224224

225225
/// Callback required for quotation generation
226-
tcVal: ConstraintSolver.TcValF }
226+
tcVal: ConstraintSolver.TcValF
227+
228+
inlineBindingBodies: Dictionary<Stamp, Expr> }
227229

228230
override x.ToString() = "<cenv>"
229231

@@ -519,6 +521,9 @@ let AccessInternalsVisibleToAsInternal thisCompPath internalsVisibleToPaths acce
519521
(access, internalsVisibleToPaths) ||> List.fold (fun access internalsVisibleToPath ->
520522
accessSubstPaths (thisCompPath, internalsVisibleToPath) access)
521523

524+
let isLessAccessibleWithVisibility (cenv: cenv) itemAccess refAccess =
525+
let thisCompPath = compPathOfCcu cenv.viewCcu
526+
isLessAccessible (itemAccess |> AccessInternalsVisibleToAsInternal thisCompPath cenv.internalsVisibleToPaths) refAccess
522527

523528
let CheckTypeForAccess (cenv: cenv) env objName valAcc m ty =
524529
if cenv.reportErrors then
@@ -529,9 +534,7 @@ let CheckTypeForAccess (cenv: cenv) env objName valAcc m ty =
529534
match tryTcrefOfAppTy cenv.g ty with
530535
| ValueNone -> ()
531536
| ValueSome tcref ->
532-
let thisCompPath = compPathOfCcu cenv.viewCcu
533-
let tyconAcc = tcref.Accessibility |> AccessInternalsVisibleToAsInternal thisCompPath cenv.internalsVisibleToPaths
534-
if isLessAccessible tyconAcc valAcc then
537+
if isLessAccessibleWithVisibility cenv tcref.Accessibility valAcc then
535538
errorR(Error(FSComp.SR.chkTypeLessAccessibleThanType(tcref.DisplayName, objName()), m))
536539

537540
CheckTypeDeep cenv (visitType, None, None, None, None) cenv.g env NoInfo ty
@@ -545,9 +548,7 @@ let WarnOnWrongTypeForAccess (cenv: cenv) env objName valAcc m ty =
545548
match tryTcrefOfAppTy cenv.g ty with
546549
| ValueNone -> ()
547550
| ValueSome tcref ->
548-
let thisCompPath = compPathOfCcu cenv.viewCcu
549-
let tyconAcc = tcref.Accessibility |> AccessInternalsVisibleToAsInternal thisCompPath cenv.internalsVisibleToPaths
550-
if isLessAccessible tyconAcc valAcc then
551+
if isLessAccessibleWithVisibility cenv tcref.Accessibility valAcc then
551552
let errorText = FSComp.SR.chkTypeLessAccessibleThanType(tcref.DisplayName, objName()) |> snd
552553
let warningText = errorText + Environment.NewLine + FSComp.SR.tcTypeAbbreviationsCheckedAtCompileTime()
553554
warning(ObsoleteDiagnostic(false, None, Some warningText, None, m))
@@ -2081,6 +2082,29 @@ and AdjustAccess isHidden (cpath: unit -> CompilationPath) access =
20812082
else
20822083
access
20832084

2085+
// An 'inline' value is inlined into (possibly external) callers, so any function it references must be
2086+
// at least as accessible as the value itself (FS1113). Inline callees are followed transitively because
2087+
// the optimizer inlines them away; only module/member bindings can escape their scope.
2088+
and CheckInlineValueIsSufficientlyAccessible cenv env (v: Val) bindRhs =
2089+
if cenv.reportErrors && v.ShouldInline && not v.IsCompilerGenerated &&
2090+
(v.IsMemberOrModuleBinding || v.IsMember) && not v.IsIncrClassGeneratedMember then
2091+
let inlineAcc =
2092+
AdjustAccess (IsHiddenVal env.sigToImplRemapInfo v) (fun () -> v.DeclaringEntity.CompilationPath) v.Accessibility
2093+
let visited = HashSet<Stamp>()
2094+
let rec escapes expr =
2095+
(freeInExpr CollectLocals expr).FreeLocals |> Zset.exists (fun w ->
2096+
(w.IsMemberOrModuleBinding || w.IsMember) &&
2097+
isLessAccessibleWithVisibility cenv w.Accessibility inlineAcc &&
2098+
(if w.ShouldInline then
2099+
visited.Add w.Stamp &&
2100+
(match cenv.inlineBindingBodies.TryGetValue w.Stamp with
2101+
| true, body -> escapes body
2102+
| _ -> false)
2103+
else
2104+
true))
2105+
if escapes bindRhs then
2106+
errorR(Error(FSComp.SR.optValueMarkedInlineButIncomplete(v.DisplayName), v.Range))
2107+
20842108
and CheckBinding cenv env alwaysCheckNoReraise ctxt (TBind(v, bindRhs, _) as bind) : Limit =
20852109
let vref = mkLocalValRef v
20862110
let g = cenv.g
@@ -2113,6 +2137,8 @@ and CheckBinding cenv env alwaysCheckNoReraise ctxt (TBind(v, bindRhs, _) as bin
21132137
let access = AdjustAccess (IsHiddenVal env.sigToImplRemapInfo v) (fun () -> v.DeclaringEntity.CompilationPath) v.Accessibility
21142138
CheckTypeForAccess cenv env (fun () -> NicePrint.stringOfQualifiedValOrMember cenv.denv cenv.infoReader vref) access v.Range v.Type
21152139

2140+
CheckInlineValueIsSufficientlyAccessible cenv env v bindRhs
2141+
21162142
if cenv.reportErrors then
21172143

21182144
// Check top-level let-bound values
@@ -2781,7 +2807,20 @@ let CheckImplFileContents cenv env implFileTy implFileContents =
27812807
UpdatePrettyTyparNames.updateModuleOrNamespaceType implFileTy
27822808
CheckDefnInModule cenv env implFileContents
27832809

2810+
let rec private collectInlineBindingBodies (acc: Dictionary<Stamp, Expr>) mdef =
2811+
match mdef with
2812+
| TMDefRec(bindings = mbinds) ->
2813+
for mbind in mbinds do
2814+
match mbind with
2815+
| ModuleOrNamespaceBinding.Binding (TBind(v, e, _)) -> if v.ShouldInline then acc[v.Stamp] <- e
2816+
| ModuleOrNamespaceBinding.Module(_, def) -> collectInlineBindingBodies acc def
2817+
| TMDefLet(TBind(v, e, _), _) -> if v.ShouldInline then acc[v.Stamp] <- e
2818+
| TMDefDo _ | TMDefOpens _ -> ()
2819+
| TMDefs defs -> for def in defs do collectInlineBindingBodies acc def
2820+
27842821
let CheckImplFile (g, amap, reportErrors, infoReader, internalsVisibleToPaths, viewCcu, tcValF, denv, implFileTy, implFileContents, extraAttribs, isLastCompiland: bool*bool, isInternalTestSpanStackReferring) =
2822+
let inlineBindingBodies = Dictionary<Stamp, Expr>(HashIdentity.Structural)
2823+
collectInlineBindingBodies inlineBindingBodies implFileContents
27852824
let cenv =
27862825
{ g = g
27872826
reportErrors = reportErrors
@@ -2799,7 +2838,8 @@ let CheckImplFile (g, amap, reportErrors, infoReader, internalsVisibleToPaths, v
27992838
isLastCompiland = isLastCompiland
28002839
isInternalTestSpanStackReferring = isInternalTestSpanStackReferring
28012840
tcVal = tcValF
2802-
entryPointGiven = false}
2841+
entryPointGiven = false
2842+
inlineBindingBodies = inlineBindingBodies }
28032843

28042844
// Certain type equality checks go faster if these TyconRefs are pre-resolved.
28052845
// This is because pre-resolving allows tycon equality to be determined by pointer equality on the entities.

src/Compiler/CodeGen/IlxGen.fs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ type IlxGenOptions =
269269

270270
/// When set to true, the IlxGen will delay generation of method bodies and generated them later in parallel (parallelized across files)
271271
parallelIlxGenEnabled: bool
272+
273+
alwaysInline: bool
272274
}
273275

274276
/// Compilation environment for compiling a fragment of an assembly
@@ -5840,8 +5842,13 @@ and GenTraitCall (cenv: cenv) cgbuf eenv (traitInfo: TraitConstraintInfo, argExp
58405842

58415843
| None ->
58425844

5843-
// If witnesses are available, we should now always find trait witnesses in scope
5844-
assert not generateWitnesses
5845+
// When alwaysInline is true, all trait calls should be resolved via witnesses in scope.
5846+
// When alwaysInline is false, inline functions are kept as calls rather than inlined.
5847+
// Their witness arguments may contain TraitCall operations for constraints that were resolved
5848+
// without a witness (e.g., when the constraint is satisfied by a known concrete type).
5849+
// In such cases, generateWitnesses can be true (because other witnesses are in scope) but
5850+
// the specific trait's witness is not found. Fall through to the constraint solver to resolve it.
5851+
assert (not generateWitnesses || not cenv.options.alwaysInline)
58455852

58465853
let exprOpt =
58475854
CommitOperationResult(ConstraintSolver.CodegenWitnessExprForTraitConstraint cenv.tcVal g cenv.amap m traitInfo argExprs)
@@ -7290,8 +7297,19 @@ and GetIlxClosureFreeVars cenv m (thisVars: ValRef list) boxity eenv takenNames
72907297
let cloName =
72917298
// Ensure that we have an g.CompilerGlobalState
72927299
assert (g.CompilerGlobalState |> Option.isSome)
7300+
// The closure name counter is keyed by (basicName, fileIndex). When an expression is copied
7301+
// from another file (e.g. specializing an inline function body across files), its ranges
7302+
// still point at the original file, so its closures fall into a different counter bucket
7303+
// than closures minted for the current file. Since all these closures live under the same
7304+
// enclosing type, that can produce two closures with the same final name. Bucket the counter
7305+
// by the enclosing type's file while keeping expr.Range's StartLine for the displayed name.
7306+
let nameRange =
7307+
if expr.Range.FileIndex = eenv.cloc.Range.FileIndex then
7308+
expr.Range
7309+
else
7310+
Range.mkFileIndexRange eenv.cloc.Range.FileIndex expr.Range.Start expr.Range.End
72937311

7294-
g.CompilerGlobalState.Value.StableNameGenerator.GetUniqueCompilerGeneratedName(basenameSafeForUseAsTypename, expr.Range, uniq)
7312+
g.CompilerGlobalState.Value.StableNameGenerator.GetUniqueCompilerGeneratedName(basenameSafeForUseAsTypename, nameRange, uniq)
72957313

72967314
let ilCloTypeRef = NestedTypeRefForCompLoc eenv.cloc cloName
72977315

@@ -7349,6 +7367,21 @@ and GetIlxClosureFreeVars cenv m (thisVars: ValRef list) boxity eenv takenNames
73497367

73507368
let cloFreeTyvars = cloFreeTyvars.FreeTypars |> Zset.elements
73517369

7370+
// When generating witnesses, witness types may reference type variables that appear
7371+
// only in SRTP constraints of the captured type variables (e.g. 'b in 'a : (member M: unit -> 'b)).
7372+
// Include those so they are available when generating witness field types.
7373+
let cloFreeTyvars =
7374+
if ComputeGenerateWitnesses g eenv then
7375+
let extra =
7376+
GetTraitWitnessInfosOfTypars g 0 cloFreeTyvars
7377+
|> List.collect (fun w ->
7378+
(freeInType CollectTyparsNoCaching (GenWitnessTy g w)).FreeTypars
7379+
|> Zset.elements)
7380+
7381+
(cloFreeTyvars @ extra) |> List.distinctBy (fun tp -> tp.Stamp)
7382+
else
7383+
cloFreeTyvars
7384+
73527385
let eenvinner = eenv |> EnvForTypars cloFreeTyvars
73537386

73547387
let ilCloTyInner =

src/Compiler/CodeGen/IlxGen.fsi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ type internal IlxGenOptions =
6161

6262
/// When set to true, the IlxGen will delay generation of method bodies and generate them later in parallel (parallelized across files)
6363
parallelIlxGenEnabled: bool
64+
65+
/// Indicates if inline functions are being inlined or emitted as calls
66+
alwaysInline: bool
6467
}
6568

6669
/// The results of the ILX compilation of one fragment of an assembly

src/Compiler/Driver/CompilerConfig.fs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,8 @@ type TcConfigBuilder =
600600

601601
mutable strictIndentation: bool option
602602

603+
mutable alwaysInline: bool option
604+
603605
mutable exename: string option
604606

605607
// If true - the compiler will copy FSharp.Core.dll along the produced binaries
@@ -853,6 +855,7 @@ type TcConfigBuilder =
853855
dumpSignatureData = false
854856
realsig = false
855857
strictIndentation = None
858+
alwaysInline = None
856859
compilationMode = TcGlobals.CompilationMode.Unset
857860
}
858861

@@ -1253,6 +1256,14 @@ type TcConfig private (data: TcConfigBuilder, validate: bool) =
12531256
member _.fsiMultiAssemblyEmit = data.fsiMultiAssemblyEmit
12541257
member _.FxResolver = data.FxResolver
12551258
member _.strictIndentation = data.strictIndentation
1259+
1260+
member _.alwaysInline =
1261+
data.alwaysInline
1262+
|> Option.defaultValue (
1263+
data.optSettings.LocalOptimizationsEnabled
1264+
|| data.extraOptimizationIterations > 0
1265+
)
1266+
12561267
member _.primaryAssembly = data.primaryAssembly
12571268
member _.noFeedback = data.noFeedback
12581269
member _.stackReserveSize = data.stackReserveSize

src/Compiler/Driver/CompilerConfig.fsi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,8 @@ type TcConfigBuilder =
472472

473473
mutable strictIndentation: bool option
474474

475+
mutable alwaysInline: bool option
476+
475477
mutable exename: string option
476478

477479
mutable copyFSharpCore: CopyFSharpCoreFlag
@@ -814,6 +816,8 @@ type TcConfig =
814816

815817
member strictIndentation: bool option
816818

819+
member alwaysInline: bool
820+
817821
member GetTargetFrameworkDirectories: unit -> string list
818822

819823
/// Get the loaded sources that exist and issue a warning for the ones that don't

src/Compiler/Driver/CompilerOptions.fs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,14 @@ let languageFlags tcConfigB =
12071207
None,
12081208
Some(FSComp.SR.optsStrictIndentation (formatOptionSwitch (Option.defaultValue false tcConfigB.strictIndentation)))
12091209
)
1210+
1211+
CompilerOption(
1212+
"always-inline",
1213+
tagNone,
1214+
OptionSwitch(fun switch -> tcConfigB.alwaysInline <- Some(switch = OptionSwitch.On)),
1215+
None,
1216+
Some(FSComp.SR.optsAlwaysInline ())
1217+
)
12101218
]
12111219

12121220
// OptionBlock: Advanced user options

src/Compiler/Driver/OptimizeInputs.fs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ let ApplyAllOptimizations
327327
// Only do abstractBigTargets in the first phase, and only when TLR is on.
328328
abstractBigTargets = tcConfig.doTLR
329329
reportingPhase = true
330+
alwaysInline = tcConfig.alwaysInline
330331
}
331332

332333
// Only do these two steps in the first phase.
@@ -583,6 +584,7 @@ let GenerateIlxCode
583584
isInteractiveItExpr = isInteractiveItExpr
584585
alwaysCallVirt = tcConfig.alwaysCallVirt
585586
parallelIlxGenEnabled = tcConfig.parallelIlxGen
587+
alwaysInline = tcConfig.alwaysInline
586588
}
587589

588590
ilxGenerator.GenerateCode(ilxGenOpts, optimizedImpls, topAttrs.assemblyAttrs, topAttrs.netModuleAttrs)

src/Compiler/FSComp.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,6 +1561,7 @@ optsSetLangVersion,"Specify language version such as 'latest' or 'preview'."
15611561
optsDisableLanguageFeature,"Disable a specific language feature by name."
15621562
optsSupportedLangVersions,"Supported language versions:"
15631563
optsStrictIndentation,"Override indentation rules implied by the language version (%s by default)"
1564+
optsAlwaysInline,"Always inline 'inline' functions"
15641565
nativeResourceFormatError,"Stream does not begin with a null resource and is not in '.RES' format."
15651566
nativeResourceHeaderMalformed,"Resource header beginning at offset %s is malformed."
15661567
formatDashItem," - %s"

0 commit comments

Comments
 (0)