(I'm Cc-ing Diego since he originally contributed the VRP pass and Jeff because I've seen him in git blame on many lines of vr-values.cc around the place I would like to make modifications)
Hello, In this RFC I'd like to propose the following: When the value range statement simplification machinery encounters a switch with a default case that never executes, it should mark the default case with __builtin_unreachable instead of removing the default case. I think that should be the "canonical" representation of switches with this property. In terms of changes made to GCC code, this would be a small patch. I'd like to hear other people's opinions on this. Perhaps there are implications of this change or some other issues with the idea which I don't see. Initial example =============== Consider this C code switch (v & 3) { case 0: return 0; case 1: return 1; case 2: return 2; case 3: return 3; default: __builtin_unreachable(); } Currently, when early vrp is processing this code (vr-values.cc:simplify_switch_using_ranges), it sees that the index expression can only have values [0,3]. Since the numbered cases cover this range entirely, vrp decides to transform the switch so that it only has these 4 numbered cases and removes the default case. Because GIMPLE switches have to have a default, vrp converts case 0 to the default case. The switch then looks like this switch (v & 3) { default: return 0; case 1: return 1; case 2: return 2; case 3: return 3; } However, I think it would be better if the default case with __builtin_unreachable remained. I think it conveys more information to the passes running after vrp. What would this solve ===================== Notice that the initial example switch doesn't do anything. It is just an identity mapping. The example is actually taken from PR112687. The PR shows that currently GCC (at least in some cases) fails to optimize similar identity switches. Currently the initial example looks like this at the end of the GIMPLE pipeline: int unopt (int v) { int _1; int _2; unsigned int _6; unsigned int _7; <bb 2> _1 = v_3(D) & 3; _6 = (unsigned int) _1; _7 = _6 + 4294967295; if (_7 <= 2) goto <bb 4>; else goto <bb 3>; <bb 3> <bb 4> # _2 = PHI <_1(2), 0(3)> return _2; } While it could look like this: int unopt (int v) { int _1; <bb 2> _1 = v_2(D) & 3; return _1; } Here is another example of similar code that currently doesn't get optimized... int unopt(int v) { switch (v & 3) { case 0: return 0; case 1: return 2; case 2: return 4; case 3: return 6; default: return -1; } } ...and could get optimized into something like this if VRP marked the default case as unreachable. int opt(int v) { return (v & 3) * 2; } I have originally thought about solving this problem in the switch conversion pass. However, I think that would require basically detecting that the "take away the default case" transformation happened and then reversing it. That seems pretty messy to me. Also, as I already mentioned, I think that the canonical representation of this kind of switches should be some representation that marks the default case as unreachable (with __builtin_unreachable or somehow else). How I'd do this =============== I'm attaching a work-in-progress patch to this RFC. I'm aware that in the final version it would be good to also modify the code surrounding the blob that I insert in the patch. For example, cleanup_edges_and_switches() contains code dealing with the situation when the default case gets removed, which is a situation that never happens with my patch applied. I'll appreciate comments on if I should place the blob of code creating the __builtin_unreachable somewhere else in the file. I'll also appreciate any other comments on the patch. Cheers, Filip Kastl P.S. While writing this I realized that in this case... int unopt(int v) { switch (v & 3) { default: return 0; case 1: return 1; case 2: return 2; case 3: return 3; } } ...GCC fails to optimize the switch even with my patch applied. This is because here VRP sees a default case that actually gets executed sometimes and leaves the default case be. We could make the following change to remedy this: Whenever VRP encounters a default that only corresponds to one possible value of the index expression of the switch, it could create a numbered case corresponding to this value and mark the default case as unreachable. -- 8< -- diff --git a/gcc/tree-vrp.cc b/gcc/tree-vrp.cc index cf273a3fc62..b297e782b09 100644 --- a/gcc/vr-values.cc +++ b/gcc/vr-values.cc @@ -1368,6 +1368,32 @@ simplify_using_ranges::simplify_switch_using_ranges (gswitch *stmt) else return false; + if (!take_default) + { + basic_block default_bb = gimple_switch_default_bb (cfun, stmt); + if (!gimple_seq_unreachable_p (bb_seq (default_bb))) + { + /* Add bb with __builtin_unreachable and make default point to + it. */ + basic_block switch_bb = gimple_bb (stmt); + basic_block new_bb = create_empty_bb (switch_bb); + new_bb->loop_father = switch_bb->loop_father; + e = gimple_switch_default_edge (cfun, stmt); + redirect_edge_succ (e, new_bb); + gimple_stmt_iterator gsi = gsi_start_bb (new_bb); + gimple *stmt_unreachable = + gimple_build_builtin_unreachable (UNKNOWN_LOCATION); + gsi_insert_after (&gsi, stmt_unreachable, GSI_NEW_STMT); + tree label_unreachable = gimple_block_label (new_bb); + tree default_case_label = gimple_switch_default_label (stmt); + CASE_LABEL (default_case_label) = label_unreachable; + if (dom_info_available_p (CDI_DOMINATORS)) + set_immediate_dominator (CDI_DOMINATORS, new_bb, switch_bb); + + } + take_default = true; + } + n = gimple_switch_num_labels (stmt); /* We can truncate the case label ranges that partially overlap with OP's