From 775683fec6f4553aff639557088d2a277fbb9582 Mon Sep 17 00:00:00 2001 From: Alexis Beingessner Date: Wed, 9 May 2018 13:15:52 -0400 Subject: [PATCH 1/2] make various intrinsics work in const contexts --- src/librustc_mir/interpret/const_eval.rs | 324 ++++++++++++++++++- src/librustc_mir/transform/qualify_consts.rs | 59 +++- 2 files changed, 379 insertions(+), 4 deletions(-) diff --git a/src/librustc_mir/interpret/const_eval.rs b/src/librustc_mir/interpret/const_eval.rs index dff9fa271aba5..468e4b218a288 100644 --- a/src/librustc_mir/interpret/const_eval.rs +++ b/src/librustc_mir/interpret/const_eval.rs @@ -9,7 +9,7 @@ use rustc::ty::subst::Subst; use syntax::ast::Mutability; use syntax::codemap::Span; -use rustc::mir::interpret::{EvalResult, EvalError, EvalErrorKind, GlobalId, Value, MemoryPointer, Pointer, PrimVal, AllocId}; +use rustc::mir::interpret::{EvalResult, EvalError, EvalErrorKind, GlobalId, Value, MemoryPointer, Pointer, PrimVal, PrimValKind, AllocId}; use super::{Place, EvalContext, StackPopCleanup, ValTy, PlaceExtra, Memory}; use std::fmt; @@ -256,7 +256,7 @@ impl<'mir, 'tcx> super::Machine<'mir, 'tcx> for CompileTimeEvaluator { fn call_intrinsic<'a>( ecx: &mut EvalContext<'a, 'mir, 'tcx, Self>, instance: ty::Instance<'tcx>, - _args: &[ValTy<'tcx>], + args: &[ValTy<'tcx>], dest: Place, dest_layout: layout::TyLayout<'tcx>, target: mir::BasicBlock, @@ -265,6 +265,280 @@ impl<'mir, 'tcx> super::Machine<'mir, 'tcx> for CompileTimeEvaluator { let intrinsic_name = &ecx.tcx.item_name(instance.def_id()).as_str()[..]; match intrinsic_name { + "add_with_overflow" => { + ecx.intrinsic_with_overflow( + mir::BinOp::Add, + args[0], + args[1], + dest, + dest_layout.ty, + )? + } + + "sub_with_overflow" => { + ecx.intrinsic_with_overflow( + mir::BinOp::Sub, + args[0], + args[1], + dest, + dest_layout.ty, + )? + } + + "mul_with_overflow" => { + ecx.intrinsic_with_overflow( + mir::BinOp::Mul, + args[0], + args[1], + dest, + dest_layout.ty, + )? + } + + "overflowing_sub" => { + ecx.intrinsic_overflowing( + mir::BinOp::Sub, + args[0], + args[1], + dest, + dest_layout.ty, + )?; + } + + "overflowing_mul" => { + ecx.intrinsic_overflowing( + mir::BinOp::Mul, + args[0], + args[1], + dest, + dest_layout.ty, + )?; + } + + "overflowing_add" => { + ecx.intrinsic_overflowing( + mir::BinOp::Add, + args[0], + args[1], + dest, + dest_layout.ty, + )?; + } + + "powf32" => { + let f = ecx.value_to_primval(args[0])?.to_bytes()?; + let f = f32::from_bits(f as u32); + let f2 = ecx.value_to_primval(args[1])?.to_bytes()?; + let f2 = f32::from_bits(f2 as u32); + ecx.write_primval( + dest, + PrimVal::Bytes(f.powf(f2).to_bits() as u128), + dest_layout.ty, + )?; + } + + "powf64" => { + let f = ecx.value_to_primval(args[0])?.to_bytes()?; + let f = f64::from_bits(f as u64); + let f2 = ecx.value_to_primval(args[1])?.to_bytes()?; + let f2 = f64::from_bits(f2 as u64); + ecx.write_primval( + dest, + PrimVal::Bytes(f.powf(f2).to_bits() as u128), + dest_layout.ty, + )?; + } + + "fmaf32" => { + let a = ecx.value_to_primval(args[0])?.to_bytes()?; + let a = f32::from_bits(a as u32); + let b = ecx.value_to_primval(args[1])?.to_bytes()?; + let b = f32::from_bits(b as u32); + let c = ecx.value_to_primval(args[2])?.to_bytes()?; + let c = f32::from_bits(c as u32); + ecx.write_primval( + dest, + PrimVal::Bytes((a * b + c).to_bits() as u128), + dest_layout.ty, + )?; + } + + "fmaf64" => { + let a = ecx.value_to_primval(args[0])?.to_bytes()?; + let a = f64::from_bits(a as u64); + let b = ecx.value_to_primval(args[1])?.to_bytes()?; + let b = f64::from_bits(b as u64); + let c = ecx.value_to_primval(args[2])?.to_bytes()?; + let c = f64::from_bits(c as u64); + ecx.write_primval( + dest, + PrimVal::Bytes((a * b + c).to_bits() as u128), + dest_layout.ty, + )?; + } + + "powif32" => { + let f = ecx.value_to_primval(args[0])?.to_bytes()?; + let f = f32::from_bits(f as u32); + let i = ecx.value_to_primval(args[1])?.to_i128()?; + ecx.write_primval( + dest, + PrimVal::Bytes(f.powi(i as i32).to_bits() as u128), + dest_layout.ty, + )?; + } + + "powif64" => { + let f = ecx.value_to_primval(args[0])?.to_bytes()?; + let f = f64::from_bits(f as u64); + let i = ecx.value_to_primval(args[1])?.to_i128()?; + ecx.write_primval( + dest, + PrimVal::Bytes(f.powi(i as i32).to_bits() as u128), + dest_layout.ty, + )?; + } + + "unchecked_shl" => { + let bits = dest_layout.size.bytes() as u128 * 8; + let rhs = ecx.value_to_primval(args[1])? + .to_bytes()?; + if rhs >= bits { + return err!(Intrinsic( + format!("Overflowing shift by {} in unchecked_shl", rhs), + )); + } + ecx.intrinsic_overflowing( + mir::BinOp::Shl, + args[0], + args[1], + dest, + dest_layout.ty, + )?; + } + + "unchecked_shr" => { + let bits = dest_layout.size.bytes() as u128 * 8; + let rhs = ecx.value_to_primval(args[1])? + .to_bytes()?; + if rhs >= bits { + return err!(Intrinsic( + format!("Overflowing shift by {} in unchecked_shr", rhs), + )); + } + ecx.intrinsic_overflowing( + mir::BinOp::Shr, + args[0], + args[1], + dest, + dest_layout.ty, + )?; + } + + "unchecked_div" => { + let rhs = ecx.value_to_primval(args[1])? + .to_bytes()?; + if rhs == 0 { + return err!(Intrinsic(format!("Division by 0 in unchecked_div"))); + } + ecx.intrinsic_overflowing( + mir::BinOp::Div, + args[0], + args[1], + dest, + dest_layout.ty, + )?; + } + + "unchecked_rem" => { + let rhs = ecx.value_to_primval(args[1])? + .to_bytes()?; + if rhs == 0 { + return err!(Intrinsic(format!("Division by 0 in unchecked_rem"))); + } + ecx.intrinsic_overflowing( + mir::BinOp::Rem, + args[0], + args[1], + dest, + dest_layout.ty, + )?; + } + + "ctpop" | "cttz" | "cttz_nonzero" | "ctlz" | "ctlz_nonzero" | "bswap" => { + let ty = substs.type_at(0); + let num = ecx.value_to_primval(args[0])?.to_bytes()?; + let kind = ecx.ty_to_primval_kind(ty)?; + let num = if intrinsic_name.ends_with("_nonzero") { + if num == 0 { + return err!(Intrinsic(format!("{} called on 0", intrinsic_name))); + } + numeric_intrinsic(intrinsic_name.trim_right_matches("_nonzero"), num, kind)? + } else { + numeric_intrinsic(intrinsic_name, num, kind)? + }; + ecx.write_primval(dest, num, ty)?; + } + + "sinf32" | "fabsf32" | "cosf32" | "sqrtf32" | "expf32" | "exp2f32" | "logf32" | + "log10f32" | "log2f32" | "floorf32" | "ceilf32" | "truncf32" => { + let f = ecx.value_to_primval(args[0])?.to_bytes()?; + let f = f32::from_bits(f as u32); + let f = match intrinsic_name { + "sinf32" => f.sin(), + "fabsf32" => f.abs(), + "cosf32" => f.cos(), + "sqrtf32" => f.sqrt(), + "expf32" => f.exp(), + "exp2f32" => f.exp2(), + "logf32" => f.ln(), + "log10f32" => f.log10(), + "log2f32" => f.log2(), + "floorf32" => f.floor(), + "ceilf32" => f.ceil(), + "truncf32" => f.trunc(), + _ => bug!(), + }; + ecx.write_primval(dest, PrimVal::Bytes(f.to_bits() as u128), dest_layout.ty)?; + } + + "sinf64" | "fabsf64" | "cosf64" | "sqrtf64" | "expf64" | "exp2f64" | "logf64" | + "log10f64" | "log2f64" | "floorf64" | "ceilf64" | "truncf64" => { + let f = ecx.value_to_primval(args[0])?.to_bytes()?; + let f = f64::from_bits(f as u64); + let f = match intrinsic_name { + "sinf64" => f.sin(), + "fabsf64" => f.abs(), + "cosf64" => f.cos(), + "sqrtf64" => f.sqrt(), + "expf64" => f.exp(), + "exp2f64" => f.exp2(), + "logf64" => f.ln(), + "log10f64" => f.log10(), + "log2f64" => f.log2(), + "floorf64" => f.floor(), + "ceilf64" => f.ceil(), + "truncf64" => f.trunc(), + _ => bug!(), + }; + ecx.write_primval(dest, PrimVal::Bytes(f.to_bits() as u128), dest_layout.ty)?; + } + + "assume" => { + let cond = ecx.value_to_primval(args[0])?.to_bool()?; + if !cond { + return err!(AssumptionNotHeld); + } + } + + "likely" | "unlikely" | "forget" => {} + + "align_offset" => { + // FIXME: return a real value in case the target allocation has an + // alignment bigger than the one requested + ecx.write_primval(dest, PrimVal::Bytes(u128::max_value()), dest_layout.ty)?; + }, + "min_align_of" => { let elem_ty = substs.type_at(0); let elem_align = ecx.layout_of(elem_ty)?.align.abi(); @@ -278,6 +552,14 @@ impl<'mir, 'tcx> super::Machine<'mir, 'tcx> for CompileTimeEvaluator { ecx.write_primval(dest, PrimVal::from_u128(size), dest_layout.ty)?; } + "transmute" => { + let src_ty = substs.type_at(0); + let _src_align = ecx.layout_of(src_ty)?.align; + let ptr = ecx.force_allocation(dest)?.to_ptr()?; + let dest_align = ecx.layout_of(substs.type_at(1))?.align; + ecx.write_value_to_ptr(args[0].value, ptr.into(), dest_align, src_ty).unwrap(); + } + "type_id" => { let ty = substs.type_at(0); let type_id = ecx.tcx.type_id_hash(ty) as u128; @@ -351,6 +633,44 @@ impl<'mir, 'tcx> super::Machine<'mir, 'tcx> for CompileTimeEvaluator { } } + +fn numeric_intrinsic<'tcx>( + name: &str, + bytes: u128, + kind: PrimValKind, +) -> EvalResult<'tcx, PrimVal> { + macro_rules! integer_intrinsic { + ($method:ident) => ({ + use rustc::mir::interpret::PrimValKind::*; + let result_bytes = match kind { + I8 => (bytes as i8).$method() as u128, + U8 => (bytes as u8).$method() as u128, + I16 => (bytes as i16).$method() as u128, + U16 => (bytes as u16).$method() as u128, + I32 => (bytes as i32).$method() as u128, + U32 => (bytes as u32).$method() as u128, + I64 => (bytes as i64).$method() as u128, + U64 => (bytes as u64).$method() as u128, + I128 => (bytes as i128).$method() as u128, + U128 => bytes.$method() as u128, + _ => bug!("invalid `{}` argument: {:?}", name, bytes), + }; + + PrimVal::Bytes(result_bytes) + }); + } + + let result_val = match name { + "bswap" => integer_intrinsic!(swap_bytes), + "ctlz" => integer_intrinsic!(leading_zeros), + "ctpop" => integer_intrinsic!(count_ones), + "cttz" => integer_intrinsic!(trailing_zeros), + _ => bug!("not a numeric intrinsic: {}", name), + }; + + Ok(result_val) +} + pub fn const_val_field<'a, 'tcx>( tcx: TyCtxt<'a, 'tcx, 'tcx>, param_env: ty::ParamEnv<'tcx>, diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs index 4762c6aaa27cc..f7a38d88cd9bb 100644 --- a/src/librustc_mir/transform/qualify_consts.rs +++ b/src/librustc_mir/transform/qualify_consts.rs @@ -869,8 +869,63 @@ This does not pose a problem by itself because they can't be accessed directly." Abi::PlatformIntrinsic => { assert!(!self.tcx.is_const_fn(def_id)); match &self.tcx.item_name(def_id).as_str()[..] { - "size_of" | "min_align_of" | "type_id" => is_const_fn = Some(def_id), - + "add_with_overflow" | + "sub_with_overflow" | + "mul_with_overflow" | + "overflowing_sub" | + "overflowing_mul" | + "overflowing_add" | + "powf32" | + "powf64" | + "fmaf32" | + "fmaf64" | + "powif32" | + "powif64" | + "unchecked_shl" | + "unchecked_shr" | + "unchecked_div" | + "unchecked_rem" | + "ctpop" | + "cttz" | + "cttz_nonzero" | + "ctlz" | + "ctlz_nonzero" | + "bswap" | + "sinf32" | + "fabsf32" | + "cosf32" | + "sqrtf32" | + "expf32" | + "exp2f32" | + "logf32" | + "log10f32" | + "log2f32" | + "floorf32" | + "ceilf32" | + "truncf32" | + "sinf64" | + "fabsf64" | + "cosf64" | + "sqrtf64" | + "expf64" | + "exp2f64" | + "logf64" | + "log10f64" | + "log2f64" | + "floorf64" | + "ceilf64" | + "truncf64" | + "assume" | + "likely" | + "unlikely" | + "forget" | + "align_offset" | + "min_align_of" | + "size_of" | + "transmute" | + "type_id" => { + is_const_fn = Some(def_id); + } name if name.starts_with("simd_shuffle") => { is_shuffle = true; } From 8dc056745b2efceaa0c06ae3665df4c8d8eb346c Mon Sep 17 00:00:00 2001 From: Alexis Beingessner Date: Wed, 9 May 2018 14:43:47 -0400 Subject: [PATCH 2/2] change test to use still-non-const intrinsic --- src/test/compile-fail/const-fn-not-safe-for-const.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/compile-fail/const-fn-not-safe-for-const.rs b/src/test/compile-fail/const-fn-not-safe-for-const.rs index 48877a60d2504..0771bd930d259 100644 --- a/src/test/compile-fail/const-fn-not-safe-for-const.rs +++ b/src/test/compile-fail/const-fn-not-safe-for-const.rs @@ -12,12 +12,12 @@ #![feature(const_fn)] -use std::mem::transmute; +use std::mem::size_of_val; fn random() -> u32 { 0 } const fn sub(x: &u32) -> usize { - unsafe { transmute(x) } //~ ERROR E0015 + unsafe { size_of_val(x) } //~ ERROR E0015 } const fn sub1() -> u32 {