Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f6cad50
Add Roslyn-format EnC CustomDebugInformation codec and portable PDB m…
NatElkins Jul 1, 2026
0d02145
Use ProcessStartInfo.Arguments for net472 compatibility
NatElkins Jul 1, 2026
66a1412
Make the EnC CDI test module public for xunit v3 discovery
NatElkins Jul 2, 2026
c8639f3
Extract stable synthesized-name replay layer
NatElkins Jul 2, 2026
d6319c1
Add ECMA-335 EnC metadata delta writer
NatElkins Jul 1, 2026
d505ab9
Fix generated-name scope test in stable names slice
NatElkins Jul 8, 2026
7aaacc5
Fix EnC CDI cross-validation test process launch on Desktop and bare …
NatElkins Jul 3, 2026
b0a487f
Add typed-tree diff slice for hot reload
NatElkins Jul 2, 2026
c78b2f6
Remove extra EOF blank line in delta metadata tests
NatElkins Jul 8, 2026
cd1339b
Merge branch 'refresh/20260717/hotreload-stable-names' into refresh/2…
NatElkins Jul 17, 2026
c23986b
Add hot reload baseline reader state
NatElkins Jul 3, 2026
11e8fa0
Fix synthesized-name snapshot assertions to use the array equality ov…
NatElkins Jul 3, 2026
2facaf2
Merge branch 'refresh/20260717/hotreload-typedtree-diff' into refresh…
NatElkins Jul 17, 2026
ba5bce9
Merge branch 'refresh/20260717/enc-metadata-delta-writer' into refres…
NatElkins Jul 17, 2026
ac6a1ba
Extract hot reload delta emitter
NatElkins Jul 3, 2026
b228654
Align the signature-change diff test with the evolved differ classifi…
NatElkins Jul 3, 2026
5fdf705
Enforce cumulative metadata delta invariants
NatElkins Jul 17, 2026
c584660
Validate hot reload generated names before classification
NatElkins Jul 17, 2026
9a9211e
Validate hot reload baseline metadata inputs
NatElkins Jul 17, 2026
be6df7b
Preserve reviewed lower-slice invariants in delta emitter
NatElkins Jul 17, 2026
8932904
Unify delta emission on the shared metadata writer
NatElkins Jul 17, 2026
d40f0bc
Fail closed on unresolved accessor metadata
NatElkins Jul 17, 2026
11b50cc
Extract hot reload session slice
NatElkins Jul 3, 2026
36ef5ba
Fix CI compile breaks and extraction debris in the session slice tests
NatElkins Jul 3, 2026
330761c
Fix net472 and CI-image portability breaks in session slice tests
NatElkins Jul 3, 2026
a886637
Drop the netcore-only Contains overload from delta builder tests
NatElkins Jul 3, 2026
9bb2547
Gate the Roslyn baseline comparison tests to .NET Core
NatElkins Jul 3, 2026
6420b62
Normalize fixture line endings before update-source splicing
NatElkins Jul 3, 2026
c67ec56
Update ilxgen name-path architecture guard
NatElkins Jul 8, 2026
a4b8616
Remove duplicate delta metadata test copies
NatElkins Jul 8, 2026
4a1efa4
Remove duplicate compiler global reset member
NatElkins Jul 8, 2026
7216294
Remove duplicate compiler global reset signature
NatElkins Jul 8, 2026
2b461a8
Repair session slice after metadata writer consolidation
NatElkins Jul 17, 2026
9bf2891
Make hot reload session updates transactional
NatElkins Jul 17, 2026
1f7fa5d
Extract the flag-gated in-process compile perf slice
NatElkins Jul 3, 2026
8554882
Apply post-main hot reload perf cleanup
NatElkins Jul 7, 2026
eb1e86d
Remove trailing EOF whitespace in hot reload tests
NatElkins Jul 8, 2026
9c2d4b6
Fix synthesized type alias reuse in in-process hot reload
NatElkins Jul 10, 2026
464ee2a
Preserve hot reload slice invariants in in-process compilation
NatElkins Jul 17, 2026
8ea9e6b
Harden in-process hot reload compilation
NatElkins Jul 17, 2026
63db0b3
Verify overlapping hot reload emits serialize
NatElkins Jul 17, 2026
00f11e6
Minimize in-process compiler surface drift
NatElkins Jul 17, 2026
408de0c
Report hot reload update capabilities
NatElkins Jul 17, 2026
5fa9d32
Expose required hot reload capabilities
NatElkins Jul 17, 2026
412edf5
Document F# hot reload rude-edit diagnostics
NatElkins Jul 17, 2026
c7aed88
Format delta metadata writer sources
NatElkins Jul 17, 2026
7e8cf28
Format in-process hot reload sources
NatElkins Jul 17, 2026
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
5 changes: 5 additions & 0 deletions FSharp.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
<BuildType Solution="Proto|*" Project="Release" />
</Project>
</Folder>
<Folder Name="/Tests/HotReloadDemo/">
<Project Path="tests/projects/HotReloadDemo/HotReloadDemoApp/HotReloadDemoApp.fsproj">
<BuildType Solution="Proto|*" Project="Debug" />
</Project>
</Folder>
<Folder Name="/Tests/FSharp.Compiler.Service.Tests support/">
<Project Path="tests/service/data/CSharp_Analysis/CSharp_Analysis.csproj">
<BuildType Solution="Proto|*" Project="Debug" />
Expand Down
27 changes: 27 additions & 0 deletions docs/hot-reload-rude-edits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# F# hot reload rude-edit diagnostics

F# hot reload reports an `FSHRDL` diagnostic when an edit cannot be applied safely to the running process. The application is rebuilt and restarted instead of applying a delta that could leave it in an invalid state.

The diagnostic message identifies the affected declaration and the reason for the restart. These codes are owned by the F# compiler and are separate from Roslyn's `ENC` diagnostic namespace.

| Code | Meaning | What to do |
| --- | --- | --- |
| `FSHRDL001` | A member signature changed. | Undo the signature change to apply in place, or allow the rebuild and restart. |
| `FSHRDL002` | An `inline` annotation changed. | Allow the rebuild and restart. |
| `FSHRDL003` | A type representation or layout changed. | Allow the rebuild and restart. |
| `FSHRDL004` | A declaration was added in a shape the runtime cannot add. | Allow the rebuild and restart. |
| `FSHRDL005` | A declaration was removed. | Allow the rebuild and restart. |
| `FSHRDL006` | A virtual, abstract, or override member was added. | Allow the rebuild and restart. |
| `FSHRDL007` | A constructor was added. | Allow the rebuild and restart. |
| `FSHRDL008` | A user-defined operator was added. | Allow the rebuild and restart. |
| `FSHRDL009` | An explicit interface implementation was added. | Allow the rebuild and restart. |
| `FSHRDL010` | A member was added to an interface. | Allow the rebuild and restart. |
| `FSHRDL011` | A field was added in a shape the runtime cannot add. | Allow the rebuild and restart. |
| `FSHRDL012` | A lambda's lowered shape changed incompatibly. | Allow the rebuild and restart. |
| `FSHRDL013` | A state machine's resumable or hoisted layout changed incompatibly. | Keep the existing resume-point and captured-value layout, or allow the rebuild and restart. |
| `FSHRDL014` | A query expression's lowered shape changed incompatibly. | Allow the rebuild and restart. |
| `FSHRDL015` | A synthesized compiler declaration changed incompatibly. | Allow the rebuild and restart. |
| `FSHRDL016` | The runtime did not advertise a capability required by the edit. | Update the runtime if a newer version supports the capability, or allow the rebuild and restart. |
| `FSHRDL099` | The edit is unsupported for another fail-closed reason. | Follow the detailed message and allow the rebuild and restart. |

These diagnostics are intentionally fail closed. If the compiler cannot prove that an edit is safe, it requests a restart and leaves the running application unchanged.
12 changes: 10 additions & 2 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
* Fix internal error (FS0193) when calling an indexed property setter with a named argument that matches an indexer parameter. ([Issue #16034](https://github.com/dotnet/fsharp/issues/16034), [PR #19851](https://github.com/dotnet/fsharp/pull/19851))
* Fix missing FS1182 ("unused binding") warning for unused `let` function bindings inside class types. ([Issue #13849](https://github.com/dotnet/fsharp/issues/13849), [PR #19805](https://github.com/dotnet/fsharp/pull/19805))
* Fix internal compiler error FS1110 in `task { let! }` (and other computation expressions) when a generic IL extension method whose `this`-parameter is a method-level type variable is in scope (e.g. `open ReactiveUI`). Regression from PR #19536. ([Issue #19936](https://github.com/dotnet/fsharp/issues/19936))
* Fix inner mutually-recursive `let rec ... and ...` functions under `--realsig+` not being lifted to top-level static methods (TLR), causing `FSharpFunc` closure allocations and loss of `tail.` opcodes the large struct-mutual-recursion perf regression reported in [Issue #17607](https://github.com/dotnet/fsharp/issues/17607). ([PR #19882](https://github.com/dotnet/fsharp/pull/19882))
* Fix inner mutually-recursive `let rec ... and ...` functions under `--realsig+` not being lifted to top-level static methods (TLR), causing `FSharpFunc` closure allocations and loss of `tail.` opcodes - the large struct-mutual-recursion perf regression reported in [Issue #17607](https://github.com/dotnet/fsharp/issues/17607). ([PR #19882](https://github.com/dotnet/fsharp/pull/19882))
* Fix `TypeLoadException` ("Specialize tried to implicitly override a method with weaker type parameter constraints") and the related CLR crash with constrained inline calls by stripping constraints from closure-class typars in `EraseClosures.convIlxClosureDef`. ([Issue #14492](https://github.com/dotnet/fsharp/issues/14492), [Issue #19075](https://github.com/dotnet/fsharp/issues/19075), [PR #19882](https://github.com/dotnet/fsharp/pull/19882))
* Fix `FieldAccessException` at runtime when the optimizer relocates a read of a `protected` (family) base-class field into a method outside the field's family (e.g. a trivial member inlined into module/startup code under `--optimize+`). Protected (family) IL field access is no longer hoisted out of its declaring family by inlining or method-splitting. ([Issue #19963](https://github.com/dotnet/fsharp/issues/19963), [PR #19964](https://github.com/dotnet/fsharp/pull/19964))

Expand Down Expand Up @@ -128,6 +128,10 @@

### Added

* Added internal synthesized-name replay infrastructure for compiler-generated names, preserving normal compilation output while enabling future hot reload name stability work.
* Add an internal typed-tree diff utility for future F# hot reload edit classification. It is not called by normal compilation. ([PR #19941](https://github.com/dotnet/fsharp/pull/19941))
* Added internal F# hot reload delta emitter and symbol matcher infrastructure with direct emitter test coverage.
* Added an experimental, internal, flag-gated in-process compile path for hot reload sessions. `FSHARP_HOTRELOAD_INPROCESS_COMPILE` refreshes the output assembly and PDB from the latest checked project before delta emission, while `FSHARP_HOTRELOAD_INCREMENTAL_EMIT` enables a nested per-file optimized-tree cache.
* Added `FSharpMemberOrFunctionOrValue.IsPropertyAccessor` convenience property that returns true for compiler-generated property accessors (`get_X` / `set_X`). ([Issue #18157](https://github.com/dotnet/fsharp/issues/18157), [PR #19883](https://github.com/dotnet/fsharp/pull/19883))
* Added warning FS3884 when a function or delegate value is used as an interpolated string argument. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))
* Symbols: add ObsoleteDiagnosticInfo ([PR #19359](https://github.com/dotnet/fsharp/pull/19359))
Expand All @@ -140,6 +144,10 @@
* Checker: recover on checking language version ([PR ##19970](https://github.com/dotnet/fsharp/pull/19970))
* Implied argument names for function-to-delegate coercions now fall back to the delegate's `Invoke` parameter names when the function has no recoverable names (e.g. a partial application like `System.Func<int, int>((+) 1)`), instead of synthetic `delegateArg0`, `delegateArg1`, … names. ([PR #20001](https://github.com/dotnet/fsharp/pull/20001))
* Add internal `ResetCompilerGeneratedNameState` to `CompilerGlobalState` name generators so warm-checker re-compilation can produce fresh-process-identical generated names. ([PR #20017](https://github.com/dotnet/fsharp/pull/20017))
* Add Roslyn-format EnC CustomDebugInformation codec and portable PDB method CDI emission support to AbstractIL. ([PR #20018](https://github.com/dotnet/fsharp/pull/20018))
* Add internal ECMA-335 Edit-and-Continue metadata delta writer to AbstractIL. ([PR #20019](https://github.com/dotnet/fsharp/pull/20019))
* Add internal `ResetCompilerGeneratedNameState` to `CompilerGlobalState` name generators so warm-checker re-compilation can produce fresh-process-identical generated names. ([PR #20017](https://github.com/dotnet/fsharp/pull/20017))
* Add experimental hot reload support: `--test:HotReloadDeltas` baseline capture in fsc, EnC metadata/IL/PDB delta emission, rude-edit detection, and an `FSharpChecker` session API (`CreateHotReloadSession`, with `AddProject`/`EmitDelta`/`Commit`/`Discard`). Off by default; flag-off compilation is unchanged. ([Issue #11636](https://github.com/dotnet/fsharp/issues/11636), [PR #19941](https://github.com/dotnet/fsharp/pull/19941))

### Improved

Expand All @@ -151,4 +159,4 @@
* 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))

### Breaking Changes
* Optimizer: don't inline named functions in debug builds ([PR #19548](https://github.com/dotnet/fsharp/pull/19548)
* Optimizer: don't inline named functions in debug builds ([PR #19548](https://github.com/dotnet/fsharp/pull/19548)
1 change: 1 addition & 0 deletions release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ These release notes track our current efforts to document changes to the F# proj

### FSharp Compiler Service (main)

* Added internal hot reload baseline reading for recorded EnC state and synthesized-name snapshot PDB data.
* In FSharpParsingOptions, rename ConditionalCompilationDefines --> ConditionalDefines
* Some syntax tree nodes have changed, e.g. introduction of SyntaxTree trivia
* Resolved expressions (FSharpExpr) now reveal debug points, you must match them explicitly using `DebugPoint(dp, expr)`
Expand Down
186 changes: 186 additions & 0 deletions src/Compiler/AbstractIL/DeltaIndexSizing.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

/// Computes coded index sizing for delta metadata emission.
///
/// This module determines whether various metadata indices require 2 or 4 bytes
/// based on row counts in the metadata tables. This is per ECMA-335 II.24.2.6.
///
/// Uses TableNames from BinaryConstants.fs for ECMA-335 metadata table indices,
/// following the same pattern as the baseline IL writer (ilwrite.fs).
module internal FSharp.Compiler.AbstractIL.DeltaIndexSizing

open FSharp.Compiler.AbstractIL.BinaryConstants
open FSharp.Compiler.AbstractIL.ILDeltaHandles
open FSharp.Compiler.AbstractIL.DeltaMetadataEncoding

type MetadataHeapSizes = FSharp.Compiler.AbstractIL.ILBinaryWriter.MetadataHeapSizes

/// Holds computed "bigness" flags for all coded index types.
/// When true, the index requires 4 bytes; when false, 2 bytes suffice.
type CodedIndexSizes =
{
StringsBig: bool
GuidsBig: bool
BlobsBig: bool
SimpleIndexBig: bool[]
TypeDefOrRefBig: bool
TypeOrMethodDefBig: bool
HasConstantBig: bool
HasCustomAttributeBig: bool
HasFieldMarshalBig: bool
HasDeclSecurityBig: bool
MemberRefParentBig: bool
HasSemanticsBig: bool
MethodDefOrRefBig: bool
MemberForwardedBig: bool
ImplementationBig: bool
CustomAttributeTypeBig: bool
ResolutionScopeBig: bool
}

let private tableSize (tableRowCounts: int[]) (table: int) = tableRowCounts.[table]

let private totalRowCount (tableRowCounts: int[]) (externalRowCounts: int[]) (table: int) =
let index = table

let external =
if externalRowCounts.Length = tableRowCounts.Length then
externalRowCounts.[index]
else
0

tableRowCounts.[index] + external

let private referenceExceedsLimit (tableRowCounts: int[]) (externalRowCounts: int[]) (maxValueExclusive: int) (tables: int[]) =
tables
|> Array.exists (fun table -> totalRowCount tableRowCounts externalRowCounts table >= maxValueExclusive)

/// Determines if a coded index requires 4 bytes (big) or 2 bytes (small).
/// For EnC deltas (uncompressed), all indices are 4 bytes.
/// For compressed metadata, size depends on whether any referenced table
/// has enough rows to overflow the available bits after the tag.
let private codedBigness (tagBits: int) (tableRowCounts: int[]) (externalRowCounts: int[]) (isCompressed: bool) (tables: int[]) =
if not isCompressed then
// EnC deltas always use 4-byte indices
true
else
let limit = pown 2 (16 - tagBits)
referenceExceedsLimit tableRowCounts externalRowCounts limit tables

let private isSimpleIndexBig (tableRowCounts: int[]) (externalRowCounts: int[]) (isCompressed: bool) (tableIndex: int) =
if not isCompressed then
true
else
let local =
if tableIndex < tableRowCounts.Length then
tableRowCounts.[tableIndex]
else
0

let external =
if tableIndex < externalRowCounts.Length then
externalRowCounts.[tableIndex]
else
0

local + external >= 0x10000

/// Compute coded index sizes for all index types.
/// This determines the byte width of each reference type in the metadata tables.
let compute (tableRowCounts: int[]) (externalRowCounts: int[]) (heapSizes: MetadataHeapSizes) (isEncDelta: bool) : CodedIndexSizes =

let isCompressed = not isEncDelta

// Heap indices: 4 bytes if uncompressed or heap >= 64KB
let stringsBig = (not isCompressed) || heapSizes.StringHeapSize >= 0x10000
let blobsBig = (not isCompressed) || heapSizes.BlobHeapSize >= 0x10000
let guidsBig = (not isCompressed) || heapSizes.GuidHeapSize >= 0x10000

// Simple table indices
let simpleIndexBig =
Array.init DeltaTokens.TableCount (fun i -> isSimpleIndexBig tableRowCounts externalRowCounts isCompressed i)

// Helper to compute coded index bigness for a set of tables
let coded tag tables =
codedBigness tag tableRowCounts externalRowCounts isCompressed tables

// -------------------------------------------------------------------------
// Coded Index Definitions (per ECMA-335 II.24.2.6)
// -------------------------------------------------------------------------
// Each coded index combines a tag (to identify which table) with a row index.
// The tag uses the low N bits; the row index uses the remaining bits.
// If any table in the coded index exceeds (2^(16-N) - 1) rows, we need 4 bytes.

// TypeDefOrRef: TypeDef(0), TypeRef(1), TypeSpec(2) - 2-bit tag
let typeDefOrRefBig =
coded CodedIndices.TypeDefOrRef.TagBits CodedIndices.TypeDefOrRef.Tables

// TypeOrMethodDef: TypeDef(0), MethodDef(1) - 1-bit tag
let typeOrMethodDefBig =
coded CodedIndices.TypeOrMethodDef.TagBits CodedIndices.TypeOrMethodDef.Tables

// HasConstant: Field(0), Param(1), Property(2) - 2-bit tag
let hasConstantBig =
coded CodedIndices.HasConstant.TagBits CodedIndices.HasConstant.Tables

// HasCustomAttribute: 22 possible parent types - 5-bit tag
// This is the largest coded index, covering most metadata entities
let hasCustomAttributeBig =
coded CodedIndices.HasCustomAttribute.TagBits CodedIndices.HasCustomAttribute.Tables

// HasFieldMarshal: Field(0), Param(1) - 1-bit tag
let hasFieldMarshalBig =
coded CodedIndices.HasFieldMarshal.TagBits CodedIndices.HasFieldMarshal.Tables

// HasDeclSecurity: TypeDef(0), MethodDef(1), Assembly(2) - 2-bit tag
let hasDeclSecurityBig =
coded CodedIndices.HasDeclSecurity.TagBits CodedIndices.HasDeclSecurity.Tables

// MemberRefParent: TypeDef(0), TypeRef(1), ModuleRef(2), MethodDef(3), TypeSpec(4) - 3-bit tag
let memberRefParentBig =
coded CodedIndices.MemberRefParent.TagBits CodedIndices.MemberRefParent.Tables

// HasSemantics: Event(0), Property(1) - 1-bit tag
let hasSemanticsBig =
coded CodedIndices.HasSemantics.TagBits CodedIndices.HasSemantics.Tables

// MethodDefOrRef: MethodDef(0), MemberRef(1) - 1-bit tag
let methodDefOrRefBig =
coded CodedIndices.MethodDefOrRef.TagBits CodedIndices.MethodDefOrRef.Tables

// MemberForwarded: Field(0), MethodDef(1) - 1-bit tag
let memberForwardedBig =
coded CodedIndices.MemberForwarded.TagBits CodedIndices.MemberForwarded.Tables

// Implementation: File(0), AssemblyRef(1), ExportedType(2) - 2-bit tag
let implementationBig =
coded CodedIndices.Implementation.TagBits CodedIndices.Implementation.Tables

// CustomAttributeType: MethodDef(2), MemberRef(3) - 3-bit tag
// Note: tags 0, 1, 4 are reserved/unused
let customAttributeTypeBig =
coded CodedIndices.CustomAttributeType.TagBits CodedIndices.CustomAttributeType.Tables

// ResolutionScope: Module(0), ModuleRef(1), AssemblyRef(2), TypeRef(3) - 2-bit tag
let resolutionScopeBig =
coded CodedIndices.ResolutionScope.TagBits CodedIndices.ResolutionScope.Tables

{
StringsBig = stringsBig
GuidsBig = guidsBig
BlobsBig = blobsBig
SimpleIndexBig = simpleIndexBig
TypeDefOrRefBig = typeDefOrRefBig
TypeOrMethodDefBig = typeOrMethodDefBig
HasConstantBig = hasConstantBig
HasCustomAttributeBig = hasCustomAttributeBig
HasFieldMarshalBig = hasFieldMarshalBig
HasDeclSecurityBig = hasDeclSecurityBig
MemberRefParentBig = memberRefParentBig
HasSemanticsBig = hasSemanticsBig
MethodDefOrRefBig = methodDefOrRefBig
MemberForwardedBig = memberForwardedBig
ImplementationBig = implementationBig
CustomAttributeTypeBig = customAttributeTypeBig
ResolutionScopeBig = resolutionScopeBig
}
Loading
Loading