emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.35k stars 3.25k forks source link

IGNORE_MISSING_MAIN compiler has no effect #22132

Closed orsinium closed 6 days ago

orsinium commented 1 week ago

Summary: I set IGNORE_MISSING_MAIN option but compilation still fails with undefined symbol: main.

Code (main.cpp):

#include <stdint.h>

__attribute__((export_name("boot"))) void
boot()
{
}

Build command:

em++ main.cpp -s IGNORE_MISSING_MAIN=1 -o main.wasm

Output:

wasm-ld: error: /home/gram/Documents/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/libstandalonewasm-nocatch.a(__main_void.o): undefined symbol: main
em++: error: '/home/gram/Documents/emsdk/upstream/bin/wasm-ld -o main.wasm /tmp/emscripten_temp_c6qyt8v0/main_0.o -L/home/gram/Documents/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten /home/gram/Documents/emsdk/upstream/emscripten/cache/sysroot/lib/wasm32-emscripten/crt1.o -lGL-getprocaddr -lal -lhtml5 -lstandalonewasm-nocatch -lstubs-debug -lc-debug -ldlmalloc -lcompiler_rt -lc++-noexcept -lc++abi-debug-noexcept -lsockets -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr /tmp/tmpbxg6b8c_libemscripten_js_symbols.so --strip-debug --export=emscripten_stack_get_end --export=emscripten_stack_get_free --export=emscripten_stack_get_base --export=emscripten_stack_get_current --export=emscripten_stack_init --export=_emscripten_stack_restore --export-if-defined=__start_em_asm --export-if-defined=__stop_em_asm --export-if-defined=__start_em_lib_deps --export-if-defined=__stop_em_lib_deps --export-if-defined=__start_em_js --export-if-defined=__stop_em_js --export-table -z stack-size=65536 --no-growable-memory --initial-heap=16777216 --stack-first --table-base=1' failed (returned 1)

The problem I'm trying to solve is that if I define an empty main function, emscripten will insert proc_exit and unreachable at the end of _start. However, in my case, I need the wasm binary to be a "reactor": to export callbacks that are explicitly called by the runtime when needed. The EXIT_RUNTIME is supposed to do this, but then I get the following error:

em++: error: explicitly setting EXIT_RUNTIME not compatible with STANDALONE_WASM.  EXIT_RUNTIME will always be True for programs (with a main function) and False for reactors (not main function).

So, I tried to remove main and set IGNORE_MISSING_MAIN but then I get the error described above.

em++ --version:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.61 (67fa4c16496b157a7fc3377afd69ee0445e8a6e3)
sbc100 commented 1 week ago

I think the simplest solution here is to use the --no-entry command line flag.

Alternatively you can pass -sEXPORTED_FUNCTIONS=_boot (any list doesn't doesn't include main will work here).

orsinium commented 1 week ago

I tried to use --no-entry and it (supposedly) caused some issues with pocketpy: https://github.com/pocketpy/pocketpy/issues/279

@szdytom said:

we cannot set --no-entry for a C++ source because the linker needs to call the constructor of globals.

https://github.com/pocketpy/pocketpy/issues/279#issuecomment-2179641284

UPD: And I think they are right: compiling with -Wl,--no-entry seems to produce a binary without the _start export. Only some custom emscripten_stack_* exports.

orsinium commented 1 week ago

Also, I tried setting EXPORTED_FUNCTIONS. It does compile without main but then the binary has env.__main_argc_argv import. I'm not sure what it is and why it is there now. Searching online gives only one hit:

https://bugs.archlinux.org/task/75269

UPD: Turns out, it compiles not because I set EXPORTED_FUNCTIONS but because I also set ERROR_ON_UNDEFINED_SYMBOLS=0.

sbc100 commented 1 week ago

When you use --no-entry you also need to call the _initialize export to run the static constructors. This need to be called before you call any other export. This is part of the "reactor" ABI.

sbc100 commented 1 week ago

I would strongly suggest --no-extry as the solution here since that tells emscripten you are building a reactor.

I would stringly recommend against using ERROR_ON_UNDEFINED_SYMBOLS=0 as that can lead to undefined symbols at rutime.

orsinium commented 1 week ago

There is no _initialize export as well.

Code:

#include "../vendor/pocketpy/include/pocketpy.h"

using namespace pkpy;

#define WASM_EXPORT(NAME) __attribute__((export_name(NAME)))

WASM_EXPORT("boot")
void boot()
{
    VM *vm = new VM();
    vm->exec("a = [1, 2, 3]");
}

Build command:

em++ ./src/main.cpp pocketpy/*.o -Ivendor/pocketpy/include -fignore-exceptions -frtti -Os -s STANDALONE_WASM -Wl,--no-entry -o main.wasm

wasm-objdump -x --section=export ./main.wasm:

Export[5]:
 - memory[0] -> "memory"
 - func[8] <boot> -> "boot"
 - table[0] -> "__indirect_function_table"
 - func[2161] <_emscripten_stack_restore> -> "_emscripten_stack_restore"
 - func[2162] <emscripten_stack_get_current> -> "emscripten_stack_get_current"
szdytom commented 1 week ago

I'm no expert in WASM, but maybe you should add -mexec-model=reactor flag? I'm no sure. Here are some reference I found that might be related to this topic:

orsinium commented 1 week ago

How can I set this flag through emscripten? I know that it should generally work but AFAIK it's the clang's flag, not emsdk's.

orsinium commented 1 week ago
$ em++ ./src/main.cpp pocketpy/*.o -Ivendor/pocketpy/include -fignore-exceptions -frtti -Os -s STANDALONE_WASM -Wl,--no-entry -mexec-model=reactor -o main.wasm
clang++: error: unsupported option '-mexec-model=' for target 'wasm32-unknown-emscripten'
em++: error: '/home/gram/Documents/emsdk/upstream/bin/clang++ -target wasm32-unknown-emscripten -fignore-exceptions -mllvm -combiner-global-alias-analysis=false -mllvm -enable-emscripten-sjlj -mllvm -disable-lsr --sysroot=/home/gram/Documents/emsdk/upstream/emscripten/cache/sysroot -DEMSCRIPTEN -Xclang -iwithsysroot/include/fakesdl -Xclang -iwithsysroot/include/compat -Ivendor/pocketpy/include -fignore-exceptions -frtti -Os -mexec-model=reactor ./src/main.cpp -c -o /tmp/emscripten_temp_dnw4pz3k/main_0.o' failed (returned 1)

I know that the flags that wasm4 uses for compiling wasm binaries work great, and they indeed set -mexec-model=reactor: https://github.com/aduros/wasm4/blob/main/cli/assets/templates/c/Makefile#L24

However, they use wasi-sdk instead of emscripten. I tried to use wasi-sdk earlier as well to compile pocketpy, but so far emscripten brought me seemingly closer to success.

sbc100 commented 1 week ago

Sorry the --no-entry flag is an emcc flag.. you don't need to use -Wl, before it.

e.g:

$ ./emcc -Os test/hello_world.c -o out.wasm --no-entry
$ wasm-objdump -x out.wasm | grep _init
 - func[0] sig=0 <_initialize>
 - func[0] <_initialize> -> "_initialize"
  - elem[1] = ref.func:0 <_initialize>
 - func[0] size=2 <_initialize>

This tells emscripten (not just the linker) that you are building a reactor.

orsinium commented 6 days ago

It works! Thank you! You both were very helpful, and you made Python on Firefly Zero possible. FOSS needs more people like you :)