Skip to content
Open
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
4 changes: 0 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 4 additions & 52 deletions libdd-otel-thread-ctx-ffi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,8 @@ that external readers (e.g. the eBPF profiler) can discover.

Currently Linux-only (x86-64 and aarch64).

## Optimized build (cross-language inlining)
## TLS

The OTel thread-level context sharing specification requires the use of the
TLSDESC dialect for the thread-local variable that holds the current context.
Because (stable) `rustc` doesn't currently provide a way to control the TLS
dialect, we need to use a small C shim that defines the variable and expose a
one-line getter. This unfortunately adds one level of indirection (a function
call) when attaching or detaching a context.

With the right toolchain, it's possible to use Link-Time Optimization (LTO) to
inline the C wrapper at link time. The requirements are:

- `clang` is available to compile the C shim to LLVM IR (version requirements
aren't clear -- tested with clang18 and clang20, but ideally the version
should be the same or close to the LLVM version shipped with `rustc`)
- Either the Rust toolchain ships `lld` or there's a system-wide `lld` install
(Rust has been shipping `rust-lld` for a long time now, something like since
1.53+, however some musl-based distro like Alpine might have the Rust
toolchain without `rust-lld`)
- `lld` version is at least 18.1 (TLSDESC support)

**If those requirements are met, setting the environment variables
`CARGO_TARGET_<TARGET>_RUSTFLAGS=-Clinker-plugin-lto -Clinker=clang` and
`LIBDD_OTEL_THREAD_CTX_INLINE=1` when calling to `cargo` will trigger the
optimized build where the C shim is inlined.** Here, `<TARGET>` is the target
triple in screaming snake case.

External environment variables are needed because cross-language LTO requires
two `rustc` codegen flags (`-Clinker-plugin-lto` and `-Clinker=clang`) that
cannot be set from a Cargo build script: they must come from `RUSTFLAGS` or
`.cargo/config.toml`, which can't be entirely automated from Rust only. We
advise to set those flags via the target-scoped
`CARGO_TARGET_<TARGET>_RUSTFLAGS` env var so they don't leak to build scripts
or proc-macros if cross-compiling.

### Build script

The `build-optimized.sh` wrapper script is provided as a convenience and as an
example.

#### Usage

```bash
./build-optimized.sh
```

The script auto-detects the host triple. To cross-compile:

```bash
./build-optimized.sh --target aarch64-unknown-linux-gnu
```

Extra arguments are forwarded to `cargo build`.
The thread-local variable `otel_thread_ctx_v1` and its TLSDESC accessor are
implemented in pure Rust using `global_asm!` and `asm!` in the
`libdd-otel-thread-ctx` crate.
69 changes: 0 additions & 69 deletions libdd-otel-thread-ctx-ffi/build-optimized.sh

This file was deleted.

100 changes: 7 additions & 93 deletions libdd-otel-thread-ctx-ffi/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,84 +3,20 @@
extern crate build_common;

use build_common::{find_rust_lld_dir, generate_and_configure_header};
use std::{env, fmt::Display, path::PathBuf, process::Command};

#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
struct LldVersion {
major: u32,
minor: u32,
}

impl Display for LldVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}", self.major, self.minor)
}
}

/// Parse the major and minor version from `ld.lld --version` output.
///
/// Typical formats:
/// "LLD 18.1.3 (compatible with GNU linkers)"
/// "LLD 19.1.0"
fn system_lld_version() -> Option<LldVersion> {
let output = Command::new("ld.lld").arg("--version").output().ok()?;
if !output.status.success() {
return None;
}
String::from_utf8_lossy(&output.stdout)
.split_whitespace()
.find_map(|tok| {
let mut splitted = tok.split('.');
let major = splitted.next()?.parse::<u32>().ok()?;
let minor = splitted.next()?.parse::<u32>().ok()?;

Some(LldVersion { major, minor })
})
}

/// TLSDESC is supported in LLD from version 18.1.
const MIN_LLD_VERSION_FOR_TLSDESC: LldVersion = LldVersion {
major: 18,
minor: 1,
};

/// Validate that a suitable LLD is available for cross-language LTO.
///
/// Returns the rust-lld `gcc-ld/` directory if found; `None` means the system
/// `ld.lld` will be used instead. Panics with a clear message when the
/// requirements are not met.
fn resolve_lld_for_inline(target_arch: &str) -> Option<PathBuf> {
if let Some(dir) = find_rust_lld_dir() {
return Some(dir);
}

match system_lld_version() {
Some(v) if target_arch != "x86_64" || v >= MIN_LLD_VERSION_FOR_TLSDESC => None,
Some(v) => panic!(
"LIBDD_OTEL_THREAD_CTX_INLINE requires LLD >= {MIN_LLD_VERSION_FOR_TLSDESC} on \
x86-64 (for -mllvm -enable-tlsdesc), but system ld.lld is version {v}. \
Install a newer LLD or use a Rust toolchain that bundles rust-lld."
),
None => panic!(
"LIBDD_OTEL_THREAD_CTX_INLINE requires LLD for cross-language LTO, but neither \
rust-lld nor a system ld.lld was found."
),
}
}
use std::env;

fn main() {
generate_and_configure_header("otel-thread-ctx.h");

let cross_compiling = env::var("HOST").unwrap() != env::var("TARGET").unwrap();
println!("cargo:rustc-env=LIBDD_OTEL_THREAD_CTX_FFI_CROSS_COMPILING={cross_compiling}");

let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os != "linux" {
return;
}

println!("cargo:rerun-if-env-changed=LIBDD_OTEL_THREAD_CTX_INLINE");

let inline_mode = env::var("LIBDD_OTEL_THREAD_CTX_INLINE").is_ok_and(|v| v == "1");
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();

// Export the TLSDESC thread-local variable to the dynamic symbol table so external readers
// (e.g. the eBPF profiler) can discover it. Rust's cdylib linker applies a version script with
Expand All @@ -93,32 +29,10 @@ fn main() {
// Merging multiple version scripts is not supported by GNU ld, so we need lld. We prefer the
// toolchain's bundled rust-lld (LLD 19+ since Rust 1.84) over the system lld (if it even
// exists). If rust-lld is not found we fall back to whatever `lld` the system provides.

// If `LIBDD_OTEL_THREAD_CTX_INLINE` is set to `1`, we try to inline the C shim. See the README
// for more details.
if inline_mode {
let rust_lld_dir = resolve_lld_for_inline(&target_arch);

// Emit link args for ALL link types (not just cdylib) so that test binaries also link
// correctly when RUSTFLAGS sets clang as the linker (in practice we should only build/care
// about the shared object file in inline mode).
if let Some(dir) = rust_lld_dir {
println!("cargo:rustc-link-arg=-B{}", dir.display());
}
println!("cargo:rustc-link-arg=-fuse-ld=lld");

// On x86-64, tell the LLVM backend to use TLSDESC during LTO codegen.
// On aarch64 TLSDESC is the default and the only model.
if target_arch == "x86_64" {
println!("cargo:rustc-link-arg=-Wl,-mllvm,-enable-tlsdesc");
}
} else {
// Default mode: only the cdylib needs lld (for the version script).
if let Some(gcc_ld_dir) = find_rust_lld_dir() {
println!("cargo:rustc-cdylib-link-arg=-B{}", gcc_ld_dir.display());
}
println!("cargo:rustc-cdylib-link-arg=-fuse-ld=lld");
if let Some(gcc_ld_dir) = find_rust_lld_dir() {
println!("cargo:rustc-cdylib-link-arg=-B{}", gcc_ld_dir.display());
}
println!("cargo:rustc-cdylib-link-arg=-fuse-ld=lld");

println!(
"cargo:rustc-cdylib-link-arg=-Wl,--version-script={manifest_dir}/tls-dynamic-list.txt"
Expand Down
Loading
Loading