diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000000000000000000000000000000000000..5f8df3c8c9e8490845248e33e3a7cdf1e20c89a7
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,5 @@
+---
+BasedOnStyle:  Google
+ColumnLimit:     100
+TabWidth: 2
+UseTab: Always
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000000000000000000000000000000000000..b914e66b2dbd60a60b3c86493b0f09439fe15064
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,24 @@
+Checks: >
+  bugprone-*,
+  cert-*,
+  linuxkernel-*,
+  modernize-*,
+  performance-*,
+  portability-*,
+  readability-*,
+  -cert-err58-cpp,
+  -clang-diagnostic-empty-translation-unit,
+  -readability-braces-around-statements,
+  -readability-function-cognitive-complexity,
+  -readability-implicit-bool-conversion,
+  -readability-isolate-declaration,
+  -readability-magic-numbers,
+
+WarningsAsErrors: >
+  bugprone-*,
+  modernize-*,
+  clang-*,
+  readability-*,
+  performance-*,
+
+HeaderFilterRegex: .*
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 550e2f028887b50da3420a13f510a83ab8e77eef..3efa392f146c8d902d48d66e88669bb74d9d4a48 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,7 +2,9 @@ image: "flowdalic/debian-testing-dev:1.14"
 
 before_script:
   - |
+    apt install -y bear
     readarray TOOLS <<EOF
+    bear
     cc
     gcc
     EOF
@@ -14,3 +16,11 @@ before_script:
 test-eval:
   script:
     - make
+
+tidy:
+  script:
+    - bear -- make all && make tidy
+
+check-format:
+  script:
+    - make check-format
diff --git a/Makefile b/Makefile
index 0e6ff8ceca944f13dd2f072bcde102bfd3c9a813..a9ced29e266a8d583c161a584437764298764a16 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,14 @@
 BENCH_MAIN := bench.c
-SYSCALLS := blocking io-uring io-uring-sqpoll epoll aio-sig aio-thrd
+SYSCALLS := blocking io-uring io-uring-sqpoll io-uring-no-syscall epoll aio-sig aio-thrd
 
 OBJ := $(addprefix bench-,$(SYSCALLS))
 
 LDFLAGS := -luring -pthread -lrt
 
-# CFLAGS := -Werror -Wall -g -O3
-CFLAGS := -Werror -Wall -g -O0
+CFLAGS := -Werror -Wall -g -O3
+# CFLAGS := -Werror -Wall -g -O0
 
-.PHONY: all clean eval docker-eval
+.PHONY: all clean eval docker-eval check
 
 eval: all
 	@for syscall in $(SYSCALLS); do echo -n "$$syscall " ; ./bench-$$syscall; done
@@ -19,7 +19,7 @@ docker-eval:
 all: $(OBJ)
 
 define generateTargets
-bench-$(1): $(1).c bench.c | Makefile
+bench-$(1): $(1).c bench.c stopwatch.c | Makefile
 	$(CC) $(CFLAGS) -o $$@ $$^ $(LDFLAGS)
 endef
 
@@ -27,3 +27,14 @@ $(foreach syscall,$(SYSCALLS),$(eval $(call generateTargets,$(syscall))))
 
 clean:
 	rm -f $(OBJ)
+
+check: tidy check-format
+
+tidy:
+	clang-tidy *.c
+
+check-format:
+	tools/check-format
+
+format:
+	clang-format -i *.c *.h
diff --git a/aio-sig.c b/aio-sig.c
index fda8c7365a0fc404f9da513027b1054d9ceb83a8..5c115e80263bb82cbba7241ee82d8987eea7eb0b 100644
--- a/aio-sig.c
+++ b/aio-sig.c
@@ -1,29 +1,24 @@
 #include <aio.h>
 #include <err.h>
-#include <semaphore.h>
 #include <signal.h>
+#include <stdatomic.h>
 #include <stdint.h>
 #include <stdlib.h>
 
-#include "rdtsc.h"
+#include "common.h"
+#include "stopwatch.h"
 
-int64_t clock_before, clock_after;
 struct aiocb aiocb;
 
-sem_t sem;
+atomic_int done;
 
-void callback(int sig, siginfo_t* info, void* context) {
-	clock_after = rdtsc_s();
-	if (info->si_value.sival_int != 42)
-		errx(EXIT_FAILURE, "got unexpected sigval value");
+void callback(int sig, siginfo_t *info, void *context) {
+	if (unlikely(info->si_value.sival_int != 42)) errx(EXIT_FAILURE, "got unexpected sigval value");
 
-	sem_post(&sem);
+	atomic_store(&done, 1);
 }
 
 void init(int fd) {
-	if (sem_init(&sem, 0, 0) == -1)
-		err(EXIT_FAILURE, "sem_init failed");
-
 	aiocb.aio_fildes = fd;
 	aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
 	aiocb.aio_sigevent.sigev_signo = SIGUSR1;
@@ -33,20 +28,20 @@ void init(int fd) {
 	sa.sa_sigaction = callback;
 	sigemptyset(&sa.sa_mask);
 	sa.sa_flags = SA_SIGINFO;
-	if (sigaction(SIGUSR1, &sa, NULL) == -1)
-		err(EXIT_FAILURE, "sigaction failed");
+	if (sigaction(SIGUSR1, &sa, NULL) == -1) err(EXIT_FAILURE, "sigaction failed");
 }
 
-int64_t do_write(int fd, const void* buf, size_t count) {
-	aiocb.aio_buf = (void*)buf;
+void do_write(int fd, const void *buf, size_t count) {
+	aiocb.aio_buf = (void *)buf;
 	aiocb.aio_nbytes = count;
 
-	clock_before = rdtsc_s();
+	start_watch();
 	int res = aio_write(&aiocb);
-	sem_wait(&sem);
 
-	if (res == -1)
-		err(EXIT_FAILURE, "aio_write failed");
+	if (unlikely(res == -1)) err(EXIT_FAILURE, "aio_write failed");
 
-	return clock_after - clock_before;
+	while (!atomic_load(&done)) {
+	}
+	stop_watch();
+	atomic_store(&done, 1);
 }
diff --git a/aio-thrd.c b/aio-thrd.c
index 422917f35b116cd560ce3e7cf38c54102cc2c4d3..ef7c36508e9ae140c8a8e80fdbeab2c92f7911d4 100644
--- a/aio-thrd.c
+++ b/aio-thrd.c
@@ -1,46 +1,40 @@
 #include <aio.h>
 #include <err.h>
-#include <semaphore.h>
 #include <signal.h>
+#include <stdatomic.h>
 #include <stdint.h>
 #include <stdlib.h>
 
-#include "rdtsc.h"
+#include "stopwatch.h"
 
-int64_t clock_before, clock_after;
-struct aiocb aiocb;
-
-sem_t sem;
+atomic_int done;
 
+struct aiocb aiocb;
 void callback(union sigval sigval) {
-	clock_after = rdtsc_s();
+	stop_watch();
 
-	if (sigval.sival_int != 42)
-		errx(EXIT_FAILURE, "got unexpected sigval value");
+	if (sigval.sival_int != 42) errx(EXIT_FAILURE, "got unexpected sigval value");
 
-	sem_post(&sem);
+	atomic_store(&done, 1);
 }
 
 void init(int fd) {
-	if (sem_init(&sem, 0, 0) == -1)
-		err(EXIT_FAILURE, "sem_init failed");
-
 	aiocb.aio_fildes = fd;
 	aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;
 	aiocb.aio_sigevent.sigev_notify_function = callback;
 	aiocb.aio_sigevent.sigev_value.sival_int = 42;
 }
 
-int64_t do_write(int fd, void* buf, size_t count) {
+void do_write(int fd, void *buf, size_t count) {
 	aiocb.aio_buf = buf;
 	aiocb.aio_nbytes = count;
 
-	clock_before = rdtsc_s();
+	start_watch();
 	int res = aio_write(&aiocb);
-	sem_wait(&sem);
 
-	if (res == -1)
-		err(EXIT_FAILURE, "aio_write failed");
+	while (!atomic_load(&done)) {
+	}
+	atomic_store(&done, 0);
 
-	return clock_after - clock_before;
+	if (res == -1) err(EXIT_FAILURE, "aio_write failed");
 }
diff --git a/bench.c b/bench.c
index 861ea702cf28a4ecefbdcf77a78a060dce92e79f..b7439a6778522fdcc0a15b1f6cb0c9ea8176fa76 100644
--- a/bench.c
+++ b/bench.c
@@ -5,22 +5,16 @@
 #include <stdlib.h>
 #include <sys/eventfd.h>
 
+#include "stopwatch.h"
+
 void init(int fd);
-int64_t do_write(int fd, const void* buf, size_t count);
+void do_write(int fd, const void *buf, size_t count);
 
 const size_t warmup = 10000;
 const size_t iterations = 1000000;
 
 #define BUFSIZE 64
 
-/* static int open_dev_zero() { */
-	/* int fd = open("/dev/zero", O_RDONLY); */
-	/* if (fd == -1) { */
-		/* err(EXIT_FAILURE, "opening /dev/zero failed"); */
-	/* } */
-	/* return fd; */
-/* } */
-
 static int create_eventfd() {
 	int fd = eventfd(0, 0);
 	if (fd == -1) {
@@ -30,25 +24,30 @@ static int create_eventfd() {
 }
 
 int main() {
-	double avg_cycles = 0;
+	uint64_t cycles_sum = 0;
+	uint64_t nanos_sum = 0;
+	uint64_t write_buf = 1;
 
-	/* int fd = open_dev_zero(); */
 	int fd = create_eventfd();
 
 	init(fd);
 
-	/* char read_buf[BUFSIZE]; */
-	uint64_t write_buf = 1;
+	for (size_t i = 0; i < warmup; ++i) do_write(fd, &write_buf, sizeof(write_buf));
 
-	for (size_t i = 0; i < warmup; ++i)
+	for (int64_t i = 1; i <= iterations; ++i) {
 		do_write(fd, &write_buf, sizeof(write_buf));
-
-	for (size_t i = 1; i <= iterations; ++i) {
-		ssize_t cycles = do_write(fd, &write_buf, sizeof(write_buf));
-		avg_cycles += ((double)cycles - avg_cycles) / i;
+		if (__builtin_add_overflow(nanos_sum, clock_diff_nanos(), &nanos_sum))
+			errx(EXIT_FAILURE, "nanos overflowed at %ld", i);
+		if (__builtin_add_overflow(cycles_sum, clock_diff_cycles(), &cycles_sum))
+			errx(EXIT_FAILURE, "cycles overflowed at %ld", i);
 	}
 
-	printf("avg_cycles: %lf\n", avg_cycles);
+	// Since uint64_t <-> double conversion are not well defined
+	// and we use really small units (cycles and nanoseconds) I am willing
+	// to accept that we throw away anything after the decimal point.
+	uint64_t avg_nanos = nanos_sum / iterations;
+	uint64_t avg_cycles = cycles_sum / iterations;
 
+	printf("%lu ns, %lu cycles\n", avg_nanos, avg_cycles);
 	return 0;
 }
diff --git a/blocking.c b/blocking.c
index 9e057146d9850c74d25c7ae7d9bff98e791aca93..3f3cdbdb44a5baf3d0b4c27bf4c67dd812655423 100644
--- a/blocking.c
+++ b/blocking.c
@@ -2,19 +2,14 @@
 #include <stdlib.h>
 #include <unistd.h>
 
-#include "rdtsc.h"
+#include "stopwatch.h"
 
 void init(__attribute__((unused)) int fd) {}
 
-int64_t do_write(int fd, const void* buf, size_t count) {
-	int64_t clock_before, clock_after;
-
-	clock_before = rdtsc_s();
+void do_write(int fd, const void *buf, size_t count) {
+	start_watch();
 	ssize_t res = write(fd, buf, count);
-	clock_after = rdtsc_s();
-
-	if (res == -1)
-		err(EXIT_FAILURE, "write failed");
+	stop_watch();
 
-	return clock_after - clock_before;
+	if (res == -1) err(EXIT_FAILURE, "write failed");
 }
diff --git a/common.h b/common.h
index 909aa54da958bc60b92ee9facccad859ff77ca9c..b8acbfda87da27e7e0953a71780e913394bc80ca 100644
--- a/common.h
+++ b/common.h
@@ -1,22 +1,3 @@
-#include <err.h>
-#include <fcntl.h>
-#include <stdint.h>
-#include <stdlib.h>
+#pragma once
 
-#define BUFSIZE 64
-
-int64_t do_write(int fd, const void* buf, size_t count);
-
-__inline__ int64_t rdtsc_s(void) {
-	unsigned a, d;
-	asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx");
-	asm volatile("rdtsc" : "=a" (a), "=d" (d));
-	return ((unsigned long)a) | (((unsigned long)d) << 32);
-}
-
-__inline__ int64_t rdtsc_e(void) {
-	unsigned a, d;
-	asm volatile("rdtscp" : "=a" (a), "=d" (d));
-	asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx");
-	return ((unsigned long)a) | (((unsigned long)d) << 32);
-}
+#define unlikely(x) __builtin_expect(!!(x), 0)
diff --git a/epoll.c b/epoll.c
index d898734ee9a9203afe743b135d605e6202f98d12..dd27dd8ab48a010aa7209de17ac4d20e923d9ae3 100644
--- a/epoll.c
+++ b/epoll.c
@@ -3,40 +3,30 @@
 #include <sys/epoll.h>
 #include <unistd.h>
 
-#include "rdtsc.h"
+#include "stopwatch.h"
 
 struct epoll_event ev;
 int nfds, epollfd;
 
 void init(int fd) {
 	epollfd = epoll_create1(0);
-	if (epollfd == -1)
-		err(EXIT_FAILURE, "creating epoll failed");
+	if (epollfd == -1) err(EXIT_FAILURE, "creating epoll failed");
 
 	ev.events = EPOLLOUT;
 	ev.data.fd = fd;
 
-	if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1)
-		err(EXIT_FAILURE, "epoll_ctl failed");
-
+	if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) err(EXIT_FAILURE, "epoll_ctl failed");
 }
 
-int64_t do_write(int fd, const void* buf, size_t count) {
-	int64_t clock_before, clock_after;
-
-	clock_before = rdtsc_s();
+void do_write(int fd, const void *buf, size_t count) {
+	start_watch();
 	nfds = epoll_wait(epollfd, &ev, 1, -1);
 	size_t res = write(fd, buf, count);
-	clock_after = rdtsc_s();
-
-	if (nfds == -1)
-		err(EXIT_FAILURE, "epoll_wait failed");
+	stop_watch();
 
-	if (ev.data.fd != fd)
-		errx(EXIT_FAILURE, "got unexpected fd from epoll");
+	if (nfds == -1) err(EXIT_FAILURE, "epoll_wait failed");
 
-	if (res == -1)
-		err(EXIT_FAILURE, "write failed");
+	if (ev.data.fd != fd) errx(EXIT_FAILURE, "got unexpected fd from epoll");
 
-	return clock_after - clock_before;
+	if (res == -1) err(EXIT_FAILURE, "write failed");
 }
diff --git a/io-uring-no-syscall.c b/io-uring-no-syscall.c
new file mode 100644
index 0000000000000000000000000000000000000000..62f7c5a257cce2b6fdc146660b8968082736c798
--- /dev/null
+++ b/io-uring-no-syscall.c
@@ -0,0 +1,42 @@
+#include <err.h>
+#include <errno.h>
+#include <liburing.h>
+#include <stdlib.h>
+
+#include "common.h"
+#include "stopwatch.h"
+
+struct io_uring ring;
+
+void init(__attribute__((unused)) int fd) {
+	int res = io_uring_queue_init(16, &ring, IORING_SETUP_SQPOLL);
+	if (res < 0) {
+		errno = res;
+		err(EXIT_FAILURE, "io_uring_setup failed");
+	}
+}
+
+void do_write(int fd, const void *buf, size_t count) {
+	start_watch();
+	struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
+	while (unlikely(!sqe)) {
+		sqe = io_uring_get_sqe(&ring);
+	}
+	io_uring_prep_write(sqe, fd, buf, count, 0);
+
+	int res = io_uring_submit(&ring);
+
+	struct io_uring_cqe *cqe;
+	while (io_uring_peek_cqe(&ring, &cqe) == -EAGAIN) {
+	}
+
+	stop_watch();
+
+	if (res < 0) {
+		err(EXIT_FAILURE, "io_submit failed");
+	}
+
+	if (cqe->res < 0) {
+		err(EXIT_FAILURE, "write request failed");
+	}
+}
diff --git a/io-uring-sqpoll.c b/io-uring-sqpoll.c
index e46e51706e79a30323c56289d7a0e983e7a6f7d7..349f71ff4266e9469b7cc1b5773904524ed6c4a0 100644
--- a/io-uring-sqpoll.c
+++ b/io-uring-sqpoll.c
@@ -3,9 +3,8 @@
 #include <liburing.h>
 #include <stdlib.h>
 
-#define unlikely(x) __builtin_expect(!!(x), 0)
-
-#include "rdtsc.h"
+#include "common.h"
+#include "stopwatch.h"
 
 struct io_uring ring;
 
@@ -17,9 +16,8 @@ void init(__attribute__((unused)) int fd) {
 	}
 }
 
-int64_t do_write(int fd, const void* buf, size_t count) {
-	int64_t clock_before, clock_after;
-	clock_before = rdtsc_s();
+void do_write(int fd, const void *buf, size_t count) {
+	start_watch();
 
 	struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
 	while (unlikely(!sqe)) {
@@ -29,13 +27,13 @@ int64_t do_write(int fd, const void* buf, size_t count) {
 
 	int res = io_uring_submit_and_wait(&ring, 1);
 
-	clock_after = rdtsc_e();
+	stop_watch();
 
 	if (res < 0) {
 		err(EXIT_FAILURE, "io_uring_submit_and_wait failed");
 	}
 
-	struct io_uring_cqe* cqe;
+	struct io_uring_cqe *cqe;
 	res = io_uring_peek_cqe(&ring, &cqe);
 	if (res < 0) {
 		err(EXIT_FAILURE, "io_uring_peek_cqe failed");
@@ -44,6 +42,4 @@ int64_t do_write(int fd, const void* buf, size_t count) {
 	if (cqe->res < 0) {
 		err(EXIT_FAILURE, "write request failed");
 	}
-
-	return clock_after - clock_before;
 }
diff --git a/io-uring.c b/io-uring.c
index 53f16f1d745450432f8fdc20a1efcb9c35c50c24..ea0141eb2f8a09a3e7ce45c12458227a2e66ff68 100644
--- a/io-uring.c
+++ b/io-uring.c
@@ -3,7 +3,7 @@
 #include <liburing.h>
 #include <stdlib.h>
 
-#include "rdtsc.h"
+#include "stopwatch.h"
 
 struct io_uring ring;
 
@@ -15,30 +15,21 @@ void init(__attribute__((unused)) int fd) {
 	}
 }
 
-int64_t do_write(int fd, const void* buf, size_t count) {
-	int64_t clock_before, clock_after;
-	clock_before = rdtsc_s();
+void do_write(int fd, const void *buf, size_t count) {
+	start_watch();
 
 	struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
 	io_uring_prep_write(sqe, fd, buf, count, 0);
 
 	int res = io_uring_submit_and_wait(&ring, 1);
 
-	clock_after = rdtsc_e();
+	stop_watch();
 
-	if (res < 0) {
-		err(EXIT_FAILURE, "io_uring_submit_and_wait failed");
-	}
+	if (res < 0) err(EXIT_FAILURE, "io_uring_submit_and_wait failed");
 
-	struct io_uring_cqe* cqe;
+	struct io_uring_cqe *cqe;
 	res = io_uring_peek_cqe(&ring, &cqe);
-	if (res < 0) {
-		err(EXIT_FAILURE, "io_uring_peek_cqe failed");
-	}
-
-	if (cqe->res < 0) {
-		err(EXIT_FAILURE, "write request failed");
-	}
+	if (res < 0) err(EXIT_FAILURE, "io_uring_peek_cqe failed");
 
-	return clock_after - clock_before;
+	if (cqe->res < 0) err(EXIT_FAILURE, "write request failed");
 }
diff --git a/rdtsc.h b/rdtsc.h
deleted file mode 100644
index ac687bb6762c64e94bffb91b65cf0f7857987672..0000000000000000000000000000000000000000
--- a/rdtsc.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#include <stdint.h>
-
-static __inline__ int64_t rdtsc_s(void) {
-	unsigned a, d;
-	asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx");
-	asm volatile("rdtsc" : "=a" (a), "=d" (d));
-	return ((unsigned long)a) | (((unsigned long)d) << 32);
-}
-
-static __inline__ int64_t rdtsc_e(void) {
-	unsigned a, d;
-	asm volatile("rdtscp" : "=a" (a), "=d" (d));
-	asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx");
-	return ((unsigned long)a) | (((unsigned long)d) << 32);
-}
diff --git a/stopwatch.c b/stopwatch.c
new file mode 100644
index 0000000000000000000000000000000000000000..472baf59370274ea7880e627ed6fe96d9f74c5b7
--- /dev/null
+++ b/stopwatch.c
@@ -0,0 +1,16 @@
+#include "stopwatch.h"
+
+uint64_t cycles_start, cycles_stop;
+struct timespec start, stop;
+
+int64_t sec_to_nanos(int64_t sec) { return sec * 1000 * 1000 * 1000; }
+
+double nanos_to_millis(int64_t nanos) { return ((double)nanos) / (1000 * 1000); }
+
+int64_t clock_diff_nanos() {
+	int64_t nanos = sec_to_nanos(stop.tv_sec - start.tv_sec);
+	nanos += (stop.tv_nsec - start.tv_nsec);
+	return nanos;
+}
+
+uint64_t clock_diff_cycles() { return cycles_stop - cycles_start; }
diff --git a/stopwatch.h b/stopwatch.h
new file mode 100644
index 0000000000000000000000000000000000000000..7b670d132102574a27c90b9753a20ca6a486c653
--- /dev/null
+++ b/stopwatch.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <stdint.h>
+#include <time.h>
+
+extern uint64_t cycles_start, cycles_stop;
+extern struct timespec start, stop;
+
+static __inline__ uint64_t rdtsc_s(void) {
+	unsigned a, d;
+	asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx");
+	asm volatile("rdtsc" : "=a"(a), "=d"(d));
+	return ((unsigned long)a) | (((unsigned long)d) << 32);
+}
+
+static __inline__ uint64_t rdtsc_e(void) {
+	unsigned a, d;
+	asm volatile("rdtscp" : "=a"(a), "=d"(d));
+	asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx");
+	return ((unsigned long)a) | (((unsigned long)d) << 32);
+}
+
+static inline void start_watch() {
+	clock_gettime(CLOCK_MONOTONIC, &start);
+	cycles_start = rdtsc_s();
+}
+
+static inline void stop_watch() {
+	cycles_stop = rdtsc_e();
+	clock_gettime(CLOCK_MONOTONIC, &stop);
+}
+
+uint64_t clock_diff_cycles();
+int64_t clock_diff_nanos();
+double nanos_to_millis(int64_t nanos);
+int64_t sec_to_nanos(int64_t sec);
diff --git a/tools/check-format b/tools/check-format
new file mode 100755
index 0000000000000000000000000000000000000000..005c77c1ecfef05fc891dbb6ffc2d3c90a2a6912
--- /dev/null
+++ b/tools/check-format
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# Pretty fancy method to get reliable the absolute path of a shell
+# script, *even if it is sourced*. Credits go to GreenFox on
+# stackoverflow: http://stackoverflow.com/a/12197518/194894
+pushd . > /dev/null
+SCRIPTDIR="${BASH_SOURCE[0]}";
+while([ -h "${SCRIPTDIR}" ]); do
+    cd "`dirname "${SCRIPTDIR}"`"
+    SCRIPTDIR="$(readlink "`basename "${SCRIPTDIR}"`")";
+done
+cd "`dirname "${SCRIPTDIR}"`" > /dev/null
+SCRIPTDIR="`pwd`";
+popd  > /dev/null
+
+DEBUG=false
+while getopts d OPT; do
+	case $OPT in
+		d)
+			set -x
+			DEBUG=true
+			;;
+		*)
+			echo "usage: ${0##*/} [-dq} [--] ARGS..."
+			exit 2
+	esac
+done
+shift $(( OPTIND - 1 ))
+OPTIND=1
+
+ROOTDIR=$(readlink -f "${SCRIPTDIR}/..")
+
+MAX_PROCS=$(nproc)
+
+CHECKED_FILES_FILE=$(mktemp)
+if ! $DEBUG; then
+	trap 'rm "${CHECKED_FILES_FILE}"' EXIT
+fi
+
+cd "${ROOTDIR}"
+# Note that the --dry-run and --Werror clang-format arguments require
+# clang-format 10 or higher. See https://reviews.llvm.org/D68554
+find . \( -path '*/\.*' -o -path "./subprojects*" -o -path "./build*" \) -prune -o \
+	-type f -regextype posix-extended -regex '.*\.(c|h|cpp|hpp)' -print0 |\
+	tee "${CHECKED_FILES_FILE}" |\
+	xargs --null --max-args=3 --max-procs="${MAX_PROCS}" \
+		  clang-format --style=file --dry-run -Werror
+
+FILE_COUNT=$(<"${CHECKED_FILES_FILE}" tr -cd '\0' | wc -c)
+echo "Checked ${FILE_COUNT} files for format violations"