CUDADirective-1.2

Manage resource lifetimes with constructors and destructors

Required inputs: IR, StaticSemanticAnalysis

CUDA DIRECTIVE 1.2 [raii] Manage resource lifetimes with constructors and destructors

Use resource management types that acquire resources during construction and release resources upon de- struction. This pattern is commonly known as Resource Acquisition is Initialization (RAII).

Scope: Host, Device.
Audience: CUDA C++, CUDA Libraries.
Hardware Applicability: All Compute Capabilities.
Rationale

Manual resource management is unreliable. Manually managing resource lifetimes is prone to programmer error as it is easy to forget to release resources. Additionally, it is not exception safe. RAII is a zero-cost alternative that is less error prone and operates correctly in the face of exceptions.

Example 1 (Bad)
# include <memory>
# include <cassert>

int main() {
  int32_t* raw_u = nullptr;
  cudaError_t const error0 = cudaMalloc(&raw_u, sizeof(int32_t));
  assert(cudaSuccess == error0);
  // If an exception or abnormal exit occurs, the resource will not be reclaimed.
  // There is no recourse if manual reclamation is forgotten.
  cudaError_t const error1 = cudaFree(raw_u);
  assert(cudaSuccess == error1);
}
Example 2 (Good)
# include <memory>
# include <cassert>

int main() {
  auto deleter = [] (int32_t* ptr) {
    cudaError_t const error0 = cudaFree(ptr);
    assert(cudaSuccess == error0);
  };
  int32_t* raw_u = nullptr;
  cudaError_t const error1 = cudaMalloc(&raw_u, sizeof(int32_t));
  assert(cudaSuccess == error1);
  std::unique_ptr<int32_t, decltype(deleter)> up(raw_u, deleter);
  // Reclamation occurs when `up` is destroyed, either at the natural end of
  // the scope or due to an exception or abnormal exit.
}
Example 3 (Bad)
# include <memory>
# include <cassert>

int main() {
  CUstream_st* raw_stream = nullptr;
  cudaError_t const error0 = cudaStreamCreate(&raw_stream);
  assert(cudaSuccess == error0);
  // If an exception or abnormal exit occurs, the resource will not be reclaimed.
  // There is no recourse if manual reclamation is forgotten.
  cudaError_t const error1 = cudaStreamDestroy(raw_stream);
  assert(cudaSuccess == error1);
}
Example 4 (Good)
# include <memory>
# include <cassert>

int main() {
  auto deleter = [] (CUstream_st* ptr) {
    cudaError_t const error0 = cudaStreamDestroy(ptr);
    assert(cudaSuccess == error0);
  };
  CUstream_st* raw_stream = nullptr;
  cudaError_t const error1 = cudaStreamCreate(&raw_stream);
  assert(cudaSuccess == error1);
  std::unique_ptr<CUstream_st, decltype(deleter)> stream(raw_stream, deleter);
  // Reclamation occurs when `stream` is destroyed, either at the natural end of
  // the scope or due to an exception or abnormal exit.
}
Excerpt from NVIDIA CUDA C++ Guidelines for robust and safety-critical programming, Version 3.0.1, Copyright (C) 2018-2023 NVIDIA Corporation.

Possible Messages

Key

Text

Severity

Disabled

not_passed_to_smart_ptr

Use RAII: Result of allocation is not only passed to smart pointer.

None

False

Options

allocating_routines

allocating_routines : set[bauhaus.analysis.config.QualifiedName] = {'cudaMalloc', 'cudaStreamCreate'}

Routines that allocate resources which should be managed via RAII. An argument of pointer type passed to these routines is considered to hold a pointer to allocated memory, that should be managed via RAII, i.e., by passing it to a smart pointer constructor.