Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@ Booth — Changelog

## Unreleased

### Architecture

- #160: DCE and mem2reg move instructions without moving `inst_lines[]`
with them, so every line number past the first deleted instruction
pointed at the wrong source. Four sites fixed
(Zane Hambly, 2026-08-09)

### Build

- #160: vendor Kauri (MIT) as `src/kauri.h`, included from `barracuda.h`,
so `KA_GUARD`, `KA_CHK` and `KA_PNEW` are available tree-wide
(Zane Hambly, 2026-08-09)

### CI and tests

- #160: `make repro` compiles every test file twice under `--amdgpu`,
`--nvidia-ptx` and `--ir` and compares the bytes, so the deterministic
layout `bir.h` claims is checked rather than assumed
(Zane Hambly, 2026-08-09)

## 2026-08-07

Version 0.5.2.
Expand Down
26 changes: 26 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,32 @@ Booth is written in a defensive C99 style. I spent too much time staring at NASA

**No floats where integers will do.** If you're comparing ratios, cross-multiply. Floating point is for GPU shader maths, not compiler internals.

### Kauri

Three of those rules used to be things I just tried to remember. Now there are macros for them. `barracuda.h` pulls in [Kauri](https://github.com/Zaneham/kauri), so they're already available anywhere in the tree and you don't need to include anything.

```c
/* Bounded loop. g counts down, so a cone that won't converge stops
* instead of hanging. Pick a bound you can justify. */
KA_GUARD(g, 64);
while (changed && g--) {
changed = fold_once(M);
}

/* Untrusted index. Returns 1 when out of bounds, so it reads as
* "if that's rubbish, refuse". Internal bookkeeping doesn't need it. */
if (KA_CHK(idx, M->num_insts)) return BC_ERR_VERIFY;

/* Pool allocation. Index 0 is the sentinel, so 0 means the pool is
* full. Pass the sentinel upwards rather than winding the counter back. */
uint32_t ni = KA_PNEW(M->num_insts, BIR_MAX_INSTS);
if (ni == 0) return 0;
```

Most of the existing code was written before these existed, so there's plenty that could use them. If you're reading a file and spot a loop that could take a guard, or an index coming in from outside that isn't checked, a PR is very welcome. If you're not sure whether a particular one wants it, raise an issue and we can work it out, that's a more interesting conversation than it sounds.

Two things worth knowing. `KA_PNEW` evaluates its counter twice, so give it a plain lvalue and nothing with side effects. And Kauri has an arena allocator and a result type that Booth doesn't use, since allocation happens once per phase and errors come back as `BC_ERR_*`, so there's no need to reach for `KA_TRY`.

### Naming

Function and variable names are short, 4-7 characters. Think of it like reading a motorway sign at 100km/h, you want "SH1 NORTH" not "STATE_HIGHWAY_ONE_NORTHBOUND_DIRECTION". When you're reading a thousand lines of instruction selector at 2am you want `ra_gc` not `regalloc_graphcolor`. Look at the newer code for the pattern: `isel_emit`, `mk_hash`, `enc_vop3`, `xt_meta`, `dce_copy`.
Expand Down
11 changes: 8 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ ifeq ($(UNAME_S),Linux)
ALT_RT = $(OBJDIR)/src/runtime/lf_gpu_hsa.o
endif

SOURCES = src/main.c \
SOURCES = src/main.c src/kauri_impl.c \
src/fe/bc_err.c src/fe/bc_render.c src/fe/preproc.c src/fe/lexer.c src/fe/parser.c src/fe/sema.c \
src/ir/bir.c src/ir/bir_print.c src/ir/bir_lower.c src/ir/bir_mem2reg.c src/ir/bir_cfold.c src/ir/bir_dce.c src/ir/bir_struct.c src/ir/bir_insert.c src/ir/bir_sroa.c src/ir/bir_inline.c \
src/tdf/tdf.c src/tdf/tdf_lower.c src/tdf/tdf_fission.c src/tdf/tdf_place.c src/tdf/tdf_noc.c \
Expand Down Expand Up @@ -113,7 +113,7 @@ TSRC = tests/tmain.c tests/tsmoke.c tests/tcomp.c tests/tenc.c \
tests/tbackend.c

TOBJS = $(TSRC:%.c=$(OBJDIR)/%.o)
COBJS = $(OBJDIR)/src/ir/bir.o $(OBJDIR)/src/ir/bir_print.o $(OBJDIR)/src/ir/bir_lower.o $(OBJDIR)/src/ir/bir_mem2reg.o $(OBJDIR)/src/ir/bir_cfold.o $(OBJDIR)/src/ir/bir_dce.o $(OBJDIR)/src/ir/bir_struct.o $(OBJDIR)/src/ir/bir_insert.o $(OBJDIR)/src/ir/bir_sroa.o $(OBJDIR)/src/ir/bir_inline.o \
COBJS = $(OBJDIR)/src/kauri_impl.o $(OBJDIR)/src/ir/bir.o $(OBJDIR)/src/ir/bir_print.o $(OBJDIR)/src/ir/bir_lower.o $(OBJDIR)/src/ir/bir_mem2reg.o $(OBJDIR)/src/ir/bir_cfold.o $(OBJDIR)/src/ir/bir_dce.o $(OBJDIR)/src/ir/bir_struct.o $(OBJDIR)/src/ir/bir_insert.o $(OBJDIR)/src/ir/bir_sroa.o $(OBJDIR)/src/ir/bir_inline.o \
$(OBJDIR)/src/tdf/tdf.o $(OBJDIR)/src/tdf/tdf_lower.o $(OBJDIR)/src/tdf/tdf_fission.o $(OBJDIR)/src/tdf/tdf_place.o $(OBJDIR)/src/tdf/tdf_noc.o \
$(OBJDIR)/src/tensix/rv_enc.o $(OBJDIR)/src/tensix/rv_buf.o $(OBJDIR)/src/tensix/rv_elf.o $(OBJDIR)/src/tensix/rv_isel.o $(OBJDIR)/src/tensix/noc.o $(OBJDIR)/src/tensix/emit.o \
$(OBJDIR)/runtime/soft_fp.o $(OBJDIR)/runtime/sysprint.o \
Expand All @@ -133,6 +133,11 @@ COBJS = $(OBJDIR)/src/ir/bir.o $(OBJDIR)/src/ir/bir_print.o $(OBJDIR)/src/ir/b
test: $(TARGET) trunner
./trunner --all

# bir.h claims a deterministic layout. This makes that a property rather
# than an intention, and it only stays cheap if it runs from now on.
repro: $(TARGET)
tests/reprocheck.sh

trunner: $(TOBJS) $(COBJS)
$(CC) $(TCFLAGS) -o $@ $^ $(LIBS) $(DL_LIB)

Expand Down Expand Up @@ -239,4 +244,4 @@ clean:
# linked in and the build silently disagrees with the source.
-include $(OBJECTS:.o=.d) $(TOBJS:.o=.d) $(HOSTRT:.o=.d)

.PHONY: all clean test install uninstall coverage
.PHONY: all clean test repro install uninstall coverage
4 changes: 4 additions & 0 deletions src/barracuda.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#include <stdio.h>
#include <string.h>

/* Bounded loops and checked indices. The arena goes unused here, since Booth
* allocates once per phase and never in a hot path. */
#include "kauri.h"

/* ---- Version ----
* The 0.5 release was tagged v5.01, which was a typo for 0.5.1 and made the
* compiler look four major versions further along than it is. Corrected here;
Expand Down
9 changes: 8 additions & 1 deletion src/ir/bir_dce.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,13 @@ static void compact(opt_t *S)
for (uint32_t j = 0; j < B->num_insts; j++) {
uint32_t ii = B->first_inst + j;
if ((S->dead[ii / 32] >> (ii % 32)) & 1) continue;
if (wr != ii)
if (wr != ii) {
M->insts[wr] = M->insts[ii];
/* The line table is indexed by instruction position, so it
* has to move with them or every line past the first dead
* instruction points at the wrong source. */
M->inst_lines[wr] = M->inst_lines[ii];
}
wr++;
}
M->blocks[abs_b].first_inst = block_start;
Expand Down Expand Up @@ -299,6 +304,8 @@ int bir_dce(bir_module_t *M)

memmove(&M->insts[dst], &M->insts[src],
count * sizeof(bir_inst_t));
memmove(&M->inst_lines[dst], &M->inst_lines[src],
count * sizeof(M->inst_lines[0]));

for (uint16_t bi = 0; bi < F->num_blocks; bi++) {
uint32_t abs_b = F->first_block + bi;
Expand Down
9 changes: 9 additions & 0 deletions src/ir/bir_mem2reg.c
Original file line number Diff line number Diff line change
Expand Up @@ -746,24 +746,31 @@ static void step7_compact(m2r_t *S)
/* Second pass: copy instructions into new positions.
* Use a static scratch buffer to avoid overlap issues. */
static bir_inst_t scratch[BIR_MAX_INSTS];
/* Lines travel with their instructions or they end up describing
* whatever landed in the slot instead. */
static uint32_t scratch_lines[BIR_MAX_INSTS];
uint32_t si = 0;

for (uint32_t bi = 0; bi < S->num_blocks; bi++) {
for (int pi = 0; pi < S->num_phis; pi++) {
if (S->phis[pi].block != bi) continue;
scratch_lines[si] = M->inst_lines[S->phis[pi].inst];
scratch[si++] = M->insts[S->phis[pi].inst];
}
uint32_t abs_b = S->base_block + bi;
const bir_block_t *B = &M->blocks[abs_b];
for (uint32_t j = 0; j < B->num_insts; j++) {
uint32_t ii = B->first_inst + j;
if (S->dead[ii]) continue;
scratch_lines[si] = M->inst_lines[ii];
scratch[si++] = M->insts[ii];
}
}

/* Copy scratch back */
memcpy(&M->insts[S->base_inst], scratch, si * sizeof(bir_inst_t));
memcpy(&M->inst_lines[S->base_inst], scratch_lines,
si * sizeof(scratch_lines[0]));

/* Update block boundaries */
uint32_t cursor = S->base_inst;
Expand Down Expand Up @@ -921,6 +928,8 @@ int bir_mem2reg(bir_module_t *M)
/* Move instructions */
memmove(&M->insts[dst], &M->insts[src],
count * sizeof(bir_inst_t));
memmove(&M->inst_lines[dst], &M->inst_lines[src],
count * sizeof(M->inst_lines[0]));

/* Update block boundaries */
for (uint16_t bi = 0; bi < F->num_blocks; bi++) {
Expand Down
Loading
Loading