Skip to content

special-case the lowering of ... catch(err) switch(err) and if (...) |p| ... else |err| switch(err) #11957

@andrewrk

Description

@andrewrk

This proposal is mainly concerned about the self-hosted compiler implementation, however it also has some implications for the language specification, discussed at the end.

The idea here is to special-case switching on an error, and lower it in a more compact way:

test {
    var a: error{ A, B }!u64 = 0;
    var b = a catch |err| switch (err) {
        error.A => 0,
        else => unreachable,
    };
}

Status quo ZIR looks something like this. Note the double block; one for the catch and one for the switch:

 %39 = block({
   %36 = load(%31) node_offset:10:13
   %37 = is_non_err(%36) node_offset:10:15
   %38 = condbr(%37, {
     %40 = err_union_payload_unsafe(%36) node_offset:10:15
     %56 = break(%39, %40)
   }, {
     %42 = err_union_code(%36) node_offset:10:15
     %43 = switch_cond(%42) node_offset:10:35
     %44 = typeof(%43) node_offset:10:35
     %46 = error_value("A") token_offset:11:15
     %47 = as_node(%44, %46) node_offset:11:9
     %45 = switch_block(%43,
       else => {
         %52 = dbg_block_begin())
         %54 = dbg_block_end())
         %53 = unreachable(node_offset:12:17
       },
       %47 => {
         %48 = dbg_block_begin())
         %50 = dbg_block_end())
         %51 = break(%45, @Zir.Inst.Ref.zero)
       }) node_offset:10:27
     %57 = break(%39, %45)

After this proposal it would be one block whose operand is the error union directly. The "ok" prong is for the non-error case.

 %36 = load(%31) node_offset:10:13
 %45 = switch_block_err_union(%36,
   ok => {
    %40 = err_union_payload_unsafe(%36) node_offset:10:15
    %a = break(%45, %40)
   },
   else => {
     %53 = unreachable() node_offset:12:17
   },
   %47 => {
     %51 = break(%45, @Zir.Inst.Ref.zero)
   }) node_offset:10:27
 %57 = break(%39, %45)

That example is specific to the pattern catch |err| switch (err)

If-error syntax could additionally take advantage of this:

test {
    var a: error{ A, B }!u64 = 0;
    var b = if (a) |x| x else |err| switch (err) {
        error.A => 0,
        else => unreachable,
    };
}

This would lower to the exact same ZIR.

Language Specification Implications

In both of these code examples, this would make the success expression and the error expressions peers. This has implications for peer type resolution as well as result location coercion. It prevents this compile error:

../test/behavior/error.zig:711:37: error: value with comptime only type 'comptime_int' depends on runtime control flow
    var value = S.foo() catch |err| switch (err) {
                                    ^
../test/behavior/error.zig:711:45: note: runtime control flow here
    var value = S.foo() catch |err| switch (err) {
                                            ^

because the success expression participates in the type resolution, and the error expressions end up coercing to a type capable of storing a runtime value.

This is the main motivation for this proposal.

Additional Benefits

This pattern is extremely common in Zig source code, and this change reduces the amount of data passing through most phases of the compiler pipeline, which should be a small performance benefit.

Additionally, the respective AIR code will contain a single switch_br rather than a cond_br and a switch_br, likely resulting in improved machine code of debug builds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedThis proposal is planned.enhancementSolving this issue will likely involve adding new logic or components to the codebase.frontendTokenization, parsing, AstGen, Sema, and Liveness.proposalThis issue suggests modifications. If it also has the "accepted" label then it is planned.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions