diff --git a/doc/langref.html.in b/doc/langref.html.in index cea86e895541..9f12829349fa 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -2495,6 +2495,53 @@ or

{#code|test_exhaustive_switch.zig#} + {#header_close#} + + {#header_open|Labeled switch#} +

+ When a switch statement is labeled, it can be referenced from a + {#syntax#}break{#endsyntax#} or {#syntax#}continue{#endsyntax#}. + {#syntax#}break{#endsyntax#} will return a value from the {#syntax#} + switch{#endsyntax#}. +

+

+ A {#syntax#}continue{#endsyntax#} targeting a switch must have an + operand. When executed, it will jump to the matching prong, as if the + {#syntax#}switch{#endsyntax#} were executed again with the {#syntax#} + continue{#endsyntax#}'s operand replacing the initial switch value. +

+ + {#code|test_switch_continue.zig#} + +

+ Semantically, this is equivalent to the following loop: +

+ {#code|test_switch_continue_equivalent.zig#} + +

+ This can improve clarity of (for example) state machines, where the syntax {#syntax#}continue :sw .next_state{#endsyntax#} is unambiguous, explicit, and immediately understandable. +

+

+ However, the motivating example is a switch on each element of an array, where using a single switch can improve clarity and performance: +

+ {#code|test_switch_dispatch_loop.zig#} + +

+ If the operand to {#syntax#}continue{#endsyntax#} is + {#link|comptime#}-known, then it can be lowered to an unconditional branch + to the relevant case. Such a branch is perfectly predicted, and hence + typically very fast to execute. +

+ +

+ If the operand is runtime-known, each {#syntax#}continue{#endsyntax#} can + embed a conditional branch inline (ideally through a jump table), which + allows a CPU to predict its target independently of any other prong. A + loop-based lowering would force every branch through the same dispatch + point, hindering branch prediction. +

+ + {#header_close#} {#header_open|Inline Switch Prongs#} diff --git a/doc/langref/test_switch_continue.zig b/doc/langref/test_switch_continue.zig new file mode 100644 index 000000000000..dc6ba67b0e1c --- /dev/null +++ b/doc/langref/test_switch_continue.zig @@ -0,0 +1,26 @@ +const std = @import("std"); + +test "switch continue" { + sw: switch (@as(i32, 5)) { + 5 => continue :sw 4, + + // `continue` can occur multiple times within a single switch prong. + 2...4 => |v| { + if (v > 3) { + continue :sw 2; + } else if (v == 3) { + + // `break` can target labeled loops. + break :sw; + } + + continue :sw 1; + }, + + 1 => return, + + else => unreachable, + } +} + +// test diff --git a/doc/langref/test_switch_continue_equivalent.zig b/doc/langref/test_switch_continue_equivalent.zig new file mode 100644 index 000000000000..8e2fce8f70b9 --- /dev/null +++ b/doc/langref/test_switch_continue_equivalent.zig @@ -0,0 +1,28 @@ +const std = @import("std"); + +test "switch continue, equivalent loop" { + var sw: i32 = 5; + while (true) { + switch (sw) { + 5 => { + sw = 4; + continue; + }, + 2...4 => |v| { + if (v > 3) { + sw = 2; + continue; + } else if (v == 3) { + break; + } + + sw = 1; + continue; + }, + 1 => return, + else => unreachable, + } + } +} + +// test diff --git a/doc/langref/test_switch_dispatch_loop.zig b/doc/langref/test_switch_dispatch_loop.zig new file mode 100644 index 000000000000..cc7af1704e65 --- /dev/null +++ b/doc/langref/test_switch_dispatch_loop.zig @@ -0,0 +1,38 @@ +const std = @import("std"); +const expectEqual = std.testing.expectEqual; + +const Instruction = enum { + add, + mul, + end, +}; + +fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 { + var stack = try std.BoundedArray(i32, 8).fromSlice(initial_stack); + var ip: usize = 0; + + return vm: switch (code[ip]) { + // Because all code after `continue` is unreachable, this branch does + // not provide a result. + .add => { + try stack.append(stack.pop() + stack.pop()); + + ip += 1; + continue :vm code[ip]; + }, + .mul => { + try stack.append(stack.pop() * stack.pop()); + + ip += 1; + continue :vm code[ip]; + }, + .end => stack.pop(), + }; +} + +test "evaluate" { + const result = try evaluate(&.{ 7, 2, -3 }, &.{ .mul, .add, .end }); + try expectEqual(1, result); +} + +// test