LCOV - code coverage report
Current view: top level - base_comm/resources/ccu/ccu_microcode_opt - instruction_scheduler.cc (source / functions) Coverage Total Hit
Test: coverage.info Lines: 92.2 % 103 95
Test Date: 2026-08-25 19:18:03 Functions: 100.0 % 10 10

            Line data    Source code
       1              : /**
       2              :  * Copyright (c) 2026 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 "instruction_scheduler.h"
      12              : 
      13              : #include <limits>
      14              : #include <unordered_map>
      15              : 
      16              : #include "ccu_log.h"
      17              : 
      18              : #include "config/barrier_config.h" // 提供 InstrCodeV2 opcode 常量
      19              : 
      20              : namespace hcomm {
      21              : namespace CcuOpt {
      22              : 
      23              :     namespace {
      24              : 
      25           32 :         inline uint32_t RegKey(const RegOperand& operand)
      26              :         {
      27           32 :             return (static_cast<uint32_t>(operand.type) << 16) | static_cast<uint32_t>(operand.regId);
      28              :         }
      29              : 
      30           86 :         inline CcuRep::CcuInstr MakeNop()
      31              :         {
      32              :             // CcuInstr 为 POD (header + union, 无非平凡成员), {} 值初始化已将全部字节零化,
      33              :             // 无需再 memset_s; 仅设置 NOP 的 header 即可.
      34           86 :             CcuRep::CcuInstr nopInstr{};
      35           86 :             nopInstr.header = CcuRep::InstrHeader(InstrCodeV2::LOAD_TYPE, InstrCodeV2::NOP_CODE);
      36           86 :             return nopInstr;
      37              :         }
      38              : 
      39              :         // CkeOnly 顺序调度的可变状态: 输出序列、原->输出映射、统计信息, 以及只跟踪 CKE 写者发射
      40              :         // cycle 的表. 不做启发式放大, 保证补 NOP 有界.
      41              :         struct CkeOnlyState {
      42              :             std::vector<CcuRep::CcuInstr>& outVec;
      43              :             std::vector<int32_t>& origToOut;
      44              :             SchedulerStats& stats;
      45              :             std::unordered_map<uint32_t, int64_t> lastCkeWriterCycle{};
      46              :             int64_t cycle = 0;
      47              :         };
      48              : 
      49           86 :         inline void EmitNop(CkeOnlyState& state)
      50              :         {
      51           86 :             state.outVec.push_back(MakeNop());
      52           86 :             state.stats.originIndex.push_back(-1); // 无对应源.
      53           86 :             state.stats.nopInserted++;
      54           86 :             state.cycle++;
      55           86 :         }
      56              : 
      57              :         // 计算当前指令为满足 CKE 写后读 latency 所需的最早发射 cycle.
      58          764 :         inline int64_t EarliestCkeIssueCycle(const CkeOnlyState& state, const std::vector<RegOperand>& operands)
      59              :         {
      60          764 :             const int64_t ckeLatency = static_cast<int64_t>(CcuRep::CCU_CKE_RAW_LATENCY);
      61          764 :             int64_t earliest = state.cycle;
      62         1752 :             for (const auto& operand : operands) {
      63          988 :                 if (operand.isDef || operand.type != RegType::CKE) {
      64          980 :                     continue;
      65              :                 }
      66           16 :                 auto it = state.lastCkeWriterCycle.find(RegKey(operand));
      67           16 :                 if (it == state.lastCkeWriterCycle.end()) {
      68            8 :                     continue;
      69              :                 }
      70            8 :                 int64_t needed = it->second + ckeLatency;
      71            8 :                 if (needed > earliest) {
      72            7 :                     earliest = needed;
      73              :                 }
      74              :             }
      75          764 :             return earliest;
      76              :         }
      77              : 
      78              :         // 处理单条指令: 先补齐 latency NOP, 再原序发射, 最后记录 CKE 写者的发射 cycle.
      79          764 :         void ScheduleOneCkeInstr(CkeOnlyState& state, const CcuRep::CcuInstr& instr, size_t originIdx)
      80              :         {
      81          764 :             auto operands = ExtractOperandsV2(instr);
      82              : 
      83          764 :             int64_t earliestIssueCycle = EarliestCkeIssueCycle(state, operands);
      84          850 :             while (state.cycle < earliestIssueCycle) {
      85           86 :                 EmitNop(state);
      86              :             }
      87              : 
      88          764 :             state.origToOut[originIdx] = static_cast<int32_t>(state.outVec.size());
      89          764 :             state.outVec.push_back(instr);
      90          764 :             state.stats.originIndex.push_back(static_cast<int32_t>(originIdx)); // CkeOnly 顺序保持.
      91          764 :             int64_t issueCycle = state.cycle;
      92          764 :             state.cycle++;
      93              : 
      94         1752 :             for (const auto& operand : operands) {
      95          988 :                 if (!operand.isDef || operand.type != RegType::CKE) {
      96          972 :                     continue;
      97              :                 }
      98           16 :                 state.lastCkeWriterCycle[RegKey(operand)] = issueCycle;
      99              :             }
     100          764 :         }
     101              : 
     102              :         // 依据已确定的 out.missionStartInstrId 重新推导 missionInstrCount:
     103              :         // mission 起点落在输出序列内则取到序列尾部的长度, 否则计 0.
     104           21 :         inline void RecomputeMissionCount(CcuRep::CcuInstrInfo& out, uint16_t startId)
     105              :         {
     106           21 :             if (static_cast<uint32_t>(out.missionStartInstrId)
     107           21 :                 < static_cast<uint32_t>(startId) + static_cast<uint32_t>(out.instrCount)) {
     108           21 :                 out.missionInstrCount = static_cast<uint16_t>(startId + out.instrCount - out.missionStartInstrId);
     109              :             } else {
     110            0 :                 out.missionInstrCount = 0;
     111              :             }
     112           21 :         }
     113              : 
     114              :         // 顺序扫描后处理: 修正 missionStartInstrId / missionInstrCount 与 Loop / LoopGroup 引用.
     115              :         // CkeOnly 只在原序上插入 NOP, 不重排, 故按 origToOut 平移引用即可.
     116           21 :         void FixReferences(
     117              :             const CcuRep::CcuInstrInfo& input, const std::vector<int32_t>& origToOut, CcuRep::CcuInstrInfo& out)
     118              :         {
     119              :             using namespace InstrCodeV2;
     120           21 :             const auto& origVec = input.instrVec;
     121           21 :             const size_t instrCount = origVec.size();
     122           21 :             const uint16_t startId = input.startInstrId;
     123              : 
     124           71 :             auto remapGlobal = [&](uint16_t globalId) -> uint16_t {
     125              :                 // globalId 是"startId + localId" 编码的全局 id, 越界或未映射则原样返回.
     126           71 :                 if (globalId < startId)
     127            0 :                     return globalId;
     128           71 :                 uint32_t localId = static_cast<uint32_t>(globalId) - static_cast<uint32_t>(startId);
     129           71 :                 if (localId >= origToOut.size())
     130            0 :                     return globalId;
     131           71 :                 int32_t newPos = origToOut[localId];
     132           71 :                 if (newPos < 0)
     133            0 :                     return globalId;
     134           71 :                 return static_cast<uint16_t>(startId + newPos);
     135           21 :             };
     136              : 
     137              :             // missionStartInstrId 修正: 若 mission 起点原本在本序列范围内, 映射到新的位置;
     138              :             // missionInstrCount 用序列尾部长度重新推导.
     139           21 :             if (input.missionStartInstrId >= startId
     140           21 :                 && static_cast<uint32_t>(input.missionStartInstrId) < static_cast<uint32_t>(startId) + instrCount) {
     141           21 :                 out.missionStartInstrId = remapGlobal(input.missionStartInstrId);
     142           21 :                 RecomputeMissionCount(out, startId);
     143              :             } else {
     144            0 :                 out.missionStartInstrId = input.missionStartInstrId;
     145            0 :                 out.missionInstrCount = input.missionInstrCount;
     146              :             }
     147              : 
     148          785 :             for (size_t originIdx = 0; originIdx < instrCount; ++originIdx) {
     149          764 :                 const auto& origInstr = origVec[originIdx];
     150          764 :                 int32_t outPos = origToOut[originIdx];
     151          764 :                 if (outPos < 0)
     152            0 :                     continue;
     153          764 :                 auto& outInstr = out.instrVec[outPos];
     154          764 :                 if (origInstr.header.type == CTRL_TYPE && origInstr.header.code == LOOP_CODE) {
     155           20 :                     outInstr.v2.loop.startInstrId = remapGlobal(origInstr.v2.loop.startInstrId);
     156           20 :                     outInstr.v2.loop.endInstrId = remapGlobal(origInstr.v2.loop.endInstrId);
     157          744 :                 } else if (origInstr.header.type == CTRL_TYPE && origInstr.header.code == LOOPGROUP_CODE) {
     158           10 :                     outInstr.v2.loopGroup.startLoopInstrId = remapGlobal(origInstr.v2.loopGroup.startLoopInstrId);
     159              :                 }
     160              :             }
     161           21 :         }
     162              : 
     163              :     } // namespace
     164              : 
     165           21 :     CcuRep::CcuInstrInfo InstructionScheduler::Schedule(const CcuRep::CcuInstrInfo& input)
     166              :     {
     167           21 :         return ScheduleCkeOnly(input);
     168              :     }
     169              : 
     170              :     // CkeOnly 默认档: 保持原序, 只对 CKE 寄存器的写后读 (某条 setcke 写 CKE, 之后 waitcke/
     171              :     // clearcke 读同一 CKE) 按固定 cke latency 补 NOP; XN / MS 写后读交由硬件 interlock, 不补
     172              :     // 任何 NOP. 每个 CKE 读者最多补 (L-1) 条 NOP, 与"每个 wait 类 rep 预留 L 条"精确对齐.
     173           21 :     CcuRep::CcuInstrInfo InstructionScheduler::ScheduleCkeOnly(const CcuRep::CcuInstrInfo& input)
     174              :     {
     175           21 :         stats_ = {};
     176           21 :         const auto& origVec = input.instrVec;
     177           21 :         const size_t instrCount = origVec.size();
     178              : 
     179           21 :         std::vector<CcuRep::CcuInstr> outVec;
     180           21 :         std::vector<int32_t> origToOut;
     181           21 :         outVec.reserve(instrCount);
     182           21 :         origToOut.assign(instrCount, -1);
     183              : 
     184              :         // 只跟踪 CKE 写者的发射 cycle; 不做启发式放大, 保证补 NOP 有界.
     185              :         // 索引用 size_t 与 vector::size() 对齐, 避免 instrCount 逼近 65535 时 uint16_t 回绕死循环;
     186              :         // 输出条数是否越界预留区由上游 TransRepSequenceToMicrocode 按 instrVec.size() 快速失败兜底.
     187           21 :         CkeOnlyState state{outVec, origToOut, stats_};
     188          785 :         for (size_t i = 0; i < instrCount; ++i) {
     189          764 :             ScheduleOneCkeInstr(state, origVec[i], i);
     190              :         }
     191              : 
     192              :         // CkeOnly 不做 BB 切分, 用 1 作为占位 (仅统计意义).
     193           21 :         stats_.basicBlocks = instrCount > 0 ? 1 : 0;
     194              : 
     195           21 :         CcuRep::CcuInstrInfo out;
     196           21 :         out.instrVec = std::move(outVec);
     197           21 :         out.startInstrId = input.startInstrId;
     198              : 
     199              :         // instrCount 字段为 uint16_t. 正常路径下上游按 CKE 预留区申请, 优化后条数远小于 65535;
     200              :         // 但一旦补 NOP 后输出条数超过 uint16_t 上限, 直接截断会让 instrCount 与真实 instrVec 大小
     201              :         // 不一致, 进而使 FixReferences 的引用重映射错位. 此处显式记录错误再截断, 把静默数据损坏
     202              :         // 变成可观测告警; instrVec 保留完整大小, 由上游 TransRepSequenceToMicrocode 按
     203              :         // instrVec.size() > regionSize 快速失败兜底 (见 ccu_kernel_mgr.cc).
     204           21 :         constexpr size_t kMaxInstrCount = std::numeric_limits<uint16_t>::max();
     205           21 :         if (out.instrVec.size() > kMaxInstrCount) {
     206            0 :             HCCL_ERROR(
     207              :                 "[InstructionScheduler] optimized instr count[%zu] exceeds uint16_t range[%zu]; "
     208              :                 "instrCount field will be truncated, upstream region-size check will reject it.",
     209              :                 out.instrVec.size(), kMaxInstrCount);
     210              :         }
     211           21 :         out.instrCount = static_cast<uint16_t>(out.instrVec.size());
     212              : 
     213           21 :         FixReferences(input, origToOut, out);
     214              : 
     215           21 :         return out;
     216           42 :     }
     217              : 
     218              : } // namespace CcuOpt
     219              : } // namespace hcomm
        

Generated by: LCOV version 2.0-1