Skip to content

Commit 8990356

Browse files
committed
[compiler-v2] Add loop labels to the language
Besides the user being able to describe more complex algorithms more efficiently, loop labels are required to express any reducible control flow in the AST language, and create parity of the AST with the bytecode level for this kind of code (which is also what can be generated from Move).
1 parent 867fce7 commit 8990356

25 files changed

+461
-59
lines changed

third_party/move/move-compiler-v2/src/bytecode_generator.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -447,11 +447,11 @@ impl<'env> Generator<'env> {
447447
self.emit_with(*id, |attr| Bytecode::Jump(attr, continue_label));
448448
self.emit_with(*id, |attr| Bytecode::Label(attr, break_label));
449449
},
450-
ExpData::LoopCont(id, 0, do_continue) => {
450+
ExpData::LoopCont(id, nest, do_continue) => {
451451
if let Some(LoopContext {
452452
continue_label,
453453
break_label,
454-
}) = self.loops.last()
454+
}) = self.loops.iter().rev().nth(*nest)
455455
{
456456
let target = if *do_continue {
457457
*continue_label
@@ -463,9 +463,6 @@ impl<'env> Generator<'env> {
463463
self.error(*id, "missing enclosing loop statement")
464464
}
465465
},
466-
ExpData::LoopCont(_, _, _) => {
467-
unimplemented!("continue/break with nesting")
468-
},
469466
ExpData::SpecBlock(id, spec) => {
470467
// Map locals in spec to assigned temporaries.
471468
let mut replacer = |id, target| {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// -- Model dump before bytecode pipeline
2+
module 0x815::test {
3+
private fun f1() {
4+
loop {
5+
loop {
6+
loop {
7+
if true {
8+
loop {
9+
if false {
10+
continue[3]
11+
} else {
12+
break[1]
13+
};
14+
break
15+
}
16+
} else {
17+
continue[2]
18+
}
19+
}
20+
};
21+
break
22+
}
23+
}
24+
} // end 0x815::test
25+
26+
// -- Sourcified model before bytecode pipeline
27+
module 0x815::test {
28+
fun f1() {
29+
'l0: loop {
30+
loop 'l1: loop if (true) loop {
31+
if (false) continue 'l0 else break 'l1;
32+
break
33+
} else continue 'l0;
34+
break
35+
}
36+
}
37+
}
38+
39+
============ initial bytecode ================
40+
41+
[variant baseline]
42+
fun test::f1() {
43+
var $t0: bool
44+
var $t1: bool
45+
0: label L0
46+
1: label L2
47+
2: label L4
48+
3: $t0 := true
49+
4: if ($t0) goto 5 else goto 19
50+
5: label L6
51+
6: label L9
52+
7: $t1 := false
53+
8: if ($t1) goto 9 else goto 12
54+
9: label L11
55+
10: goto 0
56+
11: goto 14
57+
12: label L12
58+
13: goto 23
59+
14: label L13
60+
15: goto 17
61+
16: goto 6
62+
17: label L10
63+
18: goto 21
64+
19: label L7
65+
20: goto 0
66+
21: label L8
67+
22: goto 2
68+
23: label L5
69+
24: goto 1
70+
25: label L3
71+
26: goto 28
72+
27: goto 0
73+
28: label L1
74+
29: return ()
75+
}
76+
77+
78+
============ bytecode verification succeeded ========
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module 0x815::test {
2+
fun f1() {
3+
'outer: loop {
4+
// unlabeled loop, but counts in nesting in AST
5+
loop {
6+
'inner: loop if (true) loop {
7+
if (false) continue 'outer else break 'inner;
8+
break
9+
} else continue 'outer
10+
};
11+
break
12+
}
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
Diagnostics:
3+
error: unsupported language construct
4+
┌─ tests/checking-lang-v1/loop_labels.move:3:9
5+
6+
3 │ 'outer: loop {
7+
│ ^^^^^^ Move language construct `'outer` is not enabled until version 2.1
8+
9+
error: unsupported language construct
10+
┌─ tests/checking-lang-v1/loop_labels.move:6:17
11+
12+
6 │ 'inner: loop if (true) loop {
13+
│ ^^^^^^ Move language construct `'inner` is not enabled until version 2.1
14+
15+
error: unsupported language construct
16+
┌─ tests/checking-lang-v1/loop_labels.move:7:41
17+
18+
7 │ if (false) continue 'outer else break 'inner;
19+
│ ^^^^^^ Move language construct `'outer` is not enabled until version 2.1
20+
21+
error: unsupported language construct
22+
┌─ tests/checking-lang-v1/loop_labels.move:7:59
23+
24+
7 │ if (false) continue 'outer else break 'inner;
25+
│ ^^^^^^ Move language construct `'inner` is not enabled until version 2.1
26+
27+
error: unsupported language construct
28+
┌─ tests/checking-lang-v1/loop_labels.move:9:33
29+
30+
9 │ } else continue 'outer
31+
│ ^^^^^^ Move language construct `'outer` is not enabled until version 2.1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module 0x815::test {
2+
fun f1() {
3+
'outer: loop {
4+
// unlabeled loop, but counts in nesting in AST
5+
loop {
6+
'inner: loop if (true) loop {
7+
if (false) continue 'outer else break 'inner;
8+
break
9+
} else continue 'outer
10+
};
11+
break
12+
}
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
Diagnostics:
3+
error: label `'outer` undefined
4+
┌─ tests/checking/control_flow/loop_labels_check_err.move:3:15
5+
6+
3 │ break 'outer;
7+
│ ^^^^^^
8+
9+
error: label `'inner` undefined
10+
┌─ tests/checking/control_flow/loop_labels_check_err.move:5:19
11+
12+
5 │ break 'inner
13+
│ ^^^^^^
14+
15+
error: label `'l1` already used by outer loop
16+
┌─ tests/checking/control_flow/loop_labels_check_err.move:11:19
17+
18+
11 │ 'l1: loop 'l1: loop {};
19+
│ --- ^^^
20+
│ │
21+
│ outer definition of label
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module 0x815::test {
2+
fun undefined_label() {
3+
break 'outer;
4+
'outer: loop {
5+
break 'inner
6+
}
7+
}
8+
9+
fun duplicate_label() {
10+
'l1: loop {};
11+
'l1: loop 'l1: loop {};
12+
'l1: loop {}
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// -- Model dump before bytecode pipeline
2+
module 0x815::test {
3+
private fun f1() {
4+
loop {
5+
loop {
6+
loop {
7+
if true {
8+
loop {
9+
if false {
10+
continue[3]
11+
} else {
12+
break[1]
13+
};
14+
break
15+
}
16+
} else {
17+
continue[2]
18+
}
19+
}
20+
};
21+
break
22+
}
23+
}
24+
} // end 0x815::test
25+
26+
// -- Sourcified model before bytecode pipeline
27+
module 0x815::test {
28+
fun f1() {
29+
'l0: loop {
30+
loop 'l1: loop if (true) loop {
31+
if (false) continue 'l0 else break 'l1;
32+
break
33+
} else continue 'l0;
34+
break
35+
}
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module 0x815::test {
2+
fun f1() {
3+
'outer: loop {
4+
// unlabeled loop, but counts in nesting in AST
5+
loop {
6+
'inner: loop if (true) loop {
7+
if (false) continue 'outer else break 'inner;
8+
break
9+
} else continue 'outer
10+
};
11+
break
12+
}
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
Diagnostics:
3+
error: unexpected token
4+
┌─ tests/checking/control_flow/loop_labels_parse_err1.move:3:13
5+
6+
3 │ 'a: if (true) false else true
7+
│ ^^
8+
│ │
9+
│ Unexpected 'if'
10+
│ Expected one of: 'while' or 'loop'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module 0x815::test {
2+
fun f1(): bool {
3+
'a: if (true) false else true
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
Diagnostics:
3+
error: unexpected token
4+
┌─ tests/checking/control_flow/loop_labels_parse_err2.move:3:13
5+
6+
3 │ 'a: if (true) false else true
7+
│ ^^
8+
│ │
9+
│ Unexpected 'if'
10+
│ Expected one of: 'while' or 'loop'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module 0x815::test {
2+
fun f1(): bool {
3+
'a: if (true) false else true
4+
}
5+
}

third_party/move/move-compiler-v2/tests/testsuite.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ const TEST_CONFIGS: Lazy<BTreeMap<&str, TestConfig>> = Lazy::new(|| {
106106
// Turn optimization on by default. Some configs below may turn it off.
107107
.set_experiment(Experiment::OPTIMIZE, true)
108108
.set_experiment(Experiment::OPTIMIZE_WAITING_FOR_COMPARE_TESTS, true)
109-
.set_language_version(LanguageVersion::V2_0);
109+
.set_language_version(LanguageVersion::V2_1);
110110
opts.testing = true;
111111
let configs = vec![
112112
// --- Tests for checking and ast processing
@@ -723,7 +723,7 @@ const TEST_CONFIGS: Lazy<BTreeMap<&str, TestConfig>> = Lazy::new(|| {
723723
include: vec!["/op-equal/"],
724724
exclude: vec![],
725725
exp_suffix: None,
726-
options: opts.clone().set_language_version(LanguageVersion::V2_1),
726+
options: opts.clone(),
727727
// Run the entire compiler pipeline to double-check the result
728728
stop_after: StopAfter::FileFormat,
729729
dump_ast: DumpLevel::EndStage,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
processed 1 task
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//# run
2+
script {
3+
fun main() {
4+
let result = 0;
5+
'outer: while (result < 100) {
6+
while (result < 50) {
7+
'inner: while (result < 30) {
8+
result += 1;
9+
continue 'outer
10+
};
11+
result += 10;
12+
continue 'outer
13+
};
14+
result += 20
15+
};
16+
assert!(result == 110);
17+
}
18+
}

0 commit comments

Comments
 (0)