Search code examples
javascriptcpthreadsweb-workeremscripten

The `Module` variable's mechanism in Emscripten while compiling Pthread to workers


I am confused by the Module variable in Emsripten while compiling Pthread to Web Worker + Wasm

There is a simple Pthread code which just adds 1 in the shared variable sum in each thread. (simple.c is attached in the end.)

We can use the command to compile the Pthread code:

$ emcc simple.c  -s USE_PTHREADS=1  -s PTHREAD_POOL_SIZE=4 -o simple.html

Emscripten will generate simple.html, simple.js, simple.worker.js, and simple.wasm.

In simple.worker.js, there is a snippet:

// simple.worker.js
var Module = {};

// ...

Module['instantiateWasm'] = function(info, receiveInstance) {
  // Instantiate from the module posted from the main thread.
  // We can just use sync instantiation in the worker.
  var instance = new WebAssembly.Instance(Module['wasmModule'], info);
  // We don't need the module anymore; new threads will be spawned from the main thread.
  Module['wasmModule'] = null;
  receiveInstance(instance); // The second 'module' parameter is intentionally null here, we don't need to keep a ref to the Module object from here.
  return instance.exports;
};

Note that it declares var Module = {} in the worker, and defines Module['instantiateWasm'].

However, Module['instantiateWasm'] is only called by simple.js, which the code snippet looks like:

//simple.js

var Module = {}

// ...
  if (Module['instantiateWasm']) {
    try {
      var exports = Module['instantiateWasm'](info, receiveInstance);
      return exports;
    } catch(e) {
      err('Module.instantiateWasm callback failed with error: ' + e);
      return false;
    }
  }
// ...

As we can see, simple.js also declares var Module = {} too.

AFAIK, VAR global variables cannot be accessed across the main thread and its worker. I don't understand why simple.js can call Module['instantiateWasm'] as the Module of simple.js and the Module of simple.worker.js should be not the same thing.


Pthread Code:

// simple.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUMTHRDS 4
#define MAGNIFICATION 1e9

typedef struct
{
    int thread_id;
    double *sum;
} Arg;

pthread_t callThd[NUMTHRDS];
pthread_mutex_t mutexsum;

void *count_pi(void *arg)
{

    Arg *data = (Arg *)arg;
    int thread_id = data->thread_id;
    double *sum = data->sum;
    pthread_mutex_lock(&mutexsum);
    *sum += 1;
    pthread_mutex_unlock(&mutexsum);

    printf("Thread %d: sum=%f\n", thread_id, *sum);

    pthread_exit((void *)0);
}

int main(int argc, char *argv[])
{
    pthread_mutex_init(&mutexsum, NULL);

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    double *sum = malloc(sizeof(*sum));
    *sum = 0;

    Arg arg[NUMTHRDS];
    for (int i = 0; i < NUMTHRDS; i++)
    {
        arg[i].thread_id = i;
        arg[i].sum = sum;
        pthread_create(&callThd[i], &attr, count_pi, (void *)&arg[i]);
    }

    pthread_attr_destroy(&attr);

    void *status;
    for (int i = 0; i < NUMTHRDS; i++)
    {
        pthread_join(callThd[i], &status);
    }

    printf("Final Sum =  %f \n", *sum);

    free(sum);

    pthread_mutex_destroy(&mutexsum);
    pthread_exit(NULL);
}

Solution

  • A main program sends self to worker.

    // simple.js
    // Ask the new worker to load up the Emscripten-compiled page. This is a heavy operation.
    worker.postMessage({
      'cmd': 'load',
      // If the application main .js file was loaded from a Blob, then it is not possible
      // to access the URL of the current script that could be passed to a Web Worker so that
      // it could load up the same file. In that case, developer must either deliver the Blob
      // object in Module['mainScriptUrlOrBlob'], or a URL to it, so that pthread Workers can
      // independently load up the same main application file.
      'urlOrBlob': Module['mainScriptUrlOrBlob'] || _scriptDir,
      'wasmMemory': wasmMemory,
      'wasmModule': wasmModule,
      'DYNAMIC_BASE': DYNAMIC_BASE,
      'DYNAMICTOP_PTR': DYNAMICTOP_PTR
    });
    

    and worker import that.

    // simple.worker.js
    if (typeof e.data.urlOrBlob === 'string') {
      importScripts(e.data.urlOrBlob);
    } else {
      var objectUrl = URL.createObjectURL(e.data.urlOrBlob);
      importScripts(objectUrl);
      URL.revokeObjectURL(objectUrl);
    }
    

    Therefore Module isn't shared, but it is initialized independently.