As I said, you need to encode object identity (here - as a string to look up in the global namespace) and handle requests for doing things with that object (encoded as a method name + a list of strings as arguments) on the JS side. Strings are probably encoded as pointer + length, and the JS side takes care of locating the object, decoding arguments into JS objects, performing the action, and returning the result, encapsulated in a val class instance.
But, you're right, I forgot about this part of emscripten, my bad :) My use case was passing a 2Mb of image data to WASM, which was simple to do with just WebAssembly.Memory, so I didn't get the chance to use this part of the FFI (I used the part going in the opposite direction[1]). I don't know the details of the val implementation, nor the details of JS-side handling the calls, but the basic principle should be as I said: the only "things" you can pass between JS and WASM are numbers and linear buffers. To do anything above that you need support on the JS side and some kind of encoding/serialization, similarly to what you do in IPC/RPC. EDIT: I also suspect it's possible to exclude/disable val.h with a define flag (I didn't check this though).
[1] https://emscripten.org/docs/api_reference/bind.h.html#_CPPv4...