commit 7eaf7bf92d5dc73af08024a55e2495bd71439b25
parent 52897e0145b4e42aa1cfd85e10839ed370938ba1
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 2 Jun 2026 05:36:01 -0700
cg: make unreachable a first-class terminator hook + IR op (Track 4b)
Diffstat:
28 files changed, 95 insertions(+), 34 deletions(-)
diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c
@@ -3347,7 +3347,6 @@ static void aa_intrinsic(NativeTarget* t, IntrinKind kind,
case INTRIN_PREFETCH:
return;
case INTRIN_TRAP:
- case INTRIN_UNREACHABLE:
aa_trap(t);
return;
default:
diff --git a/src/arch/c_target/c_emit.c b/src/arch/c_target/c_emit.c
@@ -2391,6 +2391,16 @@ void c_emit_ret(
t->last_was_terminator = 1;
}
+/* === unreachable ===
+ * Control terminator for statically-unreachable code (the C
+ * __builtin_unreachable point). Ends the basic block; emit the host
+ * compiler's `__builtin_unreachable()` so it sees the path is dead. */
+void c_emit_unreachable(CTarget* t) {
+ if (t->last_was_terminator) return;
+ cbuf_puts(&t->body, " __builtin_unreachable();\n");
+ t->last_was_terminator = 1;
+}
+
/* === alias ===
* `cfree_cg_alias` makes alias_sym refer to target_sym's body. In obj-file
* land that's two ObjSyms sharing a (section_id, value); in C source we
@@ -2518,9 +2528,6 @@ void c_emit_intrinsic(CTarget* t, IntrinKind k, Operand* dsts, u32 ndst,
const Operand* args, u32 narg) {
SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0};
switch (k) {
- case INTRIN_UNREACHABLE:
- cbuf_puts(&t->body, " __builtin_unreachable();\n");
- return;
case INTRIN_TRAP:
cbuf_puts(&t->body, " __builtin_trap();\n");
return;
diff --git a/src/arch/c_target/c_emit.h b/src/arch/c_target/c_emit.h
@@ -194,6 +194,7 @@ void c_emit_alias(CTarget*, ObjSymId, ObjSymId, CfreeCgTypeId);
/* Re-emit a file-scope `__asm__("...")` block verbatim at TU scope. */
void c_emit_file_scope_asm(CTarget*, const char* src, size_t len);
void c_emit_ret(CTarget*, const CGLocal*, u32);
+void c_emit_unreachable(CTarget*);
void c_emit_load_imm(CTarget*, Operand, i64);
void c_emit_load_const(CTarget*, Operand, ConstBytes);
void c_emit_copy(CTarget*, Operand, Operand);
diff --git a/src/arch/c_target/ir_emit.c b/src/arch/c_target/ir_emit.c
@@ -129,6 +129,9 @@ static void ir_emit_inst(CIrEmitter* e, const CgIrInst* in) {
c_emit_ret(t, aux->values, aux->nvalues);
return;
}
+ case CG_IR_UNREACHABLE:
+ c_emit_unreachable(t);
+ return;
case CG_IR_BR:
c_emit_jump(t, (Label)in->extra.imm);
return;
diff --git a/src/arch/check_target.c b/src/arch/check_target.c
@@ -237,6 +237,8 @@ static void check_ret(CgTarget* t, const CGLocal* values, u32 nvalues) {
(void)nvalues;
}
+static void check_unreachable(CgTarget* t) { (void)t; }
+
static void check_alloca(CgTarget* t, Operand dst, Operand size, u32 align) {
(void)t;
(void)dst;
@@ -401,6 +403,7 @@ static CgTarget* check_backend_make(Compiler* c, ObjBuilder* o,
t->convert = check_convert;
t->call = check_call;
t->ret = check_ret;
+ t->unreachable = check_unreachable;
t->alloca_ = check_alloca;
t->va_start_ = check_one_operand;
t->va_arg_ = check_va_arg;
diff --git a/src/arch/rv64/native.c b/src/arch/rv64/native.c
@@ -2624,7 +2624,6 @@ static void rv_intrinsic(NativeTarget* t, IntrinKind kind, const NativeLoc* dsts
}
case INTRIN_PREFETCH:
return;
- case INTRIN_UNREACHABLE:
case INTRIN_TRAP:
rv64_emit32(mc, rv_ebreak());
return;
diff --git a/src/arch/wasm/emit.c b/src/arch/wasm/emit.c
@@ -1588,8 +1588,6 @@ static const char* intrin_name(IntrinKind k) {
return "__builtin_assume_aligned";
case INTRIN_EXPECT:
return "__builtin_expect";
- case INTRIN_UNREACHABLE:
- return "__builtin_unreachable";
case INTRIN_TRAP:
return "__builtin_trap";
case INTRIN_SETJMP:
@@ -1619,7 +1617,6 @@ void wasm_intrinsic(CGTarget* tg, IntrinKind k, Operand* dst, u32 ndst,
switch (k) {
case INTRIN_TRAP:
- case INTRIN_UNREACHABLE:
wasm_emit_unreachable(t);
return;
@@ -2025,6 +2022,10 @@ void wasm_emit_unreachable(WTarget* t) {
t->dead = 1;
}
+/* Control terminator (the C __builtin_unreachable point): emit the Wasm
+ * `unreachable` opcode, which traps if reached. Ends the current block. */
+void wasm_unreachable(CGTarget* tg) { wasm_emit_unreachable((WTarget*)tg); }
+
/* -----------------------------------------------------------------
* WIR -> WasmFunc lowering
* ----------------------------------------------------------------- */
diff --git a/src/arch/wasm/ir_emit.c b/src/arch/wasm/ir_emit.c
@@ -41,6 +41,7 @@ void wasm_cmp(CGTarget*, CmpOp, Operand, Operand, Operand);
void wasm_convert(CGTarget*, ConvKind, Operand, Operand);
void wasm_call(CGTarget*, const CGCallDesc*);
void wasm_ret(CGTarget*, const CGABIValue*);
+void wasm_unreachable(CGTarget*);
void wasm_alloca(CGTarget*, Operand, Operand, u32);
void wasm_va_start(CGTarget*, Operand);
void wasm_va_arg(CGTarget*, Operand, Operand, CfreeCgTypeId);
@@ -639,6 +640,9 @@ static void wasm_ir_emit_inst(WasmIrEmitter* e, const CgIrFunc* f,
case CG_IR_RET:
wasm_ir_emit_ret(e, f, in);
return;
+ case CG_IR_UNREACHABLE:
+ wasm_unreachable(t);
+ return;
case CG_IR_BR:
wasm_jump(t, (Label)in->extra.imm);
return;
diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c
@@ -3216,7 +3216,6 @@ static void x64_intrinsic(NativeTarget* t, IntrinKind kind,
return;
case INTRIN_PREFETCH:
return;
- case INTRIN_UNREACHABLE:
case INTRIN_TRAP:
emit_ud2(mc);
return;
diff --git a/src/cg/cgtarget.h b/src/cg/cgtarget.h
@@ -102,8 +102,10 @@ typedef enum ConvKind {
/* Compiler-intrinsic kinds dispatched through CgTarget.intrinsic and carried
* on IR_INTRINSIC via IRIntrinAux.kind. The set is bounded: a backend
* must know each one to choose inline-vs-libcall. Hint intrinsics
- * (EXPECT/UNREACHABLE/TRAP/PREFETCH/ASSUME_ALIGNED) ride the same dispatch:
+ * (EXPECT/TRAP/PREFETCH/ASSUME_ALIGNED) ride the same dispatch:
* the backend decides whether they emit an instruction or a no-op.
+ * `unreachable` is NOT here: it is a first-class control terminator with
+ * its own CgTarget hook (see below), not an intrinsic.
*
* Not every C builtin lives here. Parser-evaluated builtins
* (__builtin_offsetof, __builtin_constant_p, __builtin_choose_expr,
@@ -129,7 +131,6 @@ typedef enum IntrinKind {
/* hints */
INTRIN_EXPECT,
- INTRIN_UNREACHABLE,
INTRIN_TRAP,
/* non-local control */
@@ -601,6 +602,14 @@ struct CgTarget {
* target supports no tail calls at all. */
const char* (*tail_call_unrealizable_reason)(CgTarget*, const CGCallDesc*);
void (*ret)(CgTarget*, const CGLocal* values, u32 nvalues);
+ /* Control terminator marking statically-unreachable code (the C
+ * __builtin_unreachable point). Like ret/jump it ends the current basic
+ * block: no fall-through successor is implied. Backends typically emit a
+ * trap instruction (brk/ud2/ebreak), a Wasm `unreachable`, or a
+ * `__builtin_unreachable()` in the C-source target; an interpreter faults.
+ * Distinct from INTRIN_TRAP, which is an expression-level intrinsic that
+ * does not terminate the block. */
+ void (*unreachable)(CgTarget*);
/* ---- alloca ----
* Dynamic stack allocation. `size` is i64 bytes; `align` is the required
@@ -647,7 +656,7 @@ struct CgTarget {
* PREFETCH : dsts none; args = (addr [, rw [, locality]])
* ASSUME_ALIGNED : dsts[0] LOCAL; args = (ptr, align [, offset])
* EXPECT : dsts[0] LOCAL; args = (val, expected)
- * UNREACHABLE / TRAP : dsts none; args none
+ * TRAP : dsts none; args none
* SETJMP : dsts[0] LOCAL i32 result; args = (&buf)
* LONGJMP : dsts none; args = (&buf, val); no return
* ADD/SUB/MUL_OVERFLOW : dsts[0] LOCAL result, dsts[1] LOCAL i1
diff --git a/src/cg/control.c b/src/cg/control.c
@@ -396,7 +396,7 @@ void cfree_cg_computed_goto(CfreeCg* g, const CfreeCgLabel* valid_targets,
void cfree_cg_unreachable(CfreeCg* g) {
if (!g) return;
api_local_const_control_boundary(g);
- g->target->intrinsic(g->target, INTRIN_UNREACHABLE, NULL, 0, NULL, 0);
+ g->target->unreachable(g->target);
}
/* ============================================================
diff --git a/src/cg/ir.h b/src/cg/ir.h
@@ -30,6 +30,7 @@ typedef enum CgIrOp {
CG_IR_CALL,
CG_IR_RET,
+ CG_IR_UNREACHABLE,
CG_IR_BR,
CG_IR_CMP_BRANCH,
diff --git a/src/cg/ir_dump.c b/src/cg/ir_dump.c
@@ -39,6 +39,7 @@ static const char* cg_ir_op_name(CgIrOp op) {
case CG_IR_CONVERT: return "convert";
case CG_IR_CALL: return "call";
case CG_IR_RET: return "ret";
+ case CG_IR_UNREACHABLE: return "unreachable";
case CG_IR_BR: return "br";
case CG_IR_CMP_BRANCH: return "cmp_branch";
case CG_IR_SWITCH: return "switch";
diff --git a/src/cg/ir_recorder.c b/src/cg/ir_recorder.c
@@ -429,6 +429,10 @@ static void rec_ret(CgTarget* t, const CGLocal* values, u32 nvalues) {
in->extra.aux = aux;
}
+static void rec_unreachable(CgTarget* t) {
+ (void)emit(rec_of(t), CG_IR_UNREACHABLE);
+}
+
static void rec_alloca(CgTarget* t, Operand dst, Operand size, u32 align) {
CgIrInst* in = emit(rec_of(t), CG_IR_ALLOCA);
Operand ops[2] = {dst, size};
@@ -639,6 +643,7 @@ CgTarget* cg_ir_recorder_new(Compiler* c, ObjBuilder* obj,
r->base.call = rec_call;
r->base.tail_call_unrealizable_reason = rec_tail_call_unrealizable_reason;
r->base.ret = rec_ret;
+ r->base.unreachable = rec_unreachable;
r->base.alloca_ = rec_alloca;
r->base.va_start_ = rec_va_start;
r->base.va_arg_ = rec_va_arg;
diff --git a/src/cg/native_direct_target.c b/src/cg/native_direct_target.c
@@ -1536,6 +1536,13 @@ static void nd_ret(CgTarget* t, const CGLocal* values, u32 nvalues) {
d->native->ret(d->native);
}
+static void nd_unreachable(CgTarget* t) {
+ NativeDirectTarget* d = nd_of(t);
+ nd_flush_all(d);
+ ND_REQUIRE_NATIVE(d, trap, "target does not emit traps");
+ d->native->trap(d->native);
+}
+
static void nd_alloca(CgTarget* t, Operand dst, Operand size, u32 align) {
NativeDirectTarget* d = nd_of(t);
NativeLoc sr, dr;
@@ -1787,6 +1794,7 @@ CgTarget* native_direct_target_new(Compiler* c, ObjBuilder* obj,
d->base.call = nd_call;
d->base.tail_call_unrealizable_reason = nd_tail_call_unrealizable_reason;
d->base.ret = nd_ret;
+ d->base.unreachable = nd_unreachable;
d->base.alloca_ = nd_alloca;
d->base.va_start_ = nd_va_start;
d->base.va_arg_ = nd_va_arg;
diff --git a/src/interp/engine.c b/src/interp/engine.c
@@ -726,7 +726,8 @@ static u64 ext_call(InterpStack* st, InterpFrame* fr, u64* regs, void* host_fp,
X(IOP_ALLOCA) X(IOP_AGG_COPY) X(IOP_AGG_SET) X(IOP_BITFIELD_LOAD) \
X(IOP_BITFIELD_STORE) X(IOP_VA_START) X(IOP_VA_ARG) X(IOP_VA_END) \
X(IOP_VA_COPY) X(IOP_ATOMIC_LOAD) X(IOP_ATOMIC_STORE) X(IOP_ATOMIC_RMW) \
- X(IOP_ATOMIC_CAS) X(IOP_FENCE) X(IOP_INTRINSIC) X(IOP_TRAP)
+ X(IOP_ATOMIC_CAS) X(IOP_FENCE) X(IOP_INTRINSIC) X(IOP_UNREACHABLE) \
+ X(IOP_TRAP)
#if INTERP_DISPATCH_THREADED
# define OP(name) L_##name
@@ -1308,6 +1309,9 @@ CfreeInterpStatus interp_run_stack(InterpStack* st, int64_t* out_ret) {
write_dst(st, fn, regs, mem_off, &I->opnds[1], ok); /* ok flag */
NEXT();
}
+ OP(IOP_UNREACHABLE):
+ fault(st, "unreachable");
+ goto stop;
OP(IOP_TRAP):
unsupported(st, fn->reject_reason ? fn->reject_reason : "operation");
goto stop;
@@ -1422,9 +1426,6 @@ static int interp_intrinsic(InterpStack* st, InterpFunc* fn, u64* regs,
case INTRIN_TRAP:
fault(st, "__builtin_trap");
return 0;
- case INTRIN_UNREACHABLE:
- fault(st, "unreachable");
- return 0;
case INTRIN_SADD_OVERFLOW:
case INTRIN_UADD_OVERFLOW:
case INTRIN_SSUB_OVERFLOW:
diff --git a/src/interp/interp.h b/src/interp/interp.h
@@ -60,7 +60,8 @@ typedef enum InterpOp {
IOP_ATOMIC_CAS,
IOP_FENCE,
IOP_INTRINSIC,
- IOP_TRAP, /* unreachable/unsupported-at-runtime guard */
+ IOP_UNREACHABLE, /* control terminator: faults (TRAP) if ever reached */
+ IOP_TRAP, /* unsupported-at-runtime guard */
IOP__COUNT,
} InterpOp;
diff --git a/src/interp/lower.c b/src/interp/lower.c
@@ -115,6 +115,8 @@ static InterpOp map_op(Lower* lw, const Inst* in) {
IRRetAux* aux = (IRRetAux*)in->extra.aux;
return (aux && aux->present) ? IOP_RET : IOP_RET_VOID;
}
+ case IR_UNREACHABLE:
+ return IOP_UNREACHABLE;
case IR_ALLOCA:
return IOP_ALLOCA;
case IR_VA_START:
diff --git a/src/opt/cg_ir_lower.c b/src/opt/cg_ir_lower.c
@@ -265,6 +265,7 @@ static int cg_inst_terminates(const CgIrInst* in) {
switch ((CgIrOp)in->op) {
case CG_IR_BR:
case CG_IR_RET:
+ case CG_IR_UNREACHABLE:
case CG_IR_CMP_BRANCH:
case CG_IR_SWITCH:
case CG_IR_INDIRECT_BRANCH:
@@ -273,8 +274,7 @@ static int cg_inst_terminates(const CgIrInst* in) {
return 1;
case CG_IR_INTRINSIC: {
const CgIrIntrinsicAux* aux = (const CgIrIntrinsicAux*)in->extra.aux;
- return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP ||
- aux->kind == INTRIN_UNREACHABLE);
+ return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP);
}
default:
return 0;
@@ -847,6 +847,9 @@ static void lower_one_inst(CgIrLower* l, u32 idx) {
case CG_IR_RET:
op = IR_RET;
break;
+ case CG_IR_UNREACHABLE:
+ op = IR_UNREACHABLE;
+ break;
case CG_IR_BR:
op = IR_BR;
break;
@@ -1021,6 +1024,10 @@ static void lower_one_inst(CgIrLower* l, u32 idx) {
lower_ret(l, out, in);
l->f->blocks[block].nsucc = 0;
break;
+ case CG_IR_UNREACHABLE:
+ /* Terminator with no successors: control does not leave this block. */
+ l->f->blocks[block].nsucc = 0;
+ break;
case CG_IR_BR:
out->extra.imm = block_for_label(l, (Label)in->extra.imm, in->loc);
set_succ1(l, block, (u32)out->extra.imm);
@@ -1181,13 +1188,13 @@ static void add_fallthrough_succs(CgIrLower* l) {
case IR_SWITCH:
case IR_INDIRECT_BRANCH:
case IR_RET:
+ case IR_UNREACHABLE:
case IR_BREAK_TO:
case IR_CONTINUE_TO:
continue;
case IR_INTRINSIC: {
IRIntrinAux* aux = (IRIntrinAux*)last->extra.aux;
- if (aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP ||
- aux->kind == INTRIN_UNREACHABLE))
+ if (aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP))
continue;
break;
}
diff --git a/src/opt/ir.h b/src/opt/ir.h
@@ -294,6 +294,7 @@ typedef enum IROp {
IR_LOCAL_STATIC_DATA_LABEL_ADDR, /* extra.aux = CgIrLocalStaticLabelAux */
IR_LOCAL_STATIC_DATA_END,
IR_RET, /* extra.aux = IRRetAux* (NULL for void). */
+ IR_UNREACHABLE, /* control terminator; no operands, no successors. */
IR_SCOPE_BEGIN, /* extra.aux = IRScopeAux. */
IR_SCOPE_END, /* extra.imm = scope id (Val). */
IR_BREAK_TO, /* extra.imm = scope id (Val). */
diff --git a/src/opt/ir_print.c b/src/opt/ir_print.c
@@ -88,6 +88,8 @@ static const char* op_name(IROp op) {
return "local_static_data_end";
case IR_RET:
return "ret";
+ case IR_UNREACHABLE:
+ return "unreachable";
case IR_SCOPE_BEGIN:
return "scope_begin";
case IR_SCOPE_END:
diff --git a/src/opt/pass_analysis.c b/src/opt/pass_analysis.c
@@ -343,6 +343,7 @@ static int block_has_succ(const Block* bl, u32 succ) {
static int fixed_terminator_succ_count(const Inst* in, u32* count_out) {
switch ((IROp)in->op) {
case IR_RET:
+ case IR_UNREACHABLE:
*count_out = 0;
return 1;
case IR_BR:
@@ -356,8 +357,7 @@ static int fixed_terminator_succ_count(const Inst* in, u32* count_out) {
return 1;
case IR_INTRINSIC: {
IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux;
- if (aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP ||
- aux->kind == INTRIN_UNREACHABLE)) {
+ if (aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP)) {
*count_out = 0;
return 1;
}
diff --git a/src/opt/pass_cfg.c b/src/opt/pass_cfg.c
@@ -34,13 +34,13 @@ static int is_terminator(const Inst* in) {
case IR_SWITCH:
case IR_INDIRECT_BRANCH:
case IR_RET:
+ case IR_UNREACHABLE:
case IR_BREAK_TO:
case IR_CONTINUE_TO:
return 1;
case IR_INTRINSIC: {
IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux;
- return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP ||
- aux->kind == INTRIN_UNREACHABLE);
+ return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP);
}
default:
return 0;
@@ -231,6 +231,9 @@ void opt_build_cfg(Func* f) {
case IR_RET:
bl->nsucc = 0;
break;
+ case IR_UNREACHABLE:
+ bl->nsucc = 0;
+ break;
case IR_INTRINSIC:
bl->nsucc = 0;
break;
diff --git a/src/opt/pass_dce.c b/src/opt/pass_dce.c
@@ -53,6 +53,7 @@ int opt_inst_has_side_effect(Func* f, const Inst* in) {
case IR_LOCAL_STATIC_DATA_LABEL_ADDR:
case IR_LOCAL_STATIC_DATA_END:
case IR_RET:
+ case IR_UNREACHABLE:
case IR_SCOPE_BEGIN:
case IR_SCOPE_END:
case IR_BREAK_TO:
diff --git a/src/opt/pass_lower.c b/src/opt/pass_lower.c
@@ -1619,13 +1619,13 @@ static int lower_is_terminator(const Inst* in) {
case IR_SWITCH:
case IR_INDIRECT_BRANCH:
case IR_RET:
+ case IR_UNREACHABLE:
case IR_BREAK_TO:
case IR_CONTINUE_TO:
return 1;
case IR_INTRINSIC: {
IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux;
- return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP ||
- aux->kind == INTRIN_UNREACHABLE);
+ return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP);
}
default:
return 0;
diff --git a/src/opt/pass_native_emit.c b/src/opt/pass_native_emit.c
@@ -1219,6 +1219,9 @@ static void emit_inst(NativeEmitCtx* e, u32 block, u32 order_index, Inst* in,
case IR_FENCE:
e->target->fence(e->target, (CfreeCgMemOrder)in->extra.imm);
return;
+ case IR_UNREACHABLE:
+ e->target->trap(e->target);
+ return;
case IR_INTRINSIC: {
IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux;
NativeLoc* dsts = aux && aux->ndst
@@ -1256,13 +1259,13 @@ static int native_emit_terminates(const Inst* in) {
case IR_SWITCH:
case IR_INDIRECT_BRANCH:
case IR_RET:
+ case IR_UNREACHABLE:
case IR_BREAK_TO:
case IR_CONTINUE_TO:
return 1;
case IR_INTRINSIC: {
IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux;
- return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP ||
- aux->kind == INTRIN_UNREACHABLE);
+ return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP);
}
default:
return 0;
diff --git a/src/opt/pass_o2.c b/src/opt/pass_o2.c
@@ -245,13 +245,13 @@ static int o2_is_terminator(const Inst* in) {
case IR_SWITCH:
case IR_INDIRECT_BRANCH:
case IR_RET:
+ case IR_UNREACHABLE:
case IR_BREAK_TO:
case IR_CONTINUE_TO:
return 1;
case IR_INTRINSIC: {
IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux;
- return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP ||
- aux->kind == INTRIN_UNREACHABLE);
+ return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP);
}
default:
return 0;
diff --git a/src/opt/pass_ssa.c b/src/opt/pass_ssa.c
@@ -810,13 +810,13 @@ static int ssa_is_terminator(const Inst* in) {
case IR_SWITCH:
case IR_INDIRECT_BRANCH:
case IR_RET:
+ case IR_UNREACHABLE:
case IR_BREAK_TO:
case IR_CONTINUE_TO:
return 1;
case IR_INTRINSIC: {
IRIntrinAux* aux = (IRIntrinAux*)in->extra.aux;
- return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP ||
- aux->kind == INTRIN_UNREACHABLE);
+ return aux && (aux->kind == INTRIN_LONGJMP || aux->kind == INTRIN_TRAP);
}
default:
return 0;