Go version

go1.22

Output of go env in your module/workspace:

GOARCH=amd64
GOOS=linux

What did you do?

Compile the following:

package main

import "encoding/binary"

var src uint64
var dst []byte

func main() {
    if cap(dst)-len(dst) < 8 {
        return
    }
    dst = binary.BigEndian.AppendUint64(dst, src)
}

What did you see happen?

I see the following in the assembly:

    0x004a 00074 (/usr/local/go1.22.0/src/encoding/binary/binary.go:201)    MOVL    $8, DI
    0x004f 00079 (/usr/local/go1.22.0/src/encoding/binary/binary.go:201)    LEAQ    type:uint8(SB), SI
    0x0056 00086 (/usr/local/go1.22.0/src/encoding/binary/binary.go:201)    PCDATA  $1, $0
    0x0056 00086 (/usr/local/go1.22.0/src/encoding/binary/binary.go:201)    CALL    runtime.growslice(SB)
    0x005b 00091 (/usr/local/go1.22.0/src/encoding/binary/binary.go:201)    MOVQ    encoding/binary.v+64(SP), DX
    0x0060 00096 (/usr/local/go1.22.0/src/encoding/binary/binary.go:201)    BSWAPQ  DX
    0x0063 00099 (/usr/local/go1.22.0/src/encoding/binary/binary.go:201)    MOVQ    DX, -8(BX)(AX*1)

What did you expect to see?

No call to runtime.growslice.

This particular example seems silly, but the capacity check already happens elsewhere in code beforehand so there is no possible way that growslice would be necessary once we get to AppendUint64.

Comment From: dsnet

A fix for this should hopefully take #66691 into account such that the following avoids both growslice calls for subsequent append calls:

if cap(dst)-len(dst) < 1+8 {
    dst = slices.Grow(dst, 1+8)
}
dst = append(dst, flag)
dst = binary.BigEndian.AppendUint64(dst, src)

(Obviously, slices.Grow would need to call growslice, but after the conditional check we should be safe for subsequent append operations)

Comment From: Jorropo

With lots of massaging this works:

package main

import "encoding/binary"

var src uint64
var dst []byte

func main() {
    if uint(len(dst)+8) > uint(cap(dst)) {
        return
    }
    dst = binary.BigEndian.AppendUint64(dst, src)
}

This works because uint(len(dst)+8) > uint(cap(dst)) is the exact check generated by the frontend for this append.

I see at least two things here: - SliceLen and SliceCap doesn't seems to be recognized as uint63 or uint31 as else Less64 and Less64U should already prove each other https://godbolt.org/z/h9dYfY19M. - More general equivalence or canonicalization issues.

Comment From: dmitshur

CC @golang/compiler.

Comment From: gopherbot

Change https://go.dev/cl/599096 mentions this issue: cmd/compile: rewrite the constant parts of the prove pass