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