fsharp icon indicating copy to clipboard operation
fsharp copied to clipboard

Incorrect codegen for Debug build with srtp and mutable struct

Open glchapman opened this issue 2 years ago • 1 comments

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;
	}
}

glchapman avatar Nov 16 '23 18:11 glchapman

We might need to rethink when we do defensive copying in debug builds. In release it fully inlines and avoids this problem.

abonie avatar Aug 05 '24 17:08 abonie