Skip to content

Commit 582f56e

Browse files
asteritemichaeljkleinaakoshhTomAFrench
authored
feat: while statement (#7280)
Co-authored-by: Michael J Klein <michaeljklein@users.noreply.github.com> Co-authored-by: Akosh Farkash <aakoshh@gmail.com> Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> Co-authored-by: Tom French <tom@tomfren.ch>
1 parent b7ace68 commit 582f56e

File tree

20 files changed

+405
-16
lines changed

20 files changed

+405
-16
lines changed

compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs

+44-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use iter_extended::{try_vecmap, vecmap};
1111
use noirc_errors::Location;
1212
use noirc_frontend::ast::{UnaryOp, Visibility};
1313
use noirc_frontend::hir_def::types::Type as HirType;
14-
use noirc_frontend::monomorphization::ast::{self, Expression, Program};
14+
use noirc_frontend::monomorphization::ast::{self, Expression, Program, While};
1515

1616
use crate::{
1717
errors::RuntimeError,
@@ -153,6 +153,7 @@ impl<'a> FunctionContext<'a> {
153153
Expression::Cast(cast) => self.codegen_cast(cast),
154154
Expression::For(for_expr) => self.codegen_for(for_expr),
155155
Expression::Loop(block) => self.codegen_loop(block),
156+
Expression::While(while_) => self.codegen_while(while_),
156157
Expression::If(if_expr) => self.codegen_if(if_expr),
157158
Expression::Tuple(tuple) => self.codegen_tuple(tuple),
158159
Expression::ExtractTupleField(tuple, index) => {
@@ -588,7 +589,7 @@ impl<'a> FunctionContext<'a> {
588589
Ok(Self::unit_value())
589590
}
590591

591-
/// Codegens a loop, creating three new blocks in the process.
592+
/// Codegens a loop, creating two new blocks in the process.
592593
/// The return value of a loop is always a unit literal.
593594
///
594595
/// For example, the loop `loop { body }` is codegen'd as:
@@ -620,6 +621,47 @@ impl<'a> FunctionContext<'a> {
620621
Ok(Self::unit_value())
621622
}
622623

624+
/// Codegens a while loop, creating three new blocks in the process.
625+
/// The return value of a while is always a unit literal.
626+
///
627+
/// For example, the loop `while cond { body }` is codegen'd as:
628+
///
629+
/// ```text
630+
/// jmp while_entry()
631+
/// while_entry:
632+
/// v0 = ... codegen cond ...
633+
/// jmpif v0, then: while_body, else: while_end
634+
/// while_body():
635+
/// v3 = ... codegen body ...
636+
/// jmp while_entry()
637+
/// while_end():
638+
/// ... This is the current insert point after codegen_while finishes ...
639+
/// ```
640+
fn codegen_while(&mut self, while_: &While) -> Result<Values, RuntimeError> {
641+
let while_entry = self.builder.insert_block();
642+
let while_body = self.builder.insert_block();
643+
let while_end = self.builder.insert_block();
644+
645+
self.builder.terminate_with_jmp(while_entry, vec![]);
646+
647+
// Codegen the entry (where the condition is)
648+
self.builder.switch_to_block(while_entry);
649+
let condition = self.codegen_non_tuple_expression(&while_.condition)?;
650+
self.builder.terminate_with_jmpif(condition, while_body, while_end);
651+
652+
self.enter_loop(Loop { loop_entry: while_entry, loop_index: None, loop_end: while_end });
653+
654+
// Codegen the body
655+
self.builder.switch_to_block(while_body);
656+
self.codegen_expression(&while_.body)?;
657+
self.builder.terminate_with_jmp(while_entry, vec![]);
658+
659+
// Finish by switching to the end of the while
660+
self.builder.switch_to_block(while_end);
661+
self.exit_loop();
662+
Ok(Self::unit_value())
663+
}
664+
623665
/// Codegens an if expression, handling the case of what to do if there is no 'else'.
624666
///
625667
/// For example, the expression `if cond { a } else { b }` is codegen'd as:

compiler/noirc_frontend/src/ast/statement.rs

+13-5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pub enum StatementKind {
4646
Assign(AssignStatement),
4747
For(ForLoopStatement),
4848
Loop(Expression, Span /* loop keyword span */),
49+
While(WhileStatement),
4950
Break,
5051
Continue,
5152
/// This statement should be executed at compile-time
@@ -103,11 +104,8 @@ impl StatementKind {
103104
statement.add_semicolon(semi, span, last_statement_in_block, emit_error);
104105
StatementKind::Comptime(statement)
105106
}
106-
// A semicolon on a for loop is optional and does nothing
107-
StatementKind::For(_) => self,
108-
109-
// A semicolon on a loop is optional and does nothing
110-
StatementKind::Loop(..) => self,
107+
// A semicolon on a for loop, loop or while is optional and does nothing
108+
StatementKind::For(_) | StatementKind::Loop(..) | StatementKind::While(..) => self,
111109

112110
// No semicolon needed for a resolved statement
113111
StatementKind::Interned(_) => self,
@@ -856,6 +854,13 @@ pub struct ForLoopStatement {
856854
pub span: Span,
857855
}
858856

857+
#[derive(Debug, PartialEq, Eq, Clone)]
858+
pub struct WhileStatement {
859+
pub condition: Expression,
860+
pub body: Expression,
861+
pub while_keyword_span: Span,
862+
}
863+
859864
impl Display for StatementKind {
860865
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
861866
match self {
@@ -864,6 +869,9 @@ impl Display for StatementKind {
864869
StatementKind::Assign(assign) => assign.fmt(f),
865870
StatementKind::For(for_loop) => for_loop.fmt(f),
866871
StatementKind::Loop(block, _) => write!(f, "loop {}", block),
872+
StatementKind::While(while_) => {
873+
write!(f, "while {} {}", while_.condition, while_.body)
874+
}
867875
StatementKind::Break => write!(f, "break"),
868876
StatementKind::Continue => write!(f, "continue"),
869877
StatementKind::Comptime(statement) => write!(f, "comptime {}", statement.kind),

compiler/noirc_frontend/src/ast/visitor.rs

+10
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ pub trait Visitor {
310310
true
311311
}
312312

313+
fn visit_while_statement(&mut self, _condition: &Expression, _body: &Expression) -> bool {
314+
true
315+
}
316+
313317
fn visit_comptime_statement(&mut self, _: &Statement) -> bool {
314318
true
315319
}
@@ -1165,6 +1169,12 @@ impl Statement {
11651169
block.accept(visitor);
11661170
}
11671171
}
1172+
StatementKind::While(while_) => {
1173+
if visitor.visit_while_statement(&while_.condition, &while_.body) {
1174+
while_.condition.accept(visitor);
1175+
while_.body.accept(visitor);
1176+
}
1177+
}
11681178
StatementKind::Comptime(statement) => {
11691179
if visitor.visit_comptime_statement(statement) {
11701180
statement.accept(visitor);

compiler/noirc_frontend/src/elaborator/lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ fn can_return_without_recursing(interner: &NodeInterner, func_id: FuncId, expr_i
283283
// Rust doesn't seem to check the for loop body (it's bounds might mean it's never called).
284284
HirStatement::For(e) => check(e.start_range) && check(e.end_range),
285285
HirStatement::Loop(e) => check(e),
286+
HirStatement::While(condition, block) => check(condition) && check(block),
286287
HirStatement::Comptime(_)
287288
| HirStatement::Break
288289
| HirStatement::Continue

compiler/noirc_frontend/src/elaborator/statements.rs

+31-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use noirc_errors::{Location, Span};
33
use crate::{
44
ast::{
55
AssignStatement, Expression, ForLoopStatement, ForRange, Ident, ItemVisibility, LValue,
6-
LetStatement, Path, Statement, StatementKind,
6+
LetStatement, Path, Statement, StatementKind, WhileStatement,
77
},
88
hir::{
99
resolution::{
@@ -37,6 +37,7 @@ impl<'context> Elaborator<'context> {
3737
StatementKind::Assign(assign) => self.elaborate_assign(assign),
3838
StatementKind::For(for_stmt) => self.elaborate_for(for_stmt),
3939
StatementKind::Loop(block, span) => self.elaborate_loop(block, span),
40+
StatementKind::While(while_) => self.elaborate_while(while_),
4041
StatementKind::Break => self.elaborate_jump(true, statement.span),
4142
StatementKind::Continue => self.elaborate_jump(false, statement.span),
4243
StatementKind::Comptime(statement) => self.elaborate_comptime_statement(*statement),
@@ -258,6 +259,35 @@ impl<'context> Elaborator<'context> {
258259
(statement, Type::Unit)
259260
}
260261

262+
pub(super) fn elaborate_while(&mut self, while_: WhileStatement) -> (HirStatement, Type) {
263+
let in_constrained_function = self.in_constrained_function();
264+
if in_constrained_function {
265+
self.push_err(ResolverError::WhileInConstrainedFn { span: while_.while_keyword_span });
266+
}
267+
268+
let old_loop = std::mem::take(&mut self.current_loop);
269+
self.current_loop = Some(Loop { is_for: false, has_break: false });
270+
self.push_scope();
271+
272+
let condition_span = while_.condition.span;
273+
let (condition, cond_type) = self.elaborate_expression(while_.condition);
274+
let (block, _block_type) = self.elaborate_expression(while_.body);
275+
276+
self.unify(&cond_type, &Type::Bool, || TypeCheckError::TypeMismatch {
277+
expected_typ: Type::Bool.to_string(),
278+
expr_typ: cond_type.to_string(),
279+
expr_span: condition_span,
280+
});
281+
282+
self.pop_scope();
283+
284+
std::mem::replace(&mut self.current_loop, old_loop).expect("Expected a loop");
285+
286+
let statement = HirStatement::While(condition, block);
287+
288+
(statement, Type::Unit)
289+
}
290+
261291
fn elaborate_jump(&mut self, is_break: bool, span: noirc_errors::Span) -> (HirStatement, Type) {
262292
let in_constrained_function = self.in_constrained_function();
263293

compiler/noirc_frontend/src/hir/comptime/display.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
ForBounds, ForLoopStatement, ForRange, GenericTypeArgs, IfExpression, IndexExpression,
1111
InfixExpression, LValue, Lambda, LetStatement, Literal, MatchExpression,
1212
MemberAccessExpression, MethodCallExpression, Pattern, PrefixExpression, Statement,
13-
StatementKind, UnresolvedType, UnresolvedTypeData,
13+
StatementKind, UnresolvedType, UnresolvedTypeData, WhileStatement,
1414
},
1515
hir_def::traits::TraitConstraint,
1616
node_interner::{InternedStatementKind, NodeInterner},
@@ -766,6 +766,11 @@ fn remove_interned_in_statement_kind(
766766
StatementKind::Loop(block, span) => {
767767
StatementKind::Loop(remove_interned_in_expression(interner, block), span)
768768
}
769+
StatementKind::While(while_) => StatementKind::While(WhileStatement {
770+
condition: remove_interned_in_expression(interner, while_.condition),
771+
body: remove_interned_in_expression(interner, while_.body),
772+
while_keyword_span: while_.while_keyword_span,
773+
}),
769774
StatementKind::Comptime(statement) => {
770775
StatementKind::Comptime(Box::new(remove_interned_in_statement(interner, *statement)))
771776
}

compiler/noirc_frontend/src/hir/comptime/errors.rs

+10
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ pub enum InterpreterError {
5050
typ: Type,
5151
location: Location,
5252
},
53+
NonBoolUsedInWhile {
54+
typ: Type,
55+
location: Location,
56+
},
5357
NonBoolUsedInConstrain {
5458
typ: Type,
5559
location: Location,
@@ -285,6 +289,7 @@ impl InterpreterError {
285289
| InterpreterError::ErrorNodeEncountered { location, .. }
286290
| InterpreterError::NonFunctionCalled { location, .. }
287291
| InterpreterError::NonBoolUsedInIf { location, .. }
292+
| InterpreterError::NonBoolUsedInWhile { location, .. }
288293
| InterpreterError::NonBoolUsedInConstrain { location, .. }
289294
| InterpreterError::FailingConstraint { location, .. }
290295
| InterpreterError::NoMethodFound { location, .. }
@@ -413,6 +418,11 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic {
413418
let secondary = "If conditions must be a boolean value".to_string();
414419
CustomDiagnostic::simple_error(msg, secondary, location.span)
415420
}
421+
InterpreterError::NonBoolUsedInWhile { typ, location } => {
422+
let msg = format!("Expected a `bool` but found `{typ}`");
423+
let secondary = "While conditions must be a boolean value".to_string();
424+
CustomDiagnostic::simple_error(msg, secondary, location.span)
425+
}
416426
InterpreterError::NonBoolUsedInConstrain { typ, location } => {
417427
let msg = format!("Expected a `bool` but found `{typ}`");
418428
CustomDiagnostic::simple_error(msg, String::new(), location.span)

compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::ast::{
66
ConstructorExpression, ExpressionKind, ForLoopStatement, ForRange, GenericTypeArgs, Ident,
77
IfExpression, IndexExpression, InfixExpression, LValue, Lambda, Literal,
88
MemberAccessExpression, MethodCallExpression, Path, PathKind, PathSegment, Pattern,
9-
PrefixExpression, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression,
9+
PrefixExpression, UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, WhileStatement,
1010
};
1111
use crate::ast::{ConstrainExpression, Expression, Statement, StatementKind};
1212
use crate::hir_def::expr::{
@@ -46,6 +46,11 @@ impl HirStatement {
4646
span,
4747
}),
4848
HirStatement::Loop(block) => StatementKind::Loop(block.to_display_ast(interner), span),
49+
HirStatement::While(condition, block) => StatementKind::While(WhileStatement {
50+
condition: condition.to_display_ast(interner),
51+
body: block.to_display_ast(interner),
52+
while_keyword_span: span,
53+
}),
4954
HirStatement::Break => StatementKind::Break,
5055
HirStatement::Continue => StatementKind::Continue,
5156
HirStatement::Expression(expr) => {

compiler/noirc_frontend/src/hir/comptime/interpreter.rs

+51-1
Original file line numberDiff line numberDiff line change
@@ -1516,7 +1516,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
15161516
let condition = match self.evaluate(if_.condition)? {
15171517
Value::Bool(value) => value,
15181518
value => {
1519-
let location = self.elaborator.interner.expr_location(&id);
1519+
let location = self.elaborator.interner.expr_location(&if_.condition);
15201520
let typ = value.get_type().into_owned();
15211521
return Err(InterpreterError::NonBoolUsedInIf { typ, location });
15221522
}
@@ -1571,6 +1571,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
15711571
HirStatement::Assign(assign) => self.evaluate_assign(assign),
15721572
HirStatement::For(for_) => self.evaluate_for(for_),
15731573
HirStatement::Loop(expression) => self.evaluate_loop(expression),
1574+
HirStatement::While(condition, block) => self.evaluate_while(condition, block),
15741575
HirStatement::Break => self.evaluate_break(statement),
15751576
HirStatement::Continue => self.evaluate_continue(statement),
15761577
HirStatement::Expression(expression) => self.evaluate(expression),
@@ -1808,6 +1809,55 @@ impl<'local, 'interner> Interpreter<'local, 'interner> {
18081809
result
18091810
}
18101811

1812+
fn evaluate_while(&mut self, condition: ExprId, block: ExprId) -> IResult<Value> {
1813+
let was_in_loop = std::mem::replace(&mut self.in_loop, true);
1814+
let in_lsp = self.elaborator.interner.is_in_lsp_mode();
1815+
let mut counter = 0;
1816+
let mut result = Ok(Value::Unit);
1817+
1818+
loop {
1819+
let condition = match self.evaluate(condition)? {
1820+
Value::Bool(value) => value,
1821+
value => {
1822+
let location = self.elaborator.interner.expr_location(&condition);
1823+
let typ = value.get_type().into_owned();
1824+
return Err(InterpreterError::NonBoolUsedInWhile { typ, location });
1825+
}
1826+
};
1827+
if !condition {
1828+
break;
1829+
}
1830+
1831+
self.push_scope();
1832+
1833+
let must_break = match self.evaluate(block) {
1834+
Ok(_) => false,
1835+
Err(InterpreterError::Break) => true,
1836+
Err(InterpreterError::Continue) => false,
1837+
Err(error) => {
1838+
result = Err(error);
1839+
true
1840+
}
1841+
};
1842+
1843+
self.pop_scope();
1844+
1845+
if must_break {
1846+
break;
1847+
}
1848+
1849+
counter += 1;
1850+
if in_lsp && counter == 10_000 {
1851+
let location = self.elaborator.interner.expr_location(&block);
1852+
result = Err(InterpreterError::LoopHaltedForUiResponsiveness { location });
1853+
break;
1854+
}
1855+
}
1856+
1857+
self.in_loop = was_in_loop;
1858+
result
1859+
}
1860+
18111861
fn evaluate_break(&mut self, id: StmtId) -> IResult<Value> {
18121862
if self.in_loop {
18131863
Err(InterpreterError::Break)

compiler/noirc_frontend/src/hir/resolution/errors.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ pub enum ResolverError {
102102
LoopInConstrainedFn { span: Span },
103103
#[error("`loop` must have at least one `break` in it")]
104104
LoopWithoutBreak { span: Span },
105+
#[error("`while` is only allowed in unconstrained functions")]
106+
WhileInConstrainedFn { span: Span },
105107
#[error("break/continue are only allowed within loops")]
106108
JumpOutsideLoop { is_break: bool, span: Span },
107109
#[error("Only `comptime` globals can be mutable")]
@@ -442,7 +444,7 @@ impl<'a> From<&'a ResolverError> for Diagnostic {
442444
},
443445
ResolverError::LoopInConstrainedFn { span } => {
444446
Diagnostic::simple_error(
445-
"loop is only allowed in unconstrained functions".into(),
447+
"`loop` is only allowed in unconstrained functions".into(),
446448
"Constrained code must always have a known number of loop iterations".into(),
447449
*span,
448450
)
@@ -454,6 +456,13 @@ impl<'a> From<&'a ResolverError> for Diagnostic {
454456
*span,
455457
)
456458
},
459+
ResolverError::WhileInConstrainedFn { span } => {
460+
Diagnostic::simple_error(
461+
"`while` is only allowed in unconstrained functions".into(),
462+
"Constrained code must always have a known number of loop iterations".into(),
463+
*span,
464+
)
465+
},
457466
ResolverError::JumpOutsideLoop { is_break, span } => {
458467
let item = if *is_break { "break" } else { "continue" };
459468
Diagnostic::simple_error(

compiler/noirc_frontend/src/hir_def/stmt.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub enum HirStatement {
1515
Assign(HirAssignStatement),
1616
For(HirForStatement),
1717
Loop(ExprId),
18+
While(ExprId, ExprId),
1819
Break,
1920
Continue,
2021
Expression(ExprId),

0 commit comments

Comments
 (0)