blob: c3f2c4f2c8bfe5bf333550dbadc1ff6687690620 [file] [log] [blame]
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use llvm::{self, ValueRef};
use rustc::ty::{self, Ty};
use rustc::ty::cast::{CastTy, IntTy};
use rustc::mir::repr as mir;
use asm;
use base;
use callee::Callee;
use common::{self, val_ty, C_bool, C_null, C_uint, BlockAndBuilder, Result};
use datum::{Datum, Lvalue};
use debuginfo::DebugLoc;
use adt;
use machine;
use type_of;
use tvec;
use value::Value;
use Disr;
use super::MirContext;
use super::constant::const_scalar_checked_binop;
use super::operand::{OperandRef, OperandValue};
use super::lvalue::{LvalueRef, get_dataptr};
impl<'bcx, 'tcx> MirContext<'bcx, 'tcx> {
pub fn trans_rvalue(&mut self,
bcx: BlockAndBuilder<'bcx, 'tcx>,
dest: LvalueRef<'tcx>,
rvalue: &mir::Rvalue<'tcx>,
debug_loc: DebugLoc)
-> BlockAndBuilder<'bcx, 'tcx>
{
debug!("trans_rvalue(dest.llval={:?}, rvalue={:?})",
Value(dest.llval), rvalue);
match *rvalue {
mir::Rvalue::Use(ref operand) => {
let tr_operand = self.trans_operand(&bcx, operand);
// FIXME: consider not copying constants through stack. (fixable by translating
// constants into OperandValue::Ref, why don’t we do that yet if we don’t?)
self.store_operand(&bcx, dest.llval, tr_operand);
bcx
}
mir::Rvalue::Cast(mir::CastKind::Unsize, ref source, cast_ty) => {
let cast_ty = bcx.monomorphize(&cast_ty);
if common::type_is_fat_ptr(bcx.tcx(), cast_ty) {
// into-coerce of a thin pointer to a fat pointer - just
// use the operand path.
let (bcx, temp) = self.trans_rvalue_operand(bcx, rvalue, debug_loc);
self.store_operand(&bcx, dest.llval, temp);
return bcx;
}
// Unsize of a nontrivial struct. I would prefer for
// this to be eliminated by MIR translation, but
// `CoerceUnsized` can be passed by a where-clause,
// so the (generic) MIR may not be able to expand it.
let operand = self.trans_operand(&bcx, source);
let operand = operand.pack_if_pair(&bcx);
bcx.with_block(|bcx| {
match operand.val {
OperandValue::Pair(..) => bug!(),
OperandValue::Immediate(llval) => {
// unsize from an immediate structure. We don't
// really need a temporary alloca here, but
// avoiding it would require us to have
// `coerce_unsized_into` use extractvalue to
// index into the struct, and this case isn't
// important enough for it.
debug!("trans_rvalue: creating ugly alloca");
let lltemp = base::alloc_ty(bcx, operand.ty, "__unsize_temp");
base::store_ty(bcx, llval, lltemp, operand.ty);
base::coerce_unsized_into(bcx,
lltemp, operand.ty,
dest.llval, cast_ty);
}
OperandValue::Ref(llref) => {
base::coerce_unsized_into(bcx,
llref, operand.ty,
dest.llval, cast_ty);
}
}
});
bcx
}
mir::Rvalue::Repeat(ref elem, ref count) => {
let tr_elem = self.trans_operand(&bcx, elem);
let size = count.value.as_u64(bcx.tcx().sess.target.uint_type);
let size = C_uint(bcx.ccx(), size);
let base = get_dataptr(&bcx, dest.llval);
let bcx = bcx.map_block(|block| {
tvec::iter_vec_raw(block, base, tr_elem.ty, size, |block, llslot, _| {
self.store_operand_direct(block, llslot, tr_elem);
block
})
});
bcx
}
mir::Rvalue::Aggregate(ref kind, ref operands) => {
match *kind {
mir::AggregateKind::Adt(adt_def, index, _) => {
let repr = adt::represent_type(bcx.ccx(), dest.ty.to_ty(bcx.tcx()));
let disr = Disr::from(adt_def.variants[index].disr_val);
bcx.with_block(|bcx| {
adt::trans_set_discr(bcx, &repr, dest.llval, Disr::from(disr));
});
for (i, operand) in operands.iter().enumerate() {
let op = self.trans_operand(&bcx, operand);
// Do not generate stores and GEPis for zero-sized fields.
if !common::type_is_zero_size(bcx.ccx(), op.ty) {
let val = adt::MaybeSizedValue::sized(dest.llval);
let lldest_i = adt::trans_field_ptr_builder(&bcx, &repr,
val, disr, i);
self.store_operand(&bcx, lldest_i, op);
}
}
},
_ => {
// FIXME Shouldn't need to manually trigger closure instantiations.
if let mir::AggregateKind::Closure(def_id, substs) = *kind {
use rustc::hir;
use syntax::ast::DUMMY_NODE_ID;
use syntax::ptr::P;
use syntax_pos::DUMMY_SP;
use closure;
closure::trans_closure_expr(closure::Dest::Ignore(bcx.ccx()),
&hir::FnDecl {
inputs: P::new(),
output: hir::NoReturn(DUMMY_SP),
variadic: false
},
&hir::Block {
stmts: P::new(),
expr: None,
id: DUMMY_NODE_ID,
rules: hir::DefaultBlock,
span: DUMMY_SP
},
DUMMY_NODE_ID, def_id,
bcx.monomorphize(&substs));
}
for (i, operand) in operands.iter().enumerate() {
let op = self.trans_operand(&bcx, operand);
// Do not generate stores and GEPis for zero-sized fields.
if !common::type_is_zero_size(bcx.ccx(), op.ty) {
// Note: perhaps this should be StructGep, but
// note that in some cases the values here will
// not be structs but arrays.
let dest = bcx.gepi(dest.llval, &[0, i]);
self.store_operand(&bcx, dest, op);
}
}
}
}
bcx
}
mir::Rvalue::InlineAsm { ref asm, ref outputs, ref inputs } => {
let outputs = outputs.iter().map(|output| {
let lvalue = self.trans_lvalue(&bcx, output);
Datum::new(lvalue.llval, lvalue.ty.to_ty(bcx.tcx()),
Lvalue::new("out"))
}).collect();
let input_vals = inputs.iter().map(|input| {
self.trans_operand(&bcx, input).immediate()
}).collect();
bcx.with_block(|bcx| {
asm::trans_inline_asm(bcx, asm, outputs, input_vals);
});
bcx
}
_ => {
assert!(rvalue_creates_operand(&self.mir, &bcx, rvalue));
let (bcx, temp) = self.trans_rvalue_operand(bcx, rvalue, debug_loc);
self.store_operand(&bcx, dest.llval, temp);
bcx
}
}
}
pub fn trans_rvalue_operand(&mut self,
bcx: BlockAndBuilder<'bcx, 'tcx>,
rvalue: &mir::Rvalue<'tcx>,
debug_loc: DebugLoc)
-> (BlockAndBuilder<'bcx, 'tcx>, OperandRef<'tcx>)
{
assert!(rvalue_creates_operand(&self.mir, &bcx, rvalue),
"cannot trans {:?} to operand", rvalue);
match *rvalue {
mir::Rvalue::Cast(ref kind, ref source, cast_ty) => {
let operand = self.trans_operand(&bcx, source);
debug!("cast operand is {:?}", operand);
let cast_ty = bcx.monomorphize(&cast_ty);
let val = match *kind {
mir::CastKind::ReifyFnPointer => {
match operand.ty.sty {
ty::TyFnDef(def_id, substs, _) => {
OperandValue::Immediate(
Callee::def(bcx.ccx(), def_id, substs)
.reify(bcx.ccx()).val)
}
_ => {
bug!("{} cannot be reified to a fn ptr", operand.ty)
}
}
}
mir::CastKind::UnsafeFnPointer => {
// this is a no-op at the LLVM level
operand.val
}
mir::CastKind::Unsize => {
// unsize targets other than to a fat pointer currently
// can't be operands.
assert!(common::type_is_fat_ptr(bcx.tcx(), cast_ty));
match operand.val {
OperandValue::Pair(lldata, llextra) => {
// unsize from a fat pointer - this is a
// "trait-object-to-supertrait" coercion, for
// example,
// &'a fmt::Debug+Send => &'a fmt::Debug,
// So we need to pointercast the base to ensure
// the types match up.
let llcast_ty = type_of::fat_ptr_base_ty(bcx.ccx(), cast_ty);
let lldata = bcx.pointercast(lldata, llcast_ty);
OperandValue::Pair(lldata, llextra)
}
OperandValue::Immediate(lldata) => {
// "standard" unsize
let (lldata, llextra) = bcx.with_block(|bcx| {
base::unsize_thin_ptr(bcx, lldata,
operand.ty, cast_ty)
});
OperandValue::Pair(lldata, llextra)
}
OperandValue::Ref(_) => {
bug!("by-ref operand {:?} in trans_rvalue_operand",
operand);
}
}
}
mir::CastKind::Misc if common::type_is_immediate(bcx.ccx(), operand.ty) => {
debug_assert!(common::type_is_immediate(bcx.ccx(), cast_ty));
let r_t_in = CastTy::from_ty(operand.ty).expect("bad input type for cast");
let r_t_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
let ll_t_in = type_of::immediate_type_of(bcx.ccx(), operand.ty);
let ll_t_out = type_of::immediate_type_of(bcx.ccx(), cast_ty);
let llval = operand.immediate();
let signed = if let CastTy::Int(IntTy::CEnum) = r_t_in {
let repr = adt::represent_type(bcx.ccx(), operand.ty);
adt::is_discr_signed(&repr)
} else {
operand.ty.is_signed()
};
let newval = match (r_t_in, r_t_out) {
(CastTy::Int(_), CastTy::Int(_)) => {
let srcsz = ll_t_in.int_width();
let dstsz = ll_t_out.int_width();
if srcsz == dstsz {
bcx.bitcast(llval, ll_t_out)
} else if srcsz > dstsz {
bcx.trunc(llval, ll_t_out)
} else if signed {
bcx.sext(llval, ll_t_out)
} else {
bcx.zext(llval, ll_t_out)
}
}
(CastTy::Float, CastTy::Float) => {
let srcsz = ll_t_in.float_width();
let dstsz = ll_t_out.float_width();
if dstsz > srcsz {
bcx.fpext(llval, ll_t_out)
} else if srcsz > dstsz {
bcx.fptrunc(llval, ll_t_out)
} else {
llval
}
}
(CastTy::Ptr(_), CastTy::Ptr(_)) |
(CastTy::FnPtr, CastTy::Ptr(_)) |
(CastTy::RPtr(_), CastTy::Ptr(_)) =>
bcx.pointercast(llval, ll_t_out),
(CastTy::Ptr(_), CastTy::Int(_)) |
(CastTy::FnPtr, CastTy::Int(_)) =>
bcx.ptrtoint(llval, ll_t_out),
(CastTy::Int(_), CastTy::Ptr(_)) =>
bcx.inttoptr(llval, ll_t_out),
(CastTy::Int(_), CastTy::Float) if signed =>
bcx.sitofp(llval, ll_t_out),
(CastTy::Int(_), CastTy::Float) =>
bcx.uitofp(llval, ll_t_out),
(CastTy::Float, CastTy::Int(IntTy::I)) =>
bcx.fptosi(llval, ll_t_out),
(CastTy::Float, CastTy::Int(_)) =>
bcx.fptoui(llval, ll_t_out),
_ => bug!("unsupported cast: {:?} to {:?}", operand.ty, cast_ty)
};
OperandValue::Immediate(newval)
}
mir::CastKind::Misc => { // Casts from a fat-ptr.
let ll_cast_ty = type_of::immediate_type_of(bcx.ccx(), cast_ty);
let ll_from_ty = type_of::immediate_type_of(bcx.ccx(), operand.ty);
if let OperandValue::Pair(data_ptr, meta_ptr) = operand.val {
if common::type_is_fat_ptr(bcx.tcx(), cast_ty) {
let ll_cft = ll_cast_ty.field_types();
let ll_fft = ll_from_ty.field_types();
let data_cast = bcx.pointercast(data_ptr, ll_cft[0]);
assert_eq!(ll_cft[1].kind(), ll_fft[1].kind());
OperandValue::Pair(data_cast, meta_ptr)
} else { // cast to thin-ptr
// Cast of fat-ptr to thin-ptr is an extraction of data-ptr and
// pointer-cast of that pointer to desired pointer type.
let llval = bcx.pointercast(data_ptr, ll_cast_ty);
OperandValue::Immediate(llval)
}
} else {
bug!("Unexpected non-Pair operand")
}
}
};
let operand = OperandRef {
val: val,
ty: cast_ty
};
(bcx, operand)
}
mir::Rvalue::Ref(_, bk, ref lvalue) => {
let tr_lvalue = self.trans_lvalue(&bcx, lvalue);
let ty = tr_lvalue.ty.to_ty(bcx.tcx());
let ref_ty = bcx.tcx().mk_ref(
bcx.tcx().mk_region(ty::ReErased),
ty::TypeAndMut { ty: ty, mutbl: bk.to_mutbl_lossy() }
);
// Note: lvalues are indirect, so storing the `llval` into the
// destination effectively creates a reference.
let operand = if common::type_is_sized(bcx.tcx(), ty) {
OperandRef {
val: OperandValue::Immediate(tr_lvalue.llval),
ty: ref_ty,
}
} else {
OperandRef {
val: OperandValue::Pair(tr_lvalue.llval,
tr_lvalue.llextra),
ty: ref_ty,
}
};
(bcx, operand)
}
mir::Rvalue::Len(ref lvalue) => {
let tr_lvalue = self.trans_lvalue(&bcx, lvalue);
let operand = OperandRef {
val: OperandValue::Immediate(tr_lvalue.len(bcx.ccx())),
ty: bcx.tcx().types.usize,
};
(bcx, operand)
}
mir::Rvalue::BinaryOp(op, ref lhs, ref rhs) => {
let lhs = self.trans_operand(&bcx, lhs);
let rhs = self.trans_operand(&bcx, rhs);
let llresult = if common::type_is_fat_ptr(bcx.tcx(), lhs.ty) {
match (lhs.val, rhs.val) {
(OperandValue::Pair(lhs_addr, lhs_extra),
OperandValue::Pair(rhs_addr, rhs_extra)) => {
bcx.with_block(|bcx| {
base::compare_fat_ptrs(bcx,
lhs_addr, lhs_extra,
rhs_addr, rhs_extra,
lhs.ty, op.to_hir_binop(),
debug_loc)
})
}
_ => bug!()
}
} else {
self.trans_scalar_binop(&bcx, op,
lhs.immediate(), rhs.immediate(),
lhs.ty)
};
let operand = OperandRef {
val: OperandValue::Immediate(llresult),
ty: self.mir.binop_ty(bcx.tcx(), op, lhs.ty, rhs.ty),
};
(bcx, operand)
}
mir::Rvalue::CheckedBinaryOp(op, ref lhs, ref rhs) => {
let lhs = self.trans_operand(&bcx, lhs);
let rhs = self.trans_operand(&bcx, rhs);
let result = self.trans_scalar_checked_binop(&bcx, op,
lhs.immediate(), rhs.immediate(),
lhs.ty);
let val_ty = self.mir.binop_ty(bcx.tcx(), op, lhs.ty, rhs.ty);
let operand_ty = bcx.tcx().mk_tup(vec![val_ty, bcx.tcx().types.bool]);
let operand = OperandRef {
val: result,
ty: operand_ty
};
(bcx, operand)
}
mir::Rvalue::UnaryOp(op, ref operand) => {
let operand = self.trans_operand(&bcx, operand);
let lloperand = operand.immediate();
let is_float = operand.ty.is_fp();
let llval = match op {
mir::UnOp::Not => bcx.not(lloperand),
mir::UnOp::Neg => if is_float {
bcx.fneg(lloperand)
} else {
bcx.neg(lloperand)
}
};
(bcx, OperandRef {
val: OperandValue::Immediate(llval),
ty: operand.ty,
})
}
mir::Rvalue::Box(content_ty) => {
let content_ty: Ty<'tcx> = bcx.monomorphize(&content_ty);
let llty = type_of::type_of(bcx.ccx(), content_ty);
let llsize = machine::llsize_of(bcx.ccx(), llty);
let align = type_of::align_of(bcx.ccx(), content_ty);
let llalign = C_uint(bcx.ccx(), align);
let llty_ptr = llty.ptr_to();
let box_ty = bcx.tcx().mk_box(content_ty);
let mut llval = None;
let bcx = bcx.map_block(|bcx| {
let Result { bcx, val } = base::malloc_raw_dyn(bcx,
llty_ptr,
box_ty,
llsize,
llalign,
debug_loc);
llval = Some(val);
bcx
});
let operand = OperandRef {
val: OperandValue::Immediate(llval.unwrap()),
ty: box_ty,
};
(bcx, operand)
}
mir::Rvalue::Use(ref operand) => {
let operand = self.trans_operand(&bcx, operand);
(bcx, operand)
}
mir::Rvalue::Repeat(..) |
mir::Rvalue::Aggregate(..) |
mir::Rvalue::InlineAsm { .. } => {
bug!("cannot generate operand from rvalue {:?}", rvalue);
}
}
}
pub fn trans_scalar_binop(&mut self,
bcx: &BlockAndBuilder<'bcx, 'tcx>,
op: mir::BinOp,
lhs: ValueRef,
rhs: ValueRef,
input_ty: Ty<'tcx>) -> ValueRef {
let is_float = input_ty.is_fp();
let is_signed = input_ty.is_signed();
match op {
mir::BinOp::Add => if is_float {
bcx.fadd(lhs, rhs)
} else {
bcx.add(lhs, rhs)
},
mir::BinOp::Sub => if is_float {
bcx.fsub(lhs, rhs)
} else {
bcx.sub(lhs, rhs)
},
mir::BinOp::Mul => if is_float {
bcx.fmul(lhs, rhs)
} else {
bcx.mul(lhs, rhs)
},
mir::BinOp::Div => if is_float {
bcx.fdiv(lhs, rhs)
} else if is_signed {
bcx.sdiv(lhs, rhs)
} else {
bcx.udiv(lhs, rhs)
},
mir::BinOp::Rem => if is_float {
bcx.frem(lhs, rhs)
} else if is_signed {
bcx.srem(lhs, rhs)
} else {
bcx.urem(lhs, rhs)
},
mir::BinOp::BitOr => bcx.or(lhs, rhs),
mir::BinOp::BitAnd => bcx.and(lhs, rhs),
mir::BinOp::BitXor => bcx.xor(lhs, rhs),
mir::BinOp::Shl => {
bcx.with_block(|bcx| {
common::build_unchecked_lshift(bcx,
lhs,
rhs,
DebugLoc::None)
})
}
mir::BinOp::Shr => {
bcx.with_block(|bcx| {
common::build_unchecked_rshift(bcx,
input_ty,
lhs,
rhs,
DebugLoc::None)
})
}
mir::BinOp::Eq | mir::BinOp::Lt | mir::BinOp::Gt |
mir::BinOp::Ne | mir::BinOp::Le | mir::BinOp::Ge => {
bcx.with_block(|bcx| {
base::compare_scalar_types(bcx, lhs, rhs, input_ty,
op.to_hir_binop(), DebugLoc::None)
})
}
}
}
pub fn trans_scalar_checked_binop(&mut self,
bcx: &BlockAndBuilder<'bcx, 'tcx>,
op: mir::BinOp,
lhs: ValueRef,
rhs: ValueRef,
input_ty: Ty<'tcx>) -> OperandValue {
// This case can currently arise only from functions marked
// with #[rustc_inherit_overflow_checks] and inlined from
// another crate (mostly core::num generic/#[inline] fns),
// while the current crate doesn't use overflow checks.
if !bcx.ccx().check_overflow() {
let val = self.trans_scalar_binop(bcx, op, lhs, rhs, input_ty);
return OperandValue::Pair(val, C_bool(bcx.ccx(), false));
}
// First try performing the operation on constants, which
// will only succeed if both operands are constant.
// This is necessary to determine when an overflow Assert
// will always panic at runtime, and produce a warning.
if let Some((val, of)) = const_scalar_checked_binop(bcx.tcx(), op, lhs, rhs, input_ty) {
return OperandValue::Pair(val, C_bool(bcx.ccx(), of));
}
let (val, of) = match op {
// These are checked using intrinsics
mir::BinOp::Add | mir::BinOp::Sub | mir::BinOp::Mul => {
let oop = match op {
mir::BinOp::Add => OverflowOp::Add,
mir::BinOp::Sub => OverflowOp::Sub,
mir::BinOp::Mul => OverflowOp::Mul,
_ => unreachable!()
};
let intrinsic = get_overflow_intrinsic(oop, bcx, input_ty);
let res = bcx.call(intrinsic, &[lhs, rhs], None);
(bcx.extract_value(res, 0),
bcx.extract_value(res, 1))
}
mir::BinOp::Shl | mir::BinOp::Shr => {
let lhs_llty = val_ty(lhs);
let rhs_llty = val_ty(rhs);
let invert_mask = bcx.with_block(|bcx| {
common::shift_mask_val(bcx, lhs_llty, rhs_llty, true)
});
let outer_bits = bcx.and(rhs, invert_mask);
let of = bcx.icmp(llvm::IntNE, outer_bits, C_null(rhs_llty));
let val = self.trans_scalar_binop(bcx, op, lhs, rhs, input_ty);
(val, of)
}
_ => {
bug!("Operator `{:?}` is not a checkable operator", op)
}
};
OperandValue::Pair(val, of)
}
}
pub fn rvalue_creates_operand<'bcx, 'tcx>(_mir: &mir::Mir<'tcx>,
_bcx: &BlockAndBuilder<'bcx, 'tcx>,
rvalue: &mir::Rvalue<'tcx>) -> bool {
match *rvalue {
mir::Rvalue::Ref(..) |
mir::Rvalue::Len(..) |
mir::Rvalue::Cast(..) | // (*)
mir::Rvalue::BinaryOp(..) |
mir::Rvalue::CheckedBinaryOp(..) |
mir::Rvalue::UnaryOp(..) |
mir::Rvalue::Box(..) |
mir::Rvalue::Use(..) =>
true,
mir::Rvalue::Repeat(..) |
mir::Rvalue::Aggregate(..) |
mir::Rvalue::InlineAsm { .. } =>
false,
}
// (*) this is only true if the type is suitable
}
#[derive(Copy, Clone)]
enum OverflowOp {
Add, Sub, Mul
}
fn get_overflow_intrinsic(oop: OverflowOp, bcx: &BlockAndBuilder, ty: Ty) -> ValueRef {
use syntax::ast::IntTy::*;
use syntax::ast::UintTy::*;
use rustc::ty::{TyInt, TyUint};
let tcx = bcx.tcx();
let new_sty = match ty.sty {
TyInt(Is) => match &tcx.sess.target.target.target_pointer_width[..] {
"32" => TyInt(I32),
"64" => TyInt(I64),
_ => panic!("unsupported target word size")
},
TyUint(Us) => match &tcx.sess.target.target.target_pointer_width[..] {
"32" => TyUint(U32),
"64" => TyUint(U64),
_ => panic!("unsupported target word size")
},
ref t @ TyUint(_) | ref t @ TyInt(_) => t.clone(),
_ => panic!("tried to get overflow intrinsic for op applied to non-int type")
};
let name = match oop {
OverflowOp::Add => match new_sty {
TyInt(I8) => "llvm.sadd.with.overflow.i8",
TyInt(I16) => "llvm.sadd.with.overflow.i16",
TyInt(I32) => "llvm.sadd.with.overflow.i32",
TyInt(I64) => "llvm.sadd.with.overflow.i64",
TyUint(U8) => "llvm.uadd.with.overflow.i8",
TyUint(U16) => "llvm.uadd.with.overflow.i16",
TyUint(U32) => "llvm.uadd.with.overflow.i32",
TyUint(U64) => "llvm.uadd.with.overflow.i64",
_ => unreachable!(),
},
OverflowOp::Sub => match new_sty {
TyInt(I8) => "llvm.ssub.with.overflow.i8",
TyInt(I16) => "llvm.ssub.with.overflow.i16",
TyInt(I32) => "llvm.ssub.with.overflow.i32",
TyInt(I64) => "llvm.ssub.with.overflow.i64",
TyUint(U8) => "llvm.usub.with.overflow.i8",
TyUint(U16) => "llvm.usub.with.overflow.i16",
TyUint(U32) => "llvm.usub.with.overflow.i32",
TyUint(U64) => "llvm.usub.with.overflow.i64",
_ => unreachable!(),
},
OverflowOp::Mul => match new_sty {
TyInt(I8) => "llvm.smul.with.overflow.i8",
TyInt(I16) => "llvm.smul.with.overflow.i16",
TyInt(I32) => "llvm.smul.with.overflow.i32",
TyInt(I64) => "llvm.smul.with.overflow.i64",
TyUint(U8) => "llvm.umul.with.overflow.i8",
TyUint(U16) => "llvm.umul.with.overflow.i16",
TyUint(U32) => "llvm.umul.with.overflow.i32",
TyUint(U64) => "llvm.umul.with.overflow.i64",
_ => unreachable!(),
},
};
bcx.ccx().get_intrinsic(&name)
}