From a3620cd6e704f63c16672146d7c7595dba6ccf00 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 12 Feb 2024 09:36:33 +0300 Subject: [PATCH] Update IR IR commit: d2ab283e3c3bb4b5ba21701a4d18f14fd9b3d798 --- ext/opcache/jit/ir/ir.c | 7 +- ext/opcache/jit/ir/ir_aarch64.dasc | 94 ++++++++++++++++++---- ext/opcache/jit/ir/ir_fold.h | 98 +++++++++++++++++++++++ ext/opcache/jit/ir/ir_x86.dasc | 121 ++++++++++++++++++++++++++--- 4 files changed, 291 insertions(+), 29 deletions(-) diff --git a/ext/opcache/jit/ir/ir.c b/ext/opcache/jit/ir/ir.c index 5a76384e1b3..16a8fc33cf6 100644 --- a/ext/opcache/jit/ir/ir.c +++ b/ext/opcache/jit/ir/ir.c @@ -878,6 +878,7 @@ static ir_ref _ir_fold_cse(ir_ctx *ctx, uint32_t opt, ir_ref op1, ir_ref op2, ir #define IR_FOLD_CONST_F(_val) do { \ val.f = (_val); \ + val.u32_hi = 0; \ goto ir_fold_const; \ } while (0) @@ -1756,7 +1757,7 @@ static ir_ref ir_find_aliasing_load(ir_ctx *ctx, ir_ref ref, ir_type type, ir_re } } else if (insn->op == IR_RSTORE) { modified_regset |= (1 << insn->op3); - } else if (insn->op >= IR_START || insn->op == IR_CALL || insn->op == IR_VSTORE) { + } else if (insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN || insn->op == IR_CALL || insn->op == IR_VSTORE) { return IR_UNUSED; } ref = insn->op1; @@ -2322,7 +2323,7 @@ void _ir_GUARD(ir_ctx *ctx, ir_ref condition, ir_ref addr) condition = IR_FALSE; break; } - } else if (insn->op >= IR_START) { + } else if (insn->op == IR_START || insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN) { break; } ref = insn->op1; @@ -2354,7 +2355,7 @@ void _ir_GUARD_NOT(ir_ctx *ctx, ir_ref condition, ir_ref addr) condition = IR_TRUE; break; } - } else if (insn->op >= IR_START) { + } else if (insn->op == IR_START || insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN) { break; } ref = insn->op1; diff --git a/ext/opcache/jit/ir/ir_aarch64.dasc b/ext/opcache/jit/ir/ir_aarch64.dasc index de248eb2885..3dabfeb0dfc 100644 --- a/ext/opcache/jit/ir/ir_aarch64.dasc +++ b/ext/opcache/jit/ir/ir_aarch64.dasc @@ -160,6 +160,14 @@ static bool aarch64_may_encode_addr_offset(int64_t offset, uint32_t type_size) || } |.endmacro +|.macro ASM_REG_REG_REG_TXT_OP, op, type, dst, src1, src2, txt +|| if (ir_type_size[type] == 8) { +| op Rx(dst), Rx(src1), Rx(src2), txt +|| } else { +| op Rw(dst), Rw(src1), Rw(src2), txt +|| } +|.endmacro + |.macro ASM_REG_REG_REG_REG_OP, op, type, dst, src1, src2, src3 || if (ir_type_size[type] == 8) { | op Rx(dst), Rx(src1), Rx(src2), Rx(src3) @@ -280,6 +288,7 @@ const char *ir_reg_name(int8_t reg, ir_type type) _(DIV_PWR2) \ _(MOD_PWR2) \ _(SDIV_PWR2) \ + _(SMOD_PWR2) \ _(OP_INT) \ _(OP_FP) \ _(BINOP_INT) \ @@ -378,10 +387,6 @@ int ir_get_target_constraints(ir_ctx *ctx, ir_ref ref, ir_target_constraints *co n++; } break; - case IR_SEXT: - case IR_ZEXT: - flags = IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG; - IR_FALLTHROUGH; case IR_MUL_PWR2: case IR_DIV_PWR2: case IR_MOD_PWR2: @@ -432,6 +437,17 @@ int ir_get_target_constraints(ir_ctx *ctx, ir_ref ref, ir_target_constraints *co } } break; + case IR_SMOD_PWR2: + flags = IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG; + insn = &ctx->ir_base[ref]; + n = 0; + if (IR_IS_CONST_REF(insn->op1)) { + constraints->tmp_regs[n] = IR_TMP_REG(1, insn->type, IR_LOAD_SUB_REF, IR_DEF_SUB_REF); + n++; + } + constraints->tmp_regs[n] = IR_TMP_REG(3, insn->type, IR_USE_SUB_REF, IR_SAVE_SUB_REF); + n++; + break; case IR_CTPOP: flags = IR_USE_MUST_BE_IN_REG | IR_OP1_MUST_BE_IN_REG; insn = &ctx->ir_base[ref]; @@ -565,6 +581,13 @@ int ir_get_target_constraints(ir_ctx *ctx, ir_ref ref, ir_target_constraints *co break; case IR_COPY_INT: case IR_COPY_FP: + case IR_TRUNC: + case IR_BITCAST: + case IR_PROTO: + flags = IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG; + break; + case IR_ZEXT: + case IR_SEXT: flags = IR_DEF_REUSES_OP1_REG | IR_USE_MUST_BE_IN_REG; break; case IR_PARAM: @@ -582,11 +605,6 @@ int ir_get_target_constraints(ir_ctx *ctx, ir_ref ref, ir_target_constraints *co case IR_EXITCALL: constraints->def_reg = IR_REG_INT_RET1; break; - case IR_TRUNC: - case IR_BITCAST: - case IR_PROTO: - flags = IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG; - break; case IR_RSTORE: flags = IR_OP3_SHOULD_BE_IN_REG; break; @@ -750,9 +768,12 @@ binop_fp: /* pass */ } else if (IR_IS_CONST_REF(insn->op1)) { // const - } else if (IR_IS_TYPE_UNSIGNED(insn->type) && IR_IS_POWER_OF_TWO(op2_insn->val.u64)) { - // TODO: signed division by power of two ??? - return IR_MOD_PWR2; + } else if (IR_IS_POWER_OF_TWO(op2_insn->val.u64)) { + if (IR_IS_TYPE_UNSIGNED(insn->type)) { + return IR_MOD_PWR2; + } else { + return IR_SMOD_PWR2; + } } } return IR_BINOP_INT; @@ -2073,11 +2094,9 @@ static void ir_emit_mul_div_mod_pwr2(ir_ctx *ctx, ir_ref def, ir_insn *insn) } } else if (insn->op == IR_DIV) { uint32_t shift = IR_LOG2(ctx->ir_base[insn->op2].val.u64); - IR_ASSERT(IR_IS_TYPE_UNSIGNED(type)); | ASM_REG_REG_IMM_OP lsr, type, def_reg, op1_reg, shift } else { IR_ASSERT(insn->op == IR_MOD); - IR_ASSERT(IR_IS_TYPE_UNSIGNED(type)); uint64_t mask = ctx->ir_base[insn->op2].val.u64 - 1; | ASM_REG_REG_IMM_OP and, type, def_reg, op1_reg, mask } @@ -2142,6 +2161,50 @@ static void ir_emit_sdiv_pwr2(ir_ctx *ctx, ir_ref def, ir_insn *insn) } } +static void ir_emit_smod_pwr2(ir_ctx *ctx, ir_ref def, ir_insn *insn) +{ + ir_backend_data *data = ctx->data; + dasm_State **Dst = &data->dasm_state; + ir_type type = insn->type; + ir_ref op1 = insn->op1; + ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]); + ir_reg op1_reg = ctx->regs[def][1]; + ir_reg tmp_reg = ctx->regs[def][3]; +// uint32_t shift = IR_LOG2(ctx->ir_base[insn->op2].val.u64); + uint64_t mask = ctx->ir_base[insn->op2].val.u64 - 1; + + IR_ASSERT(IR_IS_CONST_REF(insn->op2)); + IR_ASSERT(!IR_IS_SYM_CONST(ctx->ir_base[insn->op2].op)); + IR_ASSERT(def_reg != IR_REG_NONE && tmp_reg != IR_REG_NONE && def_reg != tmp_reg); + + if (op1_reg != IR_REG_NONE && IR_REG_SPILLED(op1_reg)) { + op1_reg = IR_REG_NUM(op1_reg); + ir_emit_load(ctx, type, op1_reg, op1); + } + if (def_reg != op1_reg) { + if (op1_reg != IR_REG_NONE) { + ir_emit_mov(ctx, type, def_reg, op1_reg); + } else { + ir_emit_load(ctx, type, def_reg, op1); + } + } + +// | ASM_REG_REG_IMM_OP asr, type, tmp_reg, def_reg, (ir_type_size[type]*8-1) +// | ASM_REG_REG_IMM_OP lsr, type, tmp_reg, tmp_reg, (ir_type_size[type]*8-shift) +// | ASM_REG_REG_REG_OP add, type, def_reg, def_reg, tmp_reg +// | ASM_REG_REG_IMM_OP and, type, def_reg, def_reg, mask +// | ASM_REG_REG_REG_OP sub, type, def_reg, def_reg, tmp_reg + + | ASM_REG_REG_OP negs, type, tmp_reg, def_reg + | ASM_REG_REG_IMM_OP and, type, def_reg, def_reg, mask + | ASM_REG_REG_IMM_OP and, type, tmp_reg, tmp_reg, mask + | ASM_REG_REG_REG_TXT_OP csneg, type, def_reg, def_reg, tmp_reg, mi + + if (IR_REG_SPILLED(ctx->regs[def][0])) { + ir_emit_store(ctx, type, def, def_reg); + } +} + static void ir_emit_shift(ir_ctx *ctx, ir_ref def, ir_insn *insn) { ir_backend_data *data = ctx->data; @@ -5625,6 +5688,9 @@ void *ir_emit_code(ir_ctx *ctx, size_t *size_ptr) case IR_SDIV_PWR2: ir_emit_sdiv_pwr2(ctx, i, insn); break; + case IR_SMOD_PWR2: + ir_emit_smod_pwr2(ctx, i, insn); + break; case IR_SHIFT: ir_emit_shift(ctx, i, insn); break; diff --git a/ext/opcache/jit/ir/ir_fold.h b/ext/opcache/jit/ir/ir_fold.h index ab39bd6f2f9..08f186dda25 100644 --- a/ext/opcache/jit/ir/ir_fold.h +++ b/ext/opcache/jit/ir/ir_fold.h @@ -1992,6 +1992,104 @@ IR_FOLD(GE(ABS, C_DOUBLE)) } // TODO: conversions +IR_FOLD(FP2FP(FP2FP)) +{ + if (IR_OPT_TYPE(opt) == IR_FLOAT) { + /* (float)(double)f => f */ + IR_ASSERT(op1_insn->type == IR_DOUBLE); + IR_ASSERT(ctx->ir_base[op1_insn->op1].type == IR_FLOAT); + IR_FOLD_COPY(op1_insn->op1); + } + IR_FOLD_NEXT; +} + +IR_FOLD(FP2INT(INT2FP)) +{ + ir_type dst_type = IR_OPT_TYPE(opt); + ir_type src_type = ctx->ir_base[op1_insn->op1].type; + + if (ir_type_size[src_type] >= ir_type_size[op1_insn->type]) { + /* source integer type can not fit into intermediate floating point */ + IR_FOLD_NEXT; + } + /* (int)(double)i => i */ + if (src_type == dst_type) { + IR_FOLD_COPY(op1_insn->op1); + } + IR_FOLD_NEXT; +} + +IR_FOLD(TRUNC(ZEXT)) +IR_FOLD(TRUNC(SEXT)) +{ + ir_type dst_type = IR_OPT_TYPE(opt); + ir_type src_type = ctx->ir_base[op1_insn->op1].type; + + /* (int32_t)(int64_t)i => i */ + if (src_type == dst_type) { + IR_FOLD_COPY(op1_insn->op1); + } else if (ir_type_size[src_type] == ir_type_size[dst_type]) { + opt = IR_OPT(IR_BITCAST, dst_type); + op1 = op1_insn->op1; + IR_FOLD_RESTART; + } else if (ir_type_size[src_type] > ir_type_size[dst_type]) { + opt = IR_OPT(IR_TRUNC, dst_type); + op1 = op1_insn->op1; + IR_FOLD_RESTART; + } else { + opt = IR_OPT(op1_insn->op, dst_type); + op1 = op1_insn->op1; + IR_FOLD_RESTART; + } + IR_FOLD_NEXT; +} + +IR_FOLD(TRUNC(AND)) +{ + if (IR_IS_CONST_REF(op1_insn->op2)) { + size_t size = ir_type_size[IR_OPT_TYPE(opt)]; + uint64_t mask = ctx->ir_base[op1_insn->op2].val.u64; + + if (size == 1) { + if (mask == 0xff) { + op1 = op1_insn->op1; + IR_FOLD_RESTART; + } + } else if (size == 2) { + if (mask == 0xffff) { + op1 = op1_insn->op1; + IR_FOLD_RESTART; + } + } else if (size == 4) { + if (mask == 0xffffffff) { + op1 = op1_insn->op1; + IR_FOLD_RESTART; + } + } + } + IR_FOLD_NEXT; +} + +IR_FOLD(EQ(FP2FP, C_DOUBLE)) +IR_FOLD(NE(FP2FP, C_DOUBLE)) +IR_FOLD(LT(FP2FP, C_DOUBLE)) +IR_FOLD(GE(FP2FP, C_DOUBLE)) +IR_FOLD(LE(FP2FP, C_DOUBLE)) +IR_FOLD(GT(FP2FP, C_DOUBLE)) +IR_FOLD(ULT(FP2FP, C_DOUBLE)) +IR_FOLD(UGE(FP2FP, C_DOUBLE)) +IR_FOLD(ULE(FP2FP, C_DOUBLE)) +IR_FOLD(UGT(FP2FP, C_DOUBLE)) +{ + IR_ASSERT(op1_insn->type == IR_DOUBLE); + IR_ASSERT(ctx->ir_base[op1_insn->op1].type == IR_FLOAT); + if (op2_insn->val.d == (double)(float)op2_insn->val.d) { + op1 = op1_insn->op1; + op2 = ir_const_float(ctx, (float)op2_insn->val.d); + IR_FOLD_RESTART; + } + IR_FOLD_NEXT; +} // TODO: Reassociation IR_FOLD(ADD(ADD, C_U8)) diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc index 2e74d48d680..8cc747d5733 100644 --- a/ext/opcache/jit/ir/ir_x86.dasc +++ b/ext/opcache/jit/ir/ir_x86.dasc @@ -780,7 +780,7 @@ IR_ALWAYS_INLINE ir_mem IR_MEM(ir_reg base, int32_t offset, ir_reg index, int32_ | op..d xmm(op1-IR_REG_FP_FIRST), xmm(op2-IR_REG_FP_FIRST), qword op3 || } else { || IR_ASSERT(type == IR_FLOAT); -| op..s xmm(op2-IR_REG_FP_FIRST), xmm(op2-IR_REG_FP_FIRST), dword op3 +| op..s xmm(op1-IR_REG_FP_FIRST), xmm(op2-IR_REG_FP_FIRST), dword op3 || } |.endmacro @@ -979,6 +979,7 @@ const char *ir_reg_name(int8_t reg, ir_type type) _(DIV_PWR2) \ _(MOD_PWR2) \ _(SDIV_PWR2) \ + _(SMOD_PWR2) \ _(BOOL_NOT_INT) \ _(ABS_INT) \ _(OP_INT) \ @@ -1303,18 +1304,41 @@ op2_const: case IR_DEC: case IR_MUL_PWR2: case IR_DIV_PWR2: - case IR_MOD_PWR2: case IR_OP_INT: case IR_OP_FP: flags = IR_DEF_REUSES_OP1_REG | IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG; break; + case IR_MOD_PWR2: + flags = IR_DEF_REUSES_OP1_REG | IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG; + insn = &ctx->ir_base[ref]; + if (ir_type_size[insn->type] == 8) { + int64_t offset = ctx->ir_base[insn->op2].val.u64 - 1; + if (!IR_IS_SIGNED_32BIT(offset)) { + constraints->tmp_regs[n] = IR_TMP_REG(2, insn->type, IR_LOAD_SUB_REF, IR_DEF_SUB_REF); + n++; + } + } + break; + case IR_SMOD_PWR2: + flags = IR_DEF_REUSES_OP1_REG | IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG; + insn = &ctx->ir_base[ref]; + if (ir_type_size[insn->type] == 8) { + int64_t offset = ctx->ir_base[insn->op2].val.u64 - 1; + if (!IR_IS_SIGNED_32BIT(offset)) { + constraints->tmp_regs[n] = IR_TMP_REG(2, insn->type, IR_LOAD_SUB_REF, IR_DEF_SUB_REF); + n++; + } + } + constraints->tmp_regs[n] = IR_TMP_REG(3, insn->type, IR_USE_SUB_REF, IR_SAVE_SUB_REF); + n++; + break; case IR_SDIV_PWR2: flags = IR_DEF_CONFLICTS_WITH_INPUT_REGS | IR_USE_MUST_BE_IN_REG | IR_OP1_MUST_BE_IN_REG; insn = &ctx->ir_base[ref]; if (ir_type_size[insn->type] == 8) { int64_t offset = ctx->ir_base[insn->op2].val.u64 - 1; if (!IR_IS_SIGNED_32BIT(offset)) { - constraints->tmp_regs[n] = IR_TMP_REG(3, insn->type, IR_LOAD_SUB_REF, IR_DEF_SUB_REF); + constraints->tmp_regs[n] = IR_TMP_REG(2, insn->type, IR_LOAD_SUB_REF, IR_DEF_SUB_REF); n++; } } @@ -1345,6 +1369,9 @@ op2_const: case IR_COPY_FP: case IR_SEXT: case IR_ZEXT: + case IR_TRUNC: + case IR_BITCAST: + case IR_PROTO: flags = IR_DEF_REUSES_OP1_REG | IR_USE_MUST_BE_IN_REG | IR_OP1_SHOULD_BE_IN_REG; break; case IR_ABS_INT: @@ -1929,12 +1956,13 @@ binop_fp: /* pass */ } else if (IR_IS_CONST_REF(insn->op1)) { // const - } else if (IR_IS_TYPE_UNSIGNED(insn->type) - && IR_IS_POWER_OF_TWO(op2_insn->val.u64) - && IR_IS_UNSIGNED_32BIT(op2_insn->val.u64 - 1)) { - // TODO: signed division by power of two ??? + } else if (IR_IS_POWER_OF_TWO(op2_insn->val.u64)) { /* MOD(X, PWR2) => AND */ - return IR_MOD_PWR2; + if (IR_IS_TYPE_UNSIGNED(insn->type)) { + return IR_MOD_PWR2; + } else { + return IR_SMOD_PWR2; + } } } ir_match_fuse_load(ctx, insn->op2, ref); @@ -3716,6 +3744,7 @@ static void ir_emit_mul_div_mod_pwr2(ir_ctx *ctx, ir_ref def, ir_insn *insn) } if (insn->op == IR_MUL) { uint32_t shift = IR_LOG2(ctx->ir_base[insn->op2].val.u64); + if (shift == 1) { | ASM_REG_REG_OP add, type, def_reg, def_reg } else { @@ -3723,14 +3752,24 @@ static void ir_emit_mul_div_mod_pwr2(ir_ctx *ctx, ir_ref def, ir_insn *insn) } } else if (insn->op == IR_DIV) { uint32_t shift = IR_LOG2(ctx->ir_base[insn->op2].val.u64); - IR_ASSERT(IR_IS_TYPE_UNSIGNED(type)); + | ASM_REG_IMM_OP shr, type, def_reg, shift } else { IR_ASSERT(insn->op == IR_MOD); uint64_t mask = ctx->ir_base[insn->op2].val.u64 - 1; - IR_ASSERT(IR_IS_TYPE_UNSIGNED(type)); - IR_ASSERT(IR_IS_UNSIGNED_32BIT(mask)); - | ASM_REG_IMM_OP and, type, def_reg, mask + +|.if X64 +|| if (ir_type_size[type] == 8 && ctx->regs[def][2] != IR_REG_NONE) { +|| ir_reg op2_reg = ctx->regs[def][2]; +|| +|| ir_emit_load_imm_int(ctx, type, op2_reg, mask); + | ASM_REG_REG_OP and, type, def_reg, op2_reg +|| } else { +|.endif + | ASM_REG_IMM_OP and, type, def_reg, mask +|.if X64 +|| } +|.endif } if (IR_REG_SPILLED(ctx->regs[def][0])) { ir_emit_store(ctx, type, def, def_reg); @@ -3798,6 +3837,61 @@ static void ir_emit_sdiv_pwr2(ir_ctx *ctx, ir_ref def, ir_insn *insn) } } +static void ir_emit_smod_pwr2(ir_ctx *ctx, ir_ref def, ir_insn *insn) +{ + ir_backend_data *data = ctx->data; + dasm_State **Dst = &data->dasm_state; + ir_type type = insn->type; + ir_ref op1 = insn->op1; + ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]); + ir_reg op1_reg = ctx->regs[def][1]; + ir_reg tmp_reg = ctx->regs[def][3]; + uint32_t shift = IR_LOG2(ctx->ir_base[insn->op2].val.u64); + uint64_t mask = ctx->ir_base[insn->op2].val.u64 - 1; + + IR_ASSERT(IR_IS_CONST_REF(insn->op2)); + IR_ASSERT(!IR_IS_SYM_CONST(ctx->ir_base[insn->op2].op)); + IR_ASSERT(def_reg != IR_REG_NONE && tmp_reg != IR_REG_NONE && def_reg != tmp_reg); + + if (op1_reg != IR_REG_NONE && IR_REG_SPILLED(op1_reg)) { + op1_reg = IR_REG_NUM(op1_reg); + ir_emit_load(ctx, type, op1_reg, op1); + } + if (def_reg != op1_reg) { + if (op1_reg != IR_REG_NONE) { + ir_emit_mov(ctx, type, def_reg, op1_reg); + } else { + ir_emit_load(ctx, type, def_reg, op1); + } + } + if (tmp_reg != op1_reg) { + ir_emit_mov(ctx, type, tmp_reg, def_reg); + } + + | ASM_REG_IMM_OP sar, type, tmp_reg, (ir_type_size[type]*8-1) + | ASM_REG_IMM_OP shr, type, tmp_reg, (ir_type_size[type]*8-shift) + | ASM_REG_REG_OP add, type, def_reg, tmp_reg + +|.if X64 +|| if (ir_type_size[type] == 8 && ctx->regs[def][2] != IR_REG_NONE) { +|| ir_reg op2_reg = ctx->regs[def][2]; +|| +|| ir_emit_load_imm_int(ctx, type, op2_reg, mask); + | ASM_REG_REG_OP and, type, def_reg, op2_reg +|| } else { +|.endif + | ASM_REG_IMM_OP and, type, def_reg, mask +|.if X64 +|| } +|.endif + + | ASM_REG_REG_OP sub, type, def_reg, tmp_reg + + if (IR_REG_SPILLED(ctx->regs[def][0])) { + ir_emit_store(ctx, type, def, def_reg); + } +} + static void ir_emit_mem_mul_div_mod_pwr2(ir_ctx *ctx, ir_ref def, ir_insn *insn) { ir_backend_data *data = ctx->data; @@ -9237,6 +9331,9 @@ void *ir_emit_code(ir_ctx *ctx, size_t *size_ptr) case IR_SDIV_PWR2: ir_emit_sdiv_pwr2(ctx, i, insn); break; + case IR_SMOD_PWR2: + ir_emit_smod_pwr2(ctx, i, insn); + break; case IR_SHIFT: ir_emit_shift(ctx, i, insn); break;