diff --git a/compiler/rustc_middle/src/mir/abstract_const.rs b/compiler/rustc_middle/src/mir/abstract_const.rs index 1ef10241143..e75f084e79e 100644 --- a/compiler/rustc_middle/src/mir/abstract_const.rs +++ b/compiler/rustc_middle/src/mir/abstract_const.rs @@ -17,6 +17,7 @@ pub enum Node<'tcx> { Binop(mir::BinOp, NodeId, NodeId), UnaryOp(mir::UnOp, NodeId), FunctionCall(NodeId, &'tcx [NodeId]), + Block(&'tcx [NodeId], Option), Cast(CastKind, NodeId, Ty<'tcx>), } diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs index 0a760a740dc..390ce59cb48 100644 --- a/compiler/rustc_mir_build/src/build/mod.rs +++ b/compiler/rustc_mir_build/src/build/mod.rs @@ -28,6 +28,7 @@ crate fn mir_built<'tcx>( if let Some(def) = def.try_upgrade(tcx) { return tcx.mir_built(def); } + debug!("mir_built: def={:?}", def); let mut body = mir_build(tcx, def); if def.const_param_did.is_some() { @@ -40,6 +41,7 @@ crate fn mir_built<'tcx>( /// Construct the MIR for a given `DefId`. fn mir_build(tcx: TyCtxt<'_>, def: ty::WithOptConstParam) -> Body<'_> { + debug!("mir_build: def={:?}", def); let id = tcx.hir().local_def_id_to_hir_id(def.did); let body_owner_kind = tcx.hir().body_owner_kind(id); let typeck_results = tcx.typeck_opt_const_arg(def); @@ -47,10 +49,12 @@ fn mir_build(tcx: TyCtxt<'_>, def: ty::WithOptConstParam) -> Body<'_ // Ensure unsafeck is ran before we steal the THIR. match def { ty::WithOptConstParam { did, const_param_did: Some(const_param_did) } => { - tcx.ensure().thir_check_unsafety_for_const_arg((did, const_param_did)) + tcx.ensure().thir_check_unsafety_for_const_arg((did, const_param_did)); + tcx.ensure().mir_abstract_const_of_const_arg((did, const_param_did)); } ty::WithOptConstParam { did, const_param_did: None } => { - tcx.ensure().thir_check_unsafety(did) + tcx.ensure().thir_check_unsafety(did); + tcx.ensure().mir_abstract_const(did); } } diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 66005be05df..70a5a9286b0 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -149,7 +149,9 @@ impl<'tcx> Cx<'tcx> { } fn make_mirror_unadjusted(&mut self, expr: &'tcx hir::Expr<'tcx>) -> Expr<'tcx> { + debug!("Expr::make_mirror_unadjusted: expr={:?}", expr); let expr_ty = self.typeck_results().expr_ty(expr); + debug!("Expr::make_mirror_unadjusted: expr_ty={:?}", expr_ty); let temp_lifetime = self.region_scope_tree.temporary_scope(expr.hir_id.local_id); let kind = match expr.kind { @@ -762,6 +764,7 @@ impl<'tcx> Cx<'tcx> { hir::ExprKind::Err => unreachable!(), }; + debug!("Expr::make_mirror_unadjusted: finish"); Expr { temp_lifetime, ty: expr_ty, span: expr.span, kind } } diff --git a/compiler/rustc_mir_build/src/thir/cx/mod.rs b/compiler/rustc_mir_build/src/thir/cx/mod.rs index 5059dd939d9..5310efbccd6 100644 --- a/compiler/rustc_mir_build/src/thir/cx/mod.rs +++ b/compiler/rustc_mir_build/src/thir/cx/mod.rs @@ -20,6 +20,7 @@ crate fn thir_body<'tcx>( tcx: TyCtxt<'tcx>, owner_def: ty::WithOptConstParam, ) -> (&'tcx Steal>, ExprId) { + debug!("thir_body: {:?}", owner_def); let hir = tcx.hir(); let body = hir.body(hir.body_owned_by(hir.local_def_id_to_hir_id(owner_def.did))); let mut cx = Cx::new(tcx, owner_def); diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index 35e25e52dc5..26e1c3e7b8c 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -159,9 +159,10 @@ where self.visit_const(leaf) } ACNode::Cast(_, _, ty) => self.visit_ty(ty), - ACNode::Binop(..) | ACNode::UnaryOp(..) | ACNode::FunctionCall(_, _) => { - ControlFlow::CONTINUE - } + ACNode::Block(_, _) + | ACNode::Binop(..) + | ACNode::UnaryOp(..) + | ACNode::FunctionCall(_, _) => ControlFlow::CONTINUE, }) } diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index ddabe5967d7..4e11fefdc81 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -8,14 +8,15 @@ //! In this case we try to build an abstract representation of this constant using //! `mir_abstract_const` which can then be checked for structural equality with other //! generic constants mentioned in the `caller_bounds` of the current environment. +use rustc_data_structures::sync::Lrc; use rustc_errors::ErrorReported; use rustc_hir::def::DefKind; -use rustc_index::bit_set::BitSet; use rustc_index::vec::IndexVec; use rustc_infer::infer::InferCtxt; +use rustc_middle::mir; use rustc_middle::mir::abstract_const::{Node, NodeId, NotConstEvaluatable}; use rustc_middle::mir::interpret::ErrorHandled; -use rustc_middle::mir::{self, Rvalue, StatementKind, TerminatorKind}; +use rustc_middle::thir; use rustc_middle::ty::subst::{Subst, SubstsRef}; use rustc_middle::ty::{self, TyCtxt, TypeFoldable}; use rustc_session::lint; @@ -101,9 +102,10 @@ pub fn is_const_evaluatable<'cx, 'tcx>( ControlFlow::CONTINUE } - Node::Binop(_, _, _) | Node::UnaryOp(_, _) | Node::FunctionCall(_, _) => { - ControlFlow::CONTINUE - } + Node::Block(_, _) + | Node::Binop(_, _, _) + | Node::UnaryOp(_, _) + | Node::FunctionCall(_, _) => ControlFlow::CONTINUE, }); match failure_kind { @@ -232,7 +234,8 @@ struct WorkNode<'tcx> { struct AbstractConstBuilder<'a, 'tcx> { tcx: TyCtxt<'tcx>, - body: &'a mir::Body<'tcx>, + body_id: thir::ExprId, + body: Lrc<&'a thir::Thir<'tcx>>, /// The current WIP node tree. /// /// We require all nodes to be used in the final abstract const, @@ -240,18 +243,18 @@ struct AbstractConstBuilder<'a, 'tcx> { /// if they are mentioned in an assert, so some used nodes are never /// actually reachable by walking the [`AbstractConst`]. nodes: IndexVec>, - locals: IndexVec, - /// We only allow field accesses if they access - /// the result of a checked operation. - checked_op_locals: BitSet, } impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { + fn root_span(&self) -> Span { + self.body.exprs[self.body_id].span + } + fn error(&mut self, span: Option, msg: &str) -> Result { self.tcx .sess - .struct_span_err(self.body.span, "overly complex generic constant") - .span_label(span.unwrap_or(self.body.span), msg) + .struct_span_err(self.root_span(), "overly complex generic constant") + .span_label(span.unwrap_or(self.root_span()), msg) .help("consider moving this anonymous constant into a `const` function") .emit(); @@ -260,28 +263,12 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { fn new( tcx: TyCtxt<'tcx>, - body: &'a mir::Body<'tcx>, + (body, body_id): (&'a thir::Thir<'tcx>, thir::ExprId), ) -> Result>, ErrorReported> { - let mut builder = AbstractConstBuilder { - tcx, - body, - nodes: IndexVec::new(), - locals: IndexVec::from_elem(NodeId::MAX, &body.local_decls), - checked_op_locals: BitSet::new_empty(body.local_decls.len()), - }; + let builder = + AbstractConstBuilder { tcx, body_id, body: Lrc::new(body), nodes: IndexVec::new() }; - // We don't have to look at concrete constants, as we - // can just evaluate them. - if !body.is_polymorphic { - return Ok(None); - } - - // We only allow consts without control flow, so - // we check for cycles here which simplifies the - // rest of this implementation. - if body.is_cfg_cyclic() { - builder.error(None, "cyclic anonymous constants are forbidden")?; - } + // FIXME non-constants should return Ok(None) Ok(Some(builder)) } @@ -301,6 +288,10 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { self.nodes[func].used = true; nodes.iter().for_each(|&n| self.nodes[n].used = true); } + Node::Block(stmts, opt_expr) => { + stmts.iter().for_each(|&id| self.nodes[id].used = true); + opt_expr.map(|e| self.nodes[e].used = true); + } Node::Cast(_, operand, _) => { self.nodes[operand].used = true; } @@ -310,50 +301,6 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { self.nodes.push(WorkNode { node, span, used: false }) } - fn place_to_local( - &mut self, - span: Span, - p: &mir::Place<'tcx>, - ) -> Result { - const ZERO_FIELD: mir::Field = mir::Field::from_usize(0); - // Do not allow any projections. - // - // One exception are field accesses on the result of checked operations, - // which are required to support things like `1 + 2`. - if let Some(p) = p.as_local() { - debug_assert!(!self.checked_op_locals.contains(p)); - Ok(p) - } else if let &[mir::ProjectionElem::Field(ZERO_FIELD, _)] = p.projection.as_ref() { - // Only allow field accesses if the given local - // contains the result of a checked operation. - if self.checked_op_locals.contains(p.local) { - Ok(p.local) - } else { - self.error(Some(span), "unsupported projection")?; - } - } else { - self.error(Some(span), "unsupported projection")?; - } - } - - fn operand_to_node( - &mut self, - span: Span, - op: &mir::Operand<'tcx>, - ) -> Result { - debug!("operand_to_node: op={:?}", op); - match op { - mir::Operand::Copy(p) | mir::Operand::Move(p) => { - let local = self.place_to_local(span, p)?; - Ok(self.locals[local]) - } - mir::Operand::Constant(ct) => match ct.literal { - mir::ConstantKind::Ty(ct) => Ok(self.add_node(Node::Leaf(ct), span)), - mir::ConstantKind::Val(..) => self.error(Some(span), "unsupported constant")?, - }, - } - } - /// We do not allow all binary operations in abstract consts, so filter disallowed ones. fn check_binop(op: mir::BinOp) -> bool { use mir::BinOp::*; @@ -373,148 +320,13 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { } } - fn build_statement(&mut self, stmt: &mir::Statement<'tcx>) -> Result<(), ErrorReported> { - debug!("AbstractConstBuilder: stmt={:?}", stmt); - let span = stmt.source_info.span; - match stmt.kind { - StatementKind::Assign(box (ref place, ref rvalue)) => { - let local = self.place_to_local(span, place)?; - match *rvalue { - Rvalue::Use(ref operand) => { - self.locals[local] = self.operand_to_node(span, operand)?; - Ok(()) - } - Rvalue::BinaryOp(op, box (ref lhs, ref rhs)) if Self::check_binop(op) => { - let lhs = self.operand_to_node(span, lhs)?; - let rhs = self.operand_to_node(span, rhs)?; - self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs), span); - if op.is_checkable() { - bug!("unexpected unchecked checkable binary operation"); - } else { - Ok(()) - } - } - Rvalue::CheckedBinaryOp(op, box (ref lhs, ref rhs)) - if Self::check_binop(op) => - { - let lhs = self.operand_to_node(span, lhs)?; - let rhs = self.operand_to_node(span, rhs)?; - self.locals[local] = self.add_node(Node::Binop(op, lhs, rhs), span); - self.checked_op_locals.insert(local); - Ok(()) - } - Rvalue::UnaryOp(op, ref operand) if Self::check_unop(op) => { - let operand = self.operand_to_node(span, operand)?; - self.locals[local] = self.add_node(Node::UnaryOp(op, operand), span); - Ok(()) - } - Rvalue::Cast(cast_kind, ref operand, ty) => { - let operand = self.operand_to_node(span, operand)?; - self.locals[local] = - self.add_node(Node::Cast(cast_kind, operand, ty), span); - Ok(()) - } - _ => self.error(Some(span), "unsupported rvalue")?, - } - } - // These are not actually relevant for us here, so we can ignore them. - StatementKind::AscribeUserType(..) - | StatementKind::StorageLive(_) - | StatementKind::StorageDead(_) => Ok(()), - _ => self.error(Some(stmt.source_info.span), "unsupported statement")?, - } - } - - /// Possible return values: - /// - /// - `None`: unsupported terminator, stop building - /// - `Some(None)`: supported terminator, finish building - /// - `Some(Some(block))`: support terminator, build `block` next - fn build_terminator( - &mut self, - terminator: &mir::Terminator<'tcx>, - ) -> Result, ErrorReported> { - debug!("AbstractConstBuilder: terminator={:?}", terminator); - match terminator.kind { - TerminatorKind::Goto { target } => Ok(Some(target)), - TerminatorKind::Return => Ok(None), - TerminatorKind::Call { - ref func, - ref args, - destination: Some((ref place, target)), - // We do not care about `cleanup` here. Any branch which - // uses `cleanup` will fail const-eval and they therefore - // do not matter when checking for const evaluatability. - // - // Do note that even if `panic::catch_unwind` is made const, - // we still do not have to care about this, as we do not look - // into functions. - cleanup: _, - // Do not allow overloaded operators for now, - // we probably do want to allow this in the future. - // - // This is currently fairly irrelevant as it requires `const Trait`s. - from_hir_call: true, - fn_span, - } => { - let local = self.place_to_local(fn_span, place)?; - let func = self.operand_to_node(fn_span, func)?; - let args = self.tcx.arena.alloc_from_iter( - args.iter() - .map(|arg| self.operand_to_node(terminator.source_info.span, arg)) - .collect::, _>>()?, - ); - self.locals[local] = self.add_node(Node::FunctionCall(func, args), fn_span); - Ok(Some(target)) - } - TerminatorKind::Assert { ref cond, expected: false, target, .. } => { - let p = match cond { - mir::Operand::Copy(p) | mir::Operand::Move(p) => p, - mir::Operand::Constant(_) => bug!("unexpected assert"), - }; - - const ONE_FIELD: mir::Field = mir::Field::from_usize(1); - debug!("proj: {:?}", p.projection); - if let Some(p) = p.as_local() { - debug_assert!(!self.checked_op_locals.contains(p)); - // Mark locals directly used in asserts as used. - // - // This is needed because division does not use `CheckedBinop` but instead - // adds an explicit assert for `divisor != 0`. - self.nodes[self.locals[p]].used = true; - return Ok(Some(target)); - } else if let &[mir::ProjectionElem::Field(ONE_FIELD, _)] = p.projection.as_ref() { - // Only allow asserts checking the result of a checked operation. - if self.checked_op_locals.contains(p.local) { - return Ok(Some(target)); - } - } - - self.error(Some(terminator.source_info.span), "unsupported assertion")?; - } - _ => self.error(Some(terminator.source_info.span), "unsupported terminator")?, - } - } - - /// Builds the abstract const by walking the mir from start to finish - /// and bailing out when encountering an unsupported operation. + /// Builds the abstract const by walking the thir and bailing out when + /// encountering an unspported operation. fn build(mut self) -> Result<&'tcx [Node<'tcx>], ErrorReported> { - let mut block = &self.body.basic_blocks()[mir::START_BLOCK]; - // We checked for a cyclic cfg above, so this should terminate. - loop { - debug!("AbstractConstBuilder: block={:?}", block); - for stmt in block.statements.iter() { - self.build_statement(stmt)?; - } + debug!("Abstractconstbuilder::build: body={:?}", &*self.body); + let last = self.recurse_build(self.body_id)?; + self.nodes[last].used = true; - if let Some(next) = self.build_terminator(block.terminator())? { - block = &self.body.basic_blocks()[next]; - } else { - break; - } - } - - assert_eq!(self.locals[mir::RETURN_PLACE], self.nodes.last().unwrap()); for n in self.nodes.iter() { if let Node::Leaf(ty::Const { val: ty::ConstKind::Unevaluated(ct), ty: _ }) = n.node { // `AbstractConst`s should not contain any promoteds as they require references which @@ -523,13 +335,108 @@ impl<'a, 'tcx> AbstractConstBuilder<'a, 'tcx> { } } - self.nodes[self.locals[mir::RETURN_PLACE]].used = true; if let Some(&unused) = self.nodes.iter().find(|n| !n.used) { self.error(Some(unused.span), "dead code")?; } Ok(self.tcx.arena.alloc_from_iter(self.nodes.into_iter().map(|n| n.node))) } + + fn recurse_build(&mut self, node: thir::ExprId) -> Result { + use thir::ExprKind; + let node = &self.body.clone().exprs[node]; + debug!("recurse_build: node={:?}", node); + Ok(match &node.kind { + // I dont know if handling of these 3 is correct + &ExprKind::Scope { value, .. } => self.recurse_build(value)?, + &ExprKind::PlaceTypeAscription { source, .. } | + &ExprKind::ValueTypeAscription { source, .. } => self.recurse_build(source)?, + + &ExprKind::Literal { literal, .. } + | &ExprKind::StaticRef { literal, .. } => self.add_node(Node::Leaf(literal), node.span), + + // FIXME(generic_const_exprs) handle `from_hir_call` field + ExprKind::Call { fun, args, .. } => { + let fun = self.recurse_build(*fun)?; + + let mut new_args = Vec::::with_capacity(args.len()); + for &id in args.iter() { + new_args.push(self.recurse_build(id)?); + } + let new_args = self.tcx.arena.alloc_slice(&new_args); + self.add_node(Node::FunctionCall(fun, new_args), node.span) + }, + &ExprKind::Binary { op, lhs, rhs } if Self::check_binop(op) => { + let lhs = self.recurse_build(lhs)?; + let rhs = self.recurse_build(rhs)?; + self.add_node(Node::Binop(op, lhs, rhs), node.span) + } + &ExprKind::Unary { op, arg } if Self::check_unop(op) => { + let arg = self.recurse_build(arg)?; + self.add_node(Node::UnaryOp(op, arg), node.span) + }, + // HACK: without this arm the following doesn't compile: + // ``` + // fn foo(_: [(); N + 1]) { + // bar::<{ N + 1}>(); + // } + // ``` + // we ought to properly handle this in `try_unify` + ExprKind::Block { body: thir::Block { stmts: box [], expr: Some(e), .. }} => self.recurse_build(*e)?, + ExprKind::Block { body } => { + let mut stmts = Vec::with_capacity(body.stmts.len()); + for &id in body.stmts.iter() { + match &self.body.stmts[id].kind { + thir::StmtKind::Let { .. } => return self.error( + Some(node.span), + "let statements are not supported in generic constants", + ).map(|never| never), + thir::StmtKind::Expr { expr, .. } => stmts.push(self.recurse_build(*expr)?), + } + }; + let stmts = self.tcx.arena.alloc_slice(&stmts); + let opt_expr = body.expr.map(|e| self.recurse_build(e)).transpose()?; + self.add_node(Node::Block(stmts, opt_expr), node.span) + } + &ExprKind::Cast { source } => todo!(), + // never can arise even without panic/fail to terminate + &ExprKind::NeverToAny { source } => todo!(), + // i think this is a dummy usage of the expr to allow coercions + &ExprKind::Use { source } => todo!(), + + ExprKind::Return { .. } + | ExprKind::Box { .. } // allocations not allowed in constants + | ExprKind::AssignOp { .. } + | ExprKind::AddressOf { .. } // FIXME(generic_const_exprs) + | ExprKind::Borrow { .. } // FIXME(generic_const_exprs) + | ExprKind::Deref { .. } // FIXME(generic_const_exprs) + | ExprKind::Repeat { .. } // FIXME(generic_const_exprs) + | ExprKind::Array { .. } // FIXME(generic_const_exprs) + | ExprKind::Tuple { .. } // FIXME(generic_const_exprs) + | ExprKind::Index { .. } // FIXME(generic_const_exprs) + | ExprKind::Field { .. } // FIXME(generic_const_exprs) + | ExprKind::ConstBlock { .. } // FIXME(generic_const_exprs) + | ExprKind::Adt(_) // FIXME(generic_const_exprs) we *should* permit this but dont currently + | ExprKind::Match { .. } + | ExprKind::VarRef { .. } // + | ExprKind::UpvarRef { .. } // we dont permit let stmts so... + | ExprKind::Closure { .. } + | ExprKind::Let { .. } // let expressions imply control flow + | ExprKind::Loop { .. } + | ExprKind::Assign { .. } + | ExprKind::LogicalOp { .. } + | ExprKind::Unary { .. } // + | ExprKind::Binary { .. } // we handle valid unary/binary ops above + | ExprKind::Break { .. } + | ExprKind::Continue { .. } + | ExprKind::If { .. } + | ExprKind::Pointer { .. } // dont know if this is correct + | ExprKind::ThreadLocalRef(_) + | ExprKind::LlvmInlineAsm { .. } + | ExprKind::InlineAsm { .. } + | ExprKind::Yield { .. } => return self.error(Some(node.span), "unsupported operation in generic constant").map(|never| never), + }) + } } /// Builds an abstract const, do not use this directly, but use `AbstractConst::new` instead. @@ -547,8 +454,17 @@ pub(super) fn mir_abstract_const<'tcx>( DefKind::AnonConst => (), _ => return Ok(None), } - let body = tcx.mir_const(def).borrow(); - AbstractConstBuilder::new(tcx, &body)?.map(AbstractConstBuilder::build).transpose() + debug!("mir_abstract_const: {:?}", def); + let body = tcx.thir_body(def); + + if body.0.borrow().exprs.is_empty() { + // type error in constant, there is no thir + return Err(ErrorReported); + } + + AbstractConstBuilder::new(tcx, (&*body.0.borrow(), body.1))? + .map(AbstractConstBuilder::build) + .transpose() } else { Ok(None) } @@ -599,6 +515,12 @@ where recurse(tcx, ct.subtree(func), f)?; args.iter().try_for_each(|&arg| recurse(tcx, ct.subtree(arg), f)) } + Node::Block(stmts, opt_expr) => { + for id in stmts.iter().copied().chain(opt_expr) { + recurse(tcx, ct.subtree(id), f)?; + } + ControlFlow::CONTINUE + } Node::Cast(_, operand, _) => recurse(tcx, ct.subtree(operand), f), } } diff --git a/compiler/rustc_trait_selection/src/traits/object_safety.rs b/compiler/rustc_trait_selection/src/traits/object_safety.rs index 57b8a84300f..e64cc9e4b8f 100644 --- a/compiler/rustc_trait_selection/src/traits/object_safety.rs +++ b/compiler/rustc_trait_selection/src/traits/object_safety.rs @@ -844,9 +844,10 @@ fn contains_illegal_self_type_reference<'tcx, T: TypeFoldable<'tcx>>( self.visit_const(leaf) } Node::Cast(_, _, ty) => self.visit_ty(ty), - Node::Binop(..) | Node::UnaryOp(..) | Node::FunctionCall(_, _) => { - ControlFlow::CONTINUE - } + Node::Block(_, _) + | Node::Binop(..) + | Node::UnaryOp(..) + | Node::FunctionCall(_, _) => ControlFlow::CONTINUE, }) } else { ControlFlow::CONTINUE