emscripten-tutorial

How to build an increasingly complex C/C++ codebase to WebAssembly
git clone https://git.tronto.net/emscripten-tutorial
Download | Log | Files | Refs | README

commit ab0db69a7b11ba1a9dc52d50c94e9e321b70ebf0
parent 961c3470d7e9bdf5efbe0509f586be75d7052d8b
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Fri,  6 Jun 2025 09:13:54 +0200

Added example 6

Diffstat:
M05_callback/build.sh | 2+-
A06_storage/build.sh | 9+++++++++
A06_storage/index.html | 19+++++++++++++++++++
A06_storage/init_idbfs.js | 16++++++++++++++++
A06_storage/mime.txt | 1+
A06_storage/primes.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A06_storage/primes.h | 12++++++++++++
A06_storage/run-server.sh | 6++++++
A06_storage/script.mjs | 20++++++++++++++++++++
A06_storage/storage.c | 28++++++++++++++++++++++++++++
A06_storage/storage.h | 7+++++++
A06_storage/worker.mjs | 22++++++++++++++++++++++
MREADME.md | 13+++++++++++++
13 files changed, 248 insertions(+), 1 deletion(-)

diff --git a/05_callback/build.sh b/05_callback/build.sh @@ -2,7 +2,7 @@ mkdir -p build emcc -sEXPORTED_FUNCTIONS=_primes_in_range -sMODULARIZE -sEXPORT_NAME=Primes \ + -pthread -sPTHREAD_POOL_SIZE=16 \ -sEXPORTED_RUNTIME_METHODS=addFunction,UTF8ToString \ -sALLOW_TABLE_GROWTH \ - -pthread -sPTHREAD_POOL_SIZE=16 \ -o build/primes.mjs primes.c diff --git a/06_storage/build.sh b/06_storage/build.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +mkdir -p build +emcc -sEXPORTED_FUNCTIONS=_primes_in_range -sMODULARIZE -sEXPORT_NAME=Primes \ + -pthread -sPTHREAD_POOL_SIZE=16 \ + -sEXPORTED_RUNTIME_METHODS=addFunction,UTF8ToString \ + -sALLOW_TABLE_GROWTH \ + -lidbfs.js --pre-js init_idbfs.js -sINITIAL_MEMORY=272629760 \ + -o build/primes.mjs primes.c storage.c diff --git a/06_storage/index.html b/06_storage/index.html @@ -0,0 +1,19 @@ +<!doctype html> +<html lang="en-US"> +<head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width" /> + <title>Primes in a range</title> + <script src="./script.mjs" type="module" defer></script> +</head> + +<body> + <label for="aInput">Lower bound (included)</label><br /> + <input id="aInput" name="aInput" /> x <br /> + <label for="bInput">Upper bound (excluded)</label><br /> + <input id="bInput" name="bInput" /> <br /> + <button id="goButton" disabled>Loading, please wait...</button> <br /> + <p id="resultText"></p> +</body> + +</html> diff --git a/06_storage/init_idbfs.js b/06_storage/init_idbfs.js @@ -0,0 +1,16 @@ +Module['preRun'] = [ + async () => { + const dir = "/assets"; + + FS.mkdir(dir); + FS.mount(IDBFS, { autoPersist: true }, dir); + + Module.fileSystemLoaded = new Promise((resolve, reject) => { + FS.syncfs(true, (err) => { + if (err) reject(err); + else resolve(true); + }); + }); + + } +]; diff --git a/06_storage/mime.txt b/06_storage/mime.txt @@ -0,0 +1 @@ +text/javascript mjs diff --git a/06_storage/primes.c b/06_storage/primes.c @@ -0,0 +1,94 @@ +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include <pthread.h> + +#include "primes.h" +#include "storage.h" + +#define NTHREADS 16 +#define INDEX(i) ((i) >> 3) +#define MASK(i) (unsigned char)(1 << ((i) % 8)) + +unsigned char primes_table[TABLESIZE]; +struct interval { int low; int high; int count; }; + +void *pthread_routine(void *); +int primes_in_range(int, int, void (*)(const char *)); +void set_nonprime(unsigned char *, int); +bool isprime(const unsigned char *, int); + +int primes_in_range(int low, int high, void (*log)(const char *)) { + static bool table_is_loaded = false; + + pthread_t threads[NTHREADS]; + struct interval args[NTHREADS]; + + if (low < 0 || high < low) + return 0; + + if (!table_is_loaded) { + if (!read_table(primes_table)) { + generate_primes(primes_table, log); + if (!store_table(primes_table)) + log("Error storing table to indexed DB"); + } + + table_is_loaded = true; + } + + int interval_size = (high-low)/NTHREADS + 1; + for (int i = 0; i < NTHREADS; i++) { + args[i].low = low + i*interval_size; + args[i].high = args[i].low + interval_size; + pthread_create(&threads[i], NULL, pthread_routine, &args[i]); + } + + log("All threads have started, computing..."); + + int result = 0; + for (int i = 0; i < NTHREADS; i++) { + pthread_join(threads[i], NULL); + result += args[i].count; + } + + return result; +} + +void set_nonprime(unsigned char *table, int n) { + table[INDEX(n)] &= ~MASK(n); +} + +bool isprime(const unsigned char *table, int n) { + return table[INDEX(n)] & MASK(n); +} + +void generate_primes(unsigned char *table, void (*log)(const char *)) { + memset(table, 0xFF, TABLESIZE); + set_nonprime(table, 0); + set_nonprime(table, 1); + for (long long i = 0; i < MAX_PRIME; i++) { + if (i % 100000000 == 0 && log != NULL) { + char msg[200]; + sprintf(msg, + "Could not read table of primes, generating it\n" + "Done %llu / %d", i, MAX_PRIME); + log(msg); + } + if (!isprime(table, i)) + continue; + for (long long int j = 2*i; j < MAX_PRIME; j += i) + set_nonprime(table, j); + } +} + +void *pthread_routine(void *arg) { + struct interval *interval = arg; + + interval->count = 0; + for (int i = interval->low; i < interval->high; i++) + if (isprime(primes_table, i)) + interval->count++; + + return NULL; +} diff --git a/06_storage/primes.h b/06_storage/primes.h @@ -0,0 +1,12 @@ +#ifndef PRIMES_H +#define PRIMES_H + +#include <limits.h> + +#define MAX_PRIME INT_MAX +#define TABLESIZE (1 << 28) + +int primes_in_range(int, int, void (*)(const char *)); +void generate_primes(unsigned char *, void (*)(const char *)); + +#endif diff --git a/06_storage/run-server.sh b/06_storage/run-server.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +darkhttpd . \ + --mimetypes mime.txt \ + --header 'Cross-Origin-Opener-Policy: same-origin' \ + --header 'Cross-Origin-Embedder-Policy: require-corp' diff --git a/06_storage/script.mjs b/06_storage/script.mjs @@ -0,0 +1,20 @@ +var aInput = document.getElementById("aInput"); +var bInput = document.getElementById("bInput"); +var button = document.getElementById("goButton"); +var resultText = document.getElementById("resultText"); + +var worker = new Worker("./worker.mjs", { type: "module" }); + +button.addEventListener("click", () => worker.postMessage({ + a: Number(aInput.value), + b: Number(bInput.value) +})); + +worker.onmessage = (e) => { + if (e.data.type == "response") + resultText.innerText = e.data.message; + else if (e.data.type == "readySignal") { + button.disabled = false; + button.innerText = "Compute" + } +}; diff --git a/06_storage/storage.c b/06_storage/storage.c @@ -0,0 +1,28 @@ +#include <stdio.h> +#include <stdbool.h> + +#include "primes.h" + +#define FILENAME "/assets/primes_table" + +bool read_table(unsigned char *table) { + FILE *f = fopen(FILENAME, "rb"); + if (f == NULL) + return false; + + int b = fread(table, TABLESIZE, 1, f); + fclose(f); + + return b == 1; +} + +bool store_table(const unsigned char *table) { + FILE *f = fopen(FILENAME, "wb"); + if (f == NULL) + return false; + + int b = fwrite(table, TABLESIZE, 1, f); + fclose(f); + + return b == 1; +} diff --git a/06_storage/storage.h b/06_storage/storage.h @@ -0,0 +1,7 @@ +#ifndef STORAGE_H +#define STORAGE_H + +bool read_table(unsigned char *); +bool store_table(const unsigned char *); + +#endif diff --git a/06_storage/worker.mjs b/06_storage/worker.mjs @@ -0,0 +1,22 @@ +import Primes from "./build/primes.mjs"; + +var primes = await Primes(); + +const logPtr = primes.addFunction((cstr) => { + const str = primes.UTF8ToString(cstr); + console.log(str); + postMessage({ message: str }); +}, "vp"); + +onmessage = (e) => { + const count = primes._primes_in_range(e.data.a, e.data.b, logPtr); + postMessage({ + type: "response", + message: "There are " + count + " primes between " + + e.data.a + " and " + e.data.b + }); +}; + +primes.fileSystemLoaded.then(() => { + postMessage({ type: "readySignal" }); +}); diff --git a/README.md b/README.md @@ -75,3 +75,16 @@ thread by using a web worker. ## 5. Callback functions In `05_callback` the example is extended to include a callback function. + +## 6. Storage + +In `06_storage` we show how to use Emscripten's +[file system API](https://emscripten.org/docs/api_reference/Filesystem-API.html) +using as backend the +[indexed DB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API). +This example only works in the browser, as other runtimes (such as +Node.js) require different backends. + +In this example we make use for the first time of the `--pre-js` option of +the compiler to include custom JavaScript code that is run when our module +loads. Moreover, the C code is split into multiple files for convenience.