Skip to content

cmd/compile: BCE does not take transitivity into account #19714

Closed
@navytux

Description

@navytux

Please answer these questions before submitting your issue. Thanks!

What did you do?

Hello up there. Please consider the following function:

package xxx

func Aaa(b []byte, i int) {
        if !(0 <= i && i < len(b)) {
                return
        }

        for j := 0; j < i; j++ {
                b[j]++
        }
}

(https://play.golang.org/p/pACbUNtHoB)

here i is in valid range for b and j is iterating in [0,i) so it is also in valid range for b. However Aaa compiles to:

TEXT ·Aaa(SB), $8-32 // loopbce2.go:3
        // SUBQ    $8, SP
        // MOVQ    BP, (SP) (BP save)
        // LEAQ    (SP), BP (BP init)
        // FUNCDATA $0, gclocals·4032f753396f2012ad1784f398b170f4(SB) (args)
        FUNCDATA   $1, gclocals·69c1753bd5f81501d95132d08af04464(SB) (locals)
        MOVQ       i+24(FP), AX           // loopbce2.go:4
        TESTQ      AX, AX
        JLT        $0, pc80
        MOVQ       b+8(FP), CX
        CMPQ       AX, CX
        JGE        $0, pc80
        MOVQ       b+0(FP), DX            // loopbce2.go:8
        MOVQ       $0, BX                 // loopbce2.go:4
pc39:
        CMPQ       BX, AX                 // loopbce2.go:8
        JGE        $0, pc71
        CMPQ       BX, CX                 // loopbce2.go:9      <-- NOTE
        JCC        $0, pc64                                     <-- NOTE
        MOVBLZX    (DX)(BX*1), SI
        INCL       SI
        MOVB       SIB, (DX)(BX*1)
        INCQ       BX                     // loopbce2.go:8
        JMP        pc39
pc64:
        PCDATA     $0, $1                 // loopbce2.go:9
        CALL       runtime.panicindex(SB)                       <-- NOTE
        UNDEF
pc71:
        // MOVQ    (SP), BP (BP restore)  // loopbce2.go:11
        // ADDQ    $8, SP (SP restore)
        RET
pc80:
        // MOVQ    (SP), BP (BP restore)  // loopbce2.go:5
        // ADDQ    $8, SP (SP restore)
        RET

with b[j] access being bounds checked at runtime.

Just for the reference, if loop for j is in [0,len(b)):

        for j := 0; j < len(b); j++ {
                b[j]++
        }

no bounds checking is generated.

So looks like what is needed is to teach BCE that if i1 is in valid bounds, i2 in [0, i1] => i2 is also in valid bounds.


This actually came up as simplified case of

package xxx

func Encode(dst, src []byte) {
        if len(dst) < len(src) {
                return  // panic("dst overflow")
        }

        for i, v := range src {
                dst[i] = v
        }
}

where bounds checks are unneccessarily generated for dst[i].

The above case in turn tries to model e.g. encoding/hex.Encode in simplified way:

func Encode(dst, src []byte) int {
        //if len(dst) < len(src) * 2 {
        //      panic("dst overflow")
        //} 

        for i, v := range src {
                dst[i*2] = hextable[v>>4]
                dst[i*2+1] = hextable[v&0x0f]
        }   

        return len(src) * 2 
}

where even if initial check for len(dst) < len(src) * 2 => error is activated, still 2 bounds checks are generated (for dst[i*2] and dst[i*2+1]) making hex.Encode (together with #15808) much slower than it should be:

TEXT ·Encode(SB), $24-56 // hex.go:29
        // MOVQ    (TLS), CX (stack growth prologue)
        // CMPQ    SP, 16(CX)
        // JLS     207
        // SUBQ    $24, SP
        // MOVQ    BP, 16(SP) (BP save)
        // LEAQ    16(SP), BP (BP init)
        // FUNCDATA $0, gclocals·b9de2a960cf046391bcd3b554f7fabca(SB) (args)
        FUNCDATA   $1, gclocals·69c1753bd5f81501d95132d08af04464(SB) (locals)
        MOVQ       src+32(FP), AX         // hex.go:30
        MOVQ       AX, CX
        SHLQ       $1, AX
        MOVQ       dst+8(FP), DX
        CMPQ       DX, AX
        JLT        $0, pc177
        TESTQ      CX, CX                 // hex.go:34
        JLE        $0, pc148
        MOVQ       dst+0(FP), BX
        MOVQ       src+24(FP), SI
        MOVQ       $0, DI                 // hex.go:29
pc71:
        MOVBLZX    (SI), R8               // hex.go:34
        MOVQ       DI, R9                 // hex.go:35
        SHLQ       $1, DI
        MOVL       R8, R10
        SHRB       $4, R8B
        MOVBLZX    R8B, R8
        LEAQ       go.string."0123456789abcdef"(SB), R11 // hex.go:36  <-- #15808
        MOVBLZX    (R11)(R8*1), R8        // hex.go:35
        CMPQ       DI, DX                                       <-- NOTE
        JCC        $0, pc170                                    <-- NOTE
        MOVB       R8B, (BX)(DI*1)
        LEAQ       1(DI), R8              // hex.go:36
        ANDL       $15, R10
        MOVBLZX    (R11)(R10*1), R10
        CMPQ       R8, DX                                       <-- NOTE
        JCC        $0, pc163                                    <-- NOTE
        MOVB       R10B, 1(BX)(DI*1)
        INCQ       SI                     // hex.go:34
        LEAQ       1(R9), DI
        CMPQ       DI, CX
        JLT        $1, pc71
pc148:
        MOVQ       AX, _r2+48(FP)         // hex.go:39
        // MOVQ    16(SP), BP (BP restore)
        // ADDQ    $24, SP (SP restore)
        RET
pc163:
        // PCDATA  $0, $1 (stack growth)  // hex.go:36
        // CALL    runtime.panicindex(SB)
        // UNDEF
pc170:
        // PCDATA  $0, $1                 // hex.go:35
        // CALL    runtime.panicindex(SB)
        // UNDEF
pc177:
        // LEAQ    type.string(SB), AX    // hex.go:31
        // MOVQ    AX, (SP)
        // LEAQ    ·statictmp_12(SB), AX
        // MOVQ    AX, 8(SP)
        // PCDATA  $0, $1
        // CALL    runtime.gopanic(SB)
        // UNDEF
        // NOP
        // PCDATA  $0, $-1                // hex.go:29
        // CALL    runtime.morestack_noctxt(SB)
        // JMP     0

What did you expect to see?

No runtime bound checks.

What did you see instead?

Runtime bound check present for b[j] access.

Does this issue reproduce with the latest release (go1.8)?

Yes.

System details

go version devel +ecc6a81617 Sat Mar 25 00:35:35 2017 +0000 linux/amd64
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/kirr/go"
GORACE=""
GOROOT="/home/kirr/src/tools/go/go"
GOTOOLDIR="/home/kirr/src/tools/go/go/pkg/tool/linux_amd64"
GCCGO="/usr/bin/gccgo"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build832545546=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOROOT/bin/go version: go version devel +ecc6a81617 Sat Mar 25 00:35:35 2017 +0000 linux/amd64
GOROOT/bin/go tool compile -V: compile version devel +ecc6a81617 Sat Mar 25 00:35:35 2017 +0000 X:framepointer
uname -sr: Linux 4.9.0-2-amd64
Distributor ID:	Debian
Description:	Debian GNU/Linux 9.0 (stretch)
Release:	9.0
Codename:	stretch
/lib/x86_64-linux-gnu/libc.so.6: GNU C Library (Debian GLIBC 2.24-9) stable release version 2.24, by Roland McGrath et al.
gdb --version: GNU gdb (Debian 7.12-6) 7.12.0.20161007-git

Thanks beforehand,
Kirill

/cc @randall77, @dr2chase

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions