Skip to content

Add #[loop_match] for improved DFA codegen #138780

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7402926
Add `#[loop_match]` for improved DFA codegen
bjorn3 Feb 18, 2025
fa7bf90
Apply suggestions from code review
folkertdev Mar 22, 2025
3ee3f82
add comments to tests
folkertdev Mar 22, 2025
4f1b34b
remove some comments that are now inaccurate
folkertdev Mar 22, 2025
22835d5
static pattern matching when lowering `#[const_continue]`
folkertdev Mar 21, 2025
96be62b
clarify an unreachable branch
folkertdev Mar 24, 2025
05e6499
add error for unknown jump target
folkertdev Mar 24, 2025
9d12b9f
emit an error when a match arm has a guard
folkertdev Mar 24, 2025
8fc6368
store the span of the match expression, and use it for diagnostics
folkertdev Mar 24, 2025
358c733
add comments
folkertdev Mar 24, 2025
ee227c2
use `Size::truncate`
folkertdev Mar 28, 2025
c16c2f1
refactor `break_scope`
folkertdev Mar 28, 2025
98038c0
Handle drop in unwind paths for #[const_continue]
bjorn3 Apr 1, 2025
8b9e822
fix comment
folkertdev Mar 28, 2025
457a4a0
emit error when #[const_continue] jumps to an invalid label
folkertdev Mar 28, 2025
463dbdf
delay most error handing until later
folkertdev Mar 28, 2025
67220bb
Fix some comments
folkertdev Apr 1, 2025
9975752
fix docs for `ConstContinuableScope`
folkertdev Apr 1, 2025
8d4b9d6
[WIP] Support const blocks in const_continue
bjorn3 Mar 25, 2025
cf9eea8
Handle plain enum values
bjorn3 Mar 25, 2025
248b9a8
refactor valtree logic
folkertdev Apr 2, 2025
9b600fb
error on unsupported state type
folkertdev Apr 2, 2025
c3b4c9b
remove an unwrap
folkertdev Apr 2, 2025
178a267
use `span_bug` instead of `todo`
folkertdev Apr 2, 2025
31ad23b
simplify logic for when a #[loop_match] state type is valid
folkertdev Apr 3, 2025
2dc344c
support boolean and character patterns
folkertdev Apr 4, 2025
3e1f56f
Apply suggestions from code review
folkertdev Apr 6, 2025
8ef07b1
support float patterns
folkertdev Apr 4, 2025
1bd73c2
emit an error for constants that are too generic
folkertdev Apr 4, 2025
9cd9e87
simplify thir visitor
folkertdev Apr 8, 2025
2361fd0
add thir print test
folkertdev Apr 8, 2025
15d3095
make the drop have a side-effect
folkertdev Apr 8, 2025
94210a0
tweak a comment
folkertdev Apr 8, 2025
8ee6e7b
Add assertion to loop-match/drop-in-march-arm.rs
bjorn3 Apr 8, 2025
2ea5bc8
add unstable book entry
folkertdev Apr 22, 2025
244a754
accept macros inside the labeled block before the match
folkertdev May 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,19 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
EncodeCrossCrate::Yes, min_generic_const_args, experimental!(type_const),
),

// The `#[loop_match]` and `#[const_continue]` attributes are part of the
// lang experiment for RFC 3720 tracked in:
//
// - https://github.com/rust-lang/rust/issues/132306
gated!(
const_continue, Normal, template!(Word), ErrorFollowing,
EncodeCrossCrate::No, loop_match, experimental!(const_continue)
),
gated!(
loop_match, Normal, template!(Word), ErrorFollowing,
EncodeCrossCrate::No, loop_match, experimental!(loop_match)
),

// ==========================================================================
// Internal attributes: Stability, deprecation, and unsafe:
// ==========================================================================
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,8 @@ declare_features! (
/// Allows using `#[link(kind = "link-arg", name = "...")]`
/// to pass custom arguments to the linker.
(unstable, link_arg_attribute, "1.76.0", Some(99427)),
/// Allows fused `loop`/`match` for direct intraprocedural jumps.
(incomplete, loop_match, "CURRENT_RUSTC_VERSION", Some(132306)),
/// Give access to additional metadata about declarative macro meta-variables.
(unstable, macro_metavar_expr, "1.61.0", Some(83527)),
/// Provides a way to concatenate identifiers using metavariable expressions.
Expand Down
13 changes: 13 additions & 0 deletions compiler/rustc_middle/src/thir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,14 @@ pub enum ExprKind<'tcx> {
Loop {
body: ExprId,
},
/// A `#[loop_match] loop { state = 'blk: { match state { ... } } }` expression.
LoopMatch {
/// The state variable that is updated, and also the scrutinee of the match.
state: ExprId,
region_scope: region::Scope,
arms: Box<[ArmId]>,
match_span: Span,
},
/// Special expression representing the `let` part of an `if let` or similar construct
/// (including `if let` guards in match arms, and let-chains formed by `&&`).
///
Expand Down Expand Up @@ -451,6 +459,11 @@ pub enum ExprKind<'tcx> {
Continue {
label: region::Scope,
},
/// A `#[const_continue] break` expression.
ConstContinue {
label: region::Scope,
value: ExprId,
},
/// A `return` expression.
Return {
value: Option<ExprId>,
Expand Down
3 changes: 2 additions & 1 deletion compiler/rustc_middle/src/thir/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>(
visitor.visit_pat(pat);
}
Loop { body } => visitor.visit_expr(&visitor.thir()[body]),
Match { scrutinee, ref arms, .. } => {
LoopMatch { state: scrutinee, ref arms, .. } | Match { scrutinee, ref arms, .. } => {
visitor.visit_expr(&visitor.thir()[scrutinee]);
for &arm in &**arms {
visitor.visit_arm(&visitor.thir()[arm]);
Expand All @@ -104,6 +104,7 @@ pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>(
}
}
Continue { label: _ } => {}
ConstContinue { value, label: _ } => visitor.visit_expr(&visitor.thir()[value]),
Return { value } => {
if let Some(value) = value {
visitor.visit_expr(&visitor.thir()[value])
Expand Down
33 changes: 33 additions & 0 deletions compiler/rustc_mir_build/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ mir_build_call_to_unsafe_fn_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =

mir_build_confused = missing patterns are not covered because `{$variable}` is interpreted as a constant pattern, not a new variable

mir_build_const_continue_bad_const = could not determine the target branch for this `#[const_continue]`
.label = this value is too generic
.note = the value must be a literal or a monomorphic const

mir_build_const_continue_missing_value = a `#[const_continue]` must break to a label with a value

mir_build_const_continue_unknown_jump_target = the target of this `#[const_continue]` is not statically known
.label = this value must be a literal or a monomorphic const

mir_build_const_defined_here = constant defined here

mir_build_const_param_in_pattern = constant parameters cannot be referenced in patterns
Expand Down Expand Up @@ -212,6 +221,30 @@ mir_build_literal_in_range_out_of_bounds =
literal out of range for `{$ty}`
.label = this value does not fit into the type `{$ty}` whose range is `{$min}..={$max}`

mir_build_loop_match_arm_with_guard =
match arms that are part of a `#[loop_match]` cannot have guards

mir_build_loop_match_bad_rhs =
this expression must be a single `match` wrapped in a labeled block

mir_build_loop_match_bad_statements =
statements are not allowed in this position within a `#[loop_match]`

mir_build_loop_match_invalid_match =
invalid match on `#[loop_match]` state
.note = a local variable must be the scrutinee within a `#[loop_match]`

mir_build_loop_match_invalid_update =
invalid update of the `#[loop_match]` state
.label = the assignment must update this variable

mir_build_loop_match_missing_assignment =
expected a single assignment expression

mir_build_loop_match_unsupported_type =
this `#[loop_match]` state value has type `{$ty}`, which is not supported
.note = only integers, floats, bool, char, and enums without fields are supported

mir_build_lower_range_bound_must_be_less_than_or_equal_to_upper =
lower range bound must be less than or equal to upper
.label = lower bound larger than upper bound
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_mir_build/src/builder/expr/as_place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,12 +562,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
| ExprKind::Match { .. }
| ExprKind::If { .. }
| ExprKind::Loop { .. }
| ExprKind::LoopMatch { .. }
| ExprKind::Block { .. }
| ExprKind::Let { .. }
| ExprKind::Assign { .. }
| ExprKind::AssignOp { .. }
| ExprKind::Break { .. }
| ExprKind::Continue { .. }
| ExprKind::ConstContinue { .. }
| ExprKind::Return { .. }
| ExprKind::Become { .. }
| ExprKind::Literal { .. }
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_mir_build/src/builder/expr/as_rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
| ExprKind::RawBorrow { .. }
| ExprKind::Adt { .. }
| ExprKind::Loop { .. }
| ExprKind::LoopMatch { .. }
| ExprKind::LogicalOp { .. }
| ExprKind::Call { .. }
| ExprKind::Field { .. }
Expand All @@ -548,6 +549,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
| ExprKind::UpvarRef { .. }
| ExprKind::Break { .. }
| ExprKind::Continue { .. }
| ExprKind::ConstContinue { .. }
| ExprKind::Return { .. }
| ExprKind::Become { .. }
| ExprKind::InlineAsm { .. }
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_mir_build/src/builder/expr/category.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ impl Category {
| ExprKind::NamedConst { .. } => Some(Category::Constant),

ExprKind::Loop { .. }
| ExprKind::LoopMatch { .. }
| ExprKind::Block { .. }
| ExprKind::Break { .. }
| ExprKind::Continue { .. }
| ExprKind::ConstContinue { .. }
| ExprKind::Return { .. }
| ExprKind::Become { .. } =>
// FIXME(#27840) these probably want their own
Expand Down
122 changes: 120 additions & 2 deletions compiler/rustc_mir_build/src/builder/expr/into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ use rustc_hir::lang_items::LangItem;
use rustc_middle::mir::*;
use rustc_middle::span_bug;
use rustc_middle::thir::*;
use rustc_middle::ty::{CanonicalUserTypeAnnotation, Ty};
use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty};
use rustc_span::DUMMY_SP;
use rustc_span::source_map::Spanned;
use rustc_trait_selection::infer::InferCtxtExt;
use tracing::{debug, instrument};

use crate::builder::expr::category::{Category, RvalueFunc};
use crate::builder::matches::DeclareLetBindings;
use crate::builder::matches::{DeclareLetBindings, HasMatchGuard};
use crate::builder::{BlockAnd, BlockAndExtension, BlockFrame, Builder, NeedsTemporary};
use crate::errors::{LoopMatchArmWithGuard, LoopMatchUnsupportedType};

impl<'a, 'tcx> Builder<'a, 'tcx> {
/// Compile `expr`, storing the result into `destination`, which
Expand Down Expand Up @@ -244,6 +245,122 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
None
})
}
ExprKind::LoopMatch { state, region_scope, match_span, ref arms } => {
// Intuitively, this is a combination of a loop containing a labeled block
// containing a match.
//
// The only new bit here is that the lowering of the match is wrapped in a
// `in_const_continuable_scope`, which makes the match arms and their target basic
// block available to the lowering of `#[const_continue]`.

fn is_supported_loop_match_type(ty: Ty<'_>) -> bool {
match ty.kind() {
ty::Uint(_) | ty::Int(_) | ty::Float(_) | ty::Bool | ty::Char => true,
ty::Adt(adt_def, _) => match adt_def.adt_kind() {
ty::AdtKind::Struct | ty::AdtKind::Union => false,
ty::AdtKind::Enum => {
adt_def.variants().iter().all(|v| v.fields.is_empty())
}
},
_ => false,
}
}

let state_ty = this.thir.exprs[state].ty;
if !is_supported_loop_match_type(state_ty) {
let span = this.thir.exprs[state].span;
this.tcx.dcx().emit_fatal(LoopMatchUnsupportedType { span, ty: state_ty })
}

let loop_block = this.cfg.start_new_block();

// Start the loop.
this.cfg.goto(block, source_info, loop_block);

this.in_breakable_scope(Some(loop_block), destination, expr_span, |this| {
// Logic for `loop`.
let mut body_block = this.cfg.start_new_block();
this.cfg.terminate(
loop_block,
source_info,
TerminatorKind::FalseUnwind {
real_target: body_block,
unwind: UnwindAction::Continue,
},
);
this.diverge_from(loop_block);

// Logic for `match`.
let scrutinee_place_builder =
unpack!(body_block = this.as_place_builder(body_block, state));
let scrutinee_span = this.thir.exprs[state].span;
let match_start_span = match_span.shrink_to_lo().to(scrutinee_span);

let mut patterns = Vec::with_capacity(arms.len());
for &arm_id in arms.iter() {
let arm = &this.thir[arm_id];

if let Some(guard) = arm.guard {
let span = this.thir.exprs[guard].span;
this.tcx.dcx().emit_fatal(LoopMatchArmWithGuard { span })
}

patterns.push((&*arm.pattern, HasMatchGuard::No));
}

// The `built_tree` maps match arms to their basic block (where control flow
// jumps to when a value matches the arm). This structure is stored so that a
// `#[const_continue]` can figure out what basic block to jump to.
let built_tree = this.lower_match_tree(
body_block,
scrutinee_span,
&scrutinee_place_builder,
match_start_span,
patterns,
false,
);

let state_place = scrutinee_place_builder.to_place(this);

// This is logic for the labeled block: a block is a drop scope, hence
// `in_scope`, and a labeled block can be broken out of with a `break 'label`,
// hence the `in_breakable_scope`.
//
// Then `in_const_continuable_scope` stores information for the lowering of
// `#[const_continue]`, and finally the match is lowered in the standard way.
unpack!(
body_block = this.in_scope(
(region_scope, source_info),
LintLevel::Inherited,
move |this| {
this.in_breakable_scope(None, state_place, expr_span, |this| {
Some(this.in_const_continuable_scope(
arms.clone(),
built_tree.clone(),
state_place,
expr_span,
|this| {
this.lower_match_arms(
destination,
scrutinee_place_builder,
scrutinee_span,
arms,
built_tree,
this.source_info(match_span),
)
},
))
})
}
)
);

this.cfg.goto(body_block, source_info, loop_block);

// Loops are only exited by `break` expressions.
None
})
}
ExprKind::Call { ty: _, fun, ref args, from_hir_call, fn_span } => {
let fun = unpack!(block = this.as_local_operand(block, fun));
let args: Box<[_]> = args
Expand Down Expand Up @@ -601,6 +718,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
}

ExprKind::Continue { .. }
| ExprKind::ConstContinue { .. }
| ExprKind::Break { .. }
| ExprKind::Return { .. }
| ExprKind::Become { .. } => {
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_mir_build/src/builder/expr/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
ExprKind::Break { label, value } => {
this.break_scope(block, value, BreakableTarget::Break(label), source_info)
}
ExprKind::ConstContinue { label, value } => {
this.break_const_continuable_scope(block, value, label, source_info)
}
ExprKind::Return { value } => {
this.break_scope(block, value, BreakableTarget::Return, source_info)
}
Expand Down
Loading
Loading