WebAssembly/Native Compiler

From Lazarus wiki
Jump to navigationJump to search

Recently, Free Pascal's support for WebAssembly has improved so much, that the compiler is capable to compile itself for this platform and the new compiler is fully capable of compiling itself again. This page describes how to produce a native compiler for WebAssembly and how it can be used to compile other programs.

Building the native compiler

To obtain a working WebAssembly-hosted compiler, the following compiler options are recommended:

-O- -g- -CTbfexceptions -CTsaturatingfloattoint

Example commands for Linux:

make -j `nproc` clean OS_TARGET=wasip1 CPU_TARGET=wasm32 BINUTILSPREFIX= PP=/usr/bin/ppcx64
make -j `nproc` all OS_TARGET=wasip1 CPU_TARGET=wasm32 BINUTILSPREFIX= PP=/usr/bin/ppcx64 CROSSOPT="-O- -g- -CTbfexceptions -CTsaturatingfloattoint"

This will produce a native compiler binary in the compiler directory: ppcwasm32.wasm

Running the native compiler with WasmTime

In order to compile programs, the compiler needs access to a file system. Filesystem access is completely restricted by default, so you need to specify the -dir option, in order to give it access to certain directories. Note that you can also rename/remap the host directory names, so they are visible under a different path inside the guest. Another important thing to note is that the WASI API doesn't have the concept of a current directory, so the Free Pascal RTL emulates this internally. Since there's no official way to pass the name of the current directory from the host to the guest, the Free Pascal RTL uses the first shared directory as the default. This means that if you want to share the entire file system with the guest via the --dir / option, the guest will see / as the default directory. If you want to share the entire file system, plus the current directory, you can do this:

wasmtime run --dir `pwd` --dir / ppcwasm32.wasm <options>

Running a full native make cycle

Under Linux it is possible to register a handler for .wasm files, so they can be run natively, and thus do a full native make all, where the compiler compiles itself several times, then compiles the rtl with the final compiler. After that it compiles fpmake and tries to start building the packages in the packages directory, but this fails, because the WASI API lacks an API function for launching a process, so the native fpmake.wasm doesn't work.

Replicating this process isn't entirely trivial, because there are several caveats, that need to be worked around:

  • The WASI API doesn't support setting the executable attribute on files, so the .wasm files, produced by the compiler don't have this attribute set. Because of that, Linux refuses to execute them.
  • The Free Pascal makefiles detects the source OS as Linux, instead of WASI (which is partially correct, it is indeed Linux, running WASI only via an emulation layer). This causes an inconsistency with the OS's expected executable file extension, so in certain places the makefiles try executing ppcwasm32 instead of ppcwasm32.wasm and fpmake instead of fpmake.wasm.
  • The default stack size of wasmtime needs to be increased, otherwise compilation of fpmake blows the stack.

Here's an example wrapper script wasmtime_wrapper.sh that contains hacks that workaround these issues:

#! /bin/sh

set -e

FPC_WASM_DIR=/path/to/fpc

wasmtime run -W max-wasm-stack=16000000 --dir `pwd` --dir / "$@"
# UGLY HACK 1: set the executable attribute of .wasm files, produced during compilation
chmod --quiet +x $FPC_WASM_DIR/compiler/*.wasm $FPC_WASM_DIR/packages/fpmake.wasm || true
# UGLY HACK 2: create symlinks, so that ppcwasm32.wasm and fpmake.wasm can be invoked without the .wasm extension
ln -f -s ppcwasm32.wasm $FPC_WASM_DIR/compiler/ppcwasm32
ln -f -s fpmake.wasm $FPC_WASM_DIR/packages/fpmake

Here's a script that registers a handler for executing .wasm files (needs to be executed as root):

#! /bin/sh

{ echo ':wasm32-wasi:M::\x00asm:\xff\xff\xff\xff:/path/to/wasmtime_wrapper.sh:
' > /proc/sys/fs/binfmt_misc/register; } 2>/dev/null

Here's a make command that should make the compiler rebuild itself, and the rtl:

make clean
make all PP=/path/to/ppcwasm32.wasm OPT="-CTbfexceptions -CTsaturatingfloattoint" OVERRIDEVERSIONCHECK=1 NOWPOCYCLE=1