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: 88.1 % 252 222
Test Date: 2026-08-29 17:38:31 Functions: 100.0 % 23 23

            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           56 :         inline uint32_t RegKey(const RegOperand& operand)
      26              :         {
      27           56 :             return (static_cast<uint32_t>(operand.type) << 16) | static_cast<uint32_t>(operand.regId);
      28              :         }
      29              : 
      30          155 :         inline CcuRep::CcuInstr MakeNop()
      31              :         {
      32              :             // CcuInstr 为 POD (header + union, 无非平凡成员), {} 值初始化已将全部字节零化,
      33              :             // 无需再 memset_s; 仅设置 NOP 的 header 即可.
      34          155 :             CcuRep::CcuInstr nopInstr{};
      35          155 :             nopInstr.header = CcuRep::InstrHeader(InstrCodeV2::LOAD_TYPE, InstrCodeV2::NOP_CODE);
      36          155 :             return nopInstr;
      37              :         }
      38              : 
      39              :         // RelJmp 模板内部使用的固定常量 (与 ccu_microcode_v2.cc 同源, 见 RelJmp 生成器):
      40              :         //   两条内部 Jump 的固定跳距 (IF 分支跳 5 / NOP 分支跳 3), 及两种 conditionType.
      41              :         namespace RelJmpConst {
      42              :             constexpr uint16_t JMP_TARGET_PC_IF_BRANCH = 5;  // P+1 LoadImdToXn(xn1, 5)
      43              :             constexpr uint16_t JMP_TARGET_PC_NOP_BRANCH = 3; // P+5 LoadImdToXn(xn1, 3)
      44              :             constexpr uint16_t JMP_COND_TYPE_IF = 2;         // P+2 换算 Jump 的 conditionType
      45              :             constexpr uint16_t JMP_COND_TYPE_UNCOND = 6;     // P+6 无条件 Jump 的 conditionType
      46              :             constexpr int TEMPLATE_LEN = 9;                  // RelJmp 展开固定 9 条
      47              :         } // namespace RelJmpConst
      48              : 
      49              :         // RelJmp 模板解析结果. matched=true 时各下标字段有效.
      50              :         struct RelJmpMatch {
      51              :             bool matched = false;
      52              :             size_t p0 = 0;         // 块起始 (LoadImdToXn(xn0, jmpInstrId))
      53              :             size_t innerJmp = 0;   // P+2 换算 Jump
      54              :             size_t p3 = 0;         // P+3 LoadImdToXn(xn0, 0x10000 - jmpInstrId)
      55              :             uint16_t xn0 = 0;      // jmpInstrId 基准寄存器
      56              :             uint16_t xn1 = 0;      // 模板内固定跳距寄存器
      57              :             uint16_t targetXn = 0; // 目标绝对 id 寄存器 (被 Add/Sub 换算)
      58              :         };
      59              : 
      60           34 :         inline bool IsLoadImd(const CcuRep::CcuInstr& instr)
      61              :         {
      62           34 :             return instr.header.type == InstrCodeV2::LOAD_TYPE && instr.header.code == InstrCodeV2::LOADIMDTOX_CODE;
      63              :         }
      64          932 :         inline bool IsCtrlJmp(const CcuRep::CcuInstr& instr)
      65              :         {
      66          932 :             return instr.header.type == InstrCodeV2::CTRL_TYPE && instr.header.code == InstrCodeV2::JMP_CODE;
      67              :         }
      68           16 :         inline bool IsArith(const CcuRep::CcuInstr& instr, uint16_t code)
      69              :         {
      70           16 :             return instr.header.type == InstrCodeV2::LOAD_TYPE && instr.header.code == code;
      71              :         }
      72              : 
      73              :         // RelJmp 模板寄存器角色: 由 P+2 换算 Jump 解出的三个寄存器 id, 供逐条校验共享.
      74              :         struct RelJmpRegs {
      75              :             uint16_t xn0 = 0;      // jmpInstrId 基准寄存器
      76              :             uint16_t xn1 = 0;      // 模板内固定跳距寄存器
      77              :             uint16_t targetXn = 0; // 目标绝对 id 寄存器
      78              :         };
      79              : 
      80              :         // P+4 ADD / P+7 SUB 共享校验: 算术码 + xd/xn==targetXn + xm==xn0.
      81           16 :         inline bool IsRelJmpArith(const CcuRep::CcuInstr& instr, uint16_t code, const RelJmpRegs& regs)
      82              :         {
      83           32 :             return IsArith(instr, code) && instr.v2.operate.xdId == regs.targetXn
      84           32 :                    && instr.v2.operate.xnId == regs.targetXn && instr.v2.operate.xmId == regs.xn0;
      85              :         }
      86              : 
      87              :         // 校验 P+0..P+1 (P+2 前两条): P+1 LoadImdToXn(xn1, 5), P+0 LoadImdToXn(xn0, *).
      88              :         inline bool
      89           10 :         CheckRelJmpHead(const std::vector<CcuRep::CcuInstr>& vec, size_t innerJmpIdx, const RelJmpRegs& regs)
      90              :         {
      91              :             using namespace RelJmpConst;
      92           10 :             const auto& p1 = vec[innerJmpIdx - 1];
      93           10 :             const auto& p0 = vec[innerJmpIdx - 2];
      94           20 :             if (!IsLoadImd(p1) || p1.v2.loadImdToX.xnId != regs.xn1
      95           20 :                 || p1.v2.loadImdToX.immediate != JMP_TARGET_PC_IF_BRANCH) {
      96            2 :                 return false;
      97              :             }
      98            8 :             return IsLoadImd(p0) && p0.v2.loadImdToX.xnId == regs.xn0;
      99              :         }
     100              : 
     101              :         // 校验 P+3..P+7 (P+2 后五条): P+3 LoadImd(xn0), P+4 Add, P+5 LoadImd(xn1,3), P+6 UNCOND Jump, P+7 Sub.
     102              :         inline bool
     103            8 :         CheckRelJmpTail(const std::vector<CcuRep::CcuInstr>& vec, size_t innerJmpIdx, const RelJmpRegs& regs)
     104              :         {
     105              :             using namespace InstrCodeV2;
     106              :             using namespace RelJmpConst;
     107            8 :             const auto& p3 = vec[innerJmpIdx + 1];
     108            8 :             const auto& p4 = vec[innerJmpIdx + 2];
     109            8 :             const auto& p5 = vec[innerJmpIdx + 3];
     110            8 :             const auto& p6 = vec[innerJmpIdx + 4];
     111            8 :             const auto& p7 = vec[innerJmpIdx + 5];
     112            8 :             if (!IsLoadImd(p3) || p3.v2.loadImdToX.xnId != regs.xn0) {
     113            0 :                 return false;
     114              :             }
     115            8 :             if (!IsRelJmpArith(p4, ADD_CODE, regs)) {
     116            0 :                 return false;
     117              :             }
     118           16 :             if (!IsLoadImd(p5) || p5.v2.loadImdToX.xnId != regs.xn1
     119           16 :                 || p5.v2.loadImdToX.immediate != JMP_TARGET_PC_NOP_BRANCH) {
     120            0 :                 return false;
     121              :             }
     122           16 :             if (!IsCtrlJmp(p6) || p6.v2.jmp.conditionType != JMP_COND_TYPE_UNCOND
     123           16 :                 || p6.v2.jmp.relTarInstrXnId != regs.xn1) {
     124            0 :                 return false;
     125              :             }
     126            8 :             return IsRelJmpArith(p7, SUB_CODE, regs);
     127              :         }
     128              : 
     129              :         // 强指纹校验: 以 innerJmpIdx (候选 P+2 换算 Jump) 为锚, 校验其所在的 9 条是否构成完整 RelJmp
     130              :         // 模板. 校验点 (任一不满足即判否), 互锁性极强, 普通条件跳转不可能全中:
     131              :         //   P+2 vec[i]      : CTRL/JMP, conditionType==IF(2); 取 xn1=relTar, targetXn=condition, xn0=expected
     132              :         //   P+1 vec[i-1]    : LOADIMDTOX, xnId==xn1, immediate==5 (IF 分支固定跳距)
     133              :         //   P+0 vec[i-2]    : LOADIMDTOX, xnId==xn0
     134              :         //   P+3 vec[i+1]    : LOADIMDTOX, xnId==xn0 (immediate 应为 0x10000 - P+0.immediate)
     135              :         //   P+4 vec[i+2]    : ADD, xdId==targetXn, xnId==targetXn, xmId==xn0
     136              :         //   P+5 vec[i+3]    : LOADIMDTOX, xnId==xn1, immediate==3 (NOP 分支固定跳距)
     137              :         //   P+6 vec[i+4]    : CTRL/JMP, conditionType==UNCOND(6), relTar==xn1
     138              :         //   P+7 vec[i+5]    : SUB, xdId==targetXn, xnId==targetXn, xmId==xn0
     139              :         // 普通条件跳转 (EQ/NE/GT/... + 用户 expected/condition) 因固定跳距 5/3、成对 Add/Sub、配套第二跳
     140              :         // 缺一即被排除, 从根本上杜绝把 "expected 比较值 load" 误判为 "jmpInstrId 基准 load".
     141          108 :         RelJmpMatch MatchRelJmpTemplate(const std::vector<CcuRep::CcuInstr>& vec, size_t innerJmpIdx)
     142              :         {
     143              :             using namespace RelJmpConst;
     144          108 :             RelJmpMatch match;
     145              : 
     146              :             // 边界: innerJmp 至少是 P+2, 其后还需 P+3..P+7 (5 条).
     147          108 :             if (innerJmpIdx < 2 || innerJmpIdx + 5 >= vec.size()) {
     148           18 :                 return match;
     149              :             }
     150           90 :             const auto& jmp = vec[innerJmpIdx];
     151           90 :             if (!IsCtrlJmp(jmp) || jmp.v2.jmp.conditionType != JMP_COND_TYPE_IF) {
     152           80 :                 return match;
     153              :             }
     154           10 :             const RelJmpRegs regs{jmp.v2.jmp.expectedXnId, jmp.v2.jmp.relTarInstrXnId, jmp.v2.jmp.conditionXnId};
     155              : 
     156           10 :             if (!CheckRelJmpHead(vec, innerJmpIdx, regs) || !CheckRelJmpTail(vec, innerJmpIdx, regs)) {
     157            2 :                 return match;
     158              :             }
     159              : 
     160            8 :             match.matched = true;
     161            8 :             match.p0 = innerJmpIdx - 2;
     162            8 :             match.innerJmp = innerJmpIdx;
     163            8 :             match.p3 = innerJmpIdx + 1;
     164            8 :             match.xn0 = regs.xn0;
     165            8 :             match.xn1 = regs.xn1;
     166            8 :             match.targetXn = regs.targetXn;
     167            8 :             return match;
     168              :         }
     169              : 
     170              :         // 识别 RelJmp 原子块 (func-call / func-ret 运行期地址跳转), 返回逐指令保护掩码.
     171              :         // 用 MatchRelJmpTemplate 强指纹匹配: 命中的块为 [P+0, P+8] 共 9 条, 全部标记为块内禁止插 NOP
     172              :         // (块内固定跳距 5/3 与 P+2 相对 P+0 的换算关系依赖块内不被撕裂). 目标区间的距离修正由
     173              :         // FixRelJmpFunc 在 FixReferences 阶段完成, 不在此保护.
     174           29 :         std::vector<bool> MarkRelJmpProtectedRanges(const std::vector<CcuRep::CcuInstr>& vec)
     175              :         {
     176              :             using namespace RelJmpConst;
     177           29 :             const size_t count = vec.size();
     178           29 :             std::vector<bool> mask(count, false);
     179          863 :             for (size_t i = 0; i < count; ++i) {
     180          834 :                 if (!IsCtrlJmp(vec[i])) {
     181          830 :                     continue;
     182              :                 }
     183           54 :                 RelJmpMatch match = MatchRelJmpTemplate(vec, i);
     184           54 :                 if (!match.matched) {
     185           50 :                     continue;
     186              :                 }
     187              :                 // 块 = [P+0, P+8] = [match.p0, match.p0 + 8].
     188            4 :                 const size_t blockEnd = match.p0 + static_cast<size_t>(TEMPLATE_LEN) - 1;
     189           40 :                 for (size_t k = match.p0; k <= blockEnd && k < count; ++k) {
     190           36 :                     mask[k] = true;
     191              :                 }
     192              :             }
     193           29 :             return mask;
     194            0 :         }
     195              : 
     196              :         // CkeOnly 顺序调度的可变状态: 输出序列、原->输出映射、统计信息, 以及只跟踪 CKE 写者发射
     197              :         // cycle 的表. 不做启发式放大, 保证补 NOP 有界.
     198              :         struct CkeOnlyState {
     199              :             std::vector<CcuRep::CcuInstr>& outVec;
     200              :             std::vector<int32_t>& origToOut;
     201              :             SchedulerStats& stats;
     202              :             const std::vector<bool>& relJmpProtected; // 逐指令保护掩码: true 表示属于 RelJmp 原子块, 块内禁止插 NOP.
     203              :             std::unordered_map<uint32_t, int64_t> lastCkeWriterCycle{};
     204              :             int64_t cycle = 0;
     205              :         };
     206              : 
     207          155 :         inline void EmitNop(CkeOnlyState& state)
     208              :         {
     209          155 :             state.outVec.push_back(MakeNop());
     210          155 :             state.stats.originIndex.push_back(-1); // 无对应源.
     211          155 :             state.stats.nopInserted++;
     212          155 :             state.cycle++;
     213          155 :         }
     214              : 
     215              :         // 计算当前指令为满足 CKE 写后读 latency 所需的最早发射 cycle.
     216          798 :         inline int64_t EarliestCkeIssueCycle(const CkeOnlyState& state, const std::vector<RegOperand>& operands)
     217              :         {
     218          798 :             const int64_t ckeLatency = static_cast<int64_t>(CcuRep::CCU_CKE_RAW_LATENCY);
     219          798 :             int64_t earliest = state.cycle;
     220         1842 :             for (const auto& operand : operands) {
     221         1044 :                 if (operand.isDef || operand.type != RegType::CKE) {
     222         1030 :                     continue;
     223              :                 }
     224           28 :                 auto it = state.lastCkeWriterCycle.find(RegKey(operand));
     225           28 :                 if (it == state.lastCkeWriterCycle.end()) {
     226           14 :                     continue;
     227              :                 }
     228           14 :                 int64_t needed = it->second + ckeLatency;
     229           14 :                 if (needed > earliest) {
     230           13 :                     earliest = needed;
     231              :                 }
     232              :             }
     233          798 :             return earliest;
     234              :         }
     235              : 
     236              :         // 处理单条指令: 先补齐 latency NOP, 再原序发射, 最后记录 CKE 写者的发射 cycle.
     237          834 :         void ScheduleOneCkeInstr(CkeOnlyState& state, const CcuRep::CcuInstr& instr, size_t originIdx)
     238              :         {
     239          834 :             auto operands = ExtractOperandsV2(instr);
     240              : 
     241              :             // RelJmp 原子块内禁止插 NOP: 块内指令(Load/Jump/Add/Sub/Nop)不产生 CKE 写者, 也不含 CKE 读者,
     242              :             // earliestIssueCycle 恒等于当前 cycle, 正常路径本就不会补 NOP; 这里显式跳过补 NOP 是防御, 确保
     243              :             // 即使块紧邻的 CKE 写者仍有残余 latency 需求, 也不会把 NOP 插进/插到块中间破坏运行期地址链.
     244          834 :             const bool isProtected = originIdx < state.relJmpProtected.size() && state.relJmpProtected[originIdx];
     245          834 :             if (!isProtected) {
     246          798 :                 int64_t earliestIssueCycle = EarliestCkeIssueCycle(state, operands);
     247          953 :                 while (state.cycle < earliestIssueCycle) {
     248          155 :                     EmitNop(state);
     249              :                 }
     250              :             }
     251              : 
     252          834 :             state.origToOut[originIdx] = static_cast<int32_t>(state.outVec.size());
     253          834 :             state.outVec.push_back(instr);
     254          834 :             state.stats.originIndex.push_back(static_cast<int32_t>(originIdx)); // CkeOnly 顺序保持.
     255          834 :             int64_t issueCycle = state.cycle;
     256          834 :             state.cycle++;
     257              : 
     258         1942 :             for (const auto& operand : operands) {
     259         1108 :                 if (!operand.isDef || operand.type != RegType::CKE) {
     260         1080 :                     continue;
     261              :                 }
     262           28 :                 state.lastCkeWriterCycle[RegKey(operand)] = issueCycle;
     263              :             }
     264          834 :         }
     265              : 
     266              :         // 依据已确定的 out.missionStartInstrId 重新推导 missionInstrCount:
     267              :         // mission 起点落在输出序列内则取到序列尾部的长度, 否则计 0.
     268           29 :         inline void RecomputeMissionCount(CcuRep::CcuInstrInfo& out, uint16_t startId)
     269              :         {
     270           29 :             if (static_cast<uint32_t>(out.missionStartInstrId)
     271           29 :                 < static_cast<uint32_t>(startId) + static_cast<uint32_t>(out.instrCount)) {
     272           29 :                 out.missionInstrCount = static_cast<uint16_t>(startId + out.instrCount - out.missionStartInstrId);
     273              :             } else {
     274            0 :                 out.missionInstrCount = 0;
     275              :             }
     276           29 :         }
     277              : 
     278              :         // 与 GetRelativeInstrId / jump_executor 的回绕空间一致. 用 uint64_t 承载, 使所有涉及 immediate
     279              :         // (字段本身为 uint64_t, 可存完整 64 位业务立即数) 的读取/运算全程 64 位, 杜绝中间隐式截断.
     280              :         constexpr uint64_t kInstrIdSpace = 0x10000ULL;
     281              : 
     282              :         // 普通相对跳转 offset 修正.
     283              :         //
     284              :         // 背景: v2 的 jmp 目标不是直接写在 jmp 指令里的绝对 instrId, 而是"相对距离":
     285              :         // 生成端在紧邻 jmp 之前用一条 LoadImdToXn 把 offset = target - jmpPC 加载进 relTarInstrXnId,
     286              :         // 硬件按 nextInsIdx = jmpPC + offset (mod 0x10000) 跳转 (见 jump_executor.cc 相对跳转分支).
     287              :         // 因此只要在 jmp 与目标之间净插入了 k 条 NOP, 真实相对距离就变了, 而 offset 立即数是编译期
     288              :         // 写死的, 不修正会跳错. Loop/LoopGroup 用绝对 id 靠 remapGlobal 平移, jmp 则必须改写 offset.
     289              :         // 向前找最近一条写 tgtXn 的指令: 命中 LoadImdToXn 返回其原始下标; 命中算术等其它写者则告警并
     290              :         // 返回 -1 (放弃修正); 找不到任何写者也返回 -1 (保守不动).
     291           46 :         int32_t FindOffsetLoaderOrigIdx(const std::vector<CcuRep::CcuInstr>& origVec, size_t jmpOrigIdx, uint16_t tgtXn)
     292              :         {
     293              :             using namespace InstrCodeV2;
     294           48 :             for (int32_t j = static_cast<int32_t>(jmpOrigIdx) - 1; j >= 0; --j) {
     295           48 :                 const auto& cur = origVec[j];
     296           48 :                 if (cur.header.type == LOAD_TYPE && cur.header.code == LOADIMDTOX_CODE
     297           44 :                     && cur.v2.loadImdToX.xnId == tgtXn) {
     298           44 :                     return j;
     299              :                 }
     300            4 :                 for (const auto& op : ExtractOperandsV2(cur)) {
     301            2 :                     if (op.isDef && op.type == RegType::XN && op.regId == tgtXn) {
     302            2 :                         HCCL_WARNING(
     303              :                             "[InstructionScheduler] jmp@%zu target reg X%u overwritten by non-imm instr@%d; "
     304              :                             "skip relative-offset fix.",
     305              :                             jmpOrigIdx, static_cast<unsigned>(tgtXn), j);
     306            2 :                         return -1;
     307              :                     }
     308            4 :                 }
     309              :             }
     310            0 :             return -1; // 没找到立即数来源, 保守不动.
     311              :         }
     312              : 
     313           46 :         void FixPlainJumpOffset(
     314              :             const std::vector<CcuRep::CcuInstr>& origVec, const std::vector<int32_t>& origToOut, size_t jmpOrigIdx,
     315              :             std::vector<CcuRep::CcuInstr>& outVec)
     316              :         {
     317           46 :             const uint16_t tgtXn = origVec[jmpOrigIdx].v2.jmp.relTarInstrXnId;
     318              : 
     319           46 :             const int32_t loadOrigIdx = FindOffsetLoaderOrigIdx(origVec, jmpOrigIdx, tgtXn);
     320           46 :             if (loadOrigIdx < 0) {
     321            2 :                 return;
     322              :             }
     323              : 
     324           44 :             const int32_t loadOutPos = origToOut[loadOrigIdx];
     325           44 :             const int32_t jmpOutPos = origToOut[jmpOrigIdx];
     326           44 :             if (loadOutPos < 0 || jmpOutPos < 0) {
     327            0 :                 return; // 理论上二者都保留 (CkeOnly 不删指令), 兜底防御.
     328              :             }
     329              : 
     330           44 :             const uint64_t oldOffset = outVec[loadOutPos].v2.loadImdToX.immediate;
     331              :             // 定位护栏 (非落点校验): offset 语义上是相对距离, 合法域即 [0, 0x10000). 向前扫描找 offset
     332              :             // loader 是启发式的, 若读到 >= 0x10000, 说明大概率误命中了往同一寄存器装 64 位业务数据的
     333              :             // load (而非真正的 offset loader). 此时改写会截断高位破坏业务数据 —— 放弃修正并告警.
     334              :             // 注: 这里判的是"偏移是否超出该字段合法域", 落点是否合法由下方 origTargetIdx 越界检查负责.
     335           44 :             if (oldOffset >= kInstrIdSpace) {
     336            0 :                 HCCL_WARNING(
     337              :                     "[InstructionScheduler] jmp@%zu offset-loader immediate %llu out of relative-offset range "
     338              :                     "[0,0x10000); likely mismatched a 64-bit value load, skip fix to avoid truncation.",
     339              :                     jmpOrigIdx, static_cast<unsigned long long>(oldOffset));
     340            0 :                 return;
     341              :             }
     342              :             // 旧 offset 基准是 jmp 原始位置, 反推原始目标下标 (回绕). 全程 uint64_t.
     343           44 :             const uint64_t origTargetIdx = (static_cast<uint64_t>(jmpOrigIdx) + oldOffset) % kInstrIdSpace;
     344           44 :             if (origTargetIdx >= origToOut.size() || origToOut[origTargetIdx] < 0) {
     345            0 :                 HCCL_WARNING(
     346              :                     "[InstructionScheduler] jmp@%zu old offset %llu points outside sequence (target idx %llu); "
     347              :                     "skip relative-offset fix.",
     348              :                     jmpOrigIdx, static_cast<unsigned long long>(oldOffset),
     349              :                     static_cast<unsigned long long>(origTargetIdx));
     350            0 :                 return;
     351              :             }
     352              : 
     353           44 :             const uint64_t newTargetPos = static_cast<uint64_t>(origToOut[origTargetIdx]);
     354           44 :             const uint64_t newJmpPos = static_cast<uint64_t>(jmpOutPos);
     355           44 :             const uint64_t newOffset = (newTargetPos + kInstrIdSpace - newJmpPos) % kInstrIdSpace;
     356           44 :             outVec[loadOutPos].v2.loadImdToX.immediate = newOffset;
     357              :         }
     358              : 
     359              :         // RelJmp (func-call / func-ret 运行期地址跳转) 修正.
     360              :         //
     361              :         // RelJmp 用绝对量表达跳转: 运行时寄存器换算值 = targetAbsId - jmpInstrId, 主 Jump (紧随 9 条模板
     362              :         // 之后) 按 nextPC = 主JumpPC + 换算值 跳转; 生成端令 jmpInstrId == 主JumpPC, 故最终 nextPC ==
     363              :         // targetAbsId. 插 NOP 后主 Jump 与目标各自位移, 二者相对距离改变, 必须把两个绝对量分别重映射:
     364              :         //   * jmpInstrId (RelJmp 模板 P+0 LoadImdToXn(xn0, jmpInstrId) 与 P+3 LoadImdToXn(xn0,
     365              :         //     0x10000 - jmpInstrId)): jmpInstrId == 主 Jump 旧 PC, 平移到主 Jump 新 PC (= remap(jmpInstrId)).
     366              :         //   * 目标绝对 id (由加载 targetXn 的 LoadImdToXn 提供): remap 到新位置; 若目标由 Add 加载
     367              :         //     (funcAddrVar 运行期变量, 外部绝对地址), 不受本段插 NOP 影响, 不动.
     368              :         // 块内 9 条由 MarkRelJmpProtectedRanges 保证不被 NOP 撕裂, 故模板内固定跳距 (5 / 3) 无需修改.
     369              :         //
     370              :         // 定位方式: P+0 / P+3 由已通过强指纹校验的 RelJmpMatch 按模板固定偏移直接给出 (match.p0 / match.p3),
     371              :         // 不再靠"解释 jmp 的 expectedXnId 字段 + 向前扫描"猜测 —— 后者在普通条件跳转上会误命中 expected
     372              :         // 比较值 load. 只有整段构成 RelJmp 模板才会走到这里, 故 match.p0 / match.p3 必为 jmpInstrId 基准 load.
     373              :         //
     374              :         // RelJmp 目标绝对 id 的 remap 改写: 命中"编译期常量绝对 id"的 LoadImdToXn 后, 越界告警 / 否则 remap 平移.
     375              :         // 从 FixRelJmpFunc 拆出, 消除 for -> if -> if -> if/else -> 赋值 的过深嵌套 (超大深度函数告警).
     376              :         //
     377              :         // 硬件约束: jmp 落点绝对 PC 必须落在指令空间 [0, 0x10000). 这里 rawTarget 是"目标绝对 instrId"
     378              :         // (由生成端 LoadImdToXn(targetXn, funcBlock->StartInstrId()/funcRet.Id()) 装入编译期常量绝对 id;
     379              :         // 运行期偏移由模板 P+4 Add(0x10000 - jmpInstrId) 现算, 不落在本立即数里). 故越界校验分两处:
     380              :         //   * 旧绝对 id (rawTarget): 强指纹已保证 < 0x10000, 越界说明数据异常, 放弃改写;
     381              :         //   * remap 后新绝对 id (newTarget): 插 NOP 后 PC 整体后移, 显式校验新落点仍在指令空间内.
     382              :         template <typename RemapFn>
     383            2 :         void RemapRelJmpTargetImmediate(
     384              :             const RelJmpMatch& match, int32_t outPos, std::vector<CcuRep::CcuInstr>& outVec, RemapFn remapId)
     385              :         {
     386            2 :             const uint64_t rawTarget = outVec[outPos].v2.loadImdToX.immediate;
     387            2 :             if (rawTarget >= kInstrIdSpace) {
     388            0 :                 HCCL_WARNING(
     389              :                     "[InstructionScheduler] RelJmp@%zu: target absolute id %llu exceeds instrId space; "
     390              :                     "skip target remap.",
     391              :                     match.innerJmp, static_cast<unsigned long long>(rawTarget));
     392            0 :                 return;
     393              :             }
     394            2 :             const uint64_t newTarget = static_cast<uint64_t>(remapId(static_cast<uint16_t>(rawTarget)));
     395            2 :             if (newTarget >= kInstrIdSpace) {
     396            0 :                 HCCL_WARNING(
     397              :                     "[InstructionScheduler] RelJmp@%zu: remapped target absolute id %llu exceeds instrId space "
     398              :                     "after NOP insertion; skip target remap.",
     399              :                     match.innerJmp, static_cast<unsigned long long>(newTarget));
     400            0 :                 return;
     401              :             }
     402            2 :             outVec[outPos].v2.loadImdToX.immediate = newTarget;
     403              :         }
     404              : 
     405              :         // RelJmp (func-call / func-ret 运行期地址跳转) 专用修正. 与普通相对跳转 (FixPlainJumpOffset)
     406              :         // 区分命名: 本函数处理的 P+0/target 立即数是"编译期常量绝对 instrId", 运行期相对偏移由模板
     407              :         // Add/Sub 现算; 普通相对跳转处理的是直接写死的相对偏移立即数.
     408              :         //
     409              :         // remapId: 把"旧全局 instrId"映射到"新全局 instrId"(与 FixReferences 的 remapGlobal 同语义).
     410              :         template <typename RemapFn>
     411            4 :         void FixRelJmpFunc(
     412              :             const std::vector<CcuRep::CcuInstr>& origVec, const std::vector<int32_t>& origToOut,
     413              :             const RelJmpMatch& match, std::vector<CcuRep::CcuInstr>& outVec, RemapFn remapId)
     414              :         {
     415              :             using namespace InstrCodeV2;
     416              : 
     417              :             // 1) 修 jmpInstrId 基准: P+0 (match.p0, 立即数 jmpInstrId) 与 P+3 (match.p3, 立即数 0x10000 - jmpInstrId).
     418            4 :             const int32_t p0Out = origToOut[match.p0];
     419            4 :             const int32_t p3Out = origToOut[match.p3];
     420            4 :             if (p0Out < 0 || p3Out < 0) {
     421            0 :                 return; // 块内不删指令, 兜底防御.
     422              :             }
     423            4 :             const uint64_t rawJmpInstrId = outVec[p0Out].v2.loadImdToX.immediate;
     424            4 :             if (rawJmpInstrId >= kInstrIdSpace) {
     425              :                 // jmpInstrId 是主 Jump 的绝对 instrId (< 0x10000). 强指纹已确保这是 RelJmp, 正常不会越界;
     426              :                 // 越界则说明数据异常, 放弃并告警, 不做可能截断的改写.
     427            0 :                 HCCL_WARNING(
     428              :                     "[InstructionScheduler] RelJmp@%zu: base absolute id %llu exceeds instrId space; skip fix.",
     429              :                     match.innerJmp, static_cast<unsigned long long>(rawJmpInstrId));
     430            0 :                 return;
     431              :             }
     432            4 :             const uint64_t newJmpInstrId = static_cast<uint64_t>(remapId(static_cast<uint16_t>(rawJmpInstrId)));
     433              :             // 硬件约束: 主 Jump 落点绝对 PC 必须落在指令空间内. 插 NOP 后 PC 整体后移, 显式校验新绝对
     434              :             // id 仍 < 0x10000, 越界则放弃改写 (避免写出会被硬件回绕到错误 PC 的基准值).
     435            4 :             if (newJmpInstrId >= kInstrIdSpace) {
     436            0 :                 HCCL_WARNING(
     437              :                     "[InstructionScheduler] RelJmp@%zu: remapped base absolute id %llu exceeds instrId space "
     438              :                     "after NOP insertion; skip fix.",
     439              :                     match.innerJmp, static_cast<unsigned long long>(newJmpInstrId));
     440            0 :                 return;
     441              :             }
     442            4 :             outVec[p0Out].v2.loadImdToX.immediate = newJmpInstrId;
     443            4 :             outVec[p3Out].v2.loadImdToX.immediate = kInstrIdSpace - newJmpInstrId;
     444              : 
     445              :             // 2) 修目标绝对 id: 向前找加载 targetXn 的指令 (在块之前, 位置不固定, 但 targetXn 由强指纹给出).
     446              :             //    LoadImdToXn(xnId==targetXn) -> 目标是编译期常量绝对 id, remap 平移;
     447              :             //    Add/Sub(xdId==targetXn)     -> 目标是运行期变量 (funcAddrVar, 外部绝对地址), 不动.
     448            5 :             for (int32_t j = static_cast<int32_t>(match.p0) - 1; j >= 0; --j) {
     449            3 :                 const auto& cur = origVec[j];
     450            3 :                 if (cur.header.type == LOAD_TYPE && cur.header.code == LOADIMDTOX_CODE
     451            2 :                     && cur.v2.loadImdToX.xnId == match.targetXn) {
     452            2 :                     if (origToOut[j] >= 0) {
     453            2 :                         RemapRelJmpTargetImmediate(match, origToOut[j], outVec, remapId);
     454              :                     }
     455            2 :                     break;
     456              :                 }
     457            1 :                 if (cur.header.type == LOAD_TYPE && (cur.header.code == ADD_CODE || cur.header.code == SUB_CODE)
     458            0 :                     && cur.v2.operate.xdId == match.targetXn) {
     459              :                     // 目标为运行期变量 (外部绝对地址), 不随本段插 NOP 变化, 无需修正.
     460            0 :                     break;
     461              :                 }
     462              :             }
     463              :         }
     464              : 
     465              :         // 处理单条 JMP 指令的引用修正 (从 FixReferences 主循环拆出, 降低单函数体量与圈复杂度).
     466              :         template <typename RemapFn>
     467           54 :         void FixOneJumpReference(
     468              :             const CcuRep::CcuInstrInfo& input, const std::vector<int32_t>& origToOut,
     469              :             const std::vector<bool>& relJmpProtected, size_t originIdx, CcuRep::CcuInstrInfo& out, RemapFn remapGlobal)
     470              :         {
     471           54 :             const auto& origVec = input.instrVec;
     472           54 :             const auto& origInstr = origVec[originIdx];
     473           54 :             RelJmpMatch relJmp = MatchRelJmpTemplate(origVec, originIdx);
     474           54 :             if (origInstr.v2.jmp.jumpMode != 0) {
     475              :                 // 绝对跳转 (jumpMode == 1): 目标是绝对 instrId, 需按绝对 id 重映射, 与相对跳转不同.
     476              :                 // 当前生成端从不产生绝对跳转 (CcuV2::Jump 恒留 jumpMode=0), 不做猜测性改写, 显式告警.
     477            0 :                 HCCL_ERROR(
     478              :                     "[InstructionScheduler] jmp@%zu is absolute (jumpMode=1); unsupported, jump target may be "
     479              :                     "wrong after NOP insertion.",
     480              :                     originIdx);
     481           54 :             } else if (relJmp.matched) {
     482              :                 // RelJmp 换算 Jump (P+2, 强指纹命中): 目标用绝对量表达, 按模板固定偏移修 jmpInstrId
     483              :                 // 基准 (P+0/P+3) + 目标绝对 id, 不触碰任何 expected/condition 业务 load.
     484            4 :                 FixRelJmpFunc(origVec, origToOut, relJmp, out.instrVec, remapGlobal);
     485           50 :             } else if (originIdx < relJmpProtected.size() && relJmpProtected[originIdx]) {
     486              :                 // RelJmp 模板内的其它 jmp (P+6 无条件跳): 跳距是模板内固定常量, 块内不插 NOP, 不改.
     487              :             } else {
     488              :                 // 普通相对跳转: 改写其前置 LoadImdToXn 的 offset 立即数, 而非 jmp 指令本身.
     489           46 :                 FixPlainJumpOffset(origVec, origToOut, originIdx, out.instrVec);
     490              :             }
     491           54 :         }
     492              : 
     493              :         // 顺序扫描后处理: 修正 missionStartInstrId / missionInstrCount 与 Loop / LoopGroup 引用.
     494              :         // CkeOnly 只在原序上插入 NOP, 不重排, 故按 origToOut 平移引用即可.
     495           29 :         void FixReferences(
     496              :             const CcuRep::CcuInstrInfo& input, const std::vector<int32_t>& origToOut,
     497              :             const std::vector<bool>& relJmpProtected, CcuRep::CcuInstrInfo& out)
     498              :         {
     499              :             using namespace InstrCodeV2;
     500           29 :             const auto& origVec = input.instrVec;
     501           29 :             const size_t instrCount = origVec.size();
     502           29 :             const uint16_t startId = input.startInstrId;
     503              : 
     504           85 :             auto remapGlobal = [&](uint16_t globalId) -> uint16_t {
     505              :                 // globalId 是"startId + localId" 编码的全局 id, 越界或未映射则原样返回.
     506           85 :                 if (globalId < startId)
     507            0 :                     return globalId;
     508           85 :                 uint32_t localId = static_cast<uint32_t>(globalId) - static_cast<uint32_t>(startId);
     509           85 :                 if (localId >= origToOut.size())
     510            2 :                     return globalId;
     511           83 :                 int32_t newPos = origToOut[localId];
     512           83 :                 if (newPos < 0)
     513            0 :                     return globalId;
     514           83 :                 return static_cast<uint16_t>(startId + newPos);
     515           29 :             };
     516              : 
     517              :             // missionStartInstrId 修正: 若 mission 起点原本在本序列范围内, 映射到新的位置;
     518              :             // missionInstrCount 用序列尾部长度重新推导.
     519           29 :             if (input.missionStartInstrId >= startId
     520           29 :                 && static_cast<uint32_t>(input.missionStartInstrId) < static_cast<uint32_t>(startId) + instrCount) {
     521           29 :                 out.missionStartInstrId = remapGlobal(input.missionStartInstrId);
     522           29 :                 RecomputeMissionCount(out, startId);
     523              :             } else {
     524            0 :                 out.missionStartInstrId = input.missionStartInstrId;
     525            0 :                 out.missionInstrCount = input.missionInstrCount;
     526              :             }
     527              : 
     528          863 :             for (size_t originIdx = 0; originIdx < instrCount; ++originIdx) {
     529          834 :                 const auto& origInstr = origVec[originIdx];
     530          834 :                 int32_t outPos = origToOut[originIdx];
     531          834 :                 if (outPos < 0)
     532            0 :                     continue;
     533          834 :                 auto& outInstr = out.instrVec[outPos];
     534          834 :                 if (origInstr.header.type == CTRL_TYPE && origInstr.header.code == LOOP_CODE) {
     535           20 :                     outInstr.v2.loop.startInstrId = remapGlobal(origInstr.v2.loop.startInstrId);
     536           20 :                     outInstr.v2.loop.endInstrId = remapGlobal(origInstr.v2.loop.endInstrId);
     537          814 :                 } else if (origInstr.header.type == CTRL_TYPE && origInstr.header.code == LOOPGROUP_CODE) {
     538           10 :                     outInstr.v2.loopGroup.startLoopInstrId = remapGlobal(origInstr.v2.loopGroup.startLoopInstrId);
     539          804 :                 } else if (origInstr.header.type == CTRL_TYPE && origInstr.header.code == JMP_CODE) {
     540           54 :                     FixOneJumpReference(input, origToOut, relJmpProtected, originIdx, out, remapGlobal);
     541              :                 }
     542              :             }
     543           29 :         }
     544              : 
     545              :     } // namespace
     546              : 
     547           29 :     CcuRep::CcuInstrInfo InstructionScheduler::Schedule(const CcuRep::CcuInstrInfo& input)
     548              :     {
     549           29 :         return ScheduleCkeOnly(input);
     550              :     }
     551              : 
     552              :     // CkeOnly 默认档: 保持原序, 只对 CKE 寄存器的写后读 (某条 setcke 写 CKE, 之后 waitcke/
     553              :     // clearcke 读同一 CKE) 按固定 cke latency 补 NOP; XN / MS 写后读交由硬件 interlock, 不补
     554              :     // 任何 NOP. 每个 CKE 读者最多补 (L-1) 条 NOP, 与"每个 wait 类 rep 预留 L 条"精确对齐.
     555           29 :     CcuRep::CcuInstrInfo InstructionScheduler::ScheduleCkeOnly(const CcuRep::CcuInstrInfo& input)
     556              :     {
     557           29 :         stats_ = {};
     558           29 :         const auto& origVec = input.instrVec;
     559           29 :         const size_t instrCount = origVec.size();
     560              : 
     561           29 :         std::vector<CcuRep::CcuInstr> outVec;
     562           29 :         std::vector<int32_t> origToOut;
     563           29 :         outVec.reserve(instrCount);
     564           29 :         origToOut.assign(instrCount, -1);
     565              : 
     566              :         // 预扫描识别 RelJmp 原子块 (func-call / func-ret 运行期地址跳转), 块内禁止插 NOP.
     567           29 :         const std::vector<bool> relJmpProtected = MarkRelJmpProtectedRanges(origVec);
     568              : 
     569              :         // 只跟踪 CKE 写者的发射 cycle; 不做启发式放大, 保证补 NOP 有界.
     570              :         // 索引用 size_t 与 vector::size() 对齐, 避免 instrCount 逼近 65535 时 uint16_t 回绕死循环;
     571              :         // 输出条数是否越界预留区由上游 TransRepSequenceToMicrocode 按 instrVec.size() 快速失败兜底.
     572           29 :         CkeOnlyState state{outVec, origToOut, stats_, relJmpProtected};
     573          863 :         for (size_t i = 0; i < instrCount; ++i) {
     574          834 :             ScheduleOneCkeInstr(state, origVec[i], i);
     575              :         }
     576              : 
     577              :         // CkeOnly 不做 BB 切分, 用 1 作为占位 (仅统计意义).
     578           29 :         stats_.basicBlocks = instrCount > 0 ? 1 : 0;
     579              : 
     580           29 :         CcuRep::CcuInstrInfo out;
     581           29 :         out.instrVec = std::move(outVec);
     582           29 :         out.startInstrId = input.startInstrId;
     583              : 
     584              :         // instrCount 字段为 uint16_t. 正常路径下上游按 CKE 预留区申请, 优化后条数远小于 65535;
     585              :         // 但一旦补 NOP 后输出条数超过 uint16_t 上限, 直接截断会让 instrCount 与真实 instrVec 大小
     586              :         // 不一致, 进而使 FixReferences 的引用重映射错位. 此处显式记录错误再截断, 把静默数据损坏
     587              :         // 变成可观测告警; instrVec 保留完整大小, 由上游 TransRepSequenceToMicrocode 按
     588              :         // instrVec.size() > regionSize 快速失败兜底 (见 ccu_kernel_mgr.cc).
     589           29 :         constexpr size_t kMaxInstrCount = std::numeric_limits<uint16_t>::max();
     590           29 :         if (out.instrVec.size() > kMaxInstrCount) {
     591            0 :             HCCL_ERROR(
     592              :                 "[InstructionScheduler] optimized instr count[%zu] exceeds uint16_t range[%zu]; "
     593              :                 "instrCount field will be truncated, upstream region-size check will reject it.",
     594              :                 out.instrVec.size(), kMaxInstrCount);
     595              :         }
     596           29 :         out.instrCount = static_cast<uint16_t>(out.instrVec.size());
     597              : 
     598           29 :         FixReferences(input, origToOut, relJmpProtected, out);
     599              : 
     600           29 :         return out;
     601           58 :     }
     602              : 
     603              : } // namespace CcuOpt
     604              : } // namespace hcomm
        

Generated by: LCOV version 2.0-1