diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 4360fbeb9bbc1..a398fd80119be 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1295,6 +1295,7 @@ impl Expr { ExprKind::Yield(..) => ExprPrecedence::Yield, ExprKind::Yeet(..) => ExprPrecedence::Yeet, ExprKind::FormatArgs(..) => ExprPrecedence::FormatArgs, + ExprKind::Become(..) => ExprPrecedence::Become, ExprKind::Err => ExprPrecedence::Err, } } @@ -1515,6 +1516,11 @@ pub enum ExprKind { /// with an optional value to be returned. Yeet(Option>), + /// A tail call return, with the value to be returned. + /// + /// While `.0` must be a function call, we check this later, after parsing. + Become(P), + /// Bytes included via `include_bytes!` /// Added for optimization purposes to avoid the need to escape /// large binary blobs - should always behave like [`ExprKind::Lit`] diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 66b94d12a32c6..53a9c9a046ea0 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -1457,6 +1457,7 @@ pub fn noop_visit_expr( ExprKind::Yeet(expr) => { visit_opt(expr, |expr| vis.visit_expr(expr)); } + ExprKind::Become(expr) => vis.visit_expr(expr), ExprKind::InlineAsm(asm) => vis.visit_inline_asm(asm), ExprKind::FormatArgs(fmt) => vis.visit_format_args(fmt), ExprKind::OffsetOf(container, fields) => { diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs index 35afd5423721d..096077e09bf76 100644 --- a/compiler/rustc_ast/src/util/parser.rs +++ b/compiler/rustc_ast/src/util/parser.rs @@ -245,6 +245,7 @@ pub enum ExprPrecedence { Ret, Yield, Yeet, + Become, Range, @@ -298,7 +299,8 @@ impl ExprPrecedence { | ExprPrecedence::Continue | ExprPrecedence::Ret | ExprPrecedence::Yield - | ExprPrecedence::Yeet => PREC_JUMP, + | ExprPrecedence::Yeet + | ExprPrecedence::Become => PREC_JUMP, // `Range` claims to have higher precedence than `Assign`, but `x .. x = x` fails to // parse, instead of parsing as `(x .. x) = x`. Giving `Range` a lower precedence diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 275692ad5dda7..d9de5b8e197db 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -908,6 +908,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) { ExprKind::Yeet(optional_expression) => { walk_list!(visitor, visit_expr, optional_expression); } + ExprKind::Become(expr) => visitor.visit_expr(expr), ExprKind::MacCall(mac) => visitor.visit_mac_call(mac), ExprKind::Paren(subexpression) => visitor.visit_expr(subexpression), ExprKind::InlineAsm(asm) => visitor.visit_inline_asm(asm), diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index f52797c4f3f1d..29972dd76eb0f 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -275,6 +275,10 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::ExprKind::Ret(e) } ExprKind::Yeet(sub_expr) => self.lower_expr_yeet(e.span, sub_expr.as_deref()), + ExprKind::Become(sub_expr) => { + let sub_expr = self.lower_expr(sub_expr); + hir::ExprKind::Become(sub_expr) + } ExprKind::InlineAsm(asm) => { hir::ExprKind::InlineAsm(self.lower_inline_asm(e.span, asm)) } diff --git a/compiler/rustc_ast_passes/src/feature_gate.rs b/compiler/rustc_ast_passes/src/feature_gate.rs index 2125349909ef1..b0dbc2c23403e 100644 --- a/compiler/rustc_ast_passes/src/feature_gate.rs +++ b/compiler/rustc_ast_passes/src/feature_gate.rs @@ -555,6 +555,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session) { gate_all!(dyn_star, "`dyn*` trait objects are experimental"); gate_all!(const_closures, "const closures are experimental"); gate_all!(builtin_syntax, "`builtin #` syntax is unstable"); + gate_all!(explicit_tail_calls, "`become` expression is experimental"); if !visitor.features.negative_bounds { for &span in spans.get(&sym::negative_bounds).iter().copied().flatten() { diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 87c32ffce1214..609920180a2fa 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -537,6 +537,11 @@ impl<'a> State<'a> { self.print_expr_maybe_paren(expr, parser::PREC_JUMP); } } + ast::ExprKind::Become(result) => { + self.word("become"); + self.word(" "); + self.print_expr_maybe_paren(result, parser::PREC_JUMP); + } ast::ExprKind::InlineAsm(a) => { // FIXME: This should have its own syntax, distinct from a macro invocation. self.word("asm!"); diff --git a/compiler/rustc_borrowck/src/invalidation.rs b/compiler/rustc_borrowck/src/invalidation.rs index b2ff25ecb96f4..0ff5c623e8601 100644 --- a/compiler/rustc_borrowck/src/invalidation.rs +++ b/compiler/rustc_borrowck/src/invalidation.rs @@ -137,6 +137,12 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> { } self.mutate_place(location, *destination, Deep); } + TerminatorKind::TailCall { func, args, .. } => { + self.consume_operand(location, func); + for arg in args { + self.consume_operand(location, arg); + } + } TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => { self.consume_operand(location, cond); use rustc_middle::mir::AssertKind; diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index 99a988f2c629e..9ba97041c8add 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -719,6 +719,12 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro } self.mutate_place(loc, (*destination, span), Deep, flow_state); } + TerminatorKind::TailCall { func, args, .. } => { + self.consume_operand(loc, (func, span), flow_state); + for arg in args { + self.consume_operand(loc, (arg, span), flow_state); + } + } TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => { self.consume_operand(loc, (cond, span), flow_state); use rustc_middle::mir::AssertKind; @@ -803,7 +809,11 @@ impl<'cx, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'cx, 'tcx, R> for MirBorro } } - TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::GeneratorDrop => { + // FIXME(explicit_tail_calls): do we need to do something similar before the tail call? + TerminatorKind::Resume + | TerminatorKind::Return + | TerminatorKind::TailCall { .. } + | TerminatorKind::GeneratorDrop => { // Returning from the function implicitly kills storage for all locals and statics. // Often, the storage will already have been killed by an explicit // StorageDead, but we don't always emit those (notably on unwind paths), diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 6697e1aff7dd0..b26946e96583f 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -1370,7 +1370,15 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } // FIXME: check the values } - TerminatorKind::Call { func, args, destination, from_hir_call, target, .. } => { + TerminatorKind::Call { func, args, .. } + | TerminatorKind::TailCall { func, args, .. } => { + let from_hir_call = matches!( + &term.kind, + TerminatorKind::Call { from_hir_call: true, .. } + // tail calls don't support overloaded operators + | TerminatorKind::TailCall { .. } + ); + self.check_operand(func, term_location); for arg in args { self.check_operand(arg, term_location); @@ -1426,7 +1434,10 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ConstraintCategory::Boring, ); let sig = self.normalize(sig, term_location); - self.check_call_dest(body, term, &sig, *destination, *target, term_location); + + if let TerminatorKind::Call { destination, target, .. } = term.kind { + self.check_call_dest(body, term, &sig, destination, target, term_location); + } // The ordinary liveness rules will ensure that all // regions in the type of the callee are live here. We @@ -1444,7 +1455,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { .add_element(region_vid, term_location); } - self.check_call_inputs(body, term, &sig, args, term_location, *from_hir_call); + self.check_call_inputs(body, term, &sig, args, term_location, from_hir_call); } TerminatorKind::Assert { cond, msg, .. } => { self.check_operand(cond, term_location); @@ -1637,6 +1648,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { span_mirbug!(self, block_data, "return on cleanup block") } } + TerminatorKind::TailCall { .. } => { + if is_cleanup { + span_mirbug!(self, block_data, "tailcall on cleanup block") + } + } TerminatorKind::GeneratorDrop { .. } => { if is_cleanup { span_mirbug!(self, block_data, "generator_drop in cleanup block") diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index b619e80e15f3c..902c1e40c7541 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -320,6 +320,7 @@ impl<'cx, 'a> Context<'cx, 'a> { | ExprKind::Underscore | ExprKind::While(_, _, _) | ExprKind::Yeet(_) + | ExprKind::Become(_) | ExprKind::Yield(_) => {} } } diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index fcfa0b862d4b5..22ddc6d9d4353 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -433,6 +433,11 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) { ) }); } + // FIXME(explicit_tail_calls): add support for tail calls to the cranelift backend, once cranelift supports tail calls + TerminatorKind::TailCall { fn_span, .. } => span_bug!( + *fn_span, + "tail calls are not yet supported in `rustc_codegen_cranelift` backend" + ), TerminatorKind::InlineAsm { template, operands, diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index 77af561a58724..dea61355e2adc 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -566,6 +566,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>( { return None; } + TerminatorKind::TailCall { .. } => return None, TerminatorKind::Call { .. } => {} } } diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index f9ea0f004564b..46e328a626d2e 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -1378,6 +1378,18 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { call } + fn tail_call( + &mut self, + _typ: Type<'gcc>, + _fn_attrs: Option<&CodegenFnAttrs>, + _fn_abi: &FnAbi<'tcx, Ty<'tcx>>, + _func: RValue<'gcc>, + _: &[RValue<'gcc>], + _: Option<&Funclet> + ) { + bug!("tail calls are not yet supported in `rustc_codegen_gcc` backend") + } + fn zext(&mut self, value: RValue<'gcc>, dest_typ: Type<'gcc>) -> RValue<'gcc> { // FIXME(antoyo): this does not zero-extend. if value.get_type().is_bool() && dest_typ.is_i8(&self.cx) { diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index b4aa001547c4c..f67901c8ff159 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -14,6 +14,7 @@ use rustc_codegen_ssa::traits::*; use rustc_codegen_ssa::MemFlags; use rustc_data_structures::small_c_str::SmallCStr; use rustc_hir::def_id::DefId; +use rustc_middle::bug; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs; use rustc_middle::ty::layout::{ FnAbiError, FnAbiOfHelpers, FnAbiRequest, LayoutError, LayoutOfHelpers, TyAndLayout, @@ -1217,6 +1218,29 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { call } + fn tail_call( + &mut self, + llty: Self::Type, + fn_attrs: Option<&CodegenFnAttrs>, + fn_abi: &FnAbi<'tcx, Ty<'tcx>>, + llfn: Self::Value, + args: &[Self::Value], + funclet: Option<&Self::Funclet>, + ) { + let call = self.call(llty, fn_attrs, Some(fn_abi), llfn, args, funclet); + + // Depending on the pass mode we neet to generate different return instructions for llvm + match &fn_abi.ret.mode { + abi::call::PassMode::Ignore | abi::call::PassMode::Indirect { .. } => self.ret_void(), + abi::call::PassMode::Direct(_) | abi::call::PassMode::Pair { .. } => self.ret(call), + mode @ abi::call::PassMode::Cast(..) => { + bug!("Encountered `PassMode::{mode:?}` during codegen") + } + } + + unsafe { llvm::LLVMRustSetTailCallKind(call, llvm::TailCallKind::MustTail) }; + } + fn zext(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value { unsafe { llvm::LLVMBuildZExt(self.llbuilder, val, dest_ty, UNNAMED) } } diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 6ef3418cc5f77..32ddc228b8d7c 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -585,6 +585,17 @@ pub enum ThreadLocalMode { LocalExec, } +/// LLVMRustTailCallKind +#[derive(Copy, Clone)] +#[repr(C)] +pub enum TailCallKind { + None, + Tail, + MustTail, + NoTail, + Last, +} + /// LLVMRustChecksumKind #[derive(Copy, Clone)] #[repr(C)] @@ -1195,6 +1206,7 @@ extern "C" { NameLen: size_t, ) -> Option<&Value>; pub fn LLVMSetTailCall(CallInst: &Value, IsTailCall: Bool); + pub fn LLVMRustSetTailCallKind(CallInst: &Value, TKC: TailCallKind); // Operations on attributes pub fn LLVMRustCreateAttrNoValue(C: &Context, attr: AttributeKind) -> &Attribute; diff --git a/compiler/rustc_codegen_ssa/src/mir/analyze.rs b/compiler/rustc_codegen_ssa/src/mir/analyze.rs index 22c1f05974ddd..b377baba4d939 100644 --- a/compiler/rustc_codegen_ssa/src/mir/analyze.rs +++ b/compiler/rustc_codegen_ssa/src/mir/analyze.rs @@ -292,6 +292,7 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec { /* nothing to do */ } TerminatorKind::Call { unwind, .. } | TerminatorKind::InlineAsm { unwind, .. } diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index e0cb26d3ba866..3fc860592930f 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -239,6 +239,31 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'tcx> { } } + /// **Tail** call `fn_ptr` of `fn_abi` with the arguments `llargs`. + fn do_tail_call>( + &self, + fx: &mut FunctionCx<'a, 'tcx, Bx>, + bx: &mut Bx, + fn_abi: &'tcx FnAbi<'tcx, Ty<'tcx>>, + fn_ptr: Bx::Value, + llargs: &[Bx::Value], + copied_constant_arguments: &[PlaceRef<'tcx, ::Value>], + ) { + let fn_ty = bx.fn_decl_backend_type(&fn_abi); + + let fn_attrs = if bx.tcx().def_kind(fx.instance.def_id()).has_codegen_attrs() { + Some(bx.tcx().codegen_fn_attrs(fx.instance.def_id())) + } else { + None + }; + + bx.tail_call(fn_ty, fn_attrs, fn_abi, fn_ptr, &llargs, self.funclet(fx)); + + for tmp in copied_constant_arguments { + bx.lifetime_end(tmp.llval, tmp.layout.size); + } + } + /// Generates inline assembly with optional `destination` and `unwind`. fn do_inlineasm>( &self, @@ -1077,6 +1102,242 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { ) } + fn codegen_tail_call_terminator( + &mut self, + helper: TerminatorCodegenHelper<'tcx>, + bx: &mut Bx, + terminator: &mir::Terminator<'tcx>, + func: &mir::Operand<'tcx>, + args: &[mir::Operand<'tcx>], + fn_span: Span, + ) { + let source_info = terminator.source_info; + let span = source_info.span; + + // Create the callee. This is a fn ptr or zero-sized and hence a kind of scalar. + let callee = self.codegen_operand(bx, func); + + let (instance, mut llfn) = match *callee.layout.ty.kind() { + ty::FnDef(def_id, substs) => ( + Some( + ty::Instance::expect_resolve( + bx.tcx(), + ty::ParamEnv::reveal_all(), + def_id, + substs, + ) + .polymorphize(bx.tcx()), + ), + None, + ), + ty::FnPtr(_) => (None, Some(callee.immediate())), + _ => bug!("{} is not callable", callee.layout.ty), + }; + let def = instance.map(|i| i.def); + + if let Some(ty::InstanceDef::DropGlue(..)) = def { + bug!("tail-calling drop glue should not be possible"); + } + + // FIXME(eddyb) avoid computing this if possible, when `instance` is + // available - right now `sig` is only needed for getting the `abi` + // and figuring out how many extra args were passed to a C-variadic `fn`. + let sig = callee.layout.ty.fn_sig(bx.tcx()); + let abi = sig.abi(); + + if let Some(ty::InstanceDef::Intrinsic(def_id)) = def { + span_bug!( + fn_span, + "Attempting to tail-call `{}` intrinsic", + bx.tcx().item_name(def_id) + ); + }; + + let extra_args = &args[sig.inputs().skip_binder().len()..]; + let extra_args = bx.tcx().mk_type_list_from_iter(extra_args.iter().map(|op_arg| { + let op_ty = op_arg.ty(self.mir, bx.tcx()); + self.monomorphize(op_ty) + })); + + let fn_abi = match instance { + Some(instance) => bx.fn_abi_of_instance(instance, extra_args), + None => bx.fn_abi_of_fn_ptr(sig, extra_args), + }; + + // The arguments we'll be passing. Plus one to account for outptr, if used. + let arg_count = fn_abi.args.len() + fn_abi.ret.is_indirect() as usize; + let mut llargs = Vec::with_capacity(arg_count); + + if fn_abi.ret.is_indirect() { + let LocalRef::Place(place) = self.locals[mir::RETURN_PLACE] + else { bug!() }; + + llargs.push(place.llval); + } + + // Split the rust-call tupled arguments off. + let (first_args, untuple) = if abi == Abi::RustCall && !args.is_empty() { + let (tup, args) = args.split_last().unwrap(); + (args, Some(tup)) + } else { + (args, None) + }; + + // FIXME(explicit_tail_calls): refactor this into a separate function, deduplicate with `Call` + let mut copied_constant_arguments = vec![]; + 'make_args: for (i, arg) in first_args.iter().enumerate() { + let mut op = self.codegen_operand(bx, arg); + + if let (0, Some(ty::InstanceDef::Virtual(_, idx))) = (i, def) { + match op.val { + Pair(data_ptr, meta) => { + // In the case of Rc, we need to explicitly pass a + // *mut RcBox with a Scalar (not ScalarPair) ABI. This is a hack + // that is understood elsewhere in the compiler as a method on + // `dyn Trait`. + // To get a `*mut RcBox`, we just keep unwrapping newtypes until + // we get a value of a built-in pointer type. + // + // This is also relevant for `Pin<&mut Self>`, where we need to peel the `Pin`. + 'descend_newtypes: while !op.layout.ty.is_unsafe_ptr() + && !op.layout.ty.is_ref() + { + for i in 0..op.layout.fields.count() { + let field = op.extract_field(bx, i); + if !field.layout.is_zst() { + // we found the one non-zero-sized field that is allowed + // now find *its* non-zero-sized field, or stop if it's a + // pointer + op = field; + continue 'descend_newtypes; + } + } + + span_bug!(span, "receiver has no non-zero-sized fields {:?}", op); + } + + // now that we have `*dyn Trait` or `&dyn Trait`, split it up into its + // data pointer and vtable. Look up the method in the vtable, and pass + // the data pointer as the first argument + llfn = Some(meth::VirtualIndex::from_index(idx).get_fn( + bx, + meta, + op.layout.ty, + &fn_abi, + )); + llargs.push(data_ptr); + continue 'make_args; + } + Ref(data_ptr, Some(meta), _) => { + // by-value dynamic dispatch + llfn = Some(meth::VirtualIndex::from_index(idx).get_fn( + bx, + meta, + op.layout.ty, + &fn_abi, + )); + llargs.push(data_ptr); + continue; + } + Immediate(_) => { + // See comment above explaining why we peel these newtypes + 'descend_newtypes: while !op.layout.ty.is_unsafe_ptr() + && !op.layout.ty.is_ref() + { + for i in 0..op.layout.fields.count() { + let field = op.extract_field(bx, i); + if !field.layout.is_zst() { + // we found the one non-zero-sized field that is allowed + // now find *its* non-zero-sized field, or stop if it's a + // pointer + op = field; + continue 'descend_newtypes; + } + } + + span_bug!(span, "receiver has no non-zero-sized fields {:?}", op); + } + + // Make sure that we've actually unwrapped the rcvr down + // to a pointer or ref to `dyn* Trait`. + if !op.layout.ty.builtin_deref(true).unwrap().ty.is_dyn_star() { + span_bug!(span, "can't codegen a virtual call on {:#?}", op); + } + let place = op.deref(bx.cx()); + let data_ptr = place.project_field(bx, 0); + let meta_ptr = place.project_field(bx, 1); + let meta = bx.load_operand(meta_ptr); + llfn = Some(meth::VirtualIndex::from_index(idx).get_fn( + bx, + meta.immediate(), + op.layout.ty, + &fn_abi, + )); + llargs.push(data_ptr.llval); + continue; + } + _ => { + span_bug!(span, "can't codegen a virtual call on {:#?}", op); + } + } + } + + // The callee needs to own the argument memory if we pass it + // by-ref, so make a local copy of non-immediate constants. + match (arg, op.val) { + (&mir::Operand::Copy(_), Ref(_, None, _)) + | (&mir::Operand::Constant(_), Ref(_, None, _)) => { + let tmp = PlaceRef::alloca(bx, op.layout); + bx.lifetime_start(tmp.llval, tmp.layout.size); + op.val.store(bx, tmp); + op.val = Ref(tmp.llval, None, tmp.align); + copied_constant_arguments.push(tmp); + } + _ => {} + } + + self.codegen_argument(bx, op, &mut llargs, &fn_abi.args[i]); + } + let num_untupled = untuple.map(|tup| { + self.codegen_arguments_untupled(bx, tup, &mut llargs, &fn_abi.args[first_args.len()..]) + }); + + let needs_location = + instance.map_or(false, |i| i.def.requires_caller_location(self.cx.tcx())); + if needs_location { + let mir_args = if let Some(num_untupled) = num_untupled { + first_args.len() + num_untupled + } else { + args.len() + }; + assert_eq!( + fn_abi.args.len(), + mir_args + 1, + "#[track_caller] fn's must have 1 more argument in their ABI than in their MIR: {:?} {:?} {:?}", + instance, + fn_span, + fn_abi, + ); + let location = + self.get_caller_location(bx, mir::SourceInfo { span: fn_span, ..source_info }); + debug!( + "codegen_tail_call_terminator({:?}): location={:?} (fn_span {:?})", + terminator, location, fn_span + ); + + let last_arg = fn_abi.args.last().unwrap(); + self.codegen_argument(bx, location, &mut llargs, last_arg); + } + + let fn_ptr = match (instance, llfn) { + (Some(instance), None) => bx.get_fn_addr(instance), + (_, Some(llfn)) => llfn, + _ => span_bug!(span, "no instance or llfn for tail-call"), + }; + + helper.do_tail_call(self, bx, fn_abi, fn_ptr, &llargs, &copied_constant_arguments); + } + fn codegen_asm_terminator( &mut self, helper: TerminatorCodegenHelper<'tcx>, @@ -1294,6 +1555,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { fn_span, mergeable_succ(), ), + + mir::TerminatorKind::TailCall { ref func, ref args, fn_span } => { + self.codegen_tail_call_terminator(helper, bx, terminator, func, args, fn_span); + MergingSucc::False + } + mir::TerminatorKind::GeneratorDrop | mir::TerminatorKind::Yield { .. } => { bug!("generator ops in codegen") } diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs index 853c6934c2c24..df060469d05f1 100644 --- a/compiler/rustc_codegen_ssa/src/traits/builder.rs +++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs @@ -330,6 +330,17 @@ pub trait BuilderMethods<'a, 'tcx>: args: &[Self::Value], funclet: Option<&Self::Funclet>, ) -> Self::Value; + + fn tail_call( + &mut self, + llty: Self::Type, + fn_attrs: Option<&CodegenFnAttrs>, + fn_abi: &FnAbi<'tcx, Ty<'tcx>>, + llfn: Self::Value, + args: &[Self::Value], + funclet: Option<&Self::Funclet>, + ); + fn zext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value; fn do_not_inline(&mut self, llret: Self::Value); diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 7269ff8d53cdf..3520934967477 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -115,6 +115,75 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } } + TailCall { ref func, ref args, fn_span: _ } => { + // FIXME: a lot of code here is duplicated with normal calls, can we refactor this? + let old_frame_idx = self.frame_idx(); + let func = self.eval_operand(func, None)?; + let args = self.eval_operands(args)?; + + let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx); + let fn_sig = + self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig_binder); + let extra_args = &args[fn_sig.inputs().len()..]; + let extra_args = + self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout.ty)); + + let (fn_val, fn_abi, with_caller_location) = match *func.layout.ty.kind() { + ty::FnPtr(_sig) => { + let fn_ptr = self.read_pointer(&func)?; + let fn_val = self.get_ptr_fn(fn_ptr)?; + (fn_val, self.fn_abi_of_fn_ptr(fn_sig_binder, extra_args)?, false) + } + ty::FnDef(def_id, substs) => { + let instance = self.resolve(def_id, substs)?; + ( + FnVal::Instance(instance), + self.fn_abi_of_instance(instance, extra_args)?, + instance.def.requires_caller_location(*self.tcx), + ) + } + _ => span_bug!( + terminator.source_info.span, + "invalid callee of type {:?}", + func.layout.ty + ), + }; + + // FIXME(explicit_tail_calls): maybe we need the drop here?... + + // This is the "canonical" implementation of tails calls, + // a pop of the current stack frame, followed by a normal call + // which pushes a new stack frame, with the return address from + // the popped stack frame. + // + // Note that we can't use `pop_stack_frame` as it "executes" + // the goto to the return block, but we don't want to, + // only the tail called function should return to the current + // return block. + let Some(prev_frame) = self.stack_mut().pop() + else { span_bug!(terminator.source_info.span, "empty stack while evaluating this tail call") }; + + let StackPopCleanup::Goto { ret, unwind } = prev_frame.return_to_block + else { span_bug!(terminator.source_info.span, "tail call with the root stack frame") }; + + self.eval_fn_call( + fn_val, + (fn_sig.abi, fn_abi), + &args, + with_caller_location, + &prev_frame.return_place, + ret, + unwind, + )?; + + if self.frame_idx() != old_frame_idx { + span_bug!( + terminator.source_info.span, + "evaluating this tail call pushed a new stack frame" + ); + } + } + Drop { place, target, unwind, replace: _ } => { let frame = self.frame(); let ty = place.ty(&frame.body.local_decls, *self.tcx).ty; diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs index 57d939747aab3..e3ef4a3e11521 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs @@ -129,6 +129,8 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { ccx: &'mir ConstCx<'mir, 'tcx>, tainted_by_errors: Option, ) -> ConstQualifs { + // FIXME(explicit_tail_calls): uhhhh I think we can return without return now, does it change anything + // Find the `Return` terminator if one exists. // // If no `Return` terminator exists, this MIR is divergent. Just return the conservative @@ -702,7 +704,15 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { self.super_terminator(terminator, location); match &terminator.kind { - TerminatorKind::Call { func, args, fn_span, from_hir_call, .. } => { + TerminatorKind::Call { func, args, fn_span, .. } + | TerminatorKind::TailCall { func, args, fn_span, .. } => { + let from_hir_call = matches!( + &terminator.kind, + TerminatorKind::Call { from_hir_call: true, .. } + // tail calls don't support overloaded operators + | TerminatorKind::TailCall { .. } + ); + let ConstCx { tcx, body, param_env, .. } = *self.ccx; let caller = self.def_id(); @@ -755,7 +765,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { callee, substs, span: *fn_span, - from_hir_call: *from_hir_call, + from_hir_call, feature: Some(sym::const_trait_impl), }); return; @@ -788,7 +798,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { callee, substs, span: *fn_span, - from_hir_call: *from_hir_call, + from_hir_call, feature: None, }); @@ -814,7 +824,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { callee, substs, span: *fn_span, - from_hir_call: *from_hir_call, + from_hir_call, feature: None, }); return; @@ -857,7 +867,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { callee, substs, span: *fn_span, - from_hir_call: *from_hir_call, + from_hir_call, feature: None, }); return; @@ -917,7 +927,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { callee, substs, span: *fn_span, - from_hir_call: *from_hir_call, + from_hir_call, feature: None, }); return; diff --git a/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs b/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs index 1f1640fd80ae6..8508c8e24f826 100644 --- a/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs +++ b/compiler/rustc_const_eval/src/transform/check_consts/post_drop_elaboration.rs @@ -106,6 +106,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> { mir::TerminatorKind::Terminate | mir::TerminatorKind::Call { .. } + | mir::TerminatorKind::TailCall { .. } | mir::TerminatorKind::Assert { .. } | mir::TerminatorKind::FalseEdge { .. } | mir::TerminatorKind::FalseUnwind { .. } diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs index 3c350e25ba6ec..744aba0a368be 100644 --- a/compiler/rustc_const_eval/src/transform/validate.rs +++ b/compiler/rustc_const_eval/src/transform/validate.rs @@ -976,7 +976,9 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { self.check_edge(location, *target, EdgeKind::Normal); self.check_unwind_edge(location, *unwind); } - TerminatorKind::Call { func, args, destination, target, unwind, .. } => { + + TerminatorKind::Call { func, args, .. } + | TerminatorKind::TailCall { func, args, .. } => { let func_ty = func.ty(&self.body.local_decls, self.tcx); match func_ty.kind() { ty::FnPtr(..) | ty::FnDef(..) => {} @@ -985,16 +987,21 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { format!("encountered non-callable type {} in `Call` terminator", func_ty), ), } - if let Some(target) = target { - self.check_edge(location, *target, EdgeKind::Normal); + + if let TerminatorKind::Call { target, unwind, .. } = terminator.kind { + if let Some(target) = target { + self.check_edge(location, target, EdgeKind::Normal); + } + self.check_unwind_edge(location, unwind); } - self.check_unwind_edge(location, *unwind); // The call destination place and Operand::Move place used as an argument might be // passed by a reference to the callee. Consequently they must be non-overlapping. // Currently this simply checks for duplicate places. self.place_cache.clear(); - self.place_cache.push(destination.as_ref()); + if let TerminatorKind::Call { destination, .. } = terminator.kind { + self.place_cache.push(destination.as_ref()); + } for arg in args { if let Operand::Move(place) = arg { self.place_cache.push(place.as_ref()); @@ -1008,7 +1015,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { self.fail( location, format!( - "encountered overlapping memory in `Call` terminator: {:?}", + "encountered overlapping memory in `{}` terminator: {:?}", + terminator.kind.name(), terminator.kind, ), ); diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index 4c53f9d8369fd..af7cf8660be74 100644 --- a/compiler/rustc_feature/src/active.rs +++ b/compiler/rustc_feature/src/active.rs @@ -154,6 +154,8 @@ declare_features! ( (active, compiler_builtins, "1.13.0", None, None), /// Allows writing custom MIR (active, custom_mir, "1.65.0", None, None), + /// Allows `become` expression aka explicit tail calls (internal because no TI yet). + (active, explicit_tail_calls, "CURRENT_RUSTC_VERSION", None, None), /// Outputs useful `assert!` messages (active, generic_assert, "1.63.0", None, None), /// Allows using the `rust-intrinsic`'s "ABI". diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 70fc66947df99..df495d5c42eaa 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1719,6 +1719,7 @@ impl Expr<'_> { ExprKind::Break(..) => ExprPrecedence::Break, ExprKind::Continue(..) => ExprPrecedence::Continue, ExprKind::Ret(..) => ExprPrecedence::Ret, + ExprKind::Become(..) => ExprPrecedence::Become, ExprKind::InlineAsm(..) => ExprPrecedence::InlineAsm, ExprKind::OffsetOf(..) => ExprPrecedence::OffsetOf, ExprKind::Struct(..) => ExprPrecedence::Struct, @@ -1776,6 +1777,7 @@ impl Expr<'_> { | ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) + | ExprKind::Become(..) | ExprKind::Let(..) | ExprKind::Loop(..) | ExprKind::Assign(..) @@ -1866,6 +1868,7 @@ impl Expr<'_> { | ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) + | ExprKind::Become(..) | ExprKind::Let(..) | ExprKind::Loop(..) | ExprKind::Assign(..) @@ -2025,6 +2028,8 @@ pub enum ExprKind<'hir> { Continue(Destination), /// A `return`, with an optional value to be returned. Ret(Option<&'hir Expr<'hir>>), + /// A `become`, with the value to be returned. + Become(&'hir Expr<'hir>), /// Inline assembly (from `asm!`), with its outputs and inputs. InlineAsm(&'hir InlineAsm<'hir>), diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index f84c814bd9278..1886a91bda838 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -791,6 +791,7 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) ExprKind::Ret(ref optional_expression) => { walk_list!(visitor, visit_expr, optional_expression); } + ExprKind::Become(ref expr) => visitor.visit_expr(expr), ExprKind::InlineAsm(ref asm) => { visitor.visit_inline_asm(asm, expression.hir_id); } diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index ced46fe426c47..a699cd6c942eb 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -1554,6 +1554,11 @@ impl<'a> State<'a> { self.print_expr_maybe_paren(expr, parser::PREC_JUMP); } } + hir::ExprKind::Become(result) => { + self.word("become"); + self.word(" "); + self.print_expr_maybe_paren(result, parser::PREC_JUMP); + } hir::ExprKind::InlineAsm(asm) => { self.word("asm!"); self.print_inline_asm(asm); diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl index c1c58db57648c..82a9f1cb5cec1 100644 --- a/compiler/rustc_hir_typeck/messages.ftl +++ b/compiler/rustc_hir_typeck/messages.ftl @@ -78,8 +78,8 @@ hir_typeck_note_edition_guide = for more on editions, read https://doc.rust-lang hir_typeck_op_trait_generic_params = `{$method_name}` must not have any generic parameters hir_typeck_return_stmt_outside_of_fn_body = - return statement outside of function body - .encl_body_label = the return is part of this body... + `{$statement_kind}` statement outside of function body + .encl_body_label = the `{$statement_kind}` is part of this body... .encl_fn_label = ...not the enclosing function body hir_typeck_struct_expr_non_exhaustive = diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs index 6b4168d89446f..bb4143cf9f626 100644 --- a/compiler/rustc_hir_typeck/src/errors.rs +++ b/compiler/rustc_hir_typeck/src/errors.rs @@ -31,6 +31,8 @@ pub struct ReturnStmtOutsideOfFnBody { pub encl_body_span: Option, #[label(hir_typeck_encl_fn_label)] pub encl_fn_span: Option, + // "return" or "become" + pub statement_kind: String, } #[derive(Diagnostic)] diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 3f6847be91b65..0334b88bcad1b 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -324,6 +324,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } ExprKind::Ret(ref expr_opt) => self.check_expr_return(expr_opt.as_deref(), expr), + ExprKind::Become(call) => self.check_expr_become(call, expr), ExprKind::Let(let_expr) => self.check_expr_let(let_expr), ExprKind::Loop(body, _, source, _) => { self.check_expr_loop(body, source, expected, expr) @@ -735,47 +736,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expr: &'tcx hir::Expr<'tcx>, ) -> Ty<'tcx> { if self.ret_coercion.is_none() { - let mut err = ReturnStmtOutsideOfFnBody { - span: expr.span, - encl_body_span: None, - encl_fn_span: None, - }; - - let encl_item_id = self.tcx.hir().get_parent_item(expr.hir_id); - - if let Some(hir::Node::Item(hir::Item { - kind: hir::ItemKind::Fn(..), - span: encl_fn_span, - .. - })) - | Some(hir::Node::TraitItem(hir::TraitItem { - kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)), - span: encl_fn_span, - .. - })) - | Some(hir::Node::ImplItem(hir::ImplItem { - kind: hir::ImplItemKind::Fn(..), - span: encl_fn_span, - .. - })) = self.tcx.hir().find_by_def_id(encl_item_id.def_id) - { - // We are inside a function body, so reporting "return statement - // outside of function body" needs an explanation. - - let encl_body_owner_id = self.tcx.hir().enclosing_body_owner(expr.hir_id); - - // If this didn't hold, we would not have to report an error in - // the first place. - assert_ne!(encl_item_id.def_id, encl_body_owner_id); - - let encl_body_id = self.tcx.hir().body_owned_by(encl_body_owner_id); - let encl_body = self.tcx.hir().body(encl_body_id); - - err.encl_body_span = Some(encl_body.value.span); - err.encl_fn_span = Some(*encl_fn_span); - } - - self.tcx.sess.emit_err(err); + self.emit_return_outside_of_fn_body(expr, "return"); if let Some(e) = expr_opt { // We still have to type-check `e` (issue #86188), but calling @@ -815,6 +776,38 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx.types.never } + fn check_expr_become( + &self, + call: &'tcx hir::Expr<'tcx>, + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + match &self.ret_coercion { + Some(ret_coercion) => { + let ret_ty = ret_coercion.borrow().expected_ty(); + let call_expr_ty = self.check_expr_with_hint(call, ret_ty); + + // N.B. don't coerce here, as tail calls can't support most/all coercions + // FIXME(explicit_tail_calls): add a diagnostic note that `become` doesn't allow coercions + self.demand_suptype(expr.span, ret_ty, call_expr_ty); + } + None => { + self.emit_return_outside_of_fn_body(expr, "become"); + + // Fallback to simply type checking `call` without hint/demanding the right types. + // Best effort to highlight more errors. + self.check_expr(call); + } + } + + self.tcx.types.never + } + + /// Check an expression that _is being returned_. + /// For example, this is called with `return_expr: $expr` when `return $expr` + /// is encountered. + /// + /// Note that this function must only be called in function bodies. + /// /// `explicit_return` is `true` if we're checking an explicit `return expr`, /// and `false` if we're checking a trailing expression. pub(super) fn check_return_expr( @@ -831,10 +824,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut span = return_expr.span; // Use the span of the trailing expression for our cause, // not the span of the entire function - if !explicit_return { - if let ExprKind::Block(body, _) = return_expr.kind && let Some(last_expr) = body.expr { + if !explicit_return + && let ExprKind::Block(body, _) = return_expr.kind + && let Some(last_expr) = body.expr + { span = last_expr.span; - } } ret_coercion.borrow_mut().coerce( self, @@ -854,6 +848,57 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + /// Emit an error because `return` or `become` is used outside of a function body. + /// + /// `expr` is the `return` (`become`) "statement", `kind` is the kind of the return + /// either "return" or "become" + /// + /// FIXME(explicit_tail_calls): come up with a better way than passing strings lol + fn emit_return_outside_of_fn_body(&self, expr: &hir::Expr<'_>, kind: &'static str) { + let mut err = ReturnStmtOutsideOfFnBody { + span: expr.span, + encl_body_span: None, + encl_fn_span: None, + statement_kind: kind.to_string(), + }; + + let encl_item_id = self.tcx.hir().get_parent_item(expr.hir_id); + + if let Some(hir::Node::Item(hir::Item { + kind: hir::ItemKind::Fn(..), + span: encl_fn_span, + .. + })) + | Some(hir::Node::TraitItem(hir::TraitItem { + kind: hir::TraitItemKind::Fn(_, hir::TraitFn::Provided(_)), + span: encl_fn_span, + .. + })) + | Some(hir::Node::ImplItem(hir::ImplItem { + kind: hir::ImplItemKind::Fn(..), + span: encl_fn_span, + .. + })) = self.tcx.hir().find_by_def_id(encl_item_id.def_id) + { + // We are inside a function body, so reporting "return statement + // outside of function body" needs an explanation. + + let encl_body_owner_id = self.tcx.hir().enclosing_body_owner(expr.hir_id); + + // If this didn't hold, we would not have to report an error in + // the first place. + assert_ne!(encl_item_id.def_id, encl_body_owner_id); + + let encl_body_id = self.tcx.hir().body_owned_by(encl_body_owner_id); + let encl_body = self.tcx.hir().body(encl_body_id); + + err.encl_body_span = Some(encl_body.value.span); + err.encl_fn_span = Some(*encl_fn_span); + } + + self.tcx.sess.emit_err(err); + } + fn point_at_return_for_opaque_ty_error( &self, errors: &mut Vec>, diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index e14e8ac2ce000..57e4f0fcc9c39 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -326,6 +326,10 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { } } + hir::ExprKind::Become(call) => { + self.consume_expr(call); + } + hir::ExprKind::Assign(lhs, rhs, _) => { self.mutate_expr(lhs); self.consume_expr(rhs); diff --git a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs index 786a8c28f998b..8fa29b5b87394 100644 --- a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs +++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs @@ -214,6 +214,7 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> { | ExprKind::Break(..) | ExprKind::Continue(..) | ExprKind::Ret(..) + | ExprKind::Become(..) | ExprKind::InlineAsm(..) | ExprKind::OffsetOf(..) | ExprKind::Struct(..) @@ -451,6 +452,9 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> { } } + // FIXME(explicit_tail_calls): we should record the "drop everything that is not passed by-value" here, I think + ExprKind::Become(_call) => intravisit::walk_expr(self, expr), + ExprKind::Call(f, args) => { self.visit_expr(f); for arg in args { diff --git a/compiler/rustc_hir_typeck/src/mem_categorization.rs b/compiler/rustc_hir_typeck/src/mem_categorization.rs index 78171e0b20e8c..6c8589493cb01 100644 --- a/compiler/rustc_hir_typeck/src/mem_categorization.rs +++ b/compiler/rustc_hir_typeck/src/mem_categorization.rs @@ -361,6 +361,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { | hir::ExprKind::AssignOp(..) | hir::ExprKind::Closure { .. } | hir::ExprKind::Ret(..) + | hir::ExprKind::Become(..) | hir::ExprKind::Unary(..) | hir::ExprKind::Yield(..) | hir::ExprKind::MethodCall(..) diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index ea04899ab6872..caaf34acd1bc5 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -116,6 +116,33 @@ extern "C" LLVMValueRef LLVMRustGetNamedValue(LLVMModuleRef M, const char *Name, return wrap(unwrap(M)->getNamedValue(StringRef(Name, NameLen))); } +enum class LLVMRustTailCallKind { + None, + Tail, + MustTail, + NoTail, + Last, +}; + +static CallInst::TailCallKind fromRust(LLVMRustTailCallKind Kind) { + switch (Kind) { + case LLVMRustTailCallKind::None: + return CallInst::TailCallKind::TCK_None; + case LLVMRustTailCallKind::Tail: + return CallInst::TailCallKind::TCK_Tail; + case LLVMRustTailCallKind::MustTail: + return CallInst::TailCallKind::TCK_MustTail; + case LLVMRustTailCallKind::Last: + return CallInst::TailCallKind::TCK_NoTail; + default: + report_fatal_error("bad CallInst::TailCallKind."); + } +} + +extern "C" void LLVMRustSetTailCallKind(LLVMValueRef Call, LLVMRustTailCallKind TCK) { + unwrap(Call)->setTailCallKind(fromRust(TCK)); +} + extern "C" LLVMValueRef LLVMRustGetOrInsertFunction(LLVMModuleRef M, const char *Name, size_t NameLen, @@ -1672,6 +1699,7 @@ extern "C" void LLVMRustSetDSOLocal(LLVMValueRef Global, bool is_dso_local) { unwrap(Global)->setDSOLocal(is_dso_local); } + struct LLVMRustModuleBuffer { std::string data; }; diff --git a/compiler/rustc_middle/src/mir/spanview.rs b/compiler/rustc_middle/src/mir/spanview.rs index 2165403da2671..faed54098be4d 100644 --- a/compiler/rustc_middle/src/mir/spanview.rs +++ b/compiler/rustc_middle/src/mir/spanview.rs @@ -269,6 +269,7 @@ pub fn terminator_kind_name(term: &Terminator<'_>) -> &'static str { Call { .. } => "Call", Assert { .. } => "Assert", Yield { .. } => "Yield", + TailCall { .. } => "TailCall", GeneratorDrop => "GeneratorDrop", FalseEdge { .. } => "FalseEdge", FalseUnwind { .. } => "FalseUnwind", diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 1a65f74f4fe22..6b09c533bebfc 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -646,6 +646,36 @@ pub enum TerminatorKind<'tcx> { fn_span: Span, }, + /// Tail call. + /// + /// Roughly speaking this is a chimera of [`Call`] and [`Return`], with some caveats. + /// Semantically tail calls consists of two actions: + /// - pop of the current stack frame + /// - a call to the `func`, with the return address of the **current** caller + /// - so that a `return` inside `func` returns to the caller of the caller + /// of the function that is currently being executed + /// + /// Note that in difference with [`Call`] this is missing + /// - `destination` (because it's always the return place) + /// - `target` (because it's always taken from the current stack frame) + /// - `unwind` (because it's always taken from the current stack frame) + /// + /// [`Call`]: TerminatorKind::Call + /// [`Return`]: TerminatorKind::Return + TailCall { + /// The function that’s being called. + func: Operand<'tcx>, + /// Arguments the function is called with. + /// These are owned by the callee, which is free to modify them. + /// This allows the memory occupied by "by-value" arguments to be + /// reused across function calls without duplicating the contents. + args: Vec>, + // FIXME(explicit_tail_calls): should we have the span for `become`? is this span accurate? do we need it? + /// This `Span` is the span of the function, without the dot and receiver + /// (e.g. `foo(a, b)` in `x.foo(a, b)` + fn_span: Span, + }, + /// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`, /// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some /// unspecified constant as the function to call, all the operands stored in the `AssertMessage` @@ -771,6 +801,7 @@ impl TerminatorKind<'_> { TerminatorKind::Unreachable => "Unreachable", TerminatorKind::Drop { .. } => "Drop", TerminatorKind::Call { .. } => "Call", + TerminatorKind::TailCall { .. } => "TailCall", TerminatorKind::Assert { .. } => "Assert", TerminatorKind::Yield { .. } => "Yield", TerminatorKind::GeneratorDrop => "GeneratorDrop", diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index 561ef371b0904..ce34266c721aa 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -158,6 +158,7 @@ impl<'tcx> TerminatorKind<'tcx> { | Terminate | GeneratorDrop | Return + | TailCall { .. } | Unreachable | Call { target: None, unwind: _, .. } | InlineAsm { destination: None, unwind: _, .. } => { @@ -200,6 +201,7 @@ impl<'tcx> TerminatorKind<'tcx> { | Terminate | GeneratorDrop | Return + | TailCall { .. } | Unreachable | Call { target: None, unwind: _, .. } | InlineAsm { destination: None, unwind: _, .. } => None.into_iter().chain(&mut []), @@ -216,6 +218,7 @@ impl<'tcx> TerminatorKind<'tcx> { | TerminatorKind::Resume | TerminatorKind::Terminate | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } @@ -235,6 +238,7 @@ impl<'tcx> TerminatorKind<'tcx> { | TerminatorKind::Resume | TerminatorKind::Terminate | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } @@ -325,6 +329,16 @@ impl<'tcx> TerminatorKind<'tcx> { } write!(fmt, ")") } + TailCall { func, args, .. } => { + write!(fmt, "tailcall {func:?}(")?; + for (index, arg) in args.iter().enumerate() { + if index > 0 { + write!(fmt, ", ")?; + } + write!(fmt, "{:?}", arg)?; + } + write!(fmt, ")") + } Assert { cond, expected, msg, .. } => { write!(fmt, "assert(")?; if !expected { @@ -389,7 +403,7 @@ impl<'tcx> TerminatorKind<'tcx> { pub fn fmt_successor_labels(&self) -> Vec> { use self::TerminatorKind::*; match *self { - Return | Resume | Terminate | Unreachable | GeneratorDrop => vec![], + Return | TailCall { .. } | Resume | Terminate | Unreachable | GeneratorDrop => vec![], Goto { .. } => vec!["".into()], SwitchInt { ref targets, .. } => targets .values diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index 8d44e929afde3..1dcfb22fcd90d 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -533,6 +533,17 @@ macro_rules! make_mir_visitor { ); } + TerminatorKind::TailCall { + func, + args, + fn_span: _, + } => { + self.visit_operand(func, location); + for arg in args { + self.visit_operand(arg, location); + } + }, + TerminatorKind::Assert { cond, expected: _, diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index a6c8d41e925f1..87885770ed1dd 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -864,6 +864,12 @@ rustc_queries! { cache_on_disk_if { true } } + /// Checks well-formedness of tail calls (`become f()`). + query thir_check_tail_calls(key: LocalDefId) -> Result<(), rustc_errors::ErrorGuaranteed> { + desc { |tcx| "tail-call-checking `{}`", tcx.def_path_str(key) } + cache_on_disk_if { true } + } + /// Returns the types assumed to be well formed while "inside" of the given item. /// /// Note that we've liberated the late bound regions of function signatures, so diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs index 813e109c41e14..0f3ad6d0151d1 100644 --- a/compiler/rustc_middle/src/thir.rs +++ b/compiler/rustc_middle/src/thir.rs @@ -410,6 +410,10 @@ pub enum ExprKind<'tcx> { Return { value: Option, }, + /// A `become` expression. + Become { + value: ExprId, + }, /// An inline `const` block, e.g. `const {}`. ConstBlock { did: DefId, diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index 5c7ec31cf93d3..9cc07677e0e36 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -100,6 +100,7 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp visitor.visit_expr(&visitor.thir()[value]) } } + Become { value } => visitor.visit_expr(&visitor.thir()[value]), ConstBlock { did: _, substs: _ } => {} Repeat { value, count: _ } => { visitor.visit_expr(&visitor.thir()[value]); diff --git a/compiler/rustc_mir_build/src/build/expr/as_place.rs b/compiler/rustc_mir_build/src/build/expr/as_place.rs index 7ec57add66b59..e56eea085e2e9 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_place.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_place.rs @@ -549,6 +549,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::Break { .. } | ExprKind::Continue { .. } | ExprKind::Return { .. } + | ExprKind::Become { .. } | ExprKind::Literal { .. } | ExprKind::NamedConst { .. } | ExprKind::NonHirLiteral { .. } diff --git a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs index 3742d640e3b58..27b1390dc4e4e 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs @@ -532,6 +532,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::Break { .. } | ExprKind::Continue { .. } | ExprKind::Return { .. } + | ExprKind::Become { .. } | ExprKind::InlineAsm { .. } | ExprKind::PlaceTypeAscription { .. } | ExprKind::ValueTypeAscription { .. } => { diff --git a/compiler/rustc_mir_build/src/build/expr/category.rs b/compiler/rustc_mir_build/src/build/expr/category.rs index d9aa461c19d40..2fe9cac637808 100644 --- a/compiler/rustc_mir_build/src/build/expr/category.rs +++ b/compiler/rustc_mir_build/src/build/expr/category.rs @@ -82,7 +82,8 @@ impl Category { | ExprKind::Block { .. } | ExprKind::Break { .. } | ExprKind::Continue { .. } - | ExprKind::Return { .. } => + | ExprKind::Return { .. } + | ExprKind::Become { .. } => // FIXME(#27840) these probably want their own // category, like "nonterminating" { diff --git a/compiler/rustc_mir_build/src/build/expr/into.rs b/compiler/rustc_mir_build/src/build/expr/into.rs index 29ff916d2cc9c..1e53643c6925e 100644 --- a/compiler/rustc_mir_build/src/build/expr/into.rs +++ b/compiler/rustc_mir_build/src/build/expr/into.rs @@ -489,7 +489,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block.unit() } - ExprKind::Continue { .. } | ExprKind::Break { .. } | ExprKind::Return { .. } => { + ExprKind::Continue { .. } + | ExprKind::Break { .. } + | ExprKind::Return { .. } + | ExprKind::Become { .. } => { unpack!(block = this.stmt_expr(block, expr, None)); // No assign, as these have type `!`. block.unit() diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index ea5aeb67d8576..981e7159e0c63 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -99,6 +99,39 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { BreakableTarget::Return, source_info, ), + ExprKind::Become { value } => { + let v = &this.thir[value]; + let ExprKind::Scope { region_scope, lint_level, value, .. } = v.kind + else { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") }; + let v = &this.thir[value]; + let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind + else { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") }; + + this.in_scope((region_scope, source_info), lint_level, |this| { + let fun = unpack!(block = this.as_local_operand(block, &this.thir[fun])); + let args: Vec<_> = args + .into_iter() + .copied() + .map(|arg| { + unpack!(block = this.as_local_call_operand(block, &this.thir[arg])) + }) + .collect(); + + this.record_operands_moved(&args); + + debug!("expr_into_dest: fn_span={:?}", fn_span); + + unpack!(block = this.break_for_tail_call(block)); + + this.cfg.terminate( + block, + source_info, + TerminatorKind::TailCall { func: fun, args, fn_span }, + ); + + this.cfg.start_new_block().unit() + }) + } _ => { assert!( statement_scope.is_some(), diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs index 7f0c1d53f729a..1956183a388b5 100644 --- a/compiler/rustc_mir_build/src/build/mod.rs +++ b/compiler/rustc_mir_build/src/build/mod.rs @@ -45,6 +45,10 @@ fn mir_build(tcx: TyCtxt<'_>, def: LocalDefId) -> Body<'_> { return construct_error(tcx, def, e); } + if let Err(err) = tcx.thir_check_tail_calls(def) { + return construct_error(tcx, def, err); + } + let body = match tcx.thir_body(def) { Err(error_reported) => construct_error(tcx, def, error_reported), Ok((thir, expr)) => { diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index 7c0fbc6f81c94..9ab78416acb53 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -721,6 +721,42 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.terminate(block, source_info, TerminatorKind::Resume); } + /// Sets up the drops for explict tail calls. + /// + /// Unlike other kinds of early exits, tail calls do not go through the drop tree. + /// Instead, all scheduled drops are immediately added to the CFG. + pub(crate) fn break_for_tail_call(&mut self, mut block: BasicBlock) -> BlockAnd<()> { + // the innermost scope contains only the destructors for the tail call arguments + // we only want to drop these in case of a panic, so we skip it + for scope in self.scopes.scopes[1..].iter().rev().skip(1) { + for drop in scope.drops.iter().rev() { + match drop.kind { + DropKind::Value => { + let target = self.cfg.start_new_block(); + let terminator = TerminatorKind::Drop { + target, + // The caller will handle this if needed. + unwind: UnwindAction::Terminate, + place: drop.local.into(), + replace: false, + }; + self.cfg.terminate(block, drop.source_info, terminator); + block = target; + } + DropKind::Storage => { + let stmt = Statement { + source_info: drop.source_info, + kind: StatementKind::StorageDead(drop.local), + }; + self.cfg.push(block, stmt); + } + } + } + } + + block.unit() + } + // Add a dummy `Assign` statement to the CFG, with the span for the source code's `continue` // statement. fn add_dummy_assignment(&mut self, span: Span, block: BasicBlock, source_info: SourceInfo) { @@ -1459,6 +1495,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind { | TerminatorKind::Resume | TerminatorKind::Terminate | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } | TerminatorKind::GeneratorDrop diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs new file mode 100644 index 0000000000000..bde25353f2b7d --- /dev/null +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -0,0 +1,382 @@ +use rustc_errors::Applicability; +use rustc_hir::def::DefKind; +use rustc_hir::LangItem; +use rustc_middle::thir::visit::{self, Visitor}; +use rustc_middle::thir::ExprId; +use rustc_middle::thir::{BodyTy, Expr, ExprKind, Thir}; +use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt}; +use rustc_span::def_id::DefId; +use rustc_span::def_id::LocalDefId; +use rustc_span::{ErrorGuaranteed, Span}; +use rustc_target::spec::abi::Abi; + +pub fn thir_check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), ErrorGuaranteed> { + let (thir, expr) = tcx.thir_body(def)?; + let thir = &thir.borrow(); + + // If `thir` is empty, a type error occurred, skip this body. + if thir.exprs.is_empty() { + return Ok(()); + } + + let is_closure = matches!(tcx.def_kind(def), DefKind::Closure); + let caller_ty = tcx.type_of(def).skip_binder(); + + let mut visitor = TailCallCkVisitor { + tcx, + thir, + found_errors: Ok(()), + param_env: tcx.param_env(def), + is_closure, + caller_ty, + }; + + visitor.visit_expr(&thir[expr]); + + visitor.found_errors +} + +struct TailCallCkVisitor<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + thir: &'a Thir<'tcx>, + param_env: ParamEnv<'tcx>, + /// Whatever the currently checked body is one of a closure + is_closure: bool, + /// The result of the checks, `Err(_)` if there was a problem with some + /// tail call, `Ok(())` if all of them were fine. + found_errors: Result<(), ErrorGuaranteed>, + /// Type of the caller function. + caller_ty: Ty<'tcx>, +} + +impl<'tcx> TailCallCkVisitor<'_, 'tcx> { + fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) { + if self.is_closure { + self.report_in_closure(expr); + return; + } + + let BodyTy::Fn(caller_sig) = self.thir.body_type + else { span_bug!(call.span, "`become` outside of functions should have been disallowed by hit_typeck") }; + + let ExprKind::Scope { value, .. } = call.kind + else { span_bug!(call.span, "expected scope, found: {call:?}") }; + let value = &self.thir[value]; + + if matches!( + value.kind, + ExprKind::Binary { .. } + | ExprKind::Unary { .. } + | ExprKind::AssignOp { .. } + | ExprKind::Index { .. } + ) { + self.report_builtin_op(call, expr); + return; + } + + let ExprKind::Call { ty, fun, ref args, from_hir_call, fn_span } = value.kind + else { + self.report_non_call(value, expr); + return + }; + + if !from_hir_call { + self.report_op(ty, args, fn_span, expr); + } + + // Closures in thir look something akin to + // `for<'a> extern "rust-call" fn(&'a [closure@...], ()) -> <[closure@...] as FnOnce<()>>::Output {<[closure@...] as Fn<()>>::call}` + // So we have to check for them in this weird way... + if let &ty::FnDef(did, substs) = ty.kind() { + let parent = self.tcx.parent(did); + let fn_ = self.tcx.require_lang_item(LangItem::Fn, Some(expr.span)); + let fn_once = self.tcx.require_lang_item(LangItem::FnOnce, Some(expr.span)); + let fn_mut = self.tcx.require_lang_item(LangItem::FnMut, Some(expr.span)); + if [fn_, fn_once, fn_mut].contains(&parent) { + if substs.first().and_then(|arg| arg.as_type()).is_some_and(|t| t.is_closure()) { + self.report_calling_closure( + &self.thir[fun], + substs[1].as_type().unwrap(), + expr, + ); + + // Tail calling is likely to cause unrelated errors (ABI, argument mismatches) + return; + } + }; + } + + // Erase regions since tail calls don't care about lifetimes + let callee_sig = + self.tcx.normalize_erasing_late_bound_regions(self.param_env, ty.fn_sig(self.tcx)); + + if caller_sig.abi != callee_sig.abi { + self.report_abi_mismatch(expr.span, caller_sig.abi, callee_sig.abi); + } + + if caller_sig.inputs_and_output != callee_sig.inputs_and_output { + if caller_sig.inputs() != callee_sig.inputs() { + self.report_arguments_mismatch(expr.span, caller_sig, callee_sig); + } + + if caller_sig.output() != callee_sig.output() { + span_bug!(expr.span, "hir typeck should have checked the return type already"); + } + } + + { + let caller_needs_location = self.needs_location(self.caller_ty); + let callee_needs_location = self.needs_location(ty); + + if caller_needs_location != callee_needs_location { + self.report_track_caller_mismatch(expr.span, caller_needs_location); + } + } + + if caller_sig.c_variadic { + self.report_c_variadic_caller(expr.span); + } + + if callee_sig.c_variadic { + self.report_c_variadic_callee(expr.span); + } + } + + /// Returns true if function of type `ty` needs location argument + /// (i.e. if a function is marked as `#[track_caller]`) + fn needs_location(&self, ty: Ty<'tcx>) -> bool { + if let &ty::FnDef(did, substs) = ty.kind() { + let instance = + ty::Instance::expect_resolve(self.tcx, ty::ParamEnv::reveal_all(), did, substs) + .polymorphize(self.tcx); + + instance.def.requires_caller_location(self.tcx) + } else { + false + } + } + + fn report_in_closure(&mut self, expr: &Expr<'_>) { + let err = self.tcx.sess.span_err(expr.span, "`become` is not allowed in closures"); + self.found_errors = Err(err); + } + + fn report_builtin_op(&mut self, value: &Expr<'_>, expr: &Expr<'_>) { + let err = self + .tcx + .sess + .struct_span_err(value.span, "`become` does not support operators") + .note("using `become` on a builtin operator is not useful") + .span_suggestion( + value.span.until(expr.span), + "try using `return` instead", + "return ", + Applicability::MachineApplicable, + ) + .emit(); + self.found_errors = Err(err); + } + + fn report_op(&mut self, fun_ty: Ty<'_>, args: &[ExprId], fn_span: Span, expr: &Expr<'_>) { + let mut err = self.tcx.sess.struct_span_err(fn_span, "`become` does not support operators"); + + if let &ty::FnDef(did, _substs) = fun_ty.kind() + && let parent = self.tcx.parent(did) + && matches!(self.tcx.def_kind(parent), DefKind::Trait) + && let Some(method) = op_trait_as_method_name(self.tcx, parent) + { + match args { + &[arg] => { + let arg = &self.thir[arg]; + + err.multipart_suggestion( + "try using the method directly", + vec![ + (fn_span.shrink_to_lo().until(arg.span), "(".to_owned()), + (arg.span.shrink_to_hi(), format!(").{method}()")), + ], + Applicability::MaybeIncorrect, + ); + } + &[lhs, rhs] => { + let lhs = &self.thir[lhs]; + let rhs = &self.thir[rhs]; + + err.multipart_suggestion( + "try using the method directly", + vec![ + (lhs.span.shrink_to_lo(), format!("(")), + (lhs.span.between(rhs.span), format!(").{method}(")), + (rhs.span.between(expr.span.shrink_to_hi()), ")".to_owned()), + ], + Applicability::MaybeIncorrect, + ); + } + _ => span_bug!(expr.span, "operator with more than 2 args? {args:?}"), + } + } + + self.found_errors = Err(err.emit()); + } + + fn report_non_call(&mut self, value: &Expr<'_>, expr: &Expr<'_>) { + let err = self + .tcx + .sess + .struct_span_err(value.span, "`become` requires a function call") + .span_note(value.span, "not a function call") + .span_suggestion( + value.span.until(expr.span), + "try using `return` instead", + "return ", + Applicability::MaybeIncorrect, + ) + .emit(); + self.found_errors = Err(err); + } + + fn report_calling_closure(&mut self, fun: &Expr<'_>, tupled_args: Ty<'_>, expr: &Expr<'_>) { + let underscored_args = match tupled_args.kind() { + ty::Tuple(tys) if tys.is_empty() => "".to_owned(), + ty::Tuple(tys) => std::iter::repeat("_, ").take(tys.len() - 1).chain(["_"]).collect(), + _ => "_".to_owned(), + }; + + let err = self + .tcx + .sess + .struct_span_err(expr.span, "tail calling closures directly is not allowed") + .multipart_suggestion( + "try casting the closure to a function pointer type", + vec![ + (fun.span.shrink_to_lo(), "(".to_owned()), + (fun.span.shrink_to_hi(), format!(" as fn({underscored_args}) -> _)")), + ], + Applicability::MaybeIncorrect, + ) + .emit(); + self.found_errors = Err(err); + } + + fn report_abi_mismatch(&mut self, sp: Span, caller_abi: Abi, callee_abi: Abi) { + let err = self + .tcx + .sess + .struct_span_err(sp, "mismatched function ABIs") + .note("`become` requires caller and callee to have the same ABI") + .note(format!("caller ABI is `{caller_abi}`, while callee ABI is `{callee_abi}`")) + .emit(); + self.found_errors = Err(err); + } + + fn report_arguments_mismatch( + &mut self, + sp: Span, + caller_sig: ty::FnSig<'_>, + callee_sig: ty::FnSig<'_>, + ) { + let err = self + .tcx + .sess + .struct_span_err(sp, "mismatched signatures") + .note("`become` requires caller and callee to have matching signatures") + .note(format!("caller signature: `{caller_sig}`")) + .note(format!("callee signature: `{callee_sig}`")) + .emit(); + self.found_errors = Err(err); + } + + fn report_track_caller_mismatch(&mut self, sp: Span, caller_needs_location: bool) { + let err = match caller_needs_location { + true => self + .tcx + .sess + .struct_span_err( + sp, + "a function marked with `#[track_caller]` cannot tail-call one that is not", + ) + .emit(), + false => self + .tcx + .sess + .struct_span_err( + sp, + "a function mot marked with `#[track_caller]` cannot tail-call one that is", + ) + .emit(), + }; + + self.found_errors = Err(err); + } + + fn report_c_variadic_caller(&mut self, sp: Span) { + let err = self + .tcx + .sess + // FIXME(explicit_tail_calls): highlight the `...` + .struct_span_err(sp, "tail-calls are not allowed in c-variadic functions") + .emit(); + + self.found_errors = Err(err); + } + + fn report_c_variadic_callee(&mut self, sp: Span) { + let err = self + .tcx + .sess + // FIXME(explicit_tail_calls): highlight the function or something... + .struct_span_err(sp, "c-variadic functions can't be tail-called") + .emit(); + + self.found_errors = Err(err); + } +} + +impl<'a, 'tcx> Visitor<'a, 'tcx> for TailCallCkVisitor<'a, 'tcx> { + fn thir(&self) -> &'a Thir<'tcx> { + &self.thir + } + + fn visit_expr(&mut self, expr: &Expr<'tcx>) { + if let ExprKind::Become { value } = expr.kind { + let call = &self.thir[value]; + self.check_tail_call(call, expr); + } + + visit::walk_expr(self, expr); + } +} + +fn op_trait_as_method_name(tcx: TyCtxt<'_>, trait_did: DefId) -> Option<&'static str> { + let trait_did = Some(trait_did); + let items = tcx.lang_items(); + let m = match () { + _ if trait_did == items.get(LangItem::Add) => "add", + _ if trait_did == items.get(LangItem::Sub) => "sub", + _ if trait_did == items.get(LangItem::Mul) => "mul", + _ if trait_did == items.get(LangItem::Div) => "div", + _ if trait_did == items.get(LangItem::Rem) => "rem", + _ if trait_did == items.get(LangItem::Neg) => "neg", + _ if trait_did == items.get(LangItem::Not) => "not", + _ if trait_did == items.get(LangItem::BitXor) => "bitxor", + _ if trait_did == items.get(LangItem::BitAnd) => "bitand", + _ if trait_did == items.get(LangItem::BitOr) => "bitor", + _ if trait_did == items.get(LangItem::Shl) => "shl", + _ if trait_did == items.get(LangItem::Shr) => "shr", + _ if trait_did == items.get(LangItem::AddAssign) => "add_assign", + _ if trait_did == items.get(LangItem::SubAssign) => "sub_assign", + _ if trait_did == items.get(LangItem::MulAssign) => "mul_assign", + _ if trait_did == items.get(LangItem::DivAssign) => "div_assign", + _ if trait_did == items.get(LangItem::RemAssign) => "rem_assign", + _ if trait_did == items.get(LangItem::BitXorAssign) => "bitxor_assign", + _ if trait_did == items.get(LangItem::BitAndAssign) => "bitand_assign", + _ if trait_did == items.get(LangItem::BitOrAssign) => "bitor_assign", + _ if trait_did == items.get(LangItem::ShlAssign) => "shl_assign", + _ if trait_did == items.get(LangItem::ShrAssign) => "shr_assign", + _ if trait_did == items.get(LangItem::Index) => "index", + _ if trait_did == items.get(LangItem::IndexMut) => "index_mut", + _ => return None, + }; + + Some(m) +} diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index 0506f2bf2385c..7227ec3d207ed 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -316,6 +316,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { | ExprKind::Closure { .. } | ExprKind::Continue { .. } | ExprKind::Return { .. } + | ExprKind::Become { .. } | ExprKind::Yield { .. } | ExprKind::Loop { .. } | ExprKind::Let { .. } diff --git a/compiler/rustc_mir_build/src/lib.rs b/compiler/rustc_mir_build/src/lib.rs index 0eaab9b57036c..5a8e757754757 100644 --- a/compiler/rustc_mir_build/src/lib.rs +++ b/compiler/rustc_mir_build/src/lib.rs @@ -17,6 +17,7 @@ extern crate tracing; extern crate rustc_middle; mod build; +mod check_tail_calls; mod check_unsafety; mod errors; mod lints; @@ -34,6 +35,7 @@ pub fn provide(providers: &mut Providers) { providers.lit_to_const = thir::constant::lit_to_const; providers.mir_built = build::mir_built; providers.thir_check_unsafety = check_unsafety::thir_check_unsafety; + providers.thir_check_tail_calls = check_tail_calls::thir_check_tail_calls; providers.thir_body = thir::cx::thir_body; providers.thir_tree = thir::print::thir_tree; providers.thir_flat = thir::print::thir_flat; diff --git a/compiler/rustc_mir_build/src/lints.rs b/compiler/rustc_mir_build/src/lints.rs index 8e41957af0eba..5af7b17daf829 100644 --- a/compiler/rustc_mir_build/src/lints.rs +++ b/compiler/rustc_mir_build/src/lints.rs @@ -112,6 +112,8 @@ impl<'mir, 'tcx> TriColorVisitor> for Search<'mir, 'tcx> { | TerminatorKind::GeneratorDrop | TerminatorKind::Resume | TerminatorKind::Return + // FIXME(explicit_tail_calls) Is this right?? + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive), diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index b20495d602e59..3d72bfd825ee7 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -694,7 +694,8 @@ impl<'tcx> Cx<'tcx> { ExprKind::Repeat { value: self.mirror_expr(v), count: *count } } - hir::ExprKind::Ret(ref v) => ExprKind::Return { value: v.map(|v| self.mirror_expr(v)) }, + hir::ExprKind::Ret(v) => ExprKind::Return { value: v.map(|v| self.mirror_expr(v)) }, + hir::ExprKind::Become(call) => ExprKind::Become { value: self.mirror_expr(call) }, hir::ExprKind::Break(dest, ref value) => match dest.target_id { Ok(target_id) => ExprKind::Break { label: region::Scope { id: target_id.local_id, data: region::ScopeData::Node }, diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index b2f2a64e29c8c..d07ca8773a5f0 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -421,6 +421,12 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { print_indented!(self, "}", depth_lvl); } + Become { value } => { + print_indented!(self, "Return {", depth_lvl); + print_indented!(self, "value:", depth_lvl + 1); + self.print_expr(*value, depth_lvl + 2); + print_indented!(self, "}", depth_lvl); + } ConstBlock { did, substs } => { print_indented!(self, "ConstBlock {", depth_lvl); print_indented!(self, format!("did: {:?}", did), depth_lvl + 1); diff --git a/compiler/rustc_mir_dataflow/src/framework/direction.rs b/compiler/rustc_mir_dataflow/src/framework/direction.rs index 0c379288a0962..8bfef37364549 100644 --- a/compiler/rustc_mir_dataflow/src/framework/direction.rs +++ b/compiler/rustc_mir_dataflow/src/framework/direction.rs @@ -527,6 +527,10 @@ impl Direction for Forward { } } + TailCall { .. } => { + // FIXME(explicit_tail_calls): is there anything sensible to do here? + } + InlineAsm { template: _, ref operands, diff --git a/compiler/rustc_mir_dataflow/src/framework/mod.rs b/compiler/rustc_mir_dataflow/src/framework/mod.rs index e5e9c1507ba8b..40b8dbb212f24 100644 --- a/compiler/rustc_mir_dataflow/src/framework/mod.rs +++ b/compiler/rustc_mir_dataflow/src/framework/mod.rs @@ -453,7 +453,7 @@ where /// building up a `GenKillSet` and then throwing it away. pub trait GenKill { /// Inserts `elem` into the state vector. - fn gen(&mut self, elem: T); + fn gen(&mut self, der: T); /// Removes `elem` from the state vector. fn kill(&mut self, elem: T); diff --git a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs index b88ed32b687f6..e70e7ca58f69b 100644 --- a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs +++ b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs @@ -136,6 +136,7 @@ where | TerminatorKind::InlineAsm { .. } | TerminatorKind::Resume | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } => {} diff --git a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs index 666c8d50a8a8c..51449496c1d59 100644 --- a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs @@ -281,6 +281,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> { | TerminatorKind::Goto { .. } | TerminatorKind::Resume | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Unreachable => {} } @@ -318,6 +319,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> { | TerminatorKind::Goto { .. } | TerminatorKind::Resume | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Unreachable => {} } diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index 096bc0acfccb9..28d2b594c8f4f 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -411,6 +411,12 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { self.gather_init(destination.as_ref(), InitKind::NonPanicPathOnly); } } + TerminatorKind::TailCall { ref func, ref args, .. } => { + self.gather_operand(func); + for arg in args { + self.gather_operand(arg); + } + } TerminatorKind::InlineAsm { template: _, ref operands, diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs index 5693e5a4a712a..1eeaa9548f393 100644 --- a/compiler/rustc_mir_dataflow/src/value_analysis.rs +++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs @@ -258,6 +258,9 @@ pub trait ValueAnalysis<'tcx> { // They would have an effect, but are not allowed in this phase. bug!("encountered disallowed terminator"); } + TerminatorKind::TailCall { .. } => { + // FIXME(explicit_tail_calls): determine if we need to do something here (probably not) + } TerminatorKind::Goto { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Resume diff --git a/compiler/rustc_mir_transform/src/check_unsafety.rs b/compiler/rustc_mir_transform/src/check_unsafety.rs index 70812761e88d6..38c3a55e22395 100644 --- a/compiler/rustc_mir_transform/src/check_unsafety.rs +++ b/compiler/rustc_mir_transform/src/check_unsafety.rs @@ -66,7 +66,7 @@ impl<'tcx> Visitor<'tcx> for UnsafetyChecker<'_, 'tcx> { // safe (at least as emitted during MIR construction) } - TerminatorKind::Call { ref func, .. } => { + TerminatorKind::Call { ref func, .. } | TerminatorKind::TailCall { ref func, .. } => { let func_ty = func.ty(self.body, self.tcx); let func_id = if let ty::FnDef(func_id, _) = func_ty.kind() { Some(func_id) } else { None }; diff --git a/compiler/rustc_mir_transform/src/const_prop.rs b/compiler/rustc_mir_transform/src/const_prop.rs index 1d43dbda0aae4..4e7b102818b4b 100644 --- a/compiler/rustc_mir_transform/src/const_prop.rs +++ b/compiler/rustc_mir_transform/src/const_prop.rs @@ -950,11 +950,12 @@ impl<'tcx> MutVisitor<'tcx> for ConstPropagator<'_, 'tcx> { | TerminatorKind::FalseEdge { .. } | TerminatorKind::FalseUnwind { .. } | TerminatorKind::InlineAsm { .. } => {} + // Every argument in our function calls have already been propagated in `visit_operand`. // // NOTE: because LLVM codegen gives slight performance regressions with it, so this is // gated on `mir_opt_level=3`. - TerminatorKind::Call { .. } => {} + TerminatorKind::Call { .. } | TerminatorKind::TailCall { .. } => {} } } diff --git a/compiler/rustc_mir_transform/src/const_prop_lint.rs b/compiler/rustc_mir_transform/src/const_prop_lint.rs index 759650fe4db3a..79ca5ddc89c47 100644 --- a/compiler/rustc_mir_transform/src/const_prop_lint.rs +++ b/compiler/rustc_mir_transform/src/const_prop_lint.rs @@ -681,6 +681,7 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> { | TerminatorKind::Resume | TerminatorKind::Terminate | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::Drop { .. } | TerminatorKind::Yield { .. } diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index ea1223fbca642..94f31b46c6aaf 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -123,6 +123,7 @@ impl CoverageGraph { match term.kind { TerminatorKind::Return { .. } + | TerminatorKind::TailCall { .. } | TerminatorKind::Terminate | TerminatorKind::Yield { .. } | TerminatorKind::SwitchInt { .. } => { diff --git a/compiler/rustc_mir_transform/src/coverage/spans.rs b/compiler/rustc_mir_transform/src/coverage/spans.rs index d27200419e2cb..f353b7da1a56a 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans.rs @@ -841,7 +841,7 @@ pub(super) fn filtered_statement_span(statement: &Statement<'_>) -> Option /// If the MIR `Terminator` has a span contributive to computing coverage spans, /// return it; otherwise return `None`. pub(super) fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option { - match terminator.kind { + match &terminator.kind { // These terminators have spans that don't positively contribute to computing a reasonable // span of actually executed source code. (For example, SwitchInt terminators extracted from // an `if condition { block }` has a span that includes the executed block, if true, @@ -856,7 +856,8 @@ pub(super) fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option None, // Call `func` operand can have a more specific span when part of a chain of calls - | TerminatorKind::Call { ref func, .. } => { + TerminatorKind::Call { func, .. } + | TerminatorKind::TailCall { func, .. } => { let mut span = terminator.source_info.span; if let mir::Operand::Constant(box constant) = func { if constant.span.lo() > span.lo() { diff --git a/compiler/rustc_mir_transform/src/dest_prop.rs b/compiler/rustc_mir_transform/src/dest_prop.rs index 78758e2db28ab..56b603e2fd6c4 100644 --- a/compiler/rustc_mir_transform/src/dest_prop.rs +++ b/compiler/rustc_mir_transform/src/dest_prop.rs @@ -618,6 +618,12 @@ impl WriteInfo { self.add_operand(arg); } } + TerminatorKind::TailCall { func, args, .. } => { + self.add_operand(func); + for arg in args { + self.add_operand(arg); + } + } TerminatorKind::InlineAsm { operands, .. } => { for asm_operand in operands { match asm_operand { diff --git a/compiler/rustc_mir_transform/src/generator.rs b/compiler/rustc_mir_transform/src/generator.rs index fe3f8ed047a70..b72209c1b74b5 100644 --- a/compiler/rustc_mir_transform/src/generator.rs +++ b/compiler/rustc_mir_transform/src/generator.rs @@ -1217,6 +1217,8 @@ fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool { // These may unwind. TerminatorKind::Drop { .. } | TerminatorKind::Call { .. } + // FIXME(explicit_tail_calls): can tail calls unwind actually?.. + | TerminatorKind::TailCall { .. } | TerminatorKind::InlineAsm { .. } | TerminatorKind::Assert { .. } => return true, } @@ -1716,6 +1718,7 @@ impl<'tcx> Visitor<'tcx> for EnsureGeneratorFieldAssignmentsNeverAlias<'_> { | TerminatorKind::Resume | TerminatorKind::Terminate | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::Drop { .. } | TerminatorKind::Assert { .. } diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index 5487b5987e001..758095de36f33 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -1078,6 +1078,9 @@ impl<'tcx> MutVisitor<'tcx> for Integrator<'_, 'tcx> { *target = self.map_block(*target); *unwind = self.map_unwind(*unwind); } + TerminatorKind::TailCall { .. } => { + // FIXME(explicit_tail_calls): figure out how exactly tail calls are inlined + } TerminatorKind::Call { ref mut target, ref mut unwind, .. } => { if let Some(ref mut tgt) = *target { *tgt = self.map_block(*tgt); diff --git a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs index 4941c9edce305..7188bdd6576b3 100644 --- a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs +++ b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs @@ -75,6 +75,7 @@ impl RemoveNoopLandingPads { | TerminatorKind::Terminate | TerminatorKind::Unreachable | TerminatorKind::Call { .. } + | TerminatorKind::TailCall { .. } | TerminatorKind::Assert { .. } | TerminatorKind::Drop { .. } | TerminatorKind::InlineAsm { .. } => false, diff --git a/compiler/rustc_mir_transform/src/separate_const_switch.rs b/compiler/rustc_mir_transform/src/separate_const_switch.rs index f35a5fb42768a..b47830d8ae81c 100644 --- a/compiler/rustc_mir_transform/src/separate_const_switch.rs +++ b/compiler/rustc_mir_transform/src/separate_const_switch.rs @@ -109,6 +109,7 @@ pub fn separate_const_switch(body: &mut Body<'_>) -> usize { TerminatorKind::Resume | TerminatorKind::Drop { .. } | TerminatorKind::Call { .. } + | TerminatorKind::TailCall { .. } | TerminatorKind::Assert { .. } | TerminatorKind::FalseUnwind { .. } | TerminatorKind::Yield { .. } @@ -172,6 +173,7 @@ pub fn separate_const_switch(body: &mut Body<'_>) -> usize { | TerminatorKind::FalseUnwind { .. } | TerminatorKind::Drop { .. } | TerminatorKind::Call { .. } + | TerminatorKind::TailCall { .. } | TerminatorKind::InlineAsm { .. } | TerminatorKind::Yield { .. } => { span_bug!( diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index f4ee7b7587587..ba104fbf2f83b 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -744,7 +744,8 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { let tcx = self.tcx; match terminator.kind { - mir::TerminatorKind::Call { ref func, .. } => { + mir::TerminatorKind::Call { ref func, .. } + | mir::TerminatorKind::TailCall { ref func, .. } => { let callee_ty = func.ty(self.body, tcx); let callee_ty = self.monomorphize(callee_ty); visit_fn_use(self.tcx, callee_ty, true, source, &mut self.output) diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index cea2a71c98821..296e3484b43b5 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1430,6 +1430,8 @@ impl<'a> Parser<'a> { self.parse_expr_yield() } else if self.is_do_yeet() { self.parse_expr_yeet() + } else if self.eat_keyword(kw::Become) { + self.parse_expr_become() } else if self.check_keyword(kw::Let) { self.parse_expr_let() } else if self.eat_keyword(kw::Underscore) { @@ -1746,6 +1748,16 @@ impl<'a> Parser<'a> { self.maybe_recover_from_bad_qpath(expr) } + /// Parse `"become" expr`, with `"become"` token already eaten. + fn parse_expr_become(&mut self) -> PResult<'a, P> { + let lo = self.prev_token.span; + let kind = ExprKind::Become(self.parse_expr()?); + let span = lo.to(self.prev_token.span); + self.sess.gated_spans.gate(sym::explicit_tail_calls, span); + let expr = self.mk_expr(span, kind); + self.maybe_recover_from_bad_qpath(expr) + } + /// Parse `"break" (('label (:? expr)?) | expr?)` with `"break"` token already eaten. /// If the label is followed immediately by a `:` token, the label and `:` are /// parsed as part of the expression (i.e. a labeled loop). The language team has diff --git a/compiler/rustc_passes/src/hir_stats.rs b/compiler/rustc_passes/src/hir_stats.rs index dc5e454074ded..6c748147abe07 100644 --- a/compiler/rustc_passes/src/hir_stats.rs +++ b/compiler/rustc_passes/src/hir_stats.rs @@ -302,8 +302,8 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { [ ConstBlock, Array, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, DropTemps, Let, If, Loop, Match, Closure, Block, Assign, AssignOp, Field, Index, - Path, AddrOf, Break, Continue, Ret, InlineAsm, OffsetOf, Struct, Repeat, Yield, - Err + Path, AddrOf, Break, Continue, Ret, Become, InlineAsm, OffsetOf, Struct, Repeat, + Yield, Err ] ); hir_visit::walk_expr(self, e) @@ -569,7 +569,8 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let, If, While, ForLoop, Loop, Match, Closure, Block, Async, Await, TryBlock, Assign, AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret, - InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, IncludedBytes, Err + InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, + Become, IncludedBytes, Err ] ); ast_visit::walk_expr(self, e) diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs index 63b1578d43fd7..18249b10c7dbd 100644 --- a/compiler/rustc_passes/src/liveness.rs +++ b/compiler/rustc_passes/src/liveness.rs @@ -463,6 +463,8 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> { | hir::ExprKind::Lit(_) | hir::ExprKind::ConstBlock(..) | hir::ExprKind::Ret(..) + // FIXME(explicit_tail_calls): this may or may not be wrong + | hir::ExprKind::Become(..) | hir::ExprKind::Block(..) | hir::ExprKind::Assign(..) | hir::ExprKind::AssignOp(..) @@ -967,6 +969,11 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { self.propagate_through_opt_expr(o_e.as_deref(), self.exit_ln) } + hir::ExprKind::Become(ref e) => { + // Ignore succ and subst exit_ln. + self.propagate_through_expr(e, self.exit_ln) + } + hir::ExprKind::Break(label, ref opt_expr) => { // Find which label this break jumps to let target = match label.target_id { @@ -1408,6 +1415,7 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) { | hir::ExprKind::DropTemps(..) | hir::ExprKind::Unary(..) | hir::ExprKind::Ret(..) + | hir::ExprKind::Become(..) | hir::ExprKind::Break(..) | hir::ExprKind::Continue(..) | hir::ExprKind::Lit(_) diff --git a/compiler/rustc_passes/src/naked_functions.rs b/compiler/rustc_passes/src/naked_functions.rs index a849d61edfeaa..769b389009b7e 100644 --- a/compiler/rustc_passes/src/naked_functions.rs +++ b/compiler/rustc_passes/src/naked_functions.rs @@ -204,6 +204,7 @@ impl<'tcx> CheckInlineAssembly<'tcx> { | ExprKind::Continue(..) | ExprKind::Ret(..) | ExprKind::OffsetOf(..) + | ExprKind::Become(..) | ExprKind::Struct(..) | ExprKind::Repeat(..) | ExprKind::Yield(..) => { diff --git a/compiler/rustc_smir/src/rustc_smir/mod.rs b/compiler/rustc_smir/src/rustc_smir/mod.rs index 6bd030b13d1ce..cbb3239d063d9 100644 --- a/compiler/rustc_smir/src/rustc_smir/mod.rs +++ b/compiler/rustc_smir/src/rustc_smir/mod.rs @@ -372,6 +372,7 @@ impl<'tcx> Stable for mir::Terminator<'tcx> { unwind: unwind.stable(), } } + TailCall { .. } => todo!(), Yield { .. } | GeneratorDrop | FalseEdge { .. } | FalseUnwind { .. } => unreachable!(), } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index c5ce2575fff06..8b0a7659cbdde 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -687,6 +687,7 @@ symbols! { expf32, expf64, explicit_generic_args_with_impl_trait, + explicit_tail_calls, export_name, expr, extended_key_value_attributes, diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs index ce77df0df5dcf..cb5029ec04502 100644 --- a/compiler/rustc_ty_utils/src/consts.rs +++ b/compiler/rustc_ty_utils/src/consts.rs @@ -240,7 +240,8 @@ fn recurse_build<'tcx>( ExprKind::Assign { .. } | ExprKind::AssignOp { .. } => { error(GenericConstantTooComplexSub::AssignNotSupported(node.span))? } - ExprKind::Closure { .. } | ExprKind::Return { .. } => { + // FIXME(explicit_tail_calls): maybe get `become` a new error + ExprKind::Closure { .. } | ExprKind::Return { .. } | ExprKind::Become { .. } => { error(GenericConstantTooComplexSub::ClosureAndReturnNotSupported(node.span))? } // let expressions imply control flow @@ -336,6 +337,7 @@ impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> { | thir::ExprKind::Break { .. } | thir::ExprKind::Continue { .. } | thir::ExprKind::Return { .. } + | thir::ExprKind::Become { .. } | thir::ExprKind::Array { .. } | thir::ExprKind::Tuple { .. } | thir::ExprKind::Adt(_) diff --git a/library/std/src/keyword_docs.rs b/library/std/src/keyword_docs.rs index eb46f4e54bb67..27dfda7480ee3 100644 --- a/library/std/src/keyword_docs.rs +++ b/library/std/src/keyword_docs.rs @@ -1228,6 +1228,69 @@ mod ref_keyword {} /// ``` mod return_keyword {} +#[doc(keyword = "become")] +// +/// Perform a tail-call of a function. +/// +/// A `become` transfers the execution flow to a function in such a way, that +/// returning from the callee returns to the caller of the current function: +/// +/// ``` +/// #![feature(explicit_tail_calls)] +/// +/// fn a() -> u32 { +/// become b(); +/// } +/// +/// fn b() -> u32 { +/// return 2; // this return directly returns to the main ---+ +/// } // | +/// // | +/// fn main() { // | +/// let res = a(); // <--------------------------------------+ +/// assert_eq!(res, 2); +/// } +/// ``` +/// +/// This is an optimization that allows function calls to not exhaust the stack. +/// This is most useful for (mutually) recursive algorithms, but may be used in +/// other cases too. +/// +/// It is guaranteed that the call will not cause unbounded stack growth if it +/// is part of a recursive cycle in the call graph. +/// +/// For example note that the functions `halt` and `halt_loop` below are +/// identical, they both do nothing, forever. However `stack_overflow` is +/// different from them, even though it is written almost identically to +/// `halt`, `stack_overflow` exhausts the stack and so causes a stack +/// overflow, instead of running forever. +/// +/// +/// ``` +/// #![feature(explicit_tail_calls)] +/// +/// # #[allow(unreachable_code)] +/// fn halt() -> ! { +/// become halt() +/// } +/// +/// fn halt_loop() -> ! { +/// loop {} +/// } +/// +/// # #[allow(unconditional_recursion)] +/// fn stack_overflow() -> ! { +/// stack_overflow() // implicit return +/// } +/// ``` +/// +/// Note that from the algorithmic standpoint loops and tail-calls are +/// interchangeable, you can always rewrite a loop to use tail-calls +/// instead and vice versa. They are, however, very different in the code +/// structure, so sometimes one approach can make more sense that the other. +#[cfg(not(bootstrap))] +mod become_keyword {} + #[doc(keyword = "self")] // /// The receiver of a method, or the current module. diff --git a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs index 5f1fdf00be8c3..10b5e1edf9250 100644 --- a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs +++ b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs @@ -206,6 +206,12 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec, main_loop_id: H NeverLoopResult::AlwaysBreak, ) }), + ExprKind::Become(e) => { + combine_seq( + never_loop_expr(e, ignore_ids, main_loop_id), + NeverLoopResult::AlwaysBreak, + ) + } ExprKind::InlineAsm(asm) => asm .operands .iter() diff --git a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 7945275393c04..93ef07d36aea7 100644 --- a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -329,6 +329,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> { ExprKind::Field(..) | ExprKind::Index(..) | ExprKind::Ret(..) | + ExprKind::Become(..) | ExprKind::Repeat(..) | ExprKind::Yield(..) => walk_expr(self, ex), ExprKind::AddrOf(_, _, _) | diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs index 3c2bf5abab2b5..6b51974d739af 100644 --- a/src/tools/clippy/clippy_lints/src/utils/author.rs +++ b/src/tools/clippy/clippy_lints/src/utils/author.rs @@ -559,6 +559,11 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { kind!("Ret({value})"); value.if_some(|e| self.expr(e)); }, + ExprKind::Become(value) => { + bind!(self, value); + kind!("Become({value})"); + self.expr(value); + }, ExprKind::InlineAsm(_) => { kind!("InlineAsm(_)"); out!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment"); diff --git a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs index 3df40942e7b5a..0d34b8c6bf67f 100644 --- a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs +++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs @@ -152,6 +152,10 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS self.eagerness |= NoChange; return; }, + ExprKind::Become(..) => { + self.eagerness = ForceNoChange; + return; + } ExprKind::Path(ref path) => { if res_has_significant_drop(self.cx.qpath_res(path, e.hir_id), self.cx, e) { self.eagerness = ForceNoChange; diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs index a49246a783272..3e646233d539d 100644 --- a/src/tools/clippy/clippy_utils/src/hir_utils.rs +++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs @@ -845,6 +845,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_expr(e); } }, + ExprKind::Become(f) => { + self.hash_expr(f); + } ExprKind::Path(ref qpath) => { self.hash_qpath(qpath); }, diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index c0d2c835d63d4..66b21c062ca73 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -314,15 +314,8 @@ fn check_terminator<'tcx>( Err((span, "const fn generators are unstable".into())) }, - TerminatorKind::Call { - func, - args, - from_hir_call: _, - destination: _, - target: _, - unwind: _, - fn_span: _, - } => { + TerminatorKind::Call { func, args, .. } + | TerminatorKind::TailCall { func, args, .. } => { let fn_ty = func.ty(body, tcx); if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { if !is_const_fn(tcx, fn_def_id, msrv) { diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index f477524eec5cc..b38b9553558c8 100644 --- a/src/tools/clippy/clippy_utils/src/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -147,6 +147,7 @@ impl<'a> Sugg<'a> { | hir::ExprKind::Path(..) | hir::ExprKind::Repeat(..) | hir::ExprKind::Ret(..) + | hir::ExprKind::Become(..) | hir::ExprKind::Struct(..) | hir::ExprKind::Tup(..) | hir::ExprKind::Err(_) => Sugg::NonParen(get_snippet(expr.span)), @@ -211,6 +212,7 @@ impl<'a> Sugg<'a> { | ast::ExprKind::Path(..) | ast::ExprKind::Repeat(..) | ast::ExprKind::Ret(..) + | ast::ExprKind::Become(..) | ast::ExprKind::Yeet(..) | ast::ExprKind::FormatArgs(..) | ast::ExprKind::Struct(..) diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs index 5dcd71cef127e..8dafa723afa00 100644 --- a/src/tools/clippy/clippy_utils/src/visitors.rs +++ b/src/tools/clippy/clippy_utils/src/visitors.rs @@ -651,6 +651,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>( // Either drops temporaries, jumps out of the current expression, or has no sub expression. ExprKind::DropTemps(_) | ExprKind::Ret(_) + | ExprKind::Become(_) | ExprKind::Break(..) | ExprKind::Yield(..) | ExprKind::Block(..) diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index 5dc628adb0c6f..44aaddd8cb27f 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -232,6 +232,7 @@ pub(crate) fn format_expr( ast::ExprKind::Ret(Some(ref expr)) => { rewrite_unary_prefix(context, "return ", &**expr, shape) } + ast::ExprKind::Become(ref expr) => rewrite_unary_prefix(context, "become ", &**expr, shape), ast::ExprKind::Yeet(None) => Some("do yeet".to_owned()), ast::ExprKind::Yeet(Some(ref expr)) => { rewrite_unary_prefix(context, "do yeet ", &**expr, shape) diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs index ca1716574071b..890a05b8c8259 100644 --- a/src/tools/rustfmt/src/utils.rs +++ b/src/tools/rustfmt/src/utils.rs @@ -505,6 +505,7 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr | ast::ExprKind::Range(..) | ast::ExprKind::Repeat(..) | ast::ExprKind::Ret(..) + | ast::ExprKind::Become(..) | ast::ExprKind::Yeet(..) | ast::ExprKind::Tup(..) | ast::ExprKind::Type(..) diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index 55bf38110a6d5..3ed587caadc6b 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -11,7 +11,7 @@ use std::path::{Path, PathBuf}; const ENTRY_LIMIT: usize = 900; // FIXME: The following limits should be reduced eventually. const ISSUES_ENTRY_LIMIT: usize = 1896; -const ROOT_ENTRY_LIMIT: usize = 870; +const ROOT_ENTRY_LIMIT: usize = 871; const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[ "rs", // test source files diff --git a/tests/codegen/explicit-tail-calls.rs b/tests/codegen/explicit-tail-calls.rs new file mode 100644 index 0000000000000..e1eb5e1bcf4c8 --- /dev/null +++ b/tests/codegen/explicit-tail-calls.rs @@ -0,0 +1,107 @@ +// compile-flags: -C no-prepopulate-passes +// min-llvm-version: 15.0 (for opaque pointers) +#![crate_type = "lib"] +#![feature(explicit_tail_calls)] +#![feature(c_variadic)] + +/// Something that is likely to be passed indirectly +#[repr(C)] +pub struct IndirectProbably { + _0: u8, + _1: u32, + _2: u64, + _3: u8, + _4: [u32; 8], + _5: u8, + _6: u128, +} + + +#[no_mangle] +// CHECK-LABEL: @simple_f( +pub fn simple_f() -> u32 { + // CHECK: %0 = musttail call noundef i32 @simple_g() + // CHECK: ret i32 %0 + become simple_g(); +} + +#[no_mangle] +fn simple_g() -> u32 { + 0 +} + + +#[no_mangle] +// CHECK-LABEL: @unit_f( +fn unit_f() { + // CHECK: musttail call void @unit_g() + // CHECK: ret void + become unit_g(); +} + +#[no_mangle] +fn unit_g() {} + + +#[no_mangle] +// CHECK-LABEL: @indirect_f( +fn indirect_f() -> IndirectProbably { + // CHECK: musttail call void @indirect_g(ptr noalias nocapture noundef sret(%IndirectProbably) dereferenceable(80) %0) + // CHECK: ret void + become indirect_g(); +} + +#[no_mangle] +fn indirect_g() -> IndirectProbably { + todo!() +} + + +#[no_mangle] +// CHECK-LABEL: @pair_f( +pub fn pair_f() -> (u32, u8) { + // CHECK: %0 = musttail call { i32, i8 } @pair_g() + // CHECK: ret { i32, i8 } %0 + become pair_g() +} + +#[no_mangle] +fn pair_g() -> (u32, u8) { + (1, 2) +} + + +#[no_mangle] +// CHECK-LABEL: @extern_c_f(i32 noundef %x) +pub extern "C" fn extern_c_f(x: u32) -> u8 { + unsafe { + // CHECK: %0 = musttail call noundef i8 @extern_c_g(i32 noundef %x) + // CHECK: ret i8 %0 + become extern_c_g(x); + } +} + +extern "C" { + fn extern_c_g(x: u32) -> u8; +} + + +#[no_mangle] +/// Does `src + dst` in a recursive way +// CHECK-LABEL: @flow( +fn flow(src: u64, dst: u64) -> u64 { + match src { + 0 => dst, + // CHECK: %1 = musttail call noundef i64 @flow( + // CHECK: ret i64 %1 + _ => become flow(src - 1, dst + 1), + } +} + +#[no_mangle] +// CHECK-LABEL: @halt( +pub fn halt() -> ! { + // CHECK: musttail call void @halt() + // CHECK: ret void + become halt(); +} diff --git a/tests/ui/error-codes/E0572.stderr b/tests/ui/error-codes/E0572.stderr index 36619f8dee4cc..2d749580e240b 100644 --- a/tests/ui/error-codes/E0572.stderr +++ b/tests/ui/error-codes/E0572.stderr @@ -1,4 +1,4 @@ -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/E0572.rs:1:18 | LL | const FOO: u32 = return 0; diff --git a/tests/ui/explicit-tail-calls/become-c-variadic.rs b/tests/ui/explicit-tail-calls/become-c-variadic.rs new file mode 100644 index 0000000000000..659c3906976ef --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-c-variadic.rs @@ -0,0 +1,20 @@ +#![feature(explicit_tail_calls)] +#![feature(c_variadic)] + +pub unsafe extern "C" fn c_variadic_f(x: u8, mut args: ...) { + become c_variadic_g(x) + //~^ error: tail-calls are not allowed in c-variadic functions + //~| error: c-variadic functions can't be tail-called +} + +pub unsafe extern "C" fn c_variadic_g(x: u8, mut args: ...) { + become normal(x) + //~^ error: tail-calls are not allowed in c-variadic functions +} + +unsafe extern "C" fn normal(x: u8) { + become c_variadic_f(x) + //~^ error: c-variadic functions can't be tail-called +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/become-c-variadic.stderr b/tests/ui/explicit-tail-calls/become-c-variadic.stderr new file mode 100644 index 0000000000000..ce1fb9170961f --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-c-variadic.stderr @@ -0,0 +1,26 @@ +error: tail-calls are not allowed in c-variadic functions + --> $DIR/become-c-variadic.rs:5:5 + | +LL | become c_variadic_g(x) + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: c-variadic functions can't be tail-called + --> $DIR/become-c-variadic.rs:5:5 + | +LL | become c_variadic_g(x) + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: tail-calls are not allowed in c-variadic functions + --> $DIR/become-c-variadic.rs:11:5 + | +LL | become normal(x) + | ^^^^^^^^^^^^^^^^ + +error: c-variadic functions can't be tail-called + --> $DIR/become-c-variadic.rs:16:5 + | +LL | become c_variadic_f(x) + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/explicit-tail-calls/become-macro.rs b/tests/ui/explicit-tail-calls/become-macro.rs new file mode 100644 index 0000000000000..79498bbbecced --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-macro.rs @@ -0,0 +1,12 @@ +// run-pass +#![feature(explicit_tail_calls, decl_macro)] + +macro call($f:expr $(, $args:expr)* $(,)?) { + ($f)($($args),*) +} + +fn main() { + become call!(f); +} + +fn f() {} diff --git a/tests/ui/explicit-tail-calls/become-operator.fixed b/tests/ui/explicit-tail-calls/become-operator.fixed new file mode 100644 index 0000000000000..c93d45884d095 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-operator.fixed @@ -0,0 +1,41 @@ +// run-rustfix +#![feature(explicit_tail_calls)] +#![allow(unused)] +use std::num::Wrapping; +use std::ops::{Not, Add, BitXorAssign}; + +// built-ins and overloaded operators are handled differently + +fn f(a: u64, b: u64) -> u64 { + return a + b; //~ error: `become` does not support operators +} + +fn g(a: String, b: &str) -> String { + become (a).add(b); //~ error: `become` does not support operators +} + +fn h(x: u64) -> u64 { + return !x; //~ error: `become` does not support operators +} + +fn i_do_not_know_any_more_letters(x: Wrapping) -> Wrapping { + become (x).not(); //~ error: `become` does not support operators +} + +fn builtin_index(x: &[u8], i: usize) -> u8 { + return x[i] //~ error: `become` does not support operators +} + +// FIXME(explicit_tail_calls): overloaded index is represented like `[&]*x.index(i)`, +// and so need additional handling + +fn a(a: &mut u8, _: u8) { + return *a ^= 1; //~ error: `become` does not support operators +} + +fn b(b: &mut Wrapping, _: u8) { + become (*b).bitxor_assign(1); //~ error: `become` does not support operators +} + + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/become-operator.rs b/tests/ui/explicit-tail-calls/become-operator.rs new file mode 100644 index 0000000000000..c0851d7b270e8 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-operator.rs @@ -0,0 +1,41 @@ +// run-rustfix +#![feature(explicit_tail_calls)] +#![allow(unused)] +use std::num::Wrapping; +use std::ops::{Not, Add, BitXorAssign}; + +// built-ins and overloaded operators are handled differently + +fn f(a: u64, b: u64) -> u64 { + become a + b; //~ error: `become` does not support operators +} + +fn g(a: String, b: &str) -> String { + become a + b; //~ error: `become` does not support operators +} + +fn h(x: u64) -> u64 { + become !x; //~ error: `become` does not support operators +} + +fn i_do_not_know_any_more_letters(x: Wrapping) -> Wrapping { + become !x; //~ error: `become` does not support operators +} + +fn builtin_index(x: &[u8], i: usize) -> u8 { + become x[i] //~ error: `become` does not support operators +} + +// FIXME(explicit_tail_calls): overloaded index is represented like `[&]*x.index(i)`, +// and so need additional handling + +fn a(a: &mut u8, _: u8) { + become *a ^= 1; //~ error: `become` does not support operators +} + +fn b(b: &mut Wrapping, _: u8) { + become *b ^= 1; //~ error: `become` does not support operators +} + + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/become-operator.stderr b/tests/ui/explicit-tail-calls/become-operator.stderr new file mode 100644 index 0000000000000..75e2acbac65b0 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-operator.stderr @@ -0,0 +1,75 @@ +error: `become` does not support operators + --> $DIR/become-operator.rs:10:12 + | +LL | become a + b; + | -------^^^^^ + | | + | help: try using `return` instead: `return` + | + = note: using `become` on a builtin operator is not useful + +error: `become` does not support operators + --> $DIR/become-operator.rs:14:12 + | +LL | become a + b; + | ^^^^^ + | +help: try using the method directly + | +LL | become (a).add(b); + | + ~~~~~~ + + +error: `become` does not support operators + --> $DIR/become-operator.rs:18:12 + | +LL | become !x; + | -------^^ + | | + | help: try using `return` instead: `return` + | + = note: using `become` on a builtin operator is not useful + +error: `become` does not support operators + --> $DIR/become-operator.rs:22:12 + | +LL | become !x; + | ^^ + | +help: try using the method directly + | +LL | become (x).not(); + | ~ +++++++ + +error: `become` does not support operators + --> $DIR/become-operator.rs:26:12 + | +LL | become x[i] + | -------^^^^ + | | + | help: try using `return` instead: `return` + | + = note: using `become` on a builtin operator is not useful + +error: `become` does not support operators + --> $DIR/become-operator.rs:33:12 + | +LL | become *a ^= 1; + | -------^^^^^^^ + | | + | help: try using `return` instead: `return` + | + = note: using `become` on a builtin operator is not useful + +error: `become` does not support operators + --> $DIR/become-operator.rs:37:12 + | +LL | become *b ^= 1; + | ^^^^^^^ + | +help: try using the method directly + | +LL | become (*b).bitxor_assign(1); + | + ~~~~~~~~~~~~~~~~ + + +error: aborting due to 7 previous errors + diff --git a/tests/ui/explicit-tail-calls/become-outside.array.stderr b/tests/ui/explicit-tail-calls/become-outside.array.stderr new file mode 100644 index 0000000000000..acbff8b0db63b --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-outside.array.stderr @@ -0,0 +1,9 @@ +error[E0572]: `become` statement outside of function body + --> $DIR/become-outside.rs:10:17 + | +LL | struct Bad([(); become f()]); + | ^^^^^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0572`. diff --git a/tests/ui/explicit-tail-calls/become-outside.constant.stderr b/tests/ui/explicit-tail-calls/become-outside.constant.stderr new file mode 100644 index 0000000000000..4c4c11bf9cca9 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-outside.constant.stderr @@ -0,0 +1,9 @@ +error[E0572]: `become` statement outside of function body + --> $DIR/become-outside.rs:6:5 + | +LL | become f(); + | ^^^^^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0572`. diff --git a/tests/ui/explicit-tail-calls/become-outside.rs b/tests/ui/explicit-tail-calls/become-outside.rs new file mode 100644 index 0000000000000..7967a63e2a5d0 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-outside.rs @@ -0,0 +1,14 @@ +// revisions: constant array +#![feature(explicit_tail_calls)] + +#[cfg(constant)] +const _: () = { + become f(); //[constant]~ error: `become` statement outside of function body +}; + +#[cfg(array)] +struct Bad([(); become f()]); //[array]~ error: `become` statement outside of function body + +fn f() {} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/become-uncallable.fixed b/tests/ui/explicit-tail-calls/become-uncallable.fixed new file mode 100644 index 0000000000000..e0c2b2caff6a8 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-uncallable.fixed @@ -0,0 +1,17 @@ +// run-rustfix +#![feature(explicit_tail_calls)] +#![allow(unused)] + +fn f() -> u64 { + return 1; //~ error: `become` requires a function call +} + +fn g() { + return { h() }; //~ error: `become` requires a function call +} + +fn h() { + return *&g(); //~ error: `become` requires a function call +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/become-uncallable.rs b/tests/ui/explicit-tail-calls/become-uncallable.rs new file mode 100644 index 0000000000000..c18f2b7a070ed --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-uncallable.rs @@ -0,0 +1,17 @@ +// run-rustfix +#![feature(explicit_tail_calls)] +#![allow(unused)] + +fn f() -> u64 { + become 1; //~ error: `become` requires a function call +} + +fn g() { + become { h() }; //~ error: `become` requires a function call +} + +fn h() { + become *&g(); //~ error: `become` requires a function call +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/become-uncallable.stderr b/tests/ui/explicit-tail-calls/become-uncallable.stderr new file mode 100644 index 0000000000000..16dbd1ab81155 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-uncallable.stderr @@ -0,0 +1,44 @@ +error: `become` requires a function call + --> $DIR/become-uncallable.rs:6:12 + | +LL | become 1; + | -------^ + | | + | help: try using `return` instead: `return` + | +note: not a function call + --> $DIR/become-uncallable.rs:6:12 + | +LL | become 1; + | ^ + +error: `become` requires a function call + --> $DIR/become-uncallable.rs:10:12 + | +LL | become { h() }; + | -------^^^^^^^ + | | + | help: try using `return` instead: `return` + | +note: not a function call + --> $DIR/become-uncallable.rs:10:12 + | +LL | become { h() }; + | ^^^^^^^ + +error: `become` requires a function call + --> $DIR/become-uncallable.rs:14:12 + | +LL | become *&g(); + | -------^^^^^ + | | + | help: try using `return` instead: `return` + | +note: not a function call + --> $DIR/become-uncallable.rs:14:12 + | +LL | become *&g(); + | ^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/explicit-tail-calls/caller-location-mismatch.rs b/tests/ui/explicit-tail-calls/caller-location-mismatch.rs new file mode 100644 index 0000000000000..8b54e0c72cc5b --- /dev/null +++ b/tests/ui/explicit-tail-calls/caller-location-mismatch.rs @@ -0,0 +1,28 @@ +#![feature(explicit_tail_calls)] +use std::panic::Location; + +fn get_some_location1() -> &'static Location<'static> { + #[track_caller] + fn inner() -> &'static Location<'static> { + become Location::caller() + } + + become inner() + //~^ error: a function mot marked with `#[track_caller]` cannot tail-call one that is +} + +#[track_caller] +fn get_some_location2() -> &'static Location<'static> { + fn inner() -> &'static Location<'static> { + become Location::caller() + //~^ error: a function mot marked with `#[track_caller]` cannot tail-call one that is + } + + become inner() + //~^ error: a function marked with `#[track_caller]` cannot tail-call one that is not +} + +fn main() { + get_some_location1(); + get_some_location2(); +} diff --git a/tests/ui/explicit-tail-calls/caller-location-mismatch.stderr b/tests/ui/explicit-tail-calls/caller-location-mismatch.stderr new file mode 100644 index 0000000000000..9e133de773590 --- /dev/null +++ b/tests/ui/explicit-tail-calls/caller-location-mismatch.stderr @@ -0,0 +1,20 @@ +error: a function mot marked with `#[track_caller]` cannot tail-call one that is + --> $DIR/caller-location-mismatch.rs:10:5 + | +LL | become inner() + | ^^^^^^^^^^^^^^ + +error: a function marked with `#[track_caller]` cannot tail-call one that is not + --> $DIR/caller-location-mismatch.rs:21:5 + | +LL | become inner() + | ^^^^^^^^^^^^^^ + +error: a function mot marked with `#[track_caller]` cannot tail-call one that is + --> $DIR/caller-location-mismatch.rs:17:9 + | +LL | become Location::caller() + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/explicit-tail-calls/caller-location.rs b/tests/ui/explicit-tail-calls/caller-location.rs new file mode 100644 index 0000000000000..4f25c8dbdf330 --- /dev/null +++ b/tests/ui/explicit-tail-calls/caller-location.rs @@ -0,0 +1,18 @@ +// run-pass +#![feature(explicit_tail_calls)] +use std::panic::Location; + +fn main() { + assert_eq!(get_caller_location().line(), 6); + assert_eq!(get_caller_location().line(), 7); +} + +#[track_caller] +fn get_caller_location() -> &'static Location<'static> { + #[track_caller] + fn inner() -> &'static Location<'static> { + become Location::caller() + } + + become inner() +} diff --git a/tests/ui/explicit-tail-calls/closure.fixed b/tests/ui/explicit-tail-calls/closure.fixed new file mode 100644 index 0000000000000..07d469f1343fc --- /dev/null +++ b/tests/ui/explicit-tail-calls/closure.fixed @@ -0,0 +1,30 @@ +// run-rustfix +#![feature(explicit_tail_calls)] + +fn a() { + become ((|| ()) as fn() -> _)(); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn aa((): ()) { + become ((|()| ()) as fn(_) -> _)(()); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn aaa((): (), _: i32) { + become ((|(), _| ()) as fn(_, _) -> _)((), 1); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn v((): (), ((), ()): ((), ())) -> (((), ()), ()) { + let f = |(), ((), ())| (((), ()), ()); + become (f as fn(_, _) -> _)((), ((), ())); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn main() { + a(); + aa(()); + aaa((), 1); + v((), ((), ())); +} diff --git a/tests/ui/explicit-tail-calls/closure.rs b/tests/ui/explicit-tail-calls/closure.rs new file mode 100644 index 0000000000000..2a570d554ff20 --- /dev/null +++ b/tests/ui/explicit-tail-calls/closure.rs @@ -0,0 +1,30 @@ +// run-rustfix +#![feature(explicit_tail_calls)] + +fn a() { + become (|| ())(); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn aa((): ()) { + become (|()| ())(()); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn aaa((): (), _: i32) { + become (|(), _| ())((), 1); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn v((): (), ((), ()): ((), ())) -> (((), ()), ()) { + let f = |(), ((), ())| (((), ()), ()); + become f((), ((), ())); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn main() { + a(); + aa(()); + aaa((), 1); + v((), ((), ())); +} diff --git a/tests/ui/explicit-tail-calls/closure.stderr b/tests/ui/explicit-tail-calls/closure.stderr new file mode 100644 index 0000000000000..46cf312877549 --- /dev/null +++ b/tests/ui/explicit-tail-calls/closure.stderr @@ -0,0 +1,46 @@ +error: tail calling closures directly is not allowed + --> $DIR/closure.rs:5:5 + | +LL | become (|| ())(); + | ^^^^^^^^^^^^^^^^ + | +help: try casting the closure to a function pointer type + | +LL | become ((|| ()) as fn() -> _)(); + | + +++++++++++++ + +error: tail calling closures directly is not allowed + --> $DIR/closure.rs:10:5 + | +LL | become (|()| ())(()); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try casting the closure to a function pointer type + | +LL | become ((|()| ()) as fn(_) -> _)(()); + | + ++++++++++++++ + +error: tail calling closures directly is not allowed + --> $DIR/closure.rs:15:5 + | +LL | become (|(), _| ())((), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try casting the closure to a function pointer type + | +LL | become ((|(), _| ()) as fn(_, _) -> _)((), 1); + | + +++++++++++++++++ + +error: tail calling closures directly is not allowed + --> $DIR/closure.rs:21:5 + | +LL | become f((), ((), ())); + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: try casting the closure to a function pointer type + | +LL | become (f as fn(_, _) -> _)((), ((), ())); + | + +++++++++++++++++ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/explicit-tail-calls/constck.rs b/tests/ui/explicit-tail-calls/constck.rs new file mode 100644 index 0000000000000..ada1a5052308c --- /dev/null +++ b/tests/ui/explicit-tail-calls/constck.rs @@ -0,0 +1,21 @@ +#![feature(explicit_tail_calls)] + +const fn f() { + if false { + become not_const(); + //~^ error: cannot call non-const fn `not_const` in constant functions + } +} + +const fn g((): ()) { + if false { + become yes_const(not_const()); + //~^ error: cannot call non-const fn `not_const` in constant functions + } +} + +fn not_const() {} + +const fn yes_const((): ()) {} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/constck.stderr b/tests/ui/explicit-tail-calls/constck.stderr new file mode 100644 index 0000000000000..455e431e10fae --- /dev/null +++ b/tests/ui/explicit-tail-calls/constck.stderr @@ -0,0 +1,19 @@ +error[E0015]: cannot call non-const fn `not_const` in constant functions + --> $DIR/constck.rs:5:16 + | +LL | become not_const(); + | ^^^^^^^^^^^ + | + = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants + +error[E0015]: cannot call non-const fn `not_const` in constant functions + --> $DIR/constck.rs:12:26 + | +LL | become yes_const(not_const()); + | ^^^^^^^^^^^ + | + = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0015`. diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs new file mode 100644 index 0000000000000..047bfd8789777 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs @@ -0,0 +1,13 @@ +#![feature(explicit_tail_calls)] + +pub const fn test(_: &Type) { + const fn takes_borrow(_: &Type) {} + + let local = Type; + become takes_borrow(&local); + //~^ error: `local` does not live long enough +} + +struct Type; + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr new file mode 100644 index 0000000000000..e92df550d7d74 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr @@ -0,0 +1,14 @@ +error[E0597]: `local` does not live long enough + --> $DIR/ctfe-arg-bad-borrow.rs:7:25 + | +LL | let local = Type; + | ----- binding `local` declared here +LL | become takes_borrow(&local); + | ^^^^^^ borrowed value does not live long enough +LL | +LL | } + | - `local` dropped here while still borrowed + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs b/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs new file mode 100644 index 0000000000000..325ed4ec21670 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs @@ -0,0 +1,12 @@ +// check-pass +#![feature(explicit_tail_calls)] + +pub const fn test(x: &Type) { + const fn takes_borrow(_: &Type) {} + + become takes_borrow(x); +} + +pub struct Type; + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-move.rs b/tests/ui/explicit-tail-calls/ctfe-arg-move.rs new file mode 100644 index 0000000000000..8520e765349f5 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-move.rs @@ -0,0 +1,12 @@ +// check-pass +#![feature(explicit_tail_calls)] + +pub const fn test(s: String) -> String { + const fn takes_string(s: String) -> String { s } + + become takes_string(s); +} + +struct Type; + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs new file mode 100644 index 0000000000000..3f4291e9e171b --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs @@ -0,0 +1,42 @@ +// run-pass +#![feature(explicit_tail_calls)] + +/// A very unnecessarily complicated "implementation" of the callatz conjecture. +/// Returns the number of steps to reach `1`. +/// +/// This is just a test for tail calls, which involves multiple functions calling each other. +/// +/// Panics if `x == 0`. +const fn collatz(x: u32) -> u32 { + assert!(x > 0); + + const fn switch(x: u32, steps: u32) -> u32 { + match x { + 1 => steps, + _ if x & 1 == 0 => become div2(x, steps + 1), + _ => become mul3plus1(x, steps + 1), + } + } + + const fn div2(x: u32, steps: u32) -> u32 { + become switch(x >> 1, steps) + } + + const fn mul3plus1(x: u32, steps: u32) -> u32 { + become switch(3*x + 1, steps) + } + + switch(x, 0) +} + +const ASSERTS: () = { + assert!(collatz(1) == 0); + assert!(collatz(2) == 1); + assert!(collatz(3) == 7); + assert!(collatz(4) == 2); + assert!(collatz(6171) == 261); +}; + +fn main() { + let _ = ASSERTS; +} diff --git a/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr new file mode 100644 index 0000000000000..877f1a866276f --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr @@ -0,0 +1,36 @@ +error[E0080]: evaluation of constant value failed + --> $DIR/ctfe-id-unlimited.rs:16:42 + | +LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1), + | ^^^^^^^^^^^^^^^^^^^^^ reached the configured maximum number of stack frames + | +note: inside `inner` + --> $DIR/ctfe-id-unlimited.rs:16:42 + | +LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1), + | ^^^^^^^^^^^^^^^^^^^^^ +note: [... 125 additional calls inside `inner` ...] + --> $DIR/ctfe-id-unlimited.rs:16:42 + | +LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1), + | ^^^^^^^^^^^^^^^^^^^^^ +note: inside `rec_id` + --> $DIR/ctfe-id-unlimited.rs:21:5 + | +LL | inner(0, n) + | ^^^^^^^^^^^ +note: inside `ID_ED` + --> $DIR/ctfe-id-unlimited.rs:28:20 + | +LL | const ID_ED: u32 = rec_id(ORIGINAL); + | ^^^^^^^^^^^^^^^^ + +note: erroneous constant used + --> $DIR/ctfe-id-unlimited.rs:30:40 + | +LL | const ASSERT: () = assert!(ORIGINAL == ID_ED); + | ^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs new file mode 100644 index 0000000000000..b47c62141b7f0 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs @@ -0,0 +1,34 @@ +// revisions: become return +// [become] run-pass +#![feature(explicit_tail_calls)] + +// This is an identity function (`|x| x`), but implemented using recursion. +// Each step we increment accumulator and decrement the number. +// +// With normal calls this fails compilation because of the recursion limit, +// but with tail calls/`become` we don't grow the stack/spend recursion limit +// so this should compile. +const fn rec_id(n: u32) -> u32 { + const fn inner(acc: u32, n: u32) -> u32 { + match n { + 0 => acc, + #[cfg(r#become)] _ => become inner(acc + 1, n - 1), + #[cfg(r#return)] _ => return inner(acc + 1, n - 1), + //[return]~^ error: evaluation of constant value failed + } + } + + inner(0, n) +} + +// Some relatively big number that is higher than recursion limit +const ORIGINAL: u32 = 12345; +// Original number, but with identity function applied +// (this is the same, but requires execution of the recursion) +const ID_ED: u32 = rec_id(ORIGINAL); +// Assert to make absolutely sure the computation actually happens +const ASSERT: () = assert!(ORIGINAL == ID_ED); + +fn main() { + let _ = ASSERT; +} diff --git a/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs new file mode 100644 index 0000000000000..1c51263930636 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs @@ -0,0 +1,18 @@ +#![feature(explicit_tail_calls)] + +pub const fn f() { + become g(); +} + +const fn g() { + panic!() + //~^ error: evaluation of constant value failed + //~| note: in this expansion of panic! + //~| note: inside `g` + //~| note: in this expansion of panic! +} + +const _: () = f(); +//~^ note: inside `_` + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr new file mode 100644 index 0000000000000..44834580b908a --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr @@ -0,0 +1,21 @@ +error[E0080]: evaluation of constant value failed + --> $DIR/ctfe-tail-call-panic.rs:8:5 + | +LL | panic!() + | ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/ctfe-tail-call-panic.rs:8:5 + | +note: inside `g` + --> $DIR/ctfe-tail-call-panic.rs:8:5 + | +LL | panic!() + | ^^^^^^^^ +note: inside `_` + --> $DIR/ctfe-tail-call-panic.rs:15:15 + | +LL | const _: () = f(); + | ^^^ + = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs new file mode 100644 index 0000000000000..6ef4f68e91694 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs @@ -0,0 +1,23 @@ +#![feature(explicit_tail_calls, const_trait_impl, const_mut_refs)] + +pub const fn test(_: &View) { + const fn takes_view(_: &View) {} + + become takes_view(HasDrop.as_view()); + //~^ error: temporary value dropped while borrowed +} + +struct HasDrop; +struct View; + +impl HasDrop { + const fn as_view(&self) -> &View { + &View + } +} + +impl const Drop for HasDrop { + fn drop(&mut self) {} +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr new file mode 100644 index 0000000000000..3617f546932a4 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr @@ -0,0 +1,18 @@ +error[E0716]: temporary value dropped while borrowed + --> $DIR/ctfe-tmp-arg-drop.rs:6:23 + | +LL | become takes_view(HasDrop.as_view()); + | ------------------^^^^^^^------------ temporary value is freed at the end of this statement + | | | + | | creates a temporary value which is freed while still in use + | borrow later used here + | +help: consider using a `let` binding to create a longer lived value + | +LL ~ let binding = HasDrop; +LL ~ become takes_view(binding.as_view()); + | + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0716`. diff --git a/tests/ui/explicit-tail-calls/drop-order.rs b/tests/ui/explicit-tail-calls/drop-order.rs new file mode 100644 index 0000000000000..98f537f31fe91 --- /dev/null +++ b/tests/ui/explicit-tail-calls/drop-order.rs @@ -0,0 +1,67 @@ +// run-pass +#![feature(explicit_tail_calls)] +use std::cell::RefCell; + +fn main() { + let tail_counter = Default::default(); + tail_recursive(0, &tail_counter); + assert_eq!(tail_counter.into_inner(), (0..128).collect::>()); + + let simply_counter = Default::default(); + simply_recursive(0, &simply_counter); + assert_eq!(simply_counter.into_inner(), (0..128).rev().collect::>()); + + let scope_counter = Default::default(); + out_of_inner_scope(&scope_counter); + assert_eq!(scope_counter.into_inner(), (0..8).collect::>()); +} + +fn tail_recursive(n: u8, order: &RefCell>) { + if n >= 128 { + return; + } + + let _local = DropCounter(n, order); + + become tail_recursive(n + 1, order) +} + +fn simply_recursive(n: u8, order: &RefCell>) { + if n >= 128 { + return; + } + + let _local = DropCounter(n, order); + + return simply_recursive(n + 1, order) +} + +fn out_of_inner_scope(order: &RefCell>) { + fn inner(order: &RefCell>) { + let _7 = DropCounter(7, order); + let _6 = DropCounter(6, order); + } + + let _5 = DropCounter(5, order); + let _4 = DropCounter(4, order); + + if true { + let _3 = DropCounter(3, order); + let _2 = DropCounter(2, order); + loop { + let _1 = DropCounter(1, order); + let _0 = DropCounter(0, order); + + become inner(order); + } + } +} + +struct DropCounter<'a>(u8, &'a RefCell>); + +impl Drop for DropCounter<'_> { + #[track_caller] + fn drop(&mut self) { + self.1.borrow_mut().push(self.0); + } +} diff --git a/tests/ui/explicit-tail-calls/dyn-fn-once.rs b/tests/ui/explicit-tail-calls/dyn-fn-once.rs new file mode 100644 index 0000000000000..1d3871a24f590 --- /dev/null +++ b/tests/ui/explicit-tail-calls/dyn-fn-once.rs @@ -0,0 +1,15 @@ +#![feature(explicit_tail_calls)] + +fn f() { + become (Box::new(|| ()) as Box ()>)(); + //~^ error: mismatched function ABIs + //~| error: mismatched signatures +} + +fn g() { + become (&g as &dyn FnOnce() -> ())(); + //~^ error: mismatched function ABIs + //~| error: mismatched signatures +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/dyn-fn-once.stderr b/tests/ui/explicit-tail-calls/dyn-fn-once.stderr new file mode 100644 index 0000000000000..d118f8431c7a2 --- /dev/null +++ b/tests/ui/explicit-tail-calls/dyn-fn-once.stderr @@ -0,0 +1,40 @@ +error: mismatched function ABIs + --> $DIR/dyn-fn-once.rs:4:5 + | +LL | become (Box::new(|| ()) as Box ()>)(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have the same ABI + = note: caller ABI is `"Rust"`, while callee ABI is `"rust-call"` + +error: mismatched signatures + --> $DIR/dyn-fn-once.rs:4:5 + | +LL | become (Box::new(|| ()) as Box ()>)(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have matching signatures + = note: caller signature: `fn()` + = note: callee signature: `extern "rust-call" fn(Box, ())` + +error: mismatched function ABIs + --> $DIR/dyn-fn-once.rs:10:5 + | +LL | become (&g as &dyn FnOnce() -> ())(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have the same ABI + = note: caller ABI is `"Rust"`, while callee ABI is `"rust-call"` + +error: mismatched signatures + --> $DIR/dyn-fn-once.rs:10:5 + | +LL | become (&g as &dyn FnOnce() -> ())(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have matching signatures + = note: caller signature: `fn()` + = note: callee signature: `extern "rust-call" fn(dyn FnOnce(), ())` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/explicit-tail-calls/fibonacci.rs b/tests/ui/explicit-tail-calls/fibonacci.rs new file mode 100644 index 0000000000000..f1f3eac180bda --- /dev/null +++ b/tests/ui/explicit-tail-calls/fibonacci.rs @@ -0,0 +1,25 @@ +// run-pass + +fn fibonacci(n: u32) -> u128 { + fibonacci_impl(n, 0, 1) +} + +fn fibonacci_impl(left: u32, prev_prev: u128, prev: u128) -> u128 { + match left { + 0 => prev_prev, + 1 => prev, + _ => fibonacci_impl(left - 1, prev, prev_prev + prev), + } +} + +fn main() { + let expected = + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]; + assert!((0..20).map(fibonacci).eq(expected)); + + // This is the highest fibonacci number that fits in a u128 + assert_eq!( + std::hint::black_box(fibonacci(std::hint::black_box(186))), + 332825110087067562321196029789634457848 + ); +} diff --git a/tests/ui/explicit-tail-calls/fn-pointers.rs b/tests/ui/explicit-tail-calls/fn-pointers.rs new file mode 100644 index 0000000000000..ef9c40a5fe6e0 --- /dev/null +++ b/tests/ui/explicit-tail-calls/fn-pointers.rs @@ -0,0 +1,43 @@ +// run-pass +#![feature(explicit_tail_calls)] + +fn main() { + use Inst::*; + + let program = [ + Inc, // st + 1 = 1 + ShiftLeft, // st * 2 = 2 + ShiftLeft, // st * 2 = 4 + ShiftLeft, // st * 2 = 8 + Inc, // st + 1 = 9 + Inc, // st + 1 = 10 + Inc, // st + 1 = 11 + ShiftLeft, // st * 2 = 22 + Halt, // st = 22 + ]; + + assert_eq!(run(0, &program), 22); +} + +#[derive(Clone, Copy, Debug, PartialEq)] +enum Inst { + Inc, + ShiftLeft, + Halt, +} + +fn run(state: u32, program: &[Inst]) -> u32 { + const DISPATCH_TABLE: [fn(u32, &[Inst]) -> u32; 3] = [inc, shift_left, |st, _| st]; + + become DISPATCH_TABLE[program[0] as usize](state, program); +} + +fn inc(state: u32, program: &[Inst]) -> u32 { + assert_eq!(program[0], Inst::Inc); + become run(state + 1, &program[1..]) +} + +fn shift_left(state: u32, program: &[Inst]) -> u32 { + assert_eq!(program[0], Inst::ShiftLeft); + become run(state << 1, &program[1..]) +} diff --git a/tests/ui/explicit-tail-calls/from-main-1.rs b/tests/ui/explicit-tail-calls/from-main-1.rs new file mode 100644 index 0000000000000..2be833f4cc17a --- /dev/null +++ b/tests/ui/explicit-tail-calls/from-main-1.rs @@ -0,0 +1,8 @@ +// run-pass +#![feature(explicit_tail_calls)] + +fn main() { + become f(); +} + +fn f() {} diff --git a/tests/ui/explicit-tail-calls/from-main-2.rs b/tests/ui/explicit-tail-calls/from-main-2.rs new file mode 100644 index 0000000000000..c4245d5e9c813 --- /dev/null +++ b/tests/ui/explicit-tail-calls/from-main-2.rs @@ -0,0 +1,10 @@ +// run-pass +#![feature(explicit_tail_calls)] + +fn main() -> Result<(), i32> { + become f(); +} + +fn f() -> Result<(), i32> { + Ok(()) +} diff --git a/tests/ui/explicit-tail-calls/in-closure.rs b/tests/ui/explicit-tail-calls/in-closure.rs new file mode 100644 index 0000000000000..680c8671aff7b --- /dev/null +++ b/tests/ui/explicit-tail-calls/in-closure.rs @@ -0,0 +1,7 @@ +#![feature(explicit_tail_calls)] + +fn main() { + || become f(); //~ error: `become` is not allowed in closures +} + +fn f() {} diff --git a/tests/ui/explicit-tail-calls/in-closure.stderr b/tests/ui/explicit-tail-calls/in-closure.stderr new file mode 100644 index 0000000000000..5077c0a42b713 --- /dev/null +++ b/tests/ui/explicit-tail-calls/in-closure.stderr @@ -0,0 +1,8 @@ +error: `become` is not allowed in closures + --> $DIR/in-closure.rs:4:8 + | +LL | || become f(); + | ^^^^^^^^^^ + +error: aborting due to previous error + diff --git a/tests/ui/explicit-tail-calls/ll.rs b/tests/ui/explicit-tail-calls/ll.rs new file mode 100644 index 0000000000000..4cc20d3b81cde --- /dev/null +++ b/tests/ui/explicit-tail-calls/ll.rs @@ -0,0 +1,74 @@ +// revisions: tail nose +//[tail] run-pass +//[nose] run-fail +#![feature(explicit_tail_calls)] + +fn main() { + with_smol_stack(|| List::from_elem((), 1024 * 32).rec_drop()); +} + +struct List { + next: Option>>, +} + +struct Node { + elem: T, + next: Option>>, +} + +impl List { + fn from_elem(elem: T, n: usize) -> Self + where + T: Clone, + { + List { next: None }.prepend_n(elem, n) + } + + fn prepend_n(self, elem: T, n: usize) -> Self + where + T: Clone, + { + match n { + 0 => self, + 1 => Self { next: p(Node { elem, next: self.next }) }, + _ => { + #[cfg(tail)] + become Self { next: p(Node { elem: elem.clone(), next: self.next }) } + .prepend_n(elem, n - 1); + + #[cfg(nose)] + return Self { next: p(Node { elem: elem.clone(), next: self.next }) } + .prepend_n(elem, n - 1); + } + } + } + + fn rec_drop(self) { + if let Some(node) = self.next { + node.rec_drop() + } + } +} + +impl Node { + fn rec_drop(self) { + if let Some(node) = self.next { + _ = node.elem; + become node.rec_drop() + } + } +} + +fn p(v: T) -> Option> { + Some(Box::new(v)) +} + +fn with_smol_stack(f: impl FnOnce() + Send + 'static) { + std::thread::Builder::new() + .stack_size(1024 /* bytes */) + .name("smol thread".to_owned()) + .spawn(f) + .unwrap() + .join() + .unwrap(); +} diff --git a/tests/ui/explicit-tail-calls/out-of-async.rs b/tests/ui/explicit-tail-calls/out-of-async.rs new file mode 100644 index 0000000000000..15275edc3775c --- /dev/null +++ b/tests/ui/explicit-tail-calls/out-of-async.rs @@ -0,0 +1,20 @@ +// edition: 2021 +#![feature(explicit_tail_calls)] + +async fn a() { + become b(); + //~^ error: mismatched function ABIs + //~| error: mismatched signatures +} + +fn b() {} + +fn block() -> impl std::future::Future { + async { + become b(); + //~^ error: mismatched function ABIs + //~| error: mismatched signatures + } +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/out-of-async.stderr b/tests/ui/explicit-tail-calls/out-of-async.stderr new file mode 100644 index 0000000000000..366ebe6837c97 --- /dev/null +++ b/tests/ui/explicit-tail-calls/out-of-async.stderr @@ -0,0 +1,40 @@ +error: mismatched function ABIs + --> $DIR/out-of-async.rs:5:5 + | +LL | become b(); + | ^^^^^^^^^^ + | + = note: `become` requires caller and callee to have the same ABI + = note: caller ABI is `"rust-call"`, while callee ABI is `"Rust"` + +error: mismatched signatures + --> $DIR/out-of-async.rs:5:5 + | +LL | become b(); + | ^^^^^^^^^^ + | + = note: `become` requires caller and callee to have matching signatures + = note: caller signature: `extern "rust-call" fn(ResumeTy)` + = note: callee signature: `fn()` + +error: mismatched function ABIs + --> $DIR/out-of-async.rs:14:9 + | +LL | become b(); + | ^^^^^^^^^^ + | + = note: `become` requires caller and callee to have the same ABI + = note: caller ABI is `"rust-call"`, while callee ABI is `"Rust"` + +error: mismatched signatures + --> $DIR/out-of-async.rs:14:9 + | +LL | become b(); + | ^^^^^^^^^^ + | + = note: `become` requires caller and callee to have matching signatures + = note: caller signature: `extern "rust-call" fn(ResumeTy)` + = note: callee signature: `fn()` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/explicit-tail-calls/out-of-generators.rs b/tests/ui/explicit-tail-calls/out-of-generators.rs new file mode 100644 index 0000000000000..6c3a16dc0451a --- /dev/null +++ b/tests/ui/explicit-tail-calls/out-of-generators.rs @@ -0,0 +1,11 @@ +#![feature(explicit_tail_calls)] +#![feature(generators)] + +fn main() { + let _generator = || { + yield 1; + become f() //~ error: mismatched function ABIs + }; +} + +fn f() {} diff --git a/tests/ui/explicit-tail-calls/out-of-generators.stderr b/tests/ui/explicit-tail-calls/out-of-generators.stderr new file mode 100644 index 0000000000000..9886099d669b9 --- /dev/null +++ b/tests/ui/explicit-tail-calls/out-of-generators.stderr @@ -0,0 +1,11 @@ +error: mismatched function ABIs + --> $DIR/out-of-generators.rs:7:9 + | +LL | become f() + | ^^^^^^^^^^ + | + = note: `become` requires caller and callee to have the same ABI + = note: caller ABI is `"rust-call"`, while callee ABI is `"Rust"` + +error: aborting due to previous error + diff --git a/tests/ui/explicit-tail-calls/return-lifetime-sub.rs b/tests/ui/explicit-tail-calls/return-lifetime-sub.rs new file mode 100644 index 0000000000000..14f3d09a65b5e --- /dev/null +++ b/tests/ui/explicit-tail-calls/return-lifetime-sub.rs @@ -0,0 +1,12 @@ +// check-pass +#![feature(explicit_tail_calls)] + +fn _f<'a>() -> &'a [u8] { + become _g(); +} + +fn _g() -> &'static [u8] { + &[0, 1, 2, 3] +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/return-mismatches.rs b/tests/ui/explicit-tail-calls/return-mismatches.rs new file mode 100644 index 0000000000000..92e4eea6688b4 --- /dev/null +++ b/tests/ui/explicit-tail-calls/return-mismatches.rs @@ -0,0 +1,27 @@ +#![feature(explicit_tail_calls)] + +fn _f0<'a>() -> &'static [u8] { + become _g0(); //~ error: mismatched types +} + +fn _g0() -> &'static [u8; 1] { + &[0] +} + +fn _f1() { + become _g1(); //~ error: mismatched types +} + +fn _g1() -> ! { + become _g1(); +} + +fn _f2() -> u32 { + become _g2(); //~ error: mismatched types +} + +fn _g2() -> u16 { + 0 +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/return-mismatches.stderr b/tests/ui/explicit-tail-calls/return-mismatches.stderr new file mode 100644 index 0000000000000..f0f50af7f05c5 --- /dev/null +++ b/tests/ui/explicit-tail-calls/return-mismatches.stderr @@ -0,0 +1,27 @@ +error[E0308]: mismatched types + --> $DIR/return-mismatches.rs:4:5 + | +LL | become _g0(); + | ^^^^^^^^^^^^ expected `&[u8]`, found `&[u8; 1]` + | + = note: expected reference `&'static [u8]` + found reference `&'static [u8; 1]` + +error[E0308]: mismatched types + --> $DIR/return-mismatches.rs:12:5 + | +LL | become _g1(); + | ^^^^^^^^^^^^ expected `()`, found `!` + | + = note: expected unit type `()` + found type `!` + +error[E0308]: mismatched types + --> $DIR/return-mismatches.rs:20:5 + | +LL | become _g2(); + | ^^^^^^^^^^^^ expected `u32`, found `u16` + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/explicit-tail-calls/signature-mismatch.rs b/tests/ui/explicit-tail-calls/signature-mismatch.rs new file mode 100644 index 0000000000000..ef27ed61392a2 --- /dev/null +++ b/tests/ui/explicit-tail-calls/signature-mismatch.rs @@ -0,0 +1,32 @@ +#![feature(explicit_tail_calls)] +#![feature(c_variadic)] + +fn _f0((): ()) { + become _g0(); //~ error: mismatched signatures +} + +fn _g0() {} + + +fn _f1() { + become _g1(()); //~ error: mismatched signatures +} + +fn _g1((): ()) {} + + +extern "C" fn _f2() { + become _g2(); //~ error: mismatched function ABIs +} + +fn _g2() {} + + +fn _f3() { + become _g3(); //~ error: mismatched function ABIs +} + +extern "C" fn _g3() {} + + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/signature-mismatch.stderr b/tests/ui/explicit-tail-calls/signature-mismatch.stderr new file mode 100644 index 0000000000000..df19fef667656 --- /dev/null +++ b/tests/ui/explicit-tail-calls/signature-mismatch.stderr @@ -0,0 +1,40 @@ +error: mismatched signatures + --> $DIR/signature-mismatch.rs:5:5 + | +LL | become _g0(); + | ^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have matching signatures + = note: caller signature: `fn(())` + = note: callee signature: `fn()` + +error: mismatched signatures + --> $DIR/signature-mismatch.rs:12:5 + | +LL | become _g1(()); + | ^^^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have matching signatures + = note: caller signature: `fn()` + = note: callee signature: `fn(())` + +error: mismatched function ABIs + --> $DIR/signature-mismatch.rs:19:5 + | +LL | become _g2(); + | ^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have the same ABI + = note: caller ABI is `"C"`, while callee ABI is `"Rust"` + +error: mismatched function ABIs + --> $DIR/signature-mismatch.rs:26:5 + | +LL | become _g3(); + | ^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have the same ABI + = note: caller ABI is `"Rust"`, while callee ABI is `"C"` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/explicit-tail-calls/slice-fold.rs b/tests/ui/explicit-tail-calls/slice-fold.rs new file mode 100644 index 0000000000000..fb71d8fe25f76 --- /dev/null +++ b/tests/ui/explicit-tail-calls/slice-fold.rs @@ -0,0 +1,34 @@ +// run-pass +#![feature(explicit_tail_calls)] + +fn fold(slice: &[T], x: S, mut f: impl FnMut(S, &T) -> S) -> S { + match slice { + [] => x, + [head, tail @ ..] => become fold(tail, f(x, head), f), + } +} + +fn main() { + let numbers = [ + 11, 49, 81, 32, 33, 52, 121, 28, 64, 106, 99, 101, 110, 84, 123, 66, 80, 88, 94, 21, 65, + 85, 3, 54, 46, 69, 116, 26, 72, 114, 71, 86, 125, 70, 42, 68, 40, 91, 56, 22, 36, 115, 117, + 120, 18, 105, 30, 74, 63, 108, 43, 25, 122, 55, 104, 92, 12, 37, 20, 58, 35, 95, 98, 53, + 93, 100, 5, 112, 8, 78, 126, 102, 90, 97, 13, 51, 118, 62, 128, 34, 38, 4, 24, 6, 59, 48, + 44, 73, 7, 107, 61, 60, 14, 16, 111, 119, 96, 17, 57, 45, 15, 79, 10, 1, 124, 39, 9, 19, + 109, 127, 41, 47, 87, 76, 89, 50, 2, 23, 29, 27, 75, 103, 113, 77, 83, 67, 31, 82, 11, 71, + 67, 39, 64, 66, 100, 9, 92, 21, 35, 12, 6, 91, 62, 85, 13, 79, 98, 95, 30, 24, 38, 3, 78, + 99, 60, 25, 15, 82, 75, 97, 80, 2, 8, 16, 7, 19, 57, 26, 81, 33, 5, 47, 58, 68, 93, 52, 69, + 53, 49, 87, 73, 84, 76, 63, 48, 14, 34, 10, 56, 41, 20, 59, 96, 61, 42, 74, 88, 17, 43, 72, + 50, 37, 1, 70, 83, 45, 89, 90, 94, 18, 4, 31, 44, 36, 23, 29, 46, 55, 40, 77, 28, 32, 86, + 65, 54, 27, 22, 51, 8, 21, 36, 65, 66, 20, 6, 77, 94, 55, 32, 45, 12, 98, 28, 91, 64, 18, + 43, 70, 13, 73, 69, 85, 2, 39, 4, 11, 84, 71, 74, 23, 10, 40, 83, 9, 72, 62, 63, 25, 53, + 15, 96, 95, 68, 37, 79, 26, 76, 87, 89, 81, 51, 61, 5, 34, 44, 1, 46, 17, 86, 78, 82, 27, + 56, 41, 47, 90, 75, 92, 22, 50, 54, 97, 67, 57, 59, 42, 100, 35, 7, 24, 3, 19, 38, 58, 93, + 30, 49, 14, 99, 33, 48, 80, 31, 88, 52, 16, 29, 60, + ]; + + let expected = numbers.iter().sum(); + let res = fold(&numbers, 0, |s, &e| s + e); + + assert_eq!(res, expected); +} diff --git a/tests/ui/explicit-tail-calls/unsafeck.rs b/tests/ui/explicit-tail-calls/unsafeck.rs new file mode 100644 index 0000000000000..58e4edc12f867 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsafeck.rs @@ -0,0 +1,10 @@ +#![feature(explicit_tail_calls)] + +const fn f() { + become dangerous(); + //~^ error: call to unsafe function is unsafe and requires unsafe function or block +} + +const unsafe fn dangerous() {} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/unsafeck.stderr b/tests/ui/explicit-tail-calls/unsafeck.stderr new file mode 100644 index 0000000000000..790f05b17ff86 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsafeck.stderr @@ -0,0 +1,11 @@ +error[E0133]: call to unsafe function is unsafe and requires unsafe function or block + --> $DIR/unsafeck.rs:4:5 + | +LL | become dangerous(); + | ^^^^^^^^^^^^^^^^^^ call to unsafe function + | + = note: consult the function's documentation for information on how to avoid undefined behavior + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0133`. diff --git a/tests/ui/feature-gates/feature-gate-explicit_tail_calls.rs b/tests/ui/feature-gates/feature-gate-explicit_tail_calls.rs new file mode 100644 index 0000000000000..856a7f393289a --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-explicit_tail_calls.rs @@ -0,0 +1,9 @@ +pub fn you() -> T { + become bottom(); //~ error: `become` expression is experimental +} + +pub fn bottom() -> T { + become you(); //~ error: `become` expression is experimental +} + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-explicit_tail_calls.stderr b/tests/ui/feature-gates/feature-gate-explicit_tail_calls.stderr new file mode 100644 index 0000000000000..e46a3be9c3a1b --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-explicit_tail_calls.stderr @@ -0,0 +1,19 @@ +error[E0658]: `become` expression is experimental + --> $DIR/feature-gate-explicit_tail_calls.rs:2:5 + | +LL | become bottom(); + | ^^^^^^^^^^^^^^^ + | + = help: add `#![feature(explicit_tail_calls)]` to the crate attributes to enable + +error[E0658]: `become` expression is experimental + --> $DIR/feature-gate-explicit_tail_calls.rs:6:5 + | +LL | become you(); + | ^^^^^^^^^^^^ + | + = help: add `#![feature(explicit_tail_calls)]` to the crate attributes to enable + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/issues/issue-38458.rs b/tests/ui/issues/issue-38458.rs index 665a8fdf8e26e..ce31782b701a6 100644 --- a/tests/ui/issues/issue-38458.rs +++ b/tests/ui/issues/issue-38458.rs @@ -1,5 +1,5 @@ const x: () = { - return; //~ ERROR return statement outside of function body + return; //~ ERROR `return` statement outside of function body }; fn main() {} diff --git a/tests/ui/issues/issue-38458.stderr b/tests/ui/issues/issue-38458.stderr index c04a01118a441..9ce35eb404f77 100644 --- a/tests/ui/issues/issue-38458.stderr +++ b/tests/ui/issues/issue-38458.stderr @@ -1,4 +1,4 @@ -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-38458.rs:2:5 | LL | return; diff --git a/tests/ui/issues/issue-51714.rs b/tests/ui/issues/issue-51714.rs index 8716524d6f4b5..71bdb817ed2b5 100644 --- a/tests/ui/issues/issue-51714.rs +++ b/tests/ui/issues/issue-51714.rs @@ -4,18 +4,18 @@ fn main() { //~| NOTE: not the enclosing function body //~| NOTE: not the enclosing function body |_: [_; return || {}] | {}; - //~^ ERROR: return statement outside of function body [E0572] - //~| NOTE: the return is part of this body... + //~^ ERROR: `return` statement outside of function body [E0572] + //~| NOTE: the `return` is part of this body... [(); return || {}]; - //~^ ERROR: return statement outside of function body [E0572] - //~| NOTE: the return is part of this body... + //~^ ERROR: `return` statement outside of function body [E0572] + //~| NOTE: the `return` is part of this body... [(); return |ice| {}]; - //~^ ERROR: return statement outside of function body [E0572] - //~| NOTE: the return is part of this body... + //~^ ERROR: `return` statement outside of function body [E0572] + //~| NOTE: the `return` is part of this body... [(); return while let Some(n) = Some(0) {}]; - //~^ ERROR: return statement outside of function body [E0572] - //~| NOTE: the return is part of this body... + //~^ ERROR: `return` statement outside of function body [E0572] + //~| NOTE: the `return` is part of this body... } diff --git a/tests/ui/issues/issue-51714.stderr b/tests/ui/issues/issue-51714.stderr index 514d69c1c7d39..1543d75a74bc0 100644 --- a/tests/ui/issues/issue-51714.stderr +++ b/tests/ui/issues/issue-51714.stderr @@ -1,4 +1,4 @@ -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-51714.rs:6:14 | LL | / fn main() { @@ -7,13 +7,13 @@ LL | | LL | | LL | | LL | | |_: [_; return || {}] | {}; - | | ^^^^^^^^^^^^ the return is part of this body... + | | ^^^^^^^^^^^^ the `return` is part of this body... ... | LL | | LL | | } | |_- ...not the enclosing function body -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-51714.rs:10:10 | LL | / fn main() { @@ -22,13 +22,13 @@ LL | | LL | | ... | LL | | [(); return || {}]; - | | ^^^^^^^^^^^^ the return is part of this body... + | | ^^^^^^^^^^^^ the `return` is part of this body... ... | LL | | LL | | } | |_- ...not the enclosing function body -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-51714.rs:14:10 | LL | / fn main() { @@ -37,13 +37,13 @@ LL | | LL | | ... | LL | | [(); return |ice| {}]; - | | ^^^^^^^^^^^^^^^ the return is part of this body... + | | ^^^^^^^^^^^^^^^ the `return` is part of this body... ... | LL | | LL | | } | |_- ...not the enclosing function body -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-51714.rs:18:10 | LL | / fn main() { @@ -52,7 +52,7 @@ LL | | LL | | ... | LL | | [(); return while let Some(n) = Some(0) {}]; - | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the return is part of this body... + | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `return` is part of this body... LL | | LL | | LL | | } diff --git a/tests/ui/return/issue-64620.rs b/tests/ui/return/issue-64620.rs index a62e5bf8d3c62..54cebd927b2c7 100644 --- a/tests/ui/return/issue-64620.rs +++ b/tests/ui/return/issue-64620.rs @@ -1,5 +1,5 @@ enum Bug { - V1 = return [0][0] //~ERROR return statement outside of function body + V1 = return [0][0], //~ERROR `return` statement outside of function body } fn main() {} diff --git a/tests/ui/return/issue-64620.stderr b/tests/ui/return/issue-64620.stderr index f40ac4de32d59..969a664a4e8e9 100644 --- a/tests/ui/return/issue-64620.stderr +++ b/tests/ui/return/issue-64620.stderr @@ -1,7 +1,7 @@ -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-64620.rs:2:10 | -LL | V1 = return [0][0] +LL | V1 = return [0][0], | ^^^^^^^^^^^^^ error: aborting due to previous error diff --git a/tests/ui/return/issue-86188-return-not-in-fn-body.rs b/tests/ui/return/issue-86188-return-not-in-fn-body.rs index 4f076fa069383..3d61491a768b8 100644 --- a/tests/ui/return/issue-86188-return-not-in-fn-body.rs +++ b/tests/ui/return/issue-86188-return-not-in-fn-body.rs @@ -7,7 +7,7 @@ const C: [(); 42] = { [(); return || { - //~^ ERROR: return statement outside of function body [E0572] + //~^ ERROR: `return` statement outside of function body [E0572] let tx; }] }; @@ -18,24 +18,24 @@ trait Tr { fn bar() { //~^ NOTE: ...not the enclosing function body [(); return]; - //~^ ERROR: return statement outside of function body [E0572] - //~| NOTE: the return is part of this body... + //~^ ERROR: `return` statement outside of function body [E0572] + //~| NOTE: the `return` is part of this body... } } impl Tr for S { fn foo() { //~^ NOTE: ...not the enclosing function body [(); return]; - //~^ ERROR: return statement outside of function body [E0572] - //~| NOTE: the return is part of this body... + //~^ ERROR: `return` statement outside of function body [E0572] + //~| NOTE: the `return` is part of this body... } } fn main() { //~^ NOTE: ...not the enclosing function body [(); return || { - //~^ ERROR: return statement outside of function body [E0572] - //~| NOTE: the return is part of this body... + //~^ ERROR: `return` statement outside of function body [E0572] + //~| NOTE: the `return` is part of this body... let tx; }]; } diff --git a/tests/ui/return/issue-86188-return-not-in-fn-body.stderr b/tests/ui/return/issue-86188-return-not-in-fn-body.stderr index 4f938670e5e2c..3fa588f39af52 100644 --- a/tests/ui/return/issue-86188-return-not-in-fn-body.stderr +++ b/tests/ui/return/issue-86188-return-not-in-fn-body.stderr @@ -1,4 +1,4 @@ -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-86188-return-not-in-fn-body.rs:9:10 | LL | [(); return || { @@ -8,31 +8,31 @@ LL | | let tx; LL | | }] | |_____^ -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-86188-return-not-in-fn-body.rs:20:14 | LL | / fn bar() { LL | | LL | | [(); return]; - | | ^^^^^^ the return is part of this body... + | | ^^^^^^ the `return` is part of this body... LL | | LL | | LL | | } | |_____- ...not the enclosing function body -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-86188-return-not-in-fn-body.rs:28:14 | LL | / fn foo() { LL | | LL | | [(); return]; - | | ^^^^^^ the return is part of this body... + | | ^^^^^^ the `return` is part of this body... LL | | LL | | LL | | } | |_____- ...not the enclosing function body -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-86188-return-not-in-fn-body.rs:36:10 | LL | / fn main() { @@ -43,7 +43,7 @@ LL | || LL | || LL | || let tx; LL | || }]; - | ||_____^ the return is part of this body... + | ||_____^ the `return` is part of this body... LL | | } | |__- ...not the enclosing function body diff --git a/tests/ui/return/return-match-array-const.rs b/tests/ui/return/return-match-array-const.rs index b619a4d57f955..88f4f702490a1 100644 --- a/tests/ui/return/return-match-array-const.rs +++ b/tests/ui/return/return-match-array-const.rs @@ -3,16 +3,16 @@ fn main() { //~| NOTE: not the enclosing function body //~| NOTE: not the enclosing function body [(); return match 0 { n => n }]; - //~^ ERROR: return statement outside of function body [E0572] - //~| NOTE: the return is part of this body... + //~^ ERROR: `return` statement outside of function body [E0572] + //~| NOTE: the `return` is part of this body... [(); return match 0 { 0 => 0 }]; - //~^ ERROR: return statement outside of function body [E0572] - //~| NOTE: the return is part of this body... + //~^ ERROR: `return` statement outside of function body [E0572] + //~| NOTE: the `return` is part of this body... [(); return match () { 'a' => 0, _ => 0 }]; - //~^ ERROR: return statement outside of function body [E0572] - //~| NOTE: the return is part of this body... + //~^ ERROR: `return` statement outside of function body [E0572] + //~| NOTE: the `return` is part of this body... //~| ERROR: mismatched types [E0308] //~| NOTE: expected `()`, found `char` //~| NOTE: this expression has type `()` diff --git a/tests/ui/return/return-match-array-const.stderr b/tests/ui/return/return-match-array-const.stderr index 85a733adfee69..cdf6fd843fd13 100644 --- a/tests/ui/return/return-match-array-const.stderr +++ b/tests/ui/return/return-match-array-const.stderr @@ -1,4 +1,4 @@ -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/return-match-array-const.rs:5:10 | LL | / fn main() { @@ -6,13 +6,13 @@ LL | | LL | | LL | | LL | | [(); return match 0 { n => n }]; - | | ^^^^^^^^^^^^^^^^^^^^^^^^^ the return is part of this body... + | | ^^^^^^^^^^^^^^^^^^^^^^^^^ the `return` is part of this body... ... | LL | | LL | | } | |_- ...not the enclosing function body -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/return-match-array-const.rs:9:10 | LL | / fn main() { @@ -21,13 +21,13 @@ LL | | LL | | ... | LL | | [(); return match 0 { 0 => 0 }]; - | | ^^^^^^^^^^^^^^^^^^^^^^^^^ the return is part of this body... + | | ^^^^^^^^^^^^^^^^^^^^^^^^^ the `return` is part of this body... ... | LL | | LL | | } | |_- ...not the enclosing function body -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/return-match-array-const.rs:13:10 | LL | / fn main() { @@ -36,7 +36,7 @@ LL | | LL | | ... | LL | | [(); return match () { 'a' => 0, _ => 0 }]; - | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the return is part of this body... + | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `return` is part of this body... ... | LL | | LL | | } diff --git a/tests/ui/typeck/issue-86721-return-expr-ice.rev1.stderr b/tests/ui/typeck/issue-86721-return-expr-ice.rev1.stderr index b1111fcf1484c..a89c8907f3ff2 100644 --- a/tests/ui/typeck/issue-86721-return-expr-ice.rev1.stderr +++ b/tests/ui/typeck/issue-86721-return-expr-ice.rev1.stderr @@ -1,4 +1,4 @@ -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-86721-return-expr-ice.rs:9:22 | LL | const U: usize = return; diff --git a/tests/ui/typeck/issue-86721-return-expr-ice.rev2.stderr b/tests/ui/typeck/issue-86721-return-expr-ice.rev2.stderr index f489ae2002a13..6d29138c77f06 100644 --- a/tests/ui/typeck/issue-86721-return-expr-ice.rev2.stderr +++ b/tests/ui/typeck/issue-86721-return-expr-ice.rev2.stderr @@ -1,4 +1,4 @@ -error[E0572]: return statement outside of function body +error[E0572]: `return` statement outside of function body --> $DIR/issue-86721-return-expr-ice.rs:15:20 | LL | fn foo(a: [(); return]); diff --git a/tests/ui/typeck/issue-86721-return-expr-ice.rs b/tests/ui/typeck/issue-86721-return-expr-ice.rs index cd7135f18b112..de3359c8d2b81 100644 --- a/tests/ui/typeck/issue-86721-return-expr-ice.rs +++ b/tests/ui/typeck/issue-86721-return-expr-ice.rs @@ -2,16 +2,16 @@ // revisions: rev1 rev2 #![cfg_attr(any(), rev1, rev2)] -#![crate_type="lib"] +#![crate_type = "lib"] #[cfg(any(rev1))] trait T { const U: usize = return; - //[rev1]~^ ERROR: return statement outside of function body [E0572] + //[rev1]~^ ERROR: `return` statement outside of function body [E0572] } #[cfg(any(rev2))] trait T2 { fn foo(a: [(); return]); - //[rev2]~^ ERROR: return statement outside of function body [E0572] + //[rev2]~^ ERROR: `return` statement outside of function body [E0572] }