From 56f216d04897e83c8fac7ee1c98fc07b91874991 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 16 Nov 2022 18:36:17 +0000 Subject: [PATCH 01/17] Syntatically accept `become` expressions --- compiler/rustc_ast/src/ast.rs | 6 ++++++ compiler/rustc_ast/src/mut_visit.rs | 1 + compiler/rustc_ast/src/util/parser.rs | 4 +++- compiler/rustc_ast/src/visit.rs | 1 + compiler/rustc_ast_lowering/src/expr.rs | 6 ++++++ compiler/rustc_ast_passes/src/feature_gate.rs | 1 + .../rustc_ast_pretty/src/pprust/state/expr.rs | 5 +++++ .../src/assert/context.rs | 1 + compiler/rustc_feature/src/active.rs | 2 ++ compiler/rustc_parse/src/parser/expr.rs | 12 ++++++++++++ compiler/rustc_passes/src/hir_stats.rs | 3 ++- compiler/rustc_span/src/symbol.rs | 1 + .../feature-gate-explicit_tail_calls.rs | 9 +++++++++ .../feature-gate-explicit_tail_calls.stderr | 19 +++++++++++++++++++ 14 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 tests/ui/feature-gates/feature-gate-explicit_tail_calls.rs create mode 100644 tests/ui/feature-gates/feature-gate-explicit_tail_calls.stderr 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..28247404aae37 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -275,6 +275,12 @@ 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); + + // FIXME(waffle): this is obviously wrong + hir::ExprKind::Ret(Some(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_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_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_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..90809270118d0 100644 --- a/compiler/rustc_passes/src/hir_stats.rs +++ b/compiler/rustc_passes/src/hir_stats.rs @@ -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_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/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`. From 47ab1e5d609e09685ac059164c40a31311af29e9 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 21 Nov 2022 12:40:27 +0000 Subject: [PATCH 02/17] `hir`: Add `Become` expression kind --- compiler/rustc_ast_lowering/src/expr.rs | 4 +- compiler/rustc_hir/src/hir.rs | 5 + compiler/rustc_hir/src/intravisit.rs | 1 + compiler/rustc_hir_pretty/src/lib.rs | 5 + compiler/rustc_hir_typeck/messages.ftl | 4 +- compiler/rustc_hir_typeck/src/errors.rs | 2 + compiler/rustc_hir_typeck/src/expr.rs | 133 ++++++++++++------ .../rustc_hir_typeck/src/expr_use_visitor.rs | 4 + .../drop_ranges/cfg_build.rs | 4 + .../src/mem_categorization.rs | 1 + compiler/rustc_mir_build/src/thir/cx/expr.rs | 1 + compiler/rustc_passes/src/hir_stats.rs | 4 +- compiler/rustc_passes/src/liveness.rs | 8 ++ compiler/rustc_passes/src/naked_functions.rs | 1 + src/tools/tidy/src/ui_tests.rs | 2 +- tests/ui/error-codes/E0572.stderr | 2 +- .../become-outside.array.stderr | 9 ++ .../become-outside.constant.stderr | 9 ++ .../ui/explicit-tail-calls/become-outside.rs | 14 ++ .../return-lifetime-sub.rs | 12 ++ .../explicit-tail-calls/return-mismatches.rs | 27 ++++ .../return-mismatches.stderr | 27 ++++ tests/ui/issues/issue-38458.rs | 2 +- tests/ui/issues/issue-38458.stderr | 2 +- tests/ui/issues/issue-51714.rs | 16 +-- tests/ui/issues/issue-51714.stderr | 16 +-- tests/ui/return/issue-64620.rs | 2 +- tests/ui/return/issue-64620.stderr | 4 +- .../issue-86188-return-not-in-fn-body.rs | 14 +- .../issue-86188-return-not-in-fn-body.stderr | 14 +- tests/ui/return/return-match-array-const.rs | 12 +- .../ui/return/return-match-array-const.stderr | 12 +- .../issue-86721-return-expr-ice.rev1.stderr | 2 +- .../issue-86721-return-expr-ice.rev2.stderr | 2 +- .../ui/typeck/issue-86721-return-expr-ice.rs | 6 +- 35 files changed, 278 insertions(+), 105 deletions(-) create mode 100644 tests/ui/explicit-tail-calls/become-outside.array.stderr create mode 100644 tests/ui/explicit-tail-calls/become-outside.constant.stderr create mode 100644 tests/ui/explicit-tail-calls/become-outside.rs create mode 100644 tests/ui/explicit-tail-calls/return-lifetime-sub.rs create mode 100644 tests/ui/explicit-tail-calls/return-mismatches.rs create mode 100644 tests/ui/explicit-tail-calls/return-mismatches.stderr diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 28247404aae37..29972dd76eb0f 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -277,9 +277,7 @@ impl<'hir> LoweringContext<'_, 'hir> { 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); - - // FIXME(waffle): this is obviously wrong - hir::ExprKind::Ret(Some(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_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_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index b20495d602e59..bc55ae1c410b3 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -695,6 +695,7 @@ 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::Become(_call) => unimplemented!("lol (lmao)"), 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_passes/src/hir_stats.rs b/compiler/rustc_passes/src/hir_stats.rs index 90809270118d0..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) 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/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/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-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/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/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] } From cffd2df24b4c26935f9ef81eb37b25b6a11fc7bd Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 21 Nov 2022 12:47:42 +0000 Subject: [PATCH 03/17] `thir`: Add `Become` expression kind Note that this doesn't implement `become` correctly, there is still no early drop or tail calls involved. Moreother, it seems like MIR simply can not represent tail calls in its current form so I'll probably need to add new statement kinds or smh. to it in order to represent them... --- compiler/rustc_middle/src/thir.rs | 4 ++++ compiler/rustc_middle/src/thir/visit.rs | 1 + compiler/rustc_mir_build/src/build/expr/as_place.rs | 1 + compiler/rustc_mir_build/src/build/expr/as_rvalue.rs | 1 + compiler/rustc_mir_build/src/build/expr/category.rs | 3 ++- compiler/rustc_mir_build/src/build/expr/into.rs | 5 ++++- compiler/rustc_mir_build/src/build/expr/stmt.rs | 6 ++++++ compiler/rustc_mir_build/src/build/scope.rs | 7 +++++-- compiler/rustc_mir_build/src/check_unsafety.rs | 1 + compiler/rustc_mir_build/src/thir/cx/expr.rs | 4 ++-- compiler/rustc_mir_build/src/thir/print.rs | 6 ++++++ compiler/rustc_ty_utils/src/consts.rs | 4 +++- 12 files changed, 36 insertions(+), 7 deletions(-) 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..836a6007839ae 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -99,6 +99,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { BreakableTarget::Return, source_info, ), + ExprKind::Become { value } => this.break_scope( + block, + Some(&this.thir[value]), + BreakableTarget::Become, + source_info, + ), _ => { assert!( statement_scope.is_some(), diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index 7c0fbc6f81c94..8fbc2afbe70a0 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -182,6 +182,7 @@ pub(crate) enum BreakableTarget { Continue(region::Scope), Break(region::Scope), Return, + Become, } rustc_index::newtype_index! { @@ -627,10 +628,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { .unwrap_or_else(|| span_bug!(span, "no enclosing breakable scope found")) }; let (break_index, destination) = match target { - BreakableTarget::Return => { + BreakableTarget::Return | BreakableTarget::Become => { let scope = &self.scopes.breakable_scopes[0]; if scope.break_destination != Place::return_place() { - span_bug!(span, "`return` in item with no return scope"); + span_bug!(span, "`return` or `become` in item with no return scope"); } (0, Some(scope.break_destination)) } @@ -668,6 +669,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { (None, None) => {} } + // FIXME(explicit_tail_calls): this should drop stuff early for `become` + let region_scope = self.scopes.breakable_scopes[break_index].region_scope; let scope_index = self.scopes.scope_index(region_scope, span); let drops = if destination.is_some() { 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/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index bc55ae1c410b3..3d72bfd825ee7 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -694,8 +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::Become(_call) => unimplemented!("lol (lmao)"), + 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_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(_) From 400589089fefccbc12d32bfed3d80ca24d0eaa8f Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 5 May 2023 20:21:57 +0000 Subject: [PATCH 04/17] Support tail calls in mir via `TerminatorKind::TailCall` --- compiler/rustc_borrowck/src/invalidation.rs | 6 ++ compiler/rustc_borrowck/src/lib.rs | 12 +++- compiler/rustc_borrowck/src/type_check/mod.rs | 22 +++++- compiler/rustc_codegen_ssa/src/mir/analyze.rs | 1 + compiler/rustc_codegen_ssa/src/mir/block.rs | 9 +++ .../src/interpret/terminator.rs | 69 +++++++++++++++++++ .../src/transform/check_consts/check.rs | 22 ++++-- .../check_consts/post_drop_elaboration.rs | 1 + .../src/transform/validate.rs | 20 ++++-- compiler/rustc_middle/src/mir/spanview.rs | 1 + compiler/rustc_middle/src/mir/syntax.rs | 31 +++++++++ compiler/rustc_middle/src/mir/terminator.rs | 16 ++++- compiler/rustc_middle/src/mir/visit.rs | 11 +++ .../rustc_mir_build/src/build/expr/stmt.rs | 31 +++++++-- compiler/rustc_mir_build/src/build/scope.rs | 6 +- compiler/rustc_mir_build/src/lints.rs | 2 + .../src/framework/direction.rs | 4 ++ .../rustc_mir_dataflow/src/framework/mod.rs | 2 +- .../src/impls/borrowed_locals.rs | 1 + .../src/impls/storage_liveness.rs | 2 + .../src/move_paths/builder.rs | 6 ++ .../rustc_mir_dataflow/src/value_analysis.rs | 3 + .../rustc_mir_transform/src/check_unsafety.rs | 2 +- .../rustc_mir_transform/src/const_prop.rs | 3 +- .../src/const_prop_lint.rs | 1 + .../rustc_mir_transform/src/coverage/graph.rs | 1 + .../rustc_mir_transform/src/coverage/spans.rs | 5 +- compiler/rustc_mir_transform/src/dest_prop.rs | 6 ++ compiler/rustc_mir_transform/src/generator.rs | 3 + compiler/rustc_mir_transform/src/inline.rs | 3 + .../src/remove_noop_landing_pads.rs | 1 + .../src/separate_const_switch.rs | 2 + compiler/rustc_monomorphize/src/collector.rs | 3 +- compiler/rustc_smir/src/rustc_smir/mod.rs | 1 + tests/ui/explicit-tail-calls/constck.rs | 21 ++++++ tests/ui/explicit-tail-calls/constck.stderr | 19 +++++ .../ctfe-arg-bad-borrow.rs | 13 ++++ .../ctfe-arg-bad-borrow.stderr | 13 ++++ .../ctfe-arg-good-borrow.rs | 12 ++++ tests/ui/explicit-tail-calls/ctfe-arg-move.rs | 12 ++++ .../ctfe-collatz-multi-rec.rs | 42 +++++++++++ .../ctfe-id-unlimited.return.stderr | 36 ++++++++++ .../explicit-tail-calls/ctfe-id-unlimited.rs | 34 +++++++++ .../ctfe-tail-call-panic.rs | 18 +++++ .../ctfe-tail-call-panic.stderr | 21 ++++++ .../explicit-tail-calls/ctfe-tmp-arg-drop.rs | 23 +++++++ .../ctfe-tmp-arg-drop.stderr | 19 +++++ tests/ui/explicit-tail-calls/unsafeck.rs | 10 +++ tests/ui/explicit-tail-calls/unsafeck.stderr | 11 +++ 49 files changed, 581 insertions(+), 32 deletions(-) create mode 100644 tests/ui/explicit-tail-calls/constck.rs create mode 100644 tests/ui/explicit-tail-calls/constck.stderr create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-move.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr create mode 100644 tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr create mode 100644 tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr create mode 100644 tests/ui/explicit-tail-calls/unsafeck.rs create mode 100644 tests/ui/explicit-tail-calls/unsafeck.stderr 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_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..8138513527810 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1294,6 +1294,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { fn_span, mergeable_succ(), ), + + mir::TerminatorKind::TailCall { .. } => { + // FIXME(explicit_tail_calls): implement tail calls in ssa backend + span_bug!( + terminator.source_info.span, + "`TailCall` terminator is not yet supported by `rustc_codegen_ssa`" + ) + } + mir::TerminatorKind::GeneratorDrop | mir::TerminatorKind::Yield { .. } => { bug!("generator ops in codegen") } 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_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_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index 836a6007839ae..3f56e2f6c8e4c 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -99,12 +99,31 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { BreakableTarget::Return, source_info, ), - ExprKind::Become { value } => this.break_scope( - block, - Some(&this.thir[value]), - BreakableTarget::Become, - source_info, - ), + ExprKind::Become { value } => { + let v = &this.thir[value]; + let ExprKind::Scope { value, .. } = v.kind else { span_bug!(v.span, "what {v:?}") }; + let v = &this.thir[value]; + let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind else { span_bug!(v.span, "what {v:?}") }; + + 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); + + 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/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index 8fbc2afbe70a0..e2cc78bc7bd3c 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -182,7 +182,6 @@ pub(crate) enum BreakableTarget { Continue(region::Scope), Break(region::Scope), Return, - Become, } rustc_index::newtype_index! { @@ -628,10 +627,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { .unwrap_or_else(|| span_bug!(span, "no enclosing breakable scope found")) }; let (break_index, destination) = match target { - BreakableTarget::Return | BreakableTarget::Become => { + BreakableTarget::Return => { let scope = &self.scopes.breakable_scopes[0]; if scope.break_destination != Place::return_place() { - span_bug!(span, "`return` or `become` in item with no return scope"); + span_bug!(span, "`return` in item with no return scope"); } (0, Some(scope.break_destination)) } @@ -1462,6 +1461,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/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_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_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/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..146bae018639e --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr @@ -0,0 +1,13 @@ +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); + | ^^^^^^- `local` dropped here while still borrowed + | | + | borrowed value does not live long enough + +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..8c8ba9562e868 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs @@ -0,0 +1,23 @@ +#![feature(explicit_tail_calls)] + +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 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..f8a607f7b7621 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr @@ -0,0 +1,19 @@ +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/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`. From dee279d5a81f3ad2e07a90365da49ff7e382feeb Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 9 May 2023 10:02:59 +0000 Subject: [PATCH 05/17] llvm ffi: Expose `CallInst->setTailCallKind` --- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 12 ++++++++ .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+) 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_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; }; From 0722a1ec41a4f903707ca2b75d6644f89ac2a1f9 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 9 May 2023 19:15:51 +0000 Subject: [PATCH 06/17] Support tail calls in `rustc_codegen_{ssa,llvm}` --- compiler/rustc_codegen_llvm/src/builder.rs | 24 ++ compiler/rustc_codegen_ssa/src/mir/block.rs | 270 +++++++++++++++++- .../rustc_codegen_ssa/src/traits/builder.rs | 11 + tests/codegen/explicit-tail-calls.rs | 91 ++++++ 4 files changed, 390 insertions(+), 6 deletions(-) create mode 100644 tests/codegen/explicit-tail-calls.rs 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_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index 8138513527810..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>, @@ -1295,12 +1556,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { mergeable_succ(), ), - mir::TerminatorKind::TailCall { .. } => { - // FIXME(explicit_tail_calls): implement tail calls in ssa backend - span_bug!( - terminator.source_info.span, - "`TailCall` terminator is not yet supported by `rustc_codegen_ssa`" - ) + 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 { .. } => { 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/tests/codegen/explicit-tail-calls.rs b/tests/codegen/explicit-tail-calls.rs new file mode 100644 index 0000000000000..0faa3b25abbdf --- /dev/null +++ b/tests/codegen/explicit-tail-calls.rs @@ -0,0 +1,91 @@ +// compile-flags: -C no-prepopulate-passes +// min-llvm-version: 15.0 (for opaque pointers) +#![crate_type = "lib"] +#![feature(explicit_tail_calls)] + +/// 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] +/// 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(); +} From c8b602abe892a699d2c716a4fc91cbdb32e24757 Mon Sep 17 00:00:00 2001 From: DrMeepster <19316085+DrMeepster@users.noreply.github.com> Date: Wed, 14 Jun 2023 01:12:25 -0700 Subject: [PATCH 07/17] handle drops for tail calls --- .../rustc_mir_build/src/build/expr/stmt.rs | 36 +++++----- compiler/rustc_mir_build/src/build/scope.rs | 38 ++++++++++- .../ctfe-arg-bad-borrow.stderr | 7 +- .../explicit-tail-calls/ctfe-tmp-arg-drop.rs | 4 +- .../ctfe-tmp-arg-drop.stderr | 5 +- tests/ui/explicit-tail-calls/drop-order.rs | 67 +++++++++++++++++++ 6 files changed, 132 insertions(+), 25 deletions(-) create mode 100644 tests/ui/explicit-tail-calls/drop-order.rs diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index 3f56e2f6c8e4c..e1705f9fae19a 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -101,28 +101,34 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ), ExprKind::Become { value } => { let v = &this.thir[value]; - let ExprKind::Scope { value, .. } = v.kind else { span_bug!(v.span, "what {v:?}") }; + let ExprKind::Scope { region_scope, lint_level, value, .. } = v.kind else { span_bug!(v.span, "what {v:?}") }; let v = &this.thir[value]; let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind else { span_bug!(v.span, "what {v:?}") }; - 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.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); + this.record_operands_moved(&args); - debug!("expr_into_dest: fn_span={:?}", fn_span); + debug!("expr_into_dest: fn_span={:?}", fn_span); - this.cfg.terminate( - block, - source_info, - TerminatorKind::TailCall { func: fun, args, fn_span }, - ); + unpack!(block = this.break_for_tail_call(block)); - this.cfg.start_new_block().unit() + this.cfg.terminate( + block, + source_info, + TerminatorKind::TailCall { func: fun, args, fn_span }, + ); + + this.cfg.start_new_block().unit() + }) } _ => { assert!( diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index e2cc78bc7bd3c..9ab78416acb53 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -668,8 +668,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { (None, None) => {} } - // FIXME(explicit_tail_calls): this should drop stuff early for `become` - let region_scope = self.scopes.breakable_scopes[break_index].region_scope; let scope_index = self.scopes.scope_index(region_scope, span); let drops = if destination.is_some() { @@ -723,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) { diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr index 146bae018639e..e92df550d7d74 100644 --- a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr +++ b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr @@ -4,9 +4,10 @@ error[E0597]: `local` does not live long enough LL | let local = Type; | ----- binding `local` declared here LL | become takes_borrow(&local); - | ^^^^^^- `local` dropped here while still borrowed - | | - | borrowed value does not live long enough + | ^^^^^^ borrowed value does not live long enough +LL | +LL | } + | - `local` dropped here while still borrowed error: aborting due to previous error diff --git a/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs index 8c8ba9562e868..6ef4f68e91694 100644 --- a/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs +++ b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.rs @@ -1,4 +1,4 @@ -#![feature(explicit_tail_calls)] +#![feature(explicit_tail_calls, const_trait_impl, const_mut_refs)] pub const fn test(_: &View) { const fn takes_view(_: &View) {} @@ -16,7 +16,7 @@ impl HasDrop { } } -impl Drop for HasDrop { +impl const Drop for HasDrop { fn drop(&mut self) {} } diff --git a/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr index f8a607f7b7621..3617f546932a4 100644 --- a/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr +++ b/tests/ui/explicit-tail-calls/ctfe-tmp-arg-drop.stderr @@ -2,9 +2,8 @@ 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 + | ------------------^^^^^^^------------ 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 | 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); + } +} From ced6c93a73a16ac4c8dad7b005a9ae0e4fb79a3d Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 15 Jun 2023 12:45:43 +0000 Subject: [PATCH 08/17] Add more tail call tests --- tests/codegen/explicit-tail-calls.rs | 28 +++++++ .../explicit-tail-calls/become-c-variadic.rs | 20 +++++ .../ui/explicit-tail-calls/caller-location.rs | 18 +++++ tests/ui/explicit-tail-calls/fibonacci.rs | 25 +++++++ tests/ui/explicit-tail-calls/ll.rs | 74 +++++++++++++++++++ tests/ui/explicit-tail-calls/slice-fold.rs | 34 +++++++++ 6 files changed, 199 insertions(+) create mode 100644 tests/ui/explicit-tail-calls/become-c-variadic.rs create mode 100644 tests/ui/explicit-tail-calls/caller-location.rs create mode 100644 tests/ui/explicit-tail-calls/fibonacci.rs create mode 100644 tests/ui/explicit-tail-calls/ll.rs create mode 100644 tests/ui/explicit-tail-calls/slice-fold.rs diff --git a/tests/codegen/explicit-tail-calls.rs b/tests/codegen/explicit-tail-calls.rs index 0faa3b25abbdf..ca41018521038 100644 --- a/tests/codegen/explicit-tail-calls.rs +++ b/tests/codegen/explicit-tail-calls.rs @@ -2,6 +2,7 @@ // 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)] @@ -70,6 +71,33 @@ fn pair_g() -> (u32, u8) { } +#[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] +// CHECK-LABEL: @c_variadic_f(i8 noundef %x, ...) +pub unsafe extern "C" fn c_variadic_f(x: u8, ...) { + // CHECK: musttail call void (i8, ...) @c_variadic_g(i8 noundef %_3, ...) + // CHECK: ret void + become c_variadic_g(x + 1) +} + +#[no_mangle] +pub unsafe extern "C" fn c_variadic_g(_: u8, ...) {} + + #[no_mangle] /// Does `src + dst` in a recursive way // CHECK-LABEL: @flow( 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..68a84f7b060a8 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-c-variadic.rs @@ -0,0 +1,20 @@ +// run-pass +#![feature(explicit_tail_calls)] +#![feature(c_variadic)] + +pub unsafe extern "C" fn c_variadic_f(x: u8, mut args: ...) { + assert_eq!(x, 12); + let (a, b) = (args.arg::(), args.arg::()); + become c_variadic_g(x + 1, a, b, 3u32); +} + +pub unsafe extern "C" fn c_variadic_g(x: u8, mut args: ...) { + assert_eq!(x, 13); + assert_eq!(args.arg::(), 1); + assert_eq!(args.arg::(), 2); + assert_eq!(args.arg::(), 3); +} + +fn main() { + unsafe { c_variadic_f(12u8, 1u32, 2u32) }; +} 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/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/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/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); +} From 20c26dc5113bb9c65a43d3b5b3e32299bc962563 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 11 May 2023 11:43:09 +0000 Subject: [PATCH 09/17] Implement necessary checks for tail calls --- compiler/rustc_middle/src/query/mod.rs | 6 + .../rustc_mir_build/src/build/expr/stmt.rs | 6 +- compiler/rustc_mir_build/src/build/mod.rs | 4 + .../rustc_mir_build/src/check_tail_calls.rs | 382 ++++++++++++++++++ compiler/rustc_mir_build/src/lib.rs | 2 + tests/codegen/explicit-tail-calls.rs | 12 - .../explicit-tail-calls/become-c-variadic.rs | 20 +- .../become-c-variadic.stderr | 26 ++ tests/ui/explicit-tail-calls/become-macro.rs | 12 + .../explicit-tail-calls/become-operator.fixed | 41 ++ .../ui/explicit-tail-calls/become-operator.rs | 41 ++ .../become-operator.stderr | 75 ++++ .../become-uncallable.fixed | 17 + .../explicit-tail-calls/become-uncallable.rs | 17 + .../become-uncallable.stderr | 44 ++ tests/ui/explicit-tail-calls/closure.fixed | 30 ++ tests/ui/explicit-tail-calls/closure.rs | 30 ++ tests/ui/explicit-tail-calls/closure.stderr | 46 +++ tests/ui/explicit-tail-calls/in-closure.rs | 7 + .../ui/explicit-tail-calls/in-closure.stderr | 8 + .../explicit-tail-calls/signature-mismatch.rs | 32 ++ .../signature-mismatch.stderr | 40 ++ 22 files changed, 874 insertions(+), 24 deletions(-) create mode 100644 compiler/rustc_mir_build/src/check_tail_calls.rs create mode 100644 tests/ui/explicit-tail-calls/become-c-variadic.stderr create mode 100644 tests/ui/explicit-tail-calls/become-macro.rs create mode 100644 tests/ui/explicit-tail-calls/become-operator.fixed create mode 100644 tests/ui/explicit-tail-calls/become-operator.rs create mode 100644 tests/ui/explicit-tail-calls/become-operator.stderr create mode 100644 tests/ui/explicit-tail-calls/become-uncallable.fixed create mode 100644 tests/ui/explicit-tail-calls/become-uncallable.rs create mode 100644 tests/ui/explicit-tail-calls/become-uncallable.stderr create mode 100644 tests/ui/explicit-tail-calls/closure.fixed create mode 100644 tests/ui/explicit-tail-calls/closure.rs create mode 100644 tests/ui/explicit-tail-calls/closure.stderr create mode 100644 tests/ui/explicit-tail-calls/in-closure.rs create mode 100644 tests/ui/explicit-tail-calls/in-closure.stderr create mode 100644 tests/ui/explicit-tail-calls/signature-mismatch.rs create mode 100644 tests/ui/explicit-tail-calls/signature-mismatch.stderr 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_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index e1705f9fae19a..981e7159e0c63 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -101,9 +101,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ), ExprKind::Become { value } => { let v = &this.thir[value]; - let ExprKind::Scope { region_scope, lint_level, value, .. } = v.kind else { span_bug!(v.span, "what {v:?}") }; + 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, "what {v:?}") }; + 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])); 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/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/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/tests/codegen/explicit-tail-calls.rs b/tests/codegen/explicit-tail-calls.rs index ca41018521038..e1eb5e1bcf4c8 100644 --- a/tests/codegen/explicit-tail-calls.rs +++ b/tests/codegen/explicit-tail-calls.rs @@ -86,18 +86,6 @@ extern "C" { } -#[no_mangle] -// CHECK-LABEL: @c_variadic_f(i8 noundef %x, ...) -pub unsafe extern "C" fn c_variadic_f(x: u8, ...) { - // CHECK: musttail call void (i8, ...) @c_variadic_g(i8 noundef %_3, ...) - // CHECK: ret void - become c_variadic_g(x + 1) -} - -#[no_mangle] -pub unsafe extern "C" fn c_variadic_g(_: u8, ...) {} - - #[no_mangle] /// Does `src + dst` in a recursive way // CHECK-LABEL: @flow( diff --git a/tests/ui/explicit-tail-calls/become-c-variadic.rs b/tests/ui/explicit-tail-calls/become-c-variadic.rs index 68a84f7b060a8..659c3906976ef 100644 --- a/tests/ui/explicit-tail-calls/become-c-variadic.rs +++ b/tests/ui/explicit-tail-calls/become-c-variadic.rs @@ -1,20 +1,20 @@ -// run-pass #![feature(explicit_tail_calls)] #![feature(c_variadic)] pub unsafe extern "C" fn c_variadic_f(x: u8, mut args: ...) { - assert_eq!(x, 12); - let (a, b) = (args.arg::(), args.arg::()); - become c_variadic_g(x + 1, a, b, 3u32); + 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: ...) { - assert_eq!(x, 13); - assert_eq!(args.arg::(), 1); - assert_eq!(args.arg::(), 2); - assert_eq!(args.arg::(), 3); + become normal(x) + //~^ error: tail-calls are not allowed in c-variadic functions } -fn main() { - unsafe { c_variadic_f(12u8, 1u32, 2u32) }; +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-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/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/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/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 + From 0ace4e6d2dd6864d84c917e1b3c8c3493f93c6f4 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 9 May 2023 21:25:54 +0000 Subject: [PATCH 10/17] `rustc_codegen_cranelift`: bug on tail calls --- compiler/rustc_codegen_cranelift/src/base.rs | 5 +++++ compiler/rustc_codegen_cranelift/src/constant.rs | 1 + 2 files changed, 6 insertions(+) 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 { .. } => {} } } From 03b676484cf947b96792c0d687d053c869c4abcb Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 9 May 2023 21:28:54 +0000 Subject: [PATCH 11/17] `rustc_codegen_gcc`: bug on tail calls --- compiler/rustc_codegen_gcc/src/builder.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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) { From cd7742c69e4badaf16ddb5824c4877fe7b5e4235 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 9 May 2023 21:29:15 +0000 Subject: [PATCH 12/17] Implement `become` expression formatting in rustfmt --- src/tools/rustfmt/src/expr.rs | 1 + src/tools/rustfmt/src/utils.rs | 1 + 2 files changed, 2 insertions(+) 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(..) From 7cf2e9ae69c6db03356ad71cf85061d3a06625e2 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 9 May 2023 21:30:28 +0000 Subject: [PATCH 13/17] Add support for tail calls in clippy --- src/tools/clippy/clippy_lints/src/loops/never_loop.rs | 6 ++++++ .../src/matches/significant_drop_in_scrutinee.rs | 1 + src/tools/clippy/clippy_lints/src/utils/author.rs | 5 +++++ src/tools/clippy/clippy_utils/src/eager_or_lazy.rs | 4 ++++ src/tools/clippy/clippy_utils/src/hir_utils.rs | 3 +++ .../clippy/clippy_utils/src/qualify_min_const_fn.rs | 11 ++--------- src/tools/clippy/clippy_utils/src/sugg.rs | 2 ++ src/tools/clippy/clippy_utils/src/visitors.rs | 1 + 8 files changed, 24 insertions(+), 9 deletions(-) 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(..) From 9afdbe694e1071ec733d0044e9e3c5d632125f36 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 10 May 2023 09:19:31 +0000 Subject: [PATCH 14/17] Document `become` keyword --- library/std/src/keyword_docs.rs | 63 +++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) 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. From 1d8efedb38199f3c8af3de0497ecd7aa895fe878 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 15 May 2023 14:55:08 +0000 Subject: [PATCH 15/17] Add out-of-{async,generators} tests --- tests/ui/explicit-tail-calls/out-of-async.rs | 20 ++++++++++ .../explicit-tail-calls/out-of-async.stderr | 40 +++++++++++++++++++ .../explicit-tail-calls/out-of-generators.rs | 11 +++++ .../out-of-generators.stderr | 11 +++++ 4 files changed, 82 insertions(+) create mode 100644 tests/ui/explicit-tail-calls/out-of-async.rs create mode 100644 tests/ui/explicit-tail-calls/out-of-async.stderr create mode 100644 tests/ui/explicit-tail-calls/out-of-generators.rs create mode 100644 tests/ui/explicit-tail-calls/out-of-generators.stderr 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 + From 65e7db69a1f788402eb7adae3a426eb8b76fef21 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 24 May 2023 16:26:13 +0000 Subject: [PATCH 16/17] Add a test for mismatch in `#[track_caller]` wrt tail calls --- .../caller-location-mismatch.rs | 28 +++++++++++++++++++ .../caller-location-mismatch.stderr | 20 +++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 tests/ui/explicit-tail-calls/caller-location-mismatch.rs create mode 100644 tests/ui/explicit-tail-calls/caller-location-mismatch.stderr 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 + From 965c7b61bf094fbb580b03525a53fe1d513d4703 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 15 Jun 2023 14:04:56 +0000 Subject: [PATCH 17/17] Add some more tail call tests --- tests/ui/explicit-tail-calls/dyn-fn-once.rs | 15 +++++++ .../ui/explicit-tail-calls/dyn-fn-once.stderr | 40 +++++++++++++++++ tests/ui/explicit-tail-calls/fn-pointers.rs | 43 +++++++++++++++++++ tests/ui/explicit-tail-calls/from-main-1.rs | 8 ++++ tests/ui/explicit-tail-calls/from-main-2.rs | 10 +++++ 5 files changed, 116 insertions(+) create mode 100644 tests/ui/explicit-tail-calls/dyn-fn-once.rs create mode 100644 tests/ui/explicit-tail-calls/dyn-fn-once.stderr create mode 100644 tests/ui/explicit-tail-calls/fn-pointers.rs create mode 100644 tests/ui/explicit-tail-calls/from-main-1.rs create mode 100644 tests/ui/explicit-tail-calls/from-main-2.rs 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/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(()) +}