Coverage Report

Created: 2025-09-27 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}