From 57c0801e33ed39560ac23ea8b6b6d87af6fe3a3e Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Wed, 15 Nov 2017 23:27:02 -0800 Subject: [PATCH] Add a MIR pass to lower 128-bit operators to lang item calls Runs only with `-Z lower_128bit_ops` since it's not hooked into targets yet. --- src/librustc/middle/lang_items.rs | 23 +++ src/librustc/mir/mod.rs | 9 ++ src/librustc/session/config.rs | 3 + src/librustc_mir/transform/lower_128bit.rs | 160 ++++++++++++++++++++ src/librustc_mir/transform/mod.rs | 3 + src/test/mir-opt/lower_128bit_debug_test.rs | 104 +++++++++++++ src/test/mir-opt/lower_128bit_test.rs | 97 ++++++++++++ 7 files changed, 399 insertions(+) create mode 100644 src/librustc_mir/transform/lower_128bit.rs create mode 100644 src/test/mir-opt/lower_128bit_debug_test.rs create mode 100644 src/test/mir-opt/lower_128bit_test.rs diff --git a/src/librustc/middle/lang_items.rs b/src/librustc/middle/lang_items.rs index 19a43f3b5dd8..1c52a8dbc97d 100644 --- a/src/librustc/middle/lang_items.rs +++ b/src/librustc/middle/lang_items.rs @@ -310,6 +310,29 @@ 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; + I128SubFnLangItem, "i128_sub", i128_sub_fn; + I128MulFnLangItem, "i128_mul", i128_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; + 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; + 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 355fb570c000..485b7a47fa1f 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 57fae2200e27..d992dbdfcb77 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 000000000000..3a5b8e0790d6 --- /dev/null +++ b/src/librustc_mir/transform/lower_128bit.rs @@ -0,0 +1,160 @@ +// 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 call_did = + if let Some(call_did) = lower_to(&block.statements[i], local_decls, tcx) { + call_did + } else { + continue; + }; + + let after_call = BasicBlockData { + statements: 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, 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?"), + }; + + 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 lower_to<'a, 'tcx, D>(statement: &Statement<'tcx>, local_decls: &D, tcx: TyCtxt<'a, 'tcx, 'tcx>) + -> Option + 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) { + if let Some(item) = item_for_op(bin_op, is_signed) { + return Some(tcx.require_lang_item(item)) + } + } + }, + StatementKind::Assign(_, Rvalue::CheckedBinaryOp(bin_op, ref lhs, _)) => { + let ty = lhs.ty(local_decls, tcx); + if let Some(is_signed) = sign_of_128bit(&ty) { + if let Some(item) = item_for_checked_op(bin_op, is_signed) { + return Some(tcx.require_lang_item(item)) + } + } + }, + _ => {}, + } + None +} + +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 { + let i = match (bin_op, is_signed) { + (BinOp::Add, _) => LangItem::I128AddFnLangItem, + (BinOp::Sub, _) => LangItem::I128SubFnLangItem, + (BinOp::Mul, _) => LangItem::I128MulFnLangItem, + (BinOp::Div, true) => LangItem::I128DivFnLangItem, + (BinOp::Div, false) => LangItem::U128DivFnLangItem, + (BinOp::Rem, true) => LangItem::I128RemFnLangItem, + (BinOp::Rem, false) => LangItem::U128RemFnLangItem, + (BinOp::Shl, _) => LangItem::I128ShlFnLangItem, + (BinOp::Shr, true) => LangItem::I128ShrFnLangItem, + (BinOp::Shr, false) => LangItem::U128ShrFnLangItem, + _ => return None, + }; + Some(i) +} + +fn item_for_checked_op(bin_op: BinOp, is_signed: bool) -> Option { + let i = match (bin_op, is_signed) { + (BinOp::Add, true) => LangItem::I128AddoFnLangItem, + (BinOp::Add, false) => LangItem::U128AddoFnLangItem, + (BinOp::Sub, true) => LangItem::I128SuboFnLangItem, + (BinOp::Sub, false) => LangItem::U128SuboFnLangItem, + (BinOp::Mul, true) => LangItem::I128MuloFnLangItem, + (BinOp::Mul, false) => LangItem::U128MuloFnLangItem, + (BinOp::Shl, _) => LangItem::I128ShloFnLangItem, + (BinOp::Shr, true) => LangItem::I128ShroFnLangItem, + (BinOp::Shr, false) => LangItem::U128ShroFnLangItem, + _ => return None, + }; + 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 441f9be9be1f..6987cfa79be0 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 000000000000..3280c8db7f22 --- /dev/null +++ b/src/test/mir-opt/lower_128bit_debug_test.rs @@ -0,0 +1,104 @@ +// 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: i128, _y: i128) -> i128 { 4 } +#[lang="i128_rem"] +fn i128_rem(_x: i128, _y: i128) -> i128 { 5 } +#[lang="u128_rem"] +fn u128_rem(_x: i128, _y: i128) -> i128 { 6 } + +#[lang="i128_addo"] +fn i128_addo(_x: i128, _y: i128) -> (i128, bool) { (0, false) } +#[lang="u128_addo"] +fn u128_addo(_x: i128, _y: i128) -> (i128, bool) { (1, false) } +#[lang="i128_subo"] +fn i128_subo(_x: i128, _y: i128) -> (i128, bool) { (2, false) } +#[lang="u128_subo"] +fn u128_subo(_x: i128, _y: i128) -> (i128, bool) { (3, false) } +#[lang="i128_mulo"] +fn i128_mulo(_x: i128, _y: i128) -> (i128, bool) { (4, false) } +#[lang="u128_mulo"] +fn u128_mulo(_x: i128, _y: i128) -> (i128, bool) { (5, false) } +#[lang="i128_shlo"] +fn i128_shlo(_x: i128, _y: u32) -> (i128, bool) { (6, false) } +#[lang="i128_shro"] +fn i128_shro(_x: i128, _y: u32) -> (i128, bool) { (7, false) } +#[lang="u128_shro"] +fn u128_shro(_x: i128, _y: u32) -> (i128, 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; +// ... +// _3 = const i128_subo(_1, const 2i128) -> bb11; +// ... +// _4 = const i128_mulo(_1, const 3i128) -> bb12; +// ... +// _1 = const i128_div(_1, const 4i128) -> bb13; +// ... +// _1 = const i128_rem(_1, const 5i128) -> bb15; +// ... +// _14 = const i128_shro(_1, const 7i32) -> bb16; +// ... +// _13 = const i128_shlo(_1, const 6i32) -> bb14; +// END rustc.test_signed.Lower128Bit.after.mir + +// START rustc.test_unsigned.Lower128Bit.after.mir +// _2 = const u128_addo(_1, const 1u128) -> bb8; +// ... +// _3 = const u128_subo(_1, const 2u128) -> bb9; +// ... +// _4 = const u128_mulo(_1, const 3u128) -> bb10; +// ... +// _1 = const u128_div(_1, const 4u128) -> bb11; +// ... +// _1 = const u128_rem(_1, const 5u128) -> bb13; +// ... +// _8 = const u128_shro(_1, const 7i32) -> bb14; +// ... +// _7 = const i128_shlo(_1, const 6i32) -> bb12; +// 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 000000000000..15a5535dba48 --- /dev/null +++ b/src/test/mir-opt/lower_128bit_test.rs @@ -0,0 +1,97 @@ +// 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="i128_sub"] +fn i128_sub(_x: i128, _y: i128) -> i128 { 1 } +#[lang="i128_mul"] +fn i128_mul(_x: i128, _y: i128) -> i128 { 2 } +#[lang="i128_div"] +fn i128_div(_x: i128, _y: i128) -> i128 { 3 } +#[lang="u128_div"] +fn u128_div(_x: i128, _y: i128) -> i128 { 4 } +#[lang="i128_rem"] +fn i128_rem(_x: i128, _y: i128) -> i128 { 5 } +#[lang="u128_rem"] +fn u128_rem(_x: i128, _y: i128) -> i128 { 6 } +#[lang="i128_shl"] +fn i128_shl(_x: i128, _y: u32) -> i128 { 7 } +#[lang="i128_shr"] +fn i128_shr(_x: i128, _y: u32) -> i128 { 8 } +#[lang="u128_shr"] +fn u128_shr(_x: i128, _y: u32) -> i128 { 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; +// ... +// _1 = const i128_shr(_1, const 7i32) -> bb9; +// ... +// _1 = const i128_shl(_1, const 6i32) -> bb10; +// END rustc.test_signed.Lower128Bit.after.mir + +// START rustc.test_unsigned.Lower128Bit.after.mir +// _1 = const i128_add(_1, const 1u128) -> bb5; +// ... +// _1 = const u128_div(_1, const 4u128) -> bb6; +// ... +// _1 = const u128_rem(_1, const 5u128) -> bb9; +// ... +// _1 = const i128_mul(_1, const 3u128) -> bb3; +// ... +// _1 = const i128_sub(_1, const 2u128) -> bb4; +// ... +// _1 = const u128_shr(_1, const 7i32) -> bb7; +// ... +// _1 = const i128_shl(_1, const 6i32) -> bb8; +// END rustc.test_unsigned.Lower128Bit.after.mir