When working on a CUDA project I get an nvlink
error stating that __cxa_pure_virtual
has undefned reference. My repository is structured like the example in ModernCMake - Extended example.
Surprisingly I don't get this error when compiling with nvcc
, I want to compile my code using clang
because I want to work with clang
's LSP.
This is probably due to the way I'm handling virtual classes. Here is my compile command with the related error:
➜ CUDA-Path-Tracer git:(main) ✗ make build
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CUDA_COMPILER=clang++-20 -DCMAKE_CXX_COMPILER=clang++-20 -DCMAKE_CUDA_ARCHITECTURES=native
-- The CXX compiler identification is Clang 20.0.0
-- The CUDA compiler identification is Clang 20.0.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/clang++-20 - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Detecting CUDA compiler ABI info
-- Detecting CUDA compiler ABI info - done
-- Check for working CUDA compiler: /usr/bin/clang++-20 - skipped
-- Detecting CUDA compile features
-- Detecting CUDA compile features - done
-- Performing Test HAVE_FLAG__ffile_prefix_map__home_eduard_Git_CUDA_Path_Tracer_build__deps_catch2_src__
-- Performing Test HAVE_FLAG__ffile_prefix_map__home_eduard_Git_CUDA_Path_Tracer_build__deps_catch2_src__ - Success
-- Configuring done (14.8s)
-- Generating done (0.1s)
-- Build files have been written to: /home/eduard/Git/CUDA-Path-Tracer/build
cmake --build build
gmake[1]: Entering directory '/home/eduard/Git/CUDA-Path-Tracer/build'
gmake[2]: Entering directory '/home/eduard/Git/CUDA-Path-Tracer/build'
gmake[3]: Entering directory '/home/eduard/Git/CUDA-Path-Tracer/build'
gmake[3]: Leaving directory '/home/eduard/Git/CUDA-Path-Tracer/build'
gmake[3]: Entering directory '/home/eduard/Git/CUDA-Path-Tracer/build'
[ 0%] Building CUDA object src/CMakeFiles/cuda_path_tracer_lib.dir/error.cu.o
clang++-20: warning: CUDA version 12.6 is only partially supported [-Wunknown-cuda-version]
[ 0%] Building CUDA object src/CMakeFiles/cuda_path_tracer_lib.dir/vec3.cu.o
clang++-20: warning: CUDA version 12.6 is only partially supported [-Wunknown-cuda-version]
[ 1%] Building CUDA object src/CMakeFiles/cuda_path_tracer_lib.dir/sphere.cu.o
clang++-20: warning: CUDA version 12.6 is only partially supported [-Wunknown-cuda-version]
[ 2%] Building CUDA object src/CMakeFiles/cuda_path_tracer_lib.dir/render.cu.o
clang++-20: warning: CUDA version 12.6 is only partially supported [-Wunknown-cuda-version]
[ 3%] Building CUDA object src/CMakeFiles/cuda_path_tracer_lib.dir/world.cu.o
clang++-20: warning: CUDA version 12.6 is only partially supported [-Wunknown-cuda-version]
[ 4%] Building CUDA object src/CMakeFiles/cuda_path_tracer_lib.dir/ray.cu.o
clang++-20: warning: CUDA version 12.6 is only partially supported [-Wunknown-cuda-version]
[ 5%] Building CXX object src/CMakeFiles/cuda_path_tracer_lib.dir/image.cpp.o
nvlink error : Undefined reference to '__cxa_pure_virtual' in 'src/CMakeFiles/cuda_path_tracer_lib.dir/sphere.cu.o'
gmake[3]: *** [src/CMakeFiles/cuda_path_tracer_lib.dir/build.make:177: src/CMakeFiles/cuda_path_tracer_lib.dir/sm_90.cubin] Error 255
gmake[3]: Leaving directory '/home/eduard/Git/CUDA-Path-Tracer/build'
gmake[2]: *** [CMakeFiles/Makefile2:924: src/CMakeFiles/cuda_path_tracer_lib.dir/all] Error 2
gmake[2]: Leaving directory '/home/eduard/Git/CUDA-Path-Tracer/build'
gmake[1]: *** [Makefile:101: all] Error 2
gmake[1]: Leaving directory '/home/eduard/Git/CUDA-Path-Tracer/build'
make: *** [Makefile:12: build] Error 2
My repository atm is structured in this way:
├── apps
│ ├── CMakeLists.txt
│ └── main.cpp
├── CMakeLists.txt
├── include
│ └── cuda_path_tracer
│ ├── error.cuh
│ ├── image.hpp
│ ├── ray.cuh
│ ├── render.cuh
│ ├── shape.cuh
│ ├── sphere.cuh
│ ├── vec3.cuh
│ └── world.cuh
├── LICENSE
├── llvm.sh
├── Makefile
├── README.md
├── report
│ └── report.typ
├── src
│ ├── CMakeLists.txt
│ ├── error.cu
│ ├── image.cpp
│ ├── ray.cu
│ ├── render.cu
│ ├── sphere.cu
│ ├── vec3.cu
│ └── world.cu
└── tests
├── CMakeLists.txt
├── test_error.cpp
├── test_image.cpp
├── test_ray.cpp
└── test_vec3.cpp
The only virtual class I have is Shape
which is defined as
/**
* @file shape.cuh
*/
#pragma once
#include "cuda_path_tracer/ray.cuh"
class Shape {
public:
__host__ __device__ virtual ~Shape() = default;
__host__ __device__ virtual auto hit(const Ray &r) const -> bool = 0;
};
with the corresponding Sphere
class being
/**
* @file sphere.cuh
*/
#pragma once
#include "shape.cuh"
class Sphere : public Shape {
public:
__host__ __device__ Sphere(const Vec3 ¢er, float radius);
__host__ __device__ auto hit(const Ray &r) const -> bool override;
private:
Vec3 center;
float radius;
};
and
/**
* @file sphere.cu
*/
#include "cuda_path_tracer/sphere.cuh"
__host__ __device__ Sphere::Sphere(const Vec3 ¢er, float radius)
: center(center), radius(radius) {}
__host__ __device__ auto Sphere::hit(const Ray &r) const -> bool {
Vec3 oc = r.getOrigin() - center;
float a = r.getDirection().dot(r.getDirection());
float b = 2.0f * oc.dot(r.getDirection());
float c = oc.dot(oc) - radius * radius;
float discriminant = b * b - 4 * a * c;
return discriminant > 0;
}
I have tried different versions of the llvm
suite, specifically v18, 19 and now 20. I have tried tried defining implicit copy/move members as suggested by clang-tidy
, I have tried defining a default constructor in shape.cuh
but to no avail.
I can confirm that the problem stems from these classes because the code compiles when their code is commented out.
I have also tried defining the __cxa_pure_virtual
function as a loop as suggested in some issues (while(1) {}
) but that also did not work or perhaps I defined it in the wrong header.
Turns out the solution was indeed to define the __cxa_pure_virtual
as a dummy function due to https://bugs.llvm.org/show_bug.cgi?id=49839 , the issue is that I wasn't adding the __device__
attribute at the front. The function should then be something like:
extern "C" __device__ void __cxa_pure_virtual() {
while (1) {
}
}