-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
spec: clarify sequencing of function calls within expressions #48105
Comments
FWIW, if we agree the intention is to only allow 0 or 2, I think one easy way to at least informally codify that would be to update the example from:
to
|
I don't think the spec currently prevents printing
It is clear that We could certainly add a rule saying something like "all function calls must be fully evaluated with respect to all other operations." But I don't think that helps people writing Go code. This code is already indeterminate. Making it less indeterminate, while still leaving it indeterminate, doesn't make anything clearer or easier to understand. It would help to make it fully determined, but that isn't what seems to be suggested here. If we don't make this kind of expression fully determined, I'm inclined to think that we should have a guideline like "if a single statement both reads and writes the same variable, and there is no order of evaluation specified between the reads and writes, then the read may observe the original value of the variable or it may observe any of the values written to the variable, and exactly which value it observes is unspecified." (I also think we should have a vet check for statements that both read and write the same variable with no ordering specified, but unfortunately that seems like a difficult check to write.) |
Does your reasoning extend to something like |
Thanks, that's a plausible argument for introducing the rule "all function calls must be fully evaluated with respect to all other operations." |
In practice I am not bitten by these things, but looking at examples like this makes me wonder why. I would not be opposed to specifying ordering, especially w.r.t multiple assignments and _.
+1. I think we are not far from the pointer part of the complexity for go 1.17. Combine that with happens-before, especially with the memory model work going on, and things get harder. Approximations can help. Type params + pointer analysis (and + x/tools/go/ssa) is an as-of-yet unsolved problem, at least for library code AFAIK. |
There are occasional issue reports about it. E.g., cmd/compile and gccgo compile |
Regarding the vet check, perhaps an approach focusing on simple patterns like this one could work. @mdempsky, how frequent is occasional in your opinion? A more general approach relying on pointer analysis might have a false positive rate above the vet threshold. |
Somewhere between "once ever" and "once per year". I can recall it being independently reported by at least 2 people, but I can't recall beyond that. I doubt it's frequent enough to merit a vet check. |
Another example package main
var x = 0
func f() int {
x = 3
return x
}
func main() {
x = 0
a, _ := x, f()
x = 0
var b, _ = x, f()
println(a, b)
} gc (
gccgo (
|
Regarding mentioned vet check, there is a similar check in go-critic linter, I think it was added because such patterns make code less clear but the example above regarding |
This makes tests pass under TinyGo. See: tinygo-org/tinygo#3963 and golang/go#48105
Since TinyGo depends on the SSA package to parse Go code, this bug is also affecting TinyGo code generation. We just hit this with real-world code. |
@dgryski Can you elaborate on how this problem affects x/tools/go/ssa and TinyGo? I didn't think x/tools/go/ssa performed function inlining, so I wouldn't expect it to be affected. |
Ah yes, I realize this bug is about the spec and not the SSA package. However, the SSA package implements one version of the spec and the Go compiler seems to implement another interpretation. Here is a small Go program with different behaviour under Go and TinyGo, but note that TinyGo is doing a faithful translation of the SSA produced by the x/tools/go/ssa (via ssadump).
Note that if we replace
|
@dgryski I see, thanks for elaborating. Yes, that's a known difference between cmd/compile and gccgo too, and I think there are other issues filed about it. Both behaviors are spec compliant, and our response to date has been that applications should not depend on which behavior the compiler implements. I think it's outside the scope of what this issue is about though (namely whether expression evaluation can be interleaved with executing a function call's body, not merely scheduled before or after). |
@mdempsky beat me to responding. I don't have too much more to add other than here is a godbolt link where you can confirm gccgo and tinygo agree on the output |
As @mdempsky said, it is user's responsibility to avoid the unspecified behavior. s2, err := strings.Repeat(s1, 1), toUpper(&s1) |
The program below tests the order-of-evaluation semantics of
return *p, f(p)
. With gccgo, it prints 0 (i.e., evaluating*p
before callingf(p)
), and with cmd/compile it prints 2 (i.e., evaluating*p
afterf(p)
returns).But is it allowed to print
1
? I.e., can*p
be evaluated in the middle off(p)
being evaluated, after the*p = 1
and before the*p = 2
?The spec says simply:
I don't think there's any other text in the spec that requires evaluation of a function call to within an expression to be handled atomically with respect to other expressions either.
@griesemer and I agree only 0 or 2 should be allowed (i.e., 1 should not be allowed), but that the spec could be clearer about this.
The text was updated successfully, but these errors were encountered: