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 #define
s 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 cwrap
s 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