// Copyright 2012 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 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use llvm::ValueRef; use middle::def; use middle::lang_items::{PanicFnLangItem, PanicBoundsCheckFnLangItem}; use trans::base::*; use trans::basic_block::BasicBlock; use trans::build::*; use trans::callee; use trans::cleanup::CleanupMethods; use trans::cleanup; use trans::common::*; use trans::consts; use trans::debuginfo; use trans::debuginfo::{DebugLoc, ToDebugLoc}; use trans::expr; use trans::machine; use trans; use middle::ty; use rustc_front::hir; use rustc_front::util as ast_util; use syntax::ast; use syntax::parse::token::InternedString; use syntax::parse::token; pub fn trans_stmt<'blk, 'tcx>(cx: Block<'blk, 'tcx>, s: &hir::Stmt) -> Block<'blk, 'tcx> { let _icx = push_ctxt("trans_stmt"); let fcx = cx.fcx; debug!("trans_stmt({:?})", s); if cx.unreachable.get() { return cx; } if cx.sess().asm_comments() { add_span_comment(cx, s.span, &format!("{:?}", s)); } let mut bcx = cx; let id = ast_util::stmt_id(s); let cleanup_debug_loc = debuginfo::get_cleanup_debug_loc_for_ast_node(bcx.ccx(), id, s.span, false); fcx.push_ast_cleanup_scope(cleanup_debug_loc); match s.node { hir::StmtExpr(ref e, _) | hir::StmtSemi(ref e, _) => { bcx = trans_stmt_semi(bcx, &**e); } hir::StmtDecl(ref d, _) => { match d.node { hir::DeclLocal(ref local) => { bcx = init_local(bcx, &**local); debuginfo::create_local_var_metadata(bcx, &**local); } // Inner items are visited by `trans_item`/`trans_meth`. hir::DeclItem(_) => {}, } } } bcx = fcx.pop_and_trans_ast_cleanup_scope(bcx, ast_util::stmt_id(s)); return bcx; } pub fn trans_stmt_semi<'blk, 'tcx>(cx: Block<'blk, 'tcx>, e: &hir::Expr) -> Block<'blk, 'tcx> { let _icx = push_ctxt("trans_stmt_semi"); if cx.unreachable.get() { return cx; } let ty = expr_ty(cx, e); if cx.fcx.type_needs_drop(ty) { expr::trans_to_lvalue(cx, e, "stmt").bcx } else { expr::trans_into(cx, e, expr::Ignore) } } pub fn trans_block<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, b: &hir::Block, mut dest: expr::Dest) -> Block<'blk, 'tcx> { let _icx = push_ctxt("trans_block"); if bcx.unreachable.get() { return bcx; } let fcx = bcx.fcx; let mut bcx = bcx; let cleanup_debug_loc = debuginfo::get_cleanup_debug_loc_for_ast_node(bcx.ccx(), b.id, b.span, true); fcx.push_ast_cleanup_scope(cleanup_debug_loc); for s in &b.stmts { bcx = trans_stmt(bcx, &**s); } if dest != expr::Ignore { let block_ty = node_id_type(bcx, b.id); if b.expr.is_none() || type_is_zero_size(bcx.ccx(), block_ty) { dest = expr::Ignore; } else if b.expr.is_some() { // If the block has an expression, but that expression isn't reachable, // don't save into the destination given, ignore it. if let Some(ref cfg) = bcx.fcx.cfg { if !cfg.node_is_reachable(b.expr.as_ref().unwrap().id) { dest = expr::Ignore; } } } } match b.expr { Some(ref e) => { if !bcx.unreachable.get() { bcx = expr::trans_into(bcx, &**e, dest); } } None => { assert!(dest == expr::Ignore || bcx.unreachable.get()); } } bcx = fcx.pop_and_trans_ast_cleanup_scope(bcx, b.id); return bcx; } pub fn trans_if<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, if_id: ast::NodeId, cond: &hir::Expr, thn: &hir::Block, els: Option<&hir::Expr>, dest: expr::Dest) -> Block<'blk, 'tcx> { debug!("trans_if(bcx={}, if_id={}, cond={:?}, thn={}, dest={})", bcx.to_str(), if_id, cond, thn.id, dest.to_string(bcx.ccx())); let _icx = push_ctxt("trans_if"); if bcx.unreachable.get() { return bcx; } let mut bcx = bcx; let cond_val = unpack_result!(bcx, expr::trans(bcx, cond).to_llbool()); // Drop branches that are known to be impossible if let Some(cv) = const_to_opt_uint(cond_val) { if cv == 1 { // if true { .. } [else { .. }] bcx = trans_block(bcx, &*thn, dest); trans::debuginfo::clear_source_location(bcx.fcx); } else { if let Some(elexpr) = els { bcx = expr::trans_into(bcx, &*elexpr, dest); trans::debuginfo::clear_source_location(bcx.fcx); } } return bcx; } let name = format!("then-block-{}-", thn.id); let then_bcx_in = bcx.fcx.new_id_block(&name[..], thn.id); let then_bcx_out = trans_block(then_bcx_in, &*thn, dest); trans::debuginfo::clear_source_location(bcx.fcx); let cond_source_loc = cond.debug_loc(); let next_bcx; match els { Some(elexpr) => { let else_bcx_in = bcx.fcx.new_id_block("else-block", elexpr.id); let else_bcx_out = expr::trans_into(else_bcx_in, &*elexpr, dest); next_bcx = bcx.fcx.join_blocks(if_id, &[then_bcx_out, else_bcx_out]); CondBr(bcx, cond_val, then_bcx_in.llbb, else_bcx_in.llbb, cond_source_loc); } None => { next_bcx = bcx.fcx.new_id_block("next-block", if_id); Br(then_bcx_out, next_bcx.llbb, DebugLoc::None); CondBr(bcx, cond_val, then_bcx_in.llbb, next_bcx.llbb, cond_source_loc); } } // Clear the source location because it is still set to whatever has been translated // right before. trans::debuginfo::clear_source_location(next_bcx.fcx); next_bcx } pub fn trans_while<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, loop_expr: &hir::Expr, cond: &hir::Expr, body: &hir::Block) -> Block<'blk, 'tcx> { let _icx = push_ctxt("trans_while"); if bcx.unreachable.get() { return bcx; } let fcx = bcx.fcx; // bcx // | // cond_bcx_in <--------+ // | | // cond_bcx_out | // | | | // | body_bcx_in | // cleanup_blk | | // | body_bcx_out --+ // next_bcx_in let next_bcx_in = fcx.new_id_block("while_exit", loop_expr.id); let cond_bcx_in = fcx.new_id_block("while_cond", cond.id); let body_bcx_in = fcx.new_id_block("while_body", body.id); fcx.push_loop_cleanup_scope(loop_expr.id, [next_bcx_in, cond_bcx_in]); Br(bcx, cond_bcx_in.llbb, loop_expr.debug_loc()); // compile the block where we will handle loop cleanups let cleanup_llbb = fcx.normal_exit_block(loop_expr.id, cleanup::EXIT_BREAK); // compile the condition let Result {bcx: cond_bcx_out, val: cond_val} = expr::trans(cond_bcx_in, cond).to_llbool(); CondBr(cond_bcx_out, cond_val, body_bcx_in.llbb, cleanup_llbb, cond.debug_loc()); // loop body: let body_bcx_out = trans_block(body_bcx_in, body, expr::Ignore); Br(body_bcx_out, cond_bcx_in.llbb, DebugLoc::None); fcx.pop_loop_cleanup_scope(loop_expr.id); return next_bcx_in; } pub fn trans_loop<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, loop_expr: &hir::Expr, body: &hir::Block) -> Block<'blk, 'tcx> { let _icx = push_ctxt("trans_loop"); if bcx.unreachable.get() { return bcx; } let fcx = bcx.fcx; // bcx // | // body_bcx_in // | // body_bcx_out // // next_bcx // // Links between body_bcx_in and next_bcx are created by // break statements. let next_bcx_in = bcx.fcx.new_id_block("loop_exit", loop_expr.id); let body_bcx_in = bcx.fcx.new_id_block("loop_body", body.id); fcx.push_loop_cleanup_scope(loop_expr.id, [next_bcx_in, body_bcx_in]); Br(bcx, body_bcx_in.llbb, loop_expr.debug_loc()); let body_bcx_out = trans_block(body_bcx_in, body, expr::Ignore); Br(body_bcx_out, body_bcx_in.llbb, DebugLoc::None); fcx.pop_loop_cleanup_scope(loop_expr.id); // If there are no predecessors for the next block, we just translated an endless loop and the // next block is unreachable if BasicBlock(next_bcx_in.llbb).pred_iter().next().is_none() { Unreachable(next_bcx_in); } return next_bcx_in; } pub fn trans_break_cont<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, expr: &hir::Expr, opt_label: Option, exit: usize) -> Block<'blk, 'tcx> { let _icx = push_ctxt("trans_break_cont"); if bcx.unreachable.get() { return bcx; } let fcx = bcx.fcx; // Locate loop that we will break to let loop_id = match opt_label { None => fcx.top_loop_scope(), Some(_) => { match bcx.tcx().def_map.borrow().get(&expr.id).map(|d| d.full_def()) { Some(def::DefLabel(loop_id)) => loop_id, r => { bcx.tcx().sess.bug(&format!("{:?} in def-map for label", r)) } } } }; // Generate appropriate cleanup code and branch let cleanup_llbb = fcx.normal_exit_block(loop_id, exit); Br(bcx, cleanup_llbb, expr.debug_loc()); Unreachable(bcx); // anything afterwards should be ignored return bcx; } pub fn trans_break<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, expr: &hir::Expr, label_opt: Option) -> Block<'blk, 'tcx> { return trans_break_cont(bcx, expr, label_opt, cleanup::EXIT_BREAK); } pub fn trans_cont<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, expr: &hir::Expr, label_opt: Option) -> Block<'blk, 'tcx> { return trans_break_cont(bcx, expr, label_opt, cleanup::EXIT_LOOP); } pub fn trans_ret<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, return_expr: &hir::Expr, retval_expr: Option<&hir::Expr>) -> Block<'blk, 'tcx> { let _icx = push_ctxt("trans_ret"); if bcx.unreachable.get() { return bcx; } let fcx = bcx.fcx; let mut bcx = bcx; let dest = match (fcx.llretslotptr.get(), retval_expr) { (Some(_), Some(retval_expr)) => { let ret_ty = expr_ty_adjusted(bcx, &*retval_expr); expr::SaveIn(fcx.get_ret_slot(bcx, ty::FnConverging(ret_ty), "ret_slot")) } _ => expr::Ignore, }; if let Some(x) = retval_expr { bcx = expr::trans_into(bcx, &*x, dest); match dest { expr::SaveIn(slot) if fcx.needs_ret_allocas => { Store(bcx, slot, fcx.llretslotptr.get().unwrap()); } _ => {} } } let cleanup_llbb = fcx.return_exit_block(); Br(bcx, cleanup_llbb, return_expr.debug_loc()); Unreachable(bcx); return bcx; } pub fn trans_fail<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, call_info: NodeIdAndSpan, fail_str: InternedString) -> Block<'blk, 'tcx> { let ccx = bcx.ccx(); let _icx = push_ctxt("trans_fail_value"); if bcx.unreachable.get() { return bcx; } let v_str = C_str_slice(ccx, fail_str); let loc = bcx.sess().codemap().lookup_char_pos(call_info.span.lo); let filename = token::intern_and_get_ident(&loc.file.name); let filename = C_str_slice(ccx, filename); let line = C_u32(ccx, loc.line as u32); let expr_file_line_const = C_struct(ccx, &[v_str, filename, line], false); let align = machine::llalign_of_min(ccx, val_ty(expr_file_line_const)); let expr_file_line = consts::addr_of(ccx, expr_file_line_const, align, "panic_loc"); let args = vec!(expr_file_line); let did = langcall(bcx, Some(call_info.span), "", PanicFnLangItem); let bcx = callee::trans_lang_call(bcx, did, &args[..], Some(expr::Ignore), call_info.debug_loc()).bcx; Unreachable(bcx); return bcx; } pub fn trans_fail_bounds_check<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, call_info: NodeIdAndSpan, index: ValueRef, len: ValueRef) -> Block<'blk, 'tcx> { let ccx = bcx.ccx(); let _icx = push_ctxt("trans_fail_bounds_check"); if bcx.unreachable.get() { return bcx; } // Extract the file/line from the span let loc = bcx.sess().codemap().lookup_char_pos(call_info.span.lo); let filename = token::intern_and_get_ident(&loc.file.name); // Invoke the lang item let filename = C_str_slice(ccx, filename); let line = C_u32(ccx, loc.line as u32); let file_line_const = C_struct(ccx, &[filename, line], false); let align = machine::llalign_of_min(ccx, val_ty(file_line_const)); let file_line = consts::addr_of(ccx, file_line_const, align, "panic_bounds_check_loc"); let args = vec!(file_line, index, len); let did = langcall(bcx, Some(call_info.span), "", PanicBoundsCheckFnLangItem); let bcx = callee::trans_lang_call(bcx, did, &args[..], Some(expr::Ignore), call_info.debug_loc()).bcx; Unreachable(bcx); return bcx; }