| // SPDX-License-Identifier: MIT |
| |
| #include "ir.h" |
| #include "linearize.h" |
| #include <stdlib.h> |
| #include <assert.h> |
| |
| |
| static int nbr_phi_operands(struct instruction *insn) |
| { |
| pseudo_t p; |
| int nbr = 0; |
| |
| if (!insn->phi_list) |
| return 0; |
| |
| FOR_EACH_PTR(insn->phi_list, p) { |
| if (p == VOID) |
| continue; |
| nbr++; |
| } END_FOR_EACH_PTR(p); |
| |
| return nbr; |
| } |
| |
| static int check_phi_node(struct instruction *insn) |
| { |
| struct basic_block *par; |
| pseudo_t phi; |
| int err = 0; |
| |
| if (!has_users(insn->target)) |
| return err; |
| |
| if (bb_list_size(insn->bb->parents) != nbr_phi_operands(insn)) { |
| sparse_error(insn->pos, "bad number of phi operands in:\n\t%s", |
| show_instruction(insn)); |
| info(insn->pos, "parents: %d", bb_list_size(insn->bb->parents)); |
| info(insn->pos, "phisrcs: %d", nbr_phi_operands(insn)); |
| return 1; |
| } |
| |
| PREPARE_PTR_LIST(insn->bb->parents, par); |
| FOR_EACH_PTR(insn->phi_list, phi) { |
| struct instruction *src; |
| if (phi == VOID) |
| continue; |
| assert(phi->type == PSEUDO_PHI); |
| src = phi->def; |
| if (src->bb != par) { |
| sparse_error(src->pos, "wrong BB for %s:", show_instruction(src)); |
| info(src->pos, "expected: %s", show_label(par)); |
| info(src->pos, " got: %s", show_label(src->bb)); |
| err++; |
| } |
| NEXT_PTR_LIST(par); |
| } END_FOR_EACH_PTR(phi); |
| FINISH_PTR_LIST(par); |
| return err; |
| } |
| |
| static int check_user(struct instruction *insn, pseudo_t pseudo) |
| { |
| struct instruction *def; |
| |
| if (!pseudo) { |
| show_entry(insn->bb->ep); |
| sparse_error(insn->pos, "null pseudo in %s", show_instruction(insn)); |
| return 1; |
| } |
| switch (pseudo->type) { |
| case PSEUDO_PHI: |
| case PSEUDO_REG: |
| def = pseudo->def; |
| if (def && def->bb) |
| break; |
| show_entry(insn->bb->ep); |
| sparse_error(insn->pos, "wrong usage for %s in %s", show_pseudo(pseudo), |
| show_instruction(insn)); |
| return 1; |
| |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static int check_branch(struct entrypoint *ep, struct instruction *insn, struct basic_block *bb) |
| { |
| if (bb->ep && lookup_bb(ep->bbs, bb)) |
| return 0; |
| sparse_error(insn->pos, "branch to dead BB: %s", show_instruction(insn)); |
| return 1; |
| } |
| |
| static int check_switch(struct entrypoint *ep, struct instruction *insn) |
| { |
| struct multijmp *jmp; |
| int err = 0; |
| |
| FOR_EACH_PTR(insn->multijmp_list, jmp) { |
| err = check_branch(ep, insn, jmp->target); |
| if (err) |
| return err; |
| } END_FOR_EACH_PTR(jmp); |
| |
| return err; |
| } |
| |
| static int check_return(struct instruction *insn) |
| { |
| struct symbol *ctype = insn->type; |
| |
| if (ctype && ctype->bit_size > 0 && insn->src == VOID) { |
| sparse_error(insn->pos, "return without value"); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int validate_insn(struct entrypoint *ep, struct instruction *insn) |
| { |
| int err = 0; |
| |
| switch (insn->opcode) { |
| case OP_SEL: |
| case OP_RANGE: |
| err += check_user(insn, insn->src3); |
| /* fall through */ |
| |
| case OP_BINARY ... OP_BINCMP_END: |
| err += check_user(insn, insn->src2); |
| /* fall through */ |
| |
| case OP_UNOP ... OP_UNOP_END: |
| case OP_SLICE: |
| case OP_SYMADDR: |
| case OP_PHISOURCE: |
| err += check_user(insn, insn->src1); |
| break; |
| |
| case OP_CBR: |
| err += check_branch(ep, insn, insn->bb_true); |
| err += check_branch(ep, insn, insn->bb_false); |
| /* fall through */ |
| case OP_COMPUTEDGOTO: |
| err += check_user(insn, insn->cond); |
| break; |
| |
| case OP_PHI: |
| err += check_phi_node(insn); |
| break; |
| |
| case OP_CALL: |
| // FIXME: ignore for now |
| break; |
| |
| case OP_STORE: |
| err += check_user(insn, insn->target); |
| /* fall through */ |
| |
| case OP_LOAD: |
| err += check_user(insn, insn->src); |
| break; |
| |
| case OP_RET: |
| err += check_return(insn); |
| break; |
| |
| case OP_BR: |
| err += check_branch(ep, insn, insn->bb_true); |
| break; |
| case OP_SWITCH: |
| err += check_switch(ep, insn); |
| break; |
| |
| case OP_ENTRY: |
| case OP_SETVAL: |
| default: |
| break; |
| } |
| |
| return err; |
| } |
| |
| int ir_validate(struct entrypoint *ep) |
| { |
| struct basic_block *bb; |
| int err = 0; |
| |
| if (!dbg_ir || has_error) |
| return 0; |
| |
| FOR_EACH_PTR(ep->bbs, bb) { |
| struct instruction *insn; |
| FOR_EACH_PTR(bb->insns, insn) { |
| if (!insn->bb) |
| continue; |
| err += validate_insn(ep, insn); |
| } END_FOR_EACH_PTR(insn); |
| } END_FOR_EACH_PTR(bb); |
| |
| if (err) |
| abort(); |
| return err; |
| } |