diff --git a/src/librustc/middle/lang_items.rs b/src/librustc/middle/lang_items.rs index 19a43f3b5dd84..a858a8d7449f4 100644 --- a/src/librustc/middle/lang_items.rs +++ b/src/librustc/middle/lang_items.rs @@ -310,6 +310,34 @@ language_item_table! { NonZeroItem, "non_zero", non_zero; DebugTraitLangItem, "debug_trait", debug_trait; + + // A lang item for each of the 128-bit operators we can optionally lower. + I128AddFnLangItem, "i128_add", i128_add_fn; + U128AddFnLangItem, "u128_add", u128_add_fn; + I128SubFnLangItem, "i128_sub", i128_sub_fn; + U128SubFnLangItem, "u128_sub", u128_sub_fn; + I128MulFnLangItem, "i128_mul", i128_mul_fn; + U128MulFnLangItem, "u128_mul", u128_mul_fn; + I128DivFnLangItem, "i128_div", i128_div_fn; + U128DivFnLangItem, "u128_div", u128_div_fn; + I128RemFnLangItem, "i128_rem", i128_rem_fn; + U128RemFnLangItem, "u128_rem", u128_rem_fn; + I128ShlFnLangItem, "i128_shl", i128_shl_fn; + U128ShlFnLangItem, "u128_shl", u128_shl_fn; + I128ShrFnLangItem, "i128_shr", i128_shr_fn; + U128ShrFnLangItem, "u128_shr", u128_shr_fn; + // And overflow versions for the operators that are checkable. + // While MIR calls these Checked*, they return (T,bool), not Option. + I128AddoFnLangItem, "i128_addo", i128_addo_fn; + U128AddoFnLangItem, "u128_addo", u128_addo_fn; + I128SuboFnLangItem, "i128_subo", i128_subo_fn; + U128SuboFnLangItem, "u128_subo", u128_subo_fn; + I128MuloFnLangItem, "i128_mulo", i128_mulo_fn; + U128MuloFnLangItem, "u128_mulo", u128_mulo_fn; + I128ShloFnLangItem, "i128_shlo", i128_shlo_fn; + U128ShloFnLangItem, "u128_shlo", u128_shlo_fn; + I128ShroFnLangItem, "i128_shro", i128_shro_fn; + U128ShroFnLangItem, "u128_shro", u128_shro_fn; } impl<'a, 'tcx, 'gcx> TyCtxt<'a, 'tcx, 'gcx> { diff --git a/src/librustc/mir/mod.rs b/src/librustc/mir/mod.rs index 355fb570c000b..485b7a47fa1f4 100644 --- a/src/librustc/mir/mod.rs +++ b/src/librustc/mir/mod.rs @@ -170,6 +170,15 @@ impl<'tcx> Mir<'tcx> { &mut self.basic_blocks } + #[inline] + pub fn basic_blocks_and_local_decls_mut(&mut self) -> ( + &mut IndexVec>, + &mut LocalDecls<'tcx>, + ) { + self.cache.invalidate(); + (&mut self.basic_blocks, &mut self.local_decls) + } + #[inline] pub fn predecessors(&self) -> Ref>> { self.cache.predecessors(self) diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index 57fae2200e276..d992dbdfcb776 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -1142,6 +1142,9 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, saturating_float_casts: bool = (false, parse_bool, [TRACKED], "make float->int casts UB-free: numbers outside the integer type's range are clipped to \ the max/min integer respectively, and NaN is mapped to 0"), + lower_128bit_ops: bool = (false, parse_bool, [TRACKED], + "rewrite operators on i128 and u128 into lang item calls (typically provided \ + by compiler-builtins) so translation doesn't need to support them"), } pub fn default_lib_output() -> CrateType { diff --git a/src/librustc_mir/transform/lower_128bit.rs b/src/librustc_mir/transform/lower_128bit.rs new file mode 100644 index 0000000000000..9dc5fdadbb195 --- /dev/null +++ b/src/librustc_mir/transform/lower_128bit.rs @@ -0,0 +1,240 @@ +// Copyright 2017 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. + +//! Replaces 128-bit operators with lang item calls + +use rustc::hir::def_id::DefId; +use rustc::middle::lang_items::LangItem; +use rustc::mir::*; +use rustc::ty::{Slice, Ty, TyCtxt, TypeVariants}; +use rustc_data_structures::indexed_vec::{Idx}; +use transform::{MirPass, MirSource}; +use syntax; + +pub struct Lower128Bit; + +impl MirPass for Lower128Bit { + fn run_pass<'a, 'tcx>(&self, + tcx: TyCtxt<'a, 'tcx, 'tcx>, + _src: MirSource, + mir: &mut Mir<'tcx>) { + if !tcx.sess.opts.debugging_opts.lower_128bit_ops { + return + } + + self.lower_128bit_ops(tcx, mir); + } +} + +impl Lower128Bit { + fn lower_128bit_ops<'a, 'tcx>(&self, tcx: TyCtxt<'a, 'tcx, 'tcx>, mir: &mut Mir<'tcx>) { + let mut new_blocks = Vec::new(); + let cur_len = mir.basic_blocks().len(); + + let (basic_blocks, local_decls) = mir.basic_blocks_and_local_decls_mut(); + for block in basic_blocks.iter_mut() { + for i in (0..block.statements.len()).rev() { + let (lang_item, rhs_kind) = + if let Some((lang_item, rhs_kind)) = + lower_to(&block.statements[i], local_decls, tcx) + { + (lang_item, rhs_kind) + } else { + continue; + }; + + let rhs_override_ty = rhs_kind.ty(tcx); + let cast_local = + match rhs_override_ty { + None => None, + Some(ty) => { + let local_decl = LocalDecl::new_internal( + ty, block.statements[i].source_info.span); + Some(local_decls.push(local_decl)) + }, + }; + + let storage_dead = cast_local.map(|local| { + Statement { + source_info: block.statements[i].source_info, + kind: StatementKind::StorageDead(local), + } + }); + let after_call = BasicBlockData { + statements: storage_dead.into_iter() + .chain(block.statements.drain((i+1)..)).collect(), + is_cleanup: block.is_cleanup, + terminator: block.terminator.take(), + }; + + let bin_statement = block.statements.pop().unwrap(); + let (source_info, lvalue, lhs, mut rhs) = match bin_statement { + Statement { + source_info, + kind: StatementKind::Assign( + lvalue, + Rvalue::BinaryOp(_, lhs, rhs)) + } => (source_info, lvalue, lhs, rhs), + Statement { + source_info, + kind: StatementKind::Assign( + lvalue, + Rvalue::CheckedBinaryOp(_, lhs, rhs)) + } => (source_info, lvalue, lhs, rhs), + _ => bug!("Statement doesn't match pattern any more?"), + }; + + if let Some(local) = cast_local { + block.statements.push(Statement { + source_info: source_info, + kind: StatementKind::StorageLive(local), + }); + block.statements.push(Statement { + source_info: source_info, + kind: StatementKind::Assign( + Lvalue::Local(local), + Rvalue::Cast( + CastKind::Misc, + rhs, + rhs_override_ty.unwrap())), + }); + rhs = Operand::Consume(Lvalue::Local(local)); + } + + let call_did = check_lang_item_type( + lang_item, &lvalue, &lhs, &rhs, local_decls, tcx); + + let bb = BasicBlock::new(cur_len + new_blocks.len()); + new_blocks.push(after_call); + + block.terminator = + Some(Terminator { + source_info, + kind: TerminatorKind::Call { + func: Operand::function_handle(tcx, call_did, + Slice::empty(), source_info.span), + args: vec![lhs, rhs], + destination: Some((lvalue, bb)), + cleanup: None, + }, + }); + } + } + + basic_blocks.extend(new_blocks); + } +} + +fn check_lang_item_type<'a, 'tcx, D>( + lang_item: LangItem, + lvalue: &Lvalue<'tcx>, + lhs: &Operand<'tcx>, + rhs: &Operand<'tcx>, + local_decls: &D, + tcx: TyCtxt<'a, 'tcx, 'tcx>) +-> DefId + where D: HasLocalDecls<'tcx> +{ + let did = tcx.require_lang_item(lang_item); + let poly_sig = tcx.fn_sig(did); + let sig = tcx.no_late_bound_regions(&poly_sig).unwrap(); + let lhs_ty = lhs.ty(local_decls, tcx); + let rhs_ty = rhs.ty(local_decls, tcx); + let lvalue_ty = lvalue.ty(local_decls, tcx).to_ty(tcx); + let expected = [lhs_ty, rhs_ty, lvalue_ty]; + assert_eq!(sig.inputs_and_output[..], expected, + "lang item {}", tcx.def_symbol_name(did)); + did +} + +fn lower_to<'a, 'tcx, D>(statement: &Statement<'tcx>, local_decls: &D, tcx: TyCtxt<'a, 'tcx, 'tcx>) + -> Option<(LangItem, RhsKind)> + where D: HasLocalDecls<'tcx> +{ + match statement.kind { + StatementKind::Assign(_, Rvalue::BinaryOp(bin_op, ref lhs, _)) => { + let ty = lhs.ty(local_decls, tcx); + if let Some(is_signed) = sign_of_128bit(ty) { + return item_for_op(bin_op, is_signed); + } + }, + StatementKind::Assign(_, Rvalue::CheckedBinaryOp(bin_op, ref lhs, _)) => { + let ty = lhs.ty(local_decls, tcx); + if let Some(is_signed) = sign_of_128bit(ty) { + return item_for_checked_op(bin_op, is_signed); + } + }, + _ => {}, + } + None +} + +#[derive(Copy, Clone)] +enum RhsKind { + Unchanged, + ForceU128, + ForceU32, +} + +impl RhsKind { + fn ty<'a, 'tcx>(&self, tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Option> { + match *self { + RhsKind::Unchanged => None, + RhsKind::ForceU128 => Some(tcx.types.u128), + RhsKind::ForceU32 => Some(tcx.types.u32), + } + } +} + +fn sign_of_128bit(ty: Ty) -> Option { + match ty.sty { + TypeVariants::TyInt(syntax::ast::IntTy::I128) => Some(true), + TypeVariants::TyUint(syntax::ast::UintTy::U128) => Some(false), + _ => None, + } +} + +fn item_for_op(bin_op: BinOp, is_signed: bool) -> Option<(LangItem, RhsKind)> { + let i = match (bin_op, is_signed) { + (BinOp::Add, true) => (LangItem::I128AddFnLangItem, RhsKind::Unchanged), + (BinOp::Add, false) => (LangItem::U128AddFnLangItem, RhsKind::Unchanged), + (BinOp::Sub, true) => (LangItem::I128SubFnLangItem, RhsKind::Unchanged), + (BinOp::Sub, false) => (LangItem::U128SubFnLangItem, RhsKind::Unchanged), + (BinOp::Mul, true) => (LangItem::I128MulFnLangItem, RhsKind::Unchanged), + (BinOp::Mul, false) => (LangItem::U128MulFnLangItem, RhsKind::Unchanged), + (BinOp::Div, true) => (LangItem::I128DivFnLangItem, RhsKind::Unchanged), + (BinOp::Div, false) => (LangItem::U128DivFnLangItem, RhsKind::Unchanged), + (BinOp::Rem, true) => (LangItem::I128RemFnLangItem, RhsKind::Unchanged), + (BinOp::Rem, false) => (LangItem::U128RemFnLangItem, RhsKind::Unchanged), + (BinOp::Shl, true) => (LangItem::I128ShlFnLangItem, RhsKind::ForceU32), + (BinOp::Shl, false) => (LangItem::U128ShlFnLangItem, RhsKind::ForceU32), + (BinOp::Shr, true) => (LangItem::I128ShrFnLangItem, RhsKind::ForceU32), + (BinOp::Shr, false) => (LangItem::U128ShrFnLangItem, RhsKind::ForceU32), + _ => return None, + }; + Some(i) +} + +fn item_for_checked_op(bin_op: BinOp, is_signed: bool) -> Option<(LangItem, RhsKind)> { + let i = match (bin_op, is_signed) { + (BinOp::Add, true) => (LangItem::I128AddoFnLangItem, RhsKind::Unchanged), + (BinOp::Add, false) => (LangItem::U128AddoFnLangItem, RhsKind::Unchanged), + (BinOp::Sub, true) => (LangItem::I128SuboFnLangItem, RhsKind::Unchanged), + (BinOp::Sub, false) => (LangItem::U128SuboFnLangItem, RhsKind::Unchanged), + (BinOp::Mul, true) => (LangItem::I128MuloFnLangItem, RhsKind::Unchanged), + (BinOp::Mul, false) => (LangItem::U128MuloFnLangItem, RhsKind::Unchanged), + (BinOp::Shl, true) => (LangItem::I128ShloFnLangItem, RhsKind::ForceU128), + (BinOp::Shl, false) => (LangItem::U128ShloFnLangItem, RhsKind::ForceU128), + (BinOp::Shr, true) => (LangItem::I128ShroFnLangItem, RhsKind::ForceU128), + (BinOp::Shr, false) => (LangItem::U128ShroFnLangItem, RhsKind::ForceU128), + _ => bug!("That should be all the checked ones?"), + }; + Some(i) +} \ No newline at end of file diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs index 441f9be9be1f4..6987cfa79be09 100644 --- a/src/librustc_mir/transform/mod.rs +++ b/src/librustc_mir/transform/mod.rs @@ -42,6 +42,7 @@ pub mod copy_prop; pub mod generator; pub mod inline; pub mod nll; +pub mod lower_128bit; pub(crate) fn provide(providers: &mut Providers) { self::qualify_consts::provide(providers); @@ -241,6 +242,8 @@ fn optimized_mir<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> &'tcx // From here on out, regions are gone. erase_regions::EraseRegions, + lower_128bit::Lower128Bit, + // Optimizations begin. inline::Inline, instcombine::InstCombine, diff --git a/src/test/mir-opt/lower_128bit_debug_test.rs b/src/test/mir-opt/lower_128bit_debug_test.rs new file mode 100644 index 0000000000000..4626dc17e1f6e --- /dev/null +++ b/src/test/mir-opt/lower_128bit_debug_test.rs @@ -0,0 +1,145 @@ +// Copyright 2017 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. + +// compile-flags: -Z lower_128bit_ops -C debug_assertions=yes + +#![feature(i128_type)] +#![feature(lang_items)] + +#[lang="i128_div"] +fn i128_div(_x: i128, _y: i128) -> i128 { 3 } +#[lang="u128_div"] +fn u128_div(_x: u128, _y: u128) -> u128 { 4 } +#[lang="i128_rem"] +fn i128_rem(_x: i128, _y: i128) -> i128 { 5 } +#[lang="u128_rem"] +fn u128_rem(_x: u128, _y: u128) -> u128 { 6 } + +#[lang="i128_addo"] +fn i128_addo(_x: i128, _y: i128) -> (i128, bool) { (0, false) } +#[lang="u128_addo"] +fn u128_addo(_x: u128, _y: u128) -> (u128, bool) { (1, false) } +#[lang="i128_subo"] +fn i128_subo(_x: i128, _y: i128) -> (i128, bool) { (2, false) } +#[lang="u128_subo"] +fn u128_subo(_x: u128, _y: u128) -> (u128, bool) { (3, false) } +#[lang="i128_mulo"] +fn i128_mulo(_x: i128, _y: i128) -> (i128, bool) { (4, false) } +#[lang="u128_mulo"] +fn u128_mulo(_x: u128, _y: u128) -> (u128, bool) { (5, false) } +#[lang="i128_shlo"] +fn i128_shlo(_x: i128, _y: u128) -> (i128, bool) { (6, false) } +#[lang="u128_shlo"] +fn u128_shlo(_x: u128, _y: u128) -> (u128, bool) { (6, false) } +#[lang="i128_shro"] +fn i128_shro(_x: i128, _y: u128) -> (i128, bool) { (7, false) } +#[lang="u128_shro"] +fn u128_shro(_x: u128, _y: u128) -> (u128, bool) { (8, false) } + +fn test_signed(mut x: i128) -> i128 { + x += 1; + x -= 2; + x *= 3; + x /= 4; + x %= 5; + x <<= 6; + x >>= 7; + x +} + +fn test_unsigned(mut x: u128) -> u128 { + x += 1; + x -= 2; + x *= 3; + x /= 4; + x %= 5; + x <<= 6; + x >>= 7; + x +} + +fn main() { + test_signed(-200); + test_unsigned(200); +} + +// END RUST SOURCE + +// START rustc.test_signed.Lower128Bit.after.mir +// _2 = const i128_addo(_1, const 1i128) -> bb10; +// ... +// _1 = (_2.0: i128); +// _3 = const i128_subo(_1, const 2i128) -> bb11; +// ... +// _1 = (_3.0: i128); +// _4 = const i128_mulo(_1, const 3i128) -> bb12; +// ... +// _1 = (_4.0: i128); +// ... +// _1 = const i128_div(_1, const 4i128) -> bb13; +// ... +// _1 = const i128_rem(_1, const 5i128) -> bb15; +// ... +// _1 = (_13.0: i128); +// ... +// _17 = const 7i32 as u128 (Misc); +// _14 = const i128_shro(_1, _17) -> bb16; +// ... +// _1 = (_14.0: i128); +// ... +// assert(!(_2.1: bool), "attempt to add with overflow") -> bb1; +// ... +// assert(!(_3.1: bool), "attempt to subtract with overflow") -> bb2; +// ... +// assert(!(_4.1: bool), "attempt to multiply with overflow") -> bb3; +// ... +// assert(!(_13.1: bool), "attempt to shift left with overflow") -> bb8; +// ... +// _16 = const 6i32 as u128 (Misc); +// _13 = const i128_shlo(_1, _16) -> bb14; +// ... +// assert(!(_14.1: bool), "attempt to shift right with overflow") -> bb9; +// END rustc.test_signed.Lower128Bit.after.mir + +// START rustc.test_unsigned.Lower128Bit.after.mir +// _2 = const u128_addo(_1, const 1u128) -> bb8; +// ... +// _1 = (_2.0: u128); +// _3 = const u128_subo(_1, const 2u128) -> bb9; +// ... +// _1 = (_3.0: u128); +// _4 = const u128_mulo(_1, const 3u128) -> bb10; +// ... +// _1 = (_4.0: u128); +// ... +// _1 = const u128_div(_1, const 4u128) -> bb11; +// ... +// _1 = const u128_rem(_1, const 5u128) -> bb13; +// ... +// _1 = (_7.0: u128); +// ... +// _11 = const 7i32 as u128 (Misc); +// _8 = const u128_shro(_1, _11) -> bb14; +// ... +// _1 = (_8.0: u128); +// ... +// assert(!(_2.1: bool), "attempt to add with overflow") -> bb1; +// ... +// assert(!(_3.1: bool), "attempt to subtract with overflow") -> bb2; +// ... +// assert(!(_4.1: bool), "attempt to multiply with overflow") -> bb3; +// ... +// assert(!(_7.1: bool), "attempt to shift left with overflow") -> bb6; +// ... +// _10 = const 6i32 as u128 (Misc); +// _7 = const u128_shlo(_1, _10) -> bb12; +// ... +// assert(!(_8.1: bool), "attempt to shift right with overflow") -> bb7; +// END rustc.test_unsigned.Lower128Bit.after.mir diff --git a/src/test/mir-opt/lower_128bit_test.rs b/src/test/mir-opt/lower_128bit_test.rs new file mode 100644 index 0000000000000..207cd0ac57eb0 --- /dev/null +++ b/src/test/mir-opt/lower_128bit_test.rs @@ -0,0 +1,108 @@ +// Copyright 2017 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. + +// compile-flags: -Z lower_128bit_ops -C debug_assertions=no + +#![feature(i128_type)] +#![feature(lang_items)] + +#[lang="i128_add"] +fn i128_add(_x: i128, _y: i128) -> i128 { 0 } +#[lang="u128_add"] +fn u128_add(_x: u128, _y: u128) -> u128 { 0 } +#[lang="i128_sub"] +fn i128_sub(_x: i128, _y: i128) -> i128 { 1 } +#[lang="u128_sub"] +fn u128_sub(_x: u128, _y: u128) -> u128 { 1 } +#[lang="i128_mul"] +fn i128_mul(_x: i128, _y: i128) -> i128 { 2 } +#[lang="u128_mul"] +fn u128_mul(_x: u128, _y: u128) -> u128 { 2 } +#[lang="i128_div"] +fn i128_div(_x: i128, _y: i128) -> i128 { 3 } +#[lang="u128_div"] +fn u128_div(_x: u128, _y: u128) -> u128 { 4 } +#[lang="i128_rem"] +fn i128_rem(_x: i128, _y: i128) -> i128 { 5 } +#[lang="u128_rem"] +fn u128_rem(_x: u128, _y: u128) -> u128 { 6 } +#[lang="i128_shl"] +fn i128_shl(_x: i128, _y: u32) -> i128 { 7 } +#[lang="u128_shl"] +fn u128_shl(_x: u128, _y: u32) -> u128 { 7 } +#[lang="i128_shr"] +fn i128_shr(_x: i128, _y: u32) -> i128 { 8 } +#[lang="u128_shr"] +fn u128_shr(_x: u128, _y: u32) -> u128 { 9 } + +fn test_signed(mut x: i128) -> i128 { + x += 1; + x -= 2; + x *= 3; + x /= 4; + x %= 5; + x <<= 6; + x >>= 7; + x +} + +fn test_unsigned(mut x: u128) -> u128 { + x += 1; + x -= 2; + x *= 3; + x /= 4; + x %= 5; + x <<= 6; + x >>= 7; + x +} + +fn main() { + test_signed(-200); + test_unsigned(200); +} + +// END RUST SOURCE + +// START rustc.test_signed.Lower128Bit.after.mir +// _1 = const i128_add(_1, const 1i128) -> bb7; +// ... +// _1 = const i128_div(_1, const 4i128) -> bb8; +// ... +// _1 = const i128_rem(_1, const 5i128) -> bb11; +// ... +// _1 = const i128_mul(_1, const 3i128) -> bb5; +// ... +// _1 = const i128_sub(_1, const 2i128) -> bb6; +// ... +// _11 = const 7i32 as u32 (Misc); +// _1 = const i128_shr(_1, _11) -> bb9; +// ... +// _12 = const 6i32 as u32 (Misc); +// _1 = const i128_shl(_1, _12) -> bb10; +// END rustc.test_signed.Lower128Bit.after.mir + +// START rustc.test_unsigned.Lower128Bit.after.mir +// _1 = const u128_add(_1, const 1u128) -> bb5; +// ... +// _1 = const u128_div(_1, const 4u128) -> bb6; +// ... +// _1 = const u128_rem(_1, const 5u128) -> bb9; +// ... +// _1 = const u128_mul(_1, const 3u128) -> bb3; +// ... +// _1 = const u128_sub(_1, const 2u128) -> bb4; +// ... +// _5 = const 7i32 as u32 (Misc); +// _1 = const u128_shr(_1, _5) -> bb7; +// ... +// _6 = const 6i32 as u32 (Misc); +// _1 = const u128_shl(_1, _6) -> bb8; +// END rustc.test_unsigned.Lower128Bit.after.mir