1 /*========================== begin_copyright_notice ============================
2
3 Copyright (C) 2017-2021 Intel Corporation
4
5 SPDX-License-Identifier: MIT
6
7 ============================= end_copyright_notice ===========================*/
8
9 #include "IRChecker.hpp"
10 #include "../../api/iga.h"
11
12 #include <sstream>
13
14 using namespace iga;
15
16
17 struct Checker {
18 ErrorHandler *m_errHandler;
19 const Model &m_model;
20
CheckerChecker21 Checker(const Model &model, ErrorHandler *err) : m_errHandler(err), m_model(model) { }
22
warningChecker23 void warning(const Loc &loc, const char *msg) {
24 m_errHandler->reportWarning(loc, msg);
25 }
errorChecker26 void error(const Loc &loc, const char *msg) {
27 m_errHandler->reportError(loc, msg);
28 }
29 };
30
31 struct LOCChecker : Checker {
32 Loc m_loc;
33
LOCCheckerLOCChecker34 LOCChecker(const Model &model, Loc loc, ErrorHandler *err)
35 : Checker(model, err), m_loc(loc)
36 { }
37
warningLOCChecker38 void warning(const char *msg) {
39 m_errHandler->reportWarning(m_loc, msg);
40 }
errorLOCChecker41 void error(const char *msg) {
42 m_errHandler->reportError(m_loc, msg);
43 }
44 };
45
46
47 #if 0
48 // TODO: remove if not used
49 struct RegisterRegioningChecker : Checker {
50 int execSize;
51
52 bool dstIsDirect;
53 int dstTypeSz;
54 int dstSubReg;
55 int dstRgnH;
56
57 int srcReg, srcSubReg;
58 int srcRgnV, srcRgnW, srcRgnH;
59 int srcTypeSz;
60
61 RegisterRegioningChecker(
62 ErrorHandler *err,
63 const Instruction &inst,
64 const Operand &dst,
65 const Operand &src)
66 : Checker(err)
67 {
68 }
69 void checkAll() {
70 }
71 };
72 #endif
73
74 struct SemanticChecker : LOCChecker {
75 const Instruction *m_inst;
76 uint32_t m_enabled_warnings;
77
SemanticCheckerSemanticChecker78 SemanticChecker(
79 const Model &model,
80 ErrorHandler *err,
81 uint32_t enabled_warnings)
82 : LOCChecker(model, Loc::INVALID, err)
83 , m_inst(nullptr)
84 , m_enabled_warnings(enabled_warnings)
85 { }
86
checkKernelSemanticChecker87 void checkKernel(const Kernel &k) {
88 for (auto b : k.getBlockList()) {
89 for (auto i : b->getInstList()) {
90 checkInstruction(*i);
91 }
92 }
93 }
94
checkInstructionSemanticChecker95 void checkInstruction(const Instruction &i) {
96 m_inst = &i;
97 m_loc = i.getLoc();
98 checkInstImpl(i);
99 m_inst = nullptr;
100 m_loc = Loc::INVALID;
101 }
102
checkInstImplSemanticChecker103 void checkInstImpl(const Instruction &i) {
104 if (i.getOpSpec().supportsDestination()) {
105 checkDst(i, i.getDestination());
106 }
107 int srcs = i.getSourceCount();
108 if (srcs > 0) {
109 checkSrc(i, 0);
110 }
111 if (srcs > 1) {
112 checkSrc(i, 1);
113 }
114 if (srcs > 2) {
115 checkSrc(i, 2);
116 }
117 }
118
checkDstSemanticChecker119 void checkDst(const Instruction &i, const Operand &op) {
120 switch (op.getKind()) {
121 case Operand::Kind::DIRECT:
122 // if (op.getDirRegName() == RegName::ARF_CE) {
123 // warning("register is not writable (except in SIP)");
124 // }
125 if ((m_enabled_warnings & IGA_WARNINGS_SCHED) &&
126 !i.hasInstOpt(InstOpt::SWITCH) &&
127 arfNeedsSwitch(op.getDirRegName()) &&
128 m_model.supportsHwDeps())
129 {
130 warning("destination register ARF access requires {Switch} ThreadCtrl");
131 }
132 break;
133 case Operand::Kind::MACRO:
134 if (!i.isMacro()) {
135 error("not a macro instruction");
136 }
137 break;
138 default:
139 break;
140 }
141
142 // conservatively check operands
143 if (m_enabled_warnings & IGA_WARNINGS_TYPES) {
144 auto ispec = i.getOpSpec();
145 switch (ispec.format) {
146 case OpSpec::BASIC_UNARY_REG:
147 case OpSpec::BASIC_UNARY_REGIMM:
148 checkOperandTypes(i);
149 break;
150 case OpSpec::BASIC_BINARY_REG_IMM:
151 case OpSpec::BASIC_BINARY_REG_REG:
152 case OpSpec::BASIC_BINARY_REG_REGIMM:
153 case OpSpec::MATH_BINARY_REG_REGIMM:
154 checkOperandTypes(i);
155 break;
156 case OpSpec::TERNARY_REGIMM_REG_REGIMM:
157 checkOperandTypes(i);
158 break;
159
160 // punt on anything harder
161 default:
162 break;
163 }
164 }
165 }
166
checkOperandTypesSemanticChecker167 void checkOperandTypes(const Instruction &i) {
168 auto ispec = i.getOpSpec();
169 for (
170 size_t ti = 0;
171 ti < sizeof(ispec.typeMappings)/sizeof(ispec.typeMappings[0]);
172 ti++)
173 {
174 // type mappings are a zero-padded list of enum bitset pairs
175 // the raw bits are stored in statically allocated structures
176 // we construct them as EnumBitset<Type,uint32_t> and look
177 // for a pair that matches all the source and destination
178 // operand pairs
179 auto tm = ispec.typeMappings[ti];
180 const auto dsts = EnumBitset<Type>(tm.dsts);
181 if (tm.dsts == 0) { // end of array (null dst)
182 break;
183 }
184 if (!dsts.contains(i.getDestination().getType())) {
185 // destination type mismatch, try the next destination
186 continue;
187 }
188 const auto srcs = EnumBitset<Type>(tm.srcs);
189 for (size_t s_ix = 0; s_ix < i.getSourceCount(); s_ix++) {
190 if (srcs.contains(i.getSource(s_ix).getType())) {
191 // we found a valid mapping
192 return;
193 }
194
195 // WORKAROUNDS for busted BXML
196 // TODO: fix these in the BXML
197 // after they are all fixed, we can freshen BXML
198 auto t = i.getSource(s_ix).getType();
199 if (t == Type::UV || t == Type::V || t == Type::VF) {
200 return;
201 }
202 }
203 }
204 // we ran through all the type mappings without finding a match
205 // throw a fit
206 warning("invalid operand type combination for instruction");
207 }
208
checkSrcSemanticChecker209 void checkSrc(const Instruction &i, int srcIx) {
210 const Operand &src = i.getSource(srcIx);
211 const OpSpec &instSpec = i.getOpSpec();
212 if ((m_enabled_warnings & IGA_WARNINGS_REGIONS) &&
213 instSpec.supportsDestination() &&
214 !instSpec.isSendOrSendsFamily())
215 {
216 checkRegRegioningRestrictions(
217 i.getExecSize(),
218 i.getDestination(),
219 src);
220 }
221 Type srcType = src.getType();
222 bool lblArg =
223 src.getKind() == Operand::Kind::LABEL ||
224 src.getKind() == Operand::Kind::IMMEDIATE;
225 if ((m_enabled_warnings & IGA_WARNINGS_NORMFORM) &&
226 srcType != Type::INVALID &&
227 instSpec.hasImplicitSrcType(srcIx, lblArg) &&
228 instSpec.implicitSrcType(srcIx, lblArg) != srcType)
229 {
230 warning("src type is not binary normal form");
231 }
232 }
233
checkRegRegioningRestrictionsSemanticChecker234 void checkRegRegioningRestrictions(
235 ExecSize es, const Operand &dst, const Operand &src)
236 {
237 // only for the form:
238 // op (...) dst.#<H> ... src.#<V;W,H>
239 if (src.getKind() != Operand::Kind::DIRECT)
240 return; // TODO: check indirect someday too
241 int dstTypeSz = TypeSizeInBitsWithDefault(dst.getType(), 0) / 8, // to bytes
242 srcTypeSz = TypeSizeInBitsWithDefault(src.getType(), 0) / 8; // to bytes
243 if (dstTypeSz == 0 || srcTypeSz == 0)
244 return; // e.g. Type::INVALID or a sub-byte type
245 if (dst.getRegion().getHz() == Region::Horz::HZ_INVALID)
246 return; // e.g. if the dst has no region
247 if (!src.getRegion().isVWH())
248 return; // e.g. if the src has no region
249 int srcRgnV = static_cast<int>(src.getRegion().getVt()),
250 srcRgnW = static_cast<int>(src.getRegion().getWi()),
251 srcRgnH = static_cast<int>(src.getRegion().getHz());
252 int execSize = ExecSizeToInt(es);
253 // int dstReg = dst.getDirRegRef().regNum;
254 // int srcReg = src.getDirRegRef().regNum;
255 int srcSubReg = src.getDirRegRef().subRegNum;
256 // int dstSubReg = dst.getDirRegRef().subRegNum;
257
258 // Restriction 1.1: Where n is the largest element size in bytes for
259 // any source or destination operand type, ExecSize * n must be <= 64.
260 int restrictSize = 64;
261 if (m_model.platform >= Platform::XE_HPC)
262 restrictSize = 128;
263 if (execSize * srcTypeSz > restrictSize) {
264 warning(
265 "register regioning restriction warning: "
266 "ExecSize * sizeof(Type) exceeds 2 GRF\n"
267 "see Programmer's Reference Manual (Restriction 1.1)");
268 }
269 // Restriction 1.2:
270 // When the Execution Data Type is wider than the destination data type,
271 // the destination must be aligned as required by the wider execution
272 // data type and specify a HorzStride equal to the ratio in sizes of
273 // the two data types. For example, a mov with a D source and B destination
274 // must use a 4-byte aligned destination and a Dst.HorzStride of 4.
275 #if 0
276 // TODO: this fails on:
277 // mov (8|M0) r1.0<1>:b r2.0<8;8,1>:b
278 // The "Execution Data Type" ends up being 2, but the dst is 0
279 int execTypeSz = srcTypeSz == 1 ? 2 : srcTypeSz; // "Execution Data Type" is max(2,..)
280 int dstRgnH = static_cast<int>(dst.getRegion().getHz());
281 bool dstIsDirect = dst.getKind() == Operand::Kind::DIRECT;
282 if (dstIsDirect && execTypeSz > dstTypeSz) {
283 if (dstTypeSz * dstRgnH != execTypeSz) {
284 // e.g. the following violate this
285 // mov (8) r1.0<1>:b r2...:d
286 // mov (8) r1.0<2>:b r2...:d
287 // but this is okay (correct)
288 // mov (8) r1.0<4>:b r2...:d
289 warning(
290 "register regioning restriction warning: "
291 "Dst.Hz * sizeof(Dst.Type) != Execution Data Type Size (destination misaligned for type)\n"
292 "see Programmer's Reference Manual (Restriction 1.2)");
293 }
294 }
295 #endif
296
297 // Restriction 2.1: ExecSize must be greater than or equal to Width.
298 if (execSize < srcRgnW) {
299 // mov (1) r1.0<1>:d r2.0<8;8,1>:d
300 warning(
301 "register regioning restriction warning: "
302 "ExecSize <= Src.W (partial row)\n"
303 "see Programmer's Reference Manual (Restriction 2.1)");
304 }
305
306 // Restriction 2.2: If ExecSize = Width and HorzStride != 0,
307 // VertStride must be set to Width * HorzStride.
308 if (execSize == srcRgnW && srcRgnH != 0 && srcRgnV != srcRgnW * srcRgnH) {
309 warning(
310 "register regioning restriction warning: "
311 "ExecSize == Src.W && Src.H != 0 && Src.V != Src.W * Src.H (vertical misalignment)\n"
312 "see Programmer's Reference Manual (Restriction 2.2)");
313 }
314
315 // Restriction 2.3: If ExecSize = Width and HorzStride = 0,
316 // there is no restriction on VertStride.
317 // The above checks this.
318
319 // Restriction 2.4: If Width = 1, HorzStride must be 0 regardless of
320 // the values of ExecSize and VertStride.
321 if (execSize == 1 && srcRgnH != 0) {
322 warning(
323 "register regioning restriction warning: "
324 "SIMD1 requires horizontal stride of 0 (scalar region access)\n"
325 "see Programmer's Reference Manual (Restriction 2.4)");
326 }
327
328 // Restriction 2.5: If ExecSize = Width = 1, both VertStride and
329 // HorzStride must be 0.
330 if (execSize == 1 && srcRgnW == 1 && (srcRgnV != 0 || srcRgnH != 0)) {
331 warning(
332 "register regioning restriction warning: "
333 "SIMD1 requires vertical and horiztonal to be 0 (scalar region access)\n"
334 "see Programmer's Reference Manual (Restriction 2.5)");
335 }
336
337 // Restriction 2.6: If VertStride = HorzStride = 0, Width must be 1
338 // regardless of the value of ExecSize.
339 if (srcRgnV == 0 && srcRgnH == 0 && srcRgnW != 1) {
340 warning(
341 "register regioning restriction warning: "
342 "If vertical stride and horizontal stride are 0, width must be 1.\n"
343 "see Programmer's Reference Manual (Restriction 2.6)");
344 }
345
346 // Restriction 2.7: Dst.HorzStride must not be 0.
347 // This will be checked higher up since this method gets called on
348 // all source operands (and would duplicate error reports)
349
350 // Restriction 2.8: VertStride must be used to cross GRF register boundaries.
351 // This rule implies that elements within a 'Width' cannot cross GRF boundaries.
352 //
353 // Examples:
354 // LEGAL: mov (16) r3:ub r11.1<32;16,2>:ub
355 // e.g. the bytes accessed are
356 // |.0.1.2.3.4.5.6.7.8.9.A.B.C.D.E.F|
357 // LEGAL: mov (4) r3:d r11.1<8;4,2>:d
358 // e.g. the bytes accessed are
359 // |....0000....1111....2222....3333|
360 //
361 // ILLEGAL: mov (8) r3:d r2.1<8;8,1>:d
362 // |....0000111122223333444455556666|7777
363 // ^ bad
364 if (src.getDirRegName() == RegName::GRF_R) {
365 // int slotsInReg = 32/srcTypeSz/srcRgnH;
366 if (srcSubReg + (srcRgnW - 1) * srcRgnH >= restrictSize / 2 /srcTypeSz) {
367 // the last element is >= the maximum number of elements of
368 // this type size
369 warning(
370 "register regioning restriction warning: "
371 "Vertical stride must be used to cross GRF boundaries.\n"
372 "see Programmer's Reference Manual (Restriction 2.8)");
373 }
374 }
375
376 }
377 };
378
379
CheckSemantics(const Kernel & k,ErrorHandler & err,uint32_t enabled_warnings)380 void iga::CheckSemantics(
381 const Kernel &k,
382 ErrorHandler &err,
383 uint32_t enabled_warnings)
384 {
385 SemanticChecker checker(k.getModel(), &err, enabled_warnings);
386 checker.checkKernel(k);
387 }
388
389
390 // checks basic IR structure (very incomplete)
391 struct SanityChecker {
392 ErrorHandler *m_errHandler;
393 const Instruction *m_inst;
394
SanityCheckerSanityChecker395 SanityChecker() : m_errHandler(nullptr), m_inst(nullptr) {}
396
checkKernelSanityChecker397 void checkKernel(const Kernel &k) {
398 for (auto b : k.getBlockList()) {
399 for (auto i : b->getInstList()) {
400 checkInstruction(*i);
401 }
402 }
403 }
404
checkInstructionSanityChecker405 void checkInstruction(const Instruction &i) {
406 m_inst = &i;
407 if (i.getOpSpec().supportsDestination()) {
408 checkDst(i.getDestination());
409 } else if (i.getDestination().getKind() != Operand::Kind::INVALID) {
410 IGA_ASSERT_FALSE("unsupported destination should be .kind=INVALID");
411 }
412 int srcs = i.getSourceCount();
413 if (srcs > 0) {
414 checkSrc(i, 0);
415 }
416 if (srcs > 1) {
417 checkSrc(i, 1);
418 }
419 if (srcs > 2) {
420 checkSrc(i, 2);
421 }
422 m_inst = nullptr;
423 }
424
checkDstSanityChecker425 void checkDst(const Operand &op) {
426 switch (op.getKind()) {
427 case Operand::Kind::DIRECT:
428 IGA_ASSERT(op.getDirRegName() != RegName::INVALID, "invalid register");
429 break;
430 case Operand::Kind::INDIRECT:
431 break;
432 case Operand::Kind::MACRO:
433 IGA_ASSERT(m_inst->isMacro(), "instruction is not macro");
434 IGA_ASSERT(
435 op.getMathMacroExt() != MathMacroExt::INVALID,
436 "invalid accumulator for macro");
437 break;
438 default:
439 IGA_ASSERT_FALSE("wrong kind for destination");
440 }
441 }
442
checkSrcSanityChecker443 void checkSrc(const Instruction &inst, int srcIx) {
444 const Operand &op = inst.getSource(srcIx);
445 switch (op.getKind()) {
446 case Operand::Kind::DIRECT:
447 IGA_ASSERT(op.getDirRegName() != RegName::INVALID, "invalid register");
448 break;
449 case Operand::Kind::INDIRECT:
450 break;
451 case Operand::Kind::MACRO:
452 IGA_ASSERT(m_inst->isMacro(), "instruction is not macro");
453 IGA_ASSERT(
454 op.getMathMacroExt() != MathMacroExt::INVALID,
455 "invalid accumulator for macro");
456 break;
457 case Operand::Kind::IMMEDIATE:
458 case Operand::Kind::LABEL:
459 break;
460 default: {
461 std::stringstream ss;
462 auto loc = inst.getLoc();
463 ss << loc.line << ":" << loc.col << ":[" << loc.offset << "]: wrong kind for src" << srcIx;
464 IGA_ASSERT_FALSE(ss.str().c_str());
465 }
466 }
467 }
468 };
469
470
SanityCheckIR(const Kernel & k)471 void iga::SanityCheckIR(const Kernel &k) {
472 SanityChecker checker;
473 checker.checkKernel(k);
474 }
475
476
SanityCheckIR(const Instruction & i)477 void iga::SanityCheckIR(const Instruction &i) {
478 SanityChecker checker;
479 checker.checkInstruction(i);
480 }
481