corert icon indicating copy to clipboard operation
corert copied to clipboard

Can't build Web Assembly on Windows

Open guptay1 opened this issue 5 years ago • 33 comments

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?

guptay1 avatar Sep 11 '20 13:09 guptay1

cc @yowl

jkotas avatar Sep 11 '20 14:09 jkotas

@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 avatar Sep 11 '20 16:09 yowl

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

yowl avatar Sep 11 '20 16:09 yowl

@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?

guptay1 avatar Sep 11 '20 18:09 guptay1

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

yowl avatar Sep 11 '20 19:09 yowl

@yowl No, it didn't

guptay1 avatar Sep 11 '20 19:09 guptay1

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

yowl avatar Sep 11 '20 19:09 yowl

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

guptay1 avatar Sep 11 '20 19:09 guptay1

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

yowl avatar Sep 11 '20 19:09 yowl

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?

guptay1 avatar Sep 11 '20 19:09 guptay1

can you run set and check that EMSDK is set?

yowl avatar Sep 11 '20 19:09 yowl

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

yowl avatar Sep 11 '20 19:09 yowl

Yes, it is set.

Capture

guptay1 avatar Sep 11 '20 19:09 guptay1

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

yowl avatar Sep 11 '20 19:09 yowl

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

guptay1 avatar Sep 11 '20 19:09 guptay1

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)

yowl avatar Sep 11 '20 19:09 yowl

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

guptay1 avatar Sep 11 '20 19:09 guptay1

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

yowl avatar Sep 11 '20 20:09 yowl

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?

guptay1 avatar Sep 11 '20 20:09 guptay1

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 avatar Sep 11 '20 20:09 yowl

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

yowl avatar Sep 11 '20 20:09 yowl

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?

guptay1 avatar Sep 14 '20 06:09 guptay1

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 avatar Sep 14 '20 13:09 yowl

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 avatar Sep 15 '20 09:09 guptay1

@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?

guptay1 avatar Sep 21 '20 05:09 guptay1

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

yowl avatar Sep 21 '20 13:09 yowl

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??

guptay1 avatar Sep 21 '20 17:09 guptay1

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 avatar Sep 22 '20 07:09 MichalStrehovsky

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?

guptay1 avatar Sep 22 '20 07:09 guptay1

@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 avatar Sep 22 '20 07:09 MichalStrehovsky