Closed kaleidocore closed 1 year ago
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.
With a wasmconsole
template, I get:
$ WasmAppHost --runtime-config /private/tmp/wc/bin/Debug/net7.0/browser-wasm/AppBundle/wc.runtimeconfig.json
Running: node main.mjs
Using working directory: /private/tmp/wc/bin/Debug/net7.0/browser-wasm/AppBundle
mono_wasm_runtime_ready fe00e07a-5519-4dfe-b35a-f867dbaf2e28
Hello, World! Greetings from node version: v19.1.0
Error: One or more errors occurred.
(Could not find method '.ctor' due to a type load error: VTable setup of type DerivedCrash`1[T] failed assembly:wc.dll type:DerivedCrash`1 member:(null))
at jo (file:///private/tmp/wc/bin/Debug/net7.0/browser-wasm/AppBundle/dotnet.js:5:38027)
at Object.Oo (file:///private/tmp/wc/bin/Debug/net7.0/browser-wasm/AppBundle/dotnet.js:5:37626)
at _mono_wasm_marshal_promise (file:///private/tmp/wc/bin/Debug/net7.0/browser-wasm/AppBundle/dotnet.js:14:104180)
at do_icall (wasm://wasm/009924a6:wasm-function[313]:0x1d3b4)
at do_icall_wrapper (wasm://wasm/009924a6:wasm-function[283]:0x1c8d1)
at interp_exec_method (wasm://wasm/009924a6:wasm-function[221]:0xdfdd)
at interp_runtime_invoke (wasm://wasm/009924a6:wasm-function[220]:0xce8f)
at mono_jit_runtime_invoke (wasm://wasm/009924a6:wasm-function[8112]:0x1a1fcc)
at do_runtime_invoke (wasm://wasm/009924a6:wasm-function[2053]:0x859fe)
at mono_runtime_try_invoke (wasm://wasm/009924a6:wasm-function[2058]:0x86066)
exiting due to exception: Error: One or more errors occurred. (Could not find method '.ctor' due to a type load error: VTable setup of type DerivedCrash`1[T] failed assembly:wc.dll type:DerivedCrash`1 member:(null))
7.0.0
main
from few days backcc @pavelsavara I see _mono_wasm_marshal_promise
near top of the trace.
cc @BrzVlad
Tagging subscribers to 'arch-wasm': @lewing See info in area-owners.md if you want to be subscribed.
Author: | kaleidocore |
---|---|
Assignees: | - |
Labels: | `arch-wasm`, `untriaged` |
Milestone: | - |
_mono_wasm_marshal_promise
That's correct because it has async Task Main()
.
Or are you suggesting something else @radical ?
This issue is not WASM related, I can reproduce it just fine with a desktop sample. I know Mono covariance support is not perfect. Maybe @lambdageek has some observations here.
I apologize if I complicated things by bringing in an async path, it probably has nothing to with async per-se but just the fact that the DerivedCrash<T>
class resolves differently when wrapped in a Task<T>
.
I also found I get a MissingFieldException
by calling:
static IEnumerable<DerivedCrash<int>> CrashMeToo() => Enumerable.Empty<DerivedCrash<int>>();
Resulting in:
System.MissingFieldException: Field not found: !0[] .EmptyArray`1.Value Due to: Could not find field in class
at System.Linq.Enumerable.Empty[DerivedCrash`1]()
at CoCrashWASM.Program.CrashMeToo()
at CoCrashWASM.Program.Main()
Interesting. Thanks for the bug report, @kaleidocore. It doesn't have anything to do with wasm, specifically.
I think these are all the same crash (possibly with the exception of CrashMeToo
from https://github.com/dotnet/runtime/issues/78635#issuecomment-1323555329 - I can't actually repro the MissingFieldException in a console app) due to failing to set up the vtable for DerivedCrash<T>
which leads to a failed class which then manifests in different ways, depending on how we observe the failed class.
Not sure yet about the underlying issue. This is covariant returns, not interface covariance, btw. Which should be much closer to coreclr's behavior.
The ICrash
and DerivedCrash
types don't have to be generic to get CrashMe
to crash. (although DerivedCrash<>
has to be generic to get CrashMeAsync
and CrashMeToo
to crash).
The weird thing that this example does is that BaseCrash
doesn't implement ICrash
, which is different from most covariant return examples. Changing public abstract class BaseCrash { ... }
to public abstract class BaseCrash : ICrash { ... }
makes all the cases work.
public class Program
{
public static async Task Main()
{
// Each of these crashes
CrashMe(); // Can't find .ctor, VTable error
//await CrashMeAsync(); // Generic parameter 0 error
//CrashMeToo(); // also Generic parameter 0 error
}
static void CrashMe()
{
ICrash x = new DerivedCrash<int>();
x.MyMeth ();
}
static async Task CrashMeAsync() => await CrashDifferent();
static Task<DerivedCrash<int>> CrashDifferent() => throw new Exception();
static IEnumerable<DerivedCrash<int>> CrashMeToo() => Enumerable.Empty<DerivedCrash<int>>();
}
public abstract class BaseProp
{
}
public class DerivedProp : BaseProp
{
}
public interface ICrash
{
BaseProp MyMeth();
}
public abstract class BaseCrash
{
public abstract BaseProp MyMeth ();
}
// The CrashMe crash happens even if this class is just DerivedCrash (without a <T>).
// The CrashMeAsync and CrashMeToo seem to work (because the instantiated generic param of Task<> or IEnumerable<> isn't used?)
public class DerivedCrash<T> : BaseCrash, ICrash
{
public override DerivedProp MyMeth () => new();
}
@davidwrighton why does this example work on coreclr? In the IL I see:
.class public auto ansi beforefieldinit DerivedCrash`1<T>
extends BaseCrash
implements ICrash {
.param type T
.custom instance void class System.Runtime.CompilerServices.NullableAttribute::'.ctor'(unsigned int8) = (01 00 02 00 00 ) // .....
// method line 18
.method public virtual hidebysig newslot
instance default class DerivedProp MyMeth () cil managed
{
.custom instance void class System.Runtime.CompilerServices.NullableContextAttribute::'.ctor'(unsigned int8) = (01 00 01 00 00 ) // .....
.custom instance void [System.Runtime]System.Runtime.CompilerServices.PreserveBaseOverridesAttribute::.ctor() = (01 00 00 00 ) // ....
// Method begins at RVA 0x2186
.override class BaseCrash::MyMeth
// Code size 6 (0x6)
.maxstack 8
IL_0000: newobj instance void class DerivedProp::'.ctor'()
IL_0005: ret
} // end of method DerivedCrash`1::MyMeth
// method line 19
.method public hidebysig specialname rtspecialname
instance default void '.ctor' () cil managed
{
// Method begins at RVA 0x218d
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void class BaseCrash::'.ctor'()
IL_0006: nop
IL_0007: ret
} // end of method DerivedCrash`1::.ctor
} // end of class DerivedCrash`1
which looks like DerivedCrash<T>
implements MyMeth
of ICrash
implicitly, since the explicit override is just for BaseCrash
(which doesn't implement the interface ICrash
at all). which doesn't match my intuition about covariant returns based on https://github.com/davidwrighton/runtime/blob/main/docs/design/specs/Ecma-335-Augments.md#covariant-return-types
I'll take a look on Monday
@lambdageek, the behavior of CoreCLR in this case is correct.
The relevant facts as I see them.
BaseCrash
defines a abstract method BaseType MyMeth()
.DerivedCrash<T>
declares that it implements ICrash
This is an "explicit interface implementation" DerivedCrash<T>
overrides the virtual method BaseType BaseCrash.MyMeth()
with DerivedProp DerivedCrash<T>.MyMeth()
One of the somewhat obscure rules of how interface methods are invoked is that an interface method maps to a virtual method declaration, and then that virtual method is resolved to find an actual implementation. These are separate steps in the logical sense, and in the implementation of CoreCLR are actually implemented as a 2 pass algorithm first considering the interface method to virtual method declaration, followed by the virtual method declaration to virtual method implementation steps.
Another of the obscure rules is that implicit interface implementation will walk through to parent types to find implicit implementations. This is a backwards walk through the list of virtual methods implemented on the types.
So, what happens here is that when searching for implicit interface implementations of ICrash
on DerivedCrash<T>
, we find BaseType BaseCrash.MyMeth()
. Then when actually calling the interface method BaseType ICrash.MyMeth()
on some instance of DerivedCrash<T>
, it is resolved to BaseType BaseCrash.MyMeth()
, then we resolve that method to find its implementation, and find DerivedProp DerivedCrash<T>.MyMeth()
One of the somewhat obscure rules of how interface methods are invoked is that an interface method maps to a virtual method declaration, and then that virtual method is resolved to find an actual implementation. These are separate steps in the logical sense, and in the implementation of CoreCLR are actually implemented as a 2 pass algorithm first considering the interface method to virtual method declaration, followed by the virtual method declaration to virtual method implementation steps.
Got it. Thanks!
Update these notes are wrong. I misunderstood where the problem was in Mono. The issue is not that mono wasn't considering covariant returns during the first pass. it's that we weren't considering parent type's (abstract, but also non-abstract virtual) methods during the first pass.
(Just taking notes). Mono actually does two passes to resolve interface methods, too. I just didn't understand that this was a correctness requirement, not an implementation choice. It looks like the issue is that we only consider covariant returns in the first pass (matching an interface method to a virtual method) only when there is an explicit override. otherwise the second pass (matching a virtual method implementation to a virtual method declaration) that considers covariant returns. The first pass for explicit interface implementations (class explicitly declares it implements an interface) that have an implicit method implementation (ie without an overrride table row) only does exact signature matches. This is likely an oversight on my part from when we did the initial covariant returns support in Mono.
/cc @naricc
Is there an existing issue for this?
Describe the bug
A covariant class implementing an interface that matches a covariant member cannot be resolved by WASM.
WASM throws a runtime
TypeLoadException
when covariance is used in certain class/interface hierarchies. The same code works fine in regular desktop .Net 6/7.Expected Behavior
I expect WASM to handle covariance without crashing...
Steps To Reproduce
Minimal repro here: https://github.com/kaleidocore/CoCrashWASM
The code snippet above is all that is needed to repro the issue.
Exceptions (if any)
System.TypeLoadException
---> System.TypeLoadException: Could not find method '.ctor' due to a type load error: VTable setup of type CoCrashWASM.DerivedCrash'1[T] failed assembly:CoCrashWASM.Client.dll type:DerivedCrash'1 member:(null) at CoCrashWASM.Program.Main()
And for the async path:
---> System.TypeLoadException: Failed to load generic parameter 0 at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[d2](d 2& stateMachine)
at CoCrashWASM.Program.CrashMeAsync()
at CoCrashWASM.Program.Main()
.NET Version
7.0.100
Anything else?
.NET SDK: Version: 7.0.100 Commit: e12b7af219
Runtime Environment: OS Name: Windows OS Version: 10.0.22621 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\7.0.100\
Host: Version: 7.0.0 Architecture: x64 Commit: d099f075e4
.NET SDKs installed: 6.0.100 [C:\Program Files\dotnet\sdk] 6.0.108 [C:\Program Files\dotnet\sdk] 6.0.303 [C:\Program Files\dotnet\sdk] 7.0.100 [C:\Program Files\dotnet\sdk]
.NET runtimes installed: Microsoft.AspNetCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 6.0.8 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Other architectures found: x86 [C:\Program Files (x86)\dotnet] registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]
Environment variables: Not set
global.json file: Not found
Microsoft Visual Studio Professional 2022 Version 17.4.1 VisualStudio.17.Release/17.4.1+33110.190 Microsoft .NET Framework Version 4.8.09032
Installed Version: Professional
ASP.NET and Web Tools 17.4.326.54890 ASP.NET and Web Tools
Azure App Service Tools v3.0.0 17.4.326.54890 Azure App Service Tools v3.0.0
Azure Functions and Web Jobs Tools 17.4.326.54890 Azure Functions and Web Jobs Tools
C# Tools 4.4.0-6.22559.4+d7e8a398ef479a908e76bded82150c39251d0c9c C# components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.
Common Azure Tools 1.10 Provides common services for use by Azure Mobile Services and Microsoft Azure Tools.
Microsoft JVM Debugger 1.0 Provides support for connecting the Visual Studio debugger to JDWP compatible Java Virtual Machines
NuGet Package Manager 6.4.0 NuGet Package Manager in Visual Studio. For more information about NuGet, visit https://docs.nuget.org/
Razor (ASP.NET Core) 17.0.0.2246202+61cc048d36a3fc9246d2f04625988b19a18ab8f0 Provides languages services for ASP.NET Core Razor.
TypeScript Tools 17.0.10921.2001 TypeScript Tools for Microsoft Visual Studio
Visual Basic Tools 4.4.0-6.22559.4+d7e8a398ef479a908e76bded82150c39251d0c9c Visual Basic components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.
Visual F# Tools 17.4.0-beta.22512.4+525d5109e389341bb90b144c24e2ad1ceec91e7b Microsoft Visual F# Tools
Visual Studio IntelliCode 2.2 AI-assisted development for Visual Studio.