rustc_args_required_const is no longer a promotion site

This commit is contained in:
Ralf Jung 2021-05-09 14:04:34 +02:00
parent e1ff91f439
commit 22e1778ec0
11 changed files with 28 additions and 283 deletions

View File

@ -12,20 +12,16 @@
//! initialization and can otherwise silence errors, if
//! move analysis runs after promotion on broken MIR.
use rustc_ast::LitKind;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::mir::traversal::ReversePostorder;
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::*;
use rustc_middle::ty::cast::CastTy;
use rustc_middle::ty::subst::InternalSubsts;
use rustc_middle::ty::{self, List, TyCtxt, TypeFoldable};
use rustc_span::symbol::sym;
use rustc_span::Span;
use rustc_index::vec::{Idx, IndexVec};
use rustc_target::spec::abi::Abi;
use std::cell::Cell;
use std::{cmp, iter, mem};
@ -101,47 +97,16 @@ impl TempState {
pub enum Candidate {
/// Borrow of a constant temporary, candidate for lifetime extension.
Ref(Location),
/// Currently applied to function calls where the callee has the unstable
/// `#[rustc_args_required_const]` attribute as well as the SIMD shuffle
/// intrinsic. The intrinsic requires the arguments are indeed constant and
/// the attribute currently provides the semantic requirement that arguments
/// must be constant.
Argument { bb: BasicBlock, index: usize },
}
impl Candidate {
/// Returns `true` if we should use the "explicit" rules for promotability for this `Candidate`.
fn forces_explicit_promotion(&self) -> bool {
match self {
Candidate::Ref(_) => false,
Candidate::Argument { .. } => true,
}
}
fn source_info(&self, body: &Body<'_>) -> SourceInfo {
match self {
Candidate::Ref(location) => *body.source_info(*location),
Candidate::Argument { bb, .. } => *body.source_info(body.terminator_loc(*bb)),
}
}
}
fn args_required_const(tcx: TyCtxt<'_>, def_id: DefId) -> Option<Vec<usize>> {
let attrs = tcx.get_attrs(def_id);
let attr = attrs.iter().find(|a| tcx.sess.check_name(a, sym::rustc_args_required_const))?;
let mut ret = vec![];
for meta in attr.meta_item_list()? {
match meta.literal()?.kind {
LitKind::Int(a, _) => {
ret.push(a as usize);
}
_ => bug!("invalid arg index"),
}
}
Some(ret)
}
struct Collector<'a, 'tcx> {
ccx: &'a ConstCx<'a, 'tcx>,
temps: IndexVec<Local, TempState>,
@ -208,31 +173,6 @@ impl<'tcx> Visitor<'tcx> for Collector<'_, 'tcx> {
_ => {}
}
}
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
self.super_terminator(terminator, location);
if let TerminatorKind::Call { ref func, .. } = terminator.kind {
if let ty::FnDef(def_id, _) = *func.ty(self.ccx.body, self.ccx.tcx).kind() {
let fn_sig = self.ccx.tcx.fn_sig(def_id);
if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = fn_sig.abi() {
let name = self.ccx.tcx.item_name(def_id);
// FIXME(eddyb) use `#[rustc_args_required_const(2)]` for shuffles.
if name.as_str().starts_with("simd_shuffle") {
self.candidates.push(Candidate::Argument { bb: location.block, index: 2 });
return; // Don't double count `simd_shuffle` candidates
}
}
if let Some(constant_args) = args_required_const(self.ccx.tcx, def_id) {
for index in constant_args {
self.candidates.push(Candidate::Argument { bb: location.block, index });
}
}
}
}
}
}
pub fn collect_temps_and_candidates(
@ -256,14 +196,6 @@ pub fn collect_temps_and_candidates(
struct Validator<'a, 'tcx> {
ccx: &'a ConstCx<'a, 'tcx>,
temps: &'a IndexVec<Local, TempState>,
/// Explicit promotion happens e.g. for constant arguments declared via
/// `rustc_args_required_const`.
/// Implicit promotion has almost the same rules, except that disallows `const fn`
/// except for those marked `#[rustc_promotable]`. This is to avoid changing
/// a legitimate run-time operation into a failing compile-time operation
/// e.g. due to addresses being compared inside the function.
explicit: bool,
}
impl std::ops::Deref for Validator<'a, 'tcx> {
@ -280,8 +212,6 @@ impl<'tcx> Validator<'_, 'tcx> {
fn validate_candidate(&self, candidate: Candidate) -> Result<(), Unpromotable> {
match candidate {
Candidate::Ref(loc) => {
assert!(!self.explicit);
let statement = &self.body[loc.block].statements[loc.statement_index];
match &statement.kind {
StatementKind::Assign(box (_, Rvalue::Ref(_, kind, place))) => {
@ -310,15 +240,6 @@ impl<'tcx> Validator<'_, 'tcx> {
_ => bug!(),
}
}
Candidate::Argument { bb, index } => {
assert!(self.explicit);
let terminator = self.body[bb].terminator();
match &terminator.kind {
TerminatorKind::Call { args, .. } => self.validate_operand(&args[index]),
_ => bug!(),
}
}
}
}
@ -448,12 +369,10 @@ impl<'tcx> Validator<'_, 'tcx> {
ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => {}
ProjectionElem::Index(local) => {
if !self.explicit {
let mut promotable = false;
// Only accept if we can predict the index and are indexing an array.
let val = if let TempState::Defined { location: loc, .. } =
self.temps[local]
{
let mut promotable = false;
// Only accept if we can predict the index and are indexing an array.
let val =
if let TempState::Defined { location: loc, .. } = self.temps[local] {
let block = &self.body[loc.block];
if loc.statement_index < block.statements.len() {
let statement = &block.statements[loc.statement_index];
@ -470,28 +389,27 @@ impl<'tcx> Validator<'_, 'tcx> {
} else {
None
};
if let Some(idx) = val {
// Determine the type of the thing we are indexing.
let ty = place_base.ty(self.body, self.tcx).ty;
match ty.kind() {
ty::Array(_, len) => {
// It's an array; determine its length.
if let Some(len) =
len.try_eval_usize(self.tcx, self.param_env)
{
// If the index is in-bounds, go ahead.
if idx < len {
promotable = true;
}
if let Some(idx) = val {
// Determine the type of the thing we are indexing.
let ty = place_base.ty(self.body, self.tcx).ty;
match ty.kind() {
ty::Array(_, len) => {
// It's an array; determine its length.
if let Some(len) = len.try_eval_usize(self.tcx, self.param_env)
{
// If the index is in-bounds, go ahead.
if idx < len {
promotable = true;
}
}
_ => {}
}
}
if !promotable {
return Err(Unpromotable);
_ => {}
}
}
if !promotable {
return Err(Unpromotable);
}
self.validate_local(local)?;
}
@ -636,7 +554,7 @@ impl<'tcx> Validator<'_, 'tcx> {
match op {
BinOp::Div | BinOp::Rem => {
if !self.explicit && lhs_ty.is_integral() {
if lhs_ty.is_integral() {
// Integer division: the RHS must be a non-zero const.
let const_val = match rhs {
Operand::Constant(c) => {
@ -721,13 +639,12 @@ impl<'tcx> Validator<'_, 'tcx> {
) -> Result<(), Unpromotable> {
let fn_ty = callee.ty(self.body, self.tcx);
// When doing explicit promotion and inside const/static items, we promote all (eligible) function calls.
// Inside const/static items, we promote all (eligible) function calls.
// Everywhere else, we require `#[rustc_promotable]` on the callee.
let promote_all_const_fn = self.explicit
|| matches!(
self.const_kind,
Some(hir::ConstContext::Static(_) | hir::ConstContext::Const)
);
let promote_all_const_fn = matches!(
self.const_kind,
Some(hir::ConstContext::Static(_) | hir::ConstContext::Const)
);
if !promote_all_const_fn {
if let ty::FnDef(def_id, _) = *fn_ty.kind() {
// Never promote runtime `const fn` calls of
@ -765,41 +682,12 @@ pub fn validate_candidates(
temps: &IndexVec<Local, TempState>,
candidates: &[Candidate],
) -> Vec<Candidate> {
let mut validator = Validator { ccx, temps, explicit: false };
let validator = Validator { ccx, temps };
candidates
.iter()
.copied()
.filter(|&candidate| {
validator.explicit = candidate.forces_explicit_promotion();
// FIXME(eddyb) also emit the errors for shuffle indices
// and `#[rustc_args_required_const]` arguments here.
let is_promotable = validator.validate_candidate(candidate).is_ok();
// If we use explicit validation, we carry the risk of turning a legitimate run-time
// operation into a failing compile-time operation. Make sure that does not happen
// by asserting that there is no possible run-time behavior here in case promotion
// fails.
if validator.explicit && !is_promotable {
ccx.tcx.sess.delay_span_bug(
ccx.body.span,
"Explicit promotion requested, but failed to promote",
);
}
match candidate {
Candidate::Argument { bb, index } if !is_promotable => {
let span = ccx.body[bb].terminator().source_info.span;
let msg = format!("argument {} is required to be a constant", index + 1);
ccx.tcx.sess.span_err(span, &msg);
}
_ => (),
}
is_promotable
})
.filter(|&candidate| validator.validate_candidate(candidate).is_ok())
.collect()
}
@ -1039,26 +927,6 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
_ => bug!(),
}
}
Candidate::Argument { bb, index } => {
let terminator = blocks[bb].terminator_mut();
match terminator.kind {
TerminatorKind::Call { ref mut args, .. } => {
let ty = args[index].ty(local_decls, self.tcx);
let span = terminator.source_info.span;
Rvalue::Use(mem::replace(&mut args[index], promoted_operand(ty, span)))
}
// We expected a `TerminatorKind::Call` for which we'd like to promote an
// argument. `qualify_consts` saw a `TerminatorKind::Call` here, but
// we are seeing a `Goto`. That means that the `promote_temps` method
// already promoted this call away entirely. This case occurs when calling
// a function requiring a constant argument and as that constant value
// providing a value whose computation contains another call to a function
// requiring a constant argument.
TerminatorKind::Goto { .. } => return None,
_ => bug!(),
}
}
}
};
@ -1113,7 +981,6 @@ pub fn promote_candidates<'tcx>(
}
}
}
Candidate::Argument { .. } => {}
}
// Declare return place local so that `mir::Body::new` doesn't complain.

View File

@ -1,11 +0,0 @@
#![feature(rustc_attrs)]
#[rustc_args_required_const(0)]
fn foo(_imm8: i32) {}
fn bar() {
let imm8 = 3;
foo(imm8) //~ ERROR argument 1 is required to be a constant
}
fn main() {}

View File

@ -1,8 +0,0 @@
error: argument 1 is required to be a constant
--> $DIR/const_arg_local.rs:8:5
|
LL | foo(imm8)
| ^^^^^^^^^
error: aborting due to previous error

View File

@ -1,10 +0,0 @@
#![feature(rustc_attrs)]
#[rustc_args_required_const(0)]
fn foo(_imm8: i32) {}
fn bar() {
foo(*&mut 42) //~ ERROR argument 1 is required to be a constant
}
fn main() {}

View File

@ -1,8 +0,0 @@
error: argument 1 is required to be a constant
--> $DIR/const_arg_promotable.rs:7:5
|
LL | foo(*&mut 42)
| ^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -1,18 +0,0 @@
// This test is a regression test for a bug where we only checked function calls in no-const
// functions for `rustc_args_required_const` arguments. This meant that even though `bar` needs its
// argument to be const, inside a const fn (callable at runtime), the value for it may come from a
// non-constant (namely an argument to the const fn).
#![feature(rustc_attrs)]
const fn foo(a: i32) {
bar(a); //~ ERROR argument 1 is required to be a constant
}
#[rustc_args_required_const(0)]
const fn bar(_: i32) {}
fn main() {
// this function call will pass a runtime-value (number of program arguments) to `foo`, which
// will in turn forward it to `bar`, which expects a compile-time argument
foo(std::env::args().count() as i32);
}

View File

@ -1,8 +0,0 @@
error: argument 1 is required to be a constant
--> $DIR/const_arg_promotable2.rs:8:5
|
LL | bar(a);
| ^^^^^^
error: aborting due to previous error

View File

@ -1,10 +0,0 @@
#![feature(rustc_attrs)]
#[rustc_args_required_const(0)]
fn foo(_imm8: i32) {}
fn bar(imm8: i32) {
foo(imm8) //~ ERROR argument 1 is required to be a constant
}
fn main() {}

View File

@ -1,8 +0,0 @@
error: argument 1 is required to be a constant
--> $DIR/const_arg_wrapper.rs:7:5
|
LL | foo(imm8)
| ^^^^^^^^^
error: aborting due to previous error

View File

@ -1,27 +0,0 @@
#![feature(rustc_attrs)]
#[rustc_args_required_const(0)]
fn foo(_a: i32) {
}
#[rustc_args_required_const(1)]
fn bar(_a: i32, _b: i32) {
}
const A: i32 = 3;
const fn baz() -> i32 {
3
}
fn main() {
foo(2);
foo(2 + 3);
const BAZ: i32 = baz();
foo(BAZ);
let a = 4;
foo(A);
foo(a); //~ ERROR: argument 1 is required to be a constant
bar(a, 3);
bar(a, a); //~ ERROR: argument 2 is required to be a constant
}

View File

@ -1,14 +0,0 @@
error: argument 1 is required to be a constant
--> $DIR/rustc-args-required-const.rs:24:5
|
LL | foo(a);
| ^^^^^^
error: argument 2 is required to be a constant
--> $DIR/rustc-args-required-const.rs:26:5
|
LL | bar(a, a);
| ^^^^^^^^^
error: aborting due to 2 previous errors