/src/php-src/ext/opcache/jit/ir/ir_check.c
Line | Count | Source |
1 | | /* |
2 | | * IR - Lightweight JIT Compilation Framework |
3 | | * (IR verification) |
4 | | * Copyright (C) 2022 Zend by Perforce. |
5 | | * Authors: Dmitry Stogov <dmitry@php.net> |
6 | | */ |
7 | | |
8 | | #include "ir.h" |
9 | | #include "ir_private.h" |
10 | | |
11 | | void ir_consistency_check(void) |
12 | 0 | { |
13 | 0 | IR_ASSERT(IR_UNUSED == 0); |
14 | 0 | IR_ASSERT(IR_NOP == 0); |
15 | |
|
16 | 0 | IR_ASSERT((int)IR_BOOL == (int)IR_C_BOOL); |
17 | 0 | IR_ASSERT((int)IR_U8 == (int)IR_C_U8); |
18 | 0 | IR_ASSERT((int)IR_U16 == (int)IR_C_U16); |
19 | 0 | IR_ASSERT((int)IR_U32 == (int)IR_C_U32); |
20 | 0 | IR_ASSERT((int)IR_U64 == (int)IR_C_U64); |
21 | 0 | IR_ASSERT((int)IR_ADDR == (int)IR_C_ADDR); |
22 | 0 | IR_ASSERT((int)IR_CHAR == (int)IR_C_CHAR); |
23 | 0 | IR_ASSERT((int)IR_I8 == (int)IR_C_I8); |
24 | 0 | IR_ASSERT((int)IR_I16 == (int)IR_C_I16); |
25 | 0 | IR_ASSERT((int)IR_I32 == (int)IR_C_I32); |
26 | 0 | IR_ASSERT((int)IR_I64 == (int)IR_C_I64); |
27 | 0 | IR_ASSERT((int)IR_DOUBLE == (int)IR_C_DOUBLE); |
28 | 0 | IR_ASSERT((int)IR_FLOAT == (int)IR_C_FLOAT); |
29 | |
|
30 | 0 | IR_ASSERT((IR_EQ ^ 1) == IR_NE); |
31 | 0 | IR_ASSERT((IR_LT ^ 3) == IR_GT); |
32 | 0 | IR_ASSERT((IR_GT ^ 3) == IR_LT); |
33 | 0 | IR_ASSERT((IR_LE ^ 3) == IR_GE); |
34 | 0 | IR_ASSERT((IR_GE ^ 3) == IR_LE); |
35 | 0 | IR_ASSERT((IR_ULT ^ 3) == IR_UGT); |
36 | 0 | IR_ASSERT((IR_UGT ^ 3) == IR_ULT); |
37 | 0 | IR_ASSERT((IR_ULE ^ 3) == IR_UGE); |
38 | 0 | IR_ASSERT((IR_UGE ^ 3) == IR_ULE); |
39 | |
|
40 | 0 | IR_ASSERT(IR_ADD + 1 == IR_SUB); |
41 | 0 | } |
42 | | |
43 | | static bool ir_check_use_list(const ir_ctx *ctx, ir_ref from, ir_ref to) |
44 | 0 | { |
45 | 0 | ir_ref n, *p; |
46 | 0 | ir_use_list *use_list = &ctx->use_lists[from]; |
47 | |
|
48 | 0 | n = use_list->count; |
49 | 0 | for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) { |
50 | 0 | if (*p == to) { |
51 | 0 | return 1; |
52 | 0 | } |
53 | 0 | } |
54 | 0 | return 0; |
55 | 0 | } |
56 | | |
57 | | static bool ir_check_input_list(const ir_ctx *ctx, ir_ref from, ir_ref to) |
58 | 0 | { |
59 | 0 | ir_insn *insn = &ctx->ir_base[to]; |
60 | 0 | ir_ref n, j, *p; |
61 | |
|
62 | 0 | n = ir_input_edges_count(ctx, insn); |
63 | 0 | for (j = 1, p = insn->ops + 1; j <= n; j++, p++) { |
64 | 0 | if (*p == from) { |
65 | 0 | return 1; |
66 | 0 | } |
67 | 0 | } |
68 | 0 | return 0; |
69 | 0 | } |
70 | | |
71 | | static bool ir_check_domination(const ir_ctx *ctx, ir_ref def, ir_ref use) |
72 | 0 | { |
73 | 0 | uint32_t b1 = ctx->cfg_map[def]; |
74 | 0 | uint32_t b2 = ctx->cfg_map[use]; |
75 | 0 | ir_block *blocks = ctx->cfg_blocks; |
76 | 0 | uint32_t b1_depth = blocks[b1].dom_depth; |
77 | 0 | const ir_block *bb2 = &blocks[b2]; |
78 | |
|
79 | 0 | if (b1 == b2) { |
80 | 0 | return def < use; |
81 | 0 | } |
82 | 0 | while (bb2->dom_depth > b1_depth) { |
83 | 0 | b2 = bb2->dom_parent; |
84 | 0 | bb2 = &blocks[b2]; |
85 | 0 | } |
86 | 0 | return b1 == b2; |
87 | 0 | } |
88 | | |
89 | | bool ir_check(const ir_ctx *ctx) |
90 | 0 | { |
91 | 0 | ir_ref i, j, n, *p, use; |
92 | 0 | ir_insn *insn, *use_insn; |
93 | 0 | ir_type type; |
94 | 0 | uint32_t flags; |
95 | 0 | bool ok = 1; |
96 | |
|
97 | 0 | for (i = IR_UNUSED + 1, insn = ctx->ir_base + i; i < ctx->insns_count;) { |
98 | 0 | if (insn->op >= IR_LAST_OP) { |
99 | 0 | fprintf(stderr, "ir_base[%d].op invalid opcode (%d)\n", i, insn->op); |
100 | 0 | ok = 0; |
101 | 0 | break; |
102 | 0 | } |
103 | 0 | flags = ir_op_flags[insn->op]; |
104 | 0 | n = ir_input_edges_count(ctx, insn); |
105 | 0 | for (j = 1, p = insn->ops + 1; j <= n; j++, p++) { |
106 | 0 | use = *p; |
107 | 0 | if (use != IR_UNUSED) { |
108 | 0 | if (IR_IS_CONST_REF(use)) { |
109 | 0 | if (IR_OPND_KIND(flags, j) != IR_OPND_DATA) { |
110 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must not be constant\n", i, j, use); |
111 | 0 | ok = 0; |
112 | 0 | } else if (use >= ctx->consts_count) { |
113 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] constant reference (%d) is out of range\n", i, j, use); |
114 | 0 | ok = 0; |
115 | 0 | } |
116 | 0 | } else { |
117 | 0 | if (use >= ctx->insns_count) { |
118 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] insn reference (%d) is out of range\n", i, j, use); |
119 | 0 | ok = 0; |
120 | 0 | } |
121 | 0 | use_insn = &ctx->ir_base[use]; |
122 | 0 | switch (IR_OPND_KIND(flags, j)) { |
123 | 0 | case IR_OPND_DATA: |
124 | 0 | if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_DATA)) { |
125 | 0 | if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_MEM) |
126 | 0 | || use_insn->type == IR_VOID) { |
127 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be DATA\n", i, j, use); |
128 | 0 | ok = 0; |
129 | 0 | } |
130 | 0 | } |
131 | 0 | if ((ctx->flags2 & IR_LINEAR) |
132 | 0 | && use >= i |
133 | 0 | && !(insn->op == IR_PHI && ctx->ir_base[insn->op1].op == IR_LOOP_BEGIN)) { |
134 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use); |
135 | 0 | ok = 0; |
136 | 0 | } |
137 | 0 | if (flags & IR_OP_FLAG_DATA) { |
138 | 0 | switch (insn->op) { |
139 | 0 | case IR_COND: |
140 | 0 | if (j == 1) { |
141 | 0 | break; |
142 | 0 | } |
143 | 0 | IR_FALLTHROUGH; |
144 | 0 | case IR_ADD: |
145 | 0 | case IR_SUB: |
146 | 0 | case IR_MUL: |
147 | 0 | case IR_DIV: |
148 | 0 | case IR_MOD: |
149 | 0 | case IR_NEG: |
150 | 0 | case IR_ABS: |
151 | 0 | case IR_ADD_OV: |
152 | 0 | case IR_SUB_OV: |
153 | 0 | case IR_MUL_OV: |
154 | 0 | case IR_NOT: |
155 | 0 | case IR_OR: |
156 | 0 | case IR_AND: |
157 | 0 | case IR_XOR: |
158 | 0 | case IR_SHL: |
159 | 0 | case IR_SHR: |
160 | 0 | case IR_SAR: |
161 | 0 | case IR_ROL: |
162 | 0 | case IR_ROR: |
163 | 0 | case IR_BSWAP: |
164 | 0 | case IR_MIN: |
165 | 0 | case IR_MAX: |
166 | 0 | case IR_PHI: |
167 | 0 | case IR_COPY: |
168 | 0 | case IR_PI: |
169 | 0 | if (insn->type != use_insn->type) { |
170 | 0 | if (j == 2 |
171 | 0 | && (insn->op == IR_SHL |
172 | 0 | || insn->op == IR_SHR |
173 | 0 | || insn->op == IR_SAR |
174 | 0 | || insn->op == IR_ROL |
175 | 0 | || insn->op == IR_ROR) |
176 | 0 | && ir_type_size[use_insn->type] < ir_type_size[insn->type]) { |
177 | | /* second argument of SHIFT may be incompatible with result */ |
178 | 0 | break; |
179 | 0 | } |
180 | 0 | if (insn->op == IR_NOT && insn->type == IR_BOOL) { |
181 | | /* boolean not */ |
182 | 0 | break; |
183 | 0 | } |
184 | 0 | if (insn->type == IR_ADDR && (use_insn->type == IR_UINTPTR_T || use_insn->type == IR_INTPTR_T)) { |
185 | 0 | break; |
186 | 0 | } else if (use_insn->type == IR_ADDR && (insn->type == IR_UINTPTR_T || insn->type == IR_INTPTR_T)) { |
187 | 0 | break; |
188 | 0 | } |
189 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] (%d) type is incompatible with result type (%d != %d)\n", |
190 | 0 | i, j, use, use_insn->type, insn->type); |
191 | 0 | ok = 0; |
192 | 0 | } |
193 | 0 | break; |
194 | 0 | } |
195 | 0 | } |
196 | 0 | if ((ctx->flags2 & IR_LINEAR) |
197 | 0 | && ctx->cfg_map |
198 | 0 | && insn->op != IR_PHI |
199 | 0 | && !ir_check_domination(ctx, use, i)) { |
200 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] -> %d, %d doesn't dominate %d\n", i, j, use, use, i); |
201 | 0 | ok = 0; |
202 | 0 | } |
203 | 0 | break; |
204 | 0 | case IR_OPND_CONTROL: |
205 | 0 | if (flags & IR_OP_FLAG_BB_START) { |
206 | 0 | if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_BB_END)) { |
207 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be BB_END\n", i, j, use); |
208 | 0 | ok = 0; |
209 | 0 | } |
210 | 0 | } else { |
211 | 0 | if (ir_op_flags[use_insn->op] & IR_OP_FLAG_BB_END) { |
212 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must not be BB_END\n", i, j, use); |
213 | 0 | ok = 0; |
214 | 0 | } |
215 | 0 | } |
216 | 0 | if ((ctx->flags2 & IR_LINEAR) |
217 | 0 | && use >= i |
218 | 0 | && !(insn->op == IR_LOOP_BEGIN)) { |
219 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use); |
220 | 0 | ok = 0; |
221 | 0 | } |
222 | 0 | break; |
223 | 0 | case IR_OPND_CONTROL_DEP: |
224 | 0 | if ((ctx->flags2 & IR_LINEAR) |
225 | 0 | && use >= i) { |
226 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use); |
227 | 0 | ok = 0; |
228 | 0 | } else if (insn->op == IR_PHI) { |
229 | 0 | ir_insn *merge_insn = &ctx->ir_base[insn->op1]; |
230 | 0 | if (merge_insn->op != IR_MERGE && merge_insn->op != IR_LOOP_BEGIN) { |
231 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be MERGE or LOOP_BEGIN\n", i, j, use); |
232 | 0 | ok = 0; |
233 | 0 | } |
234 | 0 | } |
235 | 0 | break; |
236 | 0 | case IR_OPND_CONTROL_REF: |
237 | 0 | if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_CONTROL)) { |
238 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be CONTROL\n", i, j, use); |
239 | 0 | ok = 0; |
240 | 0 | } |
241 | 0 | break; |
242 | 0 | default: |
243 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) of unsupported kind\n", i, j, use); |
244 | 0 | ok = 0; |
245 | 0 | } |
246 | 0 | } |
247 | 0 | } else if ((insn->op == IR_RETURN || insn->op == IR_UNREACHABLE) && j == 2) { |
248 | | /* pass (function returns void) */ |
249 | 0 | } else if (insn->op == IR_BEGIN && j == 1) { |
250 | | /* pass (start of unreachable basic block) */ |
251 | 0 | } else if (IR_OPND_KIND(flags, j) != IR_OPND_CONTROL_REF |
252 | 0 | && (insn->op != IR_SNAPSHOT || j == 1)) { |
253 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] missing reference (%d)\n", i, j, use); |
254 | 0 | ok = 0; |
255 | 0 | } |
256 | 0 | if (ctx->use_lists |
257 | 0 | && use > 0 |
258 | 0 | && !ir_check_use_list(ctx, use, i)) { |
259 | 0 | fprintf(stderr, "ir_base[%d].ops[%d] is not in use list (%d)\n", i, j, use); |
260 | 0 | ok = 0; |
261 | 0 | } |
262 | 0 | } |
263 | | |
264 | 0 | switch (insn->op) { |
265 | 0 | case IR_PHI: |
266 | 0 | if (insn->inputs_count != ctx->ir_base[insn->op1].inputs_count + 1) { |
267 | 0 | fprintf(stderr, "ir_base[%d] inconsistent PHI inputs_count (%d != %d)\n", |
268 | 0 | i, insn->inputs_count, ctx->ir_base[insn->op1].inputs_count + 1); |
269 | 0 | ok = 0; |
270 | 0 | } |
271 | 0 | break; |
272 | 0 | case IR_LOAD: |
273 | 0 | case IR_STORE: |
274 | 0 | type = ctx->ir_base[insn->op2].type; |
275 | 0 | if (type != IR_ADDR |
276 | 0 | && (!IR_IS_TYPE_INT(type) || ir_type_size[type] != ir_type_size[IR_ADDR])) { |
277 | 0 | fprintf(stderr, "ir_base[%d].op2 must have ADDR type (%s)\n", |
278 | 0 | i, ir_type_name[type]); |
279 | 0 | ok = 0; |
280 | 0 | } |
281 | 0 | break; |
282 | 0 | case IR_VLOAD: |
283 | 0 | case IR_VSTORE: |
284 | 0 | if (ctx->ir_base[insn->op2].op != IR_VAR) { |
285 | 0 | fprintf(stderr, "ir_base[%d].op2 must be 'VAR' (%s)\n", |
286 | 0 | i, ir_op_name[ctx->ir_base[insn->op2].op]); |
287 | 0 | ok = 0; |
288 | 0 | } |
289 | 0 | break; |
290 | 0 | case IR_RETURN: |
291 | 0 | if (ctx->ret_type != (insn->op2 ? ctx->ir_base[insn->op2].type : IR_VOID)) { |
292 | 0 | fprintf(stderr, "ir_base[%d].type incompatible return type\n", i); |
293 | 0 | ok = 0; |
294 | 0 | } |
295 | 0 | break; |
296 | 0 | case IR_TAILCALL: |
297 | 0 | if (ctx->ret_type != insn->type) { |
298 | 0 | fprintf(stderr, "ir_base[%d].type incompatible return type\n", i); |
299 | 0 | ok = 0; |
300 | 0 | } |
301 | 0 | break; |
302 | 0 | case IR_PARAM: |
303 | 0 | if (i > 2 && ctx->ir_base[i - 1].op != IR_PARAM) { |
304 | 0 | fprintf(stderr, "ir_base[%d].op PARAMs must be used only right after START\n", i); |
305 | 0 | ok = 0; |
306 | 0 | } |
307 | 0 | break; |
308 | 0 | } |
309 | | |
310 | 0 | if (ctx->use_lists) { |
311 | 0 | ir_use_list *use_list = &ctx->use_lists[i]; |
312 | 0 | ir_ref count, n = use_list->count; |
313 | |
|
314 | 0 | for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) { |
315 | 0 | use = *p; |
316 | 0 | if (!ir_check_input_list(ctx, i, use)) { |
317 | 0 | fprintf(stderr, "ir_base[%d] is in use list of ir_base[%d]\n", use, i); |
318 | 0 | ok = 0; |
319 | 0 | } |
320 | 0 | } |
321 | |
|
322 | 0 | if ((flags & IR_OP_FLAG_CONTROL) && !(flags & IR_OP_FLAG_MEM)) { |
323 | 0 | switch (insn->op) { |
324 | 0 | case IR_SWITCH: |
325 | | /* may have many successors */ |
326 | 0 | if (use_list->count < 1) { |
327 | 0 | fprintf(stderr, "ir_base[%d].op (SWITCH) must have at least 1 successor (%d)\n", i, use_list->count); |
328 | 0 | ok = 0; |
329 | 0 | } |
330 | 0 | break; |
331 | 0 | case IR_IF: |
332 | 0 | if (use_list->count != 2) { |
333 | 0 | fprintf(stderr, "ir_base[%d].op (IF) must have 2 successors (%d)\n", i, use_list->count); |
334 | 0 | ok = 0; |
335 | 0 | } |
336 | 0 | break; |
337 | 0 | case IR_UNREACHABLE: |
338 | 0 | case IR_RETURN: |
339 | 0 | if (use_list->count == 1) { |
340 | | /* UNREACHABLE and RETURN may be linked with the following ENTRY by a fake edge */ |
341 | 0 | if (ctx->ir_base[ctx->use_edges[use_list->refs]].op == IR_ENTRY) { |
342 | 0 | break; |
343 | 0 | } |
344 | 0 | } |
345 | 0 | IR_FALLTHROUGH; |
346 | 0 | case IR_IJMP: |
347 | 0 | if (use_list->count != 0) { |
348 | 0 | fprintf(stderr, "ir_base[%d].op (%s) must not have successors (%d)\n", |
349 | 0 | i, ir_op_name[insn->op], use_list->count); |
350 | 0 | ok = 0; |
351 | 0 | } |
352 | 0 | break; |
353 | 0 | default: |
354 | | /* skip data references */ |
355 | 0 | count = n = use_list->count; |
356 | 0 | for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) { |
357 | 0 | use = *p; |
358 | 0 | if (!(ir_op_flags[ctx->ir_base[use].op] & IR_OP_FLAG_CONTROL)) { |
359 | 0 | count--; |
360 | 0 | } |
361 | 0 | } |
362 | 0 | if (count != 1) { |
363 | 0 | if (insn->op == IR_CALL && count == 2) { |
364 | | /* result of CALL may be used as data in control instruction */ |
365 | 0 | break; |
366 | 0 | } |
367 | 0 | if ((insn->op == IR_LOOP_END || insn->op == IR_END) && count == 2) { |
368 | | /* LOOP_END/END may be linked with the following ENTRY by a fake edge */ |
369 | 0 | if (ctx->ir_base[ctx->use_edges[use_list->refs]].op == IR_ENTRY) { |
370 | 0 | count--; |
371 | 0 | } |
372 | 0 | if (ctx->ir_base[ctx->use_edges[use_list->refs + 1]].op == IR_ENTRY) { |
373 | 0 | count--; |
374 | 0 | } |
375 | 0 | if (count == 1) { |
376 | 0 | break; |
377 | 0 | } |
378 | 0 | } |
379 | 0 | if (count == 0 && (insn->op == IR_END || insn->op == IR_LOOP_END)) { |
380 | | /* Dead block */ |
381 | 0 | break; |
382 | 0 | } |
383 | 0 | fprintf(stderr, "ir_base[%d].op (%s) must have 1 successor (%d)\n", |
384 | 0 | i, ir_op_name[insn->op], count); |
385 | 0 | ok = 0; |
386 | 0 | } |
387 | 0 | break; |
388 | 0 | } |
389 | 0 | } |
390 | 0 | } |
391 | 0 | n = ir_insn_inputs_to_len(n); |
392 | 0 | i += n; |
393 | 0 | insn += n; |
394 | 0 | } |
395 | | |
396 | | // if (!ok) { |
397 | | // ir_dump_codegen(ctx, stderr); |
398 | | // } |
399 | 0 | IR_ASSERT(ok); |
400 | 0 | return ok; |
401 | 0 | } |