Skip to content
Draft
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ pull request.

## Unreleased

### Features

- **Compiling to a module.** `compilerlib::compileToModule`, from
`compilerlib/compile_module.hpp`, gives the module that `compilerlib::compile` writes as text
or bitcode, created in an `llvm::LLVMContext` of the host, instead of writing it for the host
to read it back. Its arguments are those of an LLVM IR compilation; the output file is not
written, and the other files the arguments ask for, such as a dependency file, are. The
context keeps its own setting of value names, except with LLVM 16, where a context that
discards them keeps them after a compilation that names values (#265, #263).

### Fixes

- **A child of `fork()` and the vtable diagnostics, on Linux.** With `--ct-vtable-diag`, a
Expand Down Expand Up @@ -180,6 +190,10 @@ pull request.
- 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).
- Unit tests check that `compileToModule` gives the module that `compile` writes as text, at -O0
and -O2, instrumented or not, and the module of a bitcode compilation, whose dependency file is
written and output file is not; that it fails on a compilation to an object and on a broken
source; and that the context keeps its setting of value names (#265, #263).
- The Docker tests build and test the image in a job per architecture, each on a runner of its
own architecture: arm64 on `ubuntu-24.04-arm` instead of under QEMU on an amd64 runner. Under
QEMU's user-mode emulation, `sbrk()` failed now and then in the sbrk test of the full
Expand Down Expand Up @@ -208,6 +222,7 @@ pull request.

### Documentation

- The README describes compiling to a module with `compileToModule` (#265, #263).
- `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).
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,3 +498,27 @@ 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 to a module

`compilerlib::compileToModule`, from `compilerlib/compile_module.hpp`, gives the module that
`compilerlib::compile` writes as text or bitcode, created in an `llvm::LLVMContext` of the host,
instead of writing it for the host to read it back:

```cpp
#include <compilerlib/compile_module.hpp>

llvm::LLVMContext context;
compilerlib::ModuleResult result =
compilerlib::compileToModule({"-g", "-c", "-emit-llvm", "unit.cpp"}, context);
if (result.success)
analyze(*result.module);
```

Its arguments are those of an LLVM IR compilation, `-S -emit-llvm` or `-c -emit-llvm`. The output
file is not written; the other files the arguments ask for, such as a dependency file, are. With
`instrument`, the module is the instrumented one. Code generation sets options of the context, as
it does with clang, except whether value names are discarded: the context keeps its own setting,
which applies to the modules read into it later. With LLVM 16, a context that discards names keeps
them after a compilation that names values, since LLVM 16 cannot destroy those values in a context
that discards names.
42 changes: 42 additions & 0 deletions include/compilerlib/compile_module.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: Apache-2.0
#ifndef COMPILERLIB_COMPILE_MODULE_HPP
#define COMPILERLIB_COMPILE_MODULE_HPP

#include "compilerlib/attributes.hpp"

#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>

#include <memory>
#include <string>
#include <vector>

namespace compilerlib
{

struct ModuleResult
{
bool success = false;
std::string diagnostics;
/// The module, in the context compileToModule was given; null when the compilation
/// failed.
std::unique_ptr<llvm::Module> module;
};

/// Compiles one source to the module that compile() writes, created in `context`, for a
/// host that works on the module itself: it is neither written as text or bitcode nor read
/// back. `args` are those of an LLVM IR compilation, `-S -emit-llvm` or `-c -emit-llvm`;
/// the output file is not written, and the other files the arguments ask for, such as a
/// dependency file, are. With `instrument`, the module is the instrumented one.
///
/// Code generation sets options of `context` as it does with clang, except whether value
/// names are discarded, which is restored: modules read into `context` later keep theirs.
/// With LLVM 16, a context that discards names keeps them after a compilation that names
/// values, which LLVM 16 could not destroy in a context that discards names.
/// No other thread may use `context` during the compilation.
CT_NODISCARD ModuleResult compileToModule(const std::vector<std::string>& args,
llvm::LLVMContext& context, bool instrument = false);

} // namespace compilerlib

#endif // COMPILERLIB_COMPILE_MODULE_HPP
13 changes: 13 additions & 0 deletions src/compilerlib/clang_compat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ namespace compilerlib::clang_compat
return file;
}

// Removes from `context` the record that Clang's frontend action set up on it, for
// -fsave-optimization-record: before LLVM 22, the action closes the file and leaves the
// streamer, which would write into it; LLVM 22's handle removes the streamer itself.
inline void releaseFrontendOptimizationRecord(llvm::LLVMContext& context)
{
#if LLVM_VERSION_MAJOR >= 22
(void)context;
#else
context.setLLVMRemarkStreamer(nullptr);
context.setMainRemarkStreamer(nullptr);
#endif
}

// Removes the record's streamer from `context`, which must not write into `file` once
// it is released.
inline void releaseOptimizationRecord(llvm::LLVMContext& context, RemarkFile& file)
Expand Down
Loading
Loading