
Bounds Check Elimination (BCE) in Golang 1.7 - tapirl
http://www.tapirgames.com/blog/golang-1.7-bce
======
Animats
This is an area of optimization that was figured out in the Pascal era, then
forgotten during the C era. It's good to see it coming back. Work on Pascal
was able to optimize out about 95% of bounds checks. Here's an overview that
has a literature survey.[1]

A big win is hoisting bounds checks out of loops. I think the Go compiler
already optimized out the check for loops where the loop iteration variable is
bounded by the range of the array. But that's a special case. The general case
has a problem. Consider

    
    
        func copy(src []int, dst []int) {
    	for i := range(src) {
                dst[i] = src[i]
            }
        }
    

This has a subscript out of range error if dst is shorter than src. It's the
classic C buffer overflow. For optimization purposes, we can look at this as

    
    
        func copy(src []int, dst []int) {
    	for i := range(src) {
                assert(i < len(src)) // subscript check
                assert(i < len(dst)) // subscript check 
                dst[i] = src[i]
            }
        }
    

The FOR statement insures that i < len(src), so a prover would see this as (i
< len(src) implies (i < len(src)), which reduces to True, and we can discard
that assert. We still have the assert for "dst". For that, it's possible to
hoist it out of the loop. We generate the check

    
    
        func copy(src []int, dst []int) {
            assert(len(src) <= len(dst)) // generated
    	for i := range(src) {
                assert(i < len(src)) // optimized out
                assert(i < len(dst)) // subscript check 
                dst[i] = src[i]
            }
        }
    

which gives us

    
    
        len(src) <= len(dst) and i < len(src) implies (i < len(dst))
    

which simplifies to True. So assert(i < len(dst) can be optimized out. We have
one run-time check remaining, and it's outside the loop, done once. This is
hoisting a bounds check out of a loop, and it's a huge win for inner loops.
Good test for a compiler: write a matrix multiply and make sure it can do
this.

[1]
[http://www.cri.ensmp.fr/classement/doc/E-235.ps](http://www.cri.ensmp.fr/classement/doc/E-235.ps)

~~~
kevhito
If out of bounds errors were undefined behavior, lifting the bounds check out
of the loop would be legal. But in go, it isn't an error. It is equivalent to
panic(), which can be caught. If the caller recovers, they should see the
partially-completed loop, but lifting it out of the loop would change that
behavior.

Here is an example, using your copy function:

[https://play.golang.org/p/Cc3Oc3le-9](https://play.golang.org/p/Cc3Oc3le-9)

~~~
kbenson
I think the point is that in the cases where you can prove it's not possible,
you can optimize it out. This is functionally the same, because if it's
incapable of panicing because of out of bounds, you don't need to check for
it. I think what you likely get in practice is something like:

    
    
        func copy(src []int, dst []int) {
            if (len(src) <= len(dst) {
                for i := range(src) {
                    dst[i] = src[i]
            } else {
                for i := range(src) {
                    assert(i < len(dst)) // subscript check 
                    dst[i] = src[i]
                }
            }
        }
    

That way you still get panics in the case where they are applicable, but not
in the cases where you can safely abstract the bounds check away (because a
panic for that reason should not be possible). Although this is likely at the
code generation level, so you can consider the if statement to be a sort of
macro branching on what code is actually generated.

~~~
Animats
Go backed into exceptions. Originally, "panic" was always fatal.[1] Then came
"recover".[2] But it was considered bad form to use "recover" for anything
other than doing a final cleanup and exit. Now, the library JSON parser can
panic and recover due to invalid input, then return an error code. That's
exception handling.

Now we have to give up optimizations so that Go's exception handling will
work. Every function call, every subscripted access, and every map reference
is a potential loop exit. Bleah.

[1] [http://dave.cheney.net/2012/01/18/why-go-gets-exceptions-
rig...](http://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right) [2]
[https://groups.google.com/forum/#!topic/golang-
nuts/HOXNBQu5...](https://groups.google.com/forum/#!topic/golang-
nuts/HOXNBQu5c-Q%5B1-25%5D)

------
iand
This doc, written by one of the Go team at Google, enumerates the state of BCE
as of a few months ago:

[https://docs.google.com/document/d/1vdAEAjYdzjnPA9WDOQ1e4e05...](https://docs.google.com/document/d/1vdAEAjYdzjnPA9WDOQ1e4e05cYVMpqSxJYZT33Cqw2g/edit#)

------
jjnoakes
Is Go not optimizing out the dead code in those examples because of those
debug flags? Or does it not do that in general?

Or is it just doing the bounds check elimination (and debug messages about it)
before the dead code elimination?

~~~
JamesMcMinn
Go handles dead code by not allowing you to compile something with dead code.
In the examples the "_ =" is needed to force it to compile and do the work
without actually assigning the value to anything.

~~~
fizzbatter
Perhaps i misunderstand you, but isn't simply calling a function the same as
what you quoted? Eg, `_ := Foo()` is the same as `Foo()`. No assignment
needed.

`_` mainly exists to allow ignoring single values, within multiple. Eg,
dropping an error: `val, _ := MightError()`

~~~
JamesMcMinn
Foo() can have side-effects, taking a value from an array cannot, so Foo() is
not necessarily dead code, whilst a[0] certainly is.

_ serves multiple purposes, not just for ignoring a return value. It's often
used when you care about the side effect but not the return value, such as
importation a driver for a database . You'll never use the library, so you
import it as _ "db.driver.name/path/".

~~~
euyyn
Can't taking a value from an array result in a panic? Quite the side effect.

------
sfrailsdev
tapirl, just fyi this references line numbers like line 22, but doesn't
display any numbers.

~~~
tapirl
? you mean there is not line numbers at the beginning of code lines? yes, my
blog server code is too simple to show line numbers.

~~~
packetslave
you could use something like
[http://alexgorbatchev.com/SyntaxHighlighter/](http://alexgorbatchev.com/SyntaxHighlighter/)
to do it in JavaScript.

