Coverage Report

Created: 2026-06-02 06:40

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
0
  IR_ASSERT((IR_ORDERED ^ 1) == IR_UNORDERED);
40
41
0
  IR_ASSERT(IR_ADD + 1 == IR_SUB);
42
0
}
43
44
typedef struct {
45
  ir_arena  *arena;
46
  ir_bitset *use_set;
47
  ir_bitset *input_set;
48
} ir_check_ctx;
49
50
static bool ir_check_use_list(ir_check_ctx *check_ctx, const ir_ctx *ctx, ir_ref from, ir_ref to)
51
0
{
52
0
  ir_ref n, *p;
53
0
  ir_use_list *use_list = &ctx->use_lists[from];
54
55
0
  n = use_list->count;
56
0
  if (n > 16) {
57
    /* Avoid quadratic complexity by maintaining a temporary bit-set */
58
0
    ir_bitset set;
59
60
0
    if (!check_ctx->use_set || !(set = check_ctx->use_set[from])) {
61
0
      if (!check_ctx->arena) {
62
0
        check_ctx->arena = ir_arena_create(sizeof(ir_arena) +
63
0
          ctx->insns_count * sizeof(ir_bitset) +
64
0
          ir_bitset_len(ctx->insns_count) * sizeof(ir_bitset_base_t));
65
0
      }
66
0
      if (!check_ctx->use_set) {
67
0
        check_ctx->use_set = ir_arena_alloc(&check_ctx->arena, ctx->insns_count * sizeof(ir_bitset));
68
0
        memset(check_ctx->use_set, 0, ctx->insns_count * sizeof(ir_bitset));
69
0
      }
70
0
      check_ctx->use_set[from] = set = (ir_bitset)ir_arena_alloc(&check_ctx->arena,
71
0
        ir_bitset_len(ctx->insns_count) * sizeof(ir_bitset_base_t));
72
0
      memset(set, 0, ir_bitset_len(ctx->insns_count) * sizeof(ir_bitset_base_t));
73
0
      for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) {
74
0
        ir_bitset_incl(set, *p);
75
0
      }
76
0
    }
77
0
    return ir_bitset_in(set, to);
78
0
  }
79
0
  for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) {
80
0
    if (*p == to) {
81
0
      return 1;
82
0
    }
83
0
  }
84
0
  return 0;
85
0
}
86
87
static bool ir_check_input_list(ir_check_ctx *check_ctx, const ir_ctx *ctx, ir_ref from, ir_ref to)
88
0
{
89
0
  ir_insn *insn = &ctx->ir_base[to];
90
0
  ir_ref n, j, *p;
91
92
0
  n = ir_input_edges_count(ctx, insn);
93
0
  if (n > 16) {
94
    /* Avoid quadratic complexity by maintaining a temporary bit-set */
95
0
    ir_bitset set;
96
97
0
    if (!check_ctx->input_set || !(set = check_ctx->input_set[to])) {
98
0
      if (!check_ctx->arena) {
99
0
        check_ctx->arena = ir_arena_create(sizeof(ir_arena) +
100
0
          ctx->insns_count * sizeof(ir_bitset) +
101
0
          ir_bitset_len(ctx->insns_count) * sizeof(ir_bitset_base_t));
102
0
      }
103
0
      if (!check_ctx->input_set) {
104
0
        check_ctx->input_set = ir_arena_alloc(&check_ctx->arena, ctx->insns_count * sizeof(ir_bitset));
105
0
        memset(check_ctx->input_set, 0, ctx->insns_count * sizeof(ir_bitset));
106
0
      }
107
0
      check_ctx->input_set[to] = set = (ir_bitset)ir_arena_alloc(&check_ctx->arena,
108
0
        ir_bitset_len(ctx->insns_count) * sizeof(ir_bitset_base_t));
109
0
      memset(set, 0, ir_bitset_len(ctx->insns_count) * sizeof(ir_bitset_base_t));
110
0
      for (j = 1, p = insn->ops + 1; j <= n; j++, p++) {
111
0
        if (*p > 0) ir_bitset_incl(set, *p);
112
0
      }
113
0
    }
114
0
    return ir_bitset_in(set, from);
115
0
  }
116
0
  for (j = 1, p = insn->ops + 1; j <= n; j++, p++) {
117
0
    if (*p == from) {
118
0
      return 1;
119
0
    }
120
0
  }
121
0
  return 0;
122
0
}
123
124
static bool ir_check_domination(const ir_ctx *ctx, ir_ref def, ir_ref use)
125
0
{
126
0
  uint32_t b1 = ctx->cfg_map[def];
127
0
  uint32_t b2 = ctx->cfg_map[use];
128
0
  ir_block *blocks = ctx->cfg_blocks;
129
0
  uint32_t b1_depth = blocks[b1].dom_depth;
130
0
  const ir_block *bb2 = &blocks[b2];
131
132
0
  if (b1 == b2) {
133
0
    return def < use;
134
0
  }
135
0
  while (bb2->dom_depth > b1_depth) {
136
0
    b2 = bb2->dom_parent;
137
0
    bb2 = &blocks[b2];
138
0
  }
139
0
  return b1 == b2;
140
0
}
141
142
bool ir_check(const ir_ctx *ctx)
143
0
{
144
0
  ir_ref i, j, n, *p, use;
145
0
  ir_insn *insn, *use_insn;
146
0
  ir_type type;
147
0
  uint32_t flags;
148
0
  bool ok = 1;
149
0
  ir_check_ctx check_ctx;
150
151
0
  if (ctx->insns_count < 1 || ctx->ir_base[1].op != IR_START) {
152
0
    fprintf(stderr, "ir_base[1].op invalid opcode (%d)\n",
153
0
      (ctx->insns_count < 1) ? IR_NOP : ctx->ir_base[0].op);
154
0
    ok = 0;
155
0
  }
156
157
0
  check_ctx.arena = NULL;
158
0
  check_ctx.use_set = NULL;
159
0
  check_ctx.input_set = NULL;
160
161
0
  for (i = IR_UNUSED + 1, insn = ctx->ir_base + i; i < ctx->insns_count;) {
162
0
    if (insn->op >= IR_LAST_OP) {
163
0
      fprintf(stderr, "ir_base[%d].op invalid opcode (%d)\n", i, insn->op);
164
0
      ok = 0;
165
0
      break;
166
0
    }
167
0
    flags = ir_op_flags[insn->op];
168
0
    n = ir_input_edges_count(ctx, insn);
169
0
    for (j = 1, p = insn->ops + 1; j <= n; j++, p++) {
170
0
      use = *p;
171
0
      if (use != IR_UNUSED) {
172
0
        if (IR_IS_CONST_REF(use)) {
173
0
          if (IR_OPND_KIND(flags, j) != IR_OPND_DATA) {
174
0
            fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must not be constant\n", i, j, use);
175
0
            ok = 0;
176
0
          } else if (use >= ctx->consts_count) {
177
0
            fprintf(stderr, "ir_base[%d].ops[%d] constant reference (%d) is out of range\n", i, j, use);
178
0
            ok = 0;
179
0
          }
180
0
        } else {
181
0
          if (use >= ctx->insns_count) {
182
0
            fprintf(stderr, "ir_base[%d].ops[%d] insn reference (%d) is out of range\n", i, j, use);
183
0
            ok = 0;
184
0
          }
185
0
          use_insn = &ctx->ir_base[use];
186
0
          switch (IR_OPND_KIND(flags, j)) {
187
0
            case IR_OPND_DATA:
188
0
              if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_DATA)) {
189
0
                if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_MEM)
190
0
                 || use_insn->type == IR_VOID) {
191
0
                  fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be DATA\n", i, j, use);
192
0
                  ok = 0;
193
0
                }
194
0
              }
195
0
              if ((ctx->flags2 & IR_LINEAR)
196
0
               && use >= i
197
0
               && !(insn->op == IR_PHI && ctx->ir_base[insn->op1].op == IR_LOOP_BEGIN)) {
198
0
                fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use);
199
0
                ok = 0;
200
0
              }
201
0
              if (flags & IR_OP_FLAG_DATA) {
202
0
                switch (insn->op) {
203
0
                  case IR_COND:
204
0
                    if (j == 1) {
205
0
                      break;
206
0
                    }
207
0
                    IR_FALLTHROUGH;
208
0
                  case IR_ADD:
209
0
                  case IR_SUB:
210
0
                  case IR_MUL:
211
0
                  case IR_DIV:
212
0
                  case IR_MOD:
213
0
                  case IR_NEG:
214
0
                  case IR_ABS:
215
0
                  case IR_ADD_OV:
216
0
                  case IR_SUB_OV:
217
0
                  case IR_MUL_OV:
218
0
                  case IR_NOT:
219
0
                  case IR_OR:
220
0
                  case IR_AND:
221
0
                  case IR_XOR:
222
0
                  case IR_SHL:
223
0
                  case IR_SHR:
224
0
                  case IR_SAR:
225
0
                  case IR_ROL:
226
0
                  case IR_ROR:
227
0
                  case IR_BSWAP:
228
0
                  case IR_MIN:
229
0
                  case IR_MAX:
230
0
                  case IR_PHI:
231
0
                  case IR_COPY:
232
0
                  case IR_PI:
233
0
                    if (insn->type != use_insn->type) {
234
0
                      if (j == 2
235
0
                       && (insn->op == IR_SHL
236
0
                        || insn->op == IR_SHR
237
0
                        || insn->op == IR_SAR
238
0
                        || insn->op == IR_ROL
239
0
                        || insn->op == IR_ROR)
240
0
                       && ir_type_size[use_insn->type] < ir_type_size[insn->type]) {
241
                        /* second argument of SHIFT may be incompatible with result */
242
0
                        break;
243
0
                      }
244
0
                      if (insn->op == IR_NOT && insn->type == IR_BOOL) {
245
                        /* boolean not */
246
0
                        break;
247
0
                      }
248
0
                      if (insn->type == IR_ADDR && (use_insn->type == IR_UINTPTR_T || use_insn->type == IR_INTPTR_T)) {
249
0
                        break;
250
0
                      } else if (use_insn->type == IR_ADDR && (insn->type == IR_UINTPTR_T || insn->type == IR_INTPTR_T)) {
251
0
                        break;
252
0
                      }
253
0
                      fprintf(stderr, "ir_base[%d].ops[%d] (%d) type is incompatible with result type (%d != %d)\n",
254
0
                        i, j, use, use_insn->type, insn->type);
255
0
                      ok = 0;
256
0
                    }
257
0
                    break;
258
0
                }
259
0
              }
260
0
              if ((ctx->flags2 & IR_LINEAR)
261
0
               && ctx->cfg_map
262
0
               && insn->op != IR_PHI
263
0
               && !ir_check_domination(ctx, use, i)) {
264
0
                fprintf(stderr, "ir_base[%d].ops[%d] -> %d, %d doesn't dominate %d\n", i, j, use, use, i);
265
0
                ok = 0;
266
0
              }
267
0
              break;
268
0
            case IR_OPND_CONTROL:
269
0
              if (flags & IR_OP_FLAG_BB_START) {
270
0
                if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_BB_END)) {
271
0
                  fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be BB_END\n", i, j, use);
272
0
                  ok = 0;
273
0
                }
274
0
              } else {
275
0
                if (ir_op_flags[use_insn->op] & IR_OP_FLAG_BB_END) {
276
0
                  fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must not be BB_END\n", i, j, use);
277
0
                  ok = 0;
278
0
                }
279
0
              }
280
0
              if ((ctx->flags2 & IR_LINEAR)
281
0
               && use >= i
282
0
               && !(insn->op == IR_LOOP_BEGIN)) {
283
0
                fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use);
284
0
                ok = 0;
285
0
              }
286
0
              break;
287
0
            case IR_OPND_CONTROL_DEP:
288
0
              if ((ctx->flags2 & IR_LINEAR)
289
0
               && use >= i) {
290
0
                fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use);
291
0
                ok = 0;
292
0
              } else if (insn->op == IR_PHI) {
293
0
                ir_insn *merge_insn = &ctx->ir_base[insn->op1];
294
0
                if (merge_insn->op != IR_MERGE && merge_insn->op != IR_LOOP_BEGIN) {
295
0
                  fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be MERGE or LOOP_BEGIN\n", i, j, use);
296
0
                  ok = 0;
297
0
                }
298
0
              }
299
0
              break;
300
0
            case IR_OPND_CONTROL_REF:
301
0
              if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_CONTROL)) {
302
0
                fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be CONTROL\n", i, j, use);
303
0
                ok = 0;
304
0
              }
305
0
              break;
306
0
            case IR_OPND_CONTROL_GUARD:
307
0
              if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_BB_START)
308
0
               && use_insn->op != IR_GUARD
309
0
               && use_insn->op != IR_GUARD_NOT) {
310
0
                fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be BB_START or GUARD\n", i, j, use);
311
0
                ok = 0;
312
0
              }
313
0
              break;
314
0
            default:
315
0
              fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) of unsupported kind\n", i, j, use);
316
0
              ok = 0;
317
0
          }
318
0
        }
319
0
      } else if ((insn->op == IR_RETURN || insn->op == IR_UNREACHABLE) && j == 2) {
320
        /* pass (function returns void) */
321
0
      } else if (insn->op == IR_BEGIN && j == 1) {
322
        /* pass (start of unreachable basic block) */
323
0
      } else if (IR_OPND_KIND(flags, j) == IR_OPND_CONTROL_GUARD) {
324
        /* reference to control guard is optional */
325
0
      } else if (IR_OPND_KIND(flags, j) != IR_OPND_CONTROL_REF
326
0
          && (insn->op != IR_SNAPSHOT || j == 1)) {
327
0
        fprintf(stderr, "ir_base[%d].ops[%d] missing reference (%d)\n", i, j, use);
328
0
        ok = 0;
329
0
      }
330
0
      if (ctx->use_lists
331
0
       && use > 0
332
0
       && !ir_check_use_list(&check_ctx, ctx, use, i)) {
333
0
        fprintf(stderr, "ir_base[%d].ops[%d] is not in use list (%d)\n", i, j, use);
334
0
        ok = 0;
335
0
      }
336
0
    }
337
338
0
    switch (insn->op) {
339
0
      case IR_PHI:
340
0
        if (insn->inputs_count != ctx->ir_base[insn->op1].inputs_count + 1) {
341
0
          fprintf(stderr, "ir_base[%d] inconsistent PHI inputs_count (%d != %d)\n",
342
0
            i, insn->inputs_count, ctx->ir_base[insn->op1].inputs_count + 1);
343
0
          ok = 0;
344
0
        }
345
0
        break;
346
0
      case IR_LOAD:
347
0
      case IR_LOAD_v:
348
0
      case IR_STORE:
349
0
      case IR_STORE_v:
350
0
        type = ctx->ir_base[insn->op2].type;
351
0
        if (type != IR_ADDR
352
0
         && (!IR_IS_TYPE_INT(type) || ir_type_size[type] != ir_type_size[IR_ADDR])) {
353
0
          fprintf(stderr, "ir_base[%d].op2 must have ADDR type (%s)\n",
354
0
            i, ir_type_name[type]);
355
0
          ok = 0;
356
0
        }
357
0
        break;
358
0
      case IR_VLOAD:
359
0
      case IR_VLOAD_v:
360
0
      case IR_VSTORE:
361
0
      case IR_VSTORE_v:
362
0
        if (ctx->ir_base[insn->op2].op != IR_VAR) {
363
0
          fprintf(stderr, "ir_base[%d].op2 must be 'VAR' (%s)\n",
364
0
            i, ir_op_name[ctx->ir_base[insn->op2].op]);
365
0
          ok = 0;
366
0
        }
367
0
        break;
368
0
      case IR_RETURN:
369
0
        if (ctx->ret_type != (insn->op2 ? ctx->ir_base[insn->op2].type : IR_VOID)) {
370
0
          fprintf(stderr, "ir_base[%d].type incompatible return type\n", i);
371
0
          ok = 0;
372
0
        }
373
0
        break;
374
0
      case IR_TAILCALL:
375
0
        if (ctx->ret_type != insn->type) {
376
0
          fprintf(stderr, "ir_base[%d].type incompatible return type\n", i);
377
0
          ok = 0;
378
0
        }
379
0
        break;
380
0
      case IR_PARAM:
381
0
        if (i > 2 && ctx->ir_base[i - 1].op != IR_PARAM) {
382
0
          fprintf(stderr, "ir_base[%d].op PARAMs must be used only right after START\n", i);
383
0
          ok = 0;
384
0
        }
385
0
        break;
386
0
    }
387
388
0
    if (ctx->use_lists) {
389
0
      ir_use_list *use_list = &ctx->use_lists[i];
390
0
      ir_ref count, n = use_list->count;
391
392
0
      for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) {
393
0
        use = *p;
394
0
        if (!ir_check_input_list(&check_ctx, ctx, i, use)) {
395
0
          fprintf(stderr, "ir_base[%d] is in use list of ir_base[%d]\n", use, i);
396
0
          ok = 0;
397
0
        }
398
0
      }
399
400
0
      if ((flags & IR_OP_FLAG_CONTROL) && !(flags & IR_OP_FLAG_MEM)) {
401
0
        switch (insn->op) {
402
0
          case IR_SWITCH:
403
            /* may have many successors */
404
0
            if (use_list->count < 1) {
405
0
              fprintf(stderr, "ir_base[%d].op (SWITCH) must have at least 1 successor (%d)\n", i, use_list->count);
406
0
              ok = 0;
407
0
            }
408
0
            break;
409
0
          case IR_IF:
410
0
            if (use_list->count != 2) {
411
0
              fprintf(stderr, "ir_base[%d].op (IF) must have 2 successors (%d)\n", i, use_list->count);
412
0
              ok = 0;
413
0
            }
414
0
            break;
415
0
          case IR_UNREACHABLE:
416
0
          case IR_RETURN:
417
0
            if (use_list->count == 1) {
418
              /* UNREACHABLE and RETURN may be linked with the following ENTRY by a fake edge */
419
0
              if (ctx->ir_base[ctx->use_edges[use_list->refs]].op == IR_ENTRY) {
420
0
                break;
421
0
              }
422
0
            }
423
0
            IR_FALLTHROUGH;
424
0
          case IR_IJMP:
425
0
            if (use_list->count != 0) {
426
0
              fprintf(stderr, "ir_base[%d].op (%s) must not have successors (%d)\n",
427
0
                i, ir_op_name[insn->op], use_list->count);
428
0
              ok = 0;
429
0
            }
430
0
            break;
431
0
          case IR_IGOTO:
432
0
          case IR_ASM_GOTO:
433
0
            break;
434
0
          default:
435
            /* skip data references */
436
0
            count = n = use_list->count;
437
0
            for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) {
438
0
              use = *p;
439
0
              if (!(ir_op_flags[ctx->ir_base[use].op] & IR_OP_FLAG_CONTROL)) {
440
0
                count--;
441
0
              }
442
0
            }
443
0
            if (count != 1) {
444
0
              if (insn->op == IR_CALL && count == 2) {
445
                /* result of CALL may be used as data in control instruction */
446
0
                break;
447
0
              }
448
0
              if ((insn->op == IR_LOOP_END || insn->op == IR_END) && count == 2) {
449
                /* LOOP_END/END may be linked with the following ENTRY by a fake edge */
450
0
                if (ctx->ir_base[ctx->use_edges[use_list->refs]].op == IR_ENTRY) {
451
0
                  count--;
452
0
                }
453
0
                if (ctx->ir_base[ctx->use_edges[use_list->refs + 1]].op == IR_ENTRY) {
454
0
                  count--;
455
0
                }
456
0
                if (count == 1) {
457
0
                  break;
458
0
                }
459
0
              }
460
0
              if (count == 0 && (insn->op == IR_END || insn->op == IR_LOOP_END)) {
461
                /* Dead block */
462
0
                break;
463
0
              }
464
0
              fprintf(stderr, "ir_base[%d].op (%s) must have 1 successor (%d)\n",
465
0
                i, ir_op_name[insn->op], count);
466
0
              ok = 0;
467
0
            }
468
0
            break;
469
0
        }
470
0
      }
471
0
    }
472
0
    n = ir_insn_inputs_to_len(n);
473
0
    i += n;
474
0
    insn += n;
475
0
  }
476
477
0
  if (check_ctx.arena) {
478
0
    ir_arena_free(check_ctx.arena);
479
0
  }
480
481
//  if (!ok) {
482
//    ir_dump_codegen(ctx, stderr);
483
//  }
484
485
0
#ifndef IR_CHECK_NO_ABORT
486
0
  IR_ASSERT(ok);
487
0
#endif
488
489
0
  return ok;
490
0
}