Skip to content

Replaced cusparse wrappers with simple unique_ptrs and more RAII #1342

Open
Bubullzz wants to merge 2 commits into
NVIDIA:mainfrom
Bubullzz:replace_wrappers_with_unique_ptrs
Open

Replaced cusparse wrappers with simple unique_ptrs and more RAII #1342
Bubullzz wants to merge 2 commits into
NVIDIA:mainfrom
Bubullzz:replace_wrappers_with_unique_ptrs

Conversation

@Bubullzz
Copy link
Copy Markdown
Contributor

@Bubullzz Bubullzz commented May 29, 2026

The idea is to replace the big handmade wrappers for cusparse objects with more robust std::unique_ptr

Trying to clean the code a bit as a side task while waiting for other jobs to finish. This is a subset of the full PR to get feedback from the team before going forward.

@Bubullzz Bubullzz requested a review from a team as a code owner May 29, 2026 15:24
@Bubullzz Bubullzz requested review from chris-maes and rg20 May 29, 2026 15:24
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented May 29, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@Bubullzz Bubullzz added the do not merge Do not merge if this flag is set label May 29, 2026
@Bubullzz Bubullzz changed the title Replaced wrappers with simple unique_ptrs and more RAII Replaced cusparse wrappers with simple unique_ptrs and more RAII May 29, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: aa7413fe-080c-4a03-ae65-e08ac59bcaa6

📥 Commits

Reviewing files that changed from the base of the PR and between e471907 and dafb3e3.

📒 Files selected for processing (9)
  • cpp/src/barrier/cusparse_view.hpp
  • cpp/src/barrier/sparse_matrix_kernels.cuh
  • cpp/src/pdlp/optimal_batch_size_handler/optimal_batch_size_handler.cu
  • cpp/src/pdlp/pdhg.cu
  • cpp/src/pdlp/pdlp.cu
  • cpp/src/pdlp/restart_strategy/pdlp_restart_strategy.cu
  • cpp/src/pdlp/step_size_strategy/adaptive_step_size_strategy.cu
  • cpp/src/pdlp/termination_strategy/convergence_information.cu
  • cpp/src/pdlp/termination_strategy/infeasibility_information.cu

📝 Walkthrough

Walkthrough

Refactors cuSPARSE descriptor lifetime handling: replaces custom wrapper types with owning RAII unique_ptr aliases and non-owning descriptor views, adds factory helpers (make_csr/make_dnvec/make_dnmat and SpMVOp factories), and updates constructors and all cuSPARSE call sites across barrier and PDLP to use .get()/view parameters.

Changes

cuSPARSE Descriptor RAII Migration

Layer / File(s) Summary
RAII descriptor types and factory helpers
cpp/src/pdlp/cusparse_view.hpp
Defines custom deleters (cusparse_sp_mat_deleter, cusparse_dn_vec_deleter, cusparse_dn_mat_deleter), owning aliases (cusparse_sp_mat_uptr, cusparse_dn_vec_uptr, cusparse_dn_mat_uptr), non-owning view aliases (cusparse_sp_mat_descr_view, cusparse_dn_vec_descr_view, cusparse_dn_mat_descr_view), and factory functions (make_csr, make_dnvec, make_dnmat). Adds CUDA 13.2+ SpMVOp descriptor/plan types and factory declarations.
Barrier module adoption of RAII descriptors
cpp/src/barrier/cusparse_info.hpp, cpp/src/barrier/barrier.cu
Updates cusparse_info_t to use detail::cusparse_sp_mat_uptr and cusparse_spgemm_uptr for descriptor members, eliminating manual destructor cleanup. Updates iteration_data_t member types and gpu_adat_multiply signature to accept detail::cusparse_dn_vec_descr_view parameters. Updates numerous cuSPARSE call sites to pass .get() for descriptor handles.
Barrier cusparse_view refactoring
cpp/src/barrier/cusparse_view.cu, cpp/src/barrier/cusparse_view.hpp
Refactors constructor to build A_ and A_T_ descriptors via detail::make_csr and uses temporary RAII dense-vector descriptors for buffer size queries. create_vector now returns detail::cusparse_dn_vec_uptr. spmv and transpose_spmv overloads accept descriptor view parameters and pass .get() to cuSPARSE.
SpMVOp descriptor/plan RAII and runtime dispatch
cpp/src/pdlp/cusparse_view.cu
Adds dlsym-resolved SpMVOp descriptor/plan deleter implementations and introduces make_spmvop_descr/make_spmvop_plan factory helpers for CUDA 13.2+ builds. Updates cusparse_spmvop_run to accept view-type parameters.
PDLP constructor descriptor initialization and buffer size queries
cpp/src/pdlp/cusparse_view.cu
Refactors first and second constructors to instantiate descriptors (A, A_T, c, solutions, gradients, batch matrices) via make_* factories. Updates cuSPARSE buffer size queries for SpMV and SpMM and CUDA 12.4+ preprocess calls to pass .get() pointers, covering non-batch, batch-mode, and restart/reflected-primal paths.
Mixed-precision float descriptor handling
cpp/src/pdlp/cusparse_view.cu, cpp/src/pdlp/cusparse_view.hpp
Refactors A_mixed_ and A_T_mixed_ float CSR copies to use make_csr. Updates mixed_precision_spmv, mixed_precision_spmv_buffersize, and mixed_precision_spmv_preprocess function signatures to accept view-type parameters and pass .get() pointers.
Helper functions and CSR pointer redirection
cpp/src/pdlp/cusparse_view.cu
Updates my_cusparsespmm_preprocess template to accept view types. Updates redirect_cusparse_csr_structure_pointers to call cusparseCsrSetPointers with .get() descriptors including mixed-precision copies. Updates SpGEMM usage in sparse kernels to use RAII-managed descriptors.
Optimal batch size & benchmarks
cpp/src/pdlp/optimal_batch_size_handler/optimal_batch_size_handler.cu
Changes benchmark context and evaluate_node to accept view-type matrix descriptors; constructs dense x_descr/y_descr via make_dnmat and calls cuSPARSE APIs with .get().
PDHG/PDLP call-site wiring
cpp/src/pdlp/pdhg.cu, cpp/src/pdlp/pdlp.cu
Updates compute_next_dual_solution, compute_At_y, compute_A_x, update_solution, batch-resize, compute_fixed_error, and initial step-size code paths to pass .get() pointers for matrices/vectors/plans in both mixed-precision and standard paths.
Restart & termination updates
cpp/src/pdlp/restart_strategy/*, cpp/src/pdlp/termination_strategy/*
Update restart-strategy, convergence, and infeasibility routines to pass .get() descriptor pointers for SpMV/SpMM calls and recover/rebind tmp buffers in restart constructor using cusparseDnVecGetValues.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested labels

non-breaking, improvement

Suggested reviewers

  • rg20
  • chris-maes
  • KyleFromNVIDIA
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main refactoring work: replacing custom cuSPARSE wrapper classes with std::unique_ptr-based RAII patterns across multiple files.
Description check ✅ Passed The description clearly explains the purpose of the PR: replacing handmade cuSPARSE wrappers with std::unique_ptr for better robustness, and frames it as a subset of a larger cleanup effort.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch replace_wrappers_with_unique_ptrs

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/src/pdlp/cusparse_view.cu (1)

240-242: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing has_value() check before dereferencing optional.

func at line 242 is dereferenced without checking if the dlsym lookup succeeded.

Proposed fix
 void cusparse_spmvop_run(cusparseHandle_t handle,
                          cusparseSpMVOpPlan_t plan,
                          const void* alpha,
                          const void* beta,
                          cusparse_dn_vec_descr_view vecX,
                          cusparse_dn_vec_descr_view vecY,
                          cusparse_dn_vec_descr_view vecZ,
                          cudaStream_t stream)
 {
   static const auto func = dynamic_load_runtime::function<cusparseSpMVOp_sig>("cusparseSpMVOp");
+  cuopt_expects(func.has_value(), "cusparseSpMVOp symbol not found at runtime");
   RAFT_CUSPARSE_TRY(cusparseSetStream(handle, stream));
   RAFT_CUSPARSE_TRY((*func)(handle, plan, alpha, beta, vecX, vecY, vecZ));
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/pdlp/cusparse_view.cu` around lines 240 - 242, The code dereferences
the optional dynamic_load_runtime::function<cusparseSpMVOp_sig> named func
without checking it; before calling (*func)(handle, plan, alpha, beta, vecX,
vecY, vecZ) add a check like if (!func.has_value()) and handle the failure (log
or return an error/throw) with a clear message including the symbol name
"cusparseSpMVOp"; keep the existing RAFT_CUSPARSE_TRY usage for actual cuSPARSE
calls and ensure the early error path prevents the dereference of func and
returns/propagates an appropriate error.
🧹 Nitpick comments (2)
cpp/src/pdlp/cusparse_view.hpp (1)

38-57: 💤 Low value

Consider _t suffix for deleter types per project naming conventions.

The coding guidelines specify types/structs should use snake_case_t with _t suffix (e.g., cusparse_sp_mat_deleter_t). However, the current naming follows common STL deleter patterns, so this is a stylistic choice.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/pdlp/cusparse_view.hpp` around lines 38 - 57, The structs
cusparse_sp_mat_deleter, cusparse_dn_vec_deleter, and cusparse_dn_mat_deleter do
not follow the project naming convention requiring a snake_case_t suffix; rename
them to cusparse_sp_mat_deleter_t, cusparse_dn_vec_deleter_t, and
cusparse_dn_mat_deleter_t respectively, update all uses/typedefs/usings in this
compilation unit and any headers that reference these types (e.g., unique_ptr
deleter specializations or variable declarations), and ensure the operator()
implementations remain unchanged and still call RAFT_CUSPARSE_TRY_NO_THROW on
cusparseDestroySpMat/cusparseDestroyDnVec/cusparseDestroyDnMat.
cpp/src/pdlp/cusparse_view.cu (1)

147-149: 💤 Low value

Inconsistent error-checking macro.

Line 147 uses CUSPARSE_CHECK while other cuSPARSE calls in this file use RAFT_CUSPARSE_TRY. Consider using RAFT_CUSPARSE_TRY for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/pdlp/cusparse_view.cu` around lines 147 - 149, Replace the
inconsistent CUSPARSE_CHECK call with the RAFT_CUSPARSE_TRY macro to match the
rest of the file: change the call to cusparseSetStream(...) so it is wrapped
with RAFT_CUSPARSE_TRY rather than CUSPARSE_CHECK, keeping the same arguments
and preserving the subsequent RAFT_CUSPARSE_TRY(cusparseSpMM_preprocess(...))
call; ensure you reference and use the RAFT_CUSPARSE_TRY macro for both
cusparseSetStream and cusparseSpMM_preprocess to maintain consistent error
handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/src/pdlp/cusparse_view.cu`:
- Around line 213-218: The code dereferences the optional dynamic loader result
fn (dynamic_load_runtime::function<cusparseSpMVOp_createDescr_sig>) without
checking presence; update the factory that creates the cusparseSpMVOp_descr (the
block that calls (*fn)(...)) to first test fn.has_value() (or if(!fn) branch)
and handle the missing symbol by returning an empty cusparse_spmvop_descr_uptr
(or otherwise propagate a clear error) instead of dereferencing; ensure the
handling is applied where cusparseSpMVOp_createDescr is invoked so callers of
is_cusparse_runtime_spmvop_supported() are not assumed sufficient.
- Around line 221-228: The dynamic loader result `fn` in make_spmvop_plan is
dereferenced without checking it exists; update make_spmvop_plan to test
dynamic_load_runtime::function<...> fn for presence (e.g., if (!fn) throw or
return an error) before calling (*fn)(...), mirroring the fix used in
make_spmvop_descr so that cusparseSpMVOp_createPlan is only invoked when the
symbol lookup succeeded and RAFT_CUSPARSE_TRY is reached with a valid function
pointer.

---

Outside diff comments:
In `@cpp/src/pdlp/cusparse_view.cu`:
- Around line 240-242: The code dereferences the optional
dynamic_load_runtime::function<cusparseSpMVOp_sig> named func without checking
it; before calling (*func)(handle, plan, alpha, beta, vecX, vecY, vecZ) add a
check like if (!func.has_value()) and handle the failure (log or return an
error/throw) with a clear message including the symbol name "cusparseSpMVOp";
keep the existing RAFT_CUSPARSE_TRY usage for actual cuSPARSE calls and ensure
the early error path prevents the dereference of func and returns/propagates an
appropriate error.

---

Nitpick comments:
In `@cpp/src/pdlp/cusparse_view.cu`:
- Around line 147-149: Replace the inconsistent CUSPARSE_CHECK call with the
RAFT_CUSPARSE_TRY macro to match the rest of the file: change the call to
cusparseSetStream(...) so it is wrapped with RAFT_CUSPARSE_TRY rather than
CUSPARSE_CHECK, keeping the same arguments and preserving the subsequent
RAFT_CUSPARSE_TRY(cusparseSpMM_preprocess(...)) call; ensure you reference and
use the RAFT_CUSPARSE_TRY macro for both cusparseSetStream and
cusparseSpMM_preprocess to maintain consistent error handling.

In `@cpp/src/pdlp/cusparse_view.hpp`:
- Around line 38-57: The structs cusparse_sp_mat_deleter,
cusparse_dn_vec_deleter, and cusparse_dn_mat_deleter do not follow the project
naming convention requiring a snake_case_t suffix; rename them to
cusparse_sp_mat_deleter_t, cusparse_dn_vec_deleter_t, and
cusparse_dn_mat_deleter_t respectively, update all uses/typedefs/usings in this
compilation unit and any headers that reference these types (e.g., unique_ptr
deleter specializations or variable declarations), and ensure the operator()
implementations remain unchanged and still call RAFT_CUSPARSE_TRY_NO_THROW on
cusparseDestroySpMat/cusparseDestroyDnVec/cusparseDestroyDnMat.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 43105512-0a29-4bb5-b0f5-b5cd32da0e79

📥 Commits

Reviewing files that changed from the base of the PR and between ea7acf0 and e471907.

📒 Files selected for processing (5)
  • cpp/src/barrier/barrier.cu
  • cpp/src/barrier/cusparse_info.hpp
  • cpp/src/barrier/cusparse_view.cu
  • cpp/src/pdlp/cusparse_view.cu
  • cpp/src/pdlp/cusparse_view.hpp

Comment on lines +213 to +218
static const auto fn =
dynamic_load_runtime::function<cusparseSpMVOp_createDescr_sig>("cusparseSpMVOp_createDescr");
cusparseSpMVOpDescr_t descr{nullptr};
RAFT_CUSPARSE_TRY(
dlsym_create(handle, &descr_, opA, matA, vecX, vecY, vecZ, computeType, buffer.data()));
need_destruction_ = true;
}

cusparse_spmvop_descr_wrapper_t::operator cusparseSpMVOpDescr_t() const { return descr_; }

cusparse_spmvop_plan_wrapper_t::cusparse_spmvop_plan_wrapper_t()
: plan_(nullptr), need_destruction_(false)
{
}

cusparse_spmvop_plan_wrapper_t::~cusparse_spmvop_plan_wrapper_t()
{
if (!need_destruction_) { return; }
RAFT_CUSPARSE_TRY_NO_THROW(dlsym_destroy(plan_));
}

cusparse_spmvop_plan_wrapper_t::cusparse_spmvop_plan_wrapper_t(
const cusparse_spmvop_plan_wrapper_t& other)
: plan_(other.plan_), need_destruction_(false)
{
}

cusparse_spmvop_plan_wrapper_t& cusparse_spmvop_plan_wrapper_t::operator=(
cusparse_spmvop_plan_wrapper_t&& other)
{
if (need_destruction_) { RAFT_CUSPARSE_TRY(dlsym_destroy(plan_)); }
plan_ = other.plan_;
need_destruction_ = other.need_destruction_;
other.need_destruction_ = false;
return *this;
(*fn)(handle, &descr, opA, matA, vecX, vecY, vecZ, computeType, buffer.data()));
return cusparse_spmvop_descr_uptr{descr};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing has_value() check before dereferencing optional.

fn is a std::optional that could be nullopt if the dlsym lookup fails. Dereferencing it at line 217 without checking has_value() would cause undefined behavior. While is_cusparse_runtime_spmvop_supported() probes for cusparseSpMVOp, it doesn't verify that cusparseSpMVOp_createDescr exists.

Proposed fix
 cusparse_spmvop_descr_uptr make_spmvop_descr(cusparseHandle_t handle,
                                              cusparseOperation_t opA,
                                              cusparse_sp_mat_descr_view matA,
                                              cusparse_dn_vec_descr_view vecX,
                                              cusparse_dn_vec_descr_view vecY,
                                              cusparse_dn_vec_descr_view vecZ,
                                              cudaDataType computeType,
                                              rmm::device_uvector<uint8_t>& buffer)
 {
   static const auto fn =
     dynamic_load_runtime::function<cusparseSpMVOp_createDescr_sig>("cusparseSpMVOp_createDescr");
+  cuopt_expects(fn.has_value(), "cusparseSpMVOp_createDescr symbol not found at runtime");
   cusparseSpMVOpDescr_t descr{nullptr};
   RAFT_CUSPARSE_TRY(
     (*fn)(handle, &descr, opA, matA, vecX, vecY, vecZ, computeType, buffer.data()));
   return cusparse_spmvop_descr_uptr{descr};
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static const auto fn =
dynamic_load_runtime::function<cusparseSpMVOp_createDescr_sig>("cusparseSpMVOp_createDescr");
cusparseSpMVOpDescr_t descr{nullptr};
RAFT_CUSPARSE_TRY(
dlsym_create(handle, &descr_, opA, matA, vecX, vecY, vecZ, computeType, buffer.data()));
need_destruction_ = true;
}
cusparse_spmvop_descr_wrapper_t::operator cusparseSpMVOpDescr_t() const { return descr_; }
cusparse_spmvop_plan_wrapper_t::cusparse_spmvop_plan_wrapper_t()
: plan_(nullptr), need_destruction_(false)
{
}
cusparse_spmvop_plan_wrapper_t::~cusparse_spmvop_plan_wrapper_t()
{
if (!need_destruction_) { return; }
RAFT_CUSPARSE_TRY_NO_THROW(dlsym_destroy(plan_));
}
cusparse_spmvop_plan_wrapper_t::cusparse_spmvop_plan_wrapper_t(
const cusparse_spmvop_plan_wrapper_t& other)
: plan_(other.plan_), need_destruction_(false)
{
}
cusparse_spmvop_plan_wrapper_t& cusparse_spmvop_plan_wrapper_t::operator=(
cusparse_spmvop_plan_wrapper_t&& other)
{
if (need_destruction_) { RAFT_CUSPARSE_TRY(dlsym_destroy(plan_)); }
plan_ = other.plan_;
need_destruction_ = other.need_destruction_;
other.need_destruction_ = false;
return *this;
(*fn)(handle, &descr, opA, matA, vecX, vecY, vecZ, computeType, buffer.data()));
return cusparse_spmvop_descr_uptr{descr};
static const auto fn =
dynamic_load_runtime::function<cusparseSpMVOp_createDescr_sig>("cusparseSpMVOp_createDescr");
cuopt_expects(fn.has_value(), "cusparseSpMVOp_createDescr symbol not found at runtime");
cusparseSpMVOpDescr_t descr{nullptr};
RAFT_CUSPARSE_TRY(
(*fn)(handle, &descr, opA, matA, vecX, vecY, vecZ, computeType, buffer.data()));
return cusparse_spmvop_descr_uptr{descr};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/pdlp/cusparse_view.cu` around lines 213 - 218, The code dereferences
the optional dynamic loader result fn
(dynamic_load_runtime::function<cusparseSpMVOp_createDescr_sig>) without
checking presence; update the factory that creates the cusparseSpMVOp_descr (the
block that calls (*fn)(...)) to first test fn.has_value() (or if(!fn) branch)
and handle the missing symbol by returning an empty cusparse_spmvop_descr_uptr
(or otherwise propagate a clear error) instead of dereferencing; ensure the
handling is applied where cusparseSpMVOp_createDescr is invoked so callers of
is_cusparse_runtime_spmvop_supported() are not assumed sufficient.

Comment on lines +221 to +228
cusparse_spmvop_plan_uptr make_spmvop_plan(cusparseHandle_t handle, cusparseSpMVOpDescr_t descr)
{
if (need_destruction_) { RAFT_CUSPARSE_TRY(dlsym_destroy(plan_)); }
static const auto fn =
dynamic_load_runtime::function<cusparseSpMVOp_createPlan_sig>("cusparseSpMVOp_createPlan");
cusparseSpMVOpPlan_t plan{nullptr};
// cuOpt does not supply user-provided LTO IR; pass nullptr/0 so cuSPARSE JITs internally.
RAFT_CUSPARSE_TRY(dlsym_create(handle, descr, &plan_, /*ltoIRBuf=*/nullptr, /*ltoIRSize=*/0));
need_destruction_ = true;
RAFT_CUSPARSE_TRY((*fn)(handle, descr, &plan, /*ltoIRBuf=*/nullptr, /*ltoIRSize=*/0));
return cusparse_spmvop_plan_uptr{plan};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing has_value() check before dereferencing optional.

Same issue as make_spmvop_descr — line 227 dereferences fn without verifying the dlsym lookup succeeded.

Proposed fix
 cusparse_spmvop_plan_uptr make_spmvop_plan(cusparseHandle_t handle, cusparseSpMVOpDescr_t descr)
 {
   static const auto fn =
     dynamic_load_runtime::function<cusparseSpMVOp_createPlan_sig>("cusparseSpMVOp_createPlan");
+  cuopt_expects(fn.has_value(), "cusparseSpMVOp_createPlan symbol not found at runtime");
   cusparseSpMVOpPlan_t plan{nullptr};
   // cuOpt does not supply user-provided LTO IR; pass nullptr/0 so cuSPARSE JITs internally.
   RAFT_CUSPARSE_TRY((*fn)(handle, descr, &plan, /*ltoIRBuf=*/nullptr, /*ltoIRSize=*/0));
   return cusparse_spmvop_plan_uptr{plan};
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/pdlp/cusparse_view.cu` around lines 221 - 228, The dynamic loader
result `fn` in make_spmvop_plan is dereferenced without checking it exists;
update make_spmvop_plan to test dynamic_load_runtime::function<...> fn for
presence (e.g., if (!fn) throw or return an error) before calling (*fn)(...),
mirroring the fix used in make_spmvop_descr so that cusparseSpMVOp_createPlan is
only invoked when the symbol lookup succeeded and RAFT_CUSPARSE_TRY is reached
with a valid function pointer.

@mlubin
Copy link
Copy Markdown
Contributor

mlubin commented May 29, 2026

Trying to clean the code a bit as a side task while waiting for other jobs to finish.

I love the clean up effort! Leaving the review for the experts.

@Kh4ster Kh4ster requested review from Kh4ster and removed request for chris-maes and rg20 June 2, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do not merge Do not merge if this flag is set

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants