Compile QBE input files into wat or wasm. Uses QBE internals and binaryen. ISC licensed.
Usage: qbe-wasm [-h]
[-o <file>]
[-t <target>]
[-d <flags>]
[-b <base>]
[-s <skip>]
[-O <level>]
files...
-h: print this help text
-o <file>: output to file
-t <target>: generate wasm (default) or wat
-d <flags>: currently ignored
-b <base>: global base for linear memory (default 1024)
-s <skip>: skip an optimization pass
-O <level>: 0 to disable optimizations
While still in active development, qbe-wasm is able to compile 100% of
cproc's test suite and 95% of
QBE's test suite.
Both QBE and cproc are vendored so their tests can be leveraged during development. The tests are compiled to either wat or wasm depending on if they can be successfully run. Helping to convert tests from wat to wasm could be a good first issue.
Four of the QBE tests require patches. Two are since wasm won't allow some things QBE does, such
as a variable on the lhs of a void function call. Two others are due to a long double
issue described in vendor/cproc/doc/software.md. Patches are used as a last resort but
seemed better than just skipping the test entirely. Also they serve as an illustration
of possible fixes for issues that may arise during compilation.
In addition to the tests, there are also live web-based demos including the Game of Life written in C (cproc) and Hare, QBE's mandel test and some code borrowed from around the web - requires js.
qbe-wasm is developed without the use of AI. If you wish to contribute
please respect and honor that choice.
Git submodules are used, so clone/pull recursive.
I currently develop this on Alpine. QBE need to come from testing at the time
of writing. See the Dockerfile for required packages.
You can also use the Dockerfile to make a dev container if you make img
then make sh.
Once all development dependencies are installed, running make will build
the code, demos and run tests. If you need to change how the tests are
built, change scripts/tgen and then make tgen.
If you wish to build just qbe-wasm use samu bin/qbe-wasm.
qbe-wasm can be used as a drop-in replacement for QBE. It supports the
wat and wasm targets, and defaults to wasm if an unknown target is
specified. This allows, based on the tool, using qbe-wasm instead of QBE
while still accepting the tool's internally set flags.
If this does not work, getting the tool to emit the QBE textual format then
feeding that into qbe-wasm works as well.
If your compilation flow results in the usage of wasm-ld, a follow-up pass
through wasm-opt is likely desired for a "release" build. If the default
global base is used, an example invocation would be:
wasm-opt -lmu -O4 -o some.wasm some.wasm
a) When compiling cproc, using a ./configure similar to the one below will
enable using qbe-wasm, wat2wasm and wasm-ld as qbe, as and ld:
./configure \
--with-qbe="qbe-wasm -t wat -s memory-packing" \
--with-as="wat2wasm --no-check -r" \
--with-ld="wasm-ld --import-undefined --no-demangle --no-merge-data-segments -no-gc-sections -no-stack-first" \
--with-ldso=""
Then run make. Refer to the cproc docs and source for more information.
The vendored cproc is built this way (see build.ninja) and is used to
generate tests and demos for qbe-wasm. It has been tested on Alpine.
b) If you do not wish to (re)compile cproc, scripts/cproc contains examples
of how to wrap qbe-wasm, wat2wasm and wasm-ld as qbe, as and ld
respectively. If these are injected in the path prior to the cproc invocation,
you can build and link as expected:
PATH=scripts/cproc:$PATH cproc -o a.o -c a.c
PATH=scripts/cproc:$PATH cproc -o b.o -c b.c
PATH=scripts/cproc:$PATH cproc -nostdlib -o c.wasm a.o b.o
PATH=scripts/cproc:$PATH cproc -nostdlib -o single.wasm single.c
Note: these scripts are just examples and may need to be tweaked for your needs based on tooling versions, system, other flags used, etc. They are not actively tested but expected to be a place to start from.
c) cproc -emit-qbe -o some.qbe -c some.c will emit QBE's textual format
which can then be fed to qbe-wasm.
a) Building freestanding Hare code is performed much like the cproc examples
above. For example:
HAREPATH=freestanding/lib \
QBE=bin/qbe-wasm \
QBEFLAGS="-t wat -s memory-packing" \
AS=wat2wasm \
ASFLAGS="--no-check -r" \
LD=scripts/hare/ld \
hare build -RFo some.wasm cmd/some
The use of scripts/hare/ld is currently required to drop some linker flags
that come along for the ride and are not compatible with wasm-ld.
b) hare build -t ssa -o some.qbe some/module will emit QBE's textual format
which can then be fed to qbe-wasm.
Note: Hare stdlib support is in progress.
QBE has a nice property - all the type information describing a function sig
is available at the call site. Since qbe-wasm is parsing QBE input and has no
access to the front-end language or foward decls, we have to assume that the
call sites for a given function are uniform. For example, something like:
%r0 =l call $syscall1(l 12, l %r0, )
call $syscall1(l 12, l %r0, )
Will result in an error along the lines of call* type must match for the
second call. While a drop could be issued in this case its only a workaround -
what if those two lines were swapped? Which is the source of truth wrt to the
signature?
Another issue wrt call site uniformity arises in that if a function is called but not declared it is marked as an import. So if the call sites are not uniform the generated import may not be as expected. If the QBE file contained only:
call $syscall1(l 12, l %r0, )
Then the import has a signature of [i64, i64] -> [] while the host likely assumes
[i64, i64] -> [i64].
In short - wasm is much stricter in its output requirements than traditional QBE targets, so some issues may arise.
Rudimentary wasi_snapshot_preview1 support via inferred imports. For an example
see tests/wasi/tap1.c. Other wasi things have not been tried.
In a scenario like vendor/cproc/test/func-noreturn.qbe, there doesn't seem
to be a way to get the return type of main, so a void type is inferred due
to no return statements.
Not all function pointers compile correctly, wip.
Compiling non-freestanding Hare is a wip.