Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emscripten doesn't truly produce standalone/WASI output when requested #20484

Open
jnickg opened this issue Oct 18, 2023 · 5 comments
Open

Emscripten doesn't truly produce standalone/WASI output when requested #20484

jnickg opened this issue Oct 18, 2023 · 5 comments

Comments

@jnickg
Copy link
Contributor

jnickg commented Oct 18, 2023

Overview

Emscripten compiler chain doesn't seem to honor -sSTANDALONE_WASM=1 -sPURE_WASI=1

  • EMSDK version 3.1.47
  • Related ticket in wasmer project: link

Description

See related wasmer ticket above for more info.

When compiling a toy "hello world" project using the above flags, I am able to compile an example program say_example, and run via wasmer, using the command wasmer run say_example.wasm <my_message>, and I get the expected output.

However, when compiling an executable and linking against gtest (which I'm building from source in this project & implementing my own main function), I instead get the following error when running wasmer run test_say.wasm:

error: Instantiation failed
╰─▶ 1: Error while importing "env"."invoke_iii": unknown import. Expected Function(FunctionType { params: [I32, I32, I32], results: [I32] })

If I inspect test_say.js I see the wasmImports variable maps invoke_iii to a JS function implemented further down in the file. This seems to suggest that Emscripten isn't producing truly standalone webassembly.

Source Code

say.hpp

#pragma once
#include <string>
namespace wpe {
size_t say(const std::string& message);
} // namespace wpe

say.cpp

#include <wpe/say.hpp>
#include <iostream>
namespace wpe {
size_t say(const std::string& message) {
    std::cout << message << std::endl;
    return message.length();
}
} // namespace wpe

say_example.cpp

#include <wpe/say.hpp>

int main(int argc, char** argv) {
    std::string what;
    for (int i = 1; i < argc; ++i) {
        what += argv[i];
        if (i < argc - 1) {
            what += " ";
        }
    }
    wpe::say(what);
    return 0;
}

test_say.cpp

#include <gtest/gtest.h>
#include <wpe/say.hpp>
TEST(test_wpe, say) {
    std::string what = "Hello, world!";
    auto len = wpe::say(what);
    TEST_EQ(what.length(), len);
}

test_main.cpp

#include <gtest/gtest.h>
int main(int argc, char** argv) {
    ::testing::InitGoogleTest();
    return RUN_ALL_TESTS();
}
@kripken
Copy link
Member

kripken commented Oct 19, 2023

invoke_iii are used for C++ exceptions and longjmp. To avoid using those custom imports, build with emcc -fwasm-exceptions (though then you need to make sure you run on a recent enough wasm VM to have wasm exceptions support).

As context, even in PURE_WASI mode we will emit custom imports when we have no other option, and that is the case here: before wasm exceptions there was no good way to do C++ exceptions or longjmp.

@RReverser
Copy link
Collaborator

though then you need to make sure you run on a recent enough wasm VM to have wasm exceptions support

Heh more precisely no standalone engine supports them yet. Node.js is the only implementation that comes to mind that has WASI and Wasm exceptions, but it's somewhat atypical to use it for server-side WASI.

@jnickg
Copy link
Contributor Author

jnickg commented Oct 20, 2023

@kripken Thank you for the response. I tried adding that flag and now get the following undefined-symbol warnings:

warning: undefined symbol: __cxa_find_matching_catch_2 (referenced by root reference (e.g. compiled C/C++ code))
warning: undefined symbol: __cxa_find_matching_catch_3 (referenced by root reference (e.g. compiled C/C++ code))
warning: undefined symbol: __cxa_find_matching_catch_6 (referenced by root reference (e.g. compiled C/C++ code))
warning: undefined symbol: __resumeException (referenced by root reference (e.g. compiled C/C++ code))
warning: undefined symbol: llvm_eh_typeid_for (referenced by root reference (e.g. compiled C/C++ code))
em++: warning: warnings in JS library compilation [-Wjs-compiler]
Traceback (most recent call last):
  File "/Users/jgiampietro/code/github.com/emscripten-core/emsdk/upstream/emscripten/em++.py", line 14, in <module>
    sys.exit(emcc.main(sys.argv))
  File "/Users/jgiampietro/code/github.com/emscripten-core/emsdk/python/3.9.2_64bit/lib/python3.9/contextlib.py", line 79, in inner
    return func(*args, **kwds)
  File "/Users/jgiampietro/code/github.com/emscripten-core/emsdk/upstream/emscripten/emcc.py", line 4483, in main
    ret = run(args)
  File "/Users/jgiampietro/code/github.com/emscripten-core/emsdk/upstream/emscripten/emcc.py", line 1352, in run
    phase_post_link(options, state, wasm_target, wasm_target, target, js_syms)
  File "/Users/jgiampietro/code/github.com/emscripten-core/emsdk/python/3.9.2_64bit/lib/python3.9/contextlib.py", line 79, in inner
    return func(*args, **kwds)
  File "/Users/jgiampietro/code/github.com/emscripten-core/emsdk/upstream/emscripten/emcc.py", line 3192, in phase_post_link
    phase_emscript(options, in_wasm, wasm_target, memfile, js_syms)
  File "/Users/jgiampietro/code/github.com/emscripten-core/emsdk/python/3.9.2_64bit/lib/python3.9/contextlib.py", line 79, in inner
    return func(*args, **kwds)
  File "/Users/jgiampietro/code/github.com/emscripten-core/emsdk/upstream/emscripten/emcc.py", line 3216, in phase_emscript
    emscripten.run(in_wasm, wasm_target, final_js, memfile, js_syms)
  File "/Users/jgiampietro/code/github.com/emscripten-core/emsdk/upstream/emscripten/emscripten.py", line 975, in run
    emscript(in_wasm, out_wasm, outfile_js, memfile, js_syms)
  File "/Users/jgiampietro/code/github.com/emscripten-core/emsdk/upstream/emscripten/emscripten.py", line 434, in emscript
    module = create_module(receiving, metadata, forwarded_json['librarySymbols'])
  File "/Users/jgiampietro/code/github.com/emscripten-core/emsdk/upstream/emscripten/emscripten.py", line 866, in create_module
    assert not metadata.invokeFuncs, "invoke_ functions exported but exceptions and longjmp are both disabled"
AssertionError: invoke_ functions exported but exceptions and longjmp are both disabled

If I ignore them, then as expected, running with both wasmtime and wasmer give similar errors about the exceptions proposal not yet being supported.

What's the solution here? It seems like I can either try to unilaterally disable exceptions project wide (which might be possible), or just wait until the standalone wasm engines support this proposal. Longer term I'd prefer if the wasm engines supported wasm exceptions, but is there anything I can do in the interim?

@RReverser
Copy link
Collaborator

RReverser commented Oct 20, 2023

For now building without exceptions is the only way, yes.

For what it's worth, .NET NativeAOT-LLVM project worked around this limitation by generating LLVM IR that does manual unwind instead (condition on global variable after every potentially-throwing call, similar to what Asyncify does), and I can image Binaryen could do something similar to polyfill native Wasm exceptions for unsupported engines, but I suspect there is not enough interest for something this complex at the moment? @kripken

@kripken
Copy link
Member

kripken commented Oct 23, 2023

warning: undefined symbol: __cxa_find_matching_catch_2 (referenced by root reference (e.g. compiled C/C++ code))

That shouldn't happen with wasm exceptions. Were perhaps some object files, or some library, still built with the old way? Note that -fwasm-exceptions (and -fexceptions) matters at compile time, not only link.

I can image Binaryen could do something similar to polyfill native Wasm exceptions for unsupported engines

Yes, basically what you said - possible in theory, but since engines are getting support as we speak, it hasn't been a priority. I wouldn't turn down a PR though.

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

No branches or pull requests

3 participants