The Two Things That Bit Me In Emscripten
While building a web application using React, TypeScript, C++, Emscripten, and Raylib, I ran into two linker-related issues that took far longer to diagnose than they should have. This is a short article on two problems that I have faced, I am sharing this so that developers who are exploring Web Assembly using Emscripten can easily avoid these issues as I'll also cover the workarounds that solved them for me. I'll start with the major one. Link-Time Optimization and EM_JS The problem appears when Link Time Optimization (-flto) is enabled and an EM_JS(ret, name, params, ...) macro function is invoked in one translation unit but called from another. You'll find that the linker complains that the function symbol you're trying to call is undefined. The EM_JS macro defines a C interface to call the JS function. So if you have your EM_JS macros in a js_layer.cpp source file, then you need to wrap the macro invocation as extern "C" { EM_JS ( void , add , ( int a , int b ), { //your JS code; }); } The same applies to the function declaration in js_layer.hpp file. I'll briefly go through the three workarounds or 'solutions' before bringing up the last quirk. The Three Workarounds If performance isn't of any concern Well the first one is obvious, just don't use -flto in your release builds. The downside is that your binaries (the .data and .wasm compiler outputs in this case) will be larger. Without LTO, the linker has fewer opportunities for whole-program optimization and dead-code elimination, which can increase the size of the generated .wasm and potentially reduce performance. Write a wrapper You can simply have a wrapper function in the same translation unit which calls the C function interfacing with the JS function, add() if you take the above example. The wrapper can simply be: // In js_layer.hpp void add_wrapper ( int , int ); // In js_layer.cpp void add_wrapper ( int a , int b ){ add ( a , b ); } Now you can call add_wrapper() from any source file just by including