This commit is contained in:
2026-06-11 10:59:54 -06:00
commit 8650a71f67
159 changed files with 78653 additions and 0 deletions

141
doc/abi.txt Normal file
View File

@@ -0,0 +1,141 @@
==================
System V ABI AMD64
==================
This document describes concisely the subset of the amd64
ABI as it is implemented in QBE. The subset can handle
correctly arbitrary standard C-like structs containing
float and integer types. Structs that have unaligned
members are also supported through opaque types, see
the IL description document for more information about
them.
- ABI Subset Implemented
------------------------
Data classes of interest as defined by the ABI:
* INTEGER
* SSE
* MEMORY
~ Classification
1. The size of each argument gets rounded up to eightbytes.
(It keeps the stack always 8 bytes aligned.)
2. _Bool, char, short, int, long, long long and pointers
are in the INTEGER class. In the context of QBE, it
means that 'l' and 'w' are in the INTEGER class.
3. float and double are in the SSE class. In the context
of QBE, it means that 's' and 'd' are in the SSE class.
4. If the size of an object is larger than two eightbytes
or if contains unaligned fields, it has class MEMORY.
In the context of QBE, those are big aggregate types
and opaque types.
5. Otherwise, recursively classify fields and determine
the class of the two eightbytes using the classes of
their components. If any is INTEGER the result is
INTEGER, otherwise the result is SSE.
~ Passing
* Classify arguments in order.
* INTEGER arguments use in order `%rdi` `%rsi` `%rdx`
`%rcx` `%r8` `%r9`.
* SSE arguments use in order `%xmm0` - `%xmm7`.
* MEMORY gets passed on the stack. They are "pushed"
in the right-to-left order, so from the callee's
point of view, the left-most argument appears first
on the stack.
* When we run out of registers for an aggregate, revert
the assignment for the first eightbytes and pass it
on the stack.
* When all registers are taken, write arguments on the
stack from right to left.
* When calling a variadic function, %al stores the number
of vector registers used to pass arguments (it must be
an upper bound and does not have to be exact).
* Registers `%rbx`, `%r12` - `%r15` are callee-save.
~ Returning
* Classify the return type.
* Use `%rax` and `%rdx` in order for INTEGER return
values.
* Use `%xmm0` and `%xmm1` in order for SSE return values.
* If the return value's class is MEMORY, the first
argument of the function `%rdi` was a pointer to an
area big enough to fit the return value. The function
writes the return value there and returns the address
(that was in `%rdi`) in `%rax`.
- Alignment on the Stack
------------------------
The ABI is unclear on the alignment requirement of the
stack. What must be ensured is that, right before
executing a 'call' instruction, the stack pointer `%rsp`
is aligned on 16 bytes. On entry of the called
function, the stack pointer is 8 modulo 16. Since most
functions will have a prelude pushing `%rbp`, the frame
pointer, upon entry of the body code of the function is
also aligned on 16 bytes (== 0 mod 16).
Here is a diagram of the stack layout after a call from
g() to f().
| |
| g() locals |
+-------------+
^ | | \
| | stack arg 2 | '
| |xxxxxxxxxxxxx| | f()'s MEMORY
growing | +-------------+ | arguments
addresses | | stack arg 1 | ,
| |xxxxxxxxxxxxx| /
| +-------------+ -> 0 mod 16
| | ret addr |
+-------------+
| saved %rbp |
+-------------+ -> f()'s %rbp
| f() locals | 0 mod 16
| ... |
-> %rsp
Legend:
* `xxxxx` Optional padding.
- Remarks
---------
* A struct can be returned in registers in one of three
ways. Either `%rax`, `%rdx` are used, or `%xmm0`,
`%xmm1`, or finally `%rax`, `%xmm0`. The last case
happens when a struct is returned with one half
classified as INTEGER and the other as SSE. This
is a consequence of the <@Returning> section above.
* The size of the arguments area of the stack needs to
be computed first, then arguments are packed starting
from the bottom of the argument area, respecting
alignment constraints. The ABI mentions "pushing"
arguments in right-to-left order, but I think it's a
mistaken view because of the alignment constraints.
Example: If three 8 bytes MEMORY arguments are passed
to the callee and the caller's stack pointer is 16 bytes
algined, the layout will be like this.
+-------------+
|xxxxxxxxxxxxx| padding
| stack arg 3 |
| stack arg 2 |
| stack arg 1 |
+-------------+ -> 0 mod 16
The padding must not be at the end of the stack area.
A "pushing" logic would put it at the end.

1207
doc/il.txt Normal file

File diff suppressed because it is too large Load Diff

98
doc/llvm.txt Normal file
View File

@@ -0,0 +1,98 @@
===========
QBE vs LLVM
===========
Both QBE and LLVM are compiler backends using an SSA
representation. This document will explain why LLVM
does not make QBE a redundant project. Obviously,
everything following is biased, because written by me.
- Scope
-------
QBE is a much smaller scale project with different goals
than LLVM.
* QBE is for amateur language designers.
It does not address all the problems faced when
conceiving an industry-grade language. If you are
toying with some language ideas, using LLVM will
be like hauling your backpack with a truck, but
using QBE will feel more like riding a bicycle.
* QBE is about the first 70%, not the last 30%.
It attempts to pinpoint, in the extremely vast
compilation literature, the optimizations that get
you 70% of the performance in 10% of the code of
full blown compilers.
For example, copy propagation on SSA form is
implemented in 160 lines of code in QBE!
* QBE is extremely hackable.
First, it is, and will remain, a small project
(less than 8 kloc). Second, it is programmed in
non-fancy C99 without any dependencies. Third,
it is able to dump the IL and debug information in
a uniform format after each pass.
On my Core 2 Duo machine, QBE compiles in half a
second (without optimizations).
- Features
----------
LLVM is definitely more packed with features, but there
are a few things provided in QBE to consider.
* LLVM does NOT provide full C compatibility for you.
In more technical terms, any language that provides
good C compatibility and uses LLVM as a backend
needs to reimplement large chunks of the ABI in
its frontend! This well known issue in the LLVM
community causes a great deal of duplication
and bugs.
Implementing a complete C ABI (with struct arguments
and returns) is incredibly tricky, and not really
a lot of fun. QBE provides you with IL operations
to call in (and be called by) C with no pain.
Moreover the ABI implementation in QBE has been
thoroughly tested by fuzzing and manual tests.
* LLVM IL is more cluttered with memory operations.
Implementing SSA construction is hard. To save its
users from having to implement it, LLVM provides
stack slots. This means that one increment of
a variable `v` will be composed of three LLVM
instructions: one load, one add, and one store.
QBE provides simple non-SSA temporaries, so
incrementing `v` is simply done with one instruction
`%v =w add %v, 1`.
This could seem cosmetic, but dividing the size of
the IL by three makes it easier for the frontend
writers to spot bugs in the generated code.
* LLVM IL is more cluttered with type annotations and
casts.
For the sake of advanced optimizations and
correctness, LLVM has complex IL types. However,
only a few types are really first class and many
operations of source languages require casts to be
compiled.
Because QBE makes a much lighter use of types, the
IL is more readable and shorter. It can of course be
argued back that the correctness of QBE is jeopardized,
but remember that, in practice, the large amount
of casts necessary in LLVM IL is undermining the
overall effectiveness of the type system.

15
doc/native_win.txt Normal file
View File

@@ -0,0 +1,15 @@
There is an experimental amd64_win (native Windows ABI and calling
convention).
In tree, this is currently only tested via cross-compilation from a
Linux host, and using wine to run the tests.
You'll need something like:
sudo apt install mingw64-w64 dos2unix wine
and then
make check-amd64_win
should pass.

20
doc/rv64.txt Normal file
View File

@@ -0,0 +1,20 @@
=========
RISC-V 64
=========
- Known issues
--------------
ABI with structs containing floats is not yet supported.
- Possible improvements
-----------------------
rv64_isel() could turn compare used only with jnz into b{lt,ge}[u].
- Helpful links
---------------
RISC-V spec: https://github.com/riscv/riscv-isa-manual/releases/latest/download/riscv-spec.pdf
ASM manual: https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md
ABI: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc

23
doc/win.txt Normal file
View File

@@ -0,0 +1,23 @@
===================
Windows Quick Start
===================
Only 64-bit versions of windows are supported. To compile
this software you will need to get a normal UNIX toolchain.
There are several ways to get one, but I will only describe
how I did it.
1. Download and install [@1 MSYS2] (the x86_64 version).
2. In an MSYS2 terminal, run the following command.
pacman -S git make mingw-w64-x86_64-gcc mingw-w64-x86_64-gdb
3. Restart the MSYS2 terminal.
4. In the new terminal, clone QBE.
git clone git://c9x.me/qbe.git
5. Compile using `make`.
[1] http://www.msys2.org