notesassorted ramblings on computer

Executing Hare Programs using Quebex

quebex is a work-in-progress software analysis framework built around the QBE intermediate language. Similar to LLVM IR, QBE is designed to be used as an intermediate representation for compiler design. However, conceptually, intermediate representations are Turing-complete programming languages. Since code in different high-level languages can be transformed to such representations, they are interesting targets for developing language-agnostic program analysis tooling.

Currently, quebex focuses on dynamic program analysis and allows execution of QBE programs in a simulated environment. While quebex is still work-in-progress, it can already execute the QBE representation emitted by compiler frontends such as cproc (a C11 compiler). Recently,1 it also gained support for executing the QBE output emitted by the Hare compiler. The following is intended as a preliminary demonstration of quebex’s current capabilities using Hare 0.26.0.

Hello, World!

When executing a software under test in a simulator, a central challenge is enabling interactions with the environment (e.g., the file system). Presently, quebex achieves this by providing a simulated version of selected C library functions (e.g., puts(3)). Since this is specific to C, we cannot directly use functions from the Hare standard library to interact with the environment (e.g., fmt::println). However, since we can call C functions from Hare, we can write a “Hello, World” Hare program as follows:

use types::c;

export @symbol("puts") fn puts(
	s: const *c::char,
) int;

export fn main() void = {
	const hello = c::nulstr("Hello, World!\0");
	puts(hello);
}

In order to execute this program using quebex, we need to obtain its QBE representation. This requires passing an undocumented option to hare-build(1): -t ssa. Assuming the code above was written to a file named hello.ha we can compile it as follows:

$ hare build -t ssa hello.ha

This will save the QBE representation in the current directory in a file named hello.ssa. Unfortunately, this file only includes our main() function and not the functions from the standard library required for FFI (e.g., c::nulstr). However, with the above command, Hare will also store a QBE representation of standard library modules in $HARECACHE. On my system, $HARECACHE defaults to ~/.cache/hare. Within this cache, we need to find the file providing c::nulstr and concat that with our hello.ssa:

$ cat $(grep -lr '^function ..* \$types\.c\.nulstr' ~/.cache/hare) hello.ssa > hello.std.ssa

Afterward, we can execute the resulting hello.std.ssa QBE program using quebex:

$ quebex hello.std.ssa
Hello, World!

Why is this useful?

Generally speaking, we can perform all kinds of tests on the executed software by running it in a simulated environment. For example, we can inject errors to test the error-handling code of the software under test. Further, we can more easily check certain non-functional properties (e.g., memory safety).2 Specifically regarding quebex, its current analysis capabilities are documented in its README.

Future Work

Better support for Hare in quebex would require an environment model that makes it easier to support Hare standard library functions such as fmt::println. However, while Hare is an interesting target to validate quebex’s QBE implementation, better support for it is currently not a priority. Apart from language support, it would be great to have tooling that makes it easier to combine multiple QBE source files into a single one. This would also be useful for translating complex C software using QBE-based C compilers (e.g., cproc or SCC). Essentially, such a tool would be the equivalent of wllvm from the LLVM ecosystem.


  1. That is, starting with commit 0b144e61b2c6e33525faefba4e3056c7b2033948 in the quebex Git repository.↩︎

  2. The popular Valgrind tool, which is commonly used to find memory errors, is in fact also a simulator for an IR (Valgrind VEX).↩︎