-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
introduce reachable
keyword for branch hints
#21058
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I really like this proposal. If this is accepted, would we really need an unreachable keyword? I can imagine |
EDIT: syntax issue in advanced example
There's a slight syntax issue in the "advanced example" given - in status quo block labels are only available within that block. Label visibility could be special-cased within switch statements and their prongs. Alternatively, because only question about limiting `reachable` to the beginning of blocks
Does this talk about syntactic blocks a: switch(x) {
else => {
if (y) continue :a 4;
reachable :a .unlikely;
// rest of the block - could still reach :a, but unlikely
if (z) continue :a 33;
reachable .unlikely; //it's likely we have branched away at this point
},
} Otherwise you would be forced to introduce a new block, and increase indentation, questions about the behavior in loops and with branching backward
To me it looks like there's an important distinction between the two forms:
If that distinction is correct, then there may be an esoteric edge-case where both unlabeled while(iterator.next()) |element| {
if (at_most_once) a: {
reachable .likely; //it's likely this block is reached once,
reachable :a .never; //but once it has been entered we ensure/assert we won't re-enter it.
};
// rest of the loop with many other ways to exit it
} This information could only ever be utilized when transforming/unrolling the loop. xs: switch(@as(u8, 0)) {
0 => x0: {
if (skip_x1_x2()) continue :xs 3;
continue :xs 1;
},
1 => x1: {
if (skip_x2()) continue :xs 3;
continue :xs 2;
},
2 => x2: {
reachable :x1 .never; //This essentially asserts x1 is never reached again, right?
continue :xs 3;
},
3 => x3: {
if (cond) continue :xs 0; //Is x1 still never-reachable again from here? Or only if we didn't skip x2?
},
} I think the sanest behavior would be to effectively "undo" the (labeled)
@RetroDev256 In status-quo |
It's unclear why this needs to be a keyword over a builtin other than accepting labeled blocks for switch/continue. Even then, labeled usage has edge cases:
a: switch (x) {
0 => b: {
reachable :a .unlikely;
},
1 => c: {
reachable :a .unlikely;
},
2 => d: {
reachable :a .unlikely;
},
}
// 2
a: switch (x) {
0 => b: {
continue :a f();
},
1 => c: {
reachable :b .likely;
},
2 => d: {
reachable :b .likely;
},
} Switch statements have unpredictable codegen order, especially since codegen doesn't look at the order of cases. |
There are no builtins that accept a branch label as a parameter, so the alternative would be novel syntax. On the other hand, there is already a keyword that accepts a possibly labeled expression. This is simpler to specify & implement as well as easier for people to learn & remember.
These questions are answered by this part of the proposal:
I'm not sure what point you are trying to make here. Switch expressions have codegen order dictated by the same lowering rules of all code: emit the best machine code possible, given the optimization mode and other compiler flags, obeying all language rules, exploiting available hints. The absence of a language rule (switch expression prong codegen order) provides flexibility in the search space of these goals. I think the thing you're missing (as pointed out by @mlugg in #20642 (comment)) is that this information, while syntactically convenient to reside in the destination blocks of branches, is actually data belonging to the source location of the branch. Other proposed solutions to this problem fail to model this accurately. |
This proposal seems very flawed to me. Specifically, the block labeling feature doesn't seem well-thought-out (and of course, without that, this could just be a builtin). I have two issues with it. The example you give of using this with labeled blocks ("Advanced example syntax") doesn't actually give the compiler particularly useful information: we can tell it that it is likely to loop into the labeled switch again, but we can't communicate which prong we are likely to reach, which may be desirable information. So, this proposal fails to provide all the information which an optimizer could utilize about branch reachability. I don't think it's worth introducing a new keyword for a sub-optimal feature like this. Actually, your example makes reference to a block The second problem I have with this syntax is that the meaning of |
Only now I realized that there's technically the reading of I think for me the logical ordering of the full phrase would be I don't know whether the syntax should allow this additional complexity - probably not -, |
Right that's the idea. I think @mlugg missed this nuance too. |
ohhh i definitely missed that as well in my first readings with the current naming. but that does make more sense given the proposals this was meant to replace. |
Hm, okay, I see. I will say, I think the fact that 3 people individually misunderstood the intention here in exactly the same way indicates that this syntax is unclear. But, ignoring that, let me discuss the proposal as intended. I feel like the example given here overloads the meaning of the To me, the block scoping issue still makes this proposal feel inappropriate. There is no solution to this problem which retains parity with other parts of the language, and even ignoring that, allowing these out-of-scope block references just feels, frankly, very messy. We could define rules to avoid block labels "leaking" into surrounding scopes too much, but every change you make there just makes the language more confusing and the spec more complex. |
I feel that this proposal overgeneralizes the problem and maybe gives it higher status than it deserves. Branch hint information is generally not that valuable to the compiler, since there are no actual branch hint instructions on most architectures and the heavy lifting is done by the runtime branch predictor. Moreover, the actually useful information depends on the type of branching construct and therefore generalizes poorly: 1. If-ElseHere, the compiler's options are mostly limited to which branch it lays out first and which second. By default, it would leave the cases as is, so the useful information is whether the condition is unlikely. If it is known to be unpredictable, the compiler might try to lower to predicate instructions, but this tends to be faster only for really tiny IFs. 2. LoopsThere is relatively little that the compiler can do about the control flow structure of a loop per se. But it may be interested in whether the loop is hot, so it can invest into unrolling, vectorizing, and other bloaty optimizations. This annotation may also take the average number of iterations from PGO instead. This is technically not a branch hint at all. 3. FunctionsIf a function is known to be cold, it can be relocated to another segment. I'm not sure this is useful in other contexts though. Do compilers really relocate IF branches and switch cases? A hot annotation may be beneficial for the same reasons as in loops. 4. SwitchesThis is actually the most interesting case, as relative branch weights can really help the compiler select optimal fast paths in comparison-based switches. For switches that are compiled to a computed goto, this is probably less relevant. 5. ComefromThis is the goto switch case, but it's not clear to me why it's important to know where a given case is likely to be reached from. Maybe so you can reduce long branches in really big-ass switches? But this is really a micro-optimization and the corresponding constrained layout problem might be computationally expensive. I really think there should be some empirical evidence that this is worthwhile, before bringing this information into the language. TL;DREven though having a bunch of separate builtins with optional PGO parameters is less elegant, it probably gives more precise information where it can actually be useful, will hopefully discourage overuse (in my experience it's really quite hard to outwit the compiler on this front), and does not require keywords and non-standard block label semantics. |
Rejected in favor of #21148. |
Loosely inspired by #489 (comment) and #20642 (comment).
This proposal supplants:
@setCold
with@cold
#5177@branchWeight
builtin with numerical branch weights #20642Introduces a new keyword,
reachable
, which takes an optional block label, just likebreak
andcontinue
.Combined, these changes allow branches to hint about how likely or unlikely they are to be reached from any given location.
Basic example syntax:
Advanced example syntax (combined with #8220):
Some rules:
@panic
(including from safety check) implicitly are "cold".reachable
statements for the same (or no) label is a compile error.unreachable
is required to terminate a block, allreachable
statements are required to be at the beginning of a block.Furthermore:
@cold
or@setCold
builtin. (replaced byreachable .cold;
at the beginning of the function.unlikely
and can be overridden with thisreachable
keyword.Compared to the existing proposals, this generalizes better to
switch
expressions, and it actually addresses labeled continue syntax inside switch expressions, as well as loops in general.I will make a separate followup proposal for annotating source with PGO data.
Alternate syntax idea:
comefrom
😜The text was updated successfully, but these errors were encountered: