emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.75k stars 3.3k forks source link

How can I create a variable share between MAIN_MODULE and SIDE_MODULE so that SIDE_MODULE can access it when initializing #22693

Closed ruiqurm closed 1 week ago

ruiqurm commented 1 week ago

Background

In my project, I need a mechanism to share a variable between the MAIN_MODULE and SIDE_MODULE. Additionally, the side module should access this variable during initialization. I discovered that if both modules are compiled with the USE_PTHREADS=1 flag, they share the same virtual memory. However, I am unable to pass the variable's address from the main module to the side module during its initialization.

To work around this, I opted to use a constant memory address to store the address of the shared variable. When the main module starts, it sets the shared variable's address at this constant location. This allows the side module, upon loading, to read the constant address and access the shared variable.

I'm curious if there is a more efficient or elegant solution to this problem?

Reproduce demo

Here is a demo to reproduce my way to solve the problem: In a.cpp:

#include <dlfcn.h>
#include <iostream>
#include <map>
#include <stdio.h>
#include <string>
static std::map<std::string, unsigned long> register_map;
unsigned long addr_to_get_register_map = 0x6400000; // Just a random address.

int main() {
  (*(unsigned long *)addr_to_get_register_map) = (unsigned long)&register_map;
  printf("hello world,addr of `register_map`=%ld\n",
         (*(unsigned long *)addr_to_get_register_map));
  std::cout << "before loading" << std::endl;
  std::cout << register_map["hello"] << std::endl;
  std::cout << register_map["world"] << std::endl;
  void *handle = dlopen("b.so", RTLD_LAZY);
  if (!handle) {
    std::cerr << dlerror() << std::endl;
    return -1;
  }
  std::cout << "after loading" << std::endl;
  std::cout << register_map["hello"] << std::endl;
  std::cout << register_map["world"] << std::endl;
  void (*hello)();
  hello = reinterpret_cast<decltype(hello)>(register_map["hello"]);
  hello();
}

In b.cpp:

#include <iostream>
#include <map>
#include <string>

unsigned long addr_to_get_register_map = 0x6400000;

void hello() { std::cout << "hello\n"; }

void world() { std::cout << "world\n"; }

void __attribute__((constructor)) global_init() {
  unsigned long addr = (*(unsigned long *)addr_to_get_register_map);
  std::map<std::string, unsigned long> *register_map =
      (std::map<std::string, unsigned long> *)addr;
  (*register_map)["hello"] = (unsigned long)&hello;
  (*register_map)["world"] = (unsigned long)&world;
}

Use the following commands to compile them:

emcc -o b.so -s USE_PTHREADS=1 -sSIDE_MODULE -s INITIAL_MEMORY=128MB b.cpp
emcc -o a.html a.cpp -s USE_PTHREADS=1  -sMAIN_MODULE -s INITIAL_MEMORY=128MB --preload-file b.so

Using shared memory may need a HTTP server. Here is a simple way to setup it:

import http.server
import ssl

class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header('Cross-Origin-Opener-Policy', 'same-origin')
        self.send_header('Cross-Origin-Embedder-Policy', 'require-corp')
        super().end_headers()

server_address = ('0.0.0.0', 443)
httpd = http.server.HTTPServer(server_address, CustomHTTPRequestHandler)
httpd.socket = ssl.wrap_socket(httpd.socket,
                               keyfile="server.pem",  
                               certfile="server.pem",# Create you own
                               server_side=True)

print(f"Serving on port {server_address[1]}")
httpd.serve_forever()

Use python3 xxx.py to setup a https server.

If everything works well, you should get the following output in console:

hello world,addr of `register_map`=384448
before loading
0
0
after loading
7497
7498
hello

Version of emscripten/emsdk:

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.64 (a1fe3902bf73a3802eae0357d273d0e37ea79898)
clang version 19.0.0git (https:/github.com/llvm/llvm-project 4d8e42ea6a89c73f90941fd1b6e899912e31dd34)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /home/sam/emsdk/upstream/bin
ruiqurm commented 1 week ago

I notice there is a discussion forum. I will move my question there.