Skip to content

[BUG][GO]: //go:inline pragmas are no-ops — hot-path functions not being inlined #3446

@ayush00git

Description

@ayush00git

Search before asking

  • I had searched in the issues and found no similar issues.

Version

Current

Component(s)

Go

It was introduced in the PR #3071

Minimal reproduce step

Run go build -gcflags='-m -m'
Output -

buffer.go:71:  cannot inline (*ByteBuffer).WriteByte:   cost 86 > budget 80
buffer.go:86:  cannot inline (*ByteBuffer).WriteInt8:   cost 85 > budget 80
buffer.go:114: cannot inline (*ByteBuffer).WriteUint32: cost 85 > budget 80
buffer.go:121: cannot inline (*ByteBuffer).WriteInt32:  cost 86 > budget 80
buffer.go:199: cannot inline (*ByteBuffer).ReadByte:    cost 94 > budget 80
buffer.go:251: cannot inline (*ByteBuffer).ReadUint32:  cost 96 > budget 80
buffer.go:264: cannot inline (*ByteBuffer).ReadUint64:  cost 96 > budget 80

Summary

buffer.go uses //go:inline on functions. This is not a valid Go compiler directive — the standard gc compiler silently ignores it. As a result, critical hot-path functions like WriteInt32, ReadByte, ReadUint64 etc. are not being inlined, paying full function call overhead on every invocation.

Root Cause

  • Write functions: the grow() call adds enough cost (~40) to push the total over 80.
  • Read functions: the inline BufferOutOfBoundError() construction adds ~15–20 cost, pushing totals to 94–97.

Proposed Fix

  1. Remove all //go:inline pragmas — they are no-ops and misleading.
  2. Write path: inline the fast-path bounds check directly and keep only growSlow() as //go:noinline:
    func (b *ByteBuffer) WriteInt8(value int8) {
        if b.writerIndex+1 > len(b.data) {
            b.growSlow(1)
        }
        b.data[b.writerIndex] = byte(value)
        b.writerIndex++
    }
  3. Read path: move error construction to a //go:noinline cold-path helper:
    func (b *ByteBuffer) ReadInt8(err *Error) int8 {
        if b.readerIndex+1 > len(b.data) {
            b.readBoundsError(err, 1)
            return 0
        }
        v := int8(b.data[b.readerIndex])
        b.readerIndex++
        return v
    }
    
    //go:noinline
    func (b *ByteBuffer) readBoundsError(err *Error, n int) {
        *err = BufferOutOfBoundError(b.readerIndex, n, len(b.data))
    }

Both changes bring function costs well under 80, enabling automatic compiler inlining with no pragmas needed.

Impact

These are the most frequently called functions in the entire serialization pipeline. Enabling inlining eliminates function call overhead (stack frame, argument spilling) on every primitive read/write operation.

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions