Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -129,6 +129,7 @@
* Fix dot-completion after indexed expressions (`a.[0].Data.`, `a[0].Data.`, `[1;2].Length.`) returning unrelated global completions instead of expression-typings members. ([Issue #4966](https://github.com/dotnet/fsharp/issues/4966), [PR #19934](https://github.com/dotnet/fsharp/pull/19934))
* Quotations of `match s with "" -> _` no longer leak the `s <> null && s.Length = 0` lowering; the empty-string optimization moved from pattern-match compilation to the optimizer so quoted expressions keep `op_Equality(s, "")`. ([Issue #19873](https://github.com/dotnet/fsharp/issues/19873))
* Fix #5795: Allow attributes defined in a `module rec` / `namespace rec` scope to be used on union cases, record fields, and generic type parameters of types in the same recursive scope. ([Issue #5795](https://github.com/dotnet/fsharp/issues/5795), [PR #19744](https://github.com/dotnet/fsharp/pull/19744))
* Fix Release-only `System.InvalidProgramException` when a struct (value-type) collection implementing `seq<'T>` (e.g. `System.Collections.Immutable.ImmutableArray<_>`) is materialised via `Seq.collect`/`yield!` into `List.ofSeq`/`Seq.toList`/`Seq.toArray`. The comprehension lowering now coerces (boxes) the struct sub-collection to `seq<'T>` before `ListCollector`/`ArrayCollector.AddMany`, and uses `unit` as the try/finally result type, so it no longer emits invalid IL (a value type where a reference `IEnumerable` is expected, plus a spurious `ldnull` stored into a struct local). ([Issue #20203](https://github.com/dotnet/fsharp/issues/20203))

### Added

Expand Down
23 changes: 20 additions & 3 deletions src/Compiler/Optimize/LowerComputedCollections.fs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ let LowerComputedListOrArraySeqExpr tcVal g amap m collectorTy overallSeqExpr =
let infoReader = InfoReader(g, amap)
let collVal, collExpr = mkMutableCompGenLocal m "@collector" collectorTy
//let collExpr = mkValAddr m false (mkLocalValRef collVal)
// The 'seq<'T>' parameter type of AddMany/AddManyAndClose, derived from the
// ListCollector<'T>/ArrayCollector<'T> element type. A struct sub-collection must be
// coerced (boxed) to this so the AddMany call site emits valid IL.
let collectorSeqTy = mkSeqTy g (List.head (argsOfAppTy g collectorTy))
let rec ConvertSeqExprCode isUninteresting isTailcall expr =
match expr with
| SeqYield g (e, m) ->
Expand Down Expand Up @@ -98,7 +102,7 @@ let LowerComputedListOrArraySeqExpr tcVal g amap m collectorTy overallSeqExpr =
let cleanupE = BuildDisposableCleanup tcVal g infoReader m v
let exprR =
mkLet spBind m v resource
(mkTryFinally g (bodyExprR, cleanupE, m, tyOfExpr g bodyExpr, DebugPointAtTry.No, DebugPointAtFinally.No))
(mkTryFinally g (bodyExprR, cleanupE, m, g.unit_ty, DebugPointAtTry.No, DebugPointAtFinally.No))
Result.Ok (false, exprR)
| Result.Error msg -> Result.Error msg

Expand Down Expand Up @@ -126,7 +130,10 @@ let LowerComputedListOrArraySeqExpr tcVal g amap m collectorTy overallSeqExpr =
(callNonOverloadedILMethod g amap mIn "get_Current" inpEnumTy [enumve]))
bodyExprR, mIn),
cleanupE,
mFor, tyOfExpr g bodyExpr, DebugPointAtTry.No, DebugPointAtFinally.No))
// The lowered body is a unit-typed collector call, so the try/finally result
// type is unit; using the original (possibly struct) body type made the optimizer
// emit 'ldnull; stloc <struct>' - invalid IL.
mFor, g.unit_ty, DebugPointAtTry.No, DebugPointAtFinally.No))
|> addForDebugPoint
Result.Ok (false, exprR)
| Result.Error msg -> Result.Error msg
Expand All @@ -136,7 +143,7 @@ let LowerComputedListOrArraySeqExpr tcVal g amap m collectorTy overallSeqExpr =
match resBody with
| Result.Ok (_, bodyExprR) ->
let exprR =
mkTryFinally g (bodyExprR, compensation, m, tyOfExpr g bodyExpr, spTry, spFinally)
mkTryFinally g (bodyExprR, compensation, m, g.unit_ty, spTry, spFinally)
Result.Ok (false, exprR)
| Result.Error msg -> Result.Error msg

Expand Down Expand Up @@ -201,6 +208,16 @@ let LowerComputedListOrArraySeqExpr tcVal g amap m collectorTy overallSeqExpr =
// printfn "FAILED - not worth compiling an unrecognized Seq.toList at %s " (stringOfRange m)
Result.Error ()
else
// A struct sub-collection (a value-type seq<'T>, e.g. ImmutableArray<_>) is never
// implicitly upcast, so coerce it to the collector's seq<'T> param type. GenCoerce emits
// nothing for reference upcasts and 'box; unbox.any' for structs (matching the yield! path);
// without this, AddMany/AddManyAndClose receive a value type where a ref IEnumerable is
// expected, producing invalid IL.
let srcTy = tyOfExpr g arbitrarySeqExpr
let arbitrarySeqExpr =
if typeEquiv g srcTy collectorSeqTy then arbitrarySeqExpr
else mkCoerceExpr (arbitrarySeqExpr, collectorSeqTy, m, srcTy)

// If we're the final in a sequential chain then we can AddMany, Close and return
if isTailcall then
let exprR = mkCallCollectorAddManyAndClose tcVal (g: TcGlobals) infoReader m collExpr arbitrarySeqExpr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,9 @@ module ComputedCollections =
compilation
|> getCompilation
|> verifyCompilation

[<Theory; FileInlineData("StructSeqCollectToList.fs", Realsig = BooleanOptions.Both, Optimize = BooleanOptions.True)>]
let ``StructSeqCollectToList_fs`` compilation =
compilation
|> getCompilation
|> verifyCompilation
Original file line number Diff line number Diff line change
Expand Up @@ -523,9 +523,8 @@
.maxstack 5
.locals init (valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32> V_0,
class [runtime]System.Collections.Generic.IEnumerator`1<int32> V_1,
class [runtime]System.Collections.Generic.IEnumerable`1<int32> V_2,
int32 V_3,
class [runtime]System.IDisposable V_4)
int32 V_2,
class [runtime]System.IDisposable V_3)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: ldc.i4.1
Expand All @@ -541,17 +540,17 @@

IL_0012: ldloc.1
IL_0013: callvirt instance !0 class [runtime]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
IL_0018: stloc.3
IL_0018: stloc.2
IL_0019: ldarg.0
IL_001a: ldnull
IL_001b: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit,class [FSharp.Core]Microsoft.FSharp.Core.Unit>::Invoke(!0)
IL_0020: pop
IL_0021: ldloca.s V_0
IL_0023: ldloc.3
IL_0023: ldloc.2
IL_0024: call instance void valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Add(!0)
IL_0029: nop
IL_002a: ldloca.s V_0
IL_002c: ldloc.3
IL_002c: ldloc.2
IL_002d: ldc.i4.1
IL_002e: add
IL_002f: call instance void valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Add(!0)
Expand All @@ -560,29 +559,25 @@
IL_0036: callvirt instance bool [runtime]System.Collections.IEnumerator::MoveNext()
IL_003b: brtrue.s IL_0012

IL_003d: ldnull
IL_003e: stloc.2
IL_003f: leave.s IL_0056
IL_003d: leave.s IL_0051

}
finally
{
IL_0041: ldloc.1
IL_0042: isinst [runtime]System.IDisposable
IL_0047: stloc.s V_4
IL_0049: ldloc.s V_4
IL_004b: brfalse.s IL_0055

IL_004d: ldloc.s V_4
IL_004f: callvirt instance void [runtime]System.IDisposable::Dispose()
IL_0054: endfinally
IL_0055: endfinally
IL_003f: ldloc.1
IL_0040: isinst [runtime]System.IDisposable
IL_0045: stloc.3
IL_0046: ldloc.3
IL_0047: brfalse.s IL_0050

IL_0049: ldloc.3
IL_004a: callvirt instance void [runtime]System.IDisposable::Dispose()
IL_004f: endfinally
IL_0050: endfinally
}
IL_0056: ldloc.2
IL_0057: pop
IL_0058: ldloca.s V_0
IL_005a: call instance !0[] valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Close()
IL_005f: ret
IL_0051: ldloca.s V_0
IL_0053: call instance !0[] valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Close()
IL_0058: ret
}

.method public static int32[] f0000() cil managed
Expand Down Expand Up @@ -1024,12 +1019,11 @@
.maxstack 5
.locals init (valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32> V_0,
class [runtime]System.Collections.Generic.IEnumerator`1<int32> V_1,
class [runtime]System.Collections.Generic.IEnumerable`1<int32> V_2,
int32 V_3,
int32 V_2,
valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>& V_3,
valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>& V_4,
valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>& V_5,
valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>& V_6,
class [runtime]System.IDisposable V_7)
class [runtime]System.IDisposable V_6)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: ldc.i4.1
Expand All @@ -1041,60 +1035,56 @@
IL_000f: stloc.1
.try
{
IL_0010: br.s IL_004c
IL_0010: br.s IL_004a

IL_0012: ldloc.1
IL_0013: callvirt instance !0 class [runtime]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
IL_0018: stloc.3
IL_0018: stloc.2
IL_0019: ldloca.s V_0
IL_001b: stloc.s V_4
IL_001d: ldloc.s V_4
IL_001f: ldarg.0
IL_0020: ldnull
IL_0021: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit,int32>::Invoke(!0)
IL_0026: call instance void valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Add(!0)
IL_002b: nop
IL_002c: ldloca.s V_0
IL_002e: stloc.s V_5
IL_0030: ldloc.s V_5
IL_0032: ldarg.1
IL_0033: ldnull
IL_0034: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit,int32>::Invoke(!0)
IL_0039: call instance void valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Add(!0)
IL_003e: nop
IL_003f: ldloca.s V_0
IL_0041: stloc.s V_6
IL_0043: ldloc.s V_6
IL_0045: ldloc.3
IL_0046: call instance void valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Add(!0)
IL_004b: nop
IL_004c: ldloc.1
IL_004d: callvirt instance bool [runtime]System.Collections.IEnumerator::MoveNext()
IL_0052: brtrue.s IL_0012

IL_0054: ldnull
IL_0055: stloc.2
IL_0056: leave.s IL_006d
IL_001b: stloc.3
IL_001c: ldloc.3
IL_001d: ldarg.0
IL_001e: ldnull
IL_001f: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit,int32>::Invoke(!0)
IL_0024: call instance void valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Add(!0)
IL_0029: nop
IL_002a: ldloca.s V_0
IL_002c: stloc.s V_4
IL_002e: ldloc.s V_4
IL_0030: ldarg.1
IL_0031: ldnull
IL_0032: callvirt instance !1 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Core.Unit,int32>::Invoke(!0)
IL_0037: call instance void valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Add(!0)
IL_003c: nop
IL_003d: ldloca.s V_0
IL_003f: stloc.s V_5
IL_0041: ldloc.s V_5
IL_0043: ldloc.2
IL_0044: call instance void valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Add(!0)
IL_0049: nop
IL_004a: ldloc.1
IL_004b: callvirt instance bool [runtime]System.Collections.IEnumerator::MoveNext()
IL_0050: brtrue.s IL_0012

IL_0052: leave.s IL_0069

}
finally
{
IL_0058: ldloc.1
IL_0059: isinst [runtime]System.IDisposable
IL_005e: stloc.s V_7
IL_0060: ldloc.s V_7
IL_0062: brfalse.s IL_006c

IL_0064: ldloc.s V_7
IL_0066: callvirt instance void [runtime]System.IDisposable::Dispose()
IL_006b: endfinally
IL_006c: endfinally
IL_0054: ldloc.1
IL_0055: isinst [runtime]System.IDisposable
IL_005a: stloc.s V_6
IL_005c: ldloc.s V_6
IL_005e: brfalse.s IL_0068

IL_0060: ldloc.s V_6
IL_0062: callvirt instance void [runtime]System.IDisposable::Dispose()
IL_0067: endfinally
IL_0068: endfinally
}
IL_006d: ldloc.2
IL_006e: pop
IL_006f: ldloca.s V_0
IL_0071: call instance !0[] valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Close()
IL_0076: ret
IL_0069: ldloca.s V_0
IL_006b: call instance !0[] valuetype [FSharp.Core]Microsoft.FSharp.Core.CompilerServices.ArrayCollector`1<int32>::Close()
IL_0070: ret
}

.method public static int32[] f1() cil managed
Expand Down
Loading
Loading