diff --git a/cgo_go124.go b/cgo_go124.go new file mode 100644 index 00000000..933a7518 --- /dev/null +++ b/cgo_go124.go @@ -0,0 +1,18 @@ +//go:build go1.24 && !cmd_go_bootstrap + +package openssl + +// The following noescape and nocallback directives are used to prevent the Go +// compiler from allocating function parameters on the heap. See +// https://github.com/golang/go/blob/0733682e5ff4cd294f5eccb31cbe87a543147bc6/src/cmd/cgo/doc.go#L439-L461 +// +// If possible, write a C wrapper function to optimize a call rather than using +// this feature so the optimization will work for all supported Go versions. +// +// This is just a performance optimization. Only add functions that have been +// observed to benefit from these directives, not every function that is merely +// expected to meet the noescape/nocallback criteria. + +// #cgo noescape go_openssl_RAND_bytes +// #cgo nocallback go_openssl_RAND_bytes +import "C" diff --git a/openssl_test.go b/openssl_test.go index 07a3c731..182fc7b8 100644 --- a/openssl_test.go +++ b/openssl_test.go @@ -2,14 +2,19 @@ package openssl_test import ( "fmt" + "go/version" "os" "runtime" + "strings" "testing" "time" "github.com/golang-fips/openssl/v2" ) +// sink is used to prevent the compiler from optimizing out the allocations. +var sink uint8 + // getVersion returns the OpenSSL version to use for testing. func getVersion() string { v := os.Getenv("GO_OPENSSL_VERSION_OVERRIDE") @@ -78,3 +83,11 @@ func TestCheckVersion(t *testing.T) { t.Fatalf("FIPS mismatch: want %v, got %v", want, fips) } } + +// compareCurrentVersion compares v with [runtime.Version]. +// See [go/versions.Compare] for information about +// v format and comparison rules. +func compareCurrentVersion(v string) int { + ver := strings.TrimPrefix(runtime.Version(), "devel ") + return version.Compare(ver, v) +} diff --git a/pbkdf2_test.go b/pbkdf2_test.go index 2bea0a25..ff9b8293 100644 --- a/pbkdf2_test.go +++ b/pbkdf2_test.go @@ -166,8 +166,6 @@ func TestWithUnsupportedHash(t *testing.T) { } } -var sink uint8 - func benchmark(b *testing.B, h func() hash.Hash) { password := make([]byte, h().Size()) salt := make([]byte, 8) diff --git a/rand_test.go b/rand_test.go index 4735ee75..6dc956b8 100644 --- a/rand_test.go +++ b/rand_test.go @@ -12,3 +12,20 @@ func TestRand(t *testing.T) { t.Fatal(err) } } + +func TestAllocations(t *testing.T) { + n := int(testing.AllocsPerRun(10, func() { + buf := make([]byte, 32) + openssl.RandReader.Read(buf) + sink ^= buf[0] + })) + want := 1 + if compareCurrentVersion("go1.24") >= 0 { + // The go1.24 compiler is able to optimize the allocation away. + // See cgo_go124.go for more information. + want = 0 + } + if n > want { + t.Errorf("allocs = %d, want %d", n, want) + } +}