Repository: go
Issue: cmd/compile: Fannkuch11 can be speed up by ~ 30% via improving BCE
Labels: Performance,NeedsFix,compiler/runtime

This is spin-off issue from https://github.com/golang/go/issues/20393. There in the discussion it was shown that [Fannkuch11 benchmark](https://github.com/golang/go/blob/master/test/bench/go1/fannkuch_test.go) from go1 benchmark suite runs ~30% faster with `-gcflags=-B`. A proof was also provided showing that all the bounds checks emitted by the compiler are actually not needed, and thus with clever BCE it should be possible to reach ~30% Fannkuch11 speedup level demonstrated by `-gcflags=-B`.

That discussion is from May 2017. However I've just rechecked with today's go tip
```
$ gotip version
go version devel +26e0e8a840 Tue Apr 3 05:45:44 2018 +0000 linux/amd64
```

and current state is:

```
.../gotip/test/bench/go1$ gotip test -bench Fannkuch -count 10 >ftip.txt
.../gotip/test/bench/go1$ gotip test -gcflags=-B -bench Fannkuch -count 10 >ftipB.txt

.../gotip/test/bench/go1$ benchstat ftip.txt ftipB.txt 
name          old time/op  new time/op  delta
Fannkuch11-4   2.70s ± 1%   1.96s ± 0%  -27.42%  (p=0.000 n=10+10)
```

in other words there is still ~30% room for BCE related improvement.

Below comes verbatim copy of my [original proof](https://github.com/golang/go/issues/20393#issuecomment-302877947) that all bounds checks are not actually needed in Fannkuch case. Even though it discusses code emitted by old compilers, all points about bounds checks not needed should stand still valid today.

Thanks,
Kirill

/cc @josharian, @rasky, @dr2chase, @brtzsnr, @aclements 

---- 8< ----

Josh thanks for your feedback and for trying the compiler with -B. About fannkuch I've tried to look inside to see details about bounds checking. For this I've compiled `fannkuch_test.go` without and with -B, did `go tool objdump -S` on both, removed addresses of instruction so that the diff is less noisy, and investigated the difference. Let me show it below with my annotations and comments:

```diff
@@ -2,259 +2,172 @@ TEXT %22%22.fannkuch(SB) gofile..$GOROOT/test/bench/go1/fannkuch_test.go
 func fannkuch(n int) int {
                        64488b0c2500000000      MOVQ FS:0, CX           [5:9]R_TLS_LE
                        483b6110                CMPQ 0x10(CX), SP
-                       0f8623030000            JBE 0x2508              
-                       4881ec80000000          SUBQ $0x80, SP          
-                       48896c2478              MOVQ BP, 0x78(SP)       
-                       488d6c2478              LEAQ 0x78(SP), BP       
-                       488b842488000000        MOVQ 0x88(SP), AX       
+                       0f86d5010000            JBE 0x235a              
+                       4883ec48                SUBQ $0x48, SP          
+                       48896c2440              MOVQ BP, 0x40(SP)       
+                       488d6c2440              LEAQ 0x40(SP), BP       
+                       488b442450              MOVQ 0x50(SP), AX       
        if n < 1 {
                        4883f801                CMPQ $0x1, AX
-                       0f8ca8020000            JL 0x24b0               
+                       0f8ca5010000            JL 0x2347               
                        488d0d00000000          LEAQ 0(IP), CX          [3:7]R_PCREL:type.int
        perm := make([]int, n)
                        48890c24                MOVQ CX, 0(SP)
                        4889442408              MOVQ AX, 0x8(SP)
                        4889442410              MOVQ AX, 0x10(SP)
-                       e800000000              CALL 0x2222             [1:5]R_CALL:runtime.makeslice
-                       488b442420              MOVQ 0x20(SP), AX       
+                       e800000000              CALL 0x21bc             [1:5]R_CALL:runtime.makeslice
+                       488b442418              MOVQ 0x18(SP), AX       
                        4889442438              MOVQ AX, 0x38(SP)
-                       488b4c2418              MOVQ 0x18(SP), CX       
-                       48894c2468              MOVQ CX, 0x68(SP)       
-                       488d1500000000          LEAQ 0(IP), DX          [3:7]R_PCREL:type.int
+                       488d0d00000000          LEAQ 0(IP), CX          [3:7]R_PCREL:type.int
        perm1 := make([]int, n)
-                       48891424                MOVQ DX, 0(SP)          
-                       488b9c2488000000        MOVQ 0x88(SP), BX       
-                       48895c2408              MOVQ BX, 0x8(SP)        
-                       48895c2410              MOVQ BX, 0x10(SP)       
-                       e800000000              CALL 0x2258             [1:5]R_CALL:runtime.makeslice
-                       488b442418              MOVQ 0x18(SP), AX       
-                       4889442460              MOVQ AX, 0x60(SP)       
-                       488b4c2420              MOVQ 0x20(SP), CX       
-                       48894c2430              MOVQ CX, 0x30(SP)       
-                       488d1500000000          LEAQ 0(IP), DX          [3:7]R_PCREL:type.int
-       count := make([]int, n)
-                       48891424                MOVQ DX, 0(SP)          
-                       488b942488000000        MOVQ 0x88(SP), DX       
+                       48890c24                MOVQ CX, 0(SP)          
+                       488b542450              MOVQ 0x50(SP), DX       
                        4889542408              MOVQ DX, 0x8(SP)
                        4889542410              MOVQ DX, 0x10(SP)
-                       e800000000              CALL 0x228e             [1:5]R_CALL:runtime.makeslice
-                       488b442420              MOVQ 0x20(SP), AX       
-                       488b4c2418              MOVQ 0x18(SP), CX       
-                       488b942488000000        MOVQ 0x88(SP), DX       
-                       488b5c2460              MOVQ 0x60(SP), BX       
-                       488b742430              MOVQ 0x30(SP), SI       
-                       31ff                    XORL DI, DI             
+                       e800000000              CALL 0x21e5             [1:5]R_CALL:runtime.makeslice
+                       488b442418              MOVQ 0x18(SP), AX       
+                       4889442430              MOVQ AX, 0x30(SP)       
+                       488d0d00000000          LEAQ 0(IP), CX          [3:7]R_PCREL:type.int
+       count := make([]int, n)
+                       48890c24                MOVQ CX, 0(SP)          
+                       488b4c2450              MOVQ 0x50(SP), CX       
+                       48894c2408              MOVQ CX, 0x8(SP)        
+                       48894c2410              MOVQ CX, 0x10(SP)       
+                       e800000000              CALL 0x220e             [1:5]R_CALL:runtime.makeslice
+                       488b442418              MOVQ 0x18(SP), AX       
+                       488b4c2450              MOVQ 0x50(SP), CX       
+                       488b542430              MOVQ 0x30(SP), DX       
+                       31db                    XORL BX, BX             
        for i := 0; i < n; i++ {
-                       eb07                    JMP 0x22b5              
+                       eb07                    JMP 0x2228              
                perm1[i] = i // initial (trivial) permutation
-                       48893cfb                MOVQ DI, 0(BX)(DI*8)    
+                       48891cda                MOVQ BX, 0(DX)(BX*8)    
        for i := 0; i < n; i++ {
-                       48ffc7                  INCQ DI                 
-                       4839d7                  CMPQ DX, DI             
-                       7d0a                    JGE 0x22c4              
-               perm1[i] = i // initial (trivial) permutation
-                       4839f7                  CMPQ SI, DI             
-                       72ef                    JB 0x22ae               
-                       e93d020000              JMP 0x2501                      // panicindex
-                       48894c2470              MOVQ CX, 0x70(SP)       
+                       48ffc3                  INCQ BX                 
+                       4839cb                  CMPQ CX, BX             
+                       7cf4                    JL 0x2221               
```

BCE could remove ^^^ panicindex because:

- `perm1` is initialized with `make([]int, n)` and its len is never changed
- then it is initialized in a loop

```go
        for i := 0; i < n; i++ {
                perm1[i] = i // initial (trivial) permutation
        }
```

so BCE knows n <= len(perm1) (actually ==), 0 <= i < n -> i < len(perm1) -> no BC needed.

```diff
        n1 := n - 1
-                       488d7aff                LEAQ -0x1(DX), DI       
-                       48897c2440              MOVQ DI, 0x40(SP)       
-                       4989d0                  MOVQ DX, R8             
-                       4531c9                  XORL R9, R9             
-                       4531d2                  XORL R10, R10           
+                       488d59ff                LEAQ -0x1(CX), BX       
+                       4889ce                  MOVQ CX, SI             
+                       31ff                    XORL DI, DI             
+                       4531c0                  XORL R8, R8             
        for {
-                       e986010000              JMP 0x2466              
+                       e9cf000000              JMP 0x230d              
                        count[r-1] = r
-                       488954d1f8              MOVQ DX, -0x8(CX)(DX*8) 
-                       4c89da                  MOVQ R11, DX            
+                       48894cc8f8              MOVQ CX, -0x8(AX)(CX*8) 
+                       48ffc9                  DECQ CX                 
                for ; r != 1; r-- {
-                       4883fa01                CMPQ $0x1, DX           
-                       740e                    JE 0x22fc               
-                       count[r-1] = r
-                       4c8d5aff                LEAQ -0x1(DX), R11      
-                       4939c3                  CMPQ AX, R11            
-                       72e9                    JB 0x22e0               
-                       e9fe010000              JMP 0x24fa                      // panicindex
+                       4883f901                CMPQ $0x1, CX           
+                       75f2                    JNE 0x223e              
```


For `count[r-1] = r` BCE could remove ^^^ panicindex because:

- `count` is initialized with `make([]int, n)` and its len is never changed
- `r` is initialized as `n`, goes down to 1 in below loop, goes up in tail loop inside main loop `for ; r < n; r++`, so it is never > n. => r ∈ [1, n] always.
- then there is loop

```go
        for ; r != 1; r-- {
                count[r-1] = r
        }
```

so BCE knows

- `1 <= r <= n`
- -> `0 <= r-1 <= n-1 < n`

so `count[r-1]` does not need BC.

```diff
                if perm1[0] != 0 && perm1[n1] != n1 {
-                       4885f6                  TESTQ SI, SI                    
-                       0f86ee010000            JBE 0x24f3                      // panicindex
-                       4c8b1b                  MOVQ 0(BX), R11                 
-                       4d85db                  TESTQ R11, R11                  
-                       0f8490010000            JE 0x24a1                       
-                       4839f7                  CMPQ SI, DI                     
-                       0f83d9010000            JAE 0x24f3                      // panicindex
-                       4e8b5cc3f8              MOVQ -0x8(BX)(R8*8), R11        
-                       4c39df                  CMPQ R11, DI                    
-                       0f846a010000            JE 0x2492                       
-                       4c8b5c2468              MOVQ 0x68(SP), R11              
-                       4c8b642438              MOVQ 0x38(SP), R12              
-                       41bd01000000            MOVL $0x1, R13                  
+                       4c8b0a                  MOVQ 0(DX), R9          
+                       4d85c9                  TESTQ R9, R9            
+                       0f84e5000000            JE 0x233d               
+                       4c8b4cf2f8              MOVQ -0x8(DX)(SI*8), R9 
+                       4c39cb                  CMPQ R9, BX             
+                       0f84cd000000            JE 0x2333               
+                       4c8b4c2438              MOVQ 0x38(SP), R9       
+                       41ba01000000            MOVL $0x1, R10          
```


- `len(perm1)` is known to be `n`
- n is known to be >= 1 because in the beginning of fannkuch there is

```go
        if n < 1 {
                return 0
        }
```

-> this way perm1[0] does not need BC.

- `n1` is initialized as `n - 1` and is never changed
- thus it is known 0 <= n1 <= n -1 < n
- `len(perm1)` is once again known to be `n`

-> this way `perm1[n1]` does not need BC

```diff
                        for i := 1; i < n; i++ { // perm = perm1
-                       eb07                    JMP 0x2341              
+                       eb0b                    JMP 0x227e              
                                perm[i] = perm1[i]
-                       4f8934eb                MOVQ R14, 0(R11)(R13*8) 
+                       4e8b1cd2                MOVQ 0(DX)(R10*8), R11  
+                       4f891cd1                MOVQ R11, 0(R9)(R10*8)  
                        for i := 1; i < n; i++ { // perm = perm1
-                       49ffc5                  INCQ R13                
-                       4d39c5                  CMPQ R8, R13            
-                       7d17                    JGE 0x235d              
-                               perm[i] = perm1[i]
-                       4939f5                  CMPQ SI, R13            
-                       0f839d010000            JAE 0x24ec                      // panicindex
-                       4e8b34eb                MOVQ 0(BX)(R13*8), R14  
-                       4d39e5                  CMPQ R12, R13           
-                       72e2                    JB 0x233a               
-                       e98f010000              JMP 0x24ec                      // panicindex
-                       4c894c2450              MOVQ R9, 0x50(SP)       
+                       49ffc2                  INCQ R10                
+                       4939f2                  CMPQ SI, R10            
+                       7cf0                    JL 0x2273               
```

- i ∈ [1, n)
- both `perm` and `perm1` were initialized as `make([]int, n)` and their len is never changed

-> both `perm[i]` and `perm1[i]` do not need BC

```diff
                        k := perm1[0] // cache perm[0] in k
-                       4c8b2b                  MOVQ 0(BX), R13         
-                       4531f6                  XORL R14, R14           
+                       4c8b12                  MOVQ 0(DX), R10         
+                       4531db                  XORL R11, R11           
                        for {         // k!=0 ==> k>0
-                       eb6d                    JMP 0x23d7              
+                       eb32                    JMP 0x22bd              
                                        perm[i], perm[j] = perm[j], perm[i]
-                       4b8b0cfb                MOVQ 0(R11)(R15*8), CX  
-                       49890cfb                MOVQ CX, 0(R11)(DI*8)   
-                       4f890cfb                MOVQ R9, 0(R11)(R15*8)  
+                       4f8b34e1                MOVQ 0(R9)(R12*8), R14  
+                       4f8b3ce9                MOVQ 0(R9)(R13*8), R15  
+                       4f8934e9                MOVQ R14, 0(R9)(R13*8)  
+                       4f893ce1                MOVQ R15, 0(R9)(R12*8)  
                                for i, j := 1, k-1; i < j; i, j = i+1, j-1 {
-                       488d4f01                LEAQ 0x1(DI), CX        
-                       49ffcf                  DECQ R15                
-                       488b7c2440              MOVQ 0x40(SP), DI       
-                       4c8b4c2450              MOVQ 0x50(SP), R9       
-                       48894c2448              MOVQ CX, 0x48(SP)       
-                       488b4c2470              MOVQ 0x70(SP), CX       
-                       488b7c2448              MOVQ 0x48(SP), DI       
-                       4c39ff                  CMPQ R15, DI            
-                       7d17                    JGE 0x23b2              
-                                       perm[i], perm[j] = perm[j], perm[i]
-                       4c39e7                  CMPQ R12, DI            
-                       0f8341010000            JAE 0x24e5                      // panicindex
-                       4d8b0cfb                MOVQ 0(R11)(DI*8), R9   
-                       4d39e7                  CMPQ R12, R15           
-                       72bd                    JB 0x236a               
-                       e933010000              JMP 0x24e5                      // panicindex
```

- k is initialized as `perm1[0]` - no BC needed here since compiler already knows len(perm1) = n >= 1
- initially perm1 is initialized as `perm1[i] = i` i ∈ [0, n)
- from ^^^ the compiler knows initially ∀ x ∈ [0, n) perm1[x] ∈ [0, n)
- later elements of perm1 are only exchanged with each other, so the compiler can prove ∀ x ∈ [0, n) perm1[x] ∈ [0, n) holds always
- thus k itself can be proven to be in [0, n) on first iteration (see also below about all next iterations)

- the inner loop is

```go
        for i, j := 1, k-1; i < j; i, j = i+1, j-1 {
                perm[i], perm[j] = perm[j], perm[i]
        }
```

- j starts from k-1 < n-1 and is only decreasing
- i starts from 1 and is only increasing
- the loop is till i < j
- thus inside loop:
  * 1 <  j < n - 1
  * 1 <= i < n - 1

-> both i and j are in valid range and no BC is needed for `perm[i]` and `perm[j]`

```diff
-                               j := perm[k]
+                       49ffc5                  INCQ R13                
+                       49ffcc                  DECQ R12                
                        4d39e5                  CMPQ R12, R13
-                       0f8323010000            JAE 0x24de                      // panicindex
-                       4b8b3ceb                MOVQ 0(R11)(R13*8), DI  
+                       7ce5                    JL 0x228b               
+                               j := perm[k]
+                       4f8b24d1                MOVQ 0(R9)(R10*8), R12  
```

- k exchange is

```go
        // Now exchange k (caching perm[0]) and perm[k]... with care!
        j := perm[k]
        perm[k] = k
        k = j
```

- we know initially (first iter - see above) k ∈ [0, n)
- thus `perm[k]` does not need BC
- before loop `perm` is initialized as = `perm1` - this way compiler can know ∀ x ∈ [0, n) `perm[x]` ∈ [0, n) initially
- in `j := perm[k]` `j` is thus ∈ [0, n)
- `perm[k] = k` preserves ∀ x ∈ [0, n) `perm[x]` ∈ [0, n) property.
- from `k = j` compiler can see k continues to k ∈ [0, n) in next iterations

```diff
                                perm[k] = k
-                       4f892ceb                MOVQ R13, 0(R11)(R13*8) 
+                       4f8914d1                MOVQ R10, 0(R9)(R10*8)  
                                flips++
-                       4d8d6e01                LEAQ 0x1(R14), R13      
+                       4d8d5301                LEAQ 0x1(R11), R10      
                                if k == 0 {
-                       4885ff                  TESTQ DI, DI            
-                       7425                    JE 0x23f1               
-                       4d89ee                  MOVQ R13, R14           
-                       4989fd                  MOVQ DI, R13            
-                       488b7c2440              MOVQ 0x40(SP), DI       
+                       4d85e4                  TESTQ R12, R12          
+                       7412                    JE 0x22c9               
+                       4d89d3                  MOVQ R10, R11           
+                       4d89e2                  MOVQ R12, R10           
                                for i, j := 1, k-1; i < j; i, j = i+1, j-1 {
-                       4d8d7dff                LEAQ -0x1(R13), R15     
-                       4889442458              MOVQ AX, 0x58(SP)       
-                       b801000000              MOVL $0x1, AX           
-                       4889442448              MOVQ AX, 0x48(SP)       
-                       488b442458              MOVQ 0x58(SP), AX       
-                       eba0                    JMP 0x2391              
+                       4d8d62ff                LEAQ -0x1(R10), R12     
+                       41bd01000000            MOVL $0x1, R13          
+                       ebd8                    JMP 0x22a1              
                        if flipsMax < flips {
-                       4d39ea                  CMPQ R13, R10           
-                       7c56                    JL 0x244c               
-                       e992000000              JMP 0x248d              
+                       4d39d0                  CMPQ R10, R8            
+                       7c2a                    JL 0x22f8               
+                       eb5e                    JMP 0x232e              
                                perm1[i] = perm1[i+1]
-                       4e893cd3                MOVQ R15, 0(BX)(R10*8)  
-                       4d89f2                  MOVQ R14, R10           
+                       4e8b64da08              MOVQ 0x8(DX)(R11*8), R12        
+                       4e8924da                MOVQ R12, 0(DX)(R11*8)          
+                       49ffc3                  INCQ R11                        
                        for i := 0; i < r; i++ {
-                       4939d2                  CMPQ DX, R10            
-                       7d1c                    JGE 0x2423              
-                               perm1[i] = perm1[i+1]
-                       4d8d7201                LEAQ 0x1(R10), R14              
-                       4939f6                  CMPQ SI, R14                    
-                       0f83c3000000            JAE 0x24d7                      // panicindex
-                       4e8b7cd308              MOVQ 0x8(BX)(R10*8), R15        
-                       4939f2                  CMPQ SI, R10                    
-                       72dd                    JB 0x23fb                       
-                       e9b4000000              JMP 0x24d7                      // panicindex
+                       4939cb                  CMPQ CX, R11            
+                       7cef                    JL 0x22d0               
```

- outer loop is `for ; r < n; r++`
- inner loop is `for i := 0; i < r; i++`
- inside loop r is thus < n, i.e. <= n-1
- inside inner loop i is thus < r, i.e. < n-1, i.e. ∈ [0, n-1)
- thus both `perm1[i]` and `perm1[i+1]` does not need BC
- once again on `perm1[i] = perm1[i+1]` the compiler can see `∀ x ∈ [0, n) perm1[x] ∈ [0, n)` property is preserved

```diff
                        perm1[r] = perm0
-                       4839f2                  CMPQ SI, DX             
-                       0f83a4000000            JAE 0x24d0              
-                       48893cd3                MOVQ DI, 0(BX)(DX*8)    
+                       4c8904ca                MOVQ R8, 0(DX)(CX*8)    
                        count[r]--
-                       4839c2                  CMPQ AX, DX             
-                       0f8390000000            JAE 0x24c9                      // panicindex
-                       488b3cd1                MOVQ 0(CX)(DX*8), DI    
-                       48ffcf                  DECQ DI                 
-                       48893cd1                MOVQ DI, 0(CX)(DX*8)    
+                       4c8b04c8                MOVQ 0(AX)(CX*8), R8    
+                       49ffc8                  DECQ R8                 
+                       4c8904c8                MOVQ R8, 0(AX)(CX*8)    
```

- outside main loop `r` is initially set to `n`
- the main loop contains

```go
        for ; r != 1; r-- {
                count[r-1] = r
        }
```

- thus after this we know (at least on first mainloop iteration, see below about next) `r = 1` furtner

- the outer loop, once again, is `for ; r < n; r++`
- thus inside r ∈ [1, n)
- thus `perm1[r]` and `count[r]` do not need BC
- the outer loop only increases r which was initially known to be = 1, so after it is known to be >= 1
- this way when main loop runs next time in

```go
        for ; r != 1; r-- {
                count[r-1] = r
        }
```

the compiler can prove, since `r >= 1` the loop will terminate with r==1, not loop forever

```diff
                        if count[r] > 0 {
-                       4885ff                  TESTQ DI, DI            
-                       7f10                    JG 0x2459               
+                       4d85c0                  TESTQ R8, R8            
+                       7f10                    JG 0x2305               
                for ; r < n; r++ {
-                       48ffc2                  INCQ DX                 
-                       4c39c2                  CMPQ R8, DX             
-                       7d0b                    JGE 0x245c              
+                       48ffc1                  INCQ CX                 
+                       4839f1                  CMPQ SI, CX             
+                       7d0b                    JGE 0x2308              
                        perm0 := perm1[0]
-                       488b3b                  MOVQ 0(BX), DI          
-                       4531d2                  XORL R10, R10           
+                       4c8b02                  MOVQ 0(DX), R8          
+                       4531db                  XORL R11, R11           
                        for i := 0; i < r; i++ {
-                       eba9                    JMP 0x2402              
+                       ebd7                    JMP 0x22dc              
                for ; r < n; r++ {
-                       4c39c2                  CMPQ R8, DX             
+                       4839f1                  CMPQ SI, CX             
                if r == n {
-                       741a                    JE 0x2478               
-                       488b7c2440              MOVQ 0x40(SP), DI       
-                       4d89ea                  MOVQ R13, R10           
+                       7415                    JE 0x231f               
+                       4d89d0                  MOVQ R10, R8            
                if didpr < 30 {
-                       4983f91e                CMPQ $0x1e, R9          
-                       0f8d78feffff            JGE 0x22e8              
+                       4883ff1e                CMPQ $0x1e, DI          
+                       0f8d2fffffff            JGE 0x2246              
                        didpr++
-                       49ffc1                  INCQ R9                 
+                       48ffc7                  INCQ DI                 
                if didpr < 30 {
-                       e970feffff              JMP 0x22e8              
+                       e927ffffff              JMP 0x2246              
                        return flipsMax
-                       4c89ac2490000000        MOVQ R13, 0x90(SP)      
-                       488b6c2478              MOVQ 0x78(SP), BP       
-                       4881c480000000          ADDQ $0x80, SP          
+                       4c89542458              MOVQ R10, 0x58(SP)      
+                       488b6c2440              MOVQ 0x40(SP), BP       
+                       4883c448                ADDQ $0x48, SP          
                        c3                      RET                     
-                       4d89d5                  MOVQ R10, R13           
+                       4d89c2                  MOVQ R8, R10            
                        if flipsMax < flips {
-                       ebba                    JMP 0x244c              
-                       4c8b5c2468              MOVQ 0x68(SP), R11      
-                       4c8b642438              MOVQ 0x38(SP), R12      
-                       4d89d5                  MOVQ R10, R13           
+                       ebc5                    JMP 0x22f8              
+                       4c8b4c2438              MOVQ 0x38(SP), R9       
+                       4d89c2                  MOVQ R8, R10            
                if perm1[0] != 0 && perm1[n1] != n1 {
-                       ebab                    JMP 0x244c              
-                       4c8b5c2468              MOVQ 0x68(SP), R11      
-                       4c8b642438              MOVQ 0x38(SP), R12      
-                       4d89d5                  MOVQ R10, R13           
-                       eb9c                    JMP 0x244c              
+                       ebbb                    JMP 0x22f8              
+                       4c8b4c2438              MOVQ 0x38(SP), R9       
+                       4d89c2                  MOVQ R8, R10            
+                       ebb1                    JMP 0x22f8              
                return 0
-                       48c784249000000000000000        MOVQ $0x0, 0x90(SP)     
-                       488b6c2478                      MOVQ 0x78(SP), BP       
-                       4881c480000000                  ADDQ $0x80, SP          
-                       c3                              RET                     
-                       count[r]--
-  0x24c9               e800000000              CALL 0x24ce             [1:5]R_CALL:runtime.panicindex
-  0x24ce               0f0b                    UD2                     
-                       perm1[r] = perm0
-  0x24d0               e800000000              CALL 0x24d5             [1:5]R_CALL:runtime.panicindex
-  0x24d5               0f0b                    UD2                     
-                               perm1[i] = perm1[i+1]
-  0x24d7               e800000000              CALL 0x24dc             [1:5]R_CALL:runtime.panicindex
-  0x24dc               0f0b                    UD2                     
-                               j := perm[k]
-  0x24de               e800000000              CALL 0x24e3             [1:5]R_CALL:runtime.panicindex
-  0x24e3               0f0b                    UD2                     
-                                       perm[i], perm[j] = perm[j], perm[i]
-  0x24e5               e800000000              CALL 0x24ea             [1:5]R_CALL:runtime.panicindex
-  0x24ea               0f0b                    UD2                     
-                               perm[i] = perm1[i]
-  0x24ec               e800000000              CALL 0x24f1             [1:5]R_CALL:runtime.panicindex
-  0x24f1               0f0b                    UD2                     
-               if perm1[0] != 0 && perm1[n1] != n1 {
-  0x24f3               e800000000              CALL 0x24f8             [1:5]R_CALL:runtime.panicindex
-  0x24f8               0f0b                    UD2                     
-                       count[r-1] = r
-  0x24fa               e800000000              CALL 0x24ff             [1:5]R_CALL:runtime.panicindex
-  0x24ff               0f0b                    UD2                     
-               perm1[i] = i // initial (trivial) permutation
-  0x2501               e800000000              CALL 0x2506             [1:5]R_CALL:runtime.panicindex
-  0x2506               0f0b                    UD2                     
+                       48c744245800000000      MOVQ $0x0, 0x58(SP)     
+                       488b6c2440              MOVQ 0x40(SP), BP       
+                       4883c448                ADDQ $0x48, SP          
+                       c3                      RET                     
 func fannkuch(n int) int {
-                       e800000000              CALL 0x250d             [1:5]R_CALL:runtime.morestack_noctxt
-                       e9c0fcffff              JMP %22%22.fannkuch(SB) 
+                       e800000000              CALL 0x235f             [1:5]R_CALL:runtime.morestack_noctxt
+                       e90efeffff              JMP %22%22.fannkuch(SB) 
```

^^^ are all `CALL runtime.panicindex` destinations and they were all covered by my comments.

----

So maybe I missed something (appologize then) but above I was able to prove all runtime bounds check are unneccessary in fannkuch. I don't see any reason the compiler cannot be taught to do the same. Thus for fannkuch it is theoretically possible to reach -B level with just BCE.