In the previous part I wrote about compiling a C program to run in the browser with Empscripten, by using its -o output.html feature. The post centered around tribulations due to a stack overflow in the browser version, because Emscripten’s default 5MB stack size of was smaller than the example program assumed it could use.

Now instead of compiling C code to JS and running it, I want to export the C functions from the library header file Lib_pdq.h to JS, and call them from Javascript.

Exporting C functions

Emscripten is configured to do dead code elimination for you, so you must either add an attribute EMSCRIPTEN_KEEPALIVE (shorthand for __attribute__((used))), or pass in a list of functions to export with -s EXPORTED_FUNCTIONS, to prevent those from being removed during linking/optimization. I chose the latter method, and compiled the code with this script:

#build_module.sh
#!/bin/bash

#Note that you have to add an underscore to the function names in EXPORTED_FUNCTIONS,
#probably because this happens at the linker stage and the C symbol table
#prepends _ for historical reasons.

export EXPORTED_FUNCTIONS="['_PDQ_CreateClosed',
'_PDQ_CreateClosed_p',
'_PDQ_CreateOpen',
'_PDQ_CreateOpen_p',
'_PDQ_CreateNode',
'_PDQ_CreateMultiNode',
'_PDQ_GetStreamsCount',
'_PDQ_GetResponse',
'_PDQ_GetResidenceTime',
'_PDQ_GetThruput',
'_PDQ_GetLoadOpt',
'_PDQ_GetUtilization',
'_PDQ_GetQueueLength',
'_PDQ_GetThruMax',
'_PDQ_Init',
'_PDQ_Report',
'_PDQ_SetDebug',
'_PDQ_SetDemand',
'_PDQ_SetDemand_p',
'_PDQ_SetVisits',
'_PDQ_SetVisits_p',
'_PDQ_Solve',
'_PDQ_SetWUnit',
'_PDQ_SetTUnit',
'_PDQ_SetComment',
'_PDQ_GetComment'
]"

#The memory values can be specified with mb, which is nice
emcc PDQ_*.c MVA*.c -o exported.js \
	-s TOTAL_MEMORY=150mb \
	-s TOTAL_STACK=10mb \
	-s ASSERTIONS=2 \
	-s MODULARIZE=1 \ #Builds a module you can require()
	-s "EXPORTED_FUNCTIONS=$EXPORTED_FUNCTIONS" \
	-s 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall", "cwrap"]' #To expose these in the module, otherwise they get tree-shaken out

(I haven’t figured out how to integrate this into the LibPdq makefile, and plan on doing that later).

I built the list of functions to export by grepping for .*\( in the header file and then using some vim macros. In this small program it’s pretty easy but I wish I knew the build system approved way to get all the symbols defined in a file.

Calling C functions in JS, ccall and cwrap

You can technically call the exported functions (they’re on the Module object) directly, but you need to do type translations (strings to char pointers, pointers to numbers, etc). Instead It’s easier to do it with the cwrap and ccall functions. cwrap wraps the C function and returns a JS one, and ccall directly calls it and returns whatever the C function returned.

I used cwrap here to build reusable functions: You give it the name of the function, the return type and parameter types.

It automatically translates strings into C char * strings. JS only has a single number type which is the C equivalent of double, so I don’t know how it knows when to translate those to ints.

I had to to re-define the constants PDQ uses, since in C those are #defines which never end up in the compiled code. Not a big deal at all. I was able to re-create the example model in JS:

require('./exported.js')().then(mod => {
    const init = mod.cwrap('PDQ_Init', "number", ["string"]);
    const createClosed = mod.cwrap('PDQ_CreateClosed', "number", ["string", "number", "number", "number"]);
    const createMultiNode = mod.cwrap('PDQ_CreateMultiNode', "number", ["number", "string", "number", "number"]);
    const setDemand = mod.cwrap('PDQ_SetDemand', "number", ["string", "string", "number"]);
    const setWUnit = mod.cwrap('PDQ_SetWUnit', "number", ["string"]);
    const solve = mod.cwrap('PDQ_Solve', "number", ["number"]);
    const report = mod.cwrap('PDQ_Report', "number", []);

    const requests = 400;
    const threads = 300;
    const service_time = 0.444;

    const BATCH = 13;
    const MSC = 6;
    const FCFS = 8;
    const EXACT = 14;

    init("My model");
    createClosed("Requests", BATCH, requests, 0.0);
    createMultiNode(threads, "Threads", MSC, FCFS);
    setDemand("Threads", "Requests", service_time);
    setWUnit("Reqs");
    solve(EXACT);
    report();
});

And I got the exact same output!

               ==========================================
               ********     PDQ Model OUTPUTS    ********
               ==========================================

Solution method: EXACT

               ********   SYSTEM Performance     ********

Metric                   Value      Unit
------                  -------     ----
Workload: "Requests"
Mean concurrency        400.0000    Reqs
Mean throughput         675.6757    Reqs/Sec
Response time             0.5920    Sec
Stretch factor            1.3333

Bounds Analysis:
Max throughput          675.6757    Reqs/Sec
Min response              0.4440    Sec
Max demand                0.0015    Sec
Total demand              0.4440    Sec
Optimal jobs            300.0000    Jobs


               ********   RESOURCE Performance   ********

Metric          Resource     Work               Value    Unit
------          --------     ----              -------   ----
Capacity        Threads      Requests              300   Servers
Throughput      Threads      Requests         675.6757   Reqs/Sec
In service      Threads      Requests         300.0000   Reqs
Utilization     Threads      Requests         100.0000   Percent
Queue length    Threads      Requests         400.0000   Reqs
Waiting line    Threads      Requests         100.0000   Reqs
Waiting time    Threads      Requests           0.1480   Sec
Residence time  Threads      Requests           0.5920   Sec

This is great. Next I’m going to cwrap all the functions and define all the constants to turn this into a usable package I can use. I’ve just started re-reading Analyzing Computer System Performance with Perl::PDQ, at the M/M/c vs M/M/1 queue discussion, so after that part there should be more modelling.

I need to figure out how to run this in the browser without require.

Can I just use import instead? And how would I package/export my cwraps which depend on the asyncly loaded WASM module? I’m looking into Emscripten’s --post-js flag to put those functions in the Module itself.


comments powered by Disqus