New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F# System.TypeLoadException : The generic type 'System.Tuple`2' was used with an invalid instantiation in assembly #558

Closed
buybackoff opened this Issue Jul 26, 2015 · 5 comments

Comments

Projects
None yet
7 participants
@buybackoff
Contributor

buybackoff commented Jul 26, 2015

In this SO question the minimum example of a bug in Debug mode.

This code works perfectly in Release mode, but throws in Debug mode with:

System.TypeLoadException : The generic type 'System.Tuple`2' was used with an invalid instantiation in assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.

open System
open System.Collections.Generic
open System.Runtime.InteropServices


module Test =

  type MyType<'A,'B,'C>() =
    let mutable counter = 0
    member this.Counter with get() = counter and set(v) = counter <- v
    member inline this.TryDoWorkChecked(next:KeyValuePair<'A,'B>, [<Out>] value: byref<'C>) : bool =
      let before = this.Counter
      let res = this.TryDoWork(next, &value)
      if before <> this.Counter then raise (InvalidOperationException("Must not change counter during work"))
      else res
    abstract TryDoWork: next:KeyValuePair<'A,'B> * [<Out>] value: byref<'C> -> bool
    override this.TryDoWork(next:KeyValuePair<'A,'B>, [<Out>] value: byref<'C>) : bool =
      value <- Unchecked.defaultof<'C>
      if counter > 10 then true else false

    member this.DoWork(next:KeyValuePair<'A,'B>) =
      let mutable value = Unchecked.defaultof<'C>
      while not (this.TryDoWorkChecked(next, &value)) do
        counter <- counter + 1


open Test

[<EntryPoint>]
let main argv = 
    let myType = MyType<int,int,int>()
    let next = Unchecked.defaultof<KeyValuePair<int,int>>
    myType.DoWork(next)
    Console.ReadLine() |> ignore
    0

In Debug mode, ILSpy shows this:

// Program.Test.MyType<A, B, C>
public unsafe void DoWork(KeyValuePair<A, B> next)
{
    C value = default(C);
    while (true)
    {
        Tuple<KeyValuePair<A, B>, C*> tuple = new Tuple<KeyValuePair<A, B>, C*>(next, ref value);
        KeyValuePair<A, B> item = tuple.Item1;
        C* item2 = tuple.Item2;
        int num = this.Counter;
        bool flag = this.TryDoWork(item, item2);
        if ((num == this.Counter) ? flag : Operators.Raise<bool>(new InvalidOperationException("Must not change counter during work")))
        {
            break;
        }
        this.counter++;
    }
}

And this is ILSpy output in Release mode:

// Program.Test.MyType<A, B, C>
public void DoWork(KeyValuePair<A, B> next)
{
    C value = default(C);
    while (true)
    {
        int num = this.counter;
        bool flag = this.TryDoWork(next, out value);
        if (num != this.counter)
        {
            break;
        }
        if (flag)
        {
            return;
        }
        this.counter++;
    }
    throw new InvalidOperationException("Must not change counter during work");
}
@latkin

This comment has been minimized.

Contributor

latkin commented Jul 27, 2015

Minimal repro:

type A =
    static member inline Foo(x : int, y : int byref) = ()

let mutable x = 0
A.Foo(0, &x)

It appears that unoptimized codegen for inline members does not handle byref parameters correctly.

@latkin latkin added the bug label Jul 27, 2015

@latkin latkin added this to the F# 4.0 Update 1 milestone Aug 4, 2015

@dsyme dsyme added the pri-1 label Aug 14, 2015

@mrange

This comment has been minimized.

Contributor

mrange commented Sep 3, 2015

When I ran into it seemed that the compiler generated a tuple to hold the Foo parameters. This works fine for Release mode as the tuple is optimized away (I think)

Decompiled debug mode:

// Program
[EntryPoint]
public unsafe static int main(string[] argv)
{
    int x = 0;
    Tuple<int, int*> tuple = new Tuple<int, int*>(0, ref x); // CRT doesn't like tuples of ptrs
    int item = tuple.Item1;
    int* item2 = tuple.Item2;
    *item2 = item;
    return 0;
}

Decompiled release mode:

// Program
[EntryPoint]
public static int main(string[] argv)
{
    int x = 0;
    x = 0;
    return 0;
}

One way around this I guess would make sure the tuple removal optimization is applied in debug mode as well? Or better yet why is the extra tuple created in the first place?

For non-inline functions no tuple is generated in debug or release mode:

// Program
[EntryPoint]
public static int main(string[] argv)
{
    int x = 0;
    Program.A.Foo(0, ref x);
    return x;
}
@TIHan

This comment has been minimized.

Contributor

TIHan commented Sep 11, 2015

I haven't got very far, but I think this is a good place to start: https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/Optimizer.fs#L1431

@TIHan

This comment has been minimized.

Contributor

TIHan commented Sep 12, 2015

I think part of it is because in Debug mode, dead binding elimination is disabled, and the compiler creates an invalid tuple type Tuple<int, int&>. Why it breaks when the function is inline? My guess is when the function is marked inlined, any arguments that are of type tuple, the compiler will create a tuple. In Release, this created tuple gets eliminated, but in Debug is does not; and since the compiler generated a bad tuple type and no elimination happens, it blows up.

I don't know if that made sense or if I'm on the right track. Maybe a way to fix it would be to have a special case for the dead code elimination to enable itself if the binding types are referencing this bad tuple type.

@dsyme

This comment has been minimized.

Contributor

dsyme commented Jan 8, 2016

Closing in favour of master bug #862

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment