From 6e9d83e746f50fe2d4a996c7d9fbd79081b40bd9 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 11 Oct 2023 12:20:46 -0700 Subject: [PATCH] Support for `while` statements. (#3258) Basic support for type-checking and lowering of `while` statements, as well as `break` and `continue`. --- toolchain/check/context.h | 13 ++ toolchain/check/handle_loop_statement.cpp | 91 +++++++++-- toolchain/check/node_stack.h | 2 + .../testdata/while/break_continue.carbon | 154 ++++++++++++++++++ .../testdata/while/fail_bad_condition.carbon | 31 ++++ .../testdata/while/fail_break_continue.carbon | 36 ++++ .../testdata/while/unreachable_end.carbon | 62 +++++++ toolchain/check/testdata/while/while.carbon | 61 +++++++ toolchain/diagnostics/diagnostic_kind.def | 2 + toolchain/lower/file_context.cpp | 8 + toolchain/lower/handle.cpp | 1 + .../testdata/while/break_continue.carbon | 66 ++++++++ .../lower/testdata/while/preheader.carbon | 77 +++++++++ .../testdata/while/unreachable_end.carbon | 51 ++++++ toolchain/lower/testdata/while/while.carbon | 50 ++++++ toolchain/sem_ir/formatter.cpp | 17 ++ 16 files changed, 710 insertions(+), 12 deletions(-) create mode 100644 toolchain/check/testdata/while/break_continue.carbon create mode 100644 toolchain/check/testdata/while/fail_bad_condition.carbon create mode 100644 toolchain/check/testdata/while/fail_break_continue.carbon create mode 100644 toolchain/check/testdata/while/unreachable_end.carbon create mode 100644 toolchain/check/testdata/while/while.carbon create mode 100644 toolchain/lower/testdata/while/break_continue.carbon create mode 100644 toolchain/lower/testdata/while/preheader.carbon create mode 100644 toolchain/lower/testdata/while/unreachable_end.carbon create mode 100644 toolchain/lower/testdata/while/while.carbon diff --git a/toolchain/check/context.h b/toolchain/check/context.h index 0b5504e00fd2..3f6f099d3306 100644 --- a/toolchain/check/context.h +++ b/toolchain/check/context.h @@ -21,6 +21,12 @@ namespace Carbon::Check { // Context and shared functionality for semantics handlers. class Context { public: + // A scope in which `break` and `continue` can be used. + struct BreakContinueScope { + SemIR::NodeBlockId break_target; + SemIR::NodeBlockId continue_target; + }; + // Stores references for work. explicit Context(const Lex::TokenizedBuffer& tokens, DiagnosticEmitter& emitter, @@ -186,6 +192,10 @@ class Context { return return_scope_stack_; } + auto break_continue_stack() -> llvm::SmallVector& { + return break_continue_stack_; + } + auto declaration_name_stack() -> DeclarationNameStack& { return declaration_name_stack_; } @@ -273,6 +283,9 @@ class Context { // this will be a FunctionDeclaration. llvm::SmallVector return_scope_stack_; + // A stack of `break` and `continue` targets. + llvm::SmallVector break_continue_stack_; + // A stack for scope context. llvm::SmallVector scope_stack_; diff --git a/toolchain/check/handle_loop_statement.cpp b/toolchain/check/handle_loop_statement.cpp index 11cbc097184f..41e2caf36974 100644 --- a/toolchain/check/handle_loop_statement.cpp +++ b/toolchain/check/handle_loop_statement.cpp @@ -3,25 +3,50 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/context.h" +#include "toolchain/check/convert.h" namespace Carbon::Check { -auto HandleBreakStatement(Context& context, Parse::Node parse_node) -> bool { - return context.TODO(parse_node, "HandleBreakStatement"); +auto HandleBreakStatement(Context& /*context*/, Parse::Node /*parse_node*/) + -> bool { + return true; } auto HandleBreakStatementStart(Context& context, Parse::Node parse_node) -> bool { - return context.TODO(parse_node, "HandleBreakStatementStart"); + auto& stack = context.break_continue_stack(); + if (stack.empty()) { + CARBON_DIAGNOSTIC(BreakOutsideLoop, Error, + "`break` can only be used in a loop."); + context.emitter().Emit(parse_node, BreakOutsideLoop); + } else { + context.AddNode(SemIR::Branch(parse_node, stack.back().break_target)); + } + + context.node_block_stack().Pop(); + context.node_block_stack().PushUnreachable(); + return true; } -auto HandleContinueStatement(Context& context, Parse::Node parse_node) -> bool { - return context.TODO(parse_node, "HandleContinueStatement"); +auto HandleContinueStatement(Context& /*context*/, Parse::Node /*parse_node*/) + -> bool { + return true; } auto HandleContinueStatementStart(Context& context, Parse::Node parse_node) -> bool { - return context.TODO(parse_node, "HandleContinueStatementStart"); + auto& stack = context.break_continue_stack(); + if (stack.empty()) { + CARBON_DIAGNOSTIC(ContinueOutsideLoop, Error, + "`continue` can only be used in a loop."); + context.emitter().Emit(parse_node, ContinueOutsideLoop); + } else { + context.AddNode(SemIR::Branch(parse_node, stack.back().continue_target)); + } + + context.node_block_stack().Pop(); + context.node_block_stack().PushUnreachable(); + return true; } auto HandleForHeader(Context& context, Parse::Node parse_node) -> bool { @@ -40,17 +65,59 @@ auto HandleForStatement(Context& context, Parse::Node parse_node) -> bool { return context.TODO(parse_node, "HandleForStatement"); } -auto HandleWhileCondition(Context& context, Parse::Node parse_node) -> bool { - return context.TODO(parse_node, "HandleWhileCondition"); -} - auto HandleWhileConditionStart(Context& context, Parse::Node parse_node) -> bool { - return context.TODO(parse_node, "HandleWhileConditionStart"); + // Branch to the loop header block. Note that we create a new block here even + // if the current block is empty; this ensures that the loop always has a + // preheader block. + auto loop_header_id = context.AddDominatedBlockAndBranch(parse_node); + context.node_block_stack().Pop(); + + // Start emitting the loop header block. + context.node_block_stack().Push(loop_header_id); + context.AddCurrentCodeBlockToFunction(); + + context.node_stack().Push(parse_node, loop_header_id); + return true; +} + +auto HandleWhileCondition(Context& context, Parse::Node parse_node) -> bool { + auto cond_value_id = context.node_stack().PopExpression(); + auto loop_header_id = + context.node_stack().Peek(); + cond_value_id = ConvertToBoolValue(context, parse_node, cond_value_id); + + // Branch to either the loop body or the loop exit block. + auto loop_body_id = + context.AddDominatedBlockAndBranchIf(parse_node, cond_value_id); + auto loop_exit_id = context.AddDominatedBlockAndBranch(parse_node); + context.node_block_stack().Pop(); + + // Start emitting the loop body. + context.node_block_stack().Push(loop_body_id); + context.AddCurrentCodeBlockToFunction(); + context.break_continue_stack().push_back( + {.break_target = loop_exit_id, .continue_target = loop_header_id}); + + context.node_stack().Push(parse_node, loop_exit_id); + return true; } auto HandleWhileStatement(Context& context, Parse::Node parse_node) -> bool { - return context.TODO(parse_node, "HandleWhileStatement"); + auto loop_exit_id = + context.node_stack().Pop(); + auto loop_header_id = + context.node_stack().Pop(); + context.break_continue_stack().pop_back(); + + // Add the loop backedge. + context.AddNode(SemIR::Branch(parse_node, loop_header_id)); + context.node_block_stack().Pop(); + + // Start emitting the loop exit block. + context.node_block_stack().Push(loop_exit_id); + context.AddCurrentCodeBlockToFunction(); + return true; } } // namespace Carbon::Check diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index eaa28fbeb9d2..99a354dd3b46 100644 --- a/toolchain/check/node_stack.h +++ b/toolchain/check/node_stack.h @@ -264,6 +264,8 @@ class NodeStack { case Parse::NodeKind::IfCondition: case Parse::NodeKind::IfExpressionIf: case Parse::NodeKind::ParameterList: + case Parse::NodeKind::WhileCondition: + case Parse::NodeKind::WhileConditionStart: return IdKind::NodeBlockId; case Parse::NodeKind::FunctionDefinitionStart: return IdKind::FunctionId; diff --git a/toolchain/check/testdata/while/break_continue.carbon b/toolchain/check/testdata/while/break_continue.carbon new file mode 100644 index 000000000000..60795f95263c --- /dev/null +++ b/toolchain/check/testdata/while/break_continue.carbon @@ -0,0 +1,154 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn A() -> bool; +fn B() -> bool; +fn C() -> bool; +fn D() -> bool; +fn E() -> bool; +fn F() -> bool; +fn G() -> bool; +fn H() -> bool; + +fn While() { + while (A()) { + if (B()) { continue; } + if (C()) { break; } + while (D()) { + if (E()) { continue; } + if (F()) { break; } + } + if (G()) { continue; } + if (H()) { break; } + } +} + +// CHECK:STDOUT: file "break_continue.carbon" { +// CHECK:STDOUT: %A = fn_decl @A +// CHECK:STDOUT: %B = fn_decl @B +// CHECK:STDOUT: %C = fn_decl @C +// CHECK:STDOUT: %D = fn_decl @D +// CHECK:STDOUT: %E = fn_decl @E +// CHECK:STDOUT: %F = fn_decl @F +// CHECK:STDOUT: %G = fn_decl @G +// CHECK:STDOUT: %H = fn_decl @H +// CHECK:STDOUT: %While = fn_decl @While +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @A() -> bool; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @B() -> bool; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @C() -> bool; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @D() -> bool; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @E() -> bool; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> bool; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() -> bool; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @H() -> bool; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @While() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: br !while.cond.loc17 +// CHECK:STDOUT: +// CHECK:STDOUT: !while.cond.loc17: +// CHECK:STDOUT: %A.ref = name_reference_untyped "A", package.%A +// CHECK:STDOUT: %.loc17_11.1: init bool = call @A() +// CHECK:STDOUT: %.loc17_11.2: ref bool = temporary_storage +// CHECK:STDOUT: %.loc17_11.3: ref bool = temporary %.loc17_11.2, %.loc17_11.1 +// CHECK:STDOUT: %.loc17_11.4: bool = bind_value %.loc17_11.3 +// CHECK:STDOUT: if %.loc17_11.4 br !while.body.loc17 else br !while.done.loc17 +// CHECK:STDOUT: +// CHECK:STDOUT: !while.body.loc17: +// CHECK:STDOUT: %B.ref = name_reference_untyped "B", package.%B +// CHECK:STDOUT: %.loc18_10.1: init bool = call @B() +// CHECK:STDOUT: %.loc18_10.2: ref bool = temporary_storage +// CHECK:STDOUT: %.loc18_10.3: ref bool = temporary %.loc18_10.2, %.loc18_10.1 +// CHECK:STDOUT: %.loc18_10.4: bool = bind_value %.loc18_10.3 +// CHECK:STDOUT: if %.loc18_10.4 br !if.then.loc18 else br !if.else.loc18 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then.loc18: +// CHECK:STDOUT: br !while.cond.loc17 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else.loc18: +// CHECK:STDOUT: %C.ref = name_reference_untyped "C", package.%C +// CHECK:STDOUT: %.loc19_10.1: init bool = call @C() +// CHECK:STDOUT: %.loc19_10.2: ref bool = temporary_storage +// CHECK:STDOUT: %.loc19_10.3: ref bool = temporary %.loc19_10.2, %.loc19_10.1 +// CHECK:STDOUT: %.loc19_10.4: bool = bind_value %.loc19_10.3 +// CHECK:STDOUT: if %.loc19_10.4 br !if.then.loc19 else br !if.else.loc19 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then.loc19: +// CHECK:STDOUT: br !while.done.loc17 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else.loc19: +// CHECK:STDOUT: br !while.cond.loc20 +// CHECK:STDOUT: +// CHECK:STDOUT: !while.cond.loc20: +// CHECK:STDOUT: %D.ref = name_reference_untyped "D", package.%D +// CHECK:STDOUT: %.loc20_13.1: init bool = call @D() +// CHECK:STDOUT: %.loc20_13.2: ref bool = temporary_storage +// CHECK:STDOUT: %.loc20_13.3: ref bool = temporary %.loc20_13.2, %.loc20_13.1 +// CHECK:STDOUT: %.loc20_13.4: bool = bind_value %.loc20_13.3 +// CHECK:STDOUT: if %.loc20_13.4 br !while.body.loc20 else br !while.done.loc20 +// CHECK:STDOUT: +// CHECK:STDOUT: !while.body.loc20: +// CHECK:STDOUT: %E.ref = name_reference_untyped "E", package.%E +// CHECK:STDOUT: %.loc21_12.1: init bool = call @E() +// CHECK:STDOUT: %.loc21_12.2: ref bool = temporary_storage +// CHECK:STDOUT: %.loc21_12.3: ref bool = temporary %.loc21_12.2, %.loc21_12.1 +// CHECK:STDOUT: %.loc21_12.4: bool = bind_value %.loc21_12.3 +// CHECK:STDOUT: if %.loc21_12.4 br !if.then.loc21 else br !if.else.loc21 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then.loc21: +// CHECK:STDOUT: br !while.cond.loc20 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else.loc21: +// CHECK:STDOUT: %F.ref = name_reference_untyped "F", package.%F +// CHECK:STDOUT: %.loc22_12.1: init bool = call @F() +// CHECK:STDOUT: %.loc22_12.2: ref bool = temporary_storage +// CHECK:STDOUT: %.loc22_12.3: ref bool = temporary %.loc22_12.2, %.loc22_12.1 +// CHECK:STDOUT: %.loc22_12.4: bool = bind_value %.loc22_12.3 +// CHECK:STDOUT: if %.loc22_12.4 br !if.then.loc22 else br !if.else.loc22 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then.loc22: +// CHECK:STDOUT: br !while.done.loc20 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else.loc22: +// CHECK:STDOUT: br !while.cond.loc20 +// CHECK:STDOUT: +// CHECK:STDOUT: !while.done.loc20: +// CHECK:STDOUT: %G.ref = name_reference_untyped "G", package.%G +// CHECK:STDOUT: %.loc24_10.1: init bool = call @G() +// CHECK:STDOUT: %.loc24_10.2: ref bool = temporary_storage +// CHECK:STDOUT: %.loc24_10.3: ref bool = temporary %.loc24_10.2, %.loc24_10.1 +// CHECK:STDOUT: %.loc24_10.4: bool = bind_value %.loc24_10.3 +// CHECK:STDOUT: if %.loc24_10.4 br !if.then.loc24 else br !if.else.loc24 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then.loc24: +// CHECK:STDOUT: br !while.cond.loc17 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else.loc24: +// CHECK:STDOUT: %H.ref = name_reference_untyped "H", package.%H +// CHECK:STDOUT: %.loc25_10.1: init bool = call @H() +// CHECK:STDOUT: %.loc25_10.2: ref bool = temporary_storage +// CHECK:STDOUT: %.loc25_10.3: ref bool = temporary %.loc25_10.2, %.loc25_10.1 +// CHECK:STDOUT: %.loc25_10.4: bool = bind_value %.loc25_10.3 +// CHECK:STDOUT: if %.loc25_10.4 br !if.then.loc25 else br !if.else.loc25 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.then.loc25: +// CHECK:STDOUT: br !while.done.loc17 +// CHECK:STDOUT: +// CHECK:STDOUT: !if.else.loc25: +// CHECK:STDOUT: br !while.cond.loc17 +// CHECK:STDOUT: +// CHECK:STDOUT: !while.done.loc17: +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/while/fail_bad_condition.carbon b/toolchain/check/testdata/while/fail_bad_condition.carbon new file mode 100644 index 000000000000..617d9de220b0 --- /dev/null +++ b/toolchain/check/testdata/while/fail_bad_condition.carbon @@ -0,0 +1,31 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn While() { + // CHECK:STDERR: fail_bad_condition.carbon:[[@LINE+3]]:17: ERROR: Cannot implicitly convert from `String` to `bool`. + // CHECK:STDERR: while ("Hello") {} + // CHECK:STDERR: ^ + while ("Hello") {} +} + +// CHECK:STDOUT: file "fail_bad_condition.carbon" { +// CHECK:STDOUT: %While = fn_decl @While +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @While() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: br !while.cond +// CHECK:STDOUT: +// CHECK:STDOUT: !while.cond: +// CHECK:STDOUT: %.loc11: String = string_literal "Hello" +// CHECK:STDOUT: if br !while.body else br !while.done +// CHECK:STDOUT: +// CHECK:STDOUT: !while.body: +// CHECK:STDOUT: br !while.cond +// CHECK:STDOUT: +// CHECK:STDOUT: !while.done: +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/while/fail_break_continue.carbon b/toolchain/check/testdata/while/fail_break_continue.carbon new file mode 100644 index 000000000000..76f0282e78d0 --- /dev/null +++ b/toolchain/check/testdata/while/fail_break_continue.carbon @@ -0,0 +1,36 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn While() { + // CHECK:STDERR: fail_break_continue.carbon:[[@LINE+3]]:3: ERROR: `continue` can only be used in a loop. + // CHECK:STDERR: continue; + // CHECK:STDERR: ^ + continue; + // CHECK:STDERR: fail_break_continue.carbon:[[@LINE+3]]:3: ERROR: `break` can only be used in a loop. + // CHECK:STDERR: break; + // CHECK:STDERR: ^ + break; + while (false) { + continue; + break; + } + // CHECK:STDERR: fail_break_continue.carbon:[[@LINE+3]]:3: ERROR: `continue` can only be used in a loop. + // CHECK:STDERR: continue; + // CHECK:STDERR: ^ + continue; + // CHECK:STDERR: fail_break_continue.carbon:[[@LINE+3]]:3: ERROR: `break` can only be used in a loop. + // CHECK:STDERR: break; + // CHECK:STDERR: ^ + break; +} + +// CHECK:STDOUT: file "fail_break_continue.carbon" { +// CHECK:STDOUT: %While = fn_decl @While +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @While() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/while/unreachable_end.carbon b/toolchain/check/testdata/while/unreachable_end.carbon new file mode 100644 index 000000000000..ad0934b76511 --- /dev/null +++ b/toolchain/check/testdata/while/unreachable_end.carbon @@ -0,0 +1,62 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn Cond() -> bool; + +fn F(); +fn G(); +fn H(); + +fn While() { + F(); + while (Cond()) { + G(); + return; + } + H(); +} + +// CHECK:STDOUT: file "unreachable_end.carbon" { +// CHECK:STDOUT: %Cond = fn_decl @Cond +// CHECK:STDOUT: %F = fn_decl @F +// CHECK:STDOUT: %G = fn_decl @G +// CHECK:STDOUT: %H = fn_decl @H +// CHECK:STDOUT: %While = fn_decl @While +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Cond() -> bool; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @H(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @While() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref = name_reference_untyped "F", package.%F +// CHECK:STDOUT: %.loc14_4.1: type = tuple_type () +// CHECK:STDOUT: %.loc14_4.2: init () = call @F() +// CHECK:STDOUT: br !while.cond +// CHECK:STDOUT: +// CHECK:STDOUT: !while.cond: +// CHECK:STDOUT: %Cond.ref = name_reference_untyped "Cond", package.%Cond +// CHECK:STDOUT: %.loc15_14.1: init bool = call @Cond() +// CHECK:STDOUT: %.loc15_14.2: ref bool = temporary_storage +// CHECK:STDOUT: %.loc15_14.3: ref bool = temporary %.loc15_14.2, %.loc15_14.1 +// CHECK:STDOUT: %.loc15_14.4: bool = bind_value %.loc15_14.3 +// CHECK:STDOUT: if %.loc15_14.4 br !while.body else br !while.done +// CHECK:STDOUT: +// CHECK:STDOUT: !while.body: +// CHECK:STDOUT: %G.ref = name_reference_untyped "G", package.%G +// CHECK:STDOUT: %.loc16: init () = call @G() +// CHECK:STDOUT: return +// CHECK:STDOUT: +// CHECK:STDOUT: !while.done: +// CHECK:STDOUT: %H.ref = name_reference_untyped "H", package.%H +// CHECK:STDOUT: %.loc19: init () = call @H() +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/while/while.carbon b/toolchain/check/testdata/while/while.carbon new file mode 100644 index 000000000000..c6bc200412ab --- /dev/null +++ b/toolchain/check/testdata/while/while.carbon @@ -0,0 +1,61 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn Cond() -> bool; + +fn F(); +fn G(); +fn H(); + +fn While() { + F(); + while (Cond()) { + G(); + } + H(); +} + +// CHECK:STDOUT: file "while.carbon" { +// CHECK:STDOUT: %Cond = fn_decl @Cond +// CHECK:STDOUT: %F = fn_decl @F +// CHECK:STDOUT: %G = fn_decl @G +// CHECK:STDOUT: %H = fn_decl @H +// CHECK:STDOUT: %While = fn_decl @While +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Cond() -> bool; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @H(); +// CHECK:STDOUT: +// CHECK:STDOUT: fn @While() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref = name_reference_untyped "F", package.%F +// CHECK:STDOUT: %.loc14_4.1: type = tuple_type () +// CHECK:STDOUT: %.loc14_4.2: init () = call @F() +// CHECK:STDOUT: br !while.cond +// CHECK:STDOUT: +// CHECK:STDOUT: !while.cond: +// CHECK:STDOUT: %Cond.ref = name_reference_untyped "Cond", package.%Cond +// CHECK:STDOUT: %.loc15_14.1: init bool = call @Cond() +// CHECK:STDOUT: %.loc15_14.2: ref bool = temporary_storage +// CHECK:STDOUT: %.loc15_14.3: ref bool = temporary %.loc15_14.2, %.loc15_14.1 +// CHECK:STDOUT: %.loc15_14.4: bool = bind_value %.loc15_14.3 +// CHECK:STDOUT: if %.loc15_14.4 br !while.body else br !while.done +// CHECK:STDOUT: +// CHECK:STDOUT: !while.body: +// CHECK:STDOUT: %G.ref = name_reference_untyped "G", package.%G +// CHECK:STDOUT: %.loc16: init () = call @G() +// CHECK:STDOUT: br !while.cond +// CHECK:STDOUT: +// CHECK:STDOUT: !while.done: +// CHECK:STDOUT: %H.ref = name_reference_untyped "H", package.%H +// CHECK:STDOUT: %.loc18: init () = call @H() +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index e5d5b10bfd87..8a794c69ce6a 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -114,6 +114,8 @@ CARBON_DIAGNOSTIC_KIND(AddressOfNonReference) CARBON_DIAGNOSTIC_KIND(ArrayInitFromLiteralArgCountMismatch) CARBON_DIAGNOSTIC_KIND(ArrayInitFromExpressionArgCountMismatch) CARBON_DIAGNOSTIC_KIND(AssignmentToNonAssignable) +CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop) +CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop) CARBON_DIAGNOSTIC_KIND(DereferenceOfNonPointer) CARBON_DIAGNOSTIC_KIND(DereferenceOfType) CARBON_DIAGNOSTIC_KIND(NameNotFound) diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index c5af4a7a7f2a..b0c395bfbdfd 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -186,6 +186,14 @@ auto FileContext::BuildFunctionDefinition(SemIR::FunctionId function_id) function_lowering.builder().SetInsertPoint(llvm_block); function_lowering.LowerBlock(block_id); } + + // LLVM requires that the entry block has no predecessors. + auto* entry_block = &llvm_function->getEntryBlock(); + if (entry_block->hasNPredecessorsOrMore(1)) { + auto* new_entry_block = llvm::BasicBlock::Create( + llvm_context(), "entry", llvm_function, entry_block); + llvm::BranchInst::Create(entry_block, new_entry_block); + } } auto FileContext::BuildType(SemIR::NodeId node_id) -> llvm::Type* { diff --git a/toolchain/lower/handle.cpp b/toolchain/lower/handle.cpp index 77684204f3bd..4cf0d91c08cc 100644 --- a/toolchain/lower/handle.cpp +++ b/toolchain/lower/handle.cpp @@ -74,6 +74,7 @@ auto HandleBoolLiteral(FunctionContext& context, SemIR::NodeId node_id, auto HandleBranch(FunctionContext& context, SemIR::NodeId /*node_id*/, SemIR::Branch node) -> void { // Opportunistically avoid creating a BasicBlock that contains just a branch. + // TODO: Don't do this if it would remove a loop preheader block. llvm::BasicBlock* block = context.builder().GetInsertBlock(); if (block->empty() && context.TryToReuseBlock(node.target_id, block)) { // Reuse this block as the branch target. diff --git a/toolchain/lower/testdata/while/break_continue.carbon b/toolchain/lower/testdata/while/break_continue.carbon new file mode 100644 index 000000000000..ca014b97067d --- /dev/null +++ b/toolchain/lower/testdata/while/break_continue.carbon @@ -0,0 +1,66 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn A() -> bool; +fn B() -> bool; +fn C() -> bool; + +fn While() { + while (A()) { + if (B()) { continue; } + if (C()) { break; } + } +} + +// CHECK:STDOUT: ; ModuleID = 'break_continue.carbon' +// CHECK:STDOUT: source_filename = "break_continue.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare i1 @A() +// CHECK:STDOUT: +// CHECK:STDOUT: declare i1 @B() +// CHECK:STDOUT: +// CHECK:STDOUT: declare i1 @C() +// CHECK:STDOUT: +// CHECK:STDOUT: define void @While() { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: br label %0 +// CHECK:STDOUT: +// CHECK:STDOUT: 0: ; preds = %entry, %8, %4 +// CHECK:STDOUT: %A = call i1 @A() +// CHECK:STDOUT: %temp = alloca i1, align 1 +// CHECK:STDOUT: store i1 %A, ptr %temp, align 1 +// CHECK:STDOUT: %1 = load i1, ptr %temp, align 1 +// CHECK:STDOUT: br i1 %1, label %2, label %9 +// CHECK:STDOUT: +// CHECK:STDOUT: 2: ; preds = %0 +// CHECK:STDOUT: %B = call i1 @B() +// CHECK:STDOUT: %temp1 = alloca i1, align 1 +// CHECK:STDOUT: store i1 %B, ptr %temp1, align 1 +// CHECK:STDOUT: %3 = load i1, ptr %temp1, align 1 +// CHECK:STDOUT: br i1 %3, label %4, label %5 +// CHECK:STDOUT: +// CHECK:STDOUT: 4: ; preds = %2 +// CHECK:STDOUT: br label %0 +// CHECK:STDOUT: +// CHECK:STDOUT: 5: ; preds = %2 +// CHECK:STDOUT: %C = call i1 @C() +// CHECK:STDOUT: %temp2 = alloca i1, align 1 +// CHECK:STDOUT: store i1 %C, ptr %temp2, align 1 +// CHECK:STDOUT: %6 = load i1, ptr %temp2, align 1 +// CHECK:STDOUT: br i1 %6, label %7, label %8 +// CHECK:STDOUT: +// CHECK:STDOUT: 7: ; preds = %5 +// CHECK:STDOUT: br label %9 +// CHECK:STDOUT: +// CHECK:STDOUT: 8: ; preds = %5 +// CHECK:STDOUT: br label %0 +// CHECK:STDOUT: +// CHECK:STDOUT: 9: ; preds = %7, %0 +// CHECK:STDOUT: ret void +// CHECK:STDOUT: +// CHECK:STDOUT: ; uselistorder directives +// CHECK:STDOUT: uselistorder label %0, { 1, 2, 0 } +// CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/while/preheader.carbon b/toolchain/lower/testdata/while/preheader.carbon new file mode 100644 index 000000000000..22dceb127c65 --- /dev/null +++ b/toolchain/lower/testdata/while/preheader.carbon @@ -0,0 +1,77 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn Cond() -> bool; + +fn F(); +fn G(); + +// TODO: It would be better to preserve the loop preheader blocks from SemIR +// into LLVM IR. If we don't, LLVM will recreate them for us as part of loop +// canonicalization. +fn While() { + while (Cond()) { + F(); + } + + if (Cond()) { + while (Cond()) { + G(); + } + } +} + +// CHECK:STDOUT: ; ModuleID = 'preheader.carbon' +// CHECK:STDOUT: source_filename = "preheader.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare i1 @Cond() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @F() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @G() +// CHECK:STDOUT: +// CHECK:STDOUT: define void @While() { +// CHECK:STDOUT: entry: +// CHECK:STDOUT: br label %0 +// CHECK:STDOUT: +// CHECK:STDOUT: 0: ; preds = %entry, %2 +// CHECK:STDOUT: %Cond = call i1 @Cond() +// CHECK:STDOUT: %temp = alloca i1, align 1 +// CHECK:STDOUT: store i1 %Cond, ptr %temp, align 1 +// CHECK:STDOUT: %1 = load i1, ptr %temp, align 1 +// CHECK:STDOUT: br i1 %1, label %2, label %3 +// CHECK:STDOUT: +// CHECK:STDOUT: 2: ; preds = %0 +// CHECK:STDOUT: call void @F() +// CHECK:STDOUT: br label %0 +// CHECK:STDOUT: +// CHECK:STDOUT: 3: ; preds = %0 +// CHECK:STDOUT: %Cond1 = call i1 @Cond() +// CHECK:STDOUT: %temp2 = alloca i1, align 1 +// CHECK:STDOUT: store i1 %Cond1, ptr %temp2, align 1 +// CHECK:STDOUT: %4 = load i1, ptr %temp2, align 1 +// CHECK:STDOUT: br i1 %4, label %5, label %9 +// CHECK:STDOUT: +// CHECK:STDOUT: 5: ; preds = %7, %3 +// CHECK:STDOUT: %Cond3 = call i1 @Cond() +// CHECK:STDOUT: %temp4 = alloca i1, align 1 +// CHECK:STDOUT: store i1 %Cond3, ptr %temp4, align 1 +// CHECK:STDOUT: %6 = load i1, ptr %temp4, align 1 +// CHECK:STDOUT: br i1 %6, label %7, label %8 +// CHECK:STDOUT: +// CHECK:STDOUT: 7: ; preds = %5 +// CHECK:STDOUT: call void @G() +// CHECK:STDOUT: br label %5 +// CHECK:STDOUT: +// CHECK:STDOUT: 8: ; preds = %5 +// CHECK:STDOUT: br label %9 +// CHECK:STDOUT: +// CHECK:STDOUT: 9: ; preds = %8, %3 +// CHECK:STDOUT: ret void +// CHECK:STDOUT: +// CHECK:STDOUT: ; uselistorder directives +// CHECK:STDOUT: uselistorder label %0, { 1, 0 } +// CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/while/unreachable_end.carbon b/toolchain/lower/testdata/while/unreachable_end.carbon new file mode 100644 index 000000000000..ef710601c6a5 --- /dev/null +++ b/toolchain/lower/testdata/while/unreachable_end.carbon @@ -0,0 +1,51 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn Cond() -> bool; + +fn F(); +fn G(); +fn H(); + +fn While() { + F(); + while (Cond()) { + G(); + return; + } + H(); +} + +// CHECK:STDOUT: ; ModuleID = 'unreachable_end.carbon' +// CHECK:STDOUT: source_filename = "unreachable_end.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare i1 @Cond() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @F() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @G() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @H() +// CHECK:STDOUT: +// CHECK:STDOUT: define void @While() { +// CHECK:STDOUT: call void @F() +// CHECK:STDOUT: br label %1 +// CHECK:STDOUT: +// CHECK:STDOUT: 1: ; preds = %0 +// CHECK:STDOUT: %Cond = call i1 @Cond() +// CHECK:STDOUT: %temp = alloca i1, align 1 +// CHECK:STDOUT: store i1 %Cond, ptr %temp, align 1 +// CHECK:STDOUT: %2 = load i1, ptr %temp, align 1 +// CHECK:STDOUT: br i1 %2, label %3, label %4 +// CHECK:STDOUT: +// CHECK:STDOUT: 3: ; preds = %1 +// CHECK:STDOUT: call void @G() +// CHECK:STDOUT: ret void +// CHECK:STDOUT: +// CHECK:STDOUT: 4: ; preds = %1 +// CHECK:STDOUT: call void @H() +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/while/while.carbon b/toolchain/lower/testdata/while/while.carbon new file mode 100644 index 000000000000..64a173bb72e3 --- /dev/null +++ b/toolchain/lower/testdata/while/while.carbon @@ -0,0 +1,50 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +fn Cond() -> bool; + +fn F(); +fn G(); +fn H(); + +fn While() { + F(); + while (Cond()) { + G(); + } + H(); +} + +// CHECK:STDOUT: ; ModuleID = 'while.carbon' +// CHECK:STDOUT: source_filename = "while.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: declare i1 @Cond() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @F() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @G() +// CHECK:STDOUT: +// CHECK:STDOUT: declare void @H() +// CHECK:STDOUT: +// CHECK:STDOUT: define void @While() { +// CHECK:STDOUT: call void @F() +// CHECK:STDOUT: br label %1 +// CHECK:STDOUT: +// CHECK:STDOUT: 1: ; preds = %3, %0 +// CHECK:STDOUT: %Cond = call i1 @Cond() +// CHECK:STDOUT: %temp = alloca i1, align 1 +// CHECK:STDOUT: store i1 %Cond, ptr %temp, align 1 +// CHECK:STDOUT: %2 = load i1, ptr %temp, align 1 +// CHECK:STDOUT: br i1 %2, label %3, label %4 +// CHECK:STDOUT: +// CHECK:STDOUT: 3: ; preds = %1 +// CHECK:STDOUT: call void @G() +// CHECK:STDOUT: br label %1 +// CHECK:STDOUT: +// CHECK:STDOUT: 4: ; preds = %1 +// CHECK:STDOUT: call void @H() +// CHECK:STDOUT: ret void +// CHECK:STDOUT: } diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index 84afd9d303e2..61f714624ce5 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -315,6 +315,23 @@ class NodeNamer { break; } + case Parse::NodeKind::WhileConditionStart: + name = "while.cond"; + break; + + case Parse::NodeKind::WhileCondition: + switch (node.kind()) { + case NodeKind::BranchIf: + name = "while.body"; + break; + case NodeKind::Branch: + name = "while.done"; + break; + default: + break; + } + break; + default: break; }