-
Notifications
You must be signed in to change notification settings - Fork 19
feat: cross-language LTO to inline C TLS shim into Rust FFI #1982
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
yannham
wants to merge
10
commits into
main
Choose a base branch
from
yannham/thread-ctx-inlining-exp
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
ac4ac3c
feat: cross-language LTO to inline C TLS shim into Rust FFI
yannham 1ab1e73
doc: add README for the FFI crate
yannham 16e7302
chore: move script to appropriate crate
yannham bdd680e
doc: improve otel thread ctx ffi README
yannham 6de3ad2
feat: add post-build sanity check to build-optimized.sh
yannham 6a44342
doc: explain why the wrapper script is needed
yannham 88d7f6d
chore: self-review misc fixes
yannham d98d13d
fix: wrong env variable handling
yannham 9227252
refactor: move find_rust_lld_dir to build_common
yannham 7a5bbe3
chore: self-review
yannham File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # libdd-otel-thread-ctx-ffi | ||
|
|
||
| FFI bindings for the OTel thread-level context publisher. Exposes a C API for | ||
| attaching, detaching, and updating per-thread OpenTelemetry context records | ||
| that external readers (e.g. the eBPF profiler) can discover. | ||
|
|
||
| Currently Linux-only (x86-64 and aarch64). | ||
|
|
||
| ## Optimized build (cross-language inlining) | ||
|
|
||
| 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, you can use the small wrapper script provided in | ||
| this directory to build an optimized release version where the C shim is | ||
| inlined. A wrapper script is 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. The | ||
| script sets them via the target-scoped `CARGO_TARGET_<TRIPLE>_RUSTFLAGS` env | ||
| var so they don't leak to build scripts or proc-macros if cross-compiling. | ||
|
|
||
| ### 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`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| #!/usr/bin/env bash | ||
| # Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Build libdd-otel-thread-ctx-ffi with cross-language LTO so the C TLS shim is | ||
| # inlined into the Rust FFI functions, eliminating a function-call indirection | ||
| # on every TLS access. | ||
| # | ||
| # Requirements: clang, lld (rust-lld from the toolchain is used automatically). | ||
| # The requirements are checked by the build.rs script. | ||
| # | ||
| # Usage: | ||
| # # auto-detect host triple | ||
| # ./build-optimized.sh | ||
| # # explicit target | ||
| # ./build-optimized.sh --target aarch64-unknown-linux-gnu | ||
| # | ||
| # Any extra arguments are forwarded to `cargo build`. | ||
| set -euo pipefail | ||
|
|
||
| # Parse --target from args, or auto-detect the host triple. | ||
| TARGET="" | ||
| EXTRA_ARGS=() | ||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --target) | ||
| TARGET="$2"; shift 2 ;; | ||
| --target=*) | ||
| TARGET="${1#--target=}"; shift ;; | ||
| *) | ||
| EXTRA_ARGS+=("$1"); shift ;; | ||
| esac | ||
| done | ||
|
|
||
| if [[ -z "$TARGET" ]]; then | ||
| TARGET=$(rustc -vV | sed -n 's/host: //p') | ||
| fi | ||
|
|
||
| # CARGO_TARGET_<TRIPLE>_RUSTFLAGS scopes the flags to the target only, keeping | ||
| # build scripts and proc-macros unaffected. | ||
| TARGET_ENV=$(echo "$TARGET" | tr 'a-z-' 'A-Z_') | ||
| export "CARGO_TARGET_${TARGET_ENV}_RUSTFLAGS=-Clinker-plugin-lto -Clinker=clang" | ||
| export LIBDD_OTEL_THREAD_CTX_INLINE=1 | ||
|
|
||
| cargo build --release \ | ||
| --target "$TARGET" \ | ||
| -p libdd-otel-thread-ctx-ffi \ | ||
| "${EXTRA_ARGS[@]}" | ||
|
|
||
| # Sanity-check that the C shim was actually inlined, if `nm` is available. | ||
| if ! command -v nm &>/dev/null; then | ||
| echo >&2 "WARNING: skipping sanity check that the C TLS shim was inlined (\`nm\` not found)" | ||
| else | ||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" | ||
| SO="$REPO_ROOT/target/$TARGET/release/liblibdd_otel_thread_ctx_ffi.so" | ||
|
|
||
| if [[ -f "$SO" ]] && nm "$SO" 2>/dev/null | grep -q 'libdd_get_otel_thread_ctx'; then | ||
| echo >&2 "WARNING: build succeeded but the C TLS shim (libdd_get_otel_thread_ctx_v1) was NOT inlined." | ||
| echo >&2 "Cross-language LTO may not be working. Check that clang and lld versions are recent enough and compatible with the Rust toolchain's LLVM." | ||
| exit 1 | ||
| fi | ||
| fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,64 @@ | ||
| // Copyright 2026-Present Datadog, Inc. https://www.datadoghq.com/ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| extern crate build_common; | ||
|
|
||
| use std::env; | ||
| use std::process::Command; | ||
|
|
||
| fn clang_is_available() -> bool { | ||
| Command::new("clang") | ||
| .arg("--version") | ||
| .output() | ||
| .map(|o| o.status.success()) | ||
| .unwrap_or(false) | ||
| } | ||
|
|
||
| fn main() { | ||
| // Only compile the TLS shim on Linux. | ||
| #[cfg(target_os = "linux")] | ||
| { | ||
| let mut build = cc::Build::new(); | ||
| let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); | ||
| let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); | ||
|
|
||
| if target_os != "linux" { | ||
| return; | ||
| } | ||
|
|
||
| println!("cargo:rerun-if-env-changed=LIBDD_OTEL_THREAD_CTX_INLINE"); | ||
| println!("cargo:rerun-if-changed=src/tls_shim.c"); | ||
|
|
||
| // The otel-thread-ctx FFI crate has a special flag to inline the C shim inside the final | ||
| // library. This setup has additional requirements for the build of this crate, which are | ||
| // enforced below when the flag is set. | ||
| let inline_mode = env::var_os("LIBDD_OTEL_THREAD_CTX_INLINE").is_some_and(|v| v == "1"); | ||
|
|
||
| let mut build = cc::Build::new(); | ||
|
|
||
| if inline_mode { | ||
| assert!( | ||
| clang_is_available(), | ||
| "LIBDD_OTEL_THREAD_CTX_INLINE is set but `clang` was not found. \ | ||
| Cross-language LTO requires clang as the C compiler." | ||
| ); | ||
| build.compiler("clang"); | ||
| build.flag("-flto=thin"); | ||
|
|
||
| // Any binary linking this crate in inline mode (including test | ||
| // binaries) needs lld, because -Clinker-plugin-lto passes LTO plugin | ||
| // options that only lld understands. | ||
| if let Some(dir) = build_common::find_rust_lld_dir() { | ||
| println!("cargo:rustc-link-arg=-B{}", dir.display()); | ||
| } | ||
| println!("cargo:rustc-link-arg=-fuse-ld=lld"); | ||
|
|
||
| // Note: in the inline setup, TLS dialect selection is handled by the linker and is taken | ||
| // care of by the build script of otel-thread-ctx-ffi | ||
| } else { | ||
| // - On aarch64, TLSDESC is already the only dynamic TLS model so no flag is needed. | ||
| // - On x86-64, we use `-mtls-dialect=gnu2` (supported since GCC 4.4 and Clang 19+) to force | ||
| // the use of TLSDESC as mandated by the spec. If it's not supported, this build will | ||
| // fail. | ||
| #[cfg(target_arch = "x86_64")] | ||
| build.flag("-mtls-dialect=gnu2"); | ||
|
|
||
| build.file("src/tls_shim.c").compile("tls_shim"); | ||
| println!("cargo:rerun-if-changed=src/tls_shim.c"); | ||
| if target_arch == "x86_64" { | ||
| build.flag("-mtls-dialect=gnu2"); | ||
| } | ||
| } | ||
|
|
||
| build.file("src/tls_shim.c").compile("tls_shim"); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is somehow unrelated, but using static
cfg(target_os = "linux")is actually not the way to go in a build script (during cross-compilation typically, the target_os of the script and of the dynamic library could differ), so fixing it passing by.