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
|