Yes. The WASM version runs entirely in the user’s browser and except for downloading the SWI-Prolog WASM image no server communication happens (except what your app does by itself of course). The WASM version can make networks requests using JavaScript or fetch/3 from library(wasm).
Thanks, that’s great! This eliminates the hosting problem for my app.
Another question: is the process for adding a pack (in my case, CLP(BNR)) to a WASM app documented somewhere ? I saw how to load scripts, and that the SWI library is included, but nothing about whole packs.
All this is a bit in flux. In theory, you can use :- use_module('https://...')/ to load the main file. This is nice, but especially downloading raw files this way from github tends to be slow. I haven’t tried other sources. Alternatively you can compile the code to a single .qlf file using
swipl qlf compile --include mainfile.pl
Place the resulting main.qlf on a server and load this using use_module/1. This however requires the same version of SWI-Prolog and if there is conditional compilation in the code, the conditions of the compiler are used.
I think it is also possible to create a saved state and use that with swipl-bundle-nodata. Not sure how though.
What makes the matter also more convenient, is to allow
URLs in the file search path and have automatically the
origin URL in the file search path. This is very much what a
URLClassLoader in Applets did (*) in Java. I used that in
formerly Jekejeke Prolog to load Prolog text , you can
then simply write use_module(library(clpBNR)).
I carried over the approach to Dogelog Player but with
a small restriction, the file search path has only two
elements , basically the working directory sometimes,
and a library directory sometimes, both can be URLs.
The two directories are intialized as follows:
/* working directory = user code base */
set_codebase(document.URL)
/* library directory = system code base */
set_bootbase(import.meta.url)
But import.meta.url is from ES2020 JavaScript modules. So if
you use require.js polyfill I don’t know whether this will work.
To settle on JavaScript modules might also help providing
broader fetch(), since features such as CORS are only available
if you use JavaScript modules. But your JavaScript code might
not anymore run in older browsers such as Internet Explorer.
(*) I wrote “did” because Java Applets are pretty much dead,
even banned from modern browsers. So it was not a matter of
voluntarily choice, the whole migration to JavaScript. It rather
resulted from the Java Applets squeeze in 2017 with JDK 9.
But an URLClassLoader is more clever, it can also deal with
archive format .jar and .zip, I haven’t replicated that yet.
That actually works, but currently you should use your own alias, i.e., not library. The main problem is that the normal SWI-Prolog file search may involve quite a few probes at different locations using different extensions. Due to the latency that gets pretty slow over the internet. If the target would allow getting a directory listing we could avoid a lot of probes. This doesn’t seem to work on GitHub raw content:
You use an absolute URL (http or https). This can also load .qlf files.
You define an alias using file_search_path/2. The target location is computed the normal way and the system guarantees a .pl extension.
Ideas are welcome. Integration with notably GIT servers seems welcome. Would also be nice if you can edit locally and upload. That could work for servers that allow for PUT or, for GitHub, possibly using GitHub.js
Maybe a new WASM topic, but given that there’s some interest in clpBNR on WASM, I thought I should do some minimal testing using SWI-Tinker. As noted above, loading via a URL from GitHub really works and the code appears to be somewhat functional. Unfortunately simple tests expose some current flaws, as noted in the following examples:
Result is OK but leaves unnecessary choicepoint? On local version:
?- X::real.
X::real(-1.0e+16, 1.0e+16).
same on WASM:
?- X::real.
X::real(-1.0e+16, 1.0e+16) ;
false.
Incorrect answer formatting of narrow intervals (32 bit arithmetic?); local:
?- {Y>=0, Y**2==X}, {X==3}.
X = 3,
Y:: 1.732050807568877... .
WASM:
?- {Y>=0, Y**2==X}, {X==3}.
Y = 1.7320508075688772,
X = 3.
At this point I stopped further testing. I suspect 2. has something to do with supporting the answer formatting hooks - there is some platform specific code required for SWISH and it may be a similar issue here. The others may just be bugs or related to 32 bit implementation and associated libraries - all insights welcome. (I don’t currently have the skill set to dig deeper.)
I understand that WASM is still a work in progress, so I’m inclined to just keep trying these simple tests as new releases become available. But I did want to give a heads up to those adventurous souls who may be trying to use clpBNR on WASM.
I’m a little surprised. Where SWISH redefines most of the toplevel, SWI-Tinker does not. It only redefines reading the next query to use yield, get a string from the browser and parse it. It also redefines waiting for the user response after a qeury completes with a choicepoint. Output is basically unaffected.
Would be nice to add the ‘*’ command to the toplevel such that we can see the choicepoint.
As for the wrong answers, the WASM version passes the arithmetic tests (many by you) when using node (v22) to run it. I guess that arithmetic may also depend on the WASM engine, i.e., on the browser used.
Note that you can use the usual command line debugger tools such as spy/1, trace/0, etc. to see what is going. Also trace/1 works.
Noting your surprise, I dug a little deeper. FWIW, I see similar behaviour on Chrome and Firefox.
I tried setting spy points and they indeed work, but “creep” seems to skip (as if flag generate_debug_info was set to false for the module), so it’s difficult to get to the details, including what’s leaving the choicepoint.
I think both the answer format and incorrect answers can be attributed to bad rounding (at least for now). Using the ?- {Y>=0, Y**2==X}, {X==3}. example boils down to:
Local version:
?- Zl is roundtoward(3**1r2,to_negative), Zh is roundtoward(3**1r2,to_positive).
Zl = 1.732050807568877,
Zh = 1.7320508075688774.
WASM:
?- Zl is roundtoward(3**1r2,to_negative), Zh is roundtoward(3**1r2,to_positive).
Zl = Zh, Zh = 1.7320508075688772.
Without proper outward rounding, the interval narrows to a point which is not a solution. Also the “point” interval will be unified with this value explaining the top level result.
IIRC this rounding control was done through the fsetround function in C, but I don’t know which library browsers use.
If you get the same result on node as I see in the browser, there’s obviously a hole in the arithmetic tests that needs to be filled.
You should at least get warnings from the tests in tests/rational/test_ieee754.pl, e.g., in MacOS-Firefox:
?- test_roundto(2**0.5).
Warning: [Thread 1] IEEE 754 dubious rounding: 2**0.5: z = 1.4142135623730951, p = 1.4142135623730951
Warning: [Thread 1] IEEE 754 dubious rounding: 2**0.5: n = 1.4142135623730951, p = 1.4142135623730951
true.
Oh, just noticed the following in the test module doc:
Note: the Emscripten (WASM) version fails most tests. Rounding modes
are not implemented and several operations are very imprecise. For now
we disabled this test on WASM.
WASM/SWI-Tinker support probably involves few headaches. Unless people start pushing the WASM implementers, it is unlikely that the rounding issues will be fixed any time soon. The only short term solution I see is to include our own float libraries. Possibly we could lift the LibBF float facilities? That does mean headaches though
It appears that the Emscripten front end compiles C to JavaScript. Standard JS (ECMAScript) has no support for alternative rounding modes. Paraphrasing ECMA-262:
(This procedure corresponds exactly to the behaviour of the IEEE 754-2019 roundTiesToEven mode.)
So no, I have no expectation this will be fixed.
Hard to see what the motivation would be for this; use local or SWISH instead.
That is, AFAIK, not correct. In the early days, Emscripten compiled C to JavaScript. Now it compiles to WASM, which is a low-level VM. At least, that is what I understand. I’m happy to stand corrected. But, I assume that the WASM standard looks at the JavaScript standard for many low-level operations.
Yes, I should have read the Emscripten doc more carefully:
Emcc uses Clang and LLVM to compile to WebAssembly. Emcc also emits JavaScript that provides API support to the compiled code. That JavaScript can be executed by Node.js, or from within HTML in a browser.
Now there appears be a WASM development fork to implement a form of rounding control (rounding-mode-control/proposals/rounding-mode-control/Overview.md at 8af7ebe401f432b563c1814e846d188bffa2040f · WebAssembly/rounding-mode-control · GitHub) but it doesn’t appear to be very compatible with C’s fenv.h based model. Rather than using the C model which sets a mode in the floating point environment for the evaluation of an expression (or greater), it provides individual VM instructions for each primitive arithmetic operation. So it’s hard to imagine existing C code that could be compiled to WASM using that model. This would also apply to power and all the elementary functions (not hardware instructions) implemented in software libraries.
So the conclusion is the same - not likely anytime soon.
Since JavaScript allows to access float bitpatterns, you can
implement certain stuff by yourself. But I don’t know when to
choose littleEndian=true. The code uses littleEndian=false:
function nextAfter(value, direction) {
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setFloat64(0, value);
let bits = view.getBigUint64(0, false);
if (direction > value) {
bits += BigInt(1); // Add one ULP
} else if (direction < value) {
bits -= BigInt(1); // Subtract one ULP
} else {
return value;
}
view.setBigUint64(0, bits, false);
return view.getFloat64(0);
}
It seems to work, at least the single instance I tested:
/* Welcome to Node.js v23.9.0, Windows 11 Pro */
> nextAfter(3.14, 3.15)
3.1400000000000006
> nextAfter(3.14, 3.13)
3.1399999999999997
Should somehow also translate to WASM. But I
don’t know whether it captures your problem. The
add one ULP works, since a carry increments the
exponent. There is a similar argument for subtract
one ULP in that you get into subnormals. But I am no
expert here, got some help from a chat bot.
While you can usually implement some form of rounding mode control in software, I believe there are two significant disadvantages:
It is grossly inefficient compared to rounding with machine instructions.
If you’re implementing interval arithmetic, the result is two ULP’s wide, whereas ideally it is only 1, as defined in the IEEE754 standard. (Elementary functions in libraries aren’t quite there yet.)
C compilers and libraries provide pretty direct access as defined historically by Intel’s floating point co-processors; other languages and implementations vary.