In standalone wasm modules we don't import the memory or the table. We do export the memory by default, but not the table.
Should we always export it, or optionally perhaps?
We already have a linker flag for that: -Wl,--export-table. However we currently have whitelist of supported linker flags, we should probably switch the balcklist instead.
We went back and forth on the blacklist/whitelist issue I think, and I don't remember the details... maybe it's simplest to just add that to the current whitelist?
On my side the --export-table flag worked because it was part of the SUPPORTED_LLD_LINKER_FLAG_PREFIXES list in emcc.py. I needed to adjust the SUPPORTED_LLD_LINKER_FLAGS list locally to allow the --growable-table flag to work too.
Seems I am still unable to use --export-table. Using 2.0.0 (d81f400831a22f04901ab149f29d3caf77979bf6).
Without this, the table must always be imported from host with -Wl,--import-table in dynamic linking case as far as I see.
Have not seen --growable-table flag anywhere (even google), but seems emscripten enforces a fixed size by default and allows using -s ALLOW_MEMORY_GROWTH=1 to allow growing up to a point, which is nice.
Just an FYI, with the latest version of Emscripten (1.40.1), I don't need to adjust the emcc.py file anymore.
Yes -Wl,--export-table now works with standalone mode.
@Rochet2 is your issue something different? Does -Wl,--export-table -s STANDALONE_WASM not work as expected for you?
For normal mode (non-standalone) we always create the table in JS and import it into the wasm module. We don't support anyother mode that I know of.
@sbc100 Here is a full example.
The C file:
#include <stdlib.h>
int foo() {
return 42;
}
int bar() {
return 9001;
}
__attribute__((used))
int main() {
int (*indirectly_called)() = rand()%2 == 0 ? &foo : &bar;
indirectly_called();
return 0;
}
Command used to build:
docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) emscripten/emsdk emcc example.c -o example.wasm -Os -Wl,--export-table,--no-entry,--export,malloc -s ALLOW_MEMORY_GROWTH=1 -s STANDALONE_WASM
The generated .wasm converted to wat looks like this:
(module
(type $t0 (func (result i32)))
(type $t1 (func (param i32) (result i32)))
(type $t2 (func (param i32)))
(import "env" "emscripten_notify_memory_growth" (func $env.emscripten_notify_memory_growth (type $t2)))
;;;; <A bunch of functions here>
(table $T0 3 3 funcref)
(memory $memory 256 32768)
(global $g0 (mut i32) (i32.const 5244592))
(export "memory" (memory 0))
(export "__original_main" (func $__original_main))
(export "malloc" (func $malloc))
(elem $e0 (i32.const 1) $f6 $f2)
(data $d0 (i32.const 1552) "\b0\06P"))
As you may see, both flags were used and the table is not exported even if one exists.
I would expect a table to be exported even if one isn't used though.
Here is the version output:
docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) emscripten/emsdk emcc --version
emcc (Emscripten gcc/clang-like replacement) 2.0.0 (d81f400831a22f04901ab149f29d3caf77979bf6)
Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt)
This is free and open source software under the MIT license.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
I tested your code on my side and the optimization flag seems to be the issue. If I change it from -Os to -O1 I see the following:
(module
(type $type0 (func (result i32)))
(type $type1 (func (param i32) (result i32)))
(type $type2 (func (param i32)))
(import "env" "emscripten_notify_memory_growth" (func $env.emscripten_notify_memory_growth (;0;) (param i32)))
(table $__indirect_function_table (;0;) 3 3 anyfunc)
(memory $memory (;0;) 256 32768)
(global $global0 (mut i32) (i32.const 5244576))
(global $__data_end (;1;) i32 (i32.const 1532))
(export "memory" (memory $memory))
(export "__indirect_function_table" (table $__indirect_function_table))
(export "__original_main" (func $__original_main))
(export "__errno_location" (func $__errno_location))
(export "stackSave" (func $stackSave))
(export "stackRestore" (func $stackRestore))
(export "stackAlloc" (func $stackAlloc))
(export "malloc" (func $malloc))
(export "__data_end" (global $__data_end))
(export "__growWasmMemory" (func $__growWasmMemory))
(elem (i32.const 1) $func3 $func2)
...
It now has an export for __indirect_function_table.
Great :)
Without the optimization flags the table is exported, but it seems that the table is fixed size with min 3 and max 3 length.
Trying to append -s ALLOW_TABLE_GROWTH=1 -s RESERVED_FUNCTION_POINTERS=100 or only one of these to the compilation command does nothing.
If --import-table is used, the code imports a table with (import "env" "table" (table $env.table 3 funcref)) allowing the creator of the table to define the limits nicely even if no flags are provided in addition to --import-table.
I feel like there are a few issues bundled in here.
For now, I will keep on importing the table as a workaround.
Doing so allows use of any optimization flags and growing of the table.
To tell it that you want the table to be allowed to grow, include the --growable-table flag as part of the -Wl list:
-Wl,--export-table,--growable-table
Thanks for clarifying.
OK, so I see two issues here we could fix.
-s ALLOW_TABLE_GROWTH=1 should probably set --growable-table at least in standalone mode (although passing --growable-table directly seems ok since one already need to set explict wasm-ld flags to get the table exported at all).-Os.It looks like metadce removes the table export, as it sees it isn't used.
We should probably rethink how metadce works with standalone mode, as this has come up before. As a workaround for now, though, building with -O2 (less than -O3 or -Os) will avoid metadce, and preserve that export.