diff options
Diffstat (limited to '')
29 files changed, 288 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c644e1 --- /dev/null +++ b/.gitignore | |||
| @@ -0,0 +1 @@ | |||
| */build | |||
diff --git a/00_hello_world/build.sh b/00_hello_world/build.sh new file mode 100755 index 0000000..9b6b576 --- /dev/null +++ b/00_hello_world/build.sh | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | mkdir -p build | ||
| 4 | emcc -o build/index.html hello.c | ||
diff --git a/00_hello_world/hello.c b/00_hello_world/hello.c new file mode 100644 index 0000000..66e36aa --- /dev/null +++ b/00_hello_world/hello.c | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | #include <stdio.h> | ||
| 2 | |||
| 3 | int main() { | ||
| 4 | printf("Hello, web!\n"); | ||
| 5 | } | ||
diff --git a/00_hello_world/run-node.sh b/00_hello_world/run-node.sh new file mode 100755 index 0000000..3fdf61e --- /dev/null +++ b/00_hello_world/run-node.sh | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | node build/index.js | ||
diff --git a/00_hello_world/run-server.sh b/00_hello_world/run-server.sh new file mode 100755 index 0000000..c0087d4 --- /dev/null +++ b/00_hello_world/run-server.sh | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | darkhttpd build | ||
diff --git a/01_library/build.sh b/01_library/build.sh new file mode 100755 index 0000000..5af8919 --- /dev/null +++ b/01_library/build.sh | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | mkdir -p build | ||
| 4 | emcc -sEXPORTED_FUNCTIONS=_multiply -o build/library.js library.c | ||
diff --git a/01_library/index.html b/01_library/index.html new file mode 100644 index 0000000..84d8e10 --- /dev/null +++ b/01_library/index.html | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | <!doctype html> | ||
| 2 | <html lang="en-US"> | ||
| 3 | <head> | ||
| 4 | <meta charset="utf-8" /> | ||
| 5 | <meta name="viewport" content="width=device-width" /> | ||
| 6 | <title>Multiply two numbers</title> | ||
| 7 | <script src="./build/library.js" defer></script> | ||
| 8 | <script src="./script.js" defer></script> | ||
| 9 | </head> | ||
| 10 | |||
| 11 | <body> | ||
| 12 | <input id="aInput" /> x <br /> | ||
| 13 | <input id="bInput" /> <button id="goButton">=</button> <br /> | ||
| 14 | <p id="resultText"></p> | ||
| 15 | </body> | ||
| 16 | |||
| 17 | </html> | ||
diff --git a/01_library/library.c b/01_library/library.c new file mode 100644 index 0000000..de68b76 --- /dev/null +++ b/01_library/library.c | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | int multiply(int a, int b) { | ||
| 2 | return a * b; | ||
| 3 | } | ||
diff --git a/01_library/program.js b/01_library/program.js new file mode 100644 index 0000000..5cfc6c5 --- /dev/null +++ b/01_library/program.js | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | var library = require("./build/library.js"); | ||
| 2 | |||
| 3 | library.onRuntimeInitialized = () => { | ||
| 4 | const result = library._multiply(6, 7); | ||
| 5 | console.log("The answer is " + result); | ||
| 6 | }; | ||
diff --git a/01_library/run-node.sh b/01_library/run-node.sh new file mode 100755 index 0000000..238f51f --- /dev/null +++ b/01_library/run-node.sh | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | node program.js | ||
diff --git a/01_library/run-server.sh b/01_library/run-server.sh new file mode 100755 index 0000000..cc9f1e0 --- /dev/null +++ b/01_library/run-server.sh | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | darkhttpd . | ||
diff --git a/01_library/script.js b/01_library/script.js new file mode 100644 index 0000000..9432402 --- /dev/null +++ b/01_library/script.js | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | var aInput = document.getElementById("aInput"); | ||
| 2 | var bInput = document.getElementById("bInput"); | ||
| 3 | var button = document.getElementById("goButton"); | ||
| 4 | var resultText = document.getElementById("resultText"); | ||
| 5 | |||
| 6 | button.addEventListener("click", () => { | ||
| 7 | var a = Number(aInput.value); | ||
| 8 | var b = Number(bInput.value); | ||
| 9 | resultText.innerText = Module._multiply(a, b); | ||
| 10 | }); | ||
diff --git a/02_library_modularized/build.sh b/02_library_modularized/build.sh new file mode 100755 index 0000000..f5564fc --- /dev/null +++ b/02_library_modularized/build.sh | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | mkdir -p build | ||
| 4 | emcc -sEXPORTED_FUNCTIONS=_multiply -sMODULARIZE -sEXPORT_NAME=MyLibrary \ | ||
| 5 | -o build/library.mjs library.c | ||
diff --git a/02_library_modularized/index.html b/02_library_modularized/index.html new file mode 100644 index 0000000..4b31417 --- /dev/null +++ b/02_library_modularized/index.html | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | <!doctype html> | ||
| 2 | <html lang="en-US"> | ||
| 3 | <head> | ||
| 4 | <meta charset="utf-8" /> | ||
| 5 | <meta name="viewport" content="width=device-width" /> | ||
| 6 | <title>Multiply two numbers</title> | ||
| 7 | <script src="./script.mjs" type="module" defer></script> | ||
| 8 | </head> | ||
| 9 | |||
| 10 | <body> | ||
| 11 | <input id="aInput" /> x <br /> | ||
| 12 | <input id="bInput" /> <button id="goButton">=</button> <br /> | ||
| 13 | <p id="resultText"></p> | ||
| 14 | </body> | ||
| 15 | |||
| 16 | </html> | ||
diff --git a/02_library_modularized/library.c b/02_library_modularized/library.c new file mode 100644 index 0000000..de68b76 --- /dev/null +++ b/02_library_modularized/library.c | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | int multiply(int a, int b) { | ||
| 2 | return a * b; | ||
| 3 | } | ||
diff --git a/02_library_modularized/mime.txt b/02_library_modularized/mime.txt new file mode 100644 index 0000000..6a9a425 --- /dev/null +++ b/02_library_modularized/mime.txt | |||
| @@ -0,0 +1 @@ | |||
| text/javascript mjs | |||
diff --git a/02_library_modularized/program.mjs b/02_library_modularized/program.mjs new file mode 100644 index 0000000..5fc3fcd --- /dev/null +++ b/02_library_modularized/program.mjs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | import MyLibrary from "./build/library.mjs"; | ||
| 2 | |||
| 3 | var myLibraryInstance = await MyLibrary(); | ||
| 4 | |||
| 5 | const result = myLibraryInstance._multiply(6, 7); | ||
| 6 | console.log("The answer is " + result); | ||
diff --git a/02_library_modularized/run-node.sh b/02_library_modularized/run-node.sh new file mode 100755 index 0000000..a6c9aa8 --- /dev/null +++ b/02_library_modularized/run-node.sh | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | node program.mjs | ||
diff --git a/02_library_modularized/run-server.sh b/02_library_modularized/run-server.sh new file mode 100755 index 0000000..12c1a1c --- /dev/null +++ b/02_library_modularized/run-server.sh | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | darkhttpd . --mimetypes mime.txt | ||
diff --git a/02_library_modularized/script.mjs b/02_library_modularized/script.mjs new file mode 100644 index 0000000..bac26d4 --- /dev/null +++ b/02_library_modularized/script.mjs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | import MyLibrary from "./build/library.mjs"; | ||
| 2 | |||
| 3 | var myLibraryInstance = await MyLibrary(); | ||
| 4 | |||
| 5 | var aInput = document.getElementById("aInput"); | ||
| 6 | var bInput = document.getElementById("bInput"); | ||
| 7 | var button = document.getElementById("goButton"); | ||
| 8 | var resultText = document.getElementById("resultText"); | ||
| 9 | |||
| 10 | button.addEventListener("click", () => { | ||
| 11 | var a = Number(aInput.value); | ||
| 12 | var b = Number(bInput.value); | ||
| 13 | resultText.innerText = myLibraryInstance._multiply(a, b); | ||
| 14 | }); | ||
diff --git a/03_threads/build.sh b/03_threads/build.sh new file mode 100755 index 0000000..2f08909 --- /dev/null +++ b/03_threads/build.sh | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | mkdir -p build | ||
| 4 | emcc -sEXPORTED_FUNCTIONS=_primes_in_range -sMODULARIZE -sEXPORT_NAME=Primes \ | ||
| 5 | -pthread -sPTHREAD_POOL_SIZE=16 \ | ||
| 6 | -o build/primes.mjs primes.c | ||
diff --git a/03_threads/index.html b/03_threads/index.html new file mode 100644 index 0000000..29cfaf8 --- /dev/null +++ b/03_threads/index.html | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | <!doctype html> | ||
| 2 | <html lang="en-US"> | ||
| 3 | <head> | ||
| 4 | <meta charset="utf-8" /> | ||
| 5 | <meta name="viewport" content="width=device-width" /> | ||
| 6 | <title>Multiply two numbers</title> | ||
| 7 | <script src="./script.mjs" type="module" defer></script> | ||
| 8 | </head> | ||
| 9 | |||
| 10 | <body> | ||
| 11 | <label for="aInput">Lower bound (included)</label><br /> | ||
| 12 | <input id="aInput" name="aInput" /> x <br /> | ||
| 13 | <label for="bInput">Upper bound (excluded)</label><br /> | ||
| 14 | <input id="bInput" name="bInput" /> <br /> | ||
| 15 | <button id="goButton">Compute</button> <br /> | ||
| 16 | <p id="resultText"></p> | ||
| 17 | </body> | ||
| 18 | |||
| 19 | </html> | ||
diff --git a/03_threads/mime.txt b/03_threads/mime.txt new file mode 100644 index 0000000..6a9a425 --- /dev/null +++ b/03_threads/mime.txt | |||
| @@ -0,0 +1 @@ | |||
| text/javascript mjs | |||
diff --git a/03_threads/primes.c b/03_threads/primes.c new file mode 100644 index 0000000..a5076c6 --- /dev/null +++ b/03_threads/primes.c | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | #include <stdbool.h> | ||
| 2 | #include <pthread.h> | ||
| 3 | |||
| 4 | #define NTHREADS 16 | ||
| 5 | |||
| 6 | bool isprime(int); | ||
| 7 | void *pthread_routine(void *); | ||
| 8 | |||
| 9 | struct interval { int low; int high; int count; }; | ||
| 10 | |||
| 11 | int primes_in_range(int low, int high) { | ||
| 12 | pthread_t threads[NTHREADS]; | ||
| 13 | struct interval args[NTHREADS]; | ||
| 14 | |||
| 15 | if (low < 0 || high < low) | ||
| 16 | return 0; | ||
| 17 | |||
| 18 | int interval_size = (high-low)/NTHREADS + 1; | ||
| 19 | for (int i = 0; i < NTHREADS; i++) { | ||
| 20 | args[i].low = low + i*interval_size; | ||
| 21 | args[i].high = args[i].low + interval_size; | ||
| 22 | pthread_create(&threads[i], NULL, pthread_routine, &args[i]); | ||
| 23 | } | ||
| 24 | |||
| 25 | int result = 0; | ||
| 26 | for (int i = 0; i < NTHREADS; i++) { | ||
| 27 | pthread_join(threads[i], NULL); | ||
| 28 | result += args[i].count; | ||
| 29 | } | ||
| 30 | |||
| 31 | return result; | ||
| 32 | } | ||
| 33 | |||
| 34 | bool isprime(int n) { | ||
| 35 | if (n < 2) | ||
| 36 | return false; | ||
| 37 | |||
| 38 | for (int i = 2; i*i <= n; i++) | ||
| 39 | if (n % i == 0) | ||
| 40 | return false; | ||
| 41 | return true; | ||
| 42 | } | ||
| 43 | |||
| 44 | void *pthread_routine(void *arg) { | ||
| 45 | struct interval *interval = arg; | ||
| 46 | |||
| 47 | interval->count = 0; | ||
| 48 | for (int i = interval->low; i < interval->high; i++) | ||
| 49 | if (isprime(i)) | ||
| 50 | interval->count++; | ||
| 51 | |||
| 52 | return NULL; | ||
| 53 | } | ||
diff --git a/03_threads/program.mjs b/03_threads/program.mjs new file mode 100644 index 0000000..a5a89ce --- /dev/null +++ b/03_threads/program.mjs | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | import Primes from "./build/primes.mjs" | ||
| 2 | |||
| 3 | var primes = await Primes(); | ||
| 4 | |||
| 5 | const count = primes._primes_in_range(1, 100); | ||
| 6 | console.log("There are " + count + " primes betwees 1 and 100"); | ||
diff --git a/03_threads/run-node.sh b/03_threads/run-node.sh new file mode 100755 index 0000000..a6c9aa8 --- /dev/null +++ b/03_threads/run-node.sh | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | node program.mjs | ||
diff --git a/03_threads/run-server.sh b/03_threads/run-server.sh new file mode 100755 index 0000000..3a526f3 --- /dev/null +++ b/03_threads/run-server.sh | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | darkhttpd . \ | ||
| 4 | --mimetypes mime.txt \ | ||
| 5 | --header 'Cross-Origin-Opener-Policy: same-origin' \ | ||
| 6 | --header 'Cross-Origin-Embedder-Policy: require-corp' | ||
diff --git a/03_threads/script.mjs b/03_threads/script.mjs new file mode 100644 index 0000000..4b52ae0 --- /dev/null +++ b/03_threads/script.mjs | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | import Primes from "./build/primes.mjs"; | ||
| 2 | |||
| 3 | var primes = await Primes(); | ||
| 4 | |||
| 5 | var aInput = document.getElementById("aInput"); | ||
| 6 | var bInput = document.getElementById("bInput"); | ||
| 7 | var button = document.getElementById("goButton"); | ||
| 8 | var resultText = document.getElementById("resultText"); | ||
| 9 | |||
| 10 | button.addEventListener("click", () => { | ||
| 11 | var a = Number(aInput.value); | ||
| 12 | var b = Number(bInput.value); | ||
| 13 | const count = primes._primes_in_range(a, b); | ||
| 14 | resultText.innerText = "There are " + count + " primes between " + | ||
| 15 | a + " and " + b; | ||
| 16 | }); | ||
diff --git a/README.md b/README.md new file mode 100644 index 0000000..4326298 --- /dev/null +++ b/README.md | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | # How to port a complex C codebase to the web | ||
| 2 | |||
| 3 | *This repository is still work in progress* | ||
| 4 | |||
| 5 | This repository contains the source code for the examples discussed in | ||
| 6 | a post walkthrough that is going to appear shortly on my website. | ||
| 7 | It will be linked here once published. | ||
| 8 | |||
| 9 | Each sub-folder is a self-contained example of a C program (or library) | ||
| 10 | that can be compiled to [WebAssembly](https://webassembly.org/) using | ||
| 11 | [Emscripten](https://emscripten.org), and some JavaScript and HTML code | ||
| 12 | that can be used to run the C or C++ code in a web page or in a JavaScript | ||
| 13 | runtime such as [Node.js](https://nodejs.org). Following these steps in | ||
| 14 | order will walk you through the process of deploying a complex C or C++ | ||
| 15 | program (including multithreading, persistent storage, callback functions | ||
| 16 | and so on) as a web app. | ||
| 17 | |||
| 18 | Besides the source files, each folder contains a few one-line shell | ||
| 19 | scripts, for convenience: | ||
| 20 | |||
| 21 | * `build.sh`: to build the C / C++ code with Emscripten. | ||
| 22 | * `run-node.sh`: to run an example program in Node.js. | ||
| 23 | * `run-server.sh`: to start a web server on | ||
| 24 | [localhost:8080](http://localhost:8080) running an example web page | ||
| 25 | (require darkhttpd, see below). | ||
| 26 | |||
| 27 | The examples have been tested only on Linux, but should work on any | ||
| 28 | UNIX system, and should be easy to adapt to Windows or other OSes. | ||
| 29 | Pull requests are welcome. | ||
| 30 | |||
| 31 | ## Prerequisites | ||
| 32 | |||
| 33 | In order to follow this tutorial, you are going to need the following: | ||
| 34 | |||
| 35 | 1. [Emscripten](https://emscripten.org) | ||
| 36 | 2. A web server to run locally, such as | ||
| 37 | [darkhttpd](https://github.com/emikulic/darkhttpd) | ||
| 38 | 3. [Node.js](https://nodejs.org) (this is optional, since a version | ||
| 39 | of Node.js is distributed together with emscripten). | ||
| 40 | |||
| 41 | ## 0. Hello world | ||
| 42 | |||
| 43 | The folder `00_hello_world` contains the simplest possible example. | ||
| 44 | |||
| 45 | ## 1. Building a library | ||
| 46 | |||
| 47 | A common use case for building C or C++ code to WebAssembly is using some | ||
| 48 | high-performance library in a web app. In this situation, the code you | ||
| 49 | want to build does not have a `main()` entry point, but instead its public | ||
| 50 | functions are called from JavaScript. Motivated by this, in the folder | ||
| 51 | `01_library` you'll find an extremely simple C library (with only one | ||
| 52 | one-line function), and some JavaScript code to run it from a web page. | ||
| 53 | |||
| 54 | ## 2. Making it a module | ||
| 55 | |||
| 56 | In `02_library_modularized` we review the previous example and we build it | ||
| 57 | into a | ||
| 58 | [module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). The result is the same, but more convenient to use. | ||
| 59 | |||
| 60 | ## 3. Multi-threading | ||
| 61 | |||
| 62 | In `03_threads` we build a more complicated example based on | ||
| 63 | [pthreads](https://en.wikipedia.org/wiki/Pthreads). To run this | ||
| 64 | example, the web server has to be configured to provide the correct | ||
| 65 | `Cross-Origin-*` headers, see `03_threads/run-server.sh` for details. | ||
