Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-18 09:09:43

0001 //------------------------------- -*- C++ -*- -------------------------------//
0002 // Copyright Celeritas contributors: see top-level COPYRIGHT file for details
0003 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
0004 //---------------------------------------------------------------------------//
0005 /*!
0006  * \file Assert.hh
0007  * \brief Macros, exceptions, and helpers for assertions and error handling.
0008  *
0009  * This defines host- and device-compatible assertion macros that are toggled
0010  * on the \c CELERITAS_DEBUG and \c CELERITAS_DEVICE_DEBUG configure macros.
0011  */
0012 //---------------------------------------------------------------------------//
0013 #pragma once
0014 
0015 #include <stdexcept>
0016 #include <string>
0017 #if defined(__HIP__)
0018 #    include <hip/hip_runtime.h>
0019 #elif defined(__CUDA_ARCH__)
0020 // No assert header needed for CUDA
0021 #else
0022 #    include <ostream>  // IWYU pragma: export
0023 #    include <sstream>  // IWYU pragma: keep
0024 #endif
0025 
0026 #include "corecel/Config.hh"
0027 
0028 #include "Macros.hh"
0029 
0030 //---------------------------------------------------------------------------//
0031 // MACROS
0032 //---------------------------------------------------------------------------//
0033 /*!
0034  * \def CELER_EXPECT
0035  *
0036  * Precondition debug assertion macro. We "expect" that the input values
0037  * or initial state satisfy a precondition, and we throw exception in
0038  * debug mode if they do not.
0039  */
0040 /*!
0041  * \def CELER_ASSERT
0042  *
0043  * Internal debug assertion macro. This replaces standard \c assert usage.
0044  */
0045 /*!
0046  * \def CELER_ENSURE
0047  *
0048  * Postcondition debug assertion macro. Use to "ensure" that return values or
0049  * side effects are as expected when leaving a function.
0050  */
0051 /*!
0052  * \def CELER_ASSUME
0053  *
0054  * Always-on compiler assumption. This should be used very rarely: you should
0055  * make sure the resulting assembly is simplified in optimize mode from using
0056  * the assumption. For example, sometimes informing the compiler of an
0057  * assumption can reduce code bloat by skipping standard library exception
0058  * handling code (e.g. in \c std::visit by assuming
0059  * \code !var_obj.valueless_by_exception() \endcode ).
0060  */
0061 /*!
0062  * \def CELER_VALIDATE
0063  *
0064  * Always-on runtime assertion macro. This can check user input and input data
0065  * consistency, and will raise RuntimeError on failure with a descriptive error
0066  * message that is streamed as the second argument. This macro cannot be used
0067  * in \c __device__ -annotated code.
0068  *
0069  * The error message should read: \verbatim
0070    "<PROBLEM> (<WHY IT'S A PROBLEM>) <SUGGESTION>?"
0071   \endverbatim
0072  *
0073  * Examples with correct casing and punctuation:
0074  * - "failed to open '{filename}' (should contain relaxation data)"
0075  * - "unexpected end of file '{filename}' (data is inconsistent with
0076  * boundaries)"
0077  * - "MPI was not initialized (needed to construct a communicator). Maybe set
0078  * the environment variable CELER_DISABLE_PARALLEL=1 to disable externally?"
0079  * - "invalid min_range={opts.min_range} (must be positive)"
0080  *
0081  * This looks in practice like:
0082  * \code
0083    CELER_VALIDATE(file_stream,
0084                   << "failed to open '" << filename
0085                   << "' (should contain relaxation data)");
0086  * \endcode
0087  *
0088  * An always-on debug-type assertion without a detailed message can be
0089  * constructed by omitting the stream (but leaving the comma):
0090  * \code
0091     CELER_VALIDATE(file_stream,);
0092  * \endcode
0093  */
0094 /*!
0095  * \def CELER_DEBUG_FAIL
0096  *
0097  * Throw a debug assertion regardless of the \c CELERITAS_DEBUG setting. This
0098  * is used internally but is also useful for catching subtle programming errors
0099  * in downstream code.
0100  */
0101 /*!
0102  * \def CELER_ASSERT_UNREACHABLE
0103  *
0104  * Throw an assertion if the code point is reached. When debug assertions are
0105  * turned off, this changes to a compiler hint that improves optimization (and
0106  * may force the code to exit uncermoniously if the point is encountered,
0107  * rather than continuing on with undefined behavior).
0108  */
0109 /*!
0110  * \def CELER_NOT_CONFIGURED
0111  *
0112  * Assert if the code point is reached because an optional feature is disabled.
0113  * This generally should be used for the constructors of dummy class
0114  * definitions in, e.g., \c Foo.nocuda.cc:
0115  * \code
0116     Foo::Foo()
0117     {
0118         CELER_NOT_CONFIGURED("CUDA");
0119     }
0120  * \endcode
0121  */
0122 /*!
0123  * \def CELER_NOT_IMPLEMENTED
0124  *
0125  * Assert if the code point is reached because a feature has yet to be fully
0126  * implemented.
0127  *
0128  * This placeholder is so that code paths can be "declared but not defined" and
0129  * implementations safely postponed in a greppable manner. This should \em not
0130  * be used to define "unused" overrides for virtual classes. A correct use case
0131  * would be:
0132  * \code
0133    if (z > AtomicNumber{26})
0134    {
0135        CELER_NOT_IMPLEMENTED("physics for heavy nuclides");
0136    }
0137  * \endcode
0138  */
0139 
0140 //! \cond
0141 
0142 #if !defined(__HIP__) && !defined(__CUDA_ARCH__)
0143 // Throw in host code
0144 #    define CELER_DEBUG_THROW_(MSG, WHICH) \
0145         throw ::celeritas::DebugError(     \
0146             {::celeritas::DebugErrorType::WHICH, MSG, __FILE__, __LINE__})
0147 #elif defined(__CUDA_ARCH__) && !defined(NDEBUG)
0148 // Use the assert macro for CUDA when supported
0149 #    define CELER_DEBUG_THROW_(MSG, WHICH) \
0150         assert(false && sizeof(#WHICH ": " MSG))
0151 #else
0152 // Use a special device function to emulate assertion failure if HIP
0153 // (assertion from multiple threads simultaeously can cause unexpected device
0154 // failures on AMD hardware) or if NDEBUG is in use with CUDA
0155 #    define CELER_DEBUG_THROW_(MSG, WHICH) \
0156         ::celeritas::device_debug_error(   \
0157             ::celeritas::DebugErrorType::WHICH, MSG, __FILE__, __LINE__)
0158 #endif
0159 
0160 #define CELER_DEBUG_ASSERT_(COND, WHICH)      \
0161     do                                        \
0162     {                                         \
0163         if (CELER_UNLIKELY(!(COND)))          \
0164         {                                     \
0165             CELER_DEBUG_THROW_(#COND, WHICH); \
0166         }                                     \
0167     } while (0)
0168 #define CELER_NDEBUG_ASSUME_(COND)      \
0169     do                                  \
0170     {                                   \
0171         if (CELER_UNLIKELY(!(COND)))    \
0172         {                               \
0173             ::celeritas::unreachable(); \
0174         }                               \
0175     } while (0)
0176 #define CELER_NOASSERT_(COND)   \
0177     do                          \
0178     {                           \
0179         if (false && (COND)) {} \
0180     } while (0)
0181 //! \endcond
0182 
0183 #define CELER_DEBUG_FAIL(MSG, WHICH)    \
0184     do                                  \
0185     {                                   \
0186         CELER_DEBUG_THROW_(MSG, WHICH); \
0187         ::celeritas::unreachable();     \
0188     } while (0)
0189 
0190 #if !CELER_DEVICE_COMPILE
0191 #    define CELER_RUNTIME_THROW(WHICH, WHAT, COND) \
0192         throw ::celeritas::RuntimeError({          \
0193             WHICH,                                 \
0194             WHAT,                                  \
0195             COND,                                  \
0196             __FILE__,                              \
0197             __LINE__,                              \
0198         })
0199 #elif CELERITAS_DEBUG
0200 #    define CELER_RUNTIME_THROW(WHICH, WHAT, COND)                           \
0201         CELER_DEBUG_FAIL("Runtime errors cannot be thrown from device code", \
0202                          unreachable);
0203 #else
0204 // Avoid printf statements which can add substantially to local memory
0205 #    define CELER_RUNTIME_THROW(WHICH, WHAT, COND) ::celeritas::unreachable()
0206 #endif
0207 
0208 #if (CELERITAS_DEBUG && !CELER_DEVICE_COMPILE) \
0209     || (CELERITAS_DEVICE_DEBUG && CELER_DEVICE_COMPILE)
0210 #    define CELER_EXPECT(COND) CELER_DEBUG_ASSERT_(COND, precondition)
0211 #    define CELER_ASSERT(COND) CELER_DEBUG_ASSERT_(COND, internal)
0212 #    define CELER_ENSURE(COND) CELER_DEBUG_ASSERT_(COND, postcondition)
0213 #    define CELER_ASSUME(COND) CELER_DEBUG_ASSERT_(COND, assumption)
0214 #    define CELER_ASSERT_UNREACHABLE() \
0215         CELER_DEBUG_FAIL("unreachable code point encountered", unreachable)
0216 #else
0217 #    define CELER_EXPECT(COND) CELER_NOASSERT_(COND)
0218 #    define CELER_ASSERT(COND) CELER_NOASSERT_(COND)
0219 #    define CELER_ENSURE(COND) CELER_NOASSERT_(COND)
0220 #    define CELER_ASSUME(COND) CELER_NDEBUG_ASSUME_(COND)
0221 #    define CELER_ASSERT_UNREACHABLE() ::celeritas::unreachable()
0222 #endif
0223 
0224 #if !CELER_DEVICE_COMPILE
0225 #    define CELER_VALIDATE(COND, MSG)                            \
0226         do                                                       \
0227         {                                                        \
0228             if (CELER_UNLIKELY(!(COND)))                         \
0229             {                                                    \
0230                 std::ostringstream celer_runtime_msg_;           \
0231                 celer_runtime_msg_ MSG;                          \
0232                 CELER_RUNTIME_THROW(                             \
0233                     ::celeritas::RuntimeError::validate_err_str, \
0234                     celer_runtime_msg_.str(),                    \
0235                     #COND);                                      \
0236             }                                                    \
0237         } while (0)
0238 #else
0239 #    define CELER_VALIDATE(COND, MSG) CELER_RUNTIME_THROW(nullptr, "", #COND)
0240 #endif
0241 
0242 #define CELER_NOT_CONFIGURED(WHAT) \
0243     CELER_RUNTIME_THROW(           \
0244         ::celeritas::RuntimeError::not_config_err_str, WHAT, {})
0245 #define CELER_NOT_IMPLEMENTED(WHAT) \
0246     CELER_RUNTIME_THROW(::celeritas::RuntimeError::not_impl_err_str, WHAT, {})
0247 
0248 /*!
0249  * \def CELER_DEVICE_API_CALL
0250  *
0251  * Safely and portably dispatch a CUDA/HIP API call.
0252  *
0253  * When CUDA or HIP support is enabled, execute the wrapped statement
0254  * prepend the argument with "cuda" or "hip" and throw a
0255  * RuntimeError if it fails. If no device platform is enabled, throw an
0256  * unconfigured assertion.
0257  *
0258  * Example:
0259  *
0260  * \code
0261    CELER_DEVICE_API_CALL(Malloc(&ptr_gpu, 100 * sizeof(float)));
0262    CELER_DEVICE_API_CALL(DeviceSynchronize());
0263  * \endcode
0264  *
0265  * \note A file that uses this macro must include \c
0266  * corecel/DeviceRuntimeApi.hh . The \c CorecelDeviceRuntimeApiHh
0267  * declaration enforces this when CUDA/HIP are disabled, and the absence of
0268  * \c CELER_DEVICE_API_SYMBOL enforces when enabled.
0269  */
0270 #if CELERITAS_USE_CUDA || CELERITAS_USE_HIP
0271 #    define CELER_DEVICE_API_CALL(STMT)                                      \
0272         do                                                                   \
0273         {                                                                    \
0274             using ErrT_ = CELER_DEVICE_API_SYMBOL(Error_t);                  \
0275             ErrT_ result_ = CELER_DEVICE_API_SYMBOL(STMT);                   \
0276             if (CELER_UNLIKELY(result_ != CELER_DEVICE_API_SYMBOL(Success))) \
0277             {                                                                \
0278                 result_ = CELER_DEVICE_API_SYMBOL(GetLastError)();           \
0279                 CELER_RUNTIME_THROW(                                         \
0280                     CELER_DEVICE_PLATFORM_UPPER_STR,                         \
0281                     CELER_DEVICE_API_SYMBOL(GetErrorString)(result_),        \
0282                     #STMT);                                                  \
0283             }                                                                \
0284         } while (0)
0285 #else
0286 #    define CELER_DEVICE_API_CALL(STMT)              \
0287         do                                           \
0288         {                                            \
0289             CELER_NOT_CONFIGURED("CUDA or HIP");     \
0290             CELER_DISCARD(CorecelDeviceRuntimeApiHh) \
0291         } while (0)
0292 #endif
0293 
0294 // DEPRECATED: remove in v1.0
0295 #define CELER_DEVICE_PREFIX(STMT) CELER_DEVICE_API_SYMBOL(STMT)
0296 #define CELER_DEVICE_CALL_PREFIX(STMT) CELER_DEVICE_API_CALL(STMT)
0297 #define CELER_DEVICE_CHECK_ERROR() CELER_DEVICE_API_CALL(PeekAtLastError())
0298 
0299 /*!
0300  * \def CELER_MPI_CALL
0301  *
0302  * When MPI support is enabled, execute the wrapped statement and throw a
0303  * RuntimeError if it fails. If MPI is disabled, throw an unconfigured
0304  * assertion.
0305  *
0306  * \note A file that uses this macro must include \c mpi.h.
0307  */
0308 #if CELERITAS_USE_MPI
0309 #    define CELER_MPI_CALL(STATEMENT)                                     \
0310         do                                                                \
0311         {                                                                 \
0312             int mpi_result_ = (STATEMENT);                                \
0313             if (CELER_UNLIKELY(mpi_result_ != MPI_SUCCESS))               \
0314             {                                                             \
0315                 CELER_RUNTIME_THROW(                                      \
0316                     "MPI", mpi_error_to_string(mpi_result_), #STATEMENT); \
0317             }                                                             \
0318         } while (0)
0319 #else
0320 #    define CELER_MPI_CALL(STATEMENT)    \
0321         do                               \
0322         {                                \
0323             CELER_NOT_CONFIGURED("MPI"); \
0324         } while (0)
0325 #endif
0326 
0327 //---------------------------------------------------------------------------//
0328 // ENUMERATIONS
0329 //---------------------------------------------------------------------------//
0330 
0331 namespace celeritas
0332 {
0333 //---------------------------------------------------------------------------//
0334 // ENUMERATIONS
0335 //---------------------------------------------------------------------------//
0336 enum class DebugErrorType
0337 {
0338     precondition,  //!< Precondition contract violation
0339     internal,  //!< Internal assertion check failure
0340     unreachable,  //!< Internal assertion: unreachable code path
0341     postcondition,  //!< Postcondition contract violation
0342     assumption,  //!< "Assume" violation
0343 };
0344 
0345 //! Detailed properties of a debug assertion failure
0346 struct DebugErrorDetails
0347 {
0348     DebugErrorType which;
0349     char const* condition;
0350     char const* file;
0351     int line;
0352 };
0353 
0354 //! Detailed properties of a runtime error
0355 struct RuntimeErrorDetails
0356 {
0357     char const* which{nullptr};  //!< Type of error (runtime, Geant4, MPI)
0358     std::string what{};  //!< Descriptive message
0359     std::string condition{};  //!< Code/test that failed
0360     std::string file{};  //!< Source file
0361     int line{0};  //!< Source line
0362 };
0363 
0364 //---------------------------------------------------------------------------//
0365 // FUNCTIONS
0366 //---------------------------------------------------------------------------//
0367 
0368 //! Invoke undefined behavior
0369 [[noreturn]] inline CELER_FUNCTION void unreachable()
0370 {
0371     CELER_UNREACHABLE;
0372 }
0373 
0374 // Get a pretty string version of a debug error
0375 char const* to_cstring(DebugErrorType which);
0376 
0377 // Get an MPI error string
0378 std::string mpi_error_to_string(int);
0379 
0380 //---------------------------------------------------------------------------//
0381 // TYPES
0382 //---------------------------------------------------------------------------//
0383 // Forward declaration of simple struct with pointer to an NLJSON object
0384 struct JsonPimpl;
0385 
0386 //---------------------------------------------------------------------------//
0387 /*!
0388  * Error thrown by Celeritas assertions.
0389  */
0390 class DebugError : public std::logic_error
0391 {
0392   public:
0393     // Construct from debug attributes
0394     explicit DebugError(DebugErrorDetails&&);
0395     CELER_DEFAULT_COPY_MOVE(DebugError);
0396 
0397     // Default destructor to anchor vtable
0398     ~DebugError() override;
0399 
0400     //! Access the debug data
0401     DebugErrorDetails const& details() const { return details_; }
0402 
0403   private:
0404     DebugErrorDetails details_;
0405 };
0406 
0407 //---------------------------------------------------------------------------//
0408 /*!
0409  * Error thrown by working code from unexpected runtime conditions.
0410  */
0411 class RuntimeError : public std::runtime_error
0412 {
0413   public:
0414     // Construct from details
0415     explicit RuntimeError(RuntimeErrorDetails&&);
0416     CELER_DEFAULT_COPY_MOVE(RuntimeError);
0417 
0418     // Default destructor to anchor vtable
0419     ~RuntimeError() override;
0420 
0421     //! Access detailed information
0422     RuntimeErrorDetails const& details() const { return details_; }
0423 
0424     //!@{
0425     //! String constants for "which" error message
0426     static char const validate_err_str[];
0427     static char const not_config_err_str[];
0428     static char const not_impl_err_str[];
0429     //!@}
0430 
0431   private:
0432     RuntimeErrorDetails details_;
0433 };
0434 
0435 //---------------------------------------------------------------------------//
0436 /*!
0437  * Base class for writing arbitrary exception context to JSON.
0438  *
0439  * This can be overridden in higher-level parts of the code for specific needs
0440  * (e.g., writing thread, event, and track contexts in Celeritas solver
0441  * kernels). Note that in order for derived classes to work with
0442  * `std::throw_with_nested`, they *MUST NOT* be `final`.
0443  */
0444 class RichContextException : public std::exception
0445 {
0446   public:
0447     //! Write output to the given JSON object
0448     virtual void output(JsonPimpl*) const = 0;
0449 
0450     //! Provide the name for this exception class
0451     virtual char const* type() const = 0;
0452 };
0453 
0454 //---------------------------------------------------------------------------//
0455 // INLINE FUNCTION DEFINITIONS
0456 //---------------------------------------------------------------------------//
0457 
0458 #if defined(__CUDA_ARCH__) && defined(NDEBUG)
0459 //! Host+device definition for CUDA when \c assert is unavailable
0460 inline __attribute__((noinline)) __host__ __device__ void device_debug_error(
0461     DebugErrorType, char const* condition, char const* file, int line)
0462 {
0463     printf("%s:%u:\nceleritas: internal assertion failed: %s\n",
0464            file,
0465            line,
0466            condition);
0467     __trap();
0468 }
0469 #elif defined(__HIP__)
0470 //! Host-only HIP call (whether or not NDEBUG is in use)
0471 inline __host__ void device_debug_error(DebugErrorType which,
0472                                         char const* condition,
0473                                         char const* file,
0474                                         int line)
0475 {
0476     throw DebugError({which, condition, file, line});
0477 }
0478 
0479 //! Device-only call for HIP (must always be declared; only used if
0480 //! NDEBUG)
0481 inline __attribute__((noinline)) __device__ void device_debug_error(
0482     DebugErrorType, char const* condition, char const* file, int line)
0483 {
0484     printf("%s:%u:\nceleritas: internal assertion failed: %s\n",
0485            file,
0486            line,
0487            condition);
0488     abort();
0489 }
0490 #endif
0491 
0492 //---------------------------------------------------------------------------//
0493 }  // namespace celeritas