Files
TheRock/rocm-systems/projects/hip-tests/catch/unit/graph/hipGraphAddNodeBeginCapture.cc
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

915 lines
36 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
*
* SPDX-License-Identifier: MIT
*/
#include <hip_test_common.hh>
#include <hip_test_checkers.hh>
#include <hip_test_kernels.hh>
#pragma clang diagnostic ignored "-Wunused-parameter"
constexpr unsigned int SIZE = 1024 * 1024;
constexpr size_t Nbytes = SIZE * sizeof(int);
constexpr int ThreadsPerBlock = 256;
constexpr int TotalBlocks = (SIZE % 256 == 0) ? (SIZE / 256) : ((SIZE / 256) + 1);
__device__ int globalOut[SIZE];
/**
* @addtogroup hipStreamBeginCapture hipStreamBeginCapture
* @{
* @ingroup GraphTest
* `hipStreamBeginCapture(hipStream_t stream, hipStreamCaptureMode mode)` -
* Returns the last error from a runtime call.
*/
static void verifyArrayMemset(int* A_h, int val) {
int expected_val = val | (val << 8) | (val << 16) | (val << 24);
for (size_t i = 0; i < SIZE; i++) {
if (A_h[i] != expected_val) {
INFO("Memset Validation failed at i " << i << " A_h[i] " << A_h[i]);
REQUIRE(false);
}
}
}
__device__ __host__ static void callbackFunc(void* A_h) {
int* A = reinterpret_cast<int*>(A_h);
for (int i = 0; i < SIZE; i++) {
A[i] = i + i % 2;
}
}
__global__ static void kCallbackFunc(void* A_h) { callbackFunc(A_h); }
static void verifyCallbackFunc(int* A_h) {
for (size_t i = 0; i < SIZE; i++) {
if (A_h[i] != static_cast<int>(i + i % 2)) {
INFO("CallBack Validation failed i " << i << " A_h[i] " << A_h[i]);
REQUIRE(false);
}
}
}
__global__ static void addGpuKernel(int* i_d) { *i_d = *i_d + 1; }
static void CpuCallback(void* args) {
// do nothing function
}
/**
* Test Description
* ------------------------
*  - Verify hipStreamBeginCapture, hipStreamEndCapture status with
* hipGraphAddHostNode api call.
* Test source
* ------------------------
*  - unit/graph/hipGraphAddNodeBeginCapture.cc
* Test requirements
* ------------------------
*  - HIP_VERSION >= 5.6
*/
HIP_TEST_CASE(Unit_hipStreamBeginCapture_with_hipGraphAddHostNode) {
hipGraph_t graph;
hipGraphExec_t graphExec;
hipGraphNode_t cpuGraphNode;
int* i_d;
HIP_CHECK(hipMalloc(&i_d, sizeof(int)));
REQUIRE(i_d != nullptr);
hipStream_t stream;
HIP_CHECK(hipStreamCreateWithFlags(&stream, hipStreamNonBlocking));
HIP_CHECK(hipStreamBeginCapture(stream, hipStreamCaptureModeThreadLocal));
HIP_CHECK(hipGraphCreate(&graph, 0));
hipHostNodeParams p = {0, 0};
p.fn = CpuCallback;
p.userData = nullptr;
HIP_CHECK(hipGraphAddHostNode(&cpuGraphNode, graph, nullptr, 0, &p));
HIP_CHECK(hipGraphDestroy(graph));
addGpuKernel<<<1, 1, 0, stream>>>(i_d);
HIP_CHECK(hipStreamEndCapture(stream, &graph));
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, NULL, NULL, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
HIP_CHECK(hipFree(i_d));
HIP_CHECK(hipGraphExecDestroy(graphExec));
HIP_CHECK(hipGraphDestroy(graph));
HIP_CHECK(hipStreamDestroy(stream));
}
/**
* Test Description
* ------------------------
*  - Capture graph sequence using hipStreamBeginCapture and try to add a new
* node to the capture stream using hipStreamUpdateCaptureDependencies api
* which will copy back the result from the existing graph and verify
* 1) Add a hipGraphAddMemcpyNode1D node before hipStreamEndCapture
* 2) Add a hipGraphAddMemsetNode node before hipStreamEndCapture
* 3) Add a hipGraphAddMemcpyNode node before hipStreamEndCapture
* 4) Add a hipGraphAddKernelNode node before hipStreamEndCapture
* 5) Add a hipGraphAddMemcpyNodeToSymbol and hipGraphAddMemcpyNodeFromSymbol
* node before hipStreamEndCapture
* 6) Add a hipGraphAddHostNode node before hipStreamEndCapture
* 7) Add a hipGraphAddChildGraphNode node before hipStreamEndCapture
* 8) Add a hipGraphAddEmptyNode node before hipStreamEndCapture
* 9) Add a hipGraphAddEventRecordNode node before hipStreamEndCapture
* 10) Add a hipGraphAddEventWaitNode node before hipStreamEndCapture
* Test source
* ------------------------
*  - unit/graph/hipGraphAddNodeBeginCapture.cc
* Test requirements
* ------------------------
*  - HIP_VERSION >= 5.6
*/
HIP_TEST_CASE(Unit_hipStreamEndCapture_later_and_add_a_node_inbetween) {
hipGraphExec_t graphExec;
hipGraphNode_t memcpyD2H_C;
hipStream_t stream;
int *A_d, *B_d, *C_d, *A_h, *B_h, *C_h;
HipTest::initArrays(&A_d, &B_d, &C_d, &A_h, &B_h, &C_h, SIZE, false);
HIP_CHECK(hipStreamCreate(&stream));
HIP_CHECK(hipStreamBeginCapture(stream, hipStreamCaptureModeGlobal));
HIP_CHECK(hipMemcpyAsync(A_d, A_h, Nbytes, hipMemcpyHostToDevice, stream));
HIP_CHECK(hipMemcpyAsync(B_d, B_h, Nbytes, hipMemcpyHostToDevice, stream));
HipTest::vectorADD<int><<<TotalBlocks, ThreadsPerBlock, 0, stream>>>(A_d, B_d, C_d, SIZE);
hipStreamCaptureStatus captureStatus{hipStreamCaptureStatusNone};
hipGraph_t capGraph{nullptr};
const hipGraphNode_t* nodelist{};
size_t numDependencies;
HIP_CHECK(hipStreamGetCaptureInfo_v2(stream, &captureStatus, nullptr, &capGraph, &nodelist,
&numDependencies));
REQUIRE(captureStatus == hipStreamCaptureStatusActive);
REQUIRE(capGraph != nullptr);
SECTION("Add a hipGraphAddMemcpyNode1D node before hipStreamEndCapture") {
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, capGraph, nodelist, numDependencies, C_h, C_d,
Nbytes, hipMemcpyDeviceToHost));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &memcpyD2H_C, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamEndCapture(stream, &capGraph));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, capGraph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
HipTest::checkVectorADD(A_h, B_h, C_h, SIZE);
}
SECTION("Add a hipGraphAddMemsetNode node before hipStreamEndCapture") {
hipGraphNode_t memsetNode;
int memSetVal = 7;
hipMemsetParams memsetParams{};
memset(&memsetParams, 0, sizeof(memsetParams));
memsetParams.dst = reinterpret_cast<void*>(C_d);
memsetParams.value = memSetVal;
memsetParams.pitch = 0;
memsetParams.elementSize = sizeof(char);
memsetParams.width = Nbytes;
memsetParams.height = 1;
HIP_CHECK(
hipGraphAddMemsetNode(&memsetNode, capGraph, nodelist, numDependencies, &memsetParams));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &memsetNode, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(hipMemcpyAsync(C_h, C_d, Nbytes, hipMemcpyDeviceToHost, stream));
HIP_CHECK(hipStreamEndCapture(stream, &capGraph));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, capGraph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
verifyArrayMemset(C_h, memSetVal);
}
SECTION("Add a hipGraphAddMemcpyNode node before hipStreamEndCapture") {
hipMemcpy3DParms myparams;
hipGraphNode_t memcpyNode;
memset(&myparams, 0x0, sizeof(hipMemcpy3DParms));
myparams.srcPos = make_hipPos(0, 0, 0);
myparams.dstPos = make_hipPos(0, 0, 0);
myparams.srcPtr = make_hipPitchedPtr(C_d, Nbytes, 1, 1);
myparams.dstPtr = make_hipPitchedPtr(C_h, Nbytes, 1, 1);
myparams.extent = make_hipExtent(Nbytes, 1, 1);
myparams.kind = hipMemcpyDeviceToHost;
HIP_CHECK(hipGraphAddMemcpyNode(&memcpyNode, capGraph, nodelist, numDependencies, &myparams));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &memcpyNode, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamEndCapture(stream, &capGraph));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, capGraph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
HipTest::checkVectorADD(A_h, B_h, C_h, SIZE);
}
SECTION("Add a hipGraphAddKernelNode node before hipStreamEndCapture") {
hipGraphNode_t kNode;
hipKernelNodeParams kNodeParams{};
memset(&kNodeParams, 0x00, sizeof(kNodeParams));
void* kernelArgs[] = {&C_d};
kNodeParams.func = reinterpret_cast<void*>(kCallbackFunc);
kNodeParams.gridDim = dim3(1);
kNodeParams.blockDim = dim3(256);
kNodeParams.sharedMemBytes = 0;
kNodeParams.kernelParams = reinterpret_cast<void**>(kernelArgs);
kNodeParams.extra = nullptr;
HIP_CHECK(hipGraphAddKernelNode(&kNode, capGraph, nodelist, numDependencies, &kNodeParams));
HIP_CHECK(
hipStreamUpdateCaptureDependencies(stream, &kNode, 1, hipStreamSetCaptureDependencies));
HIP_CHECK(hipMemcpyAsync(C_h, C_d, Nbytes, hipMemcpyDeviceToHost, stream));
HIP_CHECK(hipStreamEndCapture(stream, &capGraph));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, capGraph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
verifyCallbackFunc(C_h);
}
SECTION("Add hipGraphAddMemcpyNodeToSymbol node before hipStreamEndCapture") {
hipGraphNode_t memcpyToSymNode, memcpyFromSymNode;
HIP_CHECK(hipGraphAddMemcpyNodeToSymbol(&memcpyToSymNode, capGraph, nodelist, numDependencies,
HIP_SYMBOL(globalOut), C_d, Nbytes, 0,
hipMemcpyDeviceToDevice));
HIP_CHECK(hipGraphAddMemcpyNodeFromSymbol(&memcpyFromSymNode, capGraph, nullptr, 0, C_h,
HIP_SYMBOL(globalOut), Nbytes, 0,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddDependencies(capGraph, &memcpyToSymNode, &memcpyFromSymNode, 1));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &memcpyToSymNode, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &memcpyFromSymNode, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamEndCapture(stream, &capGraph));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, capGraph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
HipTest::checkVectorADD(A_h, B_h, C_h, SIZE);
}
SECTION("Add a hipGraphAddHostNode node before hipStreamEndCapture") {
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, capGraph, nodelist, numDependencies, C_h, C_d,
Nbytes, hipMemcpyDeviceToHost));
hipGraphNode_t hostNode;
hipHostNodeParams hostParams = {0, 0};
hostParams.fn = callbackFunc;
hostParams.userData = C_h;
HIP_CHECK(hipGraphAddHostNode(&hostNode, capGraph, nullptr, 0, &hostParams));
HIP_CHECK(hipGraphAddDependencies(capGraph, &memcpyD2H_C, &hostNode, 1));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &memcpyD2H_C, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(
hipStreamUpdateCaptureDependencies(stream, &hostNode, 1, hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamEndCapture(stream, &capGraph));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, capGraph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
verifyCallbackFunc(C_h);
}
SECTION("Add a hipGraphAddChildGraphNode node before hipStreamEndCapture") {
hipGraph_t childGraph;
hipGraphNode_t memsetNode, childGraphNode;
int memSetVal = 7;
hipMemsetParams memsetParams{};
memset(&memsetParams, 0, sizeof(memsetParams));
memsetParams.dst = reinterpret_cast<void*>(C_d);
memsetParams.value = memSetVal;
memsetParams.pitch = 0;
memsetParams.elementSize = sizeof(char);
memsetParams.width = Nbytes;
memsetParams.height = 1;
HIP_CHECK(hipGraphCreate(&childGraph, 0));
HIP_CHECK(hipGraphAddMemsetNode(&memsetNode, childGraph, nullptr, 0, &memsetParams));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, childGraph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddDependencies(childGraph, &memsetNode, &memcpyD2H_C, 1));
HIP_CHECK(hipGraphAddChildGraphNode(&childGraphNode, capGraph, nodelist, numDependencies,
childGraph));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &childGraphNode, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamEndCapture(stream, &capGraph));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, capGraph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
verifyArrayMemset(C_h, memSetVal);
HIP_CHECK(hipGraphDestroy(childGraph));
}
SECTION("Add a hipGraphAddEmptyNode node before hipStreamEndCapture") {
hipGraphNode_t emptyNode;
HIP_CHECK(hipGraphAddEmptyNode(&emptyNode, capGraph, nodelist, numDependencies));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, capGraph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddDependencies(capGraph, &emptyNode, &memcpyD2H_C, 1));
HIP_CHECK(
hipStreamUpdateCaptureDependencies(stream, &emptyNode, 1, hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &memcpyD2H_C, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamEndCapture(stream, &capGraph));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, capGraph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
HipTest::checkVectorADD(A_h, B_h, C_h, SIZE);
}
SECTION("Add hipGraphAddEventRecordNode node before hipStreamEndCapture") {
hipGraphNode_t event_start, event_end;
hipEvent_t eventstart, eventend;
HIP_CHECK(hipEventCreate(&eventstart));
HIP_CHECK(hipEventCreate(&eventend));
HIP_CHECK(
hipGraphAddEventRecordNode(&event_start, capGraph, nodelist, numDependencies, eventstart));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, capGraph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddEventRecordNode(&event_end, capGraph, nullptr, 0, eventend));
HIP_CHECK(hipGraphAddDependencies(capGraph, &event_start, &memcpyD2H_C, 1));
HIP_CHECK(hipGraphAddDependencies(capGraph, &memcpyD2H_C, &event_end, 1));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &event_start, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &memcpyD2H_C, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(
hipStreamUpdateCaptureDependencies(stream, &event_end, 1, hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamEndCapture(stream, &capGraph));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, capGraph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
HIP_CHECK(hipEventSynchronize(eventend));
// Verify execution result
HipTest::checkVectorADD(A_h, B_h, C_h, SIZE);
float t = 0.0f;
HIP_CHECK(hipEventElapsedTime(&t, eventstart, eventend));
REQUIRE(t > 0.0f);
HIP_CHECK(hipEventDestroy(eventstart));
HIP_CHECK(hipEventDestroy(eventend));
}
SECTION("Add hipGraphAddEventWaitNode node before hipStreamEndCapture") {
hipGraphNode_t eventRecNode, eventWaitNode;
hipEvent_t event;
HIP_CHECK(hipEventCreate(&event));
HIP_CHECK(
hipGraphAddEventRecordNode(&eventRecNode, capGraph, nodelist, numDependencies, event));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, capGraph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddEventWaitNode(&eventWaitNode, capGraph, nullptr, 0, event));
HIP_CHECK(hipGraphAddDependencies(capGraph, &eventRecNode, &memcpyD2H_C, 1));
HIP_CHECK(hipGraphAddDependencies(capGraph, &memcpyD2H_C, &eventWaitNode, 1));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &eventRecNode, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &memcpyD2H_C, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamUpdateCaptureDependencies(stream, &eventWaitNode, 1,
hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamEndCapture(stream, &capGraph));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, capGraph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
HIP_CHECK(hipEventSynchronize(event));
// Verify execution result
HipTest::checkVectorADD(A_h, B_h, C_h, SIZE);
HIP_CHECK(hipEventDestroy(event));
}
HipTest::freeArrays(A_d, B_d, C_d, A_h, B_h, C_h, false);
HIP_CHECK(hipGraphExecDestroy(graphExec));
HIP_CHECK(hipGraphDestroy(capGraph));
HIP_CHECK(hipStreamDestroy(stream));
}
/**
* Test Description
* ------------------------
*  - Capture graph sequence using hipStreamBeginCapture and hipStreamEndCapture
* Try to add a new node and link this new node to the existing graph
* which will copy back the result from the existing graph and verify
* 1) Add a hipGraphAddMemcpyNode1D node after hipStreamEndCapture
* 2) Add a hipGraphAddMemsetNode node after hipStreamEndCapture
* 3) Add a hipGraphAddMemcpyNode node after hipStreamEndCapture
* 4) Add a hipGraphAddKernelNode node after hipStreamEndCapture
* 5) Add a hipGraphAddMemcpyNodeToSymbol and hipGraphAddMemcpyNodeFromSymbol
* node after hipStreamEndCapture
* 6) Add a hipGraphAddHostNode node after hipStreamEndCapture
* 7) Add a hipGraphAddChildGraphNode node after hipStreamEndCapture
* 8) Add a hipGraphAddEmptyNode node after hipStreamEndCapture
* 9) Add a hipGraphAddEventRecordNode node after hipStreamEndCapture
* 10) Add a hipGraphAddEventWaitNode node after hipStreamEndCapture
* Test source
* ------------------------
*  - unit/graph/hipGraphAddNodeBeginCapture.cc
* Test requirements
* ------------------------
*  - HIP_VERSION >= 5.6
*/
HIP_TEST_CASE(Unit_hipStreamEndCapture_first_and_add_a_node_later) {
hipGraph_t graph;
hipGraphExec_t graphExec;
hipGraphNode_t memcpyD2H_C;
hipStream_t stream;
int *A_d, *B_d, *C_d, *A_h, *B_h, *C_h;
HipTest::initArrays(&A_d, &B_d, &C_d, &A_h, &B_h, &C_h, SIZE, false);
HIP_CHECK(hipStreamCreate(&stream));
HIP_CHECK(hipStreamBeginCapture(stream, hipStreamCaptureModeGlobal));
HIP_CHECK(hipMemcpyAsync(A_d, A_h, Nbytes, hipMemcpyHostToDevice, stream));
HIP_CHECK(hipMemcpyAsync(B_d, B_h, Nbytes, hipMemcpyHostToDevice, stream));
HipTest::vectorSUB<int><<<TotalBlocks, ThreadsPerBlock, 0, stream>>>(A_d, B_d, C_d, SIZE);
HIP_CHECK(hipStreamEndCapture(stream, &graph));
size_t numN{};
int foundAt = -1;
HIP_CHECK(hipGraphGetNodes(graph, nullptr, &numN));
hipGraphNode_t* nodes = reinterpret_cast<hipGraphNode_t*>(malloc(numN * sizeof(hipGraphNode_t)));
REQUIRE(nodes != nullptr);
HIP_CHECK(hipGraphGetNodes(graph, nodes, &numN));
hipGraphNodeType nodeType;
for (int i = 0; i < numN; i++) {
HIP_CHECK(hipGraphNodeGetType(nodes[i], &nodeType));
if (nodeType == hipGraphNodeTypeKernel) {
foundAt = i;
break;
}
}
SECTION("Add a hipGraphAddMemcpyNode1D node after hipStreamEndCapture") {
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, graph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddDependencies(graph, &nodes[foundAt], &memcpyD2H_C, 1));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
HipTest::checkVectorSUB(A_h, B_h, C_h, SIZE);
}
SECTION("Add a hipGraphAddMemsetNode node after hipStreamEndCapture") {
hipGraphNode_t memsetNode;
int memSetVal = 7;
hipMemsetParams memsetParams{};
memset(&memsetParams, 0, sizeof(memsetParams));
memsetParams.dst = reinterpret_cast<void*>(C_d);
memsetParams.value = memSetVal;
memsetParams.pitch = 0;
memsetParams.elementSize = sizeof(char);
memsetParams.width = Nbytes;
memsetParams.height = 1;
HIP_CHECK(hipGraphAddMemsetNode(&memsetNode, graph, nullptr, 0, &memsetParams));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, graph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddDependencies(graph, &nodes[foundAt], &memsetNode, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &memsetNode, &memcpyD2H_C, 1));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
verifyArrayMemset(C_h, memSetVal);
}
SECTION("Add a hipGraphAddMemcpyNode node after hipStreamEndCapture") {
hipMemcpy3DParms myparams;
hipGraphNode_t memcpyNode;
memset(&myparams, 0x0, sizeof(hipMemcpy3DParms));
myparams.srcPos = make_hipPos(0, 0, 0);
myparams.dstPos = make_hipPos(0, 0, 0);
myparams.srcPtr = make_hipPitchedPtr(C_d, Nbytes, 1, 1);
myparams.dstPtr = make_hipPitchedPtr(C_h, Nbytes, 1, 1);
myparams.extent = make_hipExtent(Nbytes, 1, 1);
myparams.kind = hipMemcpyDeviceToHost;
HIP_CHECK(hipGraphAddMemcpyNode(&memcpyNode, graph, nullptr, 0, &myparams));
HIP_CHECK(hipGraphAddDependencies(graph, &nodes[foundAt], &memcpyNode, 1));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
HipTest::checkVectorSUB(A_h, B_h, C_h, SIZE);
}
SECTION("Add a hipGraphAddKernelNode node after hipStreamEndCapture") {
hipGraphNode_t kNode;
hipKernelNodeParams kNodeParams{};
memset(&kNodeParams, 0x00, sizeof(kNodeParams));
void* kernelArgs[] = {&C_d};
kNodeParams.func = reinterpret_cast<void*>(kCallbackFunc);
kNodeParams.gridDim = dim3(1);
kNodeParams.blockDim = dim3(256);
kNodeParams.sharedMemBytes = 0;
kNodeParams.kernelParams = reinterpret_cast<void**>(kernelArgs);
kNodeParams.extra = nullptr;
HIP_CHECK(hipGraphAddKernelNode(&kNode, graph, nullptr, 0, &kNodeParams));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, graph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddDependencies(graph, &nodes[foundAt], &kNode, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &kNode, &memcpyD2H_C, 1));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
verifyCallbackFunc(C_h);
}
SECTION("Add hipGraphAddMemcpyNodeToSymbol node after hipStreamEndCapture") {
hipGraphNode_t memcpyToSymNode, memcpyFromSymNode;
HIP_CHECK(hipGraphAddMemcpyNodeToSymbol(&memcpyToSymNode, graph, nullptr, 0,
HIP_SYMBOL(globalOut), C_d, Nbytes, 0,
hipMemcpyDeviceToDevice));
HIP_CHECK(hipGraphAddMemcpyNodeFromSymbol(&memcpyFromSymNode, graph, nullptr, 0, C_h,
HIP_SYMBOL(globalOut), Nbytes, 0,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddDependencies(graph, &nodes[foundAt], &memcpyToSymNode, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &memcpyToSymNode, &memcpyFromSymNode, 1));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
HipTest::checkVectorSUB(A_h, B_h, C_h, SIZE);
}
SECTION("Add hipGraphAddHostNode node after hipStreamEndCapture") {
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, graph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
hipGraphNode_t hostNode;
hipHostNodeParams hostParams = {0, 0};
hostParams.fn = callbackFunc;
hostParams.userData = C_h;
HIP_CHECK(hipGraphAddHostNode(&hostNode, graph, nullptr, 0, &hostParams));
HIP_CHECK(hipGraphAddDependencies(graph, &nodes[foundAt], &memcpyD2H_C, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &memcpyD2H_C, &hostNode, 1));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
verifyCallbackFunc(C_h);
}
SECTION("Add hipGraphAddChildGraphNode node after hipStreamEndCapture") {
hipGraph_t childGraph;
hipGraphNode_t memsetNode, childGraphNode;
int memSetVal = 7;
hipMemsetParams memsetParams{};
memset(&memsetParams, 0, sizeof(memsetParams));
memsetParams.dst = reinterpret_cast<void*>(C_d);
memsetParams.value = memSetVal;
memsetParams.pitch = 0;
memsetParams.elementSize = sizeof(char);
memsetParams.width = Nbytes;
memsetParams.height = 1;
HIP_CHECK(hipGraphCreate(&childGraph, 0));
HIP_CHECK(hipGraphAddMemsetNode(&memsetNode, childGraph, nullptr, 0, &memsetParams));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, childGraph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddDependencies(childGraph, &memsetNode, &memcpyD2H_C, 1));
HIP_CHECK(hipGraphAddChildGraphNode(&childGraphNode, graph, nullptr, 0, childGraph));
HIP_CHECK(hipGraphAddDependencies(graph, &nodes[foundAt], &childGraphNode, 1));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
verifyArrayMemset(C_h, memSetVal);
HIP_CHECK(hipGraphDestroy(childGraph));
}
SECTION("Add hipGraphAddEmptyNode node after hipStreamEndCapture") {
hipGraphNode_t emptyNode;
HIP_CHECK(hipGraphAddEmptyNode(&emptyNode, graph, nullptr, 0));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, graph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddDependencies(graph, &nodes[foundAt], &emptyNode, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &emptyNode, &memcpyD2H_C, 1));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
HipTest::checkVectorSUB(A_h, B_h, C_h, SIZE);
}
SECTION("Add hipGraphAddEventRecordNode node after hipStreamEndCapture") {
hipGraphNode_t event_start, event_end;
hipEvent_t eventstart, eventend;
HIP_CHECK(hipEventCreate(&eventstart));
HIP_CHECK(hipEventCreate(&eventend));
HIP_CHECK(hipGraphAddEventRecordNode(&event_start, graph, nullptr, 0, eventstart));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, graph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddEventRecordNode(&event_end, graph, nullptr, 0, eventend));
HIP_CHECK(hipGraphAddDependencies(graph, &nodes[foundAt], &event_start, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &event_start, &memcpyD2H_C, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &memcpyD2H_C, &event_end, 1));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
HIP_CHECK(hipEventSynchronize(eventend));
// Verify execution result
HipTest::checkVectorSUB(A_h, B_h, C_h, SIZE);
float t = 0.0f;
HIP_CHECK(hipEventElapsedTime(&t, eventstart, eventend));
REQUIRE(t > 0.0f);
HIP_CHECK(hipEventDestroy(eventstart));
HIP_CHECK(hipEventDestroy(eventend));
}
SECTION("Add hipGraphAddEventWaitNode node after hipStreamEndCapture") {
hipGraphNode_t eventRecNode, eventWaitNode;
hipEvent_t event;
HIP_CHECK(hipEventCreate(&event));
HIP_CHECK(hipGraphAddEventRecordNode(&eventRecNode, graph, nullptr, 0, event));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, graph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
HIP_CHECK(hipGraphAddEventWaitNode(&eventWaitNode, graph, nullptr, 0, event));
HIP_CHECK(hipGraphAddDependencies(graph, &nodes[foundAt], &eventRecNode, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &eventRecNode, &memcpyD2H_C, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &memcpyD2H_C, &eventWaitNode, 1));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
HIP_CHECK(hipEventSynchronize(event));
// Verify execution result
HipTest::checkVectorSUB(A_h, B_h, C_h, SIZE);
HIP_CHECK(hipEventDestroy(event));
}
free(nodes);
HipTest::freeArrays(A_d, B_d, C_d, A_h, B_h, C_h, false);
HIP_CHECK(hipGraphExecDestroy(graphExec));
HIP_CHECK(hipGraphDestroy(graph));
HIP_CHECK(hipStreamDestroy(stream));
}
/**
* Test Description
* ------------------------
*  - Capture graph sequence using hipStreamBeginCapture and hipStreamEndCapture
* Add some new node to the same graph and execute it and verify
* Test source
* ------------------------
*  - unit/graph/hipGraphAddNodeBeginCapture.cc
* Test requirements
* ------------------------
*  - HIP_VERSION >= 5.6
*/
HIP_TEST_CASE(Unit_hipStreamEndCapture_first_and_add_other_graph_node_later) {
hipGraph_t graph;
hipGraphExec_t graphExec;
hipGraphNode_t memcpyH2D_A, memcpyH2D_B, memcpyD2H_AC, memcpyH2D_C;
hipStream_t stream;
int *A_d, *B_d, *C_d, *A_h, *B_h, *C_h;
HipTest::initArrays(&A_d, &B_d, &C_d, &A_h, &B_h, &C_h, SIZE, false);
int *A_d1, *B_d1, *C_d1, *A_h1, *B_h1, *C_h1;
HipTest::initArrays(&A_d1, &B_d1, &C_d1, &A_h1, &B_h1, &C_h1, SIZE, false);
HIP_CHECK(hipStreamCreate(&stream));
HIP_CHECK(hipStreamBeginCapture(stream, hipStreamCaptureModeGlobal));
HIP_CHECK(hipMemcpyAsync(A_d1, A_h1, Nbytes, hipMemcpyHostToDevice, stream));
HIP_CHECK(hipMemcpyAsync(B_d1, B_h1, Nbytes, hipMemcpyHostToDevice, stream));
HipTest::vectorADD<int><<<TotalBlocks, ThreadsPerBlock, 0, stream>>>(A_d1, B_d1, C_d1, SIZE);
HIP_CHECK(hipMemcpyAsync(C_h1, C_d1, Nbytes, hipMemcpyDeviceToHost, stream));
HIP_CHECK(hipStreamEndCapture(stream, &graph));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyH2D_A, graph, nullptr, 0, A_d, A_h, Nbytes,
hipMemcpyHostToDevice));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyH2D_B, graph, nullptr, 0, B_d, B_h, Nbytes,
hipMemcpyHostToDevice));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyH2D_C, graph, nullptr, 0, C_d, C_h, Nbytes,
hipMemcpyHostToDevice));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_AC, graph, nullptr, 0, A_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
hipGraphNode_t hostNode;
hipHostNodeParams hostParams = {0, 0};
hostParams.fn = callbackFunc;
hostParams.userData = A_h;
HIP_CHECK(hipGraphAddHostNode(&hostNode, graph, nullptr, 0, &hostParams));
HIP_CHECK(hipGraphAddDependencies(graph, &memcpyH2D_A, &memcpyH2D_B, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &memcpyH2D_B, &memcpyD2H_AC, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &memcpyH2D_C, &memcpyD2H_AC, 1));
HIP_CHECK(hipGraphAddDependencies(graph, &memcpyD2H_AC, &hostNode, 1));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, graph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result fir above two graph operations
verifyCallbackFunc(A_h);
HipTest::checkVectorADD(A_h1, B_h1, C_h1, SIZE);
HipTest::freeArrays(A_d, B_d, C_d, A_h, B_h, C_h, false);
HipTest::freeArrays(A_d1, B_d1, C_d1, A_h1, B_h1, C_h1, false);
HIP_CHECK(hipGraphExecDestroy(graphExec));
HIP_CHECK(hipGraphDestroy(graph));
HIP_CHECK(hipStreamDestroy(stream));
}
/**
* Test Description
* ------------------------
*  - Capture graph sequence using hipStreamBeginCapture and
* add some new node before hipStreamEndCapture to the same graph
* and hipGraphAddEmptyNode to use as last node to grah to complete.
* Test source
* ------------------------
*  - unit/graph/hipGraphAddNodeBeginCapture.cc
* Test requirements
* ------------------------
*  - HIP_VERSION >= 5.6
*/
HIP_TEST_CASE(Unit_hipStreamEndCapture_later_and_addEmptyNode) {
hipGraphExec_t graphExec;
hipGraphNode_t memcpyD2H_C;
hipStream_t stream;
int *A_d, *B_d, *C_d, *A_h, *B_h, *C_h;
HipTest::initArrays(&A_d, &B_d, &C_d, &A_h, &B_h, &C_h, SIZE, false);
HIP_CHECK(hipStreamCreate(&stream));
HIP_CHECK(hipStreamBeginCapture(stream, hipStreamCaptureModeGlobal));
HIP_CHECK(hipMemcpyAsync(A_d, A_h, Nbytes, hipMemcpyHostToDevice, stream));
HIP_CHECK(hipMemcpyAsync(B_d, B_h, Nbytes, hipMemcpyHostToDevice, stream));
HipTest::vectorSUB<int><<<TotalBlocks, ThreadsPerBlock, 0, stream>>>(A_d, B_d, C_d, SIZE);
hipStreamCaptureStatus captureStatus{hipStreamCaptureStatusNone};
hipGraph_t capGraph{nullptr};
const hipGraphNode_t* nodelist{};
size_t numDependencies;
HIP_CHECK(hipStreamGetCaptureInfo_v2(stream, &captureStatus, nullptr, &capGraph, &nodelist,
&numDependencies));
REQUIRE(captureStatus == hipStreamCaptureStatusActive);
REQUIRE(capGraph != nullptr);
hipGraphNode_t memsetNode;
int memSetVal = 7;
hipMemsetParams memsetParams{};
memset(&memsetParams, 0, sizeof(memsetParams));
memsetParams.dst = reinterpret_cast<void*>(C_d);
memsetParams.value = memSetVal;
memsetParams.pitch = 0;
memsetParams.elementSize = sizeof(char);
memsetParams.width = Nbytes;
memsetParams.height = 1;
HIP_CHECK(hipGraphAddMemsetNode(&memsetNode, capGraph, nodelist, numDependencies, &memsetParams));
HIP_CHECK(hipGraphAddMemcpyNode1D(&memcpyD2H_C, capGraph, nullptr, 0, C_h, C_d, Nbytes,
hipMemcpyDeviceToHost));
hipGraphNode_t emptyNode;
HIP_CHECK(hipGraphAddEmptyNode(&emptyNode, capGraph, nullptr, 0));
HIP_CHECK(hipGraphAddDependencies(capGraph, &memsetNode, &memcpyD2H_C, 1));
HIP_CHECK(hipGraphAddDependencies(capGraph, &memcpyD2H_C, &emptyNode, 1));
HIP_CHECK(
hipStreamUpdateCaptureDependencies(stream, &memsetNode, 1, hipStreamSetCaptureDependencies));
HIP_CHECK(
hipStreamUpdateCaptureDependencies(stream, &memcpyD2H_C, 1, hipStreamSetCaptureDependencies));
HIP_CHECK(
hipStreamUpdateCaptureDependencies(stream, &emptyNode, 1, hipStreamSetCaptureDependencies));
HIP_CHECK(hipStreamEndCapture(stream, &capGraph));
// Instantiate and launch the graph
HIP_CHECK(hipGraphInstantiate(&graphExec, capGraph, nullptr, nullptr, 0));
HIP_CHECK(hipGraphLaunch(graphExec, stream));
HIP_CHECK(hipStreamSynchronize(stream));
// Verify execution result
verifyArrayMemset(C_h, memSetVal);
HipTest::freeArrays(A_d, B_d, C_d, A_h, B_h, C_h, false);
HIP_CHECK(hipGraphExecDestroy(graphExec));
HIP_CHECK(hipGraphDestroy(capGraph));
HIP_CHECK(hipStreamDestroy(stream));
}
/**
* End doxygen group GraphTest.
* @}
*/