Skip to content

Commit fc18369

Browse files
authored
Merge pull request #183 from LLNL/feature/burmark1/error_macro
Helpers to Print Function Args on Error
2 parents 8c3a0ce + dc44a33 commit fc18369

File tree

14 files changed

+630
-74
lines changed

14 files changed

+630
-74
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,8 @@ cmake-build-*/
2121
docker-*-build/
2222
/Debug/
2323
./extern/googletest/*
24+
25+
# install directories
26+
install/
27+
install-*/
28+
install_*/

docs/sphinx/user_guide/using_camp.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@ Learn more about the Camp ``tuple`` feature on the :ref:`tuple-label` page.
106106

107107
See the full example `here <https://github.com/LLNL/RAJA/blob/0aef7cc44348d82e94e73e12f77c27ea306e47b8/include/RAJA/util/View.hpp>`_.
108108

109+
RAJA also uses Camp for error checking and generating error strings for cuda
110+
and hip API functions.
111+
112+
.. code-block:: cpp
113+
114+
CAMP_HIP_API_INVOKE_AND_CHECK(hipLaunchKernel, func, gridDim, blockDim, args, shmem, res.get_stream());
115+
// C++ exception with description "HIP error: invalid configuration argument hipLaunchKernel(func=0x273ff0, gridDim={1,2,3}, blockDim={3,2,1}, args=0x7fffffff76f8, sharedMem=0, stream=0x7e0860) /path/to/RAJA/install/RAJA/include/RAJA/policy/hip/MemUtils_HIP.hpp:273" thrown in the test body.
116+
117+
In this example, RAJA uses a CAMP error checking and reporting macro when
118+
launching a hip kernel. If the kernel launch fails it will generate a string
119+
containing the error message, the function called, the function arguments, and
120+
the location of the call.
121+
109122
RAJA has many examples of using Camp. In fact, so many internal RAJA implementations use Camp that RAJA has a
110123
`Camp alias page <https://github.com/LLNL/RAJA/blob/0aef7cc44348d82e94e73e12f77c27ea306e47b8/include/RAJA/util/camp_aliases.hpp>`_ which
111124
creates RAJA aliases for many Camp features.

include/camp/defines.hpp

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#include <cstddef>
1212
#include <cstdint>
13+
#include <string>
1314

1415
#include <camp/config.hpp>
1516

@@ -29,6 +30,9 @@ namespace camp
2930

3031
#define CAMP_ALLOW_UNUSED_LOCAL(X) (void)(X)
3132

33+
#define CAMP_STRINGIFY_HELPER(...) #__VA_ARGS__
34+
#define CAMP_STRINGIFY(...) CAMP_STRINGIFY_HELPER(__VA_ARGS__)
35+
3236
#if defined(__clang__)
3337
#define CAMP_COMPILER_CLANG
3438
#elif defined(__INTEL_COMPILER)
@@ -175,19 +179,58 @@ using nullptr_t = decltype(nullptr);
175179
using type = typename X<Lambda::template expr, Rest...>::type; \
176180
}
177181

182+
178183
/// Throw a runtime_error, avoid including exception everywhere
184+
[[noreturn]]
179185
CAMP_DLL_EXPORT void throw_re(const char *s);
186+
///
187+
[[noreturn]]
188+
CAMP_DLL_EXPORT void throw_re(std::string const& s);
180189

181190
#ifdef CAMP_ENABLE_CUDA
182191

183192
#define campCudaErrchk(ans) ::camp::cudaAssert((ans), #ans, __FILE__, __LINE__)
184193

185194
#define campCudaErrchkDiscardReturn(ans) (void)::camp::cudaAssert((ans), #ans, __FILE__, __LINE__)
186195

196+
[[deprecated]]
187197
CAMP_DLL_EXPORT cudaError_t cudaAssert(cudaError_t code,
188-
const char *call,
189-
const char *file,
190-
int line);
198+
const char *call,
199+
const char *file,
200+
int line);
201+
202+
/// Invoke the given CUDA API function and report CUDA error codes.
203+
///
204+
/// NOTE: This prints arguments to the given function so the arguments
205+
/// are evaluated twice on an error.
206+
#define CAMP_CUDA_API_INVOKE_AND_CHECK_IMPLEMENTATION(func, ...) \
207+
/* Avoid shadowing by adding 56792578 to variable names */ \
208+
cudaError_t code_56792578 = func(__VA_ARGS__); \
209+
if (code_56792578 != cudaSuccess && \
210+
code_56792578 != cudaErrorNotReady) /* [[unlikely]] */ \
211+
{ \
212+
static constexpr auto func_name_56792578 = CAMP_STRINGIFY(func); \
213+
static constexpr auto arg_names_56792578 = \
214+
::camp::experimental::cuda_get_api_arg_names(func_name_56792578); \
215+
::camp::reportError( \
216+
"CUDA", \
217+
cudaGetErrorString(code_56792578), \
218+
func_name_56792578, \
219+
arg_names_56792578, \
220+
std::forward_as_tuple(__VA_ARGS__), \
221+
__FILE__, __LINE__); \
222+
}
223+
///
224+
#define CAMP_CUDA_API_INVOKE_AND_CHECK_RETURN(...) \
225+
[&](){ \
226+
CAMP_CUDA_API_INVOKE_AND_CHECK_IMPLEMENTATION(__VA_ARGS__) \
227+
return code_56792578; \
228+
}()
229+
///
230+
#define CAMP_CUDA_API_INVOKE_AND_CHECK(...) \
231+
do { \
232+
CAMP_CUDA_API_INVOKE_AND_CHECK_IMPLEMENTATION(__VA_ARGS__) \
233+
} while (false)
191234

192235
#endif //#ifdef CAMP_ENABLE_CUDA
193236

@@ -198,10 +241,45 @@ CAMP_DLL_EXPORT cudaError_t cudaAssert(cudaError_t code,
198241

199242
#define campHipErrchkDiscardReturn(ans) (void)::camp::hipAssert((ans), #ans, __FILE__, __LINE__)
200243

244+
[[deprecated]]
201245
CAMP_DLL_EXPORT hipError_t hipAssert(hipError_t code,
202-
const char *call,
203-
const char *file,
204-
int line);
246+
const char *call,
247+
const char *file,
248+
int line);
249+
250+
251+
/// Invoke the given HIP API function and report HIP error codes.
252+
///
253+
/// NOTE: This prints arguments to the given function so the arguments
254+
/// are evaluated twice on an error.
255+
#define CAMP_HIP_API_INVOKE_AND_CHECK_IMPLEMENTATION(func, ...) \
256+
/* Avoid shadowing by adding 56792578 to variable names */ \
257+
hipError_t code_56792578 = func(__VA_ARGS__); \
258+
if (code_56792578 != hipSuccess && \
259+
code_56792578 != hipErrorNotReady) /* [[unlikely]] */ \
260+
{ \
261+
static constexpr auto func_name_56792578 = CAMP_STRINGIFY(func); \
262+
static constexpr auto arg_names_56792578 = \
263+
::camp::experimental::hip_get_api_arg_names(func_name_56792578); \
264+
::camp::reportError( \
265+
"HIP", \
266+
hipGetErrorString(code_56792578), \
267+
func_name_56792578, \
268+
arg_names_56792578, \
269+
std::forward_as_tuple(__VA_ARGS__), \
270+
__FILE__, __LINE__); \
271+
}
272+
///
273+
#define CAMP_HIP_API_INVOKE_AND_CHECK_RETURN(...) \
274+
[&](){ \
275+
CAMP_HIP_API_INVOKE_AND_CHECK_IMPLEMENTATION(__VA_ARGS__) \
276+
return code_56792578; \
277+
}()
278+
///
279+
#define CAMP_HIP_API_INVOKE_AND_CHECK(...) \
280+
do { \
281+
CAMP_HIP_API_INVOKE_AND_CHECK_IMPLEMENTATION(__VA_ARGS__) \
282+
} while (false)
205283

206284
#endif //#ifdef CAMP_ENABLE_HIP
207285

include/camp/helpers.hpp

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,20 @@
1010

1111
#include <cstddef>
1212
#include <utility>
13+
#include <cstddef>
14+
#include <cstdint>
15+
#include <iostream>
16+
#include <sstream>
17+
#include <string>
18+
#include <string_view>
19+
#include <array>
20+
#include <tuple>
21+
#include <utility>
22+
#include <algorithm>
23+
#include <stdexcept>
24+
#include <type_traits>
1325

26+
#include "camp/config.hpp"
1427
#include "camp/defines.hpp"
1528

1629
namespace camp
@@ -228,6 +241,211 @@ CAMP_HOST_DEVICE void safe_swap(T& t1, T& t2)
228241
using std::swap;
229242
swap(t1, t2);
230243
}
244+
245+
246+
namespace experimental
247+
{
248+
249+
//! Printing helper class to add printability to types defined outside of camp.
250+
//
251+
// Specialize to customize printing or add printing for a type. This avoids
252+
// conflicts if an operator<< is later added for the type in question.
253+
// Write specializations for non-const reference and const-reference.
254+
template < typename T >
255+
struct StreamInsertHelper
256+
{
257+
static_assert(std::is_lvalue_reference_v<T>);
258+
259+
T m_val;
260+
261+
std::ostream& operator()(std::ostream& str) const
262+
{
263+
return str << m_val;
264+
}
265+
};
266+
267+
// deduction guide for StreamInsertHelper
268+
template<typename T>
269+
StreamInsertHelper(T& val) -> StreamInsertHelper<T&>;
270+
template<typename T>
271+
StreamInsertHelper(T const& val) -> StreamInsertHelper<T const&>;
272+
template<typename T>
273+
StreamInsertHelper(T&& val) -> StreamInsertHelper<T const&>;
274+
template<typename T>
275+
StreamInsertHelper(T const&& val) -> StreamInsertHelper<T const&>;
276+
277+
// Allow printing of StreamInsertHelper using its call operator
278+
template < typename T >
279+
inline std::ostream& operator<<(std::ostream& str, StreamInsertHelper<T> const& si)
280+
{
281+
return si(str);
282+
}
283+
284+
285+
#ifdef CAMP_ENABLE_CUDA
286+
287+
//! Get the argument names for the given cuda API function name.
288+
//
289+
// Returns a space separated string of the arguments to the given function.
290+
// Returns an empty string if func is unknown.
291+
constexpr std::string_view cuda_get_api_arg_names(std::string_view func)
292+
{
293+
using storage_type = std::pair<const char*, const char*>;
294+
constexpr storage_type known_functions[] {
295+
{"cudaDeviceSynchronize", ""},
296+
{"cudaGetDevice", "device"},
297+
{"cudaGetDeviceProperties", "prop device"},
298+
{"cudaSetDevice", "device"},
299+
{"cudaStreamCreate", "pStream"},
300+
{"cudaStreamSynchronize", "stream"},
301+
{"cudaStreamWaitEvent", "stream event flags"},
302+
{"cudaEventCreateWithFlags", "event flags"},
303+
{"cudaEventSynchronize", "event"},
304+
{"cudaEventQuery", "event"},
305+
{"cudaEventRecord", "event stream"},
306+
{"cudaHostAlloc", "pHost size flags"},
307+
{"cudaMallocHost", "ptr size"},
308+
{"cudaMalloc", "devPtr size"},
309+
{"cudaMallocManaged", "devPtr size flags"},
310+
{"cudaHostFree", "ptr"},
311+
{"cudaFree", "devPtr"},
312+
{"cudaMemset", "devPtr value count"},
313+
{"cudaMemcpy", "dst src count kind"},
314+
{"cudaMemsetAsync", "devPtr value count stream"},
315+
{"cudaMemcpyAsync", "dst src count kind stream"},
316+
{"cudaLaunchKernel", "func gridDim blockDim args sharedMem stream"},
317+
{"cudaPeekAtLastError", ""},
318+
{"cudaGetLastError", ""},
319+
{"cudaFuncGetAttributes", "attr func"},
320+
{"cudaOccupancyMaxPotentialBlockSize", "minGridSize blockSize func dynamicSMemSize blockSizeLimit"},
321+
{"cudaOccupancyMaxActiveBlocksPerMultiprocessor", "numBlocks func blockSize dynamicSMemSize"}
322+
};
323+
for (auto [api_name, api_args] : known_functions) {
324+
if (func == api_name) {
325+
return api_args;
326+
}
327+
}
328+
return "";
329+
}
330+
331+
#endif //#ifdef CAMP_ENABLE_CUDA
332+
333+
#ifdef CAMP_ENABLE_HIP
334+
335+
//! Get the argument names for the given hip API function name.
336+
//
337+
// Returns a space separated string of the arguments to the given function.
338+
// Returns an empty string if func is unknown.
339+
constexpr std::string_view hip_get_api_arg_names(std::string_view func)
340+
{
341+
using storage_type = std::pair<const char*, const char*>;
342+
constexpr storage_type known_functions[] {
343+
{"hipDeviceSynchronize", ""},
344+
{"hipGetDevice", "device"},
345+
{"hipGetDeviceProperties", "prop device"},
346+
{"hipGetDevicePropertiesR0600", "prop device"},
347+
{"hipSetDevice", "device"},
348+
{"hipStreamCreate", "pStream"},
349+
{"hipStreamSynchronize", "stream"},
350+
{"hipStreamWaitEvent", "stream event flags"},
351+
{"hipEventCreateWithFlags", "event flags"},
352+
{"hipEventSynchronize", "event"},
353+
{"hipEventQuery", "event"},
354+
{"hipEventRecord", "event stream"},
355+
{"hipHostMalloc", "pHost size flags"},
356+
{"hipMalloc", "devPtr size"},
357+
{"hipMallocManaged", "devPtr size flags"},
358+
{"hipHostFree", "ptr"},
359+
{"hipFree", "devPtr"},
360+
{"hipMemset", "devPtr value count"},
361+
{"hipMemcpy", "dst src count kind"},
362+
{"hipMemsetAsync", "devPtr value count stream"},
363+
{"hipMemcpyAsync", "dst src count kind stream"},
364+
{"hipLaunchKernel", "func gridDim blockDim args sharedMem stream"},
365+
{"hipPeekAtLastError", ""},
366+
{"hipGetLastError", ""},
367+
{"hipFuncGetAttributes", "attr func"},
368+
{"hipOccupancyMaxPotentialBlockSize", "minGridSize blockSize func dynamicSMemSize blockSizeLimit"},
369+
{"hipOccupancyMaxActiveBlocksPerMultiprocessor", "numBlocks func blockSize dynamicSMemSize"}
370+
};
371+
for (auto [api_name, api_args] : known_functions) {
372+
if (func == api_name) {
373+
return api_args;
374+
}
375+
}
376+
return "";
377+
}
378+
379+
#endif //#ifdef CAMP_ENABLE_HIP
380+
381+
382+
//! Generate a string for the given args
383+
//
384+
// This function generates a string for the given argument names and arguments.
385+
// Uses StreamInsertHelper to stringify the types in args.
386+
template < typename Tuple, std::size_t ...Is >
387+
std::ostream& insertArgsString(std::ostream& str,
388+
std::string_view arg_names,
389+
Tuple&& args_tuple,
390+
std::index_sequence<Is...>)
391+
{
392+
auto insert_arg = [&, first=true](auto&& arg) mutable {
393+
if (first) {
394+
first = false;
395+
} else {
396+
str << ", ";
397+
}
398+
if (!arg_names.empty()) {
399+
auto arg_name_size = arg_names.find(' ');
400+
str << arg_names.substr(0, arg_name_size) << "=";
401+
if (arg_name_size < arg_names.size()) {
402+
++arg_name_size; // skip space
403+
}
404+
arg_names.remove_prefix(arg_name_size);
405+
}
406+
str << ::camp::experimental::StreamInsertHelper{arg};
407+
};
408+
::camp::sink(insert_arg); // suppress unused warning from nvcc
409+
using std::get;
410+
(..., insert_arg(get<Is>(std::forward<Tuple>(args_tuple))));
411+
return str;
412+
}
413+
414+
} // namespace experimental
415+
416+
417+
/// Report hip errors by throwing an exception or printing to cerr
418+
///
419+
/// This function generates an error message by getting a string for the given
420+
/// hip error code, function, argument names, arguments, and source location
421+
/// information. Uses StreamInsertHelper to stringify the types in args.
422+
///
423+
/// This function throws an exception if abort is true otherwise prints to cerr.
424+
template < typename Tuple >
425+
void reportError(std::string_view error_name,
426+
std::string_view error_description,
427+
std::string_view func_name,
428+
std::string_view arg_names,
429+
Tuple&& args_tuple,
430+
std::string_view file,
431+
int line,
432+
bool abort = true)
433+
{
434+
std::ostringstream str;
435+
str << error_name << " error: " << error_description;
436+
str << " " << func_name << "(";
437+
::camp::experimental::insertArgsString(
438+
str, arg_names,
439+
std::forward<Tuple>(args_tuple),
440+
std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{});
441+
str << ") " << file << ":" << line;
442+
if (abort) {
443+
::camp::throw_re(str.str());
444+
} else {
445+
std::cerr << str.str();
446+
}
447+
}
448+
231449
} // namespace camp
232450

233451
#endif /* CAMP_HELPERS_HPP */

include/camp/resource.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include <mutex>
1414
#include <type_traits>
1515

16+
#include "camp/config.hpp"
17+
#include "camp/defines.hpp"
1618
#include "camp/helpers.hpp"
1719
#include "camp/resource/event.hpp"
1820
#include "camp/resource/host.hpp"

0 commit comments

Comments
 (0)