[BUG] Linker error with `--bitcode-libs` and CUDA `libdevice` due to target triple mismatch
@zyx-billy is already working on this.
Since Oct 16, 2025.
- Dominant language
- Mojo
- Stars
- 29.8k
- Forks
- 3.2k
- PR merge metrics
- No merged PRs in 30d
Description
Bug description
When using the experimental --bitcode-libs flag to link against NVIDIA's CUDA libdevice, the build fails with a ptxas fatal: Unresolved extern function error.
The root cause is a mismatch between the target triple of the libdevice bitcode (nvptx64-nvidia-gpulibs) and the target triple used by Mojo for NVIDIA GPUs (nvptx64-nvidia-cuda).
Steps to reproduce
The following code attempts to call __nv_logf from libdevice.
Code (main.mojo):
from gpu.host import DeviceContext
from gpu.id import block_dim, block_idx, thread_idx
from layout import Layout, LayoutTensor
from math import ceildiv
from sys import external_call, has_nvidia_gpu_accelerator
alias float_dtype = DType.float32
alias vector_size = 1000
alias layout = Layout.row_major(vector_size)
alias block_size = 256
alias num_blocks = ceildiv(vector_size, block_size)
fn apply_logf(
in_tensor: LayoutTensor[float_dtype, layout, ImmutableAnyOrigin],
out_tensor: LayoutTensor[float_dtype, layout, MutableAnyOrigin],
):
var tid = block_idx.x * block_dim.x + thread_idx.x
if tid < vector_size:
# float __nv_logf(float);
out_tensor[tid] = external_call["__nv_logf", Float32](
rebind[Float32](in_tensor[tid])
)
def main():
@parameter
if not has_nvidia_gpu_accelerator():
print("No compatible GPU found")
else:
ctx = DeviceContext()
in_host_buffer = ctx.enqueue_create_host_buffer[float_dtype](
vector_size
)
ctx.synchronize()
for i in range(vector_size):
in_host_buffer[i] = Scalar[float_dtype](i)
print("Input buffer: ", in_host_buffer)
in_device_buffer = ctx.enqueue_create_buffer[float_dtype](vector_size)
ctx.enqueue_copy(dst_buf=in_device_buffer, src_buf=in_host_buffer)
out_device_buffer = ctx.enqueue_create_buffer[float_dtype](vector_size)
in_tensor = LayoutTensor[float_dtype, layout](in_device_buffer)
out_tensor = LayoutTensor[float_dtype, layout](out_device_buffer)
ctx.enqueue_function[apply_logf](
in_tensor,
out_tensor,
grid_dim=num_blocks,
block_dim=block_size,
)
out_host_buffer = ctx.enqueue_create_host_buffer[float_dtype](
vector_size
)
ctx.enqueue_copy(dst_buf=out_host_buffer, src_buf=out_device_buffer)
ctx.synchronize()
print("Output vector:", out_host_buffer)
Command:
# Assuming libdevice.10.bc is located at the standard path
mojo run --bitcode-libs /usr/local/cuda/nvvm/libdevice/libdevice.10.bc main.mojo
Error:
/path/to/main.mojo:1:1: error: ptxas fatal : Unresolved extern function '__nv_logf'
from gpu.host import DeviceContext
^
mojo: error: failed to run the pass manager
Analysis
The target triple and data layout in libdevice.10.bc are:
$ llvm-dis -o - /usr/local/cuda/nvvm/libdevice/libdevice.10.bc | head -n 5
; ModuleID = '/usr/local/cuda/nvvm/libdevice/libdevice.10.bc'
source_filename = "/usr/local/cuda/nvvm/libdevice/libdevice.10.bc"
target datalayout = "e-i64:64-v16:16-v32:32-n16:32:64"
target triple = "nvptx64-nvidia-gpulibs"
Mojo generates code for nvptx64-nvidia-cuda with a different data layout. The target triple mismatch causes the fatal linker error. The data layout mismatch only produces a non-fatal warning if the triple is corrected:
warning: Linking two modules of different data layouts: './libdevice.cuda.bc' is 'e-i64:64-v16:16-v32:32-n16:32:64' whereas 'main.mojo' is 'e-p3:32:32-p4:32:32-p5:32:32-p6:32:32-p7:32:32-i64:64-i128:128-i256:256-v16:16-v32:32-n16:32:64'
Workaround
A successful workaround involves manually patching the libdevice bitcode to match Mojo's expected triple and layout.
-
Disassemble the bitcode:
$ llvm-dis /usr/local/cuda/nvvm/libdevice/libdevice.10.bc -o libdevice.10.ll -
Replace the target triple:
$ sed -i 's/target triple = "nvptx64-nvidia-gpulibs"/target triple = "nvptx64-nvidia-cuda"/' libdevice.10.ll -
Replace the data layout (to suppress the warning):
$ sed -i 's/target datalayout = "e-i64:64-v16:16-v32:32-n16:32:64"/target datalayout = "e-p3:32:32-p4:32:32-p5:32:32-p6:32:32-p7:32:32-i64:64-i128:128-i256:256-v16:16-v32:32-n16:32:64"/' libdevice.10.ll -
Reassemble the bitcode:
$ llvm-as libdevice.10.ll -o libdevice.cuda.10.bc
Using the patched libdevice.cuda.10.bc file allows the code to link and run successfully.
Suggested Approach
This is a known issue within the LLVM/clang community. Its linker includes a special case to automatically handle libdevice's non-standard triple and data layout by suppressing the compatibility checks, as seen in llvm/lib/Linker/IRMover.cpp:
Error IRLinker::run() {
// ...
// During CUDA compilation we have to link with the bitcode supplied with
// CUDA. libdevice bitcode either has no data layout set (pre-CUDA-11), or has
// the layout that is different from the one used by LLVM/clang (it does not
// include i128). Issuing a warning is not very helpful as there's not much
// the user can do about it.
bool EnableDLWarning = true;
bool EnableTripleWarning = true;
if (SrcTriple.isNVPTX() && DstTriple.isNVPTX()) {
bool SrcHasLibDeviceDL =
(SrcM->getDataLayoutStr().empty() ||
SrcM->getDataLayoutStr() == "e-i64:64-v16:16-v32:32-n16:32:64");
// libdevice bitcode uses nvptx64-nvidia-gpulibs or just
// 'nvptx-unknown-unknown' triple (before CUDA-10.x) and is compatible with
// all NVPTX variants.
bool SrcHasLibDeviceTriple = (SrcTriple.getVendor() == Triple::NVIDIA &&
SrcTriple.getOSName() == "gpulibs") ||
(SrcTriple.getVendorName() == "unknown" &&
SrcTriple.getOSName() == "unknown");
EnableTripleWarning = !SrcHasLibDeviceTriple;
EnableDLWarning = !(SrcHasLibDeviceTriple && SrcHasLibDeviceDL);
}
// ...
}
The --bitcode-libs flag could be enhanced to include this same exception for CUDA's libdevice, allowing it to link without manual intervention.
System information
System Information
$ pixi info
System
------------
Pixi version: 0.56.0
Platform: linux-64
Virtual packages: __unix=0=0
: __linux=5.15.167.4=0
: __glibc=2.39=0
: __cuda=12.9=0
: __archspec=1=x86_64_v4
Cache dir: /home/leandro/.cache/rattler/cache
Auth storage: /home/leandro/.rattler/credentials.json
Config locations: No config files found
Global
------------
Bin dir: /home/leandro/.pixi/bin
Environment dir: /home/leandro/.pixi/envs
Manifest dir: /home/leandro/.pixi/manifests/pixi-global.toml
Workspace
------------
Name: libdevice
Version: 0.1.0
Manifest file: /home/leandro/project/libdevice/pixi.toml
Last updated: 15-10-2025 01:14:37
Environments
------------
Environment: default
Features: default
Channels: https://conda.modular.com/max-nightly, conda-forge
Dependency count: 1
Dependencies: mojo
Target platforms: linux-64
Prefix location: /home/leandro/project/libdevice/.pixi/envs/default
Version Information for Mojo
$ mojo --version
Mojo 0.25.7.0.dev2025101405 (81e439c9)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.