// 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::ValueRef;
use rustc::middle::ty::{self, Ty};
use rustc::mir::repr as mir;

use trans::asm;
use trans::base;
use trans::build;
use trans::common::{self, Block, Result};
use trans::debuginfo::DebugLoc;
use trans::declare;
use trans::expr;
use trans::machine;
use trans::type_::Type;
use trans::type_of;
use trans::tvec;

use super::MirContext;
use super::operand::{OperandRef, OperandValue};

impl<'bcx, 'tcx> MirContext<'bcx, 'tcx> {
    pub fn trans_rvalue(&mut self,
                        bcx: Block<'bcx, 'tcx>,
                        lldest: ValueRef,
                        rvalue: &mir::Rvalue<'tcx>)
                        -> Block<'bcx, 'tcx>
    {
        debug!("trans_rvalue(lldest={}, rvalue={:?})",
               bcx.val_to_string(lldest),
               rvalue);

        match *rvalue {
            mir::Rvalue::Use(ref operand) => {
                self.trans_operand_into(bcx, lldest, operand);
                bcx
            }

            mir::Rvalue::Cast(mir::CastKind::Unsize, ref operand, 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);
                    self.store_operand(bcx, lldest, 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, operand);
                match operand.val {
                    OperandValue::FatPtr(..) => unreachable!(),
                    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,
                                                  lldest, cast_ty);
                    }
                    OperandValue::Ref(llref) => {
                        base::coerce_unsized_into(bcx,
                                                  llref, operand.ty,
                                                  lldest, cast_ty);
                    }
                }
                bcx
            }

            mir::Rvalue::Repeat(ref elem, ref count) => {
                let elem = self.trans_operand(bcx, elem);
                let size = self.trans_constant(bcx, count).immediate();
                let base = expr::get_dataptr(bcx, lldest);
                tvec::iter_vec_raw(bcx, base, elem.ty, size, |bcx, llslot, _| {
                    self.store_operand(bcx, llslot, elem);
                    bcx
                })
            }

            mir::Rvalue::Aggregate(_, ref operands) => {
                for (i, operand) in operands.iter().enumerate() {
                    // Note: perhaps this should be StructGep, but
                    // note that in some cases the values here will
                    // not be structs but arrays.
                    let lldest_i = build::GEPi(bcx, lldest, &[0, i]);
                    self.trans_operand_into(bcx, lldest_i, operand);
                }
                bcx
            }

            mir::Rvalue::Slice { ref input, from_start, from_end } => {
                let ccx = bcx.ccx();
                let input = self.trans_lvalue(bcx, input);
                let (llbase, lllen) = tvec::get_base_and_len(bcx,
                                                             input.llval,
                                                             input.ty.to_ty(bcx.tcx()));
                let llbase1 = build::GEPi(bcx, llbase, &[from_start]);
                let adj = common::C_uint(ccx, from_start + from_end);
                let lllen1 = build::Sub(bcx, lllen, adj, DebugLoc::None);
                let lladdrdest = expr::get_dataptr(bcx, lldest);
                build::Store(bcx, llbase1, lladdrdest);
                let llmetadest = expr::get_meta(bcx, lldest);
                build::Store(bcx, lllen1, llmetadest);
                bcx
            }

            mir::Rvalue::InlineAsm(inline_asm) => {
                asm::trans_inline_asm(bcx, inline_asm)
            }

            _ => {
                assert!(rvalue_creates_operand(rvalue));
                let (bcx, temp) = self.trans_rvalue_operand(bcx, rvalue);
                self.store_operand(bcx, lldest, temp);
                bcx
            }
        }
    }

    pub fn trans_rvalue_operand(&mut self,
                                bcx: Block<'bcx, 'tcx>,
                                rvalue: &mir::Rvalue<'tcx>)
                                -> (Block<'bcx, 'tcx>, OperandRef<'tcx>)
    {
        assert!(rvalue_creates_operand(rvalue), "cannot trans {:?} to operand", rvalue);

        match *rvalue {
            mir::Rvalue::Use(ref operand) => {
                let operand = self.trans_operand(bcx, operand);
                (bcx, operand)
            }

            mir::Rvalue::Cast(ref kind, ref operand, cast_ty) => {
                let operand = self.trans_operand(bcx, operand);
                debug!("cast operand is {}", operand.repr(bcx));
                let cast_ty = bcx.monomorphize(&cast_ty);

                let val = match *kind {
                    mir::CastKind::ReifyFnPointer |
                    mir::CastKind::UnsafeFnPointer => {
                        // these are no-ops 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::FatPtr(..) => {
                                // unsize from a fat pointer - this is a
                                // "trait-object-to-supertrait" coercion, for
                                // example,
                                //   &'a fmt::Debug+Send => &'a fmt::Debug,
                                // and is a no-op at the LLVM level
                                operand.val
                            }
                            OperandValue::Immediate(lldata) => {
                                // "standard" unsize
                                let (lldata, llextra) =
                                    base::unsize_thin_ptr(bcx, lldata,
                                                          operand.ty, cast_ty);
                                OperandValue::FatPtr(lldata, llextra)
                            }
                            OperandValue::Ref(_) => {
                                bcx.sess().bug(
                                    &format!("by-ref operand {} in trans_rvalue_operand",
                                             operand.repr(bcx)));
                            }
                        }
                    }
                    mir::CastKind::Misc => unimplemented!()
                };
                (bcx, OperandRef {
                    val: val,
                    ty: cast_ty
                })
            }

            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::ReStatic),
                    ty::TypeAndMut { ty: ty, mutbl: bk.to_mutbl_lossy() }
                );

                // Note: lvalues are indirect, so storing the `llval` into the
                // destination effectively creates a reference.
                if common::type_is_sized(bcx.tcx(), ty) {
                    (bcx, OperandRef {
                        val: OperandValue::Immediate(tr_lvalue.llval),
                        ty: ref_ty,
                    })
                } else {
                    (bcx, OperandRef {
                        val: OperandValue::FatPtr(tr_lvalue.llval,
                                                  tr_lvalue.llextra),
                        ty: ref_ty,
                    })
                }
            }

            mir::Rvalue::Len(ref lvalue) => {
                let tr_lvalue = self.trans_lvalue(bcx, lvalue);
                (bcx, OperandRef {
                    val: OperandValue::Immediate(self.lvalue_len(bcx, tr_lvalue)),
                    ty: bcx.tcx().types.usize,
                })
            }

            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::FatPtr(lhs_addr, lhs_extra),
                         OperandValue::FatPtr(rhs_addr, rhs_extra)) => {
                            base::compare_fat_ptrs(bcx,
                                                   lhs_addr, lhs_extra,
                                                   rhs_addr, rhs_extra,
                                                   lhs.ty, op.to_hir_binop(),
                                                   DebugLoc::None)
                        }
                        _ => unreachable!()
                    }

                } else {
                    self.trans_scalar_binop(bcx, op,
                                            lhs.immediate(), rhs.immediate(),
                                            lhs.ty, DebugLoc::None)
                };
                (bcx, OperandRef {
                    val: OperandValue::Immediate(llresult),
                    ty: self.mir.binop_ty(bcx.tcx(), op, lhs.ty, rhs.ty),
                })
            }

            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 debug_loc = DebugLoc::None;
                let llval = match op {
                    mir::UnOp::Not => build::Not(bcx, lloperand, debug_loc),
                    mir::UnOp::Neg => if is_float {
                        build::FNeg(bcx, lloperand, debug_loc)
                    } else {
                        build::Neg(bcx, lloperand, debug_loc)
                    }
                };
                (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 = common::C_uint(bcx.ccx(), align);
                let llty_ptr = llty.ptr_to();
                let box_ty = bcx.tcx().mk_box(content_ty);
                let Result { bcx, val: llval } = base::malloc_raw_dyn(bcx,
                                                                      llty_ptr,
                                                                      box_ty,
                                                                      llsize,
                                                                      llalign,
                                                                      DebugLoc::None);
                (bcx, OperandRef {
                    val: OperandValue::Immediate(llval),
                    ty: box_ty,
                })
            }

            mir::Rvalue::Repeat(..) |
            mir::Rvalue::Aggregate(..) |
            mir::Rvalue::Slice { .. } |
            mir::Rvalue::InlineAsm(..) => {
                bcx.tcx().sess.bug(&format!("cannot generate operand from rvalue {:?}", rvalue));
            }
        }
    }

    pub fn trans_scalar_binop(&mut self,
                              bcx: Block<'bcx, 'tcx>,
                              op: mir::BinOp,
                              lhs: ValueRef,
                              rhs: ValueRef,
                              input_ty: Ty<'tcx>,
                              debug_loc: DebugLoc) -> ValueRef {
        let is_float = input_ty.is_fp();
        let is_signed = input_ty.is_signed();
        match op {
            mir::BinOp::Add => if is_float {
                build::FAdd(bcx, lhs, rhs, debug_loc)
            } else {
                build::Add(bcx, lhs, rhs, debug_loc)
            },
            mir::BinOp::Sub => if is_float {
                build::FSub(bcx, lhs, rhs, debug_loc)
            } else {
                build::Sub(bcx, lhs, rhs, debug_loc)
            },
            mir::BinOp::Mul => if is_float {
                build::FMul(bcx, lhs, rhs, debug_loc)
            } else {
                build::Mul(bcx, lhs, rhs, debug_loc)
            },
            mir::BinOp::Div => if is_float {
                build::FDiv(bcx, lhs, rhs, debug_loc)
            } else if is_signed {
                build::SDiv(bcx, lhs, rhs, debug_loc)
            } else {
                build::UDiv(bcx, lhs, rhs, debug_loc)
            },
            mir::BinOp::Rem => if is_float {
                // LLVM currently always lowers the `frem` instructions appropriate
                // library calls typically found in libm. Notably f64 gets wired up
                // to `fmod` and f32 gets wired up to `fmodf`. Inconveniently for
                // us, 32-bit MSVC does not actually have a `fmodf` symbol, it's
                // instead just an inline function in a header that goes up to a
                // f64, uses `fmod`, and then comes back down to a f32.
                //
                // Although LLVM knows that `fmodf` doesn't exist on MSVC, it will
                // still unconditionally lower frem instructions over 32-bit floats
                // to a call to `fmodf`. To work around this we special case MSVC
                // 32-bit float rem instructions and instead do the call out to
                // `fmod` ourselves.
                //
                // Note that this is currently duplicated with src/libcore/ops.rs
                // which does the same thing, and it would be nice to perhaps unify
                // these two implementations one day! Also note that we call `fmod`
                // for both 32 and 64-bit floats because if we emit any FRem
                // instruction at all then LLVM is capable of optimizing it into a
                // 32-bit FRem (which we're trying to avoid).
                let tcx = bcx.tcx();
                let use_fmod = tcx.sess.target.target.options.is_like_msvc &&
                    tcx.sess.target.target.arch == "x86";
                if use_fmod {
                    let f64t = Type::f64(bcx.ccx());
                    let fty = Type::func(&[f64t, f64t], &f64t);
                    let llfn = declare::declare_cfn(bcx.ccx(), "fmod", fty,
                                                    tcx.types.f64);
                    if input_ty == tcx.types.f32 {
                        let lllhs = build::FPExt(bcx, lhs, f64t);
                        let llrhs = build::FPExt(bcx, rhs, f64t);
                        let llres = build::Call(bcx, llfn, &[lllhs, llrhs],
                                                None, debug_loc);
                        build::FPTrunc(bcx, llres, Type::f32(bcx.ccx()))
                    } else {
                        build::Call(bcx, llfn, &[lhs, rhs],
                                    None, debug_loc)
                    }
                } else {
                    build::FRem(bcx, lhs, rhs, debug_loc)
                }
            } else if is_signed {
                build::SRem(bcx, lhs, rhs, debug_loc)
            } else {
                build::URem(bcx, lhs, rhs, debug_loc)
            },
            mir::BinOp::BitOr => build::Or(bcx, lhs, rhs, debug_loc),
            mir::BinOp::BitAnd => build::And(bcx, lhs, rhs, debug_loc),
            mir::BinOp::BitXor => build::Xor(bcx, lhs, rhs, debug_loc),
            mir::BinOp::Shl => common::build_unchecked_lshift(bcx,
                                                              lhs,
                                                              rhs,
                                                              debug_loc),
            mir::BinOp::Shr => common::build_unchecked_rshift(bcx,
                                                              input_ty,
                                                              lhs,
                                                              rhs,
                                                              debug_loc),
            mir::BinOp::Eq | mir::BinOp::Lt | mir::BinOp::Gt |
            mir::BinOp::Ne | mir::BinOp::Le | mir::BinOp::Ge => {
                base::compare_scalar_types(bcx, lhs, rhs, input_ty,
                                           op.to_hir_binop(), debug_loc)
            }
        }
    }
}

pub fn rvalue_creates_operand<'tcx>(rvalue: &mir::Rvalue<'tcx>) -> bool {
    match *rvalue {
        mir::Rvalue::Use(..) | // (*)
        mir::Rvalue::Ref(..) |
        mir::Rvalue::Len(..) |
        mir::Rvalue::Cast(..) | // (*)
        mir::Rvalue::BinaryOp(..) |
        mir::Rvalue::UnaryOp(..) |
        mir::Rvalue::Box(..) =>
            true,
        mir::Rvalue::Repeat(..) |
        mir::Rvalue::Aggregate(..) |
        mir::Rvalue::Slice { .. } |
        mir::Rvalue::InlineAsm(..) =>
            false,
    }

    // (*) this is only true if the type is suitable
}
