Skip to content

Commit c833715

Browse files
authored
Merge branch 'main' into ber.a/defaultSeverity
2 parents 5c8ce34 + 89d7886 commit c833715

File tree

14 files changed

+550
-29
lines changed

14 files changed

+550
-29
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
---
2+
name: Compile perf investigator
3+
description: Specialized agent for investigating F# build performance issues using the local compiler, trace/dump/benchmark tools, and rigorous, resumable insight-centric documentation.
4+
---
5+
6+
# Compile perf investigator
7+
8+
These are **general investigation instructions** for this agent, a template for perf analysis of slow/problematic F# compilation and build, suitable for a variety of scenarios (repos, snippets, gists).
9+
10+
---
11+
12+
## PRINCIPLES OF OPERATION
13+
14+
- **Build insight, not just logs:** The ultimate goal is meaningful insights and verified hypotheses, not just raw data or trace files.
15+
- **Resumable workflow:** The agent must support investigation suspension/resumption: `TODO.md` is canonical for next steps.
16+
- **Iterative, hypothesis-driven:** Maintain and update a knowledge base of hypotheses (HYPOTHESIS.md) marking them as tested, confirmed, or denied to prevent repetition.
17+
- **Insight publication:** For every analysis, the primary artifact is INSIGHTS.md. This is the durable, published output which drives the investigation forward.
18+
- **Intermediate findings tracking:** Collect `HOT_PATHS.md` for all key observed code/activity paths and patterns before promoting to formal hypotheses or insights.
19+
- **Self-tooling:** When appropriate, the agent should generate and use .fsx (F# script) tools to parse, reduce, or extract patterns/insights from traces or build logs. These scripts are valuable artifacts.
20+
21+
---
22+
23+
## Absolute Requirements
24+
25+
- **Always use** a fresh, local F# compiler and FSharp.Core where possible. Log full paths and proof the correct compiler is used.
26+
- **Matrix over** Debug/Release and ParallelCompilation on/off.
27+
- **For each build and analysis step:**
28+
- Record all commands, timings, and results.
29+
- Log all intermediate results (e.g., hot call stacks, wallclock timings, error messages) to `HOT_PATHS.md` as they are found.
30+
- **For every hypothesis or question:**
31+
- State it in `HYPOTHESIS.md` and update with status (untested/confirmed/denied) and pointers to relevant runs/artifacts.
32+
- **Primary artifact must be** clear, actionable, human-written text in `INSIGHTS.md`—this should summarize what the agent has learned, not just what it ran.
33+
34+
---
35+
36+
## Procedure (Resumable by TODO.md)
37+
38+
### 1. Preparation
39+
- **Setup:** Clone/generate repo/snippet/etc.
40+
- **Clear old config:** Remove `global.json` unless needed.
41+
- **Prepare local compiler:** Use `PrepareRepoForRegressionTesting.fsx` and absolute env paths.
42+
43+
### 2. Experiment Matrix
44+
45+
For each:
46+
- ParallelCompilation: true, false
47+
- Configuration: Debug, Release
48+
49+
Record:
50+
- build command and tool invocation
51+
- timings, exit codes
52+
- trace and log extraction
53+
54+
### 3. Immediate Documentation (Always Do After Step)
55+
56+
- **TODO.md:** Append completed step and state next.
57+
- **HOT_PATHS.md:** Add all notable methods, stacks, resource spikes, or patterns seen.
58+
- **ANALYSIS.md:** For each run, summarize key metrics, patterns, and questions that arise from data.
59+
60+
### 4. Hypothesis Management
61+
62+
- **HYPOTHESIS.md:** Maintain numbered/dated hypotheses about causes, behaviors, or fixes.
63+
- Mark each as untested/confirmed/denied (and why/where).
64+
- Reference experimental runs or insights which test them.
65+
- Never re-run a denied or already confirmed hypothesis: record, cross-reference, and always consult HYPOTHESIS.md on starting or resuming work.
66+
67+
### 5. Insights as Product
68+
69+
- **INSIGHTS.md:** After every meaningful update or analysis, synthesize a summary sentence/paragraph here:
70+
- What was learned?
71+
- What does the evidence suggest or refute?
72+
- Are there new lines of investigation?
73+
- (Do not just paste traces—extract the “so what”.)
74+
- At the end of a session, **publish** key conclusions and unresolved questions in INSIGHTS.md.
75+
76+
### 6. Generate & Use .fsx Tools
77+
78+
- When repetitive or large analysis is required (e.g., extracting hot methods from .nettrace, filtering logs), generate F# scripts (`.fsx`) that automate this, and:
79+
- Save these as versioned artifacts.
80+
- Log usage, output, and location for regeneration and proof.
81+
- Use script outputs as input to HOT_PATHS.md or directly to INSIGHTS.md if discovery is significant.
82+
83+
### 7. Best Practices
84+
85+
- **If agent is interrupted:** On restart, consult `TODO.md`, `HYPOTHESIS.md`, and `INSIGHTS.md` to resume exactly where left off—never duplicate work and always cross-check hypotheses.
86+
- **If insight contradicts previous belief:** Update both `INSIGHTS.md` and point back to affected hypotheses.
87+
- **If a hypothesis is tested and denied,** record why and what observation/finding proves it false (with links/log references).
88+
- **If unable to explain a result with current hypotheses,** propose a new one and add it as untested.
89+
90+
---
91+
92+
## Documentation File Roles
93+
94+
| File | Purpose |
95+
|-----------------|-----------------------------------------------------------------------|
96+
| TODO.md | Backlog of next actions; always current, supports resuming work |
97+
| HOT_PATHS.md | Key stacks, methods, or code paths surfaced during analysis |
98+
| HYPOTHESIS.md | All generated hypotheses, status-tracked, cross-referenced |
99+
| INSIGHTS.md | Published, reasoned summaries—main knowledge product of the agent |
100+
| ANALYSIS.md | Per-experiment metrics, summary comments, supplemental data |
101+
| [tool].fsx | Any custom F# scripts generated/executed as part of the investigation |
102+
103+
---
104+
105+
## Tool/Artifact Use Policy
106+
107+
- **Never** treat a .nettrace, dump, or log as an end in itself—always extract, summarize, and relate to hypotheses and insights.
108+
- **Artifacts serve as reproducibility aids**, not terminal outputs. End users should rely on INSIGHTS.md and HYPOTHESIS.md for final conclusions.
109+
- **All generated tools (.fsx)** are themselves artifacts and must be logged and published for full transparency and reusability.
110+
111+
---
112+
113+
## Example Workflow
114+
115+
1. Start by reading/setting TODO.md (`what next?`)
116+
2. Run experiment cell; immediately:
117+
- Document run, logs, and .fsx tools (if any)
118+
- Summarize in ANALYSIS.md
119+
- Extract hot paths to HOT_PATHS.md
120+
3. Update hypothesis status in HYPOTHESIS.md
121+
4. Formulate/publish a new insight if progress is made
122+
5. Set new TODO in TODO.md, and finish session
123+
6. On any restart, agent must consult all MD files to avoid redundancy and repeat work only if hypothesis or scenario materially changes.
124+
125+
---
126+
127+
## Output Expectation
128+
129+
At the end of an investigation, the primary deliverables are:
130+
- A well-maintained TODO.md to allow handoff/resume
131+
- A fully updated HYPOTHESIS.md (including rejected ideas, with reasons)
132+
- HOT_PATHS.md with all relevant interim findings, promoted as needed
133+
- INSIGHTS.md containing all meaningful conclusions and next questions, written for humans
134+
- Any custom `.fsx` tools, with location, invocation, and sample output
135+
136+
---
137+
138+
## Recap
139+
140+
**This is an insight and hypothesis-driven agent for F# build perf analysis.
141+
Its mission is to extract and publish meaningful, durable understanding—not just build logs or raw data.
142+
The workflow is always resumable, transparent, and maximally reusable for community and future agents.**
143+
144+
---

UseLocalCompiler.Directory.Build.props

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
<FSharpPrefer64BitTools>True</FSharpPrefer64BitTools>
1919
</PropertyGroup>
2020

21+
<!--
22+
Use FSharpTargetsShim to redirect the SDK to use the locally built F# targets.
23+
This replaces all the individual UsingTask overrides since Microsoft.FSharp.NetSdk.targets
24+
imports Microsoft.FSharp.Targets which registers all the F# build tasks.
25+
See: https://github.com/dotnet/sdk/blob/main/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.Sdk.FSharpTargetsShim.targets
26+
-->
2127
<PropertyGroup Condition="'$(LoadLocalFSharpBuild)' == 'True'">
2228
<LocalFSharpBuildBinPath>$(LocalFSharpCompilerPath)/artifacts/bin/fsc/$(LocalFSharpCompilerConfiguration)/net10.0</LocalFSharpBuildBinPath>
2329
<FSharpBuildAssemblyFile>$(LocalFSharpBuildBinPath)/FSharp.Build.dll</FSharpBuildAssemblyFile>
@@ -27,9 +33,6 @@
2733
<FSharpOverridesTargetsShim>$(LocalFSharpBuildBinPath)/Microsoft.FSharp.Overrides.NetSdk.targets</FSharpOverridesTargetsShim>
2834
</PropertyGroup>
2935

30-
<UsingTask Condition="'$(LoadLocalFSharpBuild)' == 'True'" TaskName="FSharpEmbedResourceText" AssemblyFile="$(FSharpBuildAssemblyFile)" Override="true" />
31-
<UsingTask Condition="'$(LoadLocalFSharpBuild)' == 'True'" TaskName="FSharpEmbedResXSource" AssemblyFile="$(FSharpBuildAssemblyFile)" Override="true" />
32-
3336
<ItemGroup>
3437
<Reference Include="$(LocalFSharpCompilerPath)/artifacts/bin/FSharp.Core/$(LocalFSharpCompilerConfiguration)/netstandard2.0/FSharp.Core.dll" />
3538
</ItemGroup>

eng/Version.Details.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ This file should be imported by eng/Versions.props
2727
<MicrosoftCodeAnalysisFeaturesPackageVersion>5.0.0-2.25480.7</MicrosoftCodeAnalysisFeaturesPackageVersion>
2828
<MicrosoftVisualStudioLanguageServicesPackageVersion>5.0.0-2.25480.7</MicrosoftVisualStudioLanguageServicesPackageVersion>
2929
<!-- dotnet/arcade dependencies -->
30-
<MicrosoftDotNetArcadeSdkPackageVersion>11.0.0-beta.25601.2</MicrosoftDotNetArcadeSdkPackageVersion>
30+
<MicrosoftDotNetArcadeSdkPackageVersion>11.0.0-beta.25603.2</MicrosoftDotNetArcadeSdkPackageVersion>
3131
<!-- _git/dotnet-optimization dependencies -->
3232
<optimizationlinuxarm64MIBCRuntimePackageVersion>1.0.0-prerelease.25502.1</optimizationlinuxarm64MIBCRuntimePackageVersion>
3333
<optimizationlinuxx64MIBCRuntimePackageVersion>1.0.0-prerelease.25502.1</optimizationlinuxx64MIBCRuntimePackageVersion>

eng/Version.Details.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Dependencies>
3-
<Source Uri="https://github.com/dotnet/dotnet" Mapping="fsharp" Sha="cc7f6e84d8dae36ad9ea51a5112627b8235982cc" BarId="291900" />
3+
<Source Uri="https://github.com/dotnet/dotnet" Mapping="fsharp" Sha="29eefe27a350eb8b0bcbababa7863a0d1086295d" BarId="293166" />
44
<ProductDependencies>
55
<Dependency Name="Microsoft.Build" Version="18.1.0-preview-25515-01">
66
<Uri>https://github.com/dotnet/msbuild</Uri>
@@ -76,9 +76,9 @@
7676
</Dependency>
7777
</ProductDependencies>
7878
<ToolsetDependencies>
79-
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="11.0.0-beta.25601.2">
79+
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="11.0.0-beta.25603.2">
8080
<Uri>https://github.com/dotnet/arcade</Uri>
81-
<Sha>846929727a388f2b1fd3ccf21f67694f44b91b4d</Sha>
81+
<Sha>9851192f7f7a7ee352358cce2627160fd1f2a54e</Sha>
8282
</Dependency>
8383
<Dependency Name="optimization.windows_nt-x64.MIBC.Runtime" Version="1.0.0-prerelease.25502.1">
8484
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-optimization</Uri>

eng/common/core-templates/job/job.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ jobs:
7373
templateContext: ${{ parameters.templateContext }}
7474

7575
variables:
76+
- name: AllowPtrToDetectTestRunRetryFiles
77+
value: true
7678
- ${{ if ne(parameters.enableTelemetry, 'false') }}:
7779
- name: DOTNET_CLI_TELEMETRY_PROFILE
7880
value: '$(Build.Repository.Uri)'

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"perl": "5.38.2.2"
2323
},
2424
"msbuild-sdks": {
25-
"Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.25601.2",
25+
"Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.25603.2",
2626
"Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23255.2"
2727
}
2828
}

src/Compiler/Symbols/Exprs.fs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,14 +508,47 @@ module FSharpExprConvert =
508508

509509
and GetWitnessArgs cenv (env: ExprTranslationEnv) (vref: ValRef) m tps tyargs : FSharpExpr list =
510510
let g = cenv.g
511-
if g.langVersion.SupportsFeature(Features.LanguageFeature.WitnessPassing) && not env.suppressWitnesses then
512-
let witnessExprs =
511+
if g.langVersion.SupportsFeature(Features.LanguageFeature.WitnessPassing) && not env.suppressWitnesses then
512+
/// There are two *conditional* properties a typar can have: equality and comparison.
513+
/// A generic type having that constraint may be conditional on whether a specific type parameter to that generic has that
514+
/// constraint.
515+
/// This function returns `true` iff after unification, the type definition contains any conditional typars.
516+
///
517+
/// Note that these conditions are only marked on typars that actually appear in the code, *not* on phantom types.
518+
/// So `hasConditionalTypar` should tell us exactly when the type parameter is actually being used in the type's equality or
519+
/// comparison.
520+
let rec hasConditionalTypar ty =
521+
match stripTyEqns g ty with
522+
| TType_var (tp, _) -> tp.ComparisonConditionalOn || tp.EqualityConditionalOn
523+
| TType_app (_, tinst, _) -> tinst |> List.exists hasConditionalTypar
524+
| _ -> false
525+
526+
let witnessExprs =
513527
match ConstraintSolver.CodegenWitnessesForTyparInst cenv.tcValF g cenv.amap m tps tyargs with
514528
// There is a case where optimized code makes expressions that do a shift-left on the 'char'
515529
// type. There is no witness for this case. This is due to the code
516530
// let inline HashChar (x:char) = (# "or" (# "shl" x 16 : int #) x : int #)
517-
// in FSharp.Core.
531+
// in FSharp.Core.
518532
| ErrorResult _ when vref.LogicalName = "op_LeftShift" && List.isSingleton tyargs -> []
533+
// We don't need a witness either at compile time or runtime when there are conditional typars.
534+
// Attempting to call a comparison operation with the type causes a compile-time check that all the generic type args
535+
// support comparison (thanks to the ComparisonConditionalOn mechanism); the compile-time check doesn't need witnesses,
536+
// it's just pure constraint solving.
537+
// Nor do we need a witness for runtime logic: the compiler generates a `CompareTo` method (see
538+
// `MakeValsForCompareAugmentation`) which handles the comparison by dynamically type-testing, not going through a witness.
539+
//
540+
// So we don't need to generate a witness.
541+
//
542+
// In fact, we *can't* generate a witness, because the constraint on the type parameter is only conditional: a rigid type
543+
// parameter, defined without the `comparison` constraint, cannot have the constraint added to it later (that's what "rigid"
544+
// means). It would change the type signature of the type to add this constraint to the type parameter!
545+
//
546+
// This code path is only reached through the auto-generated comparison/equality code, which only calls single-constraint
547+
// intrinsics: there's exactly one constraint per type parameter in each of those two cases.
548+
// In theory, if a function had an autogenerated `'a : comparison and 'b : SomethingElse`, where the `SomethingElse` was
549+
// not comparison and failed for a different reason, we'd spuriously hide that failure here; but in fact the only code
550+
// paths which get here have no other constraints.
551+
| ErrorResult _ when List.exists hasConditionalTypar tyargs -> []
519552
| res -> CommitOperationResult res
520553
let env = { env with suppressWitnesses = true }
521554
witnessExprs |> List.map (fun arg ->

src/Compiler/TypedTree/TypedTreeBasics.fsi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ val tryShortcutSolvedUnitPar: canShortcut: bool -> r: Typar -> Measure
141141

142142
val stripUnitEqnsAux: canShortcut: bool -> unt: Measure -> Measure
143143

144+
/// Follows type variable solutions: when a type variable has been solved by unifying it with another type,
145+
/// replaces that type variable with its solution.
144146
val stripTyparEqnsAux: nullness0: Nullness -> canShortcut: bool -> ty: TType -> TType
145147

146148
val replaceNullnessOfTy: nullness: Nullness -> ty: TType -> TType

src/Compiler/TypedTree/TypedTreeOps.fsi

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -604,9 +604,41 @@ val reduceTyconRefMeasureableOrProvided: TcGlobals -> TyconRef -> TypeInst -> TT
604604

605605
val reduceTyconRefAbbrevMeasureable: TyconRef -> Measure
606606

607-
/// set bool to 'true' to allow shortcutting of type parameter equation chains during stripping
608-
val stripTyEqnsA: TcGlobals -> bool -> TType -> TType
609-
607+
/// <summary>
608+
/// Normalizes types.
609+
/// </summary>
610+
/// <remarks>
611+
/// Normalizes a type by:
612+
/// <list>
613+
/// <item>replacing type variables with their solutions found by unification</item>
614+
/// <item>expanding type abbreviations</item>
615+
/// </list>
616+
/// as well as a couple of special-case normalizations:
617+
/// <list>
618+
/// <item>identifying <c>int&lt;1&gt;</c> with <c>int</c> (for any measurable type)</item>
619+
/// <item>identifying <c>byref&lt;'T&gt;</c> with <c>byref&lt;'T, ByRefKinds.InOut&gt;</c></item>
620+
/// </list>
621+
/// </remarks>
622+
/// <param name="canShortcut">
623+
/// <c>true</c> to allow shortcutting of type parameter equation chains during stripping
624+
/// </param>
625+
val stripTyEqnsA: TcGlobals -> canShortcut: bool -> TType -> TType
626+
627+
/// <summary>
628+
/// Normalizes types.
629+
/// </summary>
630+
/// <remarks>
631+
/// Normalizes a type by:
632+
/// <list>
633+
/// <item>replacing type variables with their solutions found by unification</item>
634+
/// <item>expanding type abbreviations</item>
635+
/// </list>
636+
/// as well as a couple of special-case normalizations:
637+
/// <list>
638+
/// <item>identifying <c>int&lt;1&gt;</c> with <c>int</c> (for any measurable type)</item>
639+
/// <item>identifying <c>byref&lt;'T&gt;</c> with <c>byref&lt;'T, ByRefKinds.InOut&gt;</c></item>
640+
/// </list>
641+
/// </remarks>
610642
val stripTyEqns: TcGlobals -> TType -> TType
611643

612644
val stripTyEqnsAndMeasureEqns: TcGlobals -> TType -> TType
@@ -707,6 +739,8 @@ val tcrefOfAppTy: TcGlobals -> TType -> TyconRef
707739

708740
val tryTcrefOfAppTy: TcGlobals -> TType -> TyconRef voption
709741

742+
/// Returns ValueSome if this type is a type variable, even after abbreviations are expanded and
743+
/// variables have been solved through unification.
710744
val tryDestTyparTy: TcGlobals -> TType -> Typar voption
711745

712746
val tryDestFunTy: TcGlobals -> TType -> (TType * TType) voption

src/FSharp.Build/Microsoft.FSharp.NetSdk.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
176176
<GenerateILLinkSubstitutions
177177
AssemblyName="$(AssemblyName)"
178178
IntermediateOutputPath="$(IntermediateOutputPath)">
179-
<Output TaskParameter="GeneratedItems" ItemName="Embed" />
179+
<Output TaskParameter="GeneratedItems" ItemName="EmbeddedResource" />
180180
</GenerateILLinkSubstitutions>
181181
</Target>
182182

0 commit comments

Comments
 (0)