modular / modular/modular

[BUG] Linker error with `--bitcode-libs` and CUDA `libdevice` due to target triple mismatch

Open
#5,461 0 comments 0 reactions 1 assignee View on GitHub

@zyx-billy is already working on this.

Since Oct 16, 2025.

auto_priority_triaged bug mojo
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.

  1. Disassemble the bitcode:

    $ llvm-dis /usr/local/cuda/nvvm/libdevice/libdevice.10.bc -o libdevice.10.ll
    
  2. Replace the target triple:

    $ sed -i 's/target triple = "nvptx64-nvidia-gpulibs"/target triple = "nvptx64-nvidia-cuda"/' libdevice.10.ll
    
  3. 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
    
  4. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.