SWI-Prolog in the browser using WASM

This topic discusses running SWI-Prolog in your browser using WASM (Web Assembler).

Updates

Below we list important updates that are also handled in this wiki page when applicable.

  • [2022-08-09] Fixed several build (dependency) issues. See updated build instructions.
  • [2022-08-08] Fixed yield. Add auto yielding such that the browser remains responsive and queries can be aborted. Allow creating multiple files, Added persistent (using the browser localStorage) command line history and files.
  • [2022-08-05] Extended JavaScript binding, run the browser shell using yield.
  • [2022-08-03] “Native friend” mechanism for building is no longer needed.
  • [2022-08-03] Added -s ALLOW_MEMORY_GROWTH=1 to allow (notably) for bigger stacks)

SWI-Prolog in your browser

There are two options for running SWI-Prolog in your browser:

  • Using SWISH. This is a server based solution where the actual Prolog code is executed in a sandboxed environment on a (shared) server. SWISH is a comprehensive and mature solution for running SWI-Prolog in your browser that can support many deployment options.
  • Actually running it in your browser. This is accomplished by compiling the SWI-Prolog C sources using Emscripten to WASM code. The initial port has been created by @rla. @dmchurch has made some improvements. @josderoo used it to run the EYE reasoner Inspired by the Ciao playground I (@jan) took another look at the WASM port.

A first shot at a SWI-Prolog shell in your browser

A very first version of running SWI-Prolog interactively in the browser was made by @rla. Since then Emscripten evolved and so did the configuration for SWI-Prolog. I have combined the basic test code and @rla’s initial shell to make something that at least performs the very basic tasks. It is online at

How it works

Normally, SWI-Prolog is an interactive application that reads from and writes to a console. That doesn’t work in your browser because the JavaScript functions called are supposed to complete (quickly). @rla found a nice trick around that: we initialize Prolog and for running a query we put the query into a string that is used for input and we call break/0. This starts a toplevel that reads and runs the query. The price is that the query cannot receive any input (i.e., cannot read). Another problem we are faced with is that long running Prolog queries make the browser unresponsive, i.e., we need to return to the browser’s event loop to create a responsive web page. This is solved using foreign yield, a mechanism to make the VM main function (PL_next_solution()) return such that it can be resumed. Yielding can be dong explicitly using js_yield/2. This is currently used for reading the next toplevel query and, if a query succeeds with a choicepoint, for reading whether the user wants the next result or stop. Finally, the VM auto yields every N inferences, controlled by the Prolog flag heartbeat. The JavaScript wrapper resumes the VM, roughly escaping to the browser event loop every 20ms.

Note that an alternative solution is run Prolog in a web worker. This however isolates Prolog from the page, requiring postMessage() to be used to communicate between the page and Prolog.

The SWI-Prolog web appliance uses the Emscripten virtual file system. SWI-Prolog runs in /prolog and the normal Prolog home, containing the boot file and libraries is in /swipl. You can use library(shell) utilities ls/0, pwd/0, cd/1 to look around.

The editor is a simple HTML textarea. The (Re)consult button copies the content of the textarea into a the file ‘/file.pl’ and calls consult/1 on that file. It has a dropdown to select a file and buttons to add a new file or delete a file. Files are saved to the browser’s localStorage on page unload and reloaded on page load. The query input field provides history using the arrow up/down. The history is also saved to the browser’s localStorage.

There is one extra predicate, js_call(+JavaScript) that runs JavaScript code using JavaScript eval(), so you can do e.g.,

?- js_call("alert('Hello world!')").

Status, issues and future work

Lacking features and issues of the SWI-Prolog WASM port

  • Threads
  • Unbounded integers and rational numbers (GMP)
  • Foreign extensions
  • A way to call JavaScript and get results back
  • A browser DOM manipulation library
  • Wait for input (read, etc)
    • Now deals with reading toplevel queries and prompt for more answers using yield
    • auto yielding keeps the page responsive
    • Yielding is still problematic
      • It cannot happen if there are intermediate Prolog → WASM (C) → Prolog callbacks
      • Auto yielding currently happens at the exit port. Recursive loops that call no other predicates only exit when all is done. Possibly we can also yield at the enter port (after head unification).
  • Sync the API with Ciao where possible

The future of this SWI-Prolog shell

The shell above is just a toy. It is intended as a simple platform to help debugging and resolving the above issues. A likely future scenario is to integrate the WASM based solution into SWISH.


Building the WASM version

These instructions assume Ubuntu Linux but can be adjusted to other platforms. Please edit this page if you did so for your platform of choice.

Install emscripten and the zlib library

See Download and install — Emscripten 3.1.19-git (dev) documentation. These instructions install emscripten in ~/.wasm/emsdk on a Linux machine (requires about 1.1 Gb disk)

cd
mkdir wasm
cd wasm
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest

Now build the zlib dependency for WASM using

cd ~/wasm
wget https://zlib.net/zlib-1.2.12.tar.gz
tar xf zlib-1.2.12.tar.gz
cd zlib-1.2.12/
source ../emsdk/emsdk_env.sh 
emconfigure ./configure
emmake make

Download and install SWI-Prolog from the git sources

For dependencies, see Prerequisites for Debian based systems (Ubuntu, Mint, ...)

Download the sources

cd
mkdir -p src
cd src
git clone https://github.com/SWI-Prolog/swipl-devel.git
cd swipl-devel
git submodule update --init

Build the WASM version using Emscripten

cd ~/src/swipl-devel
mkdir build.wasm

Now save the following text to a new file configure and make it executable using chmod +x configure

WASM_HOME=$HOME/wasm
source $WASM_HOME/emsdk/emsdk_env.sh
TOOLCHAIN=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake
[ -f $TOOLCHAIN ] || echo "Could not find emscripten toolchain"

cmake -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN \
          -DCMAKE_BUILD_TYPE=Release \
          -DZLIB_LIBRARY=$WASM_HOME/zlib-1.2.12/libz.a \
          -DZLIB_INCLUDE_DIR=$WASM_HOME/zlib-1.2.12 \
          -DMULTI_THREADED=OFF \
          -DUSE_SIGNALS=OFF \
          -DUSE_GMP=OFF \
          -DBUILD_SWIPL_LD=OFF \
          -DINSTALL_DOCUMENTATION=OFF \
          -G Ninja ..

Now configure and build the version in the build.wasm directory using

./configure
ninja

To update to the latest version one should run the commands below. Removing src/wasm-preload is required to make sure changes to the Prolog libraries are correctly incorporated in src/swipl-web.data which populates /swipl in the WASM version.

git pull
git submodule update --init
cd build.wasm
rm -rf src/wasm-preload && ninja

Running using Node.js

If all went right, you can now run the wasm version using node as

$ node src/swipl.js
Welcome to SWI-Prolog (32 bits, version 8.5.15-26-gc1ca50a94)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

    CMake built from "/home/janw/src/swipl-devel/build.wasm"

For online help and background, visit https://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

1 ?-

Running in your browser

The generated files for your browser are src/swipl-web.js, src/swipl-web.wasm and src/swipl-web.data. The Prolog shell discussed above can be found in swipl-devel/src/wasm/shell.html. It can be started using a Prolog based web server in the same directory named server.pl. The server must be started in the build directory as it uses src/swipl-web.* to find the WASM components.

cd ~/src/swipl-devel/build.wasm
swipl ../src/wasm/server.pl [--port=8000]

How to support such work

As noted in this post, give these GitHub repositories a star SWI-Prolog, Ciao and SWISH

Star history

Discourse Linkify words

Discourse has a plug-in (theme component) that searches post for specific text and automatically converts the text to a link (linkify).

These words with a # prefix were added for this topic

#devel_commits -To understand the changes to the code, review the GitHub commits for SWI-Prolog development repository. To make it easy to identify the link when writing a post noting the GitHub SWI-Prolog development repository commits just add #devel_commits,
Note: That while not always most of the commits related to WASM start with the name WASM.

#wasm_demo - For those that follow along with the post actively, knowing were to find the demo site is easy. For those dropping in the middle via a Google search some years in the future it can become a needle in a haystack search for the site. To make it easy to identify the link when writing a post noting the demo site just add #wasm_demo,

#wasm_wiki - While the discussion thread gets all activity the post that has the crucial facts is not always identified in the post that discuss them. To make it easy to identify the link when writing a post noting this wiki page just add #wasm_wiki

8 Likes