Skip to content
Open
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion .github/workflows/buf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ jobs:
- uses: actions/checkout@v5
- uses: bufbuild/buf-action@v1
with:
version: 1.56.0 # Keep in sync with Makefile BUF_VERSION
version: 1.66.0 # Keep in sync with internal/tools/external/go.mod.
format: false # Turn off format, since most proto definitions are for testing
token: ${{ secrets.BUF_TOKEN }}
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
# to private repositories.
os: [ubuntu-latest, macos-15]
mode: [fast, debug, race, unopt]
go-version: [1.24.x, 1.25.x]
go-version: [1.25.x, 1.26.x]
runs-on: ${{ matrix.os }}

steps:
Expand Down Expand Up @@ -82,4 +82,4 @@ jobs:

- name: Lint
uses: golangci/golangci-lint-action@v8
with: {version: v2.4.0} # Keep in sync with the Makefile.
with: {version: v2.11.3} # Keep in sync with internal/tools/external/go.mod.
1 change: 1 addition & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ linters:
- predeclared # Using predeclared names as variables is fine.
- revive # Ditto. ^

- goprintffuncname # Who cares.
- lll # More trouble than it's worth.
- makezero # Appending to non-empty slices is ok.
- nestif # This encourages artificially breaking things up.
Expand Down
71 changes: 32 additions & 39 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ export GOBIN := $(abspath $(BIN))
COPYRIGHT_YEARS := 2025
LICENSE_IGNORE := testdata/

GO_VERSION := go1.25.0
BUF_VERSION := v1.56.0 # Keep in sync w/ .github/workflows/buf.yaml.
LINT_VERSION := v2.4.0 # Keep in sync w/ .github/workflows/ci.yaml.

GOOS_HOST := $(shell go env GOOS)
GOARCH_HOST := $(shell go env GOARCH)

Expand All @@ -41,22 +37,27 @@ GOARCH ?=
GOAMD64 ?=
GOARM64 ?=

HOST_ENV ?= GOTOOLCHAIN=local
EXEC_ENV ?= GOOS=$(GOOS) GOARCH=$(GOARCH) GOAMD64=$(GOAMD64) GOARM64=$(GOARM64) GOTOOLCHAIN=local
GOTOOLCHAIN ?= local
GOEXPERIMENT ?= simd

HOST_ENV ?= GOTOOLCHAIN=$(GOTOOLCHAIN) GOEXPERIMENT=$(GOEXPERIMENT)
EXEC_ENV ?= GOOS=$(GOOS) GOARCH=$(GOARCH) GOAMD64=$(GOAMD64) GOARM64=$(GOARM64) GOTOOLCHAIN=$(GOTOOLCHAIN) GOEXPERIMENT=$(GOEXPERIMENT)

# Go will carelessly pick these up on host-side builds if we don't unexport them.
unexport GOOS
unexport GOARCH

HYPERTESTFLAGS ?=
TESTFLAGS ?=
BENCHFLAGS ?= -test.benchmem

GO ?= go
HOST_TARGET ?=
GO_HOST := $(HOST_TARGET) $(GO)
GO_HOST := $(HOST_ENV) $(GO)
GO := $(EXEC_ENV) $(GO)
TEST := $(EXEC_ENV) $(BIN)/hypertest -o $(TESTS) $(HYPERTESTFLAGS)

TOOLS := ./internal/tools/external/go.mod
TEST := $(GO_HOST) tool hypertest -o $(TESTS) -env "GOOS=$(GOOS);GOARCH=$(GOARCH);GOAMD64=$(GOAMD64);GOARM64=$(GOARM64)" $(HYPERTESTFLAGS)
DUMP := $(GO_HOST) tool hyperdump
LINT := $(GO_HOST) tool -modfile $(TOOLS) golangci-lint
BUF := $(GO_HOST) tool -modfile $(TOOLS) buf

TAGS ?= ""
REMOTE ?= ""
Expand All @@ -74,6 +75,10 @@ else
endif
PKG ?= .

# Go will carelessly pick these up on host-side builds if we don't unexport them.
unexport GOOS
unexport GOARCH

.PHONY: help
help: ## Describe useful make targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
Expand All @@ -91,7 +96,7 @@ clean: ## Delete intermediate build artifacts
git clean -Xdf

.PHONY: test
test: build $(BIN)/hypertest ## Run unit tests
test: build ## Run unit tests
$(TEST) -remote=$(REMOTE) -tags=$(TAGS) -checkptr -p $(PKGS) -- \
$(TESTFLAGS)

Expand All @@ -111,7 +116,7 @@ profile: build $(BIN)/hypertest ## Profile benchmarks and open them in pprof
.PHONY: asm
asm: build ## Generate assembly output for manual inspection
$(GO) test -tags=$(TAGS) -c -o hyperpb.test $(PKG) $(TESTFLAGS)
$(GO_HOST) run ./internal/tools/hyperdump \
$(DUMP) \
-s '$(ASM_FILTER)' \
-info $(ASM_INFO) \
-prefix 'buf.build/go/hyperpb' \
Expand All @@ -123,24 +128,29 @@ asm: build ## Generate assembly output for manual inspection
build: generate ## Build all packages
$(GO) build -tags=$(TAGS) $(PKGS)

.PHONY: show-env
show-env: ## Print the Go tool's interpreted environment.
go version
$(GO) env

.PHONY: lint
lint: $(BIN)/golangci-lint ## Lint
lint: generate ## Lint
$(GO_HOST) vet -unsafeptr=false ./...
$(BIN)/golangci-lint -v run \
$(LINT) -v run \
--timeout 3m0s \
--modules-download-mode=readonly

.PHONY: lintfix
lintfix: $(BIN)/golangci-lint ## Automatically fix some lint errors
$(BIN)/golangci-lint run \
lintfix: generate ## Automatically fix some lint errors
$(LINT) run \
--timeout 3m0s \
--modules-download-mode=readonly \
--fix

.PHONY: generate
generate: internal/gen/*/*.pb.go $(BIN)/license-header ## Regenerate code and licenses
generate: internal/gen/*/*.pb.go ## Regenerate code and licenses
$(GO_HOST) generate ./...
$(BIN)/license-header \
$(GO_HOST) tool -modfile $(TOOLS) license-header \
--license-type apache \
--copyright-holder "Buf Technologies, Inc." \
--year-range "$(COPYRIGHT_YEARS)" \
Expand All @@ -157,23 +167,6 @@ checkgenerate:
@# Used in CI to verify that `make generate` doesn't produce a diff.
git --no-pager diff --exit-code >&2

internal/gen/*/*.pb.go: $(BIN)/buf internal/proto/*/*/*.proto internal/proto/*/*/*/*.proto
$(BIN)/buf generate --clean
$(BIN)/buf generate --template buf.vt.gen.yaml

.PHONY: $(BIN)/hypertest
$(BIN)/hypertest: generate
@mkdir -p $(@D)
$(GO_HOST) build -o $(BIN)/hypertest ./internal/tools/hypertest

$(BIN)/buf: Makefile
@mkdir -p $(@D)
$(GO_HOST) install github.com/bufbuild/buf/cmd/buf@$(BUF_VERSION)

$(BIN)/license-header: Makefile
@mkdir -p $(@D)
$(GO_HOST) install github.com/bufbuild/buf/private/pkg/licenseheader/cmd/license-header@$(BUF_VERSION)

$(BIN)/golangci-lint: Makefile
@mkdir -p $(@D)
$(GO_HOST) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(LINT_VERSION)
internal/gen/*/*.pb.go: internal/proto/*/*/*.proto internal/proto/*/*/*/*.proto
$(BUF) generate --clean
$(BUF) generate --template buf.vt.gen.yaml
9 changes: 8 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module buf.build/go/hyperpb

go 1.24.0
go 1.26.1

require (
al.essio.dev/pkg/shellescape v1.6.0
Expand Down Expand Up @@ -39,3 +39,10 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect
)

tool (
buf.build/go/hyperpb/internal/tools/hyperdump
buf.build/go/hyperpb/internal/tools/hyperstencil
buf.build/go/hyperpb/internal/tools/hypertag
buf.build/go/hyperpb/internal/tools/hypertest
)
80 changes: 80 additions & 0 deletions internal/inlinetest/inlinetest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2025 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package inlinetest

import (
"errors"
"fmt"
"os"
"os/exec"
"regexp"
"runtime/debug"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

// AssertInlined returns whether the compiler is willing to inline the given
// symbols in the package being tested.
//
// The symbols must be either a single identifier or of the form Type.Method.
// Pointer-receiver methods should not use the (*Type).Method syntax.
func AssertInlined(t *testing.T, symbols ...string) {
t.Helper()
for _, symbol := range symbols {
_, ok := inlined[symbol]
assert.True(t, ok, "%s is not inlined", symbol)
}
}

var inlined = make(map[string]struct{})

func init() {
if !testing.Testing() {
panic("inlinetest: cannot import inlinetest except in a test")
}

// This is based on a pattern of tests appearing in several places in Go's
// standard library.
tool := "go"
if env, ok := os.LookupEnv("GO"); ok {
tool = env
}

info, ok := debug.ReadBuildInfo()
if !ok {
panic(errors.New("inlinetest: could not read build info"))
}

//nolint:gosec

Check failure on line 62 in internal/inlinetest/inlinetest.go

View workflow job for this annotation

GitHub Actions / Lint (1.24.x)

directive `//nolint:gosec` is unused for linter "gosec" (nolintlint)

Check failure on line 62 in internal/inlinetest/inlinetest.go

View workflow job for this annotation

GitHub Actions / Lint (1.25.x)

directive `//nolint:gosec` is unused for linter "gosec" (nolintlint)
out, err := exec.Command(

Check failure on line 63 in internal/inlinetest/inlinetest.go

View workflow job for this annotation

GitHub Actions / Lint (1.24.x)

os/exec.Command must not be called. use os/exec.CommandContext (noctx)

Check failure on line 63 in internal/inlinetest/inlinetest.go

View workflow job for this annotation

GitHub Actions / Lint (1.25.x)

os/exec.Command must not be called. use os/exec.CommandContext (noctx)
tool,
"build",
"--gcflags=-m", // -m records optimization decisions.
strings.TrimSuffix(info.Path, ".test"),
).CombinedOutput()
if err != nil {
panic(fmt.Errorf("inlinetest: go build failed: %w, %s", err, out))
}

remarkRe := regexp.MustCompile(`(?m)^\./\S+\.go:\d+:\d+: can inline (.+?)$`)
ptrRe := regexp.MustCompile(`\(\*(.+)\)\.`)
for _, match := range remarkRe.FindAllSubmatch(out, -1) {
match := string(match[1])
match = ptrRe.ReplaceAllString(match, "$1.")
inlined[match] = struct{}{}
}
}
7 changes: 6 additions & 1 deletion internal/prototest/equal.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,12 @@ func (e *equal) any(v1, v2 any, rec bool) {
slices.Reverse(b1)
slices.Reverse(b2)

e.fail("expected %v:0x%x, got %v:0x%x (%T)", v1, b1, v2, b2, v2)
diff := slices.Clone(b1)
for i := range diff {
diff[i] ^= b2[i]
}

e.fail("expected %v:0x%x, got %v:0x%x, diff: %x (%T)", v1, b1, v2, b2, diff, v2)
}
}

Expand Down
2 changes: 1 addition & 1 deletion internal/scc/scc.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (d *DAG[Node]) ForNode(node Node) *Component[Node] {
return &d.components[idx]
}

// To range over the components some node depends on.
// Topological range over the components some node depends on.
func (d *DAG[Node]) Topological() iter.Seq[*Component[Node]] {
return func(yield func(*Component[Node]) bool) {
for i := range d.components {
Expand Down
4 changes: 2 additions & 2 deletions internal/swiss/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import (
"buf.build/go/hyperpb/internal/xunsafe"
)

//go:generate go run ../tools/hyperstencil
//go:generate go tool hyperstencil

const (
minEntires = ctrlSize * 7 / 8
Expand Down Expand Up @@ -234,7 +234,7 @@ func (t *Table[K, V]) LookupFunc(k []byte, extract func(K) []byte) *V {
return t.values().Get(idx)
}

// insert returns a pointer to the slot for the given key. extract is an
// Insert returns a pointer to the slot for the given key. extract is an
// optional function for extracting variable-length key material from keys
// in the table.
//
Expand Down
2 changes: 1 addition & 1 deletion internal/swiss/table_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const (

var benchProbes = flag.Bool("hyperpb.benchprobe", false, "if true, benchmark probe sequence length")

//go:generate go run ../tools/hyperstencil
//go:generate go tool hyperstencil

func BenchmarkTable(b *testing.B) {
u32Benchmark(b, uint32s{}, mapSize)
Expand Down
4 changes: 2 additions & 2 deletions internal/tdp/compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import (
"buf.build/go/hyperpb/internal/xunsafe"
)

// CompileOption is a configuration setting for [Compile].
// Options is a configuration setting for [Compile].
type Options struct {
Profile profile.Profile
Extensions ExtensionResolver
Expand Down Expand Up @@ -452,7 +452,7 @@ func (c *compiler) codegen(ir *ir) {
)
mpf.Push(tdp.FieldParser{
Tag: mapValue,
Parse: uintptr(xunsafe.NewPC(vm.Thunk(vm.P1.ParseMapEntry))),
Parse: uintptr(xunsafe.NewPC(vm.Thunk(vm.ParseMapEntry))),
})

// Write the fast-lookup lut.
Expand Down
2 changes: 1 addition & 1 deletion internal/tdp/compiler/linker/sym.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (s *Sym) Push(v any) int {
return s.PushBytes(align, xunsafe.AnyBytes(v))
}

// Push appends raw bytes to this symbol, ensuring that it is correctly-aligned.
// PushBytes appends raw bytes to this symbol, ensuring that it is correctly-aligned.
//
// Returns the offset of the pushed data.
func (s *Sym) PushBytes(align int, data []byte) int {
Expand Down
6 changes: 3 additions & 3 deletions internal/tdp/dynamic/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func (m *Message) Get(fd protoreflect.FieldDescriptor) protoreflect.Value {
return fd.Default()
}

// GetByIndex is like [Message.Get], but it takes a raw field index, performing
// GetByIndexUnchecked is like [Message.Get], but it takes a raw field index, performing
// no bounds checks.
func (m *Message) GetByIndexUnchecked(n int) protoreflect.Value {
return m.Type().ByIndex(n).Get(unsafe.Pointer(m))
Expand Down Expand Up @@ -201,7 +201,7 @@ func (m *Message) GetBit(n uint32) bool {
return word&mask != 0
}

// StBit sets the value of the nth bit from this message's bitset.
// SetBit sets the value of the nth bit from this message's bitset.
func (m *Message) SetBit(n uint32, flag bool) {
words := xunsafe.Cast[uint32](xunsafe.Add(m, 1))
word := xunsafe.Add(words, int(n)/32)
Expand All @@ -219,7 +219,7 @@ func (m *Message) Type() *tdp.Type {
return m.Shared.lib.AtOffset(m.TypeOffset)
}

// cold returns a pointer to the cold region, or nil if it hasn't been allocated.
// Cold returns a pointer to the cold region, or nil if it hasn't been allocated.
func (m *Message) Cold() *Cold {
if m.ColdIndex < 0 {
return nil
Expand Down
6 changes: 5 additions & 1 deletion internal/tdp/dynamic/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ type Shared struct {
arena arena.Arena
lib *tdp.Library

Src *byte
Src *byte // Needs to be a pointer to root the source buffer.
Len int

// Must *not* be a pointer, it's one-past-the-end. This points to one past the
// furthest address we are guaranteed to be able to load from.
End xunsafe.Addr[byte]

// Synchronizes calls to startParse() with this context.
Lock sync.Mutex

Expand Down
Loading
Loading