Emscripten: run C and C++ codes in the browser

Introduction

In this lecture we will learn how to use and to understand the underlying concepts of Emscripten, a toolchain for compiling to asm.js and WebAssembly. Built using LLVM technology too, it lets you run C and C++ on the web at near-native speed without plugins. Its feature are mainly:

Installing Emscripten

To download and to install, please follow this guide. Then follow this tutorial about how to use Emscripten. All of this is a little tricky! You may still have problems, for instance, due to a previous install or some incompatibilities. In my case, since I got the Your fastcomp compiler is out of date message, I followed the following tutorial. The full messages list, obtained during the tutorial was:

MacBook-de-Christophe:emscripten christophecerin$ ./emcc tests/hello_world_sdl.cpp -o hello.html
cache:INFO: generating system library: libc-wasm.bc... (this will be cached in "/Users/christophecerin/.emscripten_cache/asmjs/libc-wasm.bc" for subsequent builds)
cache:INFO:  - ok
cache:INFO: generating system asset: generated_struct_info.json... (this will be cached in "/Users/christophecerin/.emscripten_cache/asmjs/generated_struct_info.json" for subsequent builds)
shared:ERROR: Your fastcomp compiler is out of date, please update! (need >= 1.38.26)
FAIL: Compilation failed!

MacBook-de-Christophe:emscripten christophecerin$ 
MacBook-de-Christophe:emscripten christophecerin$ mkdir myfastcomp
MacBook-de-Christophe:emscripten christophecerin$ cd myfastcomp/
MacBook-de-Christophe:myfastcomp christophecerin$ git clone https://github.com/emscripten-core/emscripten-fastcomp
Cloning into 'emscripten-fastcomp'...
remote: Enumerating objects: 67, done.
remote: Counting objects: 100% (67/67), done.
remote: Compressing objects: 100% (35/35), done.
remote: Total 1475278 (delta 31), reused 50 (delta 23), pack-reused 1475211
Receiving objects: 100% (1475278/1475278), 344.08 MiB | 2.31 MiB/s, done.
Resolving deltas: 100% (1219137/1219137), done.


MacBook-de-Christophe:myfastcomp christophecerin$ cd emscripten-fastcomp/
MacBook-de-Christophe:emscripten-fastcomp christophecerin$ git clone https://github.com/emscripten-core/emscripten-fastcomp-clang tools/clang
Cloning into 'tools/clang'...
remote: Enumerating objects: 50, done.
remote: Counting objects: 100% (50/50), done.
remote: Compressing objects: 100% (32/32), done.
remote: Total 663417 (delta 16), reused 31 (delta 9), pack-reused 663367
Receiving objects: 100% (663417/663417), 142.50 MiB | 2.09 MiB/s, done.
Resolving deltas: 100% (562490/562490), done.

MacBook-de-Christophe:emscripten-fastcomp christophecerin$ mkdir build
MacBook-de-Christophe:emscripten-fastcomp christophecerin$ cd build
MacBook-de-Christophe:build christophecerin$ cmake .. -DCMAKE_BUILD_TYPE=Release -DLLVM_TARGETS_TO_BUILD="host;JSBackend" -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_TESTS=OFF -DCLANG_INCLUDE_TESTS=OFF
-bash: cmake: command not found

MacBook-de-Christophe:~ christophecerin$ brew install cmake
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/cask).
No changes to formulae.

==> Downloading https://homebrew.bintray.com/bottles/cmake-3.14.3.mojave.bottle.
==> Downloading from https://akamai.bintray.com/10/103cfc24445145ceaa7904d0e894e
######################################################################## 100.0%
==> Pouring cmake-3.14.3.mojave.bottle.tar.gz
==> Caveats
Emacs Lisp files have been installed to:
  /usr/local/share/emacs/site-lisp/cmake
==> Summary
🍺  /usr/local/Cellar/cmake/3.14.3: 5,674 files, 50.4MB
MacBook-de-Christophe:~ christophecerin$ cmake
Usage

  cmake [options] <path-to-source>
  cmake [options] <path-to-existing-build>
  cmake [options] -S <path-to-source> -B <path-to-build>

Specify a source directory to (re-)generate a build system for it in the
current working directory.  Specify an existing build directory to
re-generate its build system.

Run 'cmake --help' for more information.


  
MacBook-de-Christophe:build christophecerin$ cmake .. -DCMAKE_BUILD_TYPE=Release -DLLVM_TARGETS_TO_BUILD="host;JSBackend" -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_INCLUDE_TESTS=OFF -DCLANG_INCLUDE_TESTS=OFF
CMake Deprecation Warning at CMakeLists.txt:14 (cmake_policy):
  The OLD behavior for policy CMP0051 will be removed from a future version
  of CMake.

  The cmake-policies(7) manual explains that the OLD behaviors of all
  policies are deprecated and that a policy should be set to OLD only under
  specific short-term circumstances.  Projects should be ported to the NEW
  behavior and not rely on setting a policy to OLD.


-- The C compiler identification is unknown
-- The CXX compiler identification is unknown
-- The ASM compiler identification is unknown
-- Found assembler: /Users/christophecerin/emscripten/emcc
-- Check for working C compiler: /Users/christophecerin/emscripten/emcc
-- Check for working C compiler: /Users/christophecerin/emscripten/emcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /Users/christophecerin/emscripten/em++
-- Check for working CXX compiler: /Users/christophecerin/emscripten/em++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Warning: Did not find file Compiler/-ASM
-- Found libtool - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool
-- Looking for dirent.h
CMake Warning (dev) at /usr/local/Cellar/cmake/3.14.3/share/cmake/Modules/CheckIncludeFile.cmake:80 (message):
  Policy CMP0075 is not set: Include file check macros honor
  CMAKE_REQUIRED_LIBRARIES.  Run "cmake --help-policy CMP0075" for policy
  details.  Use the cmake_policy command to set the policy and suppress this
  warning.

  CMAKE_REQUIRED_LIBRARIES is set to:

    m

  For compatibility with CMake 3.11 and below this check is ignoring it.
Call Stack (most recent call first):
  cmake/config-ix.cmake:47 (check_include_file)
  CMakeLists.txt:579 (include)
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Looking for dirent.h - found
-- Looking for dlfcn.h
-- Looking for dlfcn.h - found
-- Looking for errno.h
-- Looking for errno.h - found
-- Looking for fcntl.h
-- Looking for fcntl.h - found
-- Looking for inttypes.h
-- Looking for inttypes.h - found
-- Looking for link.h
-- Looking for link.h - found
-- Looking for malloc.h
-- Looking for malloc.h - found
-- Looking for malloc/malloc.h
-- Looking for malloc/malloc.h - not found
-- Looking for ndir.h
-- Looking for ndir.h - not found
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Looking for signal.h
-- Looking for signal.h - found
-- Looking for stdint.h
-- Looking for stdint.h - found
-- Looking for sys/dir.h
-- Looking for sys/dir.h - found
-- Looking for sys/ioctl.h
-- Looking for sys/ioctl.h - found
-- Looking for sys/mman.h
-- Looking for sys/mman.h - found
-- Looking for sys/ndir.h
-- Looking for sys/ndir.h - not found
-- Looking for sys/param.h
-- Looking for sys/param.h - found
-- Looking for sys/resource.h
-- Looking for sys/resource.h - found
-- Looking for sys/stat.h
-- Looking for sys/stat.h - found
-- Looking for sys/time.h
-- Looking for sys/time.h - found
-- Looking for sys/types.h
-- Looking for sys/types.h - found
-- Looking for sys/uio.h
-- Looking for sys/uio.h - found
-- Looking for termios.h
-- Looking for termios.h - found
-- Looking for unistd.h
-- Looking for unistd.h - found
-- Looking for valgrind/valgrind.h
-- Looking for valgrind/valgrind.h - not found
-- Looking for zlib.h
-- Looking for zlib.h - not found
-- Looking for fenv.h
-- Looking for fenv.h - found
-- Looking for FE_ALL_EXCEPT
-- Looking for FE_ALL_EXCEPT - found
-- Looking for FE_INEXACT
-- Looking for FE_INEXACT - found
-- Looking for mach/mach.h
-- Looking for mach/mach.h - not found
-- Looking for histedit.h
-- Looking for histedit.h - not found
-- Looking for CrashReporterClient.h
-- Looking for CrashReporterClient.h - not found
-- Performing Test HAVE_CRASHREPORTER_INFO
-- Performing Test HAVE_CRASHREPORTER_INFO - Success
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Looking for pthread_getspecific in pthread
-- Looking for pthread_getspecific in pthread - found
-- Looking for pthread_rwlock_init in pthread
-- Looking for pthread_rwlock_init in pthread - found
-- Looking for pthread_mutex_lock in pthread
-- Looking for pthread_mutex_lock in pthread - found
-- Looking for dlopen in dl
-- Looking for dlopen in dl - found
-- Looking for clock_gettime in rt
-- Looking for clock_gettime in rt - found
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Looking for pthread_create
-- Looking for pthread_create - found
-- Found Threads: TRUE  
-- Looking for compress2 in z
-- Looking for compress2 in z - not found
-- Looking for compress2 in zlib_static
-- Looking for compress2 in zlib_static - not found
-- Looking for compress2 in zlib
-- Looking for compress2 in zlib - not found
-- Looking for setupterm in tinfo
-- Looking for setupterm in tinfo - not found
-- Looking for setupterm in terminfo
-- Looking for setupterm in terminfo - not found
-- Looking for setupterm in curses
-- Looking for setupterm in curses - not found
-- Looking for setupterm in ncurses
-- Looking for setupterm in ncurses - not found
-- Looking for setupterm in ncursesw
-- Looking for setupterm in ncursesw - not found
-- Found LibXml2: /usr/lib/libxml2.dylib (found version "2.9.4") 
-- Looking for xar_open in xar
-- Looking for xar_open in xar - not found
-- Looking for arc4random
-- Looking for arc4random - not found
-- Looking for backtrace
-- Looking for backtrace - not found
-- Could NOT find Backtrace (missing: Backtrace_LIBRARY) 
-- Performing Test C_SUPPORTS_WERROR_UNGUARDED_AVAILABILITY_NEW
-- Performing Test C_SUPPORTS_WERROR_UNGUARDED_AVAILABILITY_NEW - Success
-- Looking for _Unwind_Backtrace
-- Looking for _Unwind_Backtrace - found
-- Looking for getpagesize
-- Looking for getpagesize - found
-- Looking for sysconf
-- Looking for sysconf - found
-- Looking for getrusage
-- Looking for getrusage - found
-- Looking for setrlimit
-- Looking for setrlimit - found
-- Looking for isatty
-- Looking for isatty - found
-- Looking for futimens
-- Looking for futimens - found
-- Looking for futimes
-- Looking for futimes - found
-- Looking for posix_fallocate
-- Looking for posix_fallocate - found
-- Looking for writev
-- Looking for writev - found
-- Looking for lseek64
-- Looking for lseek64 - found
-- Looking for mallctl
-- Looking for mallctl - not found
-- Looking for mallinfo
-- Looking for mallinfo - found
-- Looking for malloc_zone_statistics
-- Looking for malloc_zone_statistics - not found
-- Looking for mkdtemp
-- Looking for mkdtemp - found
-- Looking for mkstemp
-- Looking for mkstemp - found
-- Looking for mktemp
-- Looking for mktemp - found
-- Looking for getcwd
-- Looking for getcwd - found
-- Looking for gettimeofday
-- Looking for gettimeofday - found
-- Looking for getrlimit
-- Looking for getrlimit - found
-- Looking for posix_spawn
-- Looking for posix_spawn - found
-- Looking for pread
-- Looking for pread - found
-- Looking for realpath
-- Looking for realpath - found
-- Looking for sbrk
-- Looking for sbrk - found
-- Looking for strtoll
-- Looking for strtoll - found
-- Looking for strerror
-- Looking for strerror - found
-- Looking for strerror_r
-- Looking for strerror_r - found
-- Looking for strerror_s
-- Looking for strerror_s - not found
-- Looking for setenv
-- Looking for setenv - found
-- Looking for dlopen
-- Looking for dlopen - found
-- Looking for dladdr
-- Looking for dladdr - found
-- Looking for __GLIBC__
-- Looking for __GLIBC__ - not found
-- Looking for sched_getaffinity
-- Looking for sched_getaffinity - not found
-- Looking for CPU_COUNT
-- Looking for CPU_COUNT - not found
-- Looking for pthread_getname_np in pthread
-- Looking for pthread_getname_np in pthread - found
-- Looking for pthread_setname_np in pthread
-- Looking for pthread_setname_np in pthread - found
-- Performing Test HAVE_INT64_T
-- Performing Test HAVE_INT64_T - Success
-- Performing Test HAVE_UINT64_T
-- Performing Test HAVE_UINT64_T - Success
-- Performing Test HAVE_U_INT64_T
-- Performing Test HAVE_U_INT64_T - Success
-- Performing Test HAVE_CXX_ATOMICS64_WITHOUT_LIB
-- Performing Test HAVE_CXX_ATOMICS64_WITHOUT_LIB - Success
-- Performing Test LLVM_HAS_ATOMICS
-- Performing Test LLVM_HAS_ATOMICS - Success
-- Performing Test SUPPORTS_VARIADIC_MACROS_FLAG
-- Performing Test SUPPORTS_VARIADIC_MACROS_FLAG - Success
-- Performing Test SUPPORTS_GNU_ZERO_VARIADIC_MACRO_ARGUMENTS_FLAG
-- Performing Test SUPPORTS_GNU_ZERO_VARIADIC_MACRO_ARGUMENTS_FLAG - Success
-- Native target architecture is X86
-- Threads enabled.
-- Doxygen disabled.
-- Go bindings enabled.
-- Found ld64 - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld
-- Could NOT find OCaml (missing: OCAMLFIND OCAML_VERSION OCAML_STDLIB_PATH) 
-- Could NOT find OCaml (missing: OCAMLFIND OCAML_VERSION OCAML_STDLIB_PATH) 
-- OCaml bindings disabled.
-- Could NOT find Python module pygments
-- Could NOT find Python module pygments.lexers.c_cpp
-- Could NOT find Python module yaml
-- LLVM host triple: i386-apple-darwin18.2.0
-- LLVM default target triple: i386-apple-darwin18.2.0
-- Performing Test C_SUPPORTS_FPIC
-- Performing Test C_SUPPORTS_FPIC - Success
-- Performing Test CXX_SUPPORTS_FPIC
-- Performing Test CXX_SUPPORTS_FPIC - Success
-- Building with -fPIC
-- Performing Test SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG
-- Performing Test SUPPORTS_FVISIBILITY_INLINES_HIDDEN_FLAG - Success
-- Found PythonInterp: /Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7 (found version "2.7.16") 
-- Constructing LLVMBuild project information
-- Targeting JSBackend
-- Targeting X86
-- Looking for sys/resource.h
-- Looking for sys/resource.h - found
-- Clang version: 6.0.1
-- Configuring done
CMake Warning (dev):
  Policy CMP0068 is not set: RPATH settings on macOS do not affect
  install_name.  Run "cmake --help-policy CMP0068" for policy details.  Use
  the cmake_policy command to set the policy and suppress this warning.

  For compatibility with older versions of CMake, the install_name fields for
  the following targets are still affected by RPATH settings:

   LTO
   libclang

This warning is for project developers.  Use -Wno-dev to suppress it.

-- Generating done
-- Build files have been written to: /Users/christophecerin/emscripten/myfastcomp/emscripten-fastcomp/build
MacBook-de-Christophe:build christophecerin$

Working with examples

You only need to install the SDK once! After that you can update to the latest SDK at any time using the following in a command prompt:

#
# Check if Python certificates are installed
#
MacBook-de-Christophe:emsdk christophecerin$ cd /Applications/Python\ 2.7/
MacBook-de-Christophe:Python 2.7 christophecerin$ ll
total 800
drwxr-xr-x@ 11 root  wheel    352 16 mai 16:58 .
drwxrwxr-x+ 81 root  admin   2592 16 mai 17:50 ..
drwxr-xr-x   5 root  wheel    160  3 mar 03:32 Extras
drwxr-xr-x   3 root  wheel     96 16 mai 16:58 IDLE.app
-rw-r--r--@  1 root  wheel      0  3 mar 03:33 Icon?
-rwxr-xr-x   1 root  wheel   1411  3 mar 03:33 Install Certificates.command
-rw-r--r--   1 root  wheel  13345  3 mar 03:33 License.rtf
lrwxr-xr-x   1 root  wheel     98 16 mai 16:58 Python Documentation.html -> /Library/Frameworks/Python.framework/Versions/2.7/Resources/English.lproj/Documentation/index.html
drwxr-xr-x   3 root  wheel     96 16 mai 16:58 Python Launcher.app
-rw-r--r--   1 root  wheel   8373  3 mar 03:33 ReadMe.rtf
-rwxr-xr-x   1 root  wheel   2534  3 mar 03:33 Update Shell Profile.command
MacBook-de-Christophe:Python 2.7 christophecerin$ ./Install\ Certificates.command  
 -- pip install --upgrade certifi
Collecting certifi
  Downloading https://files.pythonhosted.org/packages/60/75/f692a584e85b7eaba0e03827b3d51f45f571c2e793dd731e598828d380aa/certifi-2019.3.9-py2.py3-none-any.whl (158kB)
    100% |████████████████████████████████| 163kB 37kB/s 
Installing collected packages: certifi
Successfully installed certifi-2019.3.9
You are using pip version 18.1, however version 19.1.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
 -- removing any existing file or link
 -- creating symlink to certifi certificate bundle
 -- setting permissions
 -- update complete
MacBook-de-Christophe:Python 2.7 christophecerin$ 

# First, go into the sdk directory
MacBook-de-Christophe:~ christophecerin$ cd emsdk
MacBook-de-Christophe:emsdk christophecerin$

# Fetch the latest registry of available tools.
./emsdk update

# Download and install the latest SDK tools.
./emsdk install latest

# Set up the compiler configuration to point to the "latest" SDK.
./emsdk activate latest

# Activate PATH and other environment variables in the current terminal
source ./emsdk_env.sh

You should obtain the following information:

MacBook-de-Christophe:emsdk-portable christophecerin$ ./emsdk install latest
Installing SDK 'sdk-1.38.31-64bit'..
Installing tool 'clang-e1.38.31-64bit'..
Downloading: /Users/christophecerin/emsdk-portable/zips/emscripten-llvm-e1.38.31.tar.gz from https://s3.amazonaws.com/mozilla-games/emscripten/packages/llvm/tag/osx_64bit/emscripten-llvm-e1.38.31.tar.gz, 317264223 Bytes
 Unpacking '/Users/christophecerin/emsdk-portable/zips/emscripten-llvm-e1.38.31.tar.gz' to '/Users/christophecerin/emsdk-portable/clang/e1.38.31_64bit'
Done installing tool 'clang-e1.38.31-64bit'.
Installing tool 'node-8.9.1-64bit'..
The contents of file 'node-v8.9.1-darwin-x64.tar.gz' already exist in destination '/Users/christophecerin/emsdk-portable/node/8.9.1_64bit', skipping.
Done installing tool 'node-8.9.1-64bit'.
Installing tool 'emscripten-1.38.31'..
Downloading: /Users/christophecerin/emsdk-portable/zips/1.38.31.tar.gz from https://github.com/kripken/emscripten/archive/1.38.31.tar.gz, 41822751 Bytes
 Unpacking '/Users/christophecerin/emsdk-portable/zips/1.38.31.tar.gz' to '/Users/christophecerin/emsdk-portable/emscripten/1.38.31'
Done installing tool 'emscripten-1.38.31'.
Done installing SDK 'sdk-1.38.31-64bit'.
MacBook-de-Christophe:emsdk-portable christophecerin$ ./emsdk activate latest
Writing .emscripten configuration file to user home directory /Users/christophecerin/
The Emscripten configuration file /Users/christophecerin/.emscripten has been rewritten with the following contents:

import os
LLVM_ROOT='/Users/christophecerin/emsdk-portable/clang/e1.38.31_64bit'
EMSCRIPTEN_NATIVE_OPTIMIZER='/Users/christophecerin/emsdk-portable/clang/e1.38.31_64bit/optimizer'
BINARYEN_ROOT='/Users/christophecerin/emsdk-portable/clang/e1.38.31_64bit/binaryen'
NODE_JS='/Users/christophecerin/emsdk-portable/node/8.9.1_64bit/bin/node'
EMSCRIPTEN_ROOT='/Users/christophecerin/emsdk-portable/emscripten/1.38.31'
SPIDERMONKEY_ENGINE = ''
V8_ENGINE = ''
TEMP_DIR = '/var/folders/63/yyxnpnc51jb5fqt5y0q4br1w0000gn/T'
COMPILER_ENGINE = NODE_JS
JS_ENGINES = [NODE_JS]

To conveniently access the selected set of tools from the command line, consider adding the following directories to PATH, or call 'source ./emsdk_env.sh' to do this for you.

   /Users/christophecerin/emsdk-portable:/Users/christophecerin/emsdk-portable/clang/e1.38.31_64bit:/Users/christophecerin/emsdk-portable/node/8.9.1_64bit/bin:/Users/christophecerin/emsdk-portable/emscripten/1.38.31
Set the following tools as active:
   clang-e1.38.31-64bit
   node-8.9.1-64bit
   emscripten-1.38.31

MacBook-de-Christophe:emsdk-portable christophecerin$ source ./emsdk_env.sh
Adding directories to PATH:
PATH += /Users/christophecerin/emsdk-portable
PATH += /Users/christophecerin/emsdk-portable/clang/e1.38.31_64bit
PATH += /Users/christophecerin/emsdk-portable/node/8.9.1_64bit/bin
PATH += /Users/christophecerin/emsdk-portable/emscripten/1.38.31

Setting environment variables:
EMSDK = /Users/christophecerin/emsdk-portable
BINARYEN_ROOT = /Users/christophecerin/emsdk-portable/clang/e1.38.31_64bit/binaryen
EMSCRIPTEN = /Users/christophecerin/emsdk-portable/emscripten/1.38.31

MacBook-de-Christophe:emsdk-portable christophecerin$ 

Concurrent activities in the browser: using Pthreads

The Pthreads library is one of the fundamental library for creating concurrent activities in C/C++ languages. The basic example we investigate is this one:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

//void *print_message_function( void *ptr );
void *print_message_function( void *ptr )
{
     char *message;
     message = (char *) ptr;
     printf("%s \n", message);
     return (void *)NULL;
}

int main()
{
     pthread_t thread1, thread2;
     const char *message1 = "Thread 1";
     const char *message2 = "Thread 2";
     int  iret1, iret2;

    /* Create independent threads each of which will execute function */

     iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1);
     if(iret1)
     {
         fprintf(stderr,"Error - pthread_create() return code: %d\n",iret1);
         exit(EXIT_FAILURE);
     }

     iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2);
     if(iret2)
     {
         fprintf(stderr,"Error - pthread_create() return code: %d\n",iret2);
         exit(EXIT_FAILURE);
     }

     printf("pthread_create() for thread 1 returns: %d\n",iret1);
     printf("pthread_create() for thread 2 returns: %d\n",iret2);

     /* Wait till threads are complete before main continues. Unless we  */
     /* wait we run the risk of executing an exit which will terminate   */
     /* the process and all threads before the threads have completed.   */

     pthread_join( thread1, NULL);
     pthread_join( thread2, NULL); 

     //exit(EXIT_SUCCESS);
     return 1;
}

We compile this program as follows:

MacBook-de-Christophe:examples christophecerin$ emcc -v pthread.c -l pthread -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=2  -o pthread.html
shared:INFO: (Emscripten: Running sanity checks)
clang version 6.0.1  (emscripten 1.38.31 : 1.38.31)
Target: asmjs-unknown-emscripten
Thread model: posix
InstalledDir: /Users/christophecerin/emsdk-portable/clang/e1.38.31_64bit
 "/Users/christophecerin/emsdk-portable/clang/e1.38.31_64bit/clang" -cc1 -triple asmjs-unknown-emscripten -emit-llvm-bc -emit-llvm-uselists -disable-free -disable-llvm-verifier -discard-value-names -main-file-name pthread.c -mrelocation-model static -mthread-model posix -mdisable-fp-elim -no-integrated-as -mconstructor-aliases -dwarf-column-info -debugger-tuning=gdb -target-linker-version 305 -v -coverage-notes-file /tmp/emscripten_temp_ztkzOD/pthread_0.gcno -nostdsysteminc -nobuiltininc -resource-dir /Users/christophecerin/emsdk-portable/clang/lib/clang/6.0.1 -D __EMSCRIPTEN_major__=1 -D __EMSCRIPTEN_minor__=38 -D __EMSCRIPTEN_tiny__=31 -D _LIBCPP_ABI_VERSION=2 -D __EMSCRIPTEN_PTHREADS__=1 -Werror=implicit-function-declaration -fno-dwarf-directory-asm -fdebug-compilation-dir /Users/christophecerin/cilk-5.4.6/examples -ferror-limit 19 -fmessage-length 124 -fobjc-runtime=gnustep -fdiagnostics-show-option -fcolor-diagnostics -nobuiltininc -nostdsysteminc -isystem/Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include/libcxx -isystem/Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/lib/libcxxabi/include -isystem/Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include/compat -isystem/Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include -isystem/Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include/SSE -isystem/Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include/libc -isystem/Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/lib/libc/musl/arch/emscripten -isystem/Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/local/include -disable-O0-optnone -isystem/Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include/SDL -o /tmp/emscripten_temp_ztkzOD/pthread_0.o -x c pthread.c
clang -cc1 version 6.0.1 based upon LLVM 6.0.1 default target x86_64-apple-darwin18.2.0
ignoring nonexistent directory "/Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include/SSE"
#include "..." search starts here:
#include <...> search starts here:
 /Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include/libcxx
 /Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/lib/libcxxabi/include
 /Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include/compat
 /Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include
 /Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include/libc
 /Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/lib/libc/musl/arch/emscripten
 /Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/local/include
 /Users/christophecerin/emsdk-portable/emscripten/1.38.31/system/include/SDL
End of search list.
 "/Users/christophecerin/emsdk-portable/clang/e1.38.31_64bit/opt" /tmp/emscripten_temp_ztkzOD/pthread_0.o /Users/christophecerin/.emscripten_cache/asmjs/libdlmalloc_threadsafe.bc /Users/christophecerin/.emscripten_cache/asmjs/libpthreads.bc /Users/christophecerin/.emscripten_cache/asmjs/libc-mt.bc /Users/christophecerin/.emscripten_cache/asmjs/libc-wasm.bc /Users/christophecerin/.emscripten_cache/asmjs/libpthreads_asmjs.bc /var/folders/63/yyxnpnc51jb5fqt5y0q4br1w0000gn/T/emscripten_temp_Jb_gxo_archive_contents/muldc3.c.o /var/folders/63/yyxnpnc51jb5fqt5y0q4br1w0000gn/T/emscripten_temp_Jb_gxo_archive_contents/mulsc3.c.o -strip-debug -disable-debug-info-type-map -internalize -internalize-public-api-list=main,malloc,free,__errno_location,fflush -globaldce -disable-loop-vectorization -disable-slp-vectorization -vectorize-loops=false -vectorize-slp=false -o /tmp/emscripten_temp_ztkzOD/pthread.bc
 "/Users/christophecerin/emsdk-portable/clang/e1.38.31_64bit/llc" /tmp/emscripten_temp_ztkzOD/pthread.bc -march=js -filetype=asm -o /tmp/tmppo97zW.4.js -emscripten-stack-size=5242880 -O0 -emscripten-precise-f32 -emscripten-enable-pthreads -emscripten-assertions=1 -emscripten-no-aliasing-function-pointers -emscripten-global-base=1024 -emscripten-no-exit-runtime -emscripten-wasm -emscripten-only-wasm  

This compilation step produces the following pthreads files and pthread.html is the one you can download in your browser:

MacBook-de-Christophe:examples christophecerin$ ll pthread.*
-rw-r--r--  1 christophecerin  staff    1411 21 mai 11:18 pthread.c
-rw-r--r--@ 1 christophecerin  staff  107168 21 mai 11:18 pthread.html
-rw-r--r--  1 christophecerin  staff    1858 21 mai 11:18 pthread.html.mem
-rw-r--r--  1 christophecerin  wheel  218371 21 mai 11:18 pthread.js
-rw-r--r--  1 christophecerin  staff   67051 21 mai 11:18 pthread.wasm
-rw-r--r--  1 christophecerin  staff   10147 21 mai 11:18 pthread.worker.js

However, some configuration needs to be done for some parameters of your browser. First, we recommend to use Mozilla Firefox Nightly as your browser. Then, type about:config in the URL bar, and set the flags as follows:

The execution of the program should look as:

A general discussion about Pthreads in the browser with Emscripten and Web Assembly can be found here. See also this post. For optimizing the Emscripten codes at compile time, see this discussion.

You can play with this example by clicking on https://lipn.univ-paris13.fr/~cerin/HDU/pthread.html. Enjoy!

Interlude: parallel programming in C

You are now ready to learn more on parallel programming. Please, read this introductory material before going further in this lecture. In fact, it is not an interlude!

Computing an approximation of Π in parallel

Computing the digits of π is an old problem. Some sequential solutions are very tricky and require a good level in mathematics. See this page for instance.

The code shown below is the calculation of π by the method of numeric integration. John Wallis (1616-1703) discovered in 1655 a method for computing π. He considered a circle with a radius equal to 1, then he was looking for the area of one fourth of the circle. He proposed the formula $$\pi/4 = \int^{1}_{0}{\sqrt{1-x^2}}. dx$$ Remember that we simply start with the equation of a circle $$x^2+y^2=r^2$$ The algorithm has a loop which executes a predetermined number of iterations (num_steps) where a reduction over the variable 'sum' is done. The variable 'x' works like a partial result which has an independent value between iterations. At the end of the loop, the operation between 'step' and 'sum' produces 'pi'. The number π will be more precise if the number of iterations is greater.

    x=0;
    sum = 0.0;
    step = 1.0/(double) num_steps;
    for (i=0; i < num_steps; ++i) {
        x = (i+0.5)*step;
        sum = sum + 4.0/(1.0+x*x);
    }
    pi = step * sum;

The parallel code with Pthreads is as follows. Please, notice the semaphore that is used to protect the pi variable against concurrent writes. The idea is to distribute the integration steps in 4 threads, each working concurrently on a distinct part of the integration domain. We control the number of iterations with the numberOfIntervals = 200 instruction, meaning that each thread is running 200/4 iterations. Hence the following code:

//
// emcc pi.c -l pthread -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=4 -o pi.html
//
// If NUM_THREADS is too large, you may have the following message:
// Cannot enlarge memory arrays to size 17842176 bytes (OOM). Either
// (1) compile with -s TOTAL_MEMORY=X with X higher than the current
// value 16777216, (2) compile with -s ALLOW_MEMORY_GROWTH=1 which
// allows increasing the size at runtime, or (3) if you want malloc to
// return NULL (0) instead of this abort, compile with -s
// ABORTING_MALLOC=0
//
// It seems that the return values of pthread_create(), pthread_join()
// and pthread_mutex_unlock() always return 0 (True)
// This is not the case when we compile with gcc/clang
//

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>

#define NUM_THREADS     4

void            *pi_function(void *p);
//returns the value of pi
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
//creates a mutex variable
double          pi = 0.0;
int             k = 0;
double          sumvalue = 0, sum = 0;
double          intervalWidth, intervalMidPoint;

int
main()
{
     pthread_t       threads[NUM_THREADS];
     //creates the number of threads NUM_THREADS
     int             iret1;
     //used to ensure that threads are created properly
     // pthread_create(thread, attr, start_routine, arg)
     int             i;
     pthread_mutex_init(&mutex1, NULL);

     for (i = 0; i < NUM_THREADS; i++) {
          iret1 = pthread_create(&threads[i], NULL, pi_function, (void *)i);
          if (iret1) {
            //printf("ERROR; return code from pthread_create() is %d\n", iret1);
            //exit(-1);
          }
     }

     for (i = 0; i < NUM_THREADS; i++) {
          iret1 = pthread_join(threads[i], NULL);
          if (iret1) {
            //printf("ERROR; return code from pthread_create() is %d\n", iret1);
            //exit(-1);
          }
     }

     pthread_mutex_destroy(&mutex1);
     printf("Main: program completed. Exiting.\n");
     printf("The value of pi is  : %f\n", pi);

     return (0);
}

void           *
pi_function(void *p)
{
     int          rc;
     int          k;
     int          i;
     int          interval;
     int          numberOfIntervals = 200;
     /* Compute the interval width. */
     double          intervalWidth = 1.0 / numberOfIntervals;
     double          intervalMidPoint, area1 = 0.0;

     k = (int)p;
     //printf("---- %d ----\n", k);
     for (interval = (numberOfIntervals / NUM_THREADS) * k; interval < (numberOfIntervals / NUM_THREADS) * k + (numberOfIntervals / NUM_THREADS); interval++) {
          intervalMidPoint = (interval + 0.5) * intervalWidth;
          area1 += 4.0 / (1.0 + intervalMidPoint * intervalMidPoint);
     }
     area1 *= intervalWidth;

     pthread_mutex_lock(&mutex1);
     //locks the share variable pi and p16
     pi += area1;
     printf("iteration (%d) %f %d %d %d\n", k, pi, (numberOfIntervals / NUM_THREADS) * k, (numberOfIntervals / NUM_THREADS) * k + (numberOfIntervals / NUM_THREADS), (numberOfIntervals / NUM_THREADS));
     rc = pthread_mutex_unlock(&mutex1);
     if (rc) {
       //printf("ERROR; return code from pthread_create() is %d\n", rc);
     }
     pthread_exit(&sumvalue);
}

You can test it (with the Mozilla Firefox Nightly browser, please) at https://lipn.univ-paris13.fr/~cerin/HDU/pi.html.

Another relation for an estimate of π it to start with the formula \(arctan(1)={\pi\over 4}\) and to use the Maclaurin formula for arctan() as follows: \(\arctan x = x - {x^3\over 3} + {x^5\over 5} - {x^7\over 7} + \cdots\ \ \ |x| < 1\) but this series is also converging for \(x=1\). Thus we have \({\pi\over 4} = 1 - {1\over 3} + {1\over 5} - {1\over 7} + {1\over 9} - {1\over 11} + \cdots\) or, much more simply \(\pi = 4 - {4\over 3} + {4\over 5} - {4\over 7} + {4\over 9} - {4\over 11} + \cdots\) You can now proceed with an implementation.

The Cpp-Taskflow library

Cpp-Taskflow is a fast C++ header-only library to help you quickly write parallel programs with complex task dependencies. Cpp-Taskflow is by far faster, more expressive, fewer lines of code, and easier for drop-in integration than existing parallel task programming libraries such as OpenMP Tasking and Intel TBB FlowGraph.

Please, install it and try the examples!

Work stealing: Cilk programming

The Cilk framework follows the principle of work stealing on idle cores. The libray can still be downloaded from this site. The compilation with emcc is quite difficult and not straitforward because the Cilk library is dependent of the type of CPU (see this message grep -i "Unsupported CPU" ./../runtime/cilk-sysdep.h). Another entry point for Clik is this point.