Open mppf opened 4 years ago
@mppf: This is another one that caught my eye while surfing through issues due to split-init and out
being mentioned. The first case seems to be generating the same output for both tests, making me think the situation has improved (?). The second I didn't take the time to understand the expected behavior figuring you'd be more familiar with the original problem and the current state of things.
PR #18593 adds tests for these cases so we can be aware of any change in behavior.
I think the question is - should split init always happen before param folding (param folding away of conditionals)? Or should it always happen after?
Today, what we have is a mix, where the split init for out
intent formals is handled after param folding (test1 / test2 in the second example -- see https://github.com/chapel-lang/chapel/blob/main/test/types/records/intents/issue-14996-3.chpl , but split init for regular variables is handled before param folding (test1 / test2 in the first example -- see https://github.com/chapel-lang/chapel/blob/main/test/types/records/intents/issue-14996-1.chpl).
I think it’s not so nice for it to do split init with out
one way and with =
a different way. To improve that, we would need to either consider the param conditionals folded, or always consider them not folded. But, we can’t process split init with out
unless we resolve the branch of the conditional (because we won’t know which function is called at all if we don’t resolve it — so we don’t know that a function with out
intent was called). As a result, I think we have to choose one of:
=
and split init with an out
formal=
and split init with an out
formal happen “after param folding” that is, ignore param false
branchesout
intent formals (or those that are used to split init), e.g. writing a call like myFnWithOut(out actual)
(Q: how would that combine with named argument passing?)I think it’s not so nice for it to do split init with out one way and with = a different way.
I agree with this.
As a result, I think we have to choose one of:
option 2 is most attractive to me if it's tractable (in the sense that it's what I found myself wanting intuitively before reading the options). Is it?
Option 2 seems most intuitive to me as well; I think Michael is concerned about the ability to tell if split-init is happening at a glance, however (the more complicated the logic that happens before we decide to split init or not, the harder it is to tell just by looking at a program).
Hmm, the first case from the original post seems to behave the same for test1 and test2 on main today (prints out assign
twice). Ditto for the second post.
Similarly, this program shows that out and =
assignment behave the same with respect to a conditional that could be param-folded out (prints out assign
twice)
record R {
proc init=(other: R) { writeln("copy init"); }
}
operator =(ref lhs: R, rhs: R) { writeln("assign"); }
proc splitInitAssign() {
var x: R;
if false {
writeln(x);
}
x = new R();
}
splitInitAssign();
proc outFn(out arg: R) {
arg = new R();
}
proc splitInitOut() {
var x: R;
if false {
writeln(x);
}
outFn(x);
}
splitInitOut();
I think that the compiler must be relying on tracking mentions of variables in folded-away code for this to work. So this is sortof Option 4 since I didn't list it in my options.
But, https://github.com/chapel-lang/chapel/blob/main/test/types/records/intents/issue-14996-3.chpl shows a case where the out
intent handling is relying on param folding:
proc outFn1(out arg: R) {
if false then
return;
arg = makeR(1);
}
Here arg
is split-inited, but it wouldn't be legal to split-init it if we were imagining the analysis happens before param-folding.
So, I think that make the problem described in this issue more subtle (and arguably bug-like than) than it sounded like above.
Nonetheless, we can consider moving the whole split-init project to happen after param folding, if we see benefits to do that. But I am not sure such a change is well-motivated at this point.
I keep thinking "but wait that can't possible work" but then I have trouble coming up with an example where it is broken.
Here is an attempt that works as I would expect (with the current design)
Here is a case where it's not always working the way I would expect. Here I was expecting that, if split init is processed before param folding, that we would decide that we can't split-init x
, because then
part mentions x
without initializing it. (But, I'm not seeing a difference between out
and =
in this one either). Interestingly, in this case, we decide not to split-init x
in the early passes, but the decide that we should split-init it during resolution.
The output of this test indicates that x
is split-inited:
config param paramOption = false;
proc testA() {
writeln("testA");
var x: R;
if paramOption {
x;
} else {
x = new R(1);
}
writeln(x);
}
testA();
(full source code in details below).
Hmm, the first case from the original post seems to behave the same for test1 and test2 on main today (prints out assign twice).
Jinx! I said that 2-1/2 years ago.
Michelle's prioritization spreadsheet says we've argued this is a bug fix, so removing the 2.0 label
PR #14917 adds support for split-init for out intent and later split init decisions in function resolution. However certain patterns currently rely on
param
-folding, which is different from other split-init. It is arguably an implementation artifact.For example, the following two tests behave differently:
Here is another example, where the analysis of return patterns within the functions differs after param folding has occurred. These variants also behave differently:
We would like the variants in these examples to have the same behavior as far as split-init.
Associated tests:
test/types/records/intents/issue-14996-1.chpl test/types/records/intents/issue-14996-2.chpl test/types/records/intents/issue-14996-3.chpl