SteveSandersonMS / dotnet-wasi-sdk

Packages for building .NET projects as standalone WASI-compliant modules
518 stars 36 forks source link

Running out of memory quite quickly and seemingly not garbage collection #11

Open devedse opened 2 years ago

devedse commented 2 years ago

Hi :),

When running the following code I'm getting an error:

    public class Program
    {
        public static void Main(string[] args)
        {
            int cur = 0;
            while (true)
            {
                cur++;
                var bytes = new byte[10000];

                Console.WriteLine(cur);
            }
        }
    }
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
[wasm_trace_logger] * Assertion at /mnt/c/git/SteveSandersonMS/dotnet-wasi-sdk/modules/runtime/src/mono/mono/metadata/sgen-stw.c:76, condition `info->client_info.stack_start >= info->client_info.info.stack_start_limit && info->client_info.stack_start < info->client_info.info.stack_end' not met

image

This error also happens when calling GC.Collect() directly. Could it be that collecting garbage is not working yet?

I'd also like to understand what the difference is between compiling using Wasi.Sdk and actually including the same code in a Blazer Client app. In a Blazor Client app this code works fine.

JesperTreetop commented 2 years ago

I'd also like to understand what the difference is between compiling using Wasi.Sdk and actually including the same code in a Blazer Client app. In a Blazor Client app this code works fine.

Blazor either compiles .dll assemblies and uses a .NET runtime in wasm, or compiles everything to wasm with an emscripten-based compiler, which uses a different API. In either case, it uses additional Javascript files on the side to use browser API and the like to fill in the basic foundations.

WASI - WebAssembly System Interface - is a new infrastructure with solely WebAssembly, where there's a wasm runtime which provides a base layer for networking, working with files, getting the time, etc., much like the C runtime or system headers do on a natively compiled x86 or ARM binary.

I have no idea where or how the GC is implemented in either of these, but the platforms are radically different.

SteveSandersonMS commented 2 years ago

Could it be that collecting garbage is not working yet?

Yes, that is definitely the case (as a known limitation currently). This is a priority for us to resolve. Sorry for the inconvenience.

devedse commented 2 years ago

Thanks for the explanation. Is there a way to use the same browser JS files locally to run C# in through WASM with an actually working garbage collector?

To me it feels tough to have to maintain 2 different products which do some similar things.

JesperTreetop commented 2 years ago

Is there a way to use the same browser JS files locally to run C# in through WASM with an actually working garbage collector?

That works because there's a browser with a JavaScript engine, a DOM implementation and a pile of browser APIs. To reproduce that, you would need to pull in all those things, at which point this is no longer a project that compiles C# to WASM that fits the WASI platform and specification.

WASI is explicitly for using modules written in WebAssembly in a system, ie outside the browser environment, because it's really convenient to have a common binary form. Think of WASI as what you could compile the ASP.NET Core host application into, the part that hosts the server, accepts incoming HTTP requests and responds to them by shipping bytes to the client, whereas Blazor compiles the view code into WASM which the user interacts with once everything has been downloaded. There's not a lot of overlap there.

This is issue #11 of an early prototype. Blazor took years to go from initial prototype to shipping and supported, and WASI isn't even a solidified specification yet. A whole lot of things are going to be broken, missing, in progress or not even considered yet.

Christian-Schl commented 2 years ago

I found this api while debug blazor webassembly.

mono_wasm_enable_on_demand_gc https://github.com/mono/mono/blob/main/sdks/wasm/src/driver.c#L750

maybe it enough to call the api before starting the runtime

https://github.com/SteveSandersonMS/dotnet-wasi-sdk/blob/main/src/Wasi.Sdk/native/main.c

I have try it but was not able to complie the code

inkeliz commented 2 years ago

That is a huge limitation. I'm not familiar with C#, but so far, it works well (even creating the Mono trampoline, to export functions). But, when you have potentially a lot of List<T>, the WASM crashes.

However, I think there's three issues:

  1. The C# doesn't release unused content.
    • That seems to be the case and C# without GC is painful to use, since Managed and Unmanaged isn't "interchangeable" as other languages (such as Swift, Go..).
  2. The C# doesn't call Memoery.Grow from WebAssembly.
    • Because of that, even using unmanaged you can't grow your memory. I think it's possible to call grow from C (or even from C#), using assembly. I don't know how to get how much memory we have left, and I'm not sure how to update the internal counter from the runtime either (if required).
    • I hooked into the Grow function from the WASM interpreter (Wazero) and that is never called. Other languages call it when more memory is required.
  3. The C# is VERY inefficient and requires so much memory (which the WASM runtime can limit).
    • For comparison, the TinyGo code, requires just 16.12MB, Swift requires 18.80MB... However, C#, demands 524.28MB just to start (and no other Grow is called). I'm comparing languages that also have GC (of any kind).

Considering everything, currently, and unfortunately, dotnet-wasi-sdk is almost unusable for most cases.

SteveSandersonMS commented 2 years ago

However, C#, demands 524.28MB just to start

That's simply because of the --initial-memory=524288000 declaration in the build you're using, i.e., it allocates 500MB up front. This is entirely unnecessary, as it works with about 24MB.

I've already reduced it to 50MB in the next release and might shrink it further to 24MB which is about the minimum that can easily be supported.

dotnet-wasi-sdk is almost unusable for most cases.

Please be aware the implementation in this repo is only experimental (which is stated in the main README.md). It's not meant to be production-ready.

inkeliz commented 2 years ago

That's simply because of the --initial-memory=52428800 declaration, i.e., it allocates 500MB up front. This is entirely unnecessary, as it works with about 24MB.

That will only solve the issue if Grow is called. In that case, you can start as 10MB and require more memory when needed. Currently, without Grow, that will only crash things earlier. For instance, using Emscripten, you need to provide -s ALLOW_MEMORY_GROWTH in order to malloc/grow works. I'm not sure how Mono/C# works internally, but I'm sure that grow is not been called and then stuck on 500MB (or other value as you mention).

EDIT: Reduce the initial memory is good, but that requires grow to work.

andrewmd5 commented 1 year ago

Any updates or suggestions on how to workaround this? Hitting the issue when chaining a lot of methods / writing strings to memory (code)

SteveSandersonMS commented 1 year ago

Great news: as of .NET 8 Preview 4, the new wasi-experimental workload supersedes this repo, and has GC properly enabled.

In the coming weeks I'll be deprecating and archiving this repo, so would recommend you switch over to .NET 8 Preview 4 at that point. We'll soon be publishing a blog post explaining what's new and how to use the new workload.

inkeliz commented 1 year ago

Have any documentation of how to import/export functions and memories with .NET 8 Preview 4? Also, that uses AOT compilation, or that is an VM inside VM?

SteveSandersonMS commented 1 year ago

Have any documentation of how to import/export functions and memories with .NET 8 Preview 4?

There is limited support for [DllImport] but you'll need to wait for a later preview for fuller support, including exports. https://github.com/dotnet/runtime/issues/65895

Also, that uses AOT compilation, or that is an VM inside VM?

By default it's still the interpreter. We expect AOT to be supported by the time .NET 8 ships.

andrewmd5 commented 1 year ago

Great news: as of .NET 8 Preview 4, the new wasi-experimental workload supersedes this repo, and has GC properly enabled.

In the coming weeks I'll be deprecating and archiving this repo, so would recommend you switch over to .NET 8 Preview 4 at that point. We'll soon be publishing a blog post explaining what's new and how to use the new workload.

Is there a place to report issues? I'm seeing a potential regression from the package on this repo, specifically:

[MONO] warning: Process terminated.
[MONO] warning: Couldn't find a valid ICU package installed on the system. Please install libicu (or icu-libs) using your package manager and try again. Alternatively you can set the configuration flag System.Globalization.Invariant to true if you want to run with no globalization support. Please see https://aka.ms/dotnet-missing-libicu for more information.

Among other things

SteveSandersonMS commented 1 year ago

wasi-experimental is now part of the core runtime, so any issues with it can be reported at https://github.com/dotnet/runtime. Bear in mind that it is "experimental" so issues will not attract the same level of urgency as issues in the fully-released runtime.