diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a4bf1b5..b39c0bd 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -186,6 +186,44 @@ jobs:
${{ runner.temp }}/ct_autofree_tests/*.log
${{ runner.temp }}/ct_runtime_tests/*.log
+ # compilerlib and its unit tests built with ThreadSanitizer: compilations from several threads
+ # at once, each in a working directory of its own, race on none of compilerlib's state. LLVM
+ # and Clang come from packages built without ThreadSanitizer: a race between two
+ # compilations inside their code goes unseen.
+ tsan-compilerlib:
+ name: compilerlib with ThreadSanitizer (ubuntu-24.04, LLVM 20)
+ runs-on: ubuntu-24.04
+ env:
+ TSAN_OPTIONS: halt_on_error=1:exitcode=66
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y build-essential cmake ninja-build
+ sudo bash scripts/ci/install-llvm-apt.sh 20 llvm-20 llvm-20-dev clang-20 libclang-20-dev \
+ libclang-rt-20-dev
+ # The kernel randomizes mmap addresses over more bits than ThreadSanitizer supports.
+ sudo sysctl vm.mmap_rnd_bits=28
+
+ - name: Build
+ run: |
+ cmake -S . -B build -G Ninja \
+ -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 \
+ "-DCMAKE_C_FLAGS=-fsanitize=thread -g" "-DCMAKE_CXX_FLAGS=-fsanitize=thread -g" \
+ -DCMAKE_EXE_LINKER_FLAGS=-fsanitize=thread \
+ -DLLVM_DIR=/usr/lib/llvm-20/lib/cmake/llvm \
+ -DClang_DIR=/usr/lib/llvm-20/lib/cmake/clang \
+ -DUSE_SHARED_LIB=OFF
+ cmake --build build --target compilerlib_unit_tests
+
+ - name: Run the compilations with a working directory, concurrent ones included
+ run: |
+ build/test/unit/compilerlib_unit_tests \
+ --gtest_filter='*WorkingDirectory*:*ConcurrentCompilationTest*'
+
# Line coverage of compilerlib, cc and the runtime (ENABLE_COVERAGE), from the unit tests,
# the smoke tests and the runtime fixture suites. Informational: no threshold is enforced.
# The suites build their programs with -fprofile-instr-generate, so that the runtime they
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a11453..952aa55 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,23 @@ pull request.
## Unreleased
+### Features
+
+- **Compilations from several threads.** `compilerlib::compile` may run from several threads at
+ once: each compilation reads and writes files through a file system of its own, whose working
+ directory `-working-directory
` moves while the process's stays. A host compiles each
+ command of a compilation database in its directory that way, instead of moving the process
+ under a lock. A compilation with a job that runs as a program, such as a link, and one that
+ passes `-mllvm`, `-mdebug-pass`, `-mlimit-float-precision` or `-ftime-report` must not run
+ while another one does (#262, #261).
+
+### Behaviour changes
+
+- **`-working-directory` leaves the process where it is** when every job of the compilation runs
+ in the process: a compilation to a file or to memory, instrumented or not. The process was
+ left in ``. A compilation that links or assembles still moves the process to ``, as
+ clang does (#262, #261).
+
### Fixes
- **A child of `fork()` and the vtable diagnostics, on Linux.** With `--ct-vtable-diag`, a
@@ -119,6 +136,12 @@ pull request.
- **`CT_DEBUG_AUTOFREE` read from several threads.** The allocation pass read the variable into
two plain statics on its first use, on which two threads instrumenting modules at once raced.
It is now read once, into a constant (#251).
+- **A relative `-working-directory`** names a directory relative to the process's working
+ directory. The compilation failed: the `-cc1` job resolved it again from the directory the
+ driver had moved to (#262, #261).
+- **Objects linked alone with `-working-directory`** are read in that directory, to choose
+ between the C and C++ drivers. They were looked up in the process's working directory, and
+ the link failed with `failed to inspect object` (#262, #261).
### Performance
@@ -177,6 +200,10 @@ pull request.
Ubuntu 26.04, where clang-format 19 ran and apt.llvm.org has no LLVM 20. `scripts/format.sh`
and `scripts/format-check.sh` run the binary that `CLANG_FORMAT` names and print its version
(#238, #237).
+- A job of the Build workflow builds compilerlib and its unit tests with ThreadSanitizer, and
+ runs the tests of `-working-directory`, eight threads compiling at once included. LLVM and
+ Clang come from packages built without ThreadSanitizer, so a race inside their code goes
+ unseen (#262, #261).
- When `sbrk()` fails in the sbrk test of the full allocation table, the test reports `errno`,
the program break and the mapping above it, `RLIMIT_DATA`, and the memory of the process and
of the system (#239, #233).
@@ -211,6 +238,8 @@ pull request.
- `NOTICE` dates the copyright from 2025, when the code began, credits the CoreTrace
contributors alongside Hugo Payet and links the repository. It keeps only these
attribution notices, without the license header meant for source files (#248).
+- The README and `compilerlib/compiler.h` describe compiling from several threads, with
+ `-working-directory`, and its limits (#262, #261).
## v0.12.0 (2026-10-22)
diff --git a/README.md b/README.md
index 8c0121b..c3f2cc2 100644
--- a/README.md
+++ b/README.md
@@ -498,3 +498,22 @@ expects, and waits for it: a host can compile from a thread with a small stack,
secondary thread on macOS, whose stack is 512 KiB (#204). The time-trace profiler a host runs on
its own thread, started with `llvm::timeTraceProfilerInitialize`, therefore does not record the
compilation. With `-ftime-trace`, each job writes its own trace file, as clang does.
+
+### Compiling from several threads
+
+`compilerlib::compile` may run from several threads at once, as for a host that compiles each
+command of a compilation database. Each compilation reads and writes files through a file system
+of its own, whose working directory `-working-directory ` sets without moving the process:
+the sources, the include directories and the files the compilation writes, its output, its
+dependency file and the others, are relative to ``, and a relative `` is relative to the
+process's working directory. A host passes each command's directory that way, instead of changing
+the process's working directory under a lock.
+
+Two kinds of compilations must not run while another one does:
+
+- **Jobs that run as programs.** The linker, the assembler and, with `-fno-integrated-cc1`, every
+ job run as programs of their own, which start in the process's working directory. With
+ `-working-directory`, such a compilation moves the whole process to ``, as clang does, and
+ leaves it there.
+- **Options of LLVM's own.** Clang applies `-mllvm`, `-mdebug-pass`, `-mlimit-float-precision`
+ and `-ftime-report` to LLVM's state, which the whole process shares.
diff --git a/include/compilerlib/compiler.h b/include/compilerlib/compiler.h
index 645d26e..b1c5fcd 100644
--- a/include/compilerlib/compiler.h
+++ b/include/compilerlib/compiler.h
@@ -30,6 +30,18 @@ namespace compilerlib
};
// std::pair compile(const std::vector& args);
+ /// Compiles as clang does with `args`, in this process.
+ ///
+ /// `-working-directory ` gives the compilation a working directory of its own: the
+ /// sources, the include directories and the files it writes are relative to ``, and a
+ /// relative `` to the process's working directory, which stays where it is. A
+ /// compilation with a job that runs as a program of its own, as linking and assembling do,
+ /// moves the whole process to `` instead, as clang does, and leaves it there.
+ ///
+ /// Compilations may run from several threads at once. A compilation that moves the process
+ /// must not run while another compilation of the process does, nor one that passes an
+ /// option that Clang applies to LLVM's state of the whole process: `-mllvm`, `-mdebug-pass`,
+ /// `-mlimit-float-precision` or `-ftime-report`.
CompileResult compile(const std::vector& args,
OutputMode mode = OutputMode::ToFile, bool instrument = false);
diff --git a/src/compilerlib/args.cpp b/src/compilerlib/args.cpp
index e3f1455..d24b692 100644
--- a/src/compilerlib/args.cpp
+++ b/src/compilerlib/args.cpp
@@ -2,10 +2,16 @@
#include "args_internal.hpp"
#include "clang_driver_compat.hpp"
+#include
+#include
#include
#include
+#include
+#include
#include
+#include