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 73efecca42dbc44200e797e02f8c1e24fcff26ef
parent 2a8e7aeeef99161b6f0378ac14e297c23ca6227b
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Thu,  5 Jun 2025 10:13:28 +0200

Added two more examples

Diffstat:
M03_threads/index.html | 2+-
M03_threads/program.mjs | 2+-
A04_no_block/build.sh | 6++++++
A04_no_block/index.html | 19+++++++++++++++++++
A04_no_block/mime.txt | 1+
A04_no_block/primes.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
A04_no_block/run-server.sh | 6++++++
A04_no_block/script.mjs | 14++++++++++++++
A04_no_block/worker.mjs | 8++++++++
A05_callback/build.sh | 8++++++++
A05_callback/index.html | 19+++++++++++++++++++
A05_callback/mime.txt | 1+
A05_callback/primes.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
A05_callback/program.mjs | 11+++++++++++
A05_callback/run-node.sh | 3+++
A05_callback/run-server.sh | 7+++++++
A05_callback/script.mjs | 13+++++++++++++
A05_callback/worker.mjs | 12++++++++++++
MREADME.md | 12++++++++++++
19 files changed, 250 insertions(+), 2 deletions(-)

diff --git a/03_threads/index.html b/03_threads/index.html @@ -3,7 +3,7 @@ <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> - <title>Multiply two numbers</title> + <title>Primes in a range</title> <script src="./script.mjs" type="module" defer></script> </head> diff --git a/03_threads/program.mjs b/03_threads/program.mjs @@ -3,4 +3,4 @@ import Primes from "./build/primes.mjs" var primes = await Primes(); const count = primes._primes_in_range(1, 100); -console.log("There are " + count + " primes betwees 1 and 100"); +console.log("There are " + count + " primes between 1 and 100"); diff --git a/04_no_block/build.sh b/04_no_block/build.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +mkdir -p build +emcc -sEXPORTED_FUNCTIONS=_primes_in_range -sMODULARIZE -sEXPORT_NAME=Primes \ + -pthread -sPTHREAD_POOL_SIZE=16 \ + -o build/primes.mjs primes.c diff --git a/04_no_block/index.html b/04_no_block/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">Compute</button> <br /> + <p id="resultText"></p> +</body> + +</html> diff --git a/04_no_block/mime.txt b/04_no_block/mime.txt @@ -0,0 +1 @@ +text/javascript mjs diff --git a/04_no_block/primes.c b/04_no_block/primes.c @@ -0,0 +1,53 @@ +#include <stdbool.h> +#include <pthread.h> + +#define NTHREADS 16 + +bool isprime(int); +void *pthread_routine(void *); + +struct interval { int low; int high; int count; }; + +int primes_in_range(int low, int high) { + pthread_t threads[NTHREADS]; + struct interval args[NTHREADS]; + + if (low < 0 || high < low) + return 0; + + 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]); + } + + int result = 0; + for (int i = 0; i < NTHREADS; i++) { + pthread_join(threads[i], NULL); + result += args[i].count; + } + + return result; +} + +bool isprime(int n) { + if (n < 2) + return false; + + for (int i = 2; i*i <= n; i++) + if (n % i == 0) + return false; + return true; +} + +void *pthread_routine(void *arg) { + struct interval *interval = arg; + + interval->count = 0; + for (int i = interval->low; i < interval->high; i++) + if (isprime(i)) + interval->count++; + + return NULL; +} diff --git a/04_no_block/run-server.sh b/04_no_block/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/04_no_block/script.mjs b/04_no_block/script.mjs @@ -0,0 +1,14 @@ +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) => resultText.innerText = "There are " + + e.data.result + " primes between " + e.data.a + " and " + e.data.b; diff --git a/04_no_block/worker.mjs b/04_no_block/worker.mjs @@ -0,0 +1,8 @@ +import Primes from "./build/primes.mjs"; + +var primes = await Primes(); + +onmessage = (e) => { + const count = primes._primes_in_range(e.data.a, e.data.b); + postMessage({ result: count, a: e.data.a, b: e.data.b }); +}; diff --git a/05_callback/build.sh b/05_callback/build.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +mkdir -p build +emcc -sEXPORTED_FUNCTIONS=_primes_in_range -sMODULARIZE -sEXPORT_NAME=Primes \ + -sEXPORTED_RUNTIME_METHODS=addFunction,UTF8ToString \ + -sALLOW_TABLE_GROWTH \ + -pthread -sPTHREAD_POOL_SIZE=16 \ + -o build/primes.mjs primes.c diff --git a/05_callback/index.html b/05_callback/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">Compute</button> <br /> + <p id="resultText"></p> +</body> + +</html> diff --git a/05_callback/mime.txt b/05_callback/mime.txt @@ -0,0 +1 @@ +text/javascript mjs diff --git a/05_callback/primes.c b/05_callback/primes.c @@ -0,0 +1,55 @@ +#include <stdbool.h> +#include <pthread.h> + +#define NTHREADS 16 + +bool isprime(int); +void *pthread_routine(void *); + +struct interval { int low; int high; int count; }; + +int primes_in_range(int low, int high, void (*log)(const char *)) { + pthread_t threads[NTHREADS]; + struct interval args[NTHREADS]; + + if (low < 0 || high < low) + return 0; + + 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; +} + +bool isprime(int n) { + if (n < 2) + return false; + + for (int i = 2; i*i <= n; i++) + if (n % i == 0) + return false; + return true; +} + +void *pthread_routine(void *arg) { + struct interval *interval = arg; + + interval->count = 0; + for (int i = interval->low; i < interval->high; i++) + if (isprime(i)) + interval->count++; + + return NULL; +} diff --git a/05_callback/program.mjs b/05_callback/program.mjs @@ -0,0 +1,11 @@ +import Primes from "./build/primes.mjs" + +var primes = await Primes(); +const logPtr = primes.addFunction((cstr) => { + console.log(primes.UTF8ToString(cstr)); +}, 'vp'); + +const a = 1; +const b = 10000000; +const count = primes._primes_in_range(a, b, logPtr); +console.log("There are " + count + " primes between " + a + " and " + b); diff --git a/05_callback/run-node.sh b/05_callback/run-node.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +node program.mjs diff --git a/05_callback/run-server.sh b/05_callback/run-server.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +darkhttpd . \ +--port 8082 \ + --mimetypes mime.txt \ + --header 'Cross-Origin-Opener-Policy: same-origin' \ + --header 'Cross-Origin-Embedder-Policy: require-corp' diff --git a/05_callback/script.mjs b/05_callback/script.mjs @@ -0,0 +1,13 @@ +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) => resultText.innerText = e.data.message; diff --git a/05_callback/worker.mjs b/05_callback/worker.mjs @@ -0,0 +1,12 @@ +import Primes from "./build/primes.mjs"; + +var primes = await Primes(); +const logPtr = primes.addFunction((cstr) => { + postMessage({ message: primes.UTF8ToString(cstr) }); +}, "vp"); + +onmessage = (e) => { + const count = primes._primes_in_range(e.data.a, e.data.b, logPtr); + postMessage({ message: "There are " + count + " primes between " + + e.data.a + " and " + e.data.b }); +}; diff --git a/README.md b/README.md @@ -24,6 +24,9 @@ scripts, for convenience: [localhost:8080](http://localhost:8080) running an example web page (require darkhttpd, see below). +If one of the `run-*` scripts is missing, it means that that example +is only meant to run in Node.js, or only as a script in a web page. + The examples have been tested only on Linux, but should work on any UNIX system, and should be easy to adapt to Windows or other OSes. Pull requests are welcome. @@ -63,3 +66,12 @@ In `03_threads` we build a more complicated example based on [pthreads](https://en.wikipedia.org/wiki/Pthreads). To run this example, the web server has to be configured to provide the correct `Cross-Origin-*` headers, see `03_threads/run-server.sh` for details. + +## 4. Don't block the main thread + +In `04_no_block` we avoid our calculations blocking the main browser +thread by using a web worker. + +## 5. Callback functions + +In `05_callback` the example is extended to include a callback function.