commit ab0db69a7b11ba1a9dc52d50c94e9e321b70ebf0
parent 961c3470d7e9bdf5efbe0509f586be75d7052d8b
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date: Fri, 6 Jun 2025 09:13:54 +0200
Added example 6
Diffstat:
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.