/* * IR - Lightweight JIT Compilation Framework * (IR verification) * Copyright (C) 2022 Zend by Perforce. * Authors: Dmitry Stogov */ #include "ir.h" #include "ir_private.h" void ir_consistency_check(void) { IR_ASSERT(IR_UNUSED == 0); IR_ASSERT(IR_NOP == 0); IR_ASSERT((int)IR_BOOL == (int)IR_C_BOOL); IR_ASSERT((int)IR_U8 == (int)IR_C_U8); IR_ASSERT((int)IR_U16 == (int)IR_C_U16); IR_ASSERT((int)IR_U32 == (int)IR_C_U32); IR_ASSERT((int)IR_U64 == (int)IR_C_U64); IR_ASSERT((int)IR_ADDR == (int)IR_C_ADDR); IR_ASSERT((int)IR_CHAR == (int)IR_C_CHAR); IR_ASSERT((int)IR_I8 == (int)IR_C_I8); IR_ASSERT((int)IR_I16 == (int)IR_C_I16); IR_ASSERT((int)IR_I32 == (int)IR_C_I32); IR_ASSERT((int)IR_I64 == (int)IR_C_I64); IR_ASSERT((int)IR_DOUBLE == (int)IR_C_DOUBLE); IR_ASSERT((int)IR_FLOAT == (int)IR_C_FLOAT); IR_ASSERT((IR_EQ ^ 1) == IR_NE); IR_ASSERT((IR_LT ^ 3) == IR_GT); IR_ASSERT((IR_GT ^ 3) == IR_LT); IR_ASSERT((IR_LE ^ 3) == IR_GE); IR_ASSERT((IR_GE ^ 3) == IR_LE); IR_ASSERT((IR_ULT ^ 3) == IR_UGT); IR_ASSERT((IR_UGT ^ 3) == IR_ULT); IR_ASSERT((IR_ULE ^ 3) == IR_UGE); IR_ASSERT((IR_UGE ^ 3) == IR_ULE); IR_ASSERT(IR_ADD + 1 == IR_SUB); } static bool ir_check_use_list(ir_ctx *ctx, ir_ref from, ir_ref to) { ir_ref n, j, *p; ir_use_list *use_list = &ctx->use_lists[from]; n = use_list->count; for (j = 0, p = &ctx->use_edges[use_list->refs]; j < n; j++, p++) { if (*p == to) { return 1; } } return 0; } static bool ir_check_input_list(ir_ctx *ctx, ir_ref from, ir_ref to) { ir_insn *insn = &ctx->ir_base[to]; ir_ref n, j, *p; n = ir_input_edges_count(ctx, insn); for (j = 1, p = insn->ops + 1; j <= n; j++, p++) { if (*p == from) { return 1; } } return 0; } bool ir_check(ir_ctx *ctx) { ir_ref i, j, n, *p, use; ir_insn *insn, *use_insn; ir_type type; uint32_t flags; bool ok = 1; for (i = IR_UNUSED + 1, insn = ctx->ir_base + i; i < ctx->insns_count;) { flags = ir_op_flags[insn->op]; n = ir_input_edges_count(ctx, insn); for (j = 1, p = insn->ops + 1; j <= n; j++, p++) { use = *p; if (use != IR_UNUSED) { if (IR_IS_CONST_REF(use)) { if (use >= ctx->consts_count) { fprintf(stderr, "ir_base[%d].ops[%d] constant reference (%d) is out of range\n", i, j, use); ok = 0; } } else { if (use >= ctx->insns_count) { fprintf(stderr, "ir_base[%d].ops[%d] insn reference (%d) is out of range\n", i, j, use); ok = 0; } use_insn = &ctx->ir_base[use]; switch (IR_OPND_KIND(flags, j)) { case IR_OPND_DATA: if (use_insn->op == IR_VAR || !(ir_op_flags[use_insn->op] & IR_OP_FLAG_DATA)) { if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_MEM) || use_insn->type == IR_VOID) { fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be DATA\n", i, j, use); ok = 0; } } if (use >= i && !(insn->op == IR_PHI && j > 1 && ctx->ir_base[insn->op1].op == IR_LOOP_BEGIN)) { fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use); ok = 0; } if (flags & IR_OP_FLAG_DATA) { switch (insn->op) { case IR_COND: if (j == 1) { break; } IR_FALLTHROUGH; case IR_ADD: case IR_SUB: case IR_MUL: case IR_DIV: case IR_MOD: case IR_NEG: case IR_ABS: case IR_ADD_OV: case IR_SUB_OV: case IR_MUL_OV: case IR_NOT: case IR_OR: case IR_AND: case IR_XOR: case IR_SHL: case IR_SHR: case IR_SAR: case IR_ROL: case IR_ROR: case IR_BSWAP: case IR_MIN: case IR_MAX: case IR_PHI: case IR_COPY: case IR_PI: if (insn->type != use_insn->type) { if (j == 2 && (insn->op == IR_SHL || insn->op == IR_SHR || insn->op == IR_SAR || insn->op == IR_ROL || insn->op == IR_ROR) && ir_type_size[use_insn->type] < ir_type_size[insn->type]) { /* second argument of SHIFT may be incompatible with result */ break; } if (sizeof(void*) == 8) { if (insn->type == IR_ADDR && use_insn->type == IR_U64) { break; } } else { if (insn->type == IR_ADDR && use_insn->type == IR_U32) { break; } } fprintf(stderr, "ir_base[%d].ops[%d] (%d) type is incompatible with result type (%d != %d)\n", i, j, use, use_insn->type, insn->type); ok = 0; } break; } } break; case IR_OPND_CONTROL: if (flags & IR_OP_FLAG_BB_START) { if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_BB_END)) { fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be BB_END\n", i, j, use); ok = 0; } } else { if (ir_op_flags[use_insn->op] & IR_OP_FLAG_BB_END) { fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must not be BB_END\n", i, j, use); ok = 0; } } break; case IR_OPND_CONTROL_DEP: if (use >= i && !(insn->op == IR_LOOP_BEGIN)) { fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use); ok = 0; } else if (insn->op == IR_PHI) { ir_insn *merge_insn = &ctx->ir_base[insn->op1]; if (merge_insn->op != IR_MERGE && merge_insn->op != IR_LOOP_BEGIN) { fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be MERGE or LOOP_BEGIN\n", i, j, use); ok = 0; } } break; case IR_OPND_CONTROL_REF: if (!(ir_op_flags[use_insn->op] & IR_OP_FLAG_CONTROL)) { fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be CONTROL\n", i, j, use); ok = 0; } break; case IR_OPND_VAR: if (ctx->ir_base[use].op != IR_VAR) { fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must be VAR\n", i, j, use); ok = 0; } if (use >= i) { fprintf(stderr, "ir_base[%d].ops[%d] invalid forward reference (%d)\n", i, j, use); ok = 0; } break; default: fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) of unsupported kind\n", i, j, use); ok = 0; } } } else if ((insn->op == IR_RETURN || insn->op == IR_UNREACHABLE) && j == 2) { /* pass (function returns void) */ } else if (insn->op == IR_BEGIN && j == 1) { /* pass (start of unreachable basic block) */ // } else if (insn->op == IR_LOOP_BEGIN || insn->op == IR_MERGE) { // /* TODO: something wrong ??? */ } else if (IR_OPND_KIND(flags, j) != IR_OPND_CONTROL_REF && (insn->op != IR_SNAPSHOT || j == 1)) { fprintf(stderr, "ir_base[%d].ops[%d] missing reference (%d)\n", i, j, use); ok = 0; } if (ctx->use_lists && use > 0 && !ir_check_use_list(ctx, use, i)) { fprintf(stderr, "ir_base[%d].ops[%d] is not in use list (%d)\n", i, j, use); ok = 0; } } switch (insn->op) { case IR_LOAD: case IR_STORE: type = ctx->ir_base[insn->op2].type; if (type != IR_ADDR && (!IR_IS_TYPE_INT(type) || ir_type_size[type] != ir_type_size[IR_ADDR])) { fprintf(stderr, "ir_base[%d].op2 must have ADDR type (%s)\n", i, ir_type_name[type]); ok = 0; } break; case IR_VLOAD: case IR_VSTORE: if (ctx->ir_base[insn->op2].op != IR_VAR) { fprintf(stderr, "ir_base[%d].op2 must be 'VAR' (%s)\n", i, ir_op_name[ctx->ir_base[insn->op2].op]); ok = 0; } break; } if (ctx->use_lists) { ir_use_list *use_list = &ctx->use_lists[i]; ir_ref count; for (j = 0, p = &ctx->use_edges[use_list->refs]; j < use_list->count; j++, p++) { use = *p; if (!ir_check_input_list(ctx, i, use)) { fprintf(stderr, "ir_base[%d] is in use list of ir_base[%d]\n", use, i); ok = 0; } } if ((flags & IR_OP_FLAG_CONTROL) && !(flags & IR_OP_FLAG_MEM)) { switch (insn->op) { case IR_SWITCH: /* may have many successors */ if (use_list->count < 1) { fprintf(stderr, "ir_base[%d].op (SWITCH) must have at least 1 succesor (%d)\n", i, use_list->count); ok = 0; } break; case IR_IF: if (use_list->count != 2) { fprintf(stderr, "ir_base[%d].op (IF) must have 2 succesors (%d)\n", i, use_list->count); ok = 0; } break; case IR_UNREACHABLE: case IR_IJMP: case IR_RETURN: if (use_list->count == 1) { /* UNREACHABLE, IJMP and RETURN may be used in MERGE with the following ENTRY */ break; } if (use_list->count != 0) { fprintf(stderr, "ir_base[%d].op (%s) must not have successors (%d)\n", i, ir_op_name[insn->op], use_list->count); ok = 0; } break; default: /* skip data references */ count = use_list->count; for (j = 0, p = &ctx->use_edges[use_list->refs]; j < use_list->count; j++, p++) { use = *p; if (!(ir_op_flags[ctx->ir_base[use].op] & IR_OP_FLAG_CONTROL)) { count--; } } if (count != 1) { if (insn->op == IR_CALL && count == 2) { /* result of CALL may be used as data in control instruction */ break; } fprintf(stderr, "ir_base[%d].op (%s) must have 1 succesor (%d)\n", i, ir_op_name[insn->op], count); ok = 0; } break; } } } n = 1 + (n >> 2); // support for multi-word instructions like MERGE and PHI i += n; insn += n; } IR_ASSERT(ok); return ok; }