Flow scoping

Brian Goetz brian.goetz at oracle.com
Sun Dec 16 16:45:27 UTC 2018

> I use workarounds for lack of flow-scoping all the time in concurrent
> code to statically ensure a single binding to locals (no field re-reads)
> but where some of them are conditional. The way I usually do this
> requires C-like declaration plus inline conditional assignment. As in
> this snippet from ForkJoinPool:
>            int b, k, cap; ForkJoinTask<?>[] a;
>            while ((a = array) != null &&
>                   (cap = a.length) > 0 &&
>                   top - (b = base) > 0) {
>                ForkJoinTask<?> t = (ForkJoinTask<?>)
>                    QA.getAcquire(a, k = (cap - 1) & b);
The two ways this code could be improved (with some help from the language) is:

 - All things being equal, it is nice to declare and initialize a variable in the same place; this helps readers as well as guaranteeing the data access patterns Doug is looking for.  (For the audience: Doug’s motivation for coding in this “strange” way is to ensure the code is already in SSA form, which reduces the ways in which we would accidentally fail to get the desired JIT optimizations.)  
 - Having to pull the declarations out of for and while loops has the accidental side-effect of expanding the scope to the entire scope in which the for/while exists, even though it may not be DA in that entire region.  If precise scoping is good, this is less precise scoping, which must be not as good.  

In other words, the scoping that Doug wants in this code is exactly the flow scoping — let me declare a variable and initial value together in one place, be able to use it in all the places where it is DA, and be protected from accidentally using it in the places where it is not DA.  

The downside of this is that there are more places _in the middle of code_ where variables are introduced; it is theoretically harder to find the declarations.  (But, when reading code like the above, your brain often skips over the decls anyway, because its not obvious what they are until you get into the code, so this might well be a wash.)  To compensate, the more precise nature of flow scoping completely eliminates the areas of code where it might be in scope but not definitely assigned.  

> Alternatives without inline assigns need more "{" scopes. Which is
> almost the same problem that flow scoping for switches addresses. If
> flow-scoping uniformly applied to this case (at least when using "var"),
> it might instead look like:
>            while ((var a = array) != null &&
>                   (var cap = a.length) > 0 &&
>                   top - (var b = base) > 0) {
>                ForkJoinTask<?> t = (ForkJoinTask<?>)
>                    QA.getAcquire(a, var k = (cap - 1) & b);

Note that to support this in the flow scoping rules, we need only make some trivial modifications:
 - For a `var x = e` expression, x goes in both the true and false sets;
 - For the `if`/`while` statements, under some conditions, we can take the intersection of the true and false sets of the boolean condition in the loop header (previously provably disjoint), and put them in scope after the statement (details are a little more complicated than that, but I think only a little.)  

> Are there other cases in which modestly expanding support for flow
> scoping would help people write less-weird code?

Yes, that’s the question!  The above treatment could be considered a bit of an “abuse” of the _current_ flow scoping design, because the design to date has been so focused on conditionality.  But, what you’re suggesting is that it could be “rotated” a bit to include some useful non-conditional cases too, and I like that.  We now have two examples to extrapolate from (much better than one!) … who has more?  

More information about the amber-spec-experts mailing list