Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 50 additions & 34 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package checker

import (
"context"
"encoding/binary"
"fmt"
"iter"
"maps"
Expand Down Expand Up @@ -17352,62 +17353,77 @@ func (c *Checker) isThislessInterface(symbol *ast.Symbol) bool {
return true
}

func hashWrite32[T ~int32 | ~uint32](h *xxh3.Hasher, value T) {
v := uint32(value)
_, _ = h.Write([]byte{
byte(v),
byte(v >> 8),
byte(v >> 16),
byte(v >> 24),
})
}

func hashWrite64[T ~int | ~uint | ~int64 | ~uint64](h *xxh3.Hasher, value T) {
v := uint64(value)
_, _ = h.Write([]byte{
byte(v),
byte(v >> 8),
byte(v >> 16),
byte(v >> 24),
byte(v >> 32),
byte(v >> 40),
byte(v >> 48),
byte(v >> 56),
})
}

type CacheHashKey xxh3.Uint128

func (k CacheHashKey) IsZero() bool {
return xxh3.Uint128(k) == xxh3.Uint128{}
}

type keyBuilder struct {
h xxh3.Hasher
inlineLength int
overflowBuffer []byte
inlineBuffer [192]byte
Comment on lines +17363 to +17365

@jakebailey Jake Bailey (jakebailey) Jul 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this be a lot easier to just initialize it with say:

type keyBuilder struct {
    buffer []byte
    space  [192]byte
}

func (b *keyBuilder) init() {
    if b.buffer == nil {
        b.buffer = b.space[:0]
    }
}

func (b *keyBuilder) writeByte(c byte) {
    b.init()
    // ...
}

And let Go manage overflowing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or a new func, or even a pool get/put)

@mds-ant Marius Schulz (mds-ant) Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, the self-referential slice defeat's Go's escape analysis, which means every var b keyBuilder gets moved to the heap. On the VS Code corpus that's +10.7M allocations and a significant wall time regression.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Theoretically it could just go onto the heap anyway in a pool

}

func (b *keyBuilder) hash() CacheHashKey {
return CacheHashKey(b.h.Sum128())
if b.overflowBuffer == nil {
return CacheHashKey(xxh3.Hash128(b.inlineBuffer[:b.inlineLength]))
}
return CacheHashKey(xxh3.Hash128(append(b.overflowBuffer, b.inlineBuffer[:b.inlineLength]...)))
}

// spill moves the buffered bytes onto the end of overflowBuffer, so the key's byte
// stream stays overflowBuffer followed by inlineBuffer.
func (b *keyBuilder) spill() {
b.overflowBuffer = append(b.overflowBuffer, b.inlineBuffer[:b.inlineLength]...)
b.inlineLength = 0
}

func (b *keyBuilder) writeByte(c byte) {
_, _ = b.h.Write([]byte{c})
if b.inlineLength == len(b.inlineBuffer) {
b.spill()
}
b.inlineBuffer[b.inlineLength] = c
b.inlineLength++
}

func (b *keyBuilder) writeString(s string) {
_, _ = b.h.WriteString(s)
if b.inlineLength+len(s) > len(b.inlineBuffer) {
b.spill()
if len(s) > len(b.inlineBuffer) {
b.overflowBuffer = append(b.overflowBuffer, s...)
return
}
}
b.inlineLength += copy(b.inlineBuffer[b.inlineLength:], s)
}

func (b *keyBuilder) writeUint32(v uint32) {
if b.inlineLength+4 > len(b.inlineBuffer) {
b.spill()
}
binary.LittleEndian.PutUint32(b.inlineBuffer[b.inlineLength:], v)
b.inlineLength += 4
}

func (b *keyBuilder) writeUint64(v uint64) {
if b.inlineLength+8 > len(b.inlineBuffer) {
b.spill()
}
binary.LittleEndian.PutUint64(b.inlineBuffer[b.inlineLength:], v)
b.inlineLength += 8
}

func (b *keyBuilder) writeInt(value int) {
hashWrite64(&b.h, value)
b.writeUint64(uint64(value))
}

func (b *keyBuilder) writeSymbol(s *ast.Symbol) {
hashWrite64(&b.h, ast.GetSymbolId(s))
b.writeUint64(uint64(ast.GetSymbolId(s)))
}

func (b *keyBuilder) writeType(t *Type) {
hashWrite32(&b.h, t.id)
b.writeUint32(uint32(t.id))
}

func (b *keyBuilder) writeTypes(types []*Type) {
Expand Down Expand Up @@ -17463,7 +17479,7 @@ func (b *keyBuilder) writeGenericTypeReferences(source *Type, target *Type, igno
}

func (b *keyBuilder) writeNodeId(id ast.NodeId) {
hashWrite64(&b.h, id)
b.writeUint64(uint64(id))
}

func (b *keyBuilder) writeNode(node *ast.Node) {
Expand Down Expand Up @@ -17560,7 +17576,7 @@ func getIndexedAccessKey(objectType *Type, indexType *Type, accessFlags AccessFl
var b keyBuilder
b.writeType(objectType)
b.writeType(indexType)
hashWrite32(&b.h, accessFlags)
b.writeUint32(uint32(accessFlags))
b.writeAlias(alias)
return b.hash()
}
Expand Down Expand Up @@ -17603,7 +17619,7 @@ func getRelationKey(source *Type, target *Type, intersectionState IntersectionSt
b.writeType(source)
b.writeType(target)
}
hashWrite32(&b.h, intersectionState)
b.writeUint32(uint32(intersectionState))
return b.hash(), constrained
}

Expand Down