Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
172 commits
Select commit Hold shift + click to select a range
500b2fb
feat(hot-reload): add AbstractIL EnC infrastructure (delta handles, h…
claude Jun 10, 2026
d509485
feat(hot-reload): deterministic compiler-generated names with replaya…
claude Jun 10, 2026
6a64f31
feat(hot-reload): TypedTree semantic diff and definition map
claude Jun 10, 2026
d4fcc17
feat(hot-reload): baseline capture and ilwrite EnC integration
claude Jun 10, 2026
fc85849
feat(hot-reload): delta metadata writer stack
claude Jun 10, 2026
613b6f9
feat(hot-reload): IL delta emitter and session core
claude Jun 10, 2026
8bfd38a
feat(hot-reload): driver plumbing and --enable:hotreloaddeltas flag
claude Jun 10, 2026
98851c2
feat(hot-reload): FSharpChecker hot reload session API
claude Jun 10, 2026
b4a76e3
test(hot-reload): port HotReload test suites (service + component)
claude Jun 10, 2026
8b06c1b
chore(hot-reload): port verification scripts, demo project, docs, CI …
claude Jun 10, 2026
16aee92
feat(hot-reload): add typed EditAndContinueCapabilities model
NatElkins Jun 10, 2026
adedb7b
feat(hot-reload): gate edit classification on negotiated runtime capa…
NatElkins Jun 10, 2026
4c2d57d
feat(hot-reload): classify capability-gated module value and function…
NatElkins Jun 10, 2026
1f152cb
feat(hot-reload): add field rows to the delta metadata writer
NatElkins Jun 10, 2026
d2201b2
feat(hot-reload): emit added module values in deltas
NatElkins Jun 10, 2026
9ad3af5
test(hot-reload): add multi-generation runtime coverage for added mod…
NatElkins Jun 10, 2026
7508d4b
feat(hot-reload): structured lambda occurrence model and alignment in…
NatElkins Jun 10, 2026
486dfe3
feat(hot-reload): EnC method debug information blob encoder and decoder
NatElkins Jun 10, 2026
c25b349
feat(hot-reload): emit EnC CDI blobs for lambda-bearing methods under…
NatElkins Jun 10, 2026
9d83021
feat(hot-reload): read baseline EnC CDI blobs into the hot reload ses…
NatElkins Jun 10, 2026
6311ef0
feat(hot-reload): occurrence-keyed closure name allocation model
NatElkins Jun 11, 2026
3d61f87
test(hot-reload): closure identity preserved across delta generations
NatElkins Jun 11, 2026
ca32393
feat(hot-reload): record lambda root stamps and baseline closure names
NatElkins Jun 11, 2026
4edb624
feat(hot-reload): occurrence-keyed closure naming in delta compiles
NatElkins Jun 11, 2026
e7f7239
test(hot-reload): occurrence-keyed naming survives lambda set changes…
NatElkins Jun 11, 2026
09bb186
feat(hot-reload): emit new type definitions in deltas
NatElkins Jun 11, 2026
7a47581
feat(hot-reload): emit added lambdas as new closure types in deltas
NatElkins Jun 11, 2026
3e914f2
test(hot-reload): closure addition coverage
NatElkins Jun 11, 2026
04fb851
chore(hot-reload): allowlist ilwritepdb.fsi surface drift for the CDI…
NatElkins Jun 11, 2026
f24895b
feat(hot-reload): emit added instance fields in deltas
NatElkins Jun 11, 2026
3eff04f
feat(hot-reload): emit added properties and events in deltas
NatElkins Jun 11, 2026
d8807d9
fix(hot-reload): chain only appended rows into next-generation table …
NatElkins Jun 11, 2026
9788d82
test(hot-reload): member addition coverage consolidation
NatElkins Jun 11, 2026
bab0aab
feat(hot-reload): derive baseline closure names from occurrence identity
NatElkins Jun 11, 2026
d39cd7a
feat(hot-reload): reconstruct closure names from CDI for disk-started…
NatElkins Jun 11, 2026
781c974
test(hot-reload): cross-process closure addition coverage
NatElkins Jun 11, 2026
716e618
feat(hot-reload): update session capabilities without restarting
NatElkins Jun 11, 2026
86f017c
feat(hot-reload): emit TypeSpec rows in deltas
NatElkins Jun 11, 2026
6724885
fix(hot-reload): read updated method rows from fresh-compile coordina…
NatElkins Jun 11, 2026
cc7906c
feat(hot-reload): scope state-machine rude edits to resumable-code me…
NatElkins Jun 11, 2026
f93a6c9
feat(hot-reload): fail closed on state machine struct layout and clos…
NatElkins Jun 11, 2026
edd93c5
feat(hot-reload): persist state machine state maps in EnC CDI
NatElkins Jun 11, 2026
770e94d
test(hot-reload): state machine edit coverage
NatElkins Jun 11, 2026
3685214
feat(hot-reload): gate generic member edits on runtime generic capabi…
NatElkins Jun 11, 2026
334df57
test(hot-reload): cover generic method body updates applying at runtime
NatElkins Jun 11, 2026
55c7667
feat(hot-reload): emit GenericParam rows for added generic methods an…
NatElkins Jun 11, 2026
7751d74
test(hot-reload): generic edit coverage and docs
NatElkins Jun 11, 2026
637b9f2
feat(hot-reload): emit custom attribute rows for added members
NatElkins Jun 11, 2026
1f1c49c
feat(hot-reload): classify and emit custom attribute changes on exist…
NatElkins Jun 11, 2026
1ee23fa
feat(hot-reload): parameter metadata updates
NatElkins Jun 11, 2026
dc2ed49
feat(hot-reload): emit user-defined new type definitions
NatElkins Jun 11, 2026
8807920
feat(hot-reload): emit GenericParamConstraint rows
NatElkins Jun 11, 2026
704a853
test(hot-reload): attribute, parameter, and new-type coverage
NatElkins Jun 11, 2026
7136810
feat(hot-reload): active statement and sequence point update model
NatElkins Jun 11, 2026
8373c10
feat(hot-reload): compute sequence point updates for emitted deltas
NatElkins Jun 11, 2026
4f06694
feat(hot-reload): active statement remapping across edits
NatElkins Jun 11, 2026
414dd88
feat(hot-reload): feed on-disk portable PDBs into sequence point anal…
NatElkins Jun 11, 2026
5041e71
test(hot-reload): active statement and line update coverage
NatElkins Jun 11, 2026
8869a48
chore(hot-reload): refresh fsi drift hash for committed service.fsi
NatElkins Jun 11, 2026
29fcf55
fix(hot-reload): read accessor-edit property/event rows from fresh-co…
NatElkins Jun 11, 2026
5819038
feat(hot-reload): emit added modules in deltas
NatElkins Jun 11, 2026
c897019
feat(hot-reload): emit added enums in deltas
NatElkins Jun 11, 2026
c89a082
feat(hot-reload): emit added interfaces and delegates in deltas
NatElkins Jun 11, 2026
b2553d1
test(hot-reload): added type coverage consolidation
NatElkins Jun 11, 2026
614a5af
refactor(hot-reload): per-project session state inside an explicit se…
NatElkins Jun 11, 2026
a229cc4
feat(hot-reload): public FSharpHotReloadSession API
NatElkins Jun 11, 2026
fc1fe49
docs(hot-reload): architecture entity model
NatElkins Jun 11, 2026
da70443
feat(hot-reload): pin deterministic sequential codegen for baseline c…
NatElkins Jun 11, 2026
25edb3c
test(hot-reload): pin baseline determinism
NatElkins Jun 11, 2026
93f80ab
feat(hot-reload): scope in-process compiles to the emitting session
NatElkins Jun 11, 2026
23de578
refactor(hot-reload): migrate tests to the session object API
NatElkins Jun 11, 2026
9841d0d
refactor(hot-reload): remove the static session surface and ambient s…
NatElkins Jun 12, 2026
137b5a3
docs(hot-reload): add quickstart guide for trying the dotnet watch ex…
NatElkins Jun 12, 2026
434a328
docs(hot-reload): link the in-flight compiler caching work from the q…
NatElkins Jun 12, 2026
9f26a9f
docs(hot-reload): remove internal phase labels from design docs
NatElkins Jun 12, 2026
d2b152b
chore(hot-reload): remove internal phase labels from comments
NatElkins Jun 12, 2026
faf19bd
fix(hot-reload): call TypedImplementationFiles directly instead of vi…
NatElkins Jun 12, 2026
fb4132a
perf(hot-reload): build the ilwrite metadata snapshot only when a sin…
NatElkins Jun 12, 2026
95c4ed3
perf(hot-reload): collect state machine resumption points only when c…
NatElkins Jun 12, 2026
b6ac9b1
ci(hot-reload): gate the demo smoke test behind an explicit -testHotR…
NatElkins Jun 12, 2026
c0e61d5
perf(hot-reload): resolve the synthesized-name map slot once per Comp…
NatElkins Jun 12, 2026
956dc71
perf(hot-reload): resolve closure-name state once per codegen run and…
NatElkins Jun 12, 2026
98915e6
perf(hot-reload): thread the optimized tree and IlxGen env to emit on…
NatElkins Jun 12, 2026
0411073
fix(hot-reload): move tracked-input staleness from ProjectConfig to t…
NatElkins Jun 12, 2026
6cbe6d1
refactor(hot-reload): remove dead duplicate EnC types from ilbinary
NatElkins Jun 12, 2026
52ebb33
test(hot-reload): serialize generation-tracking tests with other glob…
NatElkins Jun 12, 2026
1f75f29
fix(hot-reload): drop unused bindings and clear nullness/XML-doc warn…
NatElkins Jun 18, 2026
459bca0
refactor(hot-reload): drop the SRM shadow metadata writer
NatElkins Jun 18, 2026
5a5bace
fix(hot-reload): resolve the name-map holder once to kill a torn read
NatElkins Jun 18, 2026
d4d1659
refactor(hot-reload): move hand-written name-state files to TypedTree/
NatElkins Jun 18, 2026
6f8c22a
fix(hot-reload): pin determinism for the replay compile, not just cap…
NatElkins Jun 18, 2026
a96f421
fix(hot-reload): fold base type and interfaces into the representatio…
NatElkins Jun 18, 2026
32f0ee0
fix(hot-reload): mark public active-statement API types experimental
NatElkins Jun 18, 2026
623187d
fix(hot-reload): build the PDB EncMap at the baseline method row
NatElkins Jun 18, 2026
dc08166
fix(hot-reload): stop wiping the capture store from the checker ctor
NatElkins Jun 18, 2026
cad771f
test(hot-reload): run the ilxgen name-path guard in CI; document emis…
NatElkins Jun 18, 2026
389be03
refactor(hot-reload): move the hot-reload session types out of servic…
NatElkins Jun 18, 2026
91b8f93
style(hot-reload): apply fantomas formatting
NatElkins Jun 18, 2026
dff7e96
test(hot-reload): point the enc-instance guard at the moved session file
NatElkins Jun 18, 2026
8bd3ab6
fix(hot-reload): restore checker-ctor capture reset; guard isEmpty ag…
NatElkins Jun 18, 2026
b9e9338
Merge remote-tracking branch 'origin/main' into hot-reload-v2
NatElkins Jun 18, 2026
1de5743
Move experimental hot reload flags under the --test: incubation switch
NatElkins Jun 19, 2026
9aac6c6
fix(hot-reload): merge-readiness fixes from review pass
NatElkins Jun 19, 2026
365ef7b
refactor(hot-reload): remove dead delta-emission code
NatElkins Jun 19, 2026
9f487ef
tests(hot-reload): compile hot reload tests on .NET Core only
NatElkins Jun 19, 2026
14afe76
fix(hot-reload): clear FS3261 nullness in the demo mdv launcher
NatElkins Jun 19, 2026
e0657a7
style(hot-reload): apply fantomas formatting
NatElkins Jun 19, 2026
2861600
docs(hot-reload): trim comment breadcrumbs per NoBloat
NatElkins Jun 19, 2026
b746d2a
refactor(hot-reload): revert gratuitous System.Guid qualification
NatElkins Jun 19, 2026
626e1b6
tests(hot-reload): use array-equality assert idiom that builds on Win…
NatElkins Jun 19, 2026
92894d6
tests(hot-reload): suppress experimental-API warning (FS0057) in the …
NatElkins Jun 19, 2026
fa73fdc
tests(hot-reload): make the suite robust in CI
NatElkins Jun 19, 2026
1b8e166
build: exclude tools/hot-reload/compare_roslyn.fsx from fantomas
NatElkins Jun 19, 2026
1564fc7
tests(hot-reload): load inspection baseline via memory stream
NatElkins Jun 19, 2026
f942d04
tests(hot-reload): make source-snippet tests CRLF-safe
NatElkins Jun 19, 2026
08f98ae
docs(hot-reload): note the prestart confirmation is trace-gated
NatElkins Jun 22, 2026
d917915
tests(hot-reload): characterize structural edits to non-struct CEs
NatElkins Jun 22, 2026
e8f7dc9
Emit class-form resumable state machines under hot reload (EnC)
NatElkins Jun 23, 2026
cf461b7
Hot reload tests: add suspend/surrounding-edit coverage; consolidate …
NatElkins Jun 23, 2026
be00224
Merge remote-tracking branch 'origin/main' into hot-reload-v2
NatElkins Jun 23, 2026
66301da
fix(hot-reload): encode state-machine shape digest via injective type…
NatElkins Jun 23, 2026
66c5e6e
refactor(hot-reload): remove dead UpdateActiveStatements forwarder
NatElkins Jun 23, 2026
ffc7369
feat(hot-reload): carry structured rude-edit diagnostics through the …
NatElkins Jun 23, 2026
72a94cb
docs(hot-reload): record the FSHRDL vs ENC id-namespace decision at t…
NatElkins Jun 23, 2026
71f5011
docs(hot-reload): one-command setup + dogfood-based quickstart
NatElkins Jun 23, 2026
c64097a
docs(hot-reload): fix the SDK build command for a fresh checkout
NatElkins Jun 24, 2026
3032a20
Merge remote-tracking branch 'origin/main' into hot-reload-v2
NatElkins Jun 24, 2026
39b4d0e
fix: adapt to main's Entity.Typars becoming a property (drops TyparsN…
NatElkins Jun 24, 2026
670b94f
fix(hot-reload): drop the redundant module prefix in rude-edit symbol…
NatElkins Jun 24, 2026
2f55e12
fix(hot-reload): reject embedded PDB capture
NatElkins Jun 24, 2026
9bd8880
fix(hot-reload): reject removed source files
NatElkins Jun 24, 2026
d6b652d
fix(hot-reload): use runtime identity for signature changes
NatElkins Jun 24, 2026
6e08bf6
fix(hot-reload): use runtime type shapes for entity digests
NatElkins Jun 24, 2026
ecb82ba
fix(hot-reload): include literal values in field digests
NatElkins Jun 24, 2026
cb96764
fix(hot-reload): fail closed on unresolved method updates
NatElkins Jun 24, 2026
1ff0fd4
fix(hot-reload): preserve unsigned AssemblyRef versions
NatElkins Jun 24, 2026
644e23c
fix(hot-reload): treat all metadata rows as payload
NatElkins Jun 24, 2026
02a2a98
fix(hot-reload): preserve deltas with added rows
NatElkins Jun 24, 2026
a7fabfa
fix(hot-reload): parse split long out option
NatElkins Jun 24, 2026
40ee38c
fix(hot-reload): compare output paths by platform
NatElkins Jun 24, 2026
232e82f
fix(hot-reload): preserve active statement ranges
NatElkins Jun 24, 2026
2648c92
fix(hot-reload): align delta PDB state machine headers
NatElkins Jun 24, 2026
659e852
fix(hot-reload): serialize PDB document names structurally
NatElkins Jun 24, 2026
33d5b2d
style(hot-reload): apply fantomas to review fixes
NatElkins Jun 24, 2026
5e27640
fix(hot-reload): fold unit return identity to void in signature diff
NatElkins Jun 25, 2026
0eebc87
fix(hot-reload): tolerate compiler-generated companions in fail-close…
NatElkins Jun 25, 2026
bdbd533
Fail closed on baseline-less compiler-generated types under external …
NatElkins Jul 1, 2026
6667cf1
Add flag-gated experimental in-process compile for hot reload
NatElkins Jul 2, 2026
8451a12
Consume the in-process compile's emitted module directly in the delta…
NatElkins Jul 2, 2026
afdf48c
Fix Release-build CLR access violation in baseline metadata reading
NatElkins Jul 2, 2026
81e128e
Skip typed-tree diffing for checked files reused from the compiler cache
NatElkins Jul 2, 2026
124791f
Scope per-edit EnC debug-info and closure-name refreshes to changed f…
NatElkins Jul 2, 2026
8812bf7
Cache per-file optimized trees for the in-process hot reload compile
NatElkins Jul 2, 2026
8523328
Reuse the in-process compile's emitted artifacts in the delta emitter
NatElkins Jul 2, 2026
6d1cb57
Add env-gated stage timing to hot reload delta emission
NatElkins Jul 2, 2026
998e429
Add delta dump diagnostic and user-string decode regression test
NatElkins Jul 2, 2026
22bf964
Replay session closure names in the in-process compile for stable-nam…
NatElkins Jul 2, 2026
bec584f
Align line-shifted synthesized closures by guarded positional pairing
NatElkins Jul 2, 2026
7a75891
Preserve generation-0 synthesized closure names across line shifts
NatElkins Jul 2, 2026
fec6f41
Install replay naming state when the snapshot itself shows mixed allo…
NatElkins Jul 2, 2026
c69cb2b
Record the allocation-ordered synthesized-name snapshot in the baseli…
NatElkins Jul 2, 2026
8b71035
Record the complete post-IlxGen name map in the baseline snapshot CDI
NatElkins Jul 2, 2026
3580a03
Merge main: reconcile parallel-codegen determinism (#19810) with hot …
NatElkins Jul 2, 2026
256bea5
Merge branch 'hot-reload-v2' into hot-reload-v2-merge
NatElkins Jul 2, 2026
865e3ab
Reconcile complete recorded snapshots with task helper-chain replay
NatElkins Jul 2, 2026
8350ab2
Fix CI compile breaks in hot reload test files
NatElkins Jul 3, 2026
7f00c56
Fix net472 and CI-image portability breaks in hot reload test files
NatElkins Jul 3, 2026
2f065f3
Gate the Roslyn baseline comparison tests to .NET Core
NatElkins Jul 3, 2026
2657562
Normalize fixture line endings before update-source splicing
NatElkins Jul 3, 2026
d8e685e
Remove the accidentally committed .dotnet symlink
NatElkins Jul 5, 2026
ca6fc6b
Merge main after deterministic codegen fix
NatElkins Jul 7, 2026
0689a74
Fix synthesized type alias reuse in in-process hot reload
NatElkins Jul 10, 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
4 changes: 4 additions & 0 deletions .fantomasignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ src/Compiler/Facilities/prim-parsing.fsi
src/Compiler/Utilities/TaggedCollections.fsi
src/Compiler/Service/ServiceDeclarationLists.fsi

# Fantomas limitation: reformatting produces invalid F# (script uses constructs fantomas mis-handles)

tools/hot-reload/compare_roslyn.fsx

# Fantomas limitations on signature files in FSharp.Core (to investigate)

src/FSharp.Core/fslib-extra-pervasives.fsi
Expand Down
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ mono/launcher text eol=lf
mono/config.make text eol=lf
targets.make text eol=lf

# Hot reload tests embed F# source snippets as string literals and run line-anchored
# string operations ('\n' Replace/Contains) on them; keep these files LF so the snippets
# behave identically on Windows checkouts.
tests/FSharp.Compiler.ComponentTests/HotReload/*.fs text eol=lf
tests/FSharp.Compiler.Service.Tests/HotReload/*.fs text eol=lf

*.sh text eol=lf

*.bsl linguist-vendored=true
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ BenchmarkDotNet.Artifacts/

/tests/scripts/current
.dotnet/
# Also ignore .dotnet as a symlink (the trailing-slash pattern above matches directories only,
# which once let a dev-machine symlink slip into a commit and break every CI checkout).
.dotnet
.packages/
.tools/

Expand Down
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
161 changes: 161 additions & 0 deletions G1_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# G1 report: stale-string hot reload delta decode

## Summary

Verdict on H1: refuted for the minimal session-level shape that emits a delta.

The Giraffe-like handler edit that successfully emitted a session delta did not point its `ldstr`
operand back into the baseline `#US` heap. The baseline `#US` heap size was 92 bytes, and the
updated method body used token `0x7000005D`, offset 93. Offset 93 is the first appended delta
user-string entry after the baseline heap, and it decodes to `"Hello World EDITED"`.

The simple working contrast shows the same fingerprint: baseline `#US` size 36, delta `ldstr`
token `0x70000025`, offset 37, value `"lib generation 1"`.

## Repro test

Added test:

`FSharp.Compiler.Service.Tests.HotReload.HotReloadSessionTests.G1 same-line Giraffe-shaped handler string edit decodes delta user string operand`

Source shape that emits:

```fsharp
module Sample.G1

type HttpFunc = string -> string

let handler1 (_: HttpFunc) (ctx: string) = "Hello World"

let endpointA: (HttpFunc -> string -> string) = fun (_: HttpFunc) (ctx: string) -> "Endpoint A"
let endpointB: (HttpFunc -> string -> string) = fun (_: HttpFunc) (ctx: string) -> "Endpoint B"
let endpointC: (HttpFunc -> string -> string) = fun (_: HttpFunc) (ctx: string) -> "Endpoint C"
```

Edit: `"Hello World"` to `"Hello World EDITED"` on the same line.

This emitted `Ok` with exactly one updated method, `0x06000001`, decoded as
`Sample.G1::handler1`. It does not reproduce the stale-string bug.

I also tried closer shapes with an endpoint container and the requested probe, including the exact
value-bound handler form from the task (`let handler1: (HttpFunc -> string -> string) = fun ...`).
Those did not produce a delta, so there was no IL to decode:

- Inline endpoint-list closures after `handler1`: `UnsupportedEdit` / `FSHRDL099`, synthesized type
`Sample.G1.endpoints@8` had no baseline counterpart.
- Named module-level endpoint lambdas plus a list or array container: same `FSHRDL099`, synthesized
type `Sample.G1.endpoints@12`.
- No endpoint container, but `probe () = handler1 id "x"` or a named equivalent argument:
`FSHRDL099`, synthesized type `Sample.G1.probe@11` or `probe@13`.

## Giraffe-like decode

Baseline:

- Baseline `#US` heap size: 92
- Updated method: `Sample.G1::handler1`
- Baseline method `ldstr`: `il+0x0000 token=0x70000001 offset=1`
- Baseline offset 1 decodes to: `"Hello World"`

Delta metadata:

- Updated methods: `0x06000001`
- EncLog: `(0x00000001, op=0), (0x06000001, op=0), (0x08000001, op=0), (0x08000002, op=0)`
- EncMap: `0x00000001, 0x06000001, 0x08000001, 0x08000002`
- Delta MethodDef row: `0x06000001`

Delta `#US`:

- Relative offset: 1
- Absolute offset: 93
- Value: `"Hello World EDITED"`
- Encoded bytes:
`25 48 00 65 00 6C 00 6C 00 6F 00 20 00 57 00 6F 00 72 00 6C 00 64 00 20 00 45 00 44 00 49 00 54 00 45 00 44 00 00`

Delta IL:

- `ldstr`: `il+0x0000 token=0x7000005D offset=93`
- `UserStringUpdates`: original `0x70000001`, new `0x7000005D`, value `"Hello World EDITED"`

Interpretation:

- Offset 93 is greater than the baseline heap size 92.
- The token points into the appended delta `#US` suffix, not into the baseline heap.
- The appended entry at absolute offset 93 is the edited string.

## Working contrast decode

Test:

`FSharp.Compiler.Service.Tests.HotReload.HotReloadSessionTests.G1 simple module string edit decodes working contrast delta user string operand`

Baseline:

- Source: `let libValue () = "lib generation 0"`
- Baseline `#US` heap size: 36
- Updated method: `SessionLib::libValue`
- Baseline method `ldstr`: `il+0x0000 token=0x70000001 offset=1`
- Baseline offset 1 decodes to: `"lib generation 0"`

Delta metadata:

- Updated methods: `0x06000001`
- EncLog: `(0x00000001, op=0), (0x06000001, op=0)`
- EncMap: `0x00000001, 0x06000001`
- Delta MethodDef row: `0x06000001`

Delta `#US`:

- Relative offset: 1
- Absolute offset: 37
- Value: `"lib generation 1"`
- Encoded bytes:
`21 6C 00 69 00 62 00 20 00 67 00 65 00 6E 00 65 00 72 00 61 00 74 00 69 00 6F 00 6E 00 20 00 31 00 00`

Delta IL:

- `ldstr`: `il+0x0000 token=0x70000025 offset=37`
- `UserStringUpdates`: original `0x70000001`, new `0x70000025`, value `"lib generation 1"`

This is the same appended-offset pattern as the Giraffe-like handler case.

## Analysis

The observed delta does not support the heap-offset hypothesis. In both cases, the original fresh
compile token was `0x70000001`, but the emitter remapped it to `baselineUserStringHeapSize + 1` via
the delta user-string allocator:

- Giraffe-like handler: `92 + 1 = 93`, token `0x7000005D`
- Simple contrast: `36 + 1 = 37`, token `0x70000025`

That is the expected behavior of `UserStringTokenCalculator` in
`src/Compiler/CodeGen/IlxDeltaStreams.fs`. The observed `UserStringUpdates` also match the remap
path in `src/Compiler/CodeGen/IlxDeltaEmitter.fs`: the fresh method body's old-looking token
`0x70000001` is resolved in the fresh assembly to the new text, then rewritten to an absolute
delta token.

So if the real Giraffe endpoint still serves the old string after the runtime accepts an update,
this session-level decode points away from a bad `#US` heap offset in the emitted delta. The next
place I would look is runtime application or method identity/body selection in the real app
topology, especially because the endpoint-container and probe variants hit synthesized-type
alignment failures in this harness before a delta can be emitted.

## Verification

Focused G1 tests:

```text
./.dotnet/dotnet build tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj --framework net10.0 --no-restore
./.dotnet/dotnet artifacts/bin/FSharp.Compiler.Service.Tests/Debug/net10.0/FSharp.Compiler.Service.Tests.dll --filter-method "*G1*" --show-live-output on --output Detailed
```

Result: 2 passed, 0 failed.

HotReload service suite:

```text
./.dotnet/dotnet build tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj --framework net10.0 --no-restore
./.dotnet/dotnet artifacts/bin/FSharp.Compiler.Service.Tests/Debug/net10.0/FSharp.Compiler.Service.Tests.dll --filter-namespace "FSharp.Compiler.Service.Tests.HotReload" --output Normal
```

Result: 419 passed, 0 failed.
58 changes: 58 additions & 0 deletions G1_TASK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# G1: decode the delta for the Giraffe stale-string edit

Worktree: /Users/nat/Projects/hot_reload_poc/fsharp-opt. Build with ./.dotnet/dotnet.
BUG under investigation: on the real Giraffe sample (module-level `let handler1: HttpHandler = fun
(_: HttpFunc) (ctx: HttpContext) -> ctx.WriteTextAsync "Hello World"`, eta-expanded to a static
method), a same-line string-literal edit produces a delta that the runtime applies successfully
(updatedMethods = the correct Program::handler1 row) but the endpoint keeps serving the OLD string.
The synthetic test apps show string edits correctly. Prime hypothesis H1: the updated method body's
ldstr token (0x70xxxxxx) resolves into the BASELINE #US heap (old string) instead of the delta's
appended #US entry (delta US offsets must be ABSOLUTE, i.e. baseline-heap-length-based, per the
delta format docs in this repo: HOT_RELOAD_SPEC / notes/delta_format may exist at the workspace root
one level up - do not rely on them, decode from first principles with SRM).

## Step 1: build a session-level repro test (scratch, in tests/FSharp.Compiler.Service.Tests/HotReload/)
Follow the existing session tests' shape (HotReloadSessionTests.fs: checker.Compile baseline to
disk, ResetSessionState, CreateHotReloadSession, AddProject, edit source, checker.Compile again OR
use the in-process flag - MATCH THE GIRAFFE TOPOLOGY: flag-off baseline compile, then EmitDelta with
FSHARP_HOTRELOAD_INPROCESS_COMPILE=1 so the fresh compile is in-process). Source shape mimicking
Giraffe (no ASP.NET dependency needed):
module Sample.G1
type HttpFunc = string -> string
let handler1: (HttpFunc -> string -> string) = fun (_: HttpFunc) (ctx: string) -> "Hello World"
let probe () = handler1 id "x"
Plus 2-3 more module-level closures AFTER handler1 (mimic the endpoints list: a list of closures)
so the file is closure-dense. Edit ONLY the literal "Hello World" -> "Hello World EDITED" (same
line, no line count change). EmitDelta must return Ok with exactly 1 updated method.

## Step 2: decode the delta
From the Ok delta (fields: Metadata: byte[], IL: byte[], Pdb; plus UpdatedMethods tokens):
1. Read the BASELINE assembly bytes (the on-disk baseline DLL): via System.Reflection.Metadata get
the baseline #US heap SIZE and verify reading the old string at its offset.
2. Parse delta.Metadata with MetadataReaderProvider.FromMetadataImage: enumerate EncLog/EncMap, the
MethodDef row(s) updated, and dump the delta #US heap bytes (raw) - what string(s) does it carry
and at what ABSOLUTE offsets do they live (delta heap offsets are logically appended after the
baseline heap)?
3. Parse delta.IL: locate the method body (tiny: header + IL). Extract the ldstr operand token(s)
(opcode 0x72, little-endian 4-byte token, high byte 0x70). Compute offset = token & 0x00FFFFFF.
4. VERDICT: offset < baselineUsHeapSize means the ldstr points at the BASELINE heap (H1 CONFIRMED:
which string does that offset decode to in the baseline? presumably the OLD "Hello World").
offset >= baselineUsHeapSize means it points into the delta's appended US entries (H1 dead:
decode which delta string it hits and whether that is the NEW string; then the bug is elsewhere -
report what you see).
5. CONTRAST: run the same decode on the delta from an edit shape that WORKS (the existing session
test's simple module function edit, e.g. reuse the flag test's libValue shape) and report its
ldstr offset vs baseline heap size. The difference between the two is the bug's fingerprint.

## Step 3: report
G1_REPORT.md: repro test name + whether it reproduces (if the repro's delta decodes CORRECTLY, say
so explicitly and describe what you tried; do NOT force a repro), the decoded numbers for both
cases (baseline US size, delta US contents+offsets, ldstr operands), the verdict on H1, and if
confirmed, your analysis of WHERE the offset goes wrong (the user-string token calculators live in
src/Compiler/CodeGen/IlxDeltaStreams.fs UserStringTokenCalculator; the emitter's US remap trace is
under traceUserStringUpdates in IlxDeltaEmitter.fs - grep '[fsharp-hotreload][userstrings]').
Suites: run the HotReload service suite once at the end to prove no regressions from your scratch
test (it may stay in the tree as a regression test if it reproduces; name it for G1).

## Rules
No commits/pushes/git changes. No em dashes. No AI mentions. Keep the repro minimal.
16 changes: 16 additions & 0 deletions docs/debug-emit.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ Some experiences are un-implemented by F# including:
* **Edit and Continue**
* **Hot reload**

## Hot reload heap tracing

The hot-reload metadata writer now exposes a lightweight tracing hook so you can capture heap sizes while iterating on Task&nbsp;2.3. Set the environment variable `FSHARP_HOTRELOAD_TRACE_HEAPS=1` before running a targeted test, for example:

```
FSHARP_HOTRELOAD_TRACE_HEAPS=1 ./.dotnet/dotnet test tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj -c Debug -f net10.0 --filter FullyQualifiedName~FSharpDeltaMetadataWriterTests
```

Each delta emitted during the run prints a summary such as:

```
[fsharp-hotreload][heap-summary] baseline:string=1234 blob=2048 guid=256 | delta:string=64 blob=32 guid=0
```

The new service-level regressions (`property/event/async delta reports baseline heap offsets`) validate that `MetadataDelta.HeapOffsets` mirror the baseline reader before any heap reuse changes are made. Combine those tests with the trace output above to document the current string/blob footprint before adjusting the builders.

## Emitted information

Emitted debug information includes:
Expand Down
Loading
Loading