Repository: go
Issue: cmd/compile: CSE some function calls args/results
Labels: Performance,ToolSpeed,compiler/runtime

```go
package p

var b bool

func f(x, y int) int {
	var r int
	if b {
		r = g(x, y)
	} else {
		r = h(x, y)
	}
	return r
}

//go:noinline
func g(x, y int) int { return 0 }

//go:noinline
func h(x, y int) int { return 0 }
```

This kind of code ("call either g or h with the same args") is not uncommon. It is compiled suboptimally; there is lots of duplicate code to set up and read the stack:

```
"".f STEXT size=122 args=0x18 locals=0x20
	0x0000 00000 (x.go:5)	TEXT	"".f(SB), $32-24
	0x0000 00000 (x.go:5)	MOVQ	(TLS), CX
	0x0009 00009 (x.go:5)	CMPQ	SP, 16(CX)
	0x000d 00013 (x.go:5)	JLS	115
	0x000f 00015 (x.go:5)	SUBQ	$32, SP
	0x0013 00019 (x.go:5)	MOVQ	BP, 24(SP)
	0x0018 00024 (x.go:5)	LEAQ	24(SP), BP
	0x001d 00029 (x.go:5)	FUNCDATA	$0, gclocals·54241e171da8af6ae173d69da0236748(SB)
	0x001d 00029 (x.go:5)	FUNCDATA	$1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x001d 00029 (x.go:7)	MOVBLZX	"".b(SB), AX
	0x0024 00036 (x.go:7)	TESTB	AL, AL
	0x0026 00038 (x.go:7)	JEQ	84
	0x0028 00040 (x.go:7)	MOVQ	"".x+40(SP), AX
	0x002d 00045 (x.go:8)	MOVQ	AX, (SP)
	0x0031 00049 (x.go:8)	MOVQ	"".y+48(SP), AX
	0x0036 00054 (x.go:8)	MOVQ	AX, 8(SP)
	0x003b 00059 (x.go:8)	PCDATA	$0, $0
	0x003b 00059 (x.go:8)	CALL	"".g(SB)
	0x0040 00064 (x.go:8)	MOVQ	16(SP), AX
	0x0045 00069 (x.go:12)	MOVQ	AX, "".~r2+56(SP)
	0x004a 00074 (x.go:12)	MOVQ	24(SP), BP
	0x004f 00079 (x.go:12)	ADDQ	$32, SP
	0x0053 00083 (x.go:12)	RET
	0x0054 00084 (x.go:12)	MOVQ	"".x+40(SP), AX
	0x0059 00089 (x.go:10)	MOVQ	AX, (SP)
	0x005d 00093 (x.go:10)	MOVQ	"".y+48(SP), AX
	0x0062 00098 (x.go:10)	MOVQ	AX, 8(SP)
	0x0067 00103 (x.go:10)	PCDATA	$0, $0
	0x0067 00103 (x.go:10)	CALL	"".h(SB)
	0x006c 00108 (x.go:10)	MOVQ	16(SP), AX
	0x0071 00113 (x.go:7)	JMP	69
	0x0073 00115 (x.go:7)	NOP
	0x0073 00115 (x.go:5)	PCDATA	$0, $-1
	0x0073 00115 (x.go:5)	CALL	runtime.morestack_noctxt(SB)
	0x0078 00120 (x.go:5)	JMP	0
```

Setting up the stack for the call and processing the return could be CSE'd to dominating and post-dominating blocks. In pseudo code:

```
func f(x, y int) int {
	var r int
        arg0 = x
        arg1 = y
	if b {
		call g
	} else {
		call h
	}
        r = ret0
	return r
}
```

I suspect that at least for function arguments, our current CSE pass would work, but for the prohibition on CSEing values of memory type.

At the end of the generic cse pass, the code above looks like:

```
f <T>
  b1:
    v1 = InitMem <mem>
    v2 = SP <uintptr>
    v3 = SB <uintptr>
    v6 = Addr <*int> {~r2} v2
    v7 = Arg <int> {x}
    v8 = Arg <int> {y}
    v10 = Addr <*bool> {"".b} v3
    v11 = Load <bool> v10 v1
    v13 = OffPtr <*int> [0] v2
    v17 = OffPtr <*int> [8] v2
    v20 = OffPtr <*int> [16] v2
    If v11 -> b2 b4
  b2: <- b1
    v15 = Store <mem> {int} v13 v7 v1
    v18 = Store <mem> {int} v17 v8 v15
    v19 = StaticCall <mem> {"".g} [24] v18
    v21 = Load <int> v20 v19
    Plain -> b3
  b3: <- b2 b4
    v29 = Phi <int> v21 v28
    v30 = Phi <mem> v19 v27
    v31 = VarDef <mem> {~r2} v30
    v32 = Store <mem> {int} v6 v29 v31
    Ret v32
  b4: <- b1
    v24 = Store <mem> {int} v13 v7 v1
    v26 = Store <mem> {int} v17 v8 v24
    v27 = StaticCall <mem> {"".h} [24] v26
    v28 = Load <int> v20 v27
    Plain -> b3
```

Observe that v15/v24 and v18/v26 certainly look ripe for CSE.

Rolling back the prohibition on CSEing memory values would require care, though, since that helped a lot with CSE toolspeed problems. Continuing to prohibit CSE of calls might be a workable middle ground.

cc @randall77 @tzneal @dr2chase