Skip to content
This repository has been archived by the owner on Nov 1, 2020. It is now read-only.

Can't build Web Assembly on Windows #8311

Open
guptay1 opened this issue Sep 11, 2020 · 33 comments
Open

Can't build Web Assembly on Windows #8311

guptay1 opened this issue Sep 11, 2020 · 33 comments

Comments

@guptay1
Copy link

guptay1 commented Sep 11, 2020

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
Copy link
Member

jkotas commented Sep 11, 2020

cc @yowl

@yowl
Copy link
Contributor

yowl commented Sep 11, 2020

@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
Copy link
Contributor

yowl commented Sep 11, 2020

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

@guptay1
Copy link
Author

guptay1 commented Sep 11, 2020

@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
Copy link
Contributor

yowl commented Sep 11, 2020

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

@guptay1
Copy link
Author

guptay1 commented Sep 11, 2020

@yowl No, it didn't

@yowl
Copy link
Contributor

yowl commented Sep 11, 2020

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

@guptay1
Copy link
Author

guptay1 commented Sep 11, 2020

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

@yowl
Copy link
Contributor

yowl commented Sep 11, 2020

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

@guptay1
Copy link
Author

guptay1 commented Sep 11, 2020

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
Copy link
Contributor

yowl commented Sep 11, 2020

can you run set and check that EMSDK is set?

@yowl
Copy link
Contributor

yowl commented Sep 11, 2020

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
Copy link
Author

guptay1 commented Sep 11, 2020

Yes, it is set.

Capture

@yowl
Copy link
Contributor

yowl commented Sep 11, 2020

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
Copy link
Author

guptay1 commented Sep 11, 2020

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
Copy link
Contributor

yowl commented Sep 11, 2020

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
Copy link
Author

guptay1 commented Sep 11, 2020

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
Copy link
Contributor

yowl commented Sep 11, 2020

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
Copy link
Author

guptay1 commented Sep 11, 2020

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
Copy link
Contributor

yowl commented Sep 11, 2020

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
Copy link
Contributor

yowl commented Sep 11, 2020

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

@guptay1
Copy link
Author

guptay1 commented Sep 14, 2020

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
Copy link
Contributor

yowl commented Sep 14, 2020

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

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

@guptay1
Copy link
Author

guptay1 commented Sep 15, 2020

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

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

@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
Copy link
Author

guptay1 commented Sep 21, 2020

@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
Copy link
Contributor

yowl commented Sep 21, 2020

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

@guptay1
Copy link
Author

guptay1 commented Sep 21, 2020

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
Copy link
Member

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
Copy link
Author

guptay1 commented Sep 22, 2020

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
Copy link
Member

@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
Copy link
Author

guptay1 commented Sep 22, 2020

@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
Copy link
Member

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
Copy link
Author

guptay1 commented Sep 22, 2020

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants