Pkg.go.dev is a new destination for Go discovery & docs. Check it out at pkg.go.dev/modernc.org/qbe/qbec and share your feedback.

Command qbec

Command qbec is a compiler for the extended QBE intermediate language.

Note: All documentation examples are for 64 bit architectures.

To install/update qbec invoke:

$ go get [-u] modernc.org/qbe/qbec

Online documentation ¶

See http://godoc.org/modernc.org/qbe/qbec.

This software is a proof of concept. It's slowly nearing alpha status.

Supported platforms and architectures ¶

The code is known to work and was tested on

os arch gcc version --------------------------- linux 386 7.4 linux amd64 7.4

No other operating system/architecture/gcc version combinations are supported ATM.

Other, presumably higher versions of gcc may work as well, but every version has different bugs.

Support for linux/{arm,arm64} is planned.

Support for other Unix-like systems and Windows is desirable but needs help from the respective platform experts as well as maintainers and testers

Maintainers/testers for other operating systems/architectures are welcome.

To compile QBE programs invoke

$ qbec [flags] [path ...]

For the documentation of flags, see: https://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html

Principle of operation ¶

The compiler wraps the GNU gcc compiler. It detects all arguments with extension .qbe, compiles them to C files in a temporary directory and replaces the arguments with the pathnames of the generated C files. Finally the compiler calls gcc with the adjusted arguments to create the executable/object/asembler file according to the provided command line options.

Optimization and stacks ¶

The compiler does not yet optimize the intermediate language completely. The translation to C thus keeps many of the temporary variables and the compiler offloads optimizing them away to gcc. This works well, except it does not when no -O is given or when -O0 is given. The outcome in such cases is that function stacks may sometime require substantially more space. Some heavily recursive code, like libpcre, when translated to QBE intermediate language, may fail tests due to default stack size limits on some operating systems.

The remedy is to ensure the -O or -O1 or similar command line option is always present when invoking the compiler.

Compiling binaries ¶

For example, having

$ cat hello.qbe export function w $main() { @start call $printf(l $fmt, ...) ret 0 } data $fmt = { b "Hello, world!

", b 0, } $

To produce an executable file:

$ qbec -O -w hello.qbe && ./a.out Hello, world! $ file a.out a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=418f8b10232cc51c9cf1e51970009a82c300a44f, with debug_info, not stripped $

Translating to C ¶

Adding the -o <path> option, where <path> has .c extension, will produce the C version of the input and exit without calling gcc:

$ qbec -o hello.c hello.qbe $ cat hello.c // qbec -o hello.c hello.qbe #include <stdint.h> extern char printf; int32_t main(); static int8_t fmt[]; static int8_t fmt[] = {72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 10, 0}; int32_t main() { ((void(*)())(intptr_t)&printf)((intptr_t)&fmt); return 0; } $

Translating to assembler ¶

Adding the -o <path> option, where <path> has .s extension, instructs gcc to stop after generating the assembly code and keep the file:

$ qbec -O -w -o hello.s hello.qbe $ cat hello.s .file "qbe-367043457.c" .text .globl main .type main, @function main: .LFB0: .cfi_startproc subq $8, %rsp .cfi_def_cfa_offset 16 movl $fmt, %edi movl $0, %eax call printf movl $0, %eax addq $8, %rsp .cfi_def_cfa_offset 8 ret .cfi_endproc .LFE0: .size main, .-main .data .align 8 .type fmt, @object .size fmt, 15 fmt: .byte 72 .byte 101 .byte 108 .byte 108 .byte 111 .byte 44 .byte 32 .byte 119 .byte 111 .byte 114 .byte 108 .byte 100 .byte 33 .byte 10 .byte 0 .ident "GCC: (SUSE Linux) 7.4.1 20190424 [gcc-7-branch revision 270538]" .section .note.GNU-stack,"",@progbits $

The same outcome can be achievd also by using the -S option:

$ qbec -O -w -S hello.qbe

QBE intermediate language often provides insufficient information to infer types of external symbols. That's why the extern symbols in the intermediate C form are most often not compatible withe the actual definitions in, say libc. For example

$ qbec -O hello.qbe && ./a.out /tmp/qbe-948308461.c:7:13: warning: built-in function ‘printf’ declared as non-function extern char printf; ^~~~~~ Hello, world! $

As seen above, the warning is harmless, the linker produces a working binary anyway.

It's possible to turn off the warnings by adding the -w flag.

$ qbec -O -w hello.qbe && ./a.out Hello, world! $

Interoperability with C and assembler code ¶

QBE code can call C and/or assembler functions - and vice versa. For example:

$ cat main.c #include <stdio.h> int g(int i, int j); int main() { printf("%i

", g(2, 3)); return 0; }

The g function is written in QBE intermediate language:

$ cat g.qbe export function w $g(w %i, w %j) { @start %a =w call $f(w %i, w 10) %b =w call $f(w %j, w 100) %c =w mul %a, %b %d =w call $h(w %c) ret %d }

The f function is written in assembler:

$ cat f.s # int f(int i, int j) { return i + j; } .text f: leal (%rdi,%rsi), %eax ret

And the h function is written in C:

$ cat h.c int h(int i) { return -i; }

Putting all the pieces together

$ qbec -O -w main.c f.s g.qbe h.c && ./a.out -1236 $

Compile Time Function Evaluation ¶

Wikipedia article

Sample session

$ gcc --version gcc (SUSE Linux) 7.4.1 20190424 [gcc-7-branch revision 270538] Copyright (C) 2017 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ cat fib.c #include <stdio.h> static int fib(int n) { if (n == 0) { return 0; } if (n == 1) { return 1; } return fib(n-1) + fib(n-2); } int main() { printf("%i

", fib(42)); return 0; } $ cat fib.qbe function w $fib(w %n) { @.1 jnz %n, @.2, @.3 @.3 ret 0 @.2 %.1 =w ceqw %n, 1 jnz %.1, @.5, @.4 @.5 ret 1 @.4 %n1 =w sub %n, 1 %t1 =w call $fib(w %n1) %n2 =w sub %n, 2 %t2 =w call $fib(w %n2) %t3 =w add %t1, %t2 ret %t3 } export function w $main() { @.1 %x =w call $fib(w 42) call $printf(l $s, w %x, ...) ret 0 } data $s = { b "%i

", b 0, } $ rm -f a.out ; time gcc -O3 fib.c && time ./a.out real 0m0.066s user 0m0.056s sys 0m0.010s 267914296 real 0m0.867s user 0m0.867s sys 0m0.000s $ rm -f a.out ; time qbec -O3 -w fib.qbe && time ./a.out real 0m0.036s user 0m0.019s sys 0m0.016s 267914296 real 0m0.001s user 0m0.000s sys 0m0.001s $

Where to find the source code ¶

The compiler is actually implemented by the Compile function here: https://godoc.org/modernc.org/qbe#Compile

Note: It's possible to call the Compile function from your program directly instead of using the os/exec package to run the compiler executable.

QBE intermediate language ¶

Overview: https://c9x.me/compile/

Reference: https://c9x.me/compile/doc/il.html

Extensions: https://godoc.org/modernc.org/qbe#hdr-QBE_extensions

QBE vs LLVM: https://c9x.me/compile/doc/llvm.html

main.go

☞ Handling of `env` in function parameters is not yet implemented. //TODO