Wiki Discussion: SWI-Prolog in the browser using WASM

I have already started doing so in some places. As I think of it, the js_yield(+Promise, -Result) is in fact await, no? It does (if I understand this correctly) the same: yield from the current (Prolog) execution and resume if the thing we are waiting for is resolved. I was already quite happy after replacing the original (string) request and reply with arbitrary objects to conclude that the only thing you need as input for js_yield/2 is a Promise :slight_smile: If so, we should name it await/2 :slight_smile:

The main issue is that SWI-Prolog cannot yield from everywhere :frowning: Notably it cannot yield from Prolog → C → Prolog calls. Possibly some cases can be fixed by hand (the general case requires a yield mechanism in C). The other nasty part is the debugger. It is probably hard to yield from the debugger as each of the ports comes with its own state that needs to be saved and restored.

Please let everybody who sees potential in this for his/her work look around for opportunities. One big investor is easiest, but several smaller ones work as well. I think I proved that the yield based design is viable. What needs to be done for a nice polished system is roughly this (might have missed something?)

  • Allow yield to work from many more places, including the debugger.
  • Write a proper debugger (we have all the stuff from the GUI traces that is also used by @oskardrums debug adapter, so it is proven to be reusable).
  • Provide asynchronous implementations for many more of the currently blocking primitives.
  • Get engines to the single threaded version. With engines we can have multiple threads of control. Engines do not need threads, but the current engine implementation sits on top of the thread implementation. These would need to be untangled.
  • Error handling, testing.
  • Documentation, demos, …
  • (Modular) packaging and simple deployment of applications.

Got one more step: we can now load files from URLs. This is now available on #wasm_demo, where you can run

?- ['scasp/prolog/scasp'].

to get s(CASP) loaded. It also runs :slight_smile: The sCASP source is made available from the server under /wasm/scasp/. The way it works is

  • If we try to load a file that doesn’t exist in the virtual file system, ensure it has a .pl extension and try to download it as a relative URL.
  • When loading from a URL, all relative paths are loaded relative to the URL of the file being loaded.
  • You can also load from an absolute URL, provided the server allows cross-origin access.

The loading performance is a little disappointing. I also noted that Prolog files from the virtual file system are loaded rather slow. On my machine it takes a little over 2 seconds to load s(CASP) compared to 0.29 using the native SWI-Prolog :frowning:

Haven’t tested whether it also loads .qlf files, but I fear it will try to interpret them as UTF-8 files somewhere along the process …


It was a bit of a challenge :slight_smile: The WASM version can now load .qlf files from a URL. That is nice. Even nicer is that I managed to extend qcompile/2 to include indirectly loaded files except for files from the Prolog system. So, we can do

?- qcompile('prolog/', [include(user)]).

And end up with a single file scasp.qlf that you can load in any compatible SWI-Prolog instance. If you go to #wasm_demo you an run this and get going:

?- time(['scasp.qlf']).
% 1,671,738 inferences, 0.638 CPU in 0.638 seconds (100% CPU, 2620280 Lips)

The current scasp source conditionally depends on a file that is not part of the WASM version. I created the .qlf file using node /path/to/swipl.js to get system with the same libraries.

Note that these work too. Both times are reported on second invocation, thus using the browser cache.

?- time(['scasp/prolog/scasp']).
% 2,278,937 inferences, 1.465 CPU in 1.465 seconds (100% CPU, 1555588 Lips)

Or, loading from GitHub raw content:

?- time(['']).
% 2,278,481 inferences, 1.425 CPU in 1.425 seconds (100% CPU, 1598934 Lips)

This is really great :grinning: it starts to make deployment quite feasible.

I don’t have emscripten setup, is there a place where I could download the swipl.js file? I would like to try out generating some qlf files and then loading them in the browser.

As is, there is no swipl.js. If there is no conditional compilation in your code you can just use any recent 8.5.x version to create the .qlf. If there is conditional compilation, you can probably tweak the condition such that you can set some flags to get the result that you need for the browser version.

Streamlining the distribution of the WASM version is still something to consider. Maybe @rla has experience? (How) is the npm version updated? Could/should we have the stuff on a CDN?

Ahh, even better!

I was referring to the wasm node.js wiki section that says:

I guess this is not possible anymore?

It is, but only in the build environment. I see that the npm package does provide swipl.js, but that doesn’t help much as it doesn’t ship with the .data file that provides the Prolog part for the browser. swipl.js is built as a necessary step in the WASM build process to perform the Prolog parts of the build using node.

@jan, @swi sorry for a slow reaction to SWI-Prolog releases. The NPM package is still very unstable but it bundles all necessary files including swipl.js, and swipl.wasm. They are in dist/swipl/*. The package README contains how to use these files.

I just built and published new version based on 8.5.17. Right now building has to be done manually but I hope we find a solution for that.

Regarding 8.5.17, it does not run on node anymore. The issue is in src/wasm/prolog.js which contains:

HTMLCollection.prototype.toList = function()
{ const ar = [];

  for(let i=0; i<this.length; i++)

  return ar;

This triggers TypeError in nodejs since HTMLCollection does not exist there.

1 Like

Great, this should make it user-friendly to generate qlf files to be deployed on the web without worrying about conditional compilation.

This seems to be a utility/helper method, could it not just be left out of the nodejs version by not compiling it in if HTMLcollection does not exist?

You are doing well :slight_smile: Thanks! Pushed a fix for the HTMLCollection. I still have trouble understanding the browser vs node issue. As I understand it, we have

  • swipl-web.js + swipl-web.wasm + These work in the browser. Notably provides the content for the Emscripten file system that appears as /swipl in the browser.
  • swipl.js and swipl.wasm. These work with node. I think swipl-web.wasm and swipl.wasm are pretty much the same, but I’m not sure. However, there is no and instead the files are on the host physical file system.

If this picture is correct, the node version should be shipped with a directory structure that provides the Prolog home. The build system creates swipl.js and swipl.wasm as these components are needed to run the Prolog steps for the build using node. In this setup the Prolog resources are in the home directory of the build tree.

Given the (large) Prolog resources that need to be distributed completely differently, it seems the browser and node versions are pretty much disjoint and the best we can do is to distribute them as different npm packages. Right?

P.s. node src/swipl.js works fine despite the HTMLCollection issue. Why is that?

the new qcompile/2 is quite nice, but it doesn’t seem to handle use_module/2 or reexport/2 properly.

:- use_module(m2).

m1 :-

:- module(m2, [

:- reexport(m3,[m3/0]).

m2 :-
   format("Hello from m2"),

:- module(m3, [

m3 :-
   format(", and from m3 via re_export").

Test 1 without qcompile/2:

1 ?- m1.
Hello from m2, and from m3 via re_export

Test 2 with qcompile:

1 ?- qcompile('',[include(user)]).
$ swipl -l m1.qlf -g m1
ERROR: -g m1: catch/3: Unknown procedure: m2:m2/0

Handling reexport/1,2 was the problem. The code for dealing with .qlf files created from multiple Prolog files is a bit old … Fixed with 7e685450f6117d2d20c2e45d123f635faaa13484.

1 Like

I will take a look. It used to be that .wasm and .data files were common for both nodejs and browser variants but seems like they are not anymore. It used to be:

  • Browser: swipl-web.js + swipl-web.wasm +
  • Nodejs: swipl.js + swipl-web.wasm +

I cloned the GitHub - rla/npm-swipl-wasm: SWI-Prolog WebAssembly build as a NPM package, built the project and I can confirm that it runs on both the browser and node. For those who want to test, after building

  • for the browser, start the node http-server in the root of the cloned repo using http-server ., open your browser at the indicated location and navigate to examples/browser.html. That should print a to the browser window. Why not “hello world”? :slight_smile:

  • For node, simply run node examples/run-on-node.js, which again prints a.

I was confused about the development environment, where after the build you can run node src/swipl.js to get a normal interactive SWI-Prolog instance that “sees” the physical file system. Looks like this does work in my local built, so something may be wrong with the way it is build in docker. I’ll sync both.

1 Like

I don’t get it. In my local build as well as when looking into @rla’s build container I can run node swipl.js and when all is in the right place, it runs the normal Prolog toplevel. Without everything in the right place I end up with an error message, either not being able to load swipl.wasm or from Prolog not being able to find its startup resources.

When I go to the dist directory though, node swipl.js exits immediately without any message. Using strace to trace its system call suggests it doesn’t even try to find the .wasm file :frowning: Maybe someone more familiar with the node and JavaScript eco system can solve this?

I debugged these issues a bit. I don’t seem to be able to run toplevel and I think that’s expected behavior since PL_initialise does not run toplevel.‘PL_initialise’) PL_initialise will print the banner and then exit as nothing keeps the nodejs process around.

The MODULARIZE option (which we also use) for Emscripten turns generated SWI-Prolog module code into a factory function that needs to be imported and then executed. This will locate (at least it will try) .wasm and .data files. That’s why the example in the package also uses:

const SWIPL = require("./swipl/swipl"); // imports the module
// ...
const swipl = await SWIPL({ options }); // runs the factory function and places the instance into swipl variable


Qt 6.4 Released

With Qt 6.4 we are taking the support for WebAssembly out of technology preview. With Qt for WebAssembly, Qt developers can use their existing skills, and often existing code, to target the web. Applications targeting the web assembly platform can run in most modern web browsers, and are easily distributed like any other web content. Thanks to near-native performance, and the rich UI and 3D features of Qt Quick and Qt Quick 3D, solutions requiring heavy data processing and demanding visualization can now easily be built for the web.

I have made two enhancements to #wasm_demo

  • Replace the <textarea> with CodeMirror and use the Prolog mode that is part of SWISH. Doesn’t use SWISH’s enhanced Prolog mode using Prolog support (which should be a lot simpler using the WASM version than server interaction).

  • Allow yield while loading local files. This does keep the browser responsive while loading large files and it allows loading from URLs from a local file. So, we can copy paste this: and use it:

:- use_module('').

p :- not q.
q :- not p.

And now

?- ? p.
% s(CASP) model
{ p,      not q
} ; 

More updated to #wasm_demo

  • Provide server-side example programs. These are added to the file dropdown and when selected copied to your local store, so you can load and edit them. Currently there are only two, one loading s(CASP) from Github and one manipulating the shell itself through DOM manipulations.
    If you have nice examples, please share them.
  • Changed the console to use a more structured DOM. The console now has a sequence of div elements that each reflect a query and its answers. This allows collapsing and removing queries and in theory a lot more. As experiment it now collapses all old queries. That gives much more overview of the history and allows selectively opening them. Please share whether you like it.
  • support the edit/1 interface.

A mayor problem when you do fancy stuff is that it is easy to get Prolog involved in multiple asynchronous queries. That is fine, as long as they are strictly nested, i.e., we can start a new asynchronous query while yielded from an older one. We must however complete the new one before we can continue the old one. That cannot be solved without engines. All we can hope for on the short term is to detect this (Prolog now crashes badly).

1 Like

Hello, I wrote a little online prolog editor along the lines of SWISH but using this WASM build. I mainly needed it to draw the trace as a graphical tree, but it might be useful to someone.

P.S. I implemented “asynchronous” queries by running them in separate workers.
Live Version