wasmerio / wasmer

🚀 The leading Wasm Runtime supporting WASIX, WASI and Emscripten
https://wasmer.io
MIT License
18.63k stars 798 forks source link

Emscripten runner erroneously cannot be used #4261

Open jnickg opened 1 year ago

jnickg commented 1 year ago

Describe the bug

(Follow-up from a discussion in the #newbie channel on Discord)

When compiling toy programs to be run with wasmer via Emscripten, I am able to accomplish it using the below Emscripten build/link flags:

            -sWASM=1
            -sSTANDALONE_WASM=1
            -sPURE_WASI=1

However, when building googletest from source and linking against that (implementing my own main for UTs, if that matters), I get the following error when trying to run a hello-world style test program:

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

Following the advice of @theduke, when using a .toml file to try and coerce wasmer to detecting an Emscripten build, I instead get the following error:

jgiampietro@Jamess-MacBook-Pro-2 bin % wasmer run .
error: Atom "test_wpe" is not an emscripten module

This seems to suggest that some combination of the following is occurring:

...So I'm filing this bug to suss out whether there's anything that should be done on the wasmer side to support this. We use Emscripten because we support large C++ code bases, and have numerous frontend applications. I'd like to use wasmer to simplify CI use cases (like running wasm-compiled unit tests) and to pave the way for revamping backend infrastructure to use Webassembly/wasmer/etc.

Wasmer version (installed via homebrew):

wasmer 4.2.2 (??????? 2023-10-05)
binary: wasmer-cli
commit-hash: 
commit-date: 2023-10-05
host: aarch64-apple-darwin
compiler: cranelift

Steps to reproduce

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

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);
}

If more code is needed let me know, and I can clean up the working repo and zip it.

Attached are the compiled output binaries from Emscripten, given the above source code and earlier-mentioned flags: test_wpe.zip

When using a toml file to invoke, here is what I used (which I manually added to the bin directory of my build output)

[package]
name = "wpe/wpe_wasm"
version = "0.0.1"
description = "TBD"
entrypoint = "main"

[[module]]
name = "test_wpe"
source = "./test_wpe.wasm"

[[command]]
name = "main"
module = "test_wpe"
runner = "https://webc.org/runner/emscripten"

Expected behavior

Output should be analogous to the natively-compiled version:

jgiampietro@Jamess-MacBook-Pro-2 wasm_package_example % ./build/Debug/native/bin/test_wpe 
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from test_wpe
[ RUN      ] test_wpe.say
Hello, world!
[       OK ] test_wpe.say (0 ms)
[----------] 1 test from test_wpe (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

Actual behavior

The below error is emitted, which seems to be an Emscripten builtin:

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

Of significant note is that this is JS glue code Emscripten uses to support exceptions. If I compile with the -fwasm-exceptions flag this problem goes away, but then I am left with the following error:

error: Unable to compile "/Users/jgiampietro/code/git.corp.adobe.com/jgiampietro/wasm_package_example/build/Debug/web/bin/test_wpe.wasm"
╰─▶ 1: Validation error: exceptions proposal not enabled (at offset 0x3a40)

Cross-linking with #3307 because of that. Is there any way to prioritize exception support and/or get visibility into the timeline for its support? I've worked in embedded systems where exceptions aren't supported, even that is rare nowadays, as exceptions are so pervasive, and required for so many third-party libraries.

Michael-F-Bryan commented 12 months ago

If you are running into issues when doing wasmer run some_emscripten_module.wasm, it's probably a bug in the wasmer_emscripten::is_emscripten_module() check used by the wasmer run command:

https://github.com/wasmerio/wasmer/blob/9127dde836d3fd12dedfa74054508bb66ddd49df/lib/cli/src/commands/run.rs#L146-L152

davidar commented 2 months ago

It looks like is_emscripten_module is looking for imports that emscripten (no longer?) provides:

https://github.com/wasmerio/wasmer/blob/94edff8e2bb4a1e7cad5f2979e04d3634a3e867f/lib/emscripten/src/utils.rs#L14-L27

$ wasmer inspect llvm-box.wasm 
Type: wasm
Size: 59.4 MB
Imports:
  Functions:
    "wasi_snapshot_preview1"."fd_close": [I32] -> [I32]
    "env"."invoke_ii": [I32, I32] -> [I32]
    "wasi_snapshot_preview1"."proc_exit": [I32] -> []
    "wasi_snapshot_preview1"."fd_write": [I32, I32, I32, I32] -> [I32]
    "env"."__syscall_unlinkat": [I32, I32, I32] -> [I32]
    "wasi_snapshot_preview1"."fd_read": [I32, I32, I32, I32] -> [I32]
    "wasi_snapshot_preview1"."fd_fdstat_get": [I32, I32] -> [I32]
    "env"."invoke_iiii": [I32, I32, I32, I32] -> [I32]
    "wasi_snapshot_preview1"."environ_sizes_get": [I32, I32] -> [I32]
    "wasi_snapshot_preview1"."clock_time_get": [I32, I64, I32] -> [I32]
    "env"."emscripten_notify_memory_growth": [I32] -> []
    "wasi_snapshot_preview1"."args_get": [I32, I32] -> [I32]
    "env"."_emscripten_throw_longjmp": [] -> []
    "env"."__syscall_utimensat": [I32, I32, I32, I32] -> [I32]
    "wasi_snapshot_preview1"."args_sizes_get": [I32, I32] -> [I32]
    "env"."__syscall_symlink": [I32, I32] -> [I32]
    "env"."__syscall_statfs64": [I32, I32, I32] -> [I32]
    "env"."__syscall_renameat": [I32, I32, I32, I32] -> [I32]
    "env"."__syscall_rmdir": [I32] -> [I32]
    "env"."__syscall_readlinkat": [I32, I32, I32, I32] -> [I32]
    "env"."__syscall_getdents64": [I32, I32, I32] -> [I32]
    "env"."__call_sighandler": [I32, I32] -> []
    "env"."invoke_vi": [I32, I32] -> []
    "wasi_snapshot_preview1"."fd_pread": [I32, I32, I32, I64, I32] -> [I32]
    "env"."__syscall_mkdirat": [I32, I32, I32] -> [I32]
    "wasi_snapshot_preview1"."fd_seek": [I32, I64, I32, I32] -> [I32]
    "env"."__syscall_getcwd": [I32, I32] -> [I32]
    "env"."__syscall_ftruncate64": [I32, I64] -> [I32]
    "env"."__syscall_lstat64": [I32, I32] -> [I32]
    "env"."__syscall_newfstatat": [I32, I32, I32, I32] -> [I32]
    "env"."__syscall_stat64": [I32, I32] -> [I32]
    "env"."__syscall_fchown32": [I32, I32, I32] -> [I32]
    "env"."invoke_vii": [I32, I32, I32] -> []
    "env"."__syscall_fchmod": [I32, I32] -> [I32]
    "env"."_dlinit": [I32] -> []
    "env"."_dlsym_js": [I32, I32] -> [I32]
    "env"."_dlopen_js": [I32] -> [I32]
    "env"."__syscall_dup3": [I32, I32, I32] -> [I32]
    "env"."__syscall_chmod": [I32, I32] -> [I32]
    "env"."__syscall_chdir": [I32] -> [I32]
    "env"."__syscall_faccessat": [I32, I32, I32, I32] -> [I32]
    "wasi_snapshot_preview1"."environ_get": [I32, I32] -> [I32]
  Memories:
  Tables:
  Globals:
Exports:
  Functions:
    "__main_argc_argv": [I32, I32] -> [I32]
    "free": [I32] -> []
    "malloc": [I32] -> [I32]
    "_start": [] -> []
    "emscripten_builtin_memalign": [I32, I32] -> [I32]
    "setThrew": [I32, I32] -> []
    "stackSave": [] -> [I32]
    "stackRestore": [I32] -> []
  Memories:
    "memory": not shared (256 pages..32768 pages)
  Tables:
    "__indirect_function_table": FuncRef (34828..34828)
  Globals: