fsharp
fsharp copied to clipboard
Incorrect codegen for Debug build with srtp and mutable struct
The showIt function in the following has incorrect code in Debug builds (the bug exists in both F# 7 and 8):
open System
open System.Buffers
open System.Text
let inline forEach<'C, 'E, 'I
when 'C: (member GetEnumerator: unit -> 'I)
and 'I: struct
and 'I: (member MoveNext: unit -> bool)
and 'I: (member Current : 'E) >
([<InlineIfLambda>] f: 'E -> unit) (container: 'C) =
let mutable iter = container.GetEnumerator()
while iter.MoveNext() do
f iter.Current
let showIt (buffer: ReadOnlySequence<byte>) =
buffer |> forEach (fun segment ->
let s = Encoding.ASCII.GetString(segment.Span)
Console.Write(s)
)
Using ILSpy, here's a (correct) Release build:
public static void showIt(ReadOnlySequence<byte> buffer)
{
ReadOnlySequence<byte>.Enumerator enumerator = buffer.GetEnumerator();
while (enumerator.MoveNext())
{
ReadOnlyMemory<byte> current = enumerator.Current;
Console.Write(Encoding.ASCII.GetString(current.Span));
}
}
Below is the incorrect Debug build. The problem is that enumerator2 is always reinitialized in the loop (using the value of enumerator). enumerator2.MoveNext() will then mutate enumerator2, but this mutation will be lost when enumerator2 is reinitialized:
public static void showIt(ReadOnlySequence<byte> buffer)
{
ReadOnlySequence<byte> readOnlySequence = buffer;
ReadOnlySequence<byte>.Enumerator enumerator = readOnlySequence.GetEnumerator();
while (true)
{
ReadOnlySequence<byte>.Enumerator enumerator2 = enumerator;
if (enumerator2.MoveNext())
{
ReadOnlyMemory<byte> segment = enumerator.Current;
string s = Encoding.ASCII.GetString(segment.Span);
Console.Write(s);
continue;
}
break;
}
}
We might need to rethink when we do defensive copying in debug builds. In release it fully inlines and avoids this problem.