-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
NativeAOT RC2 crash with GVMs on linux-x64 #77070
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
I've got a minimal repro and some more info about this: using System.Collections.Generic;
// Regression test for https://github.com/dotnet/runtime/issues/77070
public static class Gvm77070
{
public static int Run()
{
var deserializer = new JsonDeserializer();
var visitor = new Release.SerdeVisitor();
deserializer.DeserializeDictionary<Release, Release.SerdeVisitor>(visitor);
return 100;
}
public partial record struct Release
{
public sealed class SerdeVisitor : IDeserializeVisitor<Release>
{
void IDeserializeVisitor<Release>.VisitDictionary<D>(ref D d)
{
d.GetNextValue<DictWrap<string, StringWrap, string>>();
}
}
}
public record StringWrap(string Value)
: IDeserialize
{
public static void Deserialize<D>(ref D deserializer)
where D : IDeserializer
{
}
}
public readonly struct DictWrap<TKey, TKeyWrap, TValue> : IDeserialize
where TKey : notnull
where TKeyWrap : IDeserialize
{
static void IDeserialize.Deserialize<D>(ref D deserializer)
{
deserializer.DeserializeDictionary<Dictionary<TKey, TValue>, Visitor>(new Visitor());
}
private sealed class Visitor : IDeserializeVisitor<Dictionary<TKey, TValue>>
{
void IDeserializeVisitor<Dictionary<TKey, TValue>>.VisitDictionary<D>(ref D d)
{
d.GetNextValue<TKeyWrap>();
}
}
}
public interface IDeserializeVisitor<T>
{
void VisitDictionary<D>(ref D d) where D : IDeserializeDictionary;
}
public interface IDeserialize
{
static abstract void Deserialize<D>(ref D deserializer) where D : IDeserializer;
}
public interface IDeserializeDictionary
{
void GetNextValue<D>() where D : IDeserialize;
}
public interface IDeserializer
{
void DeserializeDictionary<T, V>(V v) where V : class, IDeserializeVisitor<T>;
}
public sealed partial class JsonDeserializer : IDeserializer
{
public void DeserializeDictionary<T, V>(V v) where V : class, IDeserializeVisitor<T>
{
var map = new DeDictionary(this);
v.VisitDictionary(ref map);
}
private struct DeDictionary : IDeserializeDictionary
{
private JsonDeserializer _deserializer;
public DeDictionary(JsonDeserializer de)
{
_deserializer = de;
}
public void GetNextValue<D>() where D : IDeserialize
{
D.Deserialize(ref _deserializer);
}
}
}
} The problem appears to be something about static interface methods with generics. It looks like this is blowing up in the type loader, which doesn't know how to work with this method specification. I'm not sure what the fix is, but I'm still looking into it. |
Workaround is the following RD.XML (I constructed thing by looking up the symbols associated with the addresses in the FailFast) - it pregenerates the method dictionary we're failing to build at runtime:
I looked at this in a debugger myself and yep, this needs to be fixed in the type loader (we need to implement the missing dictionary kind that this is throwing about) and it is a hard fix:
We need to call into GVM resolution logic as part of type loading and build whatever GVM resolution logic came up with. Either that or generate something that will allow us to lazily resolve this once it's needed. Touching anything in the type loader is pretty high risk. To set expectations, the fix might be too risky for servicing. |
Struct visitors are useful (no allocations), but they're tricky, as side-effects may not be preserved when they are copied by the underlying deserializer. Most importantly, a Native AOT bug (dotnet/runtime#77070) currently causes some static abstract interface calls to fail when they have to be loaded at runtime. Generic specialization can avoid this. By using a struct visitor for some compound calls (dictionaries, enumerables, nullables) the likelihood of hitting this is reduced.
If the program hits the conditions in dotnet#77070, generate a failfast message that makes it possible to create a workaround. Instead of: ``` Process terminated. Generic virtual method pointer lookup failure. Declaring type handle: MethodTable:0x00007FF66E8587B8 Target type handle: MethodTable:0x00007FF66E858810 Method name: Serialize Instantiation: Argument 00000000: MethodTable:0x00007FF66E85DA08 ``` Generate: ``` Process terminated. Generic virtual method pointer lookup failure. Declaring type handle: EETypeRva:0x005438B8(MemoryPackFormatter2`1[MemPackObject]) Target type handle: EETypeRva:0x00543910(MemoryPackableFormatter2`1[MemPackObject]) Method name: Serialize Instantiation: Argument 00000000: EETypeRva:0x00529B38(System.Buffers.ArrayBufferWriter`1[System.Byte]) ``` The workaround is then: ```xml <Directives> <Application> <Assembly Name="repro"> <Type Name="MemoryPackableFormatter2`1[[MemPackObject]]"> <Method Name="Serialize" Dynamic="Required All"> <GenericArgument Name="System.Buffers.ArrayBufferWriter`1[[System.Byte, mscorlib]],System.Memory" /> </Method> </Type> </Assembly> </Application> </Directives> ```
If the program hits the conditions in #77070, generate a failfast message that makes it possible to create a workaround. Instead of: ``` Process terminated. Generic virtual method pointer lookup failure. Declaring type handle: MethodTable:0x00007FF66E8587B8 Target type handle: MethodTable:0x00007FF66E858810 Method name: Serialize Instantiation: Argument 00000000: MethodTable:0x00007FF66E85DA08 ``` Generate: ``` Process terminated. Generic virtual method pointer lookup failure. Declaring type handle: EETypeRva:0x005438B8(MemoryPackFormatter2`1[MemPackObject]) Target type handle: EETypeRva:0x00543910(MemoryPackableFormatter2`1[MemPackObject]) Method name: Serialize Instantiation: Argument 00000000: EETypeRva:0x00529B38(System.Buffers.ArrayBufferWriter`1[System.Byte]) ``` The workaround is then: ```xml <Directives> <Application> <Assembly Name="repro"> <Type Name="MemoryPackableFormatter2`1[[MemPackObject]]"> <Method Name="Serialize" Dynamic="Required All"> <GenericArgument Name="System.Buffers.ArrayBufferWriter`1[[System.Byte, mscorlib]],System.Memory" /> </Method> </Type> </Assembly> </Application> </Directives> ```
If the program hits the conditions in #77070, generate a failfast message that makes it possible to create a workaround. Instead of: ``` Process terminated. Generic virtual method pointer lookup failure. Declaring type handle: MethodTable:0x00007FF66E8587B8 Target type handle: MethodTable:0x00007FF66E858810 Method name: Serialize Instantiation: Argument 00000000: MethodTable:0x00007FF66E85DA08 ``` Generate: ``` Process terminated. Generic virtual method pointer lookup failure. Declaring type handle: EETypeRva:0x005438B8(MemoryPackFormatter2`1[MemPackObject]) Target type handle: EETypeRva:0x00543910(MemoryPackableFormatter2`1[MemPackObject]) Method name: Serialize Instantiation: Argument 00000000: EETypeRva:0x00529B38(System.Buffers.ArrayBufferWriter`1[System.Byte]) ``` The workaround is then: ```xml <Directives> <Application> <Assembly Name="repro"> <Type Name="MemoryPackableFormatter2`1[[MemPackObject]]"> <Method Name="Serialize" Dynamic="Required All"> <GenericArgument Name="System.Buffers.ArrayBufferWriter`1[[System.Byte, mscorlib]],System.Memory" /> </Method> </Type> </Assembly> </Application> </Directives> ```
For whatever reason, the logic that walks the inheritance hierarchy and deals with the differences between interface GVM and class GVM calls was in CoreLib. Move it to the type loader because we'll need it for dotnet#77070.
) * Move all of generic virtual method resolution to the type loader For whatever reason, the logic that walks the inheritance hierarchy and deals with the differences between interface GVM and class GVM calls was in CoreLib. Move it to the type loader because we'll need it for #77070. * Oh the conversion to EEType was important
Generic virtual method resolution currently happens on top of `RuntimeTypeHandle`s (i.e. the runtime type system). This works as long as we only need to do generic virtual method resolution on top of types that exist in the runtime. In order to lay foundation for fixing #77070, we need to be able to resolve not just with types that have a type handle, but also for types that we're in the process of building (e.g. due to MakeGeneric, or due to another generic virtual method dispatch). This pull request rewrites generic virtual method dispatch to happen on top of the managed type system, instead of the runtime type system. It's a bit more than just a mechanical replacement because the existing algorithm was set up in a confusing way, with `ref` parameters that were changing what we were resolving and an odd `slotChanged` logic that I don't fully understand. I replaced the `slotChanged` with the straightforward thing (if we resolve interface method to a virtual method on a type, find the implementation of the virtual method on the current type). Tests seem to be passing. Also instead of a bunch of `ref` parameters we now just pass a `MethodDesc`. This also includes a compiler change, because in order for the managed type system's casting logic to work, we need to be able to find out the variance of parameters on generic definitions. The compiler change is about generating this info.
If the program hits the conditions in #77070, generate a failfast message that makes it possible to create a workaround. Instead of: ``` Process terminated. Generic virtual method pointer lookup failure. Declaring type handle: MethodTable:0x00007FF66E8587B8 Target type handle: MethodTable:0x00007FF66E858810 Method name: Serialize Instantiation: Argument 00000000: MethodTable:0x00007FF66E85DA08 ``` Generate: ``` Process terminated. Generic virtual method pointer lookup failure. Declaring type handle: EETypeRva:0x005438B8(MemoryPackFormatter2`1[MemPackObject]) Target type handle: EETypeRva:0x00543910(MemoryPackableFormatter2`1[MemPackObject]) Method name: Serialize Instantiation: Argument 00000000: EETypeRva:0x00529B38(System.Buffers.ArrayBufferWriter`1[System.Byte]) ``` The workaround is then: ```xml <Directives> <Application> <Assembly Name="repro"> <Type Name="MemoryPackableFormatter2`1[[MemPackObject]]"> <Method Name="Serialize" Dynamic="Required All"> <GenericArgument Name="System.Buffers.ArrayBufferWriter`1[[System.Byte, mscorlib]],System.Memory" /> </Method> </Type> </Assembly> </Application> </Directives> ``` Co-authored-by: Michal Strehovský <[email protected]>
Output:
backtrace
Figuring out how to upload the exe and core dump
The text was updated successfully, but these errors were encountered: