Skip to content

Commit 8361c6c

Browse files
committed
zz
1 parent 4323383 commit 8361c6c

File tree

10 files changed

+111
-140
lines changed

10 files changed

+111
-140
lines changed

docs/en/blog/blog0001.md

Lines changed: 0 additions & 10 deletions
This file was deleted.

docs/en/blog/index.md

Lines changed: 0 additions & 7 deletions
This file was deleted.

docs/en/guide/as-go-script.md

Lines changed: 0 additions & 21 deletions
This file was deleted.

docs/en/guide/example-prime.md

Lines changed: 0 additions & 38 deletions
This file was deleted.

docs/en/guide/index.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/en/guide/playground.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

docs/en/guide/quick-start.md

Lines changed: 0 additions & 38 deletions
This file was deleted.

docs/en/index.md

Lines changed: 0 additions & 20 deletions
This file was deleted.
File renamed without changes.

docs/smalltalk/en/st0060.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Process of solving the "Parallel ϕ-nodes" problem in Go and Wa-lang
2+
3+
Ernan Ding, Shushan Chai
4+
5+
---
6+
7+
Recently, we (the [Wa-lang](https://github.com/wa-lang/wa) development team), discovered a bug that also exists in Go. It's related to SSA and the process was quite dramatic, as recorded below.
8+
9+
## 1. Background introduction
10+
11+
Wa-lang is a general-purpose programming language based on Go, its semantics is quite similar with Go, but designed for WebAssembly. The frontend of Wa-lang (source code to AST) is modified from Go 1.17, AST to SSA conversion uses the `golang.org/x/tools/go/ssa` package, and we write the backend (SSA to Wasm) from scrath.
12+
13+
SSA (Static Single-Assignment) is an intermediate representation of code, where variables are assigned only once. SSA form helps with program analysis and optimization. For more information, see [https://en.wikipedia.org/wiki/Static_single-assignment_form](https://en.wikipedia.org/wiki/Static_single-assignment_form).
14+
15+
The `golang.org/x/tools/go/ssa` package is a toolkit provided by Go, which provides functions such as converting Go-AST to Go-SSA. Many Go-based language projects are based on or use it, including TinyGo, Go+, etc., as well as Wa-lang. But it is worth noting that **this package is not used for the Go compiler itself**, but is mainly used for external tools such as code analysis.
16+
17+
## 2. Problem appears
18+
19+
Wa-lang recently supports the semantics of Map. However, when we tried to optimize its implementation using binary search tree, the results did not meet expectations. After a day of investigation, we reduced and located the problem code as follows:
20+
21+
![](/st0060-01.png)
22+
23+
The left side of the figure is the Wa-lang code, and the right side is the equivalent Go code.
24+
According to the program logic, since `root` has been initialized in the `main` function, lines 20 and 24 of the `insert` function should be executed and print the same non-empty value. However, the running results of the above Wa-lang code are as follows:
25+
26+
```
27+
===
28+
0x3fffff0
29+
0x0
30+
```
31+
32+
That is, when in the loop body, `y` is assigned a valid value, but after leaving the loop body, `y` is set to empty.
33+
34+
The SSA form of the `insert` function is as follows:
35+
36+
```
37+
# Location: test.wa:12:6
38+
func insert():
39+
0: entry P:0 S:1
40+
t0 = println("===...":string) ()
41+
t1 = *root *node
42+
jump 3
43+
1: for.body P:1 S:1
44+
t2 = println(t6) ()
45+
t3 = &t6.next [#0] **node
46+
t4 = *t3 *node
47+
jump 3
48+
2: for.done P:1 S:0
49+
t5 = println(t7) ()
50+
return
51+
3: for.loop P:2 S:2
52+
t6 = phi [0: t1, 1: t4] #x *node
53+
t7 = phi [0: t1, 1: t6] #y *node
54+
t8 = t6 != nil:*node bool
55+
if t8 goto 1 else 2
56+
```
57+
58+
> According to the definition of SSA, any variable can only be assigned once. However, in real programs, there are many cases where different values ​​are assigned to a variable according to different conditions. To express this kind of logic, SSA has a special `phi`(ϕ) instruction, which is used to select different values ​​according to the execution path. For example, `t6 = phi [0: t1, 1: t4]` in the above code means that if it is entered from Block 0, the value of `t1` is returned, and if it is entered from Block 1, the value of `t4` is returned.
59+
60+
According to **conventional logic** (note here, it will be mentioned later), we deduce the execution order and logic of the above code as follows:
61+
62+
1. Block 0, t1 = 0x3fffff0, jump 3
63+
2. Block 3, enter from Block 0, t6 (i.e. x) = 0x3fffff0, t7 (i.e. y) = 0x3fffff0, jump 1
64+
3. Block 1, print t6(i.e. 0x3fffff0), t4 = x.next = nil, jump 3
65+
4. Block 3, enter from Block 1, t6 = nil, t7 = nil, jump 2
66+
5. Print t7(i.e. nil), return
67+
68+
That is, after leaving the loop body, `y` will be incorrectly assigned to nil. We think this is a problem of generating incorrect SSA code, so we opened an issue in the Go repository: [https://github.com/golang/go/issues/69929](https://github.com/golang/go/issues/69929).
69+
70+
## 3. Dispute
71+
72+
Soon after the issue was opened, Alan Donovan closed it. He said that SSA was fine and suggested that we learn the basics of SSA first.
73+
74+
However, we insisted that there was a problem with the `ssa` package. On the one hand, the above SSA code is not long, and its execution logic is not difficult to reason about; more importantly, we found that when the `Interpret()` function of the `ssa/interp` package was used to interpret the above SSA code, `y` was still incorrectly assigned to a nil value, and the printed result was no different from the output of the Wa-lang version! So we submitted these evidence, hoping to reopen the issue.
75+
76+
## 4. Turning point
77+
78+
Soon, Alan Donovan confirmed that the bug did exist, and it's not in `ssa` package, but in `ssa/interp` package. That is, the generated SSA was correct, but there was an error in the interpretation. He also gave a reference: [Practical Improvements to the Construction and Destruction of Static Single Assignment Form](https://homes.luddy.indiana.edu/achauhan/Teaching/B629/2006-Fall/CourseMaterial/1998-spe-briggs-ssa_improv.pdf).
79+
80+
The problem is here:
81+
82+
```
83+
3:
84+
t6 = phi [0: t1, 1: t4]
85+
t7 = phi [0: t1, 1: t6]
86+
...
87+
```
88+
89+
There are two ϕ-nodes in Block 3. According to the **conventional logic** mentioned earlier, we think the result of the first phi is the input parameter of the second one, and there is a sequence relationship between the execution of the instructions. However, when generating phis, it is based on such an assumption that "**Phis in the same Block are executed in parallel**". That is, `t6` in `phi [0: t1, 1: t6]` should take its value before entering Block 3, rather than the return value of `phi [0: t1, 1: t4]`. According to this assumption, the value of `t7` (i.e. y) is `t6` (i.e. x) of the previous iteration, and the logic is consistent with the intention of the source code. Coincidentally, the Wa-lang compiler and `ssa/interp` made the same mistake - processing phis in serial.
90+
91+
In fact, in previous private discussions, we did consider the possibility that "ϕ-nodes should be executed in parallel":
92+
93+
![](/st0060-02.jpg)
94+
95+
But the fact that the `interp` package has problems is beyond our expectation.
96+
97+
## 5. Results and thoughts
98+
99+
100+
Alan Donovan promptly submitted a fix to the `ssa/interp` package:
101+
[https://go-review.googlesource.com/c/tools/+/621595](https://go-review.googlesource.com/c/tools/+/621595).
102+
103+
We also made a fix to the handling of ϕ-nodes in the Wa-lang: [https://github.com/wa-lang/wa/commit/8138ee420dac88463515328b1edaef99eda43974](https://github.com/wa-lang/wa/commit/8138ee420dac88463515328b1edaef99eda43974).
104+
105+
The test results are correct, so this problem is solved.
106+
107+
For professionals, "Parallel ϕ-nodes" may be common sense, but how widely is this knowledge known? Compared with other instructions, ϕ-node seems so weird (even contrary to common sense). Perhaps this is one of the reasons why some recent SSA-form IRs such as MLIR use "basic block arguments" as alternative to ϕ-nodes.
108+
109+
On the other hand, why did we accidentally discover this problem that had been hidden for nearly 10 years? Maybe it's because we did not rely on LLVM, but made our own backend.
110+
111+
We are often asked, “What’s the point of reinventing the wheel?” and this experience serves as a good response.

0 commit comments

Comments
 (0)