rustc_args_required_const is no longer a promotion site
This commit is contained in:
parent
e1ff91f439
commit
22e1778ec0
11 changed files with 28 additions and 283 deletions
|
@ -12,20 +12,16 @@
|
||||||
//! initialization and can otherwise silence errors, if
|
//! initialization and can otherwise silence errors, if
|
||||||
//! move analysis runs after promotion on broken MIR.
|
//! move analysis runs after promotion on broken MIR.
|
||||||
|
|
||||||
use rustc_ast::LitKind;
|
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_hir::def_id::DefId;
|
|
||||||
use rustc_middle::mir::traversal::ReversePostorder;
|
use rustc_middle::mir::traversal::ReversePostorder;
|
||||||
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
|
use rustc_middle::mir::visit::{MutVisitor, MutatingUseContext, PlaceContext, Visitor};
|
||||||
use rustc_middle::mir::*;
|
use rustc_middle::mir::*;
|
||||||
use rustc_middle::ty::cast::CastTy;
|
use rustc_middle::ty::cast::CastTy;
|
||||||
use rustc_middle::ty::subst::InternalSubsts;
|
use rustc_middle::ty::subst::InternalSubsts;
|
||||||
use rustc_middle::ty::{self, List, TyCtxt, TypeFoldable};
|
use rustc_middle::ty::{self, List, TyCtxt, TypeFoldable};
|
||||||
use rustc_span::symbol::sym;
|
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
|
|
||||||
use rustc_index::vec::{Idx, IndexVec};
|
use rustc_index::vec::{Idx, IndexVec};
|
||||||
use rustc_target::spec::abi::Abi;
|
|
||||||
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::{cmp, iter, mem};
|
use std::{cmp, iter, mem};
|
||||||
|
@ -101,47 +97,16 @@ impl TempState {
|
||||||
pub enum Candidate {
|
pub enum Candidate {
|
||||||
/// Borrow of a constant temporary, candidate for lifetime extension.
|
/// Borrow of a constant temporary, candidate for lifetime extension.
|
||||||
Ref(Location),
|
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 {
|
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 {
|
fn source_info(&self, body: &Body<'_>) -> SourceInfo {
|
||||||
match self {
|
match self {
|
||||||
Candidate::Ref(location) => *body.source_info(*location),
|
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> {
|
struct Collector<'a, 'tcx> {
|
||||||
ccx: &'a ConstCx<'a, 'tcx>,
|
ccx: &'a ConstCx<'a, 'tcx>,
|
||||||
temps: IndexVec<Local, TempState>,
|
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(
|
pub fn collect_temps_and_candidates(
|
||||||
|
@ -256,14 +196,6 @@ pub fn collect_temps_and_candidates(
|
||||||
struct Validator<'a, 'tcx> {
|
struct Validator<'a, 'tcx> {
|
||||||
ccx: &'a ConstCx<'a, 'tcx>,
|
ccx: &'a ConstCx<'a, 'tcx>,
|
||||||
temps: &'a IndexVec<Local, TempState>,
|
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> {
|
impl std::ops::Deref for Validator<'a, 'tcx> {
|
||||||
|
@ -280,8 +212,6 @@ impl<'tcx> Validator<'_, 'tcx> {
|
||||||
fn validate_candidate(&self, candidate: Candidate) -> Result<(), Unpromotable> {
|
fn validate_candidate(&self, candidate: Candidate) -> Result<(), Unpromotable> {
|
||||||
match candidate {
|
match candidate {
|
||||||
Candidate::Ref(loc) => {
|
Candidate::Ref(loc) => {
|
||||||
assert!(!self.explicit);
|
|
||||||
|
|
||||||
let statement = &self.body[loc.block].statements[loc.statement_index];
|
let statement = &self.body[loc.block].statements[loc.statement_index];
|
||||||
match &statement.kind {
|
match &statement.kind {
|
||||||
StatementKind::Assign(box (_, Rvalue::Ref(_, kind, place))) => {
|
StatementKind::Assign(box (_, Rvalue::Ref(_, kind, place))) => {
|
||||||
|
@ -310,15 +240,6 @@ impl<'tcx> Validator<'_, 'tcx> {
|
||||||
_ => bug!(),
|
_ => 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::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => {}
|
||||||
|
|
||||||
ProjectionElem::Index(local) => {
|
ProjectionElem::Index(local) => {
|
||||||
if !self.explicit {
|
|
||||||
let mut promotable = false;
|
let mut promotable = false;
|
||||||
// Only accept if we can predict the index and are indexing an array.
|
// Only accept if we can predict the index and are indexing an array.
|
||||||
let val = if let TempState::Defined { location: loc, .. } =
|
let val =
|
||||||
self.temps[local]
|
if let TempState::Defined { location: loc, .. } = self.temps[local] {
|
||||||
{
|
|
||||||
let block = &self.body[loc.block];
|
let block = &self.body[loc.block];
|
||||||
if loc.statement_index < block.statements.len() {
|
if loc.statement_index < block.statements.len() {
|
||||||
let statement = &block.statements[loc.statement_index];
|
let statement = &block.statements[loc.statement_index];
|
||||||
|
@ -476,8 +395,7 @@ impl<'tcx> Validator<'_, 'tcx> {
|
||||||
match ty.kind() {
|
match ty.kind() {
|
||||||
ty::Array(_, len) => {
|
ty::Array(_, len) => {
|
||||||
// It's an array; determine its length.
|
// It's an array; determine its length.
|
||||||
if let Some(len) =
|
if let Some(len) = len.try_eval_usize(self.tcx, self.param_env)
|
||||||
len.try_eval_usize(self.tcx, self.param_env)
|
|
||||||
{
|
{
|
||||||
// If the index is in-bounds, go ahead.
|
// If the index is in-bounds, go ahead.
|
||||||
if idx < len {
|
if idx < len {
|
||||||
|
@ -491,7 +409,7 @@ impl<'tcx> Validator<'_, 'tcx> {
|
||||||
if !promotable {
|
if !promotable {
|
||||||
return Err(Unpromotable);
|
return Err(Unpromotable);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
self.validate_local(local)?;
|
self.validate_local(local)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,7 +554,7 @@ impl<'tcx> Validator<'_, 'tcx> {
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
BinOp::Div | BinOp::Rem => {
|
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.
|
// Integer division: the RHS must be a non-zero const.
|
||||||
let const_val = match rhs {
|
let const_val = match rhs {
|
||||||
Operand::Constant(c) => {
|
Operand::Constant(c) => {
|
||||||
|
@ -721,10 +639,9 @@ impl<'tcx> Validator<'_, 'tcx> {
|
||||||
) -> Result<(), Unpromotable> {
|
) -> Result<(), Unpromotable> {
|
||||||
let fn_ty = callee.ty(self.body, self.tcx);
|
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.
|
// Everywhere else, we require `#[rustc_promotable]` on the callee.
|
||||||
let promote_all_const_fn = self.explicit
|
let promote_all_const_fn = matches!(
|
||||||
|| matches!(
|
|
||||||
self.const_kind,
|
self.const_kind,
|
||||||
Some(hir::ConstContext::Static(_) | hir::ConstContext::Const)
|
Some(hir::ConstContext::Static(_) | hir::ConstContext::Const)
|
||||||
);
|
);
|
||||||
|
@ -765,41 +682,12 @@ pub fn validate_candidates(
|
||||||
temps: &IndexVec<Local, TempState>,
|
temps: &IndexVec<Local, TempState>,
|
||||||
candidates: &[Candidate],
|
candidates: &[Candidate],
|
||||||
) -> Vec<Candidate> {
|
) -> Vec<Candidate> {
|
||||||
let mut validator = Validator { ccx, temps, explicit: false };
|
let validator = Validator { ccx, temps };
|
||||||
|
|
||||||
candidates
|
candidates
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.filter(|&candidate| {
|
.filter(|&candidate| validator.validate_candidate(candidate).is_ok())
|
||||||
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
|
|
||||||
})
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1039,26 +927,6 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
|
||||||
_ => bug!(),
|
_ => 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.
|
// Declare return place local so that `mir::Body::new` doesn't complain.
|
||||||
|
|
|
@ -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() {}
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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() {}
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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() {}
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue