dotnet / corert

This repo contains CoreRT, an experimental .NET Core runtime optimized for AOT (ahead of time compilation) scenarios, with the accompanying compiler toolchain.
http://dot.net
MIT License
2.91k stars 508 forks source link

Can't build Web Assembly on Windows #8311

Open guptay1 opened 4 years ago

guptay1 commented 4 years ago

After following the instructions on https://github.com/morganbr/corert/blob/master/Documentation/how-to-build-WebAssembly.md and https://github.com/dotnet/corert/blob/master/Documentation/how-to-build-WebAssembly.md, I tried building a simple hello world app instead of using the sample. Added the nuget packages and set up of Emscripten. Not able to generate the .bc file and hence no wasm file can be generated using emcc. All pre-requisites and setups are working. Can someone point out how to do it from scratch for a hello world app without using the samples?

jkotas commented 4 years ago

cc @yowl

yowl commented 4 years ago

@guptay1 I dont know that you can create the wasm from a dotnet publish as I don't think there is a RID (-r option) that will work, but you can do something like, substituting for where you have built corert, and your project name:

"E:\GitHub\corert\Tools\dotnetcli\dotnet.exe" msbuild /m /ConsoleLoggerParameters:ForceNoAlign "/p:IlcPath=E:\GitHub\corert\bin\WebAssembly.wasm.Debug" "/p:Configuration=Debug" "/p:OSGroup=WebAssembly" "/p:Platform=wasm"  "/p:FrameworkLibPath=E:\GitHub\corert\bin\WebAssembly.wasm.Debug\lib" "/p:FrameworkObjPath=E:\GitHub\corert\bin\obj\WebAssembly.wasm.Debug\Framework"  /p:NativeCodeGen=wasm wasmh.csproj /t:LinkNative 

The csproj for this looks like:

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <TargetsUnix>true</TargetsUnix>
  </PropertyGroup>
  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
  <Import Project="$(IlcPath)\build\Microsoft.NETCore.Native.targets" />
</Project>
yowl commented 4 years ago

I've got net 5 preview installed but netcoreapp3.1 should also be fine.

guptay1 commented 4 years ago

@yowl I'm also using .NET 5 Preview. The LLVM bitcode file is getting created. I wasn't able to create it before. Now when I run the command:

emcc HelloWorld.bc -s WASM=1 -o HelloWasm.html

I get the following error in the browser console:

wasm streaming compile failed: TypeError: Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm' falling back to ArrayBuffer instantiation

Also tried the following emcc command from the documentation:

emcc HelloWasm.bc -s ALLOW_MEMORY_GROWTH=1 C:\corert\bin\WebAssembly.wasm.Debug\sdk\libPortableRuntime.bc C:\corert\bin\WebAssembly.wasm.Debug\sdk\libbootstrappercpp.bc -s WASM=1 -o HelloWasm.html

where actually libPortableRuntime.bc -> libPortableRuntime.a and libbootstrappercpp.bc->libbootstrappercpp.a after build.cmd. This emcc command gives many errors with a general format like:

error: undefined symbol: CoreLibNative_GetEnv (referenced by top-level compiled C/C++ code)
warning: _CoreLibNative_GetEnv may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library

Am I missing something here?

yowl commented 4 years ago

Hmm, the /t:LinkNative should have built the wasm with everything linked in for you, did it not do that?

guptay1 commented 4 years ago

@yowl No, it didn't

yowl commented 4 years ago

The 3 archive, .a files are in corert\bin\WebAssembly.wasm.Debug\sdk

guptay1 commented 4 years ago

@yowl Yes. I was pointing out that the documentation says that they are bitcode files

yowl commented 4 years ago

Ah ok, right its out of date. So under your project folder you dont have bin\wasm\Debug\net5.0\native ?

guptay1 commented 4 years ago

I do, but it's empty. There is no content in it when ideally it should've had the html, js and wasm file, Right?

yowl commented 4 years ago

can you run set and check that EMSDK is set?

yowl commented 4 years ago

I do, but it's empty. There is no content in it when ideally it should've had the html, js and wasm file, Right?

Yes it should

guptay1 commented 4 years ago

Yes, it is set.

Capture
yowl commented 4 years ago

That doesn't look right, you should have 3:

EMSDK=E:/GitHub/emsdk
EMSDK_NODE=E:\GitHub\emsdk\node\12.18.1_64bit\bin\node.exe
EMSDK_PYTHON=E:\GitHub\emsdk\python\3.7.4-pywin32_64bit\python.exe
guptay1 commented 4 years ago

That doesn't look right, you should have 3:

EMSDK=E:/GitHub/emsdk
EMSDK_NODE=E:\GitHub\emsdk\node\12.18.1_64bit\bin\node.exe
EMSDK_PYTHON=E:\GitHub\emsdk\python\3.7.4-pywin32_64bit\python.exe

Lemme Check

yowl commented 4 years ago

Without EMSDK env var, this condition will not be true and linking will not occur: <Exec Command="&quot;$(EMSDK)/upstream/emscripten/emcc.bat&quot; $(EmccArgs)" Condition="'$(NativeCodeGen)' == 'wasm' and '$(EMSDK)' != '' and '$(OS)' == 'Windows_NT'" /> (from Microsoft.NETCore.Native.targets)

guptay1 commented 4 years ago

Without EMSDK env var, this condition will not be true and linking will not occur: <Exec Command="&quot;$(EMSDK)/upstream/emscripten/emcc.bat&quot; $(EmccArgs)" Condition="'$(NativeCodeGen)' == 'wasm' and '$(EMSDK)' != '' and '$(OS)' == 'Windows_NT'" /> (from Microsoft.NETCore.Native.targets)

Added the EMSDK variable using emsdk_env.bat. I can generate the files in the bin now but on Running it on live server, it throws the following errors:

Capture1
yowl commented 4 years ago

Yeah, unfortunately Console is not working. I'm kind of hoping with the move to runtimelab and .net 5 this will get better. You can make it work by linking in some other stuff, its a bit of a hack. What you can try is running the link with an extra library libSystem.Native.a You can get this libSystem.Native.a from the artifacts from the runtime pipeline I think. https://dnceng.visualstudio.com/public/_build

However looking now I can't see the browser_wasm artifact. libSystem.Native.a.zip Here's an old one

guptay1 commented 4 years ago

Yeah, unfortunately Console is not working. I'm kind of hoping with the move to runtimelab and .net 5 this will get better. You can make it work by linking in some other stuff, its a bit of a hack. What you can try is running the link with an extra library libSystem.Native.a You can get this libSystem.Native.a from the artifacts from the runtime pipeline I think. https://dnceng.visualstudio.com/public/_build

However looking now I can't see the browser_wasm artifact. libSystem.Native.a.zip Here's an old one

So, adding this archive file to the command emcc HelloWasm.bc -s ALLOW_MEMORY_GROWTH=1 C:\corert\bin\WebAssembly.wasm.Debug\sdk\libPortableRuntime.a C:\corert\bin\WebAssembly.wasm.Debug\sdk\libbootstrappercpp.a -s WASM=1 -o HelloWasm.html will resolve the error?

yowl commented 4 years ago

mm, actually it's a bit more complicated you need basically all the dlls from the browser-wasm build which I can't find now. The easiest thing to do is to replace System.Console

#if CODEGEN_WASM
using System.Runtime.InteropServices;
using Console=BringUpTest.Console;
#endif

If CODEGEN_WASM is not set for your csproj you can define it, or just remove the #if if you dont care about running the code for other targets.

Then

#if CODEGEN_WASM
    internal class Console
    {
        private static unsafe void PrintString(string s)
        {
            int length = s.Length;
            fixed (char* curChar = s)
            {
                for (int i = 0; i < length; i++)
                {
                    TwoByteStr curCharStr = new TwoByteStr();
                    curCharStr.first = (byte)(*(curChar + i));
                    printf((byte*)&curCharStr, null);
                }
            }
        }

        internal static void WriteLine(string s)
        {
            PrintString(s);
            PrintString("\n");
        }

        internal static void WriteLine(string format, string p)
        {
            PrintString(string.Format(format, p));
            PrintString("\n");
        }
    }

    struct TwoByteStr
    {
        public byte first;
        public byte second;
    }

    [DllImport("*")]
    private static unsafe extern int printf(byte* str, byte* unused);
#endif
yowl commented 4 years ago

This is what I'm doing for the test projects.

guptay1 commented 4 years ago

This is what I'm doing for the test projects.

@yowl Can you point out one such test project that you might've made available on open source? I saw your snakewasm project but it didn't have any documentation to exactly understand what you actually did. Also, the hack above, I understand what you did there in the code but didn't understand the imports and CODEGEN_WASM. Can you elaborate more on that?

yowl commented 4 years ago

I'm referring to the CoreRT test projects that are enabled for Wasm, e.g. https://github.com/dotnet/corert/tree/master/tests/src/Simple/HelloWasm. For the imports, the normal Console is defined in System which already has a using so to make the compiler use the replacement Console, when it sees Console.WriteLine, you need to explicitly say which Console is wanted. System.Runtime.InteropServices is there because we are using the DllImport attribute.

CODEGEN_WASM is a constant that is defined in the project file, using something like

    <DefineConstants Condition="$(NativeCodeGen) == 'wasm'">$(DefineConstants);CODEGEN_WASM</DefineConstants>

in a PropertyGroup e.g.. https://github.com/dotnet/corert/blob/9fd573816ac81719f32f4b5635896a651a5c91ce/tests/src/Simple/SimpleTest.targets#L10

guptay1 commented 4 years ago

I'm referring to the CoreRT test projects that are enabled for Wasm, e.g. https://github.com/dotnet/corert/tree/master/tests/src/Simple/HelloWasm. For the imports, the normal Console is defined in System which already has a using so to make the compiler use the replacement Console, when it sees Console.WriteLine, you need to explicitly say which Console is wanted. System.Runtime.InteropServices is there because we are using the DllImport attribute.

CODEGEN_WASM is a constant that is defined in the project file, using something like

    <DefineConstants Condition="$(NativeCodeGen) == 'wasm'">$(DefineConstants);CODEGEN_WASM</DefineConstants>

in a PropertyGroup e.g..

https://github.com/dotnet/corert/blob/9fd573816ac81719f32f4b5635896a651a5c91ce/tests/src/Simple/SimpleTest.targets#L10

@yowl Did the above thing. The Console error is resolved but now I am getting a call stack size exceeded exception. Also, as you can see that the discussion is going too long and apparently there are too many details and too many hacks which aren't present in the documentation. Can you please write a sample app which won't take long ( a Hello World is also fine) based on the above discussion? Most of the things are discussed here so it will become pretty easy for me and the future readers who want to understand c# and wasm together. Or maybe point to some documentation which outlines this for new devs?

guptay1 commented 4 years ago

@yowl Any intuitions for how to clear the above call stack size exceeded error? What can be the possible cause of this and how to resolve?

yowl commented 4 years ago

You can start with https://github.com/yowl/WasmHelloWorld

guptay1 commented 4 years ago

You can start with https://github.com/yowl/WasmHelloWorld

@yowl This works. Couldn't figure out why my code wasn't working because I was doing exactly the same. Probably something I might be missing. I just need some more info to move a step further. If I want to use System.ServiceModel.Http package in my solution, then how can I go about it? I don't think directly adding the package to the project will do it. Since we are using corert, there must be some additions which have to be made to it?

Also, I have a WCF service which I want to add as a Connected service in my ConsoleApp. Is this supported in Wasm considering the calling to service and the fetching happens over http requests??

MichalStrehovsky commented 4 years ago

If I want to use System.ServiceModel.Http package in my solution, then how can I go about it?

I don't think WCF would even work when targeting Windows/Linux. Trying it with WASM might be a stretch. WCF has a custom implementation for .NET Native that we haven't hooked up into CoreRT yet. Besides some NuGet configuration kung-fu so that we pick up the UWP version of WCF (if that even works), we also lack support for the compile-time version of DispatchProxy (#279). WCF as-is will try to Reflection.Emit and that won't work.

guptay1 commented 4 years ago

If I want to use System.ServiceModel.Http package in my solution, then how can I go about it?

I don't think WCF would even work when targeting Windows/Linux. Trying it with WASM might be a stretch. WCF has a custom implementation for .NET Native that we haven't hooked up into CoreRT yet. Besides some NuGet configuration kung-fu so that we pick up the UWP version of WCF (if that even works), we also lack support for the compile-time version of DispatchProxy (#279). WCF as-is will try to Reflection.Emit and that won't work.

@MichalStrehovsky I'm targeting only wasm for now. Do you think that the support will be there when .NET 5 will be released?

MichalStrehovsky commented 4 years ago

@MichalStrehovsky I'm targeting only wasm for now. Do you think that the support will be there when .NET 5 will be released?

This repo has an experimental project in it with no official shipping schedule. The WASM support in it is even more experimental. I would look into the thing that is officially supported, which is Mono's WASM. Try it with the latest .NET 5 SDK and if it doesn't work, file an issue in the dotnet/runtime repo.

guptay1 commented 4 years ago

@MichalStrehovsky I'm targeting only wasm for now. Do you think that the support will be there when .NET 5 will be released?

This repo has an experimental project in it with no official shipping schedule. The WASM support in it is even more experimental. I would look into the thing that is officially supported, which is Mono's WASM. Try it with the latest .NET 5 SDK and if it doesn't work, file an issue in the dotnet/runtime repo.

@MichalStrehovsky The plan was to use mono-wasm. But it lacks documentation. The documentation which is present dates back to almost 3 years in which the mentioned things don't work or might've moved to some other location or made private. This repo had a much better documentation to get new devs get started with wasm easily and it seems straightforward with a bunch of hacks which make perfect sense. If you know about similar documentation for mono and where can I find it, can you share?

MichalStrehovsky commented 4 years ago

I suggest opening an issue about the lack of documentation in the dotnet/runtime repo. But yeah, the team's priority is Blazor so they mostly just care that Blazor-integrated bits work.

They have some non-Blazor samples here: https://github.com/dotnet/runtime/tree/master/src/mono/netcore/sample/wasm. AFAIK the WASM targeting runtime/SDK is only buildable on Linux because they're all Linux/mac people. (Once you build the WASM runtime, you can use Windows.)

guptay1 commented 4 years ago

I suggest opening an issue about the lack of documentation in the dotnet/runtime repo. But yeah, the team's priority is Blazor so they mostly just care that Blazor-integrated bits work.

They have some non-Blazor samples here: https://github.com/dotnet/runtime/tree/master/src/mono/netcore/sample/wasm. AFAIK the WASM targeting runtime/SDK is only buildable on Linux because they're all Linux/mac people. (Once you build the WASM runtime, you can use Windows.)

Yes, all I could find was Blazor whenever c# and wasm comes together. There is no direct c# to wasm translation being talked about except in corert. Guess I should open up an issue with dotnet/runtime repo requesting documentation.

Also, you are right. The targeting runtime/SDK needs macOS. I couldn't find the artifacts for other environment except macOS. The article and the github repo which mentions about it is also 3 years old which seems like a dead project as no new commits are made.