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
|