LCOV - code coverage report
Current view: top level - legacy/ascend950/service/collective/alg/coll_alg_factory/alg_template/prim_alg_template - temp_reduce_scatter_mesh.cc (source / functions) Coverage Total Hit
Test: coverage.info Lines: 0.0 % 172 0
Test Date: 2026-08-18 17:47:01 Functions: 0.0 % 16 0

            Line data    Source code
       1              : /**
       2              :  * Copyright (c) 2025 Huawei Technologies Co., Ltd.
       3              :  * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
       4              :  * CANN Open Software License Agreement Version 2.0 (the "License").
       5              :  * Please refer to the License for details. You may not use this file except in compliance with the License.
       6              :  * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
       7              :  * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
       8              :  * See LICENSE in the root of the software repository for the full text of the License.
       9              :  */
      10              : 
      11              : #include <numeric>
      12              : 
      13              : #include "log.h"
      14              : 
      15              : #include "temp_reduce_scatter_mesh.h"
      16              : 
      17              : namespace Hccl {
      18            0 : TempReduceScatterMesh::TempReduceScatterMesh(
      19              :     const RankId virtualRank, const u32 tempRankSize, const std::vector<std::vector<RankId>>& tempVTopo,
      20            0 :     const std::map<RankId, u32>& tempVirtRankMap)
      21            0 :     : AlgTemplateBase(virtualRank, tempRankSize, tempVTopo, tempVirtRankMap)
      22            0 : {}
      23              : 
      24            0 : TempReduceScatterMesh::~TempReduceScatterMesh() {}
      25              : 
      26              : HcclResult
      27            0 : TempReduceScatterMesh::CalcRes(const bool forAllReduce, AlgTempResReq& tempResReq, u32& requiredScratchMultiplier)
      28              : {
      29              :     (void)forAllReduce;
      30            0 :     tempResReq.queNum = tempVTopo_[0].size() - 1;
      31            0 :     requiredScratchMultiplier = tempRankSize_;
      32              : 
      33            0 :     CHK_RET(CalcResLinks(tempResReq));
      34            0 :     return HcclResult::HCCL_SUCCESS;
      35              : }
      36              : 
      37            0 : HcclResult TempReduceScatterMesh::CalcResDetour(
      38              :     const bool forAllReduce, const RankGraph* rankGraph, AlgTempResReq& tempResReq, u32& requiredScratchMultiplier)
      39              : {
      40              :     (void)forAllReduce;
      41              : 
      42              :     u32 myAlgRank;
      43            0 :     CHK_RET(GetAlgRank(myRank_, tempVTopo_[0], myAlgRank));
      44              : 
      45            0 :     linkNumBtwPeers_ = GetLinkNum(rankGraph, myRank_, tempVTopo_[0][(myAlgRank + 1) % tempRankSize_]);
      46            0 :     if (linkNumBtwPeers_ == 1) {
      47            0 :         HCCL_INFO(
      48              :             "[CollAlgFactory] [TempReduceScatterMesh] [WARNING] Rank [%d], linkNum between rank [%d] and rank [%d] "
      49              :             "equals 1, not able to detour",
      50              :             myRank_, myRank_, tempVTopo_[0][(myAlgRank + 1) % tempRankSize_]);
      51            0 :         enableDetour_ = false;
      52              :     } else {
      53            0 :         enableDetour_ = true;
      54              :     }
      55              : 
      56            0 :     queNumPerNeighbor_ = (linkNumBtwPeers_ + 1) >> 1;
      57            0 :     tempResReq.queNum = (tempVTopo_[0].size() - 1) * queNumPerNeighbor_;
      58              : 
      59            0 :     requiredScratchMultiplier = tempRankSize_;
      60              : 
      61            0 :     CHK_RET(CalcResLinks(tempResReq));
      62            0 :     return HcclResult::HCCL_SUCCESS;
      63              : }
      64              : 
      65            0 : HcclResult TempReduceScatterMesh::CalcResDetour(
      66              :     const bool forAllReduce, ConnectedLinkMgr* linkMgr, AlgTempResReq& tempResReq, u32& requiredScratchMultiplier)
      67              : {
      68              :     (void)forAllReduce;
      69              : 
      70              :     u32 myAlgRank;
      71            0 :     CHK_RET(GetAlgRank(myRank_, tempVTopo_[0], myAlgRank));
      72              : 
      73            0 :     linkNumBtwPeers_ = (linkMgr->GetLinks(tempVTopo_[0][(myAlgRank + 1) % tempRankSize_])).size();
      74              : 
      75            0 :     enableDetour_ = (linkNumBtwPeers_ == 1) ? false : true;
      76              : 
      77            0 :     queNumPerNeighbor_ = (linkNumBtwPeers_ + 1) >> 1;
      78            0 :     tempResReq.queNum = (tempVTopo_[0].size() - 1) * queNumPerNeighbor_;
      79              : 
      80            0 :     requiredScratchMultiplier = tempRankSize_;
      81              : 
      82            0 :     CHK_RET(CalcResLinks(tempResReq));
      83            0 :     return HcclResult::HCCL_SUCCESS;
      84              : }
      85              : 
      86            0 : HcclResult TempReduceScatterMesh::CalcResLinks(AlgTempResReq& tempResReq)
      87              : {
      88              :     u32 myAlgRank;
      89            0 :     CHK_RET(GetAlgRank(myRank_, tempVTopo_[0], myAlgRank));
      90              : 
      91            0 :     for (u32 queIdx = 0; queIdx < tempVTopo_[0].size() - 1; queIdx++) {
      92              :         // find neighbors -> virtualRank
      93            0 :         RankId neighborRank = tempVTopo_[0][(myAlgRank + 1 + queIdx) % tempRankSize_];
      94              : 
      95              :         // LinkNum
      96            0 :         tempResReq.links[neighborRank] = linkNumBtwPeers_;
      97              :     }
      98              : 
      99            0 :     return HcclResult::HCCL_SUCCESS;
     100              : }
     101              : 
     102              : /*
     103              : dataSize / (rankSize) --> chunkSize
     104              : dataSize / (rankSize * queNum) --> sliceSize
     105              : 
     106              : SliceInfoVecforRing: [1st chunk: [1st Slice, 2nd Slice, ...], 2nd chunk: [1st Slice, 2nd Slice, ...], ...]
     107              : */
     108            0 : HcclResult TempReduceScatterMesh::CalcSliceInfo(
     109              :     const AllignInfo& allignInfo, const bool forAllReduce, const u64 dataSize, RankSliceInfo& sliceInfoVec)
     110              : {
     111            0 :     std::vector<SliceInfo> tmp(1);
     112            0 :     sliceInfoVec.resize(tempRankSize_, tmp);
     113              : 
     114            0 :     if (forAllReduce) {
     115              :         // for allreduce, dataSize = total dataSize
     116            0 :         CHK_RET(CalcSliceInfoAllReduce(allignInfo, dataSize, sliceInfoVec));
     117              :     } else {
     118              :         // for reduce scatter, dataSize = chunkSize
     119            0 :         CHK_RET(CalcRsAgSliceInfoMesh(myRank_, tempRankSize_, allignInfo, dataSize, sliceInfoVec));
     120              :     }
     121            0 :     return HcclResult::HCCL_SUCCESS;
     122            0 : }
     123              : 
     124            0 : HcclResult TempReduceScatterMesh::CalcSliceInfoAllReduce(
     125              :     const AllignInfo& allignInfo, const u64 dataSize, RankSliceInfo& sliceInfoVec) const
     126              : {
     127              :     u64 unitAllignSize;
     128            0 :     CHK_RET(GetUnitAllignSize(allignInfo, unitAllignSize));
     129              : 
     130            0 :     u64 chunkSize = RoundUp(dataSize, (tempRankSize_ * unitAllignSize)) * unitAllignSize;
     131              : 
     132            0 :     u64 accumOff = 0;
     133            0 :     for (u32 rankIdx = 0; rankIdx < tempRankSize_; rankIdx++) {
     134            0 :         u64 currChunkSize = ((dataSize - accumOff) > chunkSize) ? chunkSize : (dataSize - accumOff);
     135            0 :         SliceInfo slice = {accumOff, currChunkSize};
     136            0 :         sliceInfoVec[rankIdx][0] = slice;
     137            0 :         accumOff += currChunkSize;
     138              :     }
     139              : 
     140            0 :     CHK_PRT_RET(
     141              :         (sliceInfoVec[tempRankSize_ - 1][0].offset + sliceInfoVec[tempRankSize_ - 1][0].size != dataSize),
     142              :         HCCL_ERROR("[CollAlgFactory] [TempReduceScatterMesh] Rank [%d], SliceInfo calculation error!", myRank_),
     143              :         HcclResult::HCCL_E_INTERNAL);
     144              : 
     145            0 :     return HcclResult::HCCL_SUCCESS;
     146              : }
     147              : 
     148            0 : HcclResult TempReduceScatterMesh::GenPrimQue(
     149              :     const TempFuncs& tempFuncs, const RankSliceInfo& sliceInfoVec, const BuffInfo& buffInfo, const ResLinks& tempLinks,
     150              :     std::vector<PrimQuePtr>& tempPrimQues)
     151              : {
     152            0 :     opMode_ = tempFuncs.opMode;
     153            0 :     enableCounterNotify_ = tempFuncs.enableCounterNotify;
     154            0 :     buffInfo_ = buffInfo;
     155              : 
     156            0 :     auto linkIter = tempLinks.begin();
     157            0 :     linkNumBtwPeers_ = linkIter->second.size();
     158            0 :     HCCL_INFO(
     159              :         "[CollAlgFactory] [TempReduceScatterMesh] Rank [%d], linkNumBtwPeers equals to [%u].", myRank_,
     160              :         linkNumBtwPeers_);
     161            0 :     queNumPerNeighbor_ = (linkNumBtwPeers_ + 1) >> 1;
     162            0 :     HCCL_INFO(
     163              :         "[CollAlgFactory] [TempReduceScatterMesh] Rank [%d], queNumPerNeighbor equals to [%u].", myRank_,
     164              :         queNumPerNeighbor_);
     165            0 :     enableDetour_ = (linkNumBtwPeers_ == 1) ? false : true;
     166              : 
     167            0 :     majorQueNum_ = tempVTopo_[0].size() - 1;
     168            0 :     CHK_PRT_RET(
     169              :         majorQueNum_ * queNumPerNeighbor_ != tempPrimQues.size(),
     170              :         HCCL_ERROR(
     171              :             "[CollAlgFactory] [TempReduceScatterMesh] Rank [%d], requiredQueNum [%u] not equals to "
     172              :             "templateQueNum [%u].",
     173              :             myRank_, majorQueNum_ * queNumPerNeighbor_, tempPrimQues.size()),
     174              :         HcclResult::HCCL_E_INTERNAL);
     175              : 
     176              :     // queue arrangement
     177            0 :     std::vector<PrimQuePtr> mainPrimQues;
     178            0 :     for (u32 queIdx = 0; queIdx < majorQueNum_; queIdx++) {
     179            0 :         mainPrimQues.push_back(tempPrimQues[queIdx * queNumPerNeighbor_]);
     180              :     }
     181              : 
     182              :     // LocalCopy: from input to scratch In Buffer for OPBASE
     183            0 :     if ((opMode_ == OpMode::OPBASE) && tempFuncs.isForepart) {
     184            0 :         CHK_RET(PreCopyOpbase(tempFuncs.usrData, mainPrimQues));
     185              :     }
     186              : 
     187              :     // semaphore sync
     188            0 :     if (majorQueNum_ > 1) {
     189            0 :         CHK_RET(PreSyncInterQueues(mainPrimQues));
     190              :     }
     191              : 
     192              :     // locate myRank in tempVTopo -> algRank
     193              :     u32 myAlgRank;
     194            0 :     CHK_RET(GetAlgRank(myRank_, tempVTopo_[0], myAlgRank));
     195              : 
     196              :     // run Mesh
     197            0 :     CHK_PRT_RET(
     198              :         RunMesh(myAlgRank, tempVTopo_[0], sliceInfoVec, tempLinks, tempPrimQues) != HcclResult::HCCL_SUCCESS,
     199              :         HCCL_ERROR("[CollAlgFactory] [TempReduceScatterMesh] Rank [%d], unable to run the mesh algorithm.", myRank_),
     200              :         HcclResult::HCCL_E_INTERNAL);
     201              : 
     202              :     // semaphore sync
     203            0 :     if (majorQueNum_ > 1) {
     204            0 :         CHK_RET(PostSyncInterQueues(mainPrimQues));
     205              :     }
     206              : 
     207              :     // LocalCopy for standalone reducescatter in Offload Mode
     208            0 :     if ((opMode_ == OpMode::OFFLOAD) && !tempFuncs.forAllReduce && !tempFuncs.forAlgSeqComb) {
     209            0 :         CHK_RET(PostCopyOffload(sliceInfoVec, mainPrimQues));
     210              :     }
     211              : 
     212              :     // LocalCopy from scratch to output for Opbase
     213            0 :     if (tempFuncs.isBottom && !tempFuncs.forAllReduce) {
     214            0 :         CHK_RET(PostCopyOpbase(tempFuncs.usrData, mainPrimQues));
     215              :     }
     216              : 
     217            0 :     return HcclResult::HCCL_SUCCESS;
     218            0 : }
     219              : 
     220            0 : HcclResult TempReduceScatterMesh::RunMesh(
     221              :     const u32 myAlgRank, const std::vector<RankId>& vTopo, const RankSliceInfo& sliceInfoVec, const ResLinks& tempLinks,
     222              :     std::vector<PrimQuePtr>& tempPrimQues)
     223              : {
     224            0 :     for (u32 queIdx = 0; queIdx < vTopo.size() - 1; queIdx++) {
     225              :         // find neighbors -> virtualRank
     226            0 :         RankId neighborRank = vTopo[(myAlgRank + 1 + queIdx) % tempRankSize_];
     227              : 
     228            0 :         u32 recvChunkIdx = tempVirtRankMap_[myRank_];
     229            0 :         u32 sendChunkIdx = tempVirtRankMap_[neighborRank];
     230              : 
     231              :         // queue assignment
     232            0 :         if (enableDetour_) {
     233            0 :             std::vector<PrimQuePtr> detourPrimQues;
     234            0 :             for (u32 detourIdx = 0; detourIdx < queNumPerNeighbor_; detourIdx++) {
     235            0 :                 detourPrimQues.push_back(tempPrimQues[queIdx * queNumPerNeighbor_ + detourIdx]);
     236              :             }
     237            0 :             HCCL_INFO("[CollAlgFactory] [TempReduceScatterMesh] Rank [%d], Run Mesh with Detour.", myRank_);
     238            0 :             CHK_RET(RunIndividualPeerDetour(
     239              :                 neighborRank, sliceInfoVec[sendChunkIdx][0], sliceInfoVec[recvChunkIdx][0], tempLinks, detourPrimQues));
     240            0 :         } else {
     241            0 :             PrimQuePtr currQue = tempPrimQues[queIdx];
     242            0 :             LinkData neighborLinkData = tempLinks.at(neighborRank)[0];
     243            0 :             HCCL_INFO("[CollAlgFactory] [TempReduceScatterMesh] Rank [%d], Run Mesh without Detour.", myRank_);
     244            0 :             CHK_RET(RunIndividualPeer(
     245              :                 neighborRank, neighborLinkData, sliceInfoVec[sendChunkIdx][0], sliceInfoVec[recvChunkIdx][0], currQue));
     246            0 :         }
     247              :     }
     248              : 
     249            0 :     return HcclResult::HCCL_SUCCESS;
     250              : }
     251              : 
     252            0 : HcclResult TempReduceScatterMesh::RunIndividualPeerDetour(
     253              :     const RankId neighborRank, const SliceInfo& sendReduceSlice, const SliceInfo& recvReduceSlice,
     254              :     const ResLinks& tempLinks, std::vector<PrimQuePtr>& detourPrimQues)
     255              : {
     256            0 :     CHK_RET(PreSyncInterQueues(detourPrimQues));
     257              : 
     258            0 :     u32 dataSizePerVolume = DataTypeSizeGet(dataType_);
     259            0 :     u64 unitRecvReduceSize = RoundUp(recvReduceSlice.size, queNumPerNeighbor_ * dataSizePerVolume) * dataSizePerVolume;
     260            0 :     u64 resRecvReduceSize = recvReduceSlice.size;
     261            0 :     u64 currRecvReduceOff = recvReduceSlice.offset;
     262              : 
     263            0 :     u64 unitSendReduceSize = RoundUp(sendReduceSlice.size, queNumPerNeighbor_ * dataSizePerVolume) * dataSizePerVolume;
     264            0 :     u64 resSendReduceSize = sendReduceSlice.size;
     265            0 :     u64 currSendReduceOff = sendReduceSlice.offset;
     266              : 
     267            0 :     std::vector<std::vector<LinkDataIterator>> sendRecvRedLinks;
     268            0 :     CHK_RET(GetSendRecvRedLinks(neighborRank, tempLinks, sendRecvRedLinks));
     269              : 
     270            0 :     for (u32 detourIdx = 0; detourIdx < queNumPerNeighbor_; detourIdx++) {
     271            0 :         u64 currSendReduceSize = resSendReduceSize > unitSendReduceSize ? unitSendReduceSize : resSendReduceSize;
     272            0 :         u64 currRecvReduceSize = resRecvReduceSize > unitRecvReduceSize ? unitRecvReduceSize : resRecvReduceSize;
     273            0 :         SliceInfo currSendReduceSlice = {currSendReduceOff, currSendReduceSize};
     274            0 :         SliceInfo currRecvReduceSlice = {currRecvReduceOff, currRecvReduceSize};
     275              : 
     276              :         std::unique_ptr<PrimGroup> primGroup = RunSendRecvReduce(
     277            0 :             neighborRank, (*sendRecvRedLinks[detourIdx][0]), (*sendRecvRedLinks[detourIdx][1]), currSendReduceSlice,
     278            0 :             currRecvReduceSlice);
     279            0 :         detourPrimQues[detourIdx]->Append(std::move(primGroup));
     280              : 
     281            0 :         resRecvReduceSize -= currRecvReduceSize;
     282            0 :         resSendReduceSize -= currSendReduceSize;
     283            0 :         currRecvReduceOff += currRecvReduceSize;
     284            0 :         currSendReduceOff += currSendReduceSize;
     285            0 :     }
     286              : 
     287            0 :     CHK_RET(PostSyncInterQueues(detourPrimQues));
     288            0 :     return HcclResult::HCCL_SUCCESS;
     289            0 : }
     290              : 
     291            0 : HcclResult TempReduceScatterMesh::GetSendRecvRedLinks(
     292              :     const RankId neighborRank, const ResLinks& tempLinks,
     293              :     std::vector<std::vector<LinkDataIterator>>& sendRecvLinks) const
     294              : {
     295            0 :     CHK_PRT_RET(
     296              :         ((queNumPerNeighbor_ != NUM_TWO) || (tempRankSize_ != NUM_TWO)),
     297              :         HCCL_ERROR(
     298              :             "[CollAlgFactory] [TempReduceScatterMesh] Rank [%d], detouring is supported only in 2P Mesh in 4P topo.",
     299              :             myRank_),
     300              :         HcclResult::HCCL_E_INTERNAL);
     301              : 
     302            0 :     std::vector<LinkDataIterator> tmpLinks(NUM_TWO);
     303            0 :     sendRecvLinks.resize(queNumPerNeighbor_, tmpLinks);
     304              : 
     305            0 :     CHK_PRT_RET(
     306              :         GetDetourSendRecvLinksIn4P(myRank_, neighborRank, tempLinks, sendRecvLinks),
     307              :         HCCL_ERROR(
     308              :             "[InsCollAlgFactory] [TempReduceScatterMesh] Rank [%d], get send recv links in 2P Mesh in 4P topo.",
     309              :             myRank_),
     310              :         HcclResult::HCCL_E_INTERNAL);
     311              : 
     312            0 :     return HcclResult::HCCL_SUCCESS;
     313            0 : }
     314              : 
     315            0 : HcclResult TempReduceScatterMesh::RunIndividualPeer(
     316              :     const RankId neighborRank, const LinkData& neighborLinkData, const SliceInfo& sendReduceSlice,
     317              :     const SliceInfo& recvReduceSlice, PrimQuePtr currQue) const
     318              : {
     319              :     std::unique_ptr<PrimGroup> primGroup
     320            0 :         = RunSendRecvReduce(neighborRank, neighborLinkData, neighborLinkData, sendReduceSlice, recvReduceSlice);
     321            0 :     currQue->Append(std::move(primGroup));
     322            0 :     return HcclResult::HCCL_SUCCESS;
     323            0 : }
     324              : 
     325            0 : std::unique_ptr<PrimGroup> TempReduceScatterMesh::RunSendRecvReduce(
     326              :     const RankId neighborRank, const LinkData& sendLinkData, const LinkData& recvLinkData,
     327              :     const SliceInfo& currSendReduceSlice, const SliceInfo& currRecvReduceSlice) const
     328              : {
     329              :     // PrimGroup
     330            0 :     std::unique_ptr<PrimGroup> primGroup = std::make_unique<PrimGroup>();
     331              : 
     332              :     // RecvReduce
     333            0 :     u64 recvOffset = currRecvReduceSlice.offset;
     334            0 :     u64 recvSize = currRecvReduceSlice.size;
     335            0 :     DataSlice recvRemSlice = DataSlice(buffInfo_.inBuffType, recvOffset + buffInfo_.inBuffBaseOff, recvSize);
     336            0 :     DataSlice recvLocSrcSlice = DataSlice(buffInfo_.scratBuffType, recvOffset + buffInfo_.scratchBuffBaseOff, recvSize);
     337            0 :     DataSlice recvLocDstSlice = DataSlice(buffInfo_.inBuffType, recvOffset + buffInfo_.inBuffBaseOff, recvSize);
     338            0 :     std::unique_ptr<Primitive> primRecvReduce = std::make_unique<PrimRecvReduce>(
     339            0 :         neighborRank, recvLinkData, recvRemSlice, recvLocSrcSlice, recvLocDstSlice, dataType_, redOp_, dmaMode_);
     340              : 
     341            0 :     primGroup->Append(std::move(primRecvReduce));
     342              : 
     343              :     // SendReduce
     344            0 :     u64 sendOffset = currSendReduceSlice.offset;
     345            0 :     u64 sendSize = currSendReduceSlice.size;
     346            0 :     DataSlice sendLocSlice = DataSlice(buffInfo_.inBuffType, sendOffset + buffInfo_.inBuffBaseOff, sendSize);
     347            0 :     DataSlice sendRemSrcSlice = DataSlice(buffInfo_.scratBuffType, sendOffset + buffInfo_.scratchBuffBaseOff, sendSize);
     348            0 :     DataSlice sendRemDstSlice = DataSlice(buffInfo_.inBuffType, sendOffset + buffInfo_.inBuffBaseOff, sendSize);
     349            0 :     std::unique_ptr<Primitive> primSendReduce = std::make_unique<PrimSendReduce>(
     350            0 :         neighborRank, sendLinkData, sendLocSlice, sendRemSrcSlice, sendRemDstSlice, dataType_, redOp_, dmaMode_);
     351              : 
     352            0 :     primGroup->Append(std::move(primSendReduce));
     353              : 
     354            0 :     return primGroup;
     355            0 : }
     356              : 
     357              : HcclResult
     358            0 : TempReduceScatterMesh::PostCopyOffload(const RankSliceInfo& sliceInfoVec, std::vector<PrimQuePtr>& tempPrimQues)
     359              : {
     360            0 :     u64 srcOffset = sliceInfoVec[tempVirtRankMap_[myRank_]][0].offset;
     361            0 :     u64 srcSize = sliceInfoVec[tempVirtRankMap_[myRank_]][0].size;
     362            0 :     u64 dstOffset = 0;
     363              : 
     364            0 :     DataSlice srcSlice = DataSlice(buffInfo_.inBuffType, srcOffset + buffInfo_.inBuffBaseOff, srcSize);
     365            0 :     DataSlice dstSlice = DataSlice(buffInfo_.outBuffType, dstOffset + buffInfo_.outBuffBaseOff, srcSize);
     366            0 :     std::unique_ptr<Primitive> primLocalCopy = std::make_unique<PrimLocalCopy>(srcSlice, dstSlice);
     367            0 :     tempPrimQues[0]->Append(std::move(primLocalCopy));
     368              : 
     369            0 :     return HcclResult::HCCL_SUCCESS;
     370            0 : }
     371              : } // namespace Hccl
        

Generated by: LCOV version 2.0-1