2.20.3-1
Add support for alternating rings, allow for cross-nic rings without
cross-rail communication.
Add support for user buffer registration for network send/recv.
Optimize aggregated operations to better utilize all channels.
Add flattening for BCM PCI gen5 switches.
Add support for inter-node NVLink communication
Add support for port fusion in NET/IB.
Add support for ReduceScatter and AllGather using Collnet.
Update net API to v8.
Fix hang during A2A connection.
[ROCm/rccl commit: b6475625fb]
This commit is contained in:
1 bovenliggende
382ae08419
commit
2ab8a3a750
74 files changed
+4631
-2164
No files matched your search
@@ -353,20 +353,22 @@ static ncclResult_t ncclProxyOpToArgs(struct ncclProxyOp* op, struct ncclProxyAr
|
||||
WARN("Proxy append out of bounds");
|
||||
return ncclInternalError;
|
||||
}
|
||||
|
||||
//memset(sub, 0, sizeof(struct ncclProxySubArgs));
|
||||
sub->connection = op->connection;
|
||||
sub->channelId = op->channelId;
|
||||
sub->nsteps = op->nsteps;
|
||||
sub->nbytes = op->nbytes;
|
||||
sub->peer = op->root;
|
||||
sub->reg = op->reg;
|
||||
sub->buffer = op->buffer;
|
||||
args->nsubs = subIndex+1;
|
||||
if (subIndex) {
|
||||
if ((args->sliceSteps != op->sliceSteps) ||
|
||||
(args->chunkSteps != op->chunkSteps) ||
|
||||
(args->protocol != op->protocol) ||
|
||||
(args->dtype != op->dtype) ||
|
||||
(args->redOp != op->redOp)) {
|
||||
(args->redOp != op->redOp) ||
|
||||
(args->coll != op->coll)) {
|
||||
WARN("Proxy append mismatch");
|
||||
return ncclInternalError;
|
||||
}
|
||||
@@ -386,6 +388,8 @@ static ncclResult_t ncclProxyOpToArgs(struct ncclProxyOp* op, struct ncclProxyAr
|
||||
args->redOp = op->redOp;
|
||||
args->pattern = op->pattern;
|
||||
args->protocol = op->protocol;
|
||||
args->coll = op->coll;
|
||||
args->specifics = op->specifics;
|
||||
args->state = ncclProxyOpReady;
|
||||
args->progress = op->connection->tcomm->proxyProgress;
|
||||
args->proxyAppendPtr = op->connection->proxyAppendPtr;
|
||||
@@ -590,7 +594,7 @@ ncclResult_t ncclProxySaveOp(struct ncclComm* comm, struct ncclProxyOp* op, bool
|
||||
|
||||
NCCL_PARAM(ChunkSize, "CHUNK_SIZE", 0);
|
||||
|
||||
ncclResult_t ncclProxyComputeP2p(struct ncclInfo* info, struct ncclProxyOp* op) {
|
||||
ncclResult_t ncclProxyComputeP2p(struct ncclInfo* info, struct ncclProxyOp* op, int reg) {
|
||||
memset(op, 0, sizeof(struct ncclProxyOp));
|
||||
int channelId = info->channelId;
|
||||
struct ncclChannel* channel = info->comm->channels+channelId;
|
||||
@@ -611,15 +615,17 @@ ncclResult_t ncclProxyComputeP2p(struct ncclInfo* info, struct ncclProxyOp* op)
|
||||
op->pattern = ncclPatternSend;
|
||||
if (op->root != info->comm->rank && peer->send[1].transportComm == &netTransport.send) {
|
||||
// Tune chunk size for the network
|
||||
if (info->count < stepSize) info->chunkSize /= 4;
|
||||
if (info->protocol == NCCL_PROTO_SIMPLE && info->count < stepSize) info->chunkSize /= 4;
|
||||
else if (info->count < 8*stepSize) info->chunkSize /= 2;
|
||||
if (info->protocol == NCCL_PROTO_SIMPLE && peer->send[1].proxyConn.sameProcess) op->reg = reg;
|
||||
}
|
||||
} else if (info->coll == ncclFuncRecv) {
|
||||
op->pattern = ncclPatternRecv;
|
||||
if (op->root != info->comm->rank && peer->recv[1].transportComm == &netTransport.recv) {
|
||||
// Tune chunk size for the network
|
||||
if (info->count < stepSize) info->chunkSize /= 4;
|
||||
if (info->protocol == NCCL_PROTO_SIMPLE && info->count < stepSize) info->chunkSize /= 4;
|
||||
else if (info->count < 8*stepSize) info->chunkSize /= 2;
|
||||
if (info->protocol == NCCL_PROTO_SIMPLE && peer->recv[1].proxyConn.sameProcess) op->reg = reg;
|
||||
}
|
||||
} else {
|
||||
WARN("P2p operation is neither send or recv");
|
||||
@@ -628,17 +634,21 @@ ncclResult_t ncclProxyComputeP2p(struct ncclInfo* info, struct ncclProxyOp* op)
|
||||
if (ncclParamChunkSize() != 0) {
|
||||
info->chunkSize = ncclParamChunkSize();
|
||||
}
|
||||
op->buffer = op->reg ? info->recvbuff : NULL;
|
||||
op->chunkSize = info->chunkSize;
|
||||
op->nbytes = info->count;
|
||||
|
||||
// Compute nSteps for proxies
|
||||
int chunkEffectiveSize = op->chunkSize;
|
||||
if (op->protocol == NCCL_PROTO_LL) {
|
||||
chunkEffectiveSize /= 2;
|
||||
op->nbytes *= 2;
|
||||
op->nbytes = DIVUP(op->nbytes, sizeof(union ncclLLFifoLine)) * sizeof(union ncclLLFifoLine);
|
||||
}
|
||||
|
||||
op->nbytes = stepSize;
|
||||
if (!op->reg) op->nbytes = std::min(op->nbytes, (ssize_t)info->chunkSize);
|
||||
op->nsteps = DIVUP(info->count, chunkEffectiveSize);
|
||||
if (op->nsteps == 0) op->nsteps = 1;
|
||||
if (op->nsteps == 0 || op->reg) op->nsteps = 1;
|
||||
|
||||
return ncclSuccess;
|
||||
}
|
||||
@@ -1069,35 +1079,60 @@ ncclResult_t ncclProxyConnect(struct ncclComm* comm, int transport, int send, in
|
||||
return ncclSuccess;
|
||||
}
|
||||
|
||||
// cuMem API support
|
||||
// The response is sent out-of-band using ncclIpcSocket for this specific command
|
||||
ncclResult_t ncclProxyClientGetFdBlocking(struct ncclComm* comm, struct ncclProxyConnector* proxyConn, void *handle, int* convertedFd) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
ncclResult_t res = ncclInProgress;
|
||||
// UDS support
|
||||
ncclResult_t ncclProxyCallBlockingUDS(struct ncclComm* comm, int tpRank, int type, void* reqBuff, int reqSize, void* respBuff, int respSize, int *respFd) {
|
||||
ncclResult_t res = ncclSuccess;
|
||||
struct ncclIpcSocket ipcSock = { 0 };
|
||||
void *opId = (void*)((((uintptr_t)random()) << 32) | random());
|
||||
|
||||
// Create a UDS socket to receive the converted fd
|
||||
NCCLCHECK(ncclIpcSocketInit(&ipcSock, comm->topParentLocalRanks[comm->localRank], (uint64_t)opId, comm->abortFlag));
|
||||
int rank = comm->topParentLocalRanks[comm->localRank];
|
||||
struct ncclProxyState* sharedProxyState = comm->proxyState;
|
||||
uint64_t pidHash = sharedProxyState->peerAddressesUDS[tpRank];
|
||||
|
||||
// Request the allocation of a UDS fd for the handle over sockets
|
||||
NCCLCHECKGOTO(ncclProxyCallAsync(comm, proxyConn, ncclProxyMsgGetFd, handle, sizeof(CUmemGenericAllocationHandle), 0, opId), ret, error);
|
||||
INFO(NCCL_PROXY, "ProxyCall UDS comm %p rank %d tpRank %d(%lx) reqSize %d respSize %d respFd %p opId %p",
|
||||
comm, rank, tpRank, pidHash, reqSize, respSize, respFd, opId);
|
||||
|
||||
// Receive the converted fd over UDS
|
||||
NCCLCHECKGOTO(ncclIpcSocketRecvFd(&ipcSock, convertedFd), ret, error);
|
||||
TRACE(NCCL_PROXY, "UDS: ClientGetFd handle 0x%lx rank %d returned fd %d", *(uint64_t*)handle, proxyConn->tpLocalRank, *convertedFd);
|
||||
NCCLCHECKGOTO(ncclIpcSocketClose(&ipcSock), ret, error);
|
||||
// cuMem: Create a UDS socket to receive the response
|
||||
NCCLCHECK(ncclIpcSocketInit(&ipcSock, rank, (uint64_t)opId, comm->abortFlag));
|
||||
|
||||
// Wait for proxy response (sockets)
|
||||
while (res == ncclInProgress) {
|
||||
res = ncclPollProxyResponse(comm, proxyConn, NULL, opId);
|
||||
}
|
||||
ncclIpcHdr hdr;
|
||||
hdr.type = type;
|
||||
hdr.rank = rank;
|
||||
hdr.reqSize = reqSize;
|
||||
hdr.respSize = respSize;
|
||||
hdr.opId = opId;
|
||||
assert(reqSize <= sizeof(hdr.data));
|
||||
memcpy(&hdr.data, reqBuff, reqSize);
|
||||
NCCLCHECKGOTO(ncclIpcSocketSendMsg(&ipcSock, &hdr, sizeof(hdr), -1, tpRank, pidHash), res, error);
|
||||
NCCLCHECKGOTO(ncclIpcSocketRecvMsg(&ipcSock, respBuff, respSize, respFd), res, error);
|
||||
NCCLCHECKGOTO(ncclIpcSocketClose(&ipcSock), res, error);
|
||||
|
||||
INFO(NCCL_PROXY, "ProxyCall UDS comm %p rank %d tpRank %d(%lx) reqSize %d respSize %d respFd %d opId %p - DONE",
|
||||
comm, rank, tpRank, pidHash, reqSize, respSize, (respFd ? *respFd : -1), opId);
|
||||
|
||||
return res;
|
||||
|
||||
error:
|
||||
NCCLCHECK(ncclIpcSocketClose(&ipcSock));
|
||||
WARN("ncclProxyCallBlockingUDS call to tpRank %d(%lx) failed : %d", tpRank, pidHash, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
// cuMem API support
|
||||
// The request/response is sent out-of-band using ncclIpcSocket for this specific command
|
||||
ncclResult_t ncclProxyClientGetFdBlocking(struct ncclComm* comm, int tpRank, void *handle, int* convertedFd) {
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
|
||||
// Request the allocation of a UDS fd for the handle
|
||||
NCCLCHECKGOTO(ncclProxyCallBlockingUDS(comm, tpRank, ncclProxyMsgGetFd, handle, sizeof(CUmemGenericAllocationHandle), NULL, 0, convertedFd), ret, error);
|
||||
|
||||
// We have now received the converted fd over UDS
|
||||
INFO(NCCL_PROXY, "UDS: ClientGetFd handle 0x%lx tpRank %d returned fd %d", *(uint64_t*)handle, tpRank, *convertedFd);
|
||||
|
||||
return ret;
|
||||
|
||||
error:
|
||||
NCCLCHECK(ncclIpcSocketClose(&ipcSock));
|
||||
WARN("ncclProxyClientGetFd call to rank %d handle 0x%lx failed : %d", proxyConn->tpRank, *(uint64_t*)handle, ret);
|
||||
WARN("ncclProxyClientGetFd call to tpRank %d handle 0x%lx failed : %d", tpRank, *(uint64_t*)handle, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1132,7 +1167,7 @@ error:
|
||||
ncclResult_t ncclPollProxyResponse(struct ncclComm* comm, struct ncclProxyConnector* proxyConn, void* respBuff, void* opId) {
|
||||
struct ncclProxyState* sharedProxyState = comm->proxyState;
|
||||
// Receive the connection pointer from the Proxy
|
||||
if (*comm->abortFlag) {
|
||||
if (__atomic_load_n(comm->abortFlag, __ATOMIC_RELAXED)) {
|
||||
WARN("Comm %p is in abort state", comm);
|
||||
return ncclInternalError;
|
||||
}
|
||||
@@ -1287,13 +1322,13 @@ static ncclResult_t proxyConnInit(struct ncclProxyLocalPeer* peer, struct ncclPr
|
||||
}
|
||||
|
||||
// cuMem API support
|
||||
static ncclResult_t proxyGetFd(struct ncclProxyLocalPeer* peer, void *opId, struct ncclProxyState* proxyState, uint64_t handle) {
|
||||
static ncclResult_t proxyGetFd(struct ncclProxyState* proxyState, int rank, void *opId, uint64_t handle) {
|
||||
#if CUDART_VERSION >= 11030
|
||||
// cuMem API support
|
||||
ncclResult_t ret = ncclSuccess;
|
||||
struct ncclIpcSocket ipcSock = { 0 };
|
||||
uint64_t hash = (uint64_t) opId;
|
||||
INFO(NCCL_PROXY, "UDS proxyGetFd received handle 0x%lx peer %d opId %lx", handle, peer->tpLocalRank, hash);
|
||||
INFO(NCCL_PROXY, "UDS proxyGetFd received handle 0x%lx peer %d opId %lx", handle, rank, hash);
|
||||
|
||||
CUmemAllocationHandleType type = CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR;
|
||||
int fd = -1;
|
||||
@@ -1301,7 +1336,7 @@ static ncclResult_t proxyGetFd(struct ncclProxyLocalPeer* peer, void *opId, stru
|
||||
CUCHECK(cuMemExportToShareableHandle(&fd, handle, type, 0));
|
||||
// Send back the converted fd using UDS
|
||||
NCCLCHECKGOTO(ncclIpcSocketInit(&ipcSock, proxyState->tpRank, hash^1, proxyState->abortFlag), ret, error);
|
||||
NCCLCHECKGOTO(ncclIpcSocketSendFd(&ipcSock, fd, peer->tpLocalRank, hash), ret, error);
|
||||
NCCLCHECKGOTO(ncclIpcSocketSendFd(&ipcSock, fd, rank, hash), ret, error);
|
||||
error:
|
||||
NCCLCHECK(ncclIpcSocketClose(&ipcSock));
|
||||
// We can now safely close the exported fd
|
||||
@@ -1326,11 +1361,8 @@ static ncclResult_t proxyProgressAsync(struct ncclProxyAsyncOp* op, struct ncclP
|
||||
TRACE(NCCL_PROXY, "proxyProgressAsync::ncclProxyMsgSharedInit opId=%p op.reqBuff=%p nChannels=%d", op->opId, op->reqBuff, nChannels);
|
||||
if (op->connection->tcomm->proxySharedInit) res = op->connection->tcomm->proxySharedInit(op->connection, proxyState, nChannels);
|
||||
__atomic_store_n(&op->connection->state, connSharedInitialized, __ATOMIC_RELEASE);
|
||||
} else if (op->type == ncclProxyMsgGetFd) {
|
||||
uint64_t handle = *(uint64_t*)op->reqBuff;
|
||||
TRACE(NCCL_PROXY, "proxyProgressAsync::ncclProxyMsgGetFd opId=%p op.reqBuff=%p handle=0x%lx", op->opId, op->reqBuff, handle);
|
||||
res = proxyGetFd(peer, op->opId, proxyState, handle); // cuMem API support
|
||||
} else if (op->type == ncclProxyMsgInit) {
|
||||
}
|
||||
else if (op->type == ncclProxyMsgInit) {
|
||||
TRACE(NCCL_PROXY, "proxyProgressAsync::ncclProxyMsgInit opId=%p op.reqBuff=%p", op->opId, op->reqBuff);
|
||||
res = proxyConnInit(peer, connectionPool, proxyState, (ncclProxyInitReq*) op->reqBuff, (ncclProxyInitResp*) op->respBuff, &op->connection);
|
||||
} else return ncclInternalError;
|
||||
@@ -1360,7 +1392,7 @@ static ncclResult_t proxyProgressAsync(struct ncclProxyAsyncOp* op, struct ncclP
|
||||
(*asyncOpCount)--;
|
||||
return ncclSuccess;
|
||||
|
||||
} else if (*proxyState->abortFlag != 0) {
|
||||
} else if (__atomic_load_n(proxyState->abortFlag, __ATOMIC_RELAXED) != 0) {
|
||||
return ncclInternalError;
|
||||
}
|
||||
|
||||
@@ -1446,7 +1478,7 @@ void* ncclProxyService(void* _args) {
|
||||
/* Even if local comm aborts, we cannot let proxy thread exit if we still have peer
|
||||
* connections. Need to wait until all other related comms call abort and safely exit
|
||||
* together, or we could face segmentation fault. */
|
||||
if (*proxyState->abortFlag != 0) stop = 1;
|
||||
if (__atomic_load_n(proxyState->abortFlag, __ATOMIC_RELAXED) != 0) stop = 1;
|
||||
/* never let proxy service thread blocks in poll, or it cannot receive abortFlag. */
|
||||
int ret;
|
||||
do {
|
||||
@@ -1563,13 +1595,71 @@ void* ncclProxyService(void* _args) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ncclResult_t ncclProxyInit(struct ncclComm* comm, struct ncclSocket* sock, union ncclSocketAddress* peerAddresses) {
|
||||
|
||||
// Process a request on the UDS socket
|
||||
static ncclResult_t proxyUDSRecvReq(struct ncclProxyState* proxyState, int reqFd) {
|
||||
ncclIpcHdr hdr;
|
||||
NCCLCHECK(ncclIpcSocketRecvMsg(&proxyState->ipcSock, &hdr, sizeof(hdr), NULL));
|
||||
if (hdr.type == ncclProxyMsgGetFd) {
|
||||
// cuMem API support
|
||||
uint64_t handle = *(uint64_t*)hdr.data;
|
||||
INFO(NCCL_PROXY, "proxyUDSRecvReq::ncclProxyMsgGetFd rank %d opId %p handle=0x%lx", hdr.rank, hdr.opId, handle);
|
||||
return proxyGetFd(proxyState, hdr.rank, hdr.opId, handle);
|
||||
}
|
||||
|
||||
return ncclInternalError;
|
||||
}
|
||||
|
||||
// UDS fd handle support
|
||||
void* ncclProxyServiceUDS(void* _args) {
|
||||
struct ncclProxyState* proxyState = (struct ncclProxyState*) _args;
|
||||
struct pollfd pollfds[1];
|
||||
|
||||
if (setProxyThreadContext(proxyState)) {
|
||||
INFO(NCCL_INIT, "[Proxy Service UDS] Created CUDA context on device %d", proxyState->cudaDev);
|
||||
} else if (cudaSetDevice(proxyState->cudaDev) != cudaSuccess) {
|
||||
WARN("[Proxy Service UDS] Failed to set CUDA device %d", proxyState->cudaDev);
|
||||
}
|
||||
|
||||
if (ncclIpcSocketGetFd(&proxyState->ipcSock, &pollfds[0].fd) != ncclSuccess) {
|
||||
WARN("[Proxy Service UDS] Get listenSock fd fails");
|
||||
return NULL;
|
||||
};
|
||||
pollfds[0].events = POLLIN|POLLHUP;
|
||||
|
||||
while (1) {
|
||||
/* never let proxy service thread blocks in poll, or it cannot receive abortFlag. */
|
||||
int ret;
|
||||
do {
|
||||
ret = poll(pollfds, 1, 500);
|
||||
} while (ret < 0 && errno == EINTR);
|
||||
if (ret < 0) {
|
||||
WARN("[Proxy Service UDS] Poll failed: %s", strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Check for stop/abort
|
||||
if (proxyState->stop || *proxyState->abortFlag) break;
|
||||
|
||||
if (pollfds[0].revents) {
|
||||
// A request was seen on the UDS fd
|
||||
proxyUDSRecvReq(proxyState, pollfds[0].fd);
|
||||
}
|
||||
}
|
||||
|
||||
ncclIpcSocketClose(&proxyState->ipcSock);
|
||||
INFO(NCCL_PROXY, "[Proxy Service UDS] exit: stop %d abortFlag %d", proxyState->stop, *proxyState->abortFlag);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ncclResult_t ncclProxyInit(struct ncclComm* comm, struct ncclSocket* sock, union ncclSocketAddress* peerAddresses, uint64_t *peerAddressesUDS) {
|
||||
assert(comm->sharedRes->proxyState == NULL);
|
||||
NCCLCHECK(ncclCalloc(&comm->sharedRes->proxyState, 1));
|
||||
comm->proxyState = comm->sharedRes->proxyState;
|
||||
comm->proxyState->refCount = 1;
|
||||
comm->proxyState->listenSock = sock;
|
||||
comm->proxyState->peerAddresses = peerAddresses;
|
||||
comm->proxyState->peerAddressesUDS = peerAddressesUDS;
|
||||
// Seed the random number generator for UDS filename generation
|
||||
struct timeval time;
|
||||
gettimeofday(&time,NULL);
|
||||
@@ -1601,6 +1691,12 @@ ncclResult_t ncclProxyCreate(struct ncclComm* comm) {
|
||||
|
||||
pthread_create(&comm->proxyState->thread, NULL, ncclProxyService, comm->proxyState);
|
||||
ncclSetThreadName(comm->proxyState->thread, "NCCL Service %2d", comm->cudaDev);
|
||||
|
||||
// UDS support
|
||||
INFO(NCCL_PROXY, "UDS: Creating service thread comm %p rank %d pidHash %lx", comm, comm->rank, comm->peerInfo[comm->rank].pidHash);
|
||||
NCCLCHECK(ncclIpcSocketInit(&comm->proxyState->ipcSock, comm->rank, comm->peerInfo[comm->rank].pidHash, comm->abortFlag));
|
||||
pthread_create(&comm->proxyState->threadUDS, NULL, ncclProxyServiceUDS, comm->proxyState);
|
||||
ncclSetThreadName(comm->proxyState->threadUDS, "NCCL UDS Service %2d", comm->cudaDev);
|
||||
}
|
||||
return ncclSuccess;
|
||||
}
|
||||
@@ -1610,8 +1706,13 @@ ncclResult_t ncclProxyStop(struct ncclComm* comm) {
|
||||
struct ncclProxyState* sharedProxyState = comm->proxyState;
|
||||
|
||||
if ((comm->proxyRefCountOld = ncclAtomicRefCountDecrement(&sharedProxyState->refCount)) == 0) {
|
||||
if (comm->proxyState->threadUDS) {
|
||||
// UDS support
|
||||
comm->proxyState->stop = 1;
|
||||
}
|
||||
|
||||
if (sharedProxyState->peerAddresses) {
|
||||
if (*comm->abortFlag == 0) {
|
||||
if (__atomic_load_n(comm->abortFlag, __ATOMIC_RELAXED) == 0) {
|
||||
struct ncclSocket sock;
|
||||
int type = ncclProxyMsgStop;
|
||||
NCCLCHECK(ncclSocketInit(&sock, sharedProxyState->peerAddresses + comm->topParentRanks[comm->rank], comm->sharedRes->magic, ncclSocketTypeProxy, comm->abortFlag));
|
||||
@@ -1636,7 +1737,7 @@ ncclResult_t ncclProxyStop(struct ncclComm* comm) {
|
||||
}
|
||||
}
|
||||
int type = ncclProxyMsgClose;
|
||||
if (*comm->abortFlag == 0) NCCLCHECK(ncclSocketSend(sharedProxyState->peerSocks + i, &type, sizeof(int)));
|
||||
if (__atomic_load_n(comm->abortFlag, __ATOMIC_RELAXED) == 0) NCCLCHECK(ncclSocketSend(sharedProxyState->peerSocks + i, &type, sizeof(int)));
|
||||
NCCLCHECK(ncclSocketClose(sharedProxyState->peerSocks + i));
|
||||
}
|
||||
}
|
||||
@@ -1652,6 +1753,7 @@ ncclResult_t ncclProxyDestroy(struct ncclComm* comm) {
|
||||
|
||||
assert(sharedProxyState->refCount == 0);
|
||||
free(sharedProxyState->peerAddresses);
|
||||
free(sharedProxyState->peerAddressesUDS);
|
||||
free(sharedProxyState->peerSocks);
|
||||
free(sharedProxyState->proxyOps);
|
||||
free(sharedProxyState->sharedDevMems);
|
||||
|
||||
Reference in new issue
Block a user