Repository: cilium/ebpf
Pull request #1647 (2025-01-20 09:49:44)

linker: do not copy instructions when there is no reference in `flattenInstructions`
When resolving the bpf-to-bpf calls we need to copy the instructions from the program to flatten the references.

But when there is no reference there is nothing to do so we can skip the copy operation altogether.


Patch:
diff --git a/linker.go b/linker.go
index 6f97af2..024b72b 100644
--- a/linker.go
+++ b/linker.go
@@ -207,13 +207,19 @@ func flattenPrograms(progs map[string]*ProgramSpec, names []string) {
 // dependencies of each program.
 func flattenInstructions(name string, progs map[string]*ProgramSpec, refs map[*ProgramSpec][]string) asm.Instructions {
 	prog := progs[name]
+	progRefs := refs[prog]
+
+	if len(progRefs) == 0 {
+		// No references, nothing to do.
+		return prog.Instructions
+	}
 
 	insns := make(asm.Instructions, len(prog.Instructions))
 	copy(insns, prog.Instructions)
 
 	// Add all direct references of prog to the list of to be linked programs.
-	pending := make([]string, len(refs[prog]))
-	copy(pending, refs[prog])
+	pending := make([]string, len(progRefs))
+	copy(pending, progRefs)
 
 	// All references for which we've appended instructions.
 	linked := make(map[string]bool)
diff --git a/pin/load.go b/pin/load.go
index 817e177..b3d2fe7 100644
--- a/pin/load.go
+++ b/pin/load.go
@@ -2,6 +2,7 @@ package pin
 
 import (
 	"fmt"
+	"io"
 
 	"github.com/cilium/ebpf"
 	"github.com/cilium/ebpf/internal/sys"
@@ -11,6 +12,7 @@ import (
 // Pinner is an interface implemented by all eBPF objects that support pinning
 // to a bpf virtual filesystem.
 type Pinner interface {
+	io.Closer
 	Pin(string) error
 }
 


Test patch:
diff --git a/linker_test.go b/linker_test.go
index 39b01cf..cbe31d6 100644
--- a/linker_test.go
+++ b/linker_test.go
@@ -159,3 +159,24 @@ func TestSplitSymbols(t *testing.T) {
 	qt.Assert(t, qt.HasLen(m["sym3"], 3))
 	qt.Assert(t, qt.HasLen(m["sym4"], 4))
 }
+
+func TestFlattenInstructionsAllocations(t *testing.T) {
+	name := "entrypoint"
+	instructions := asm.Instructions{
+		asm.LoadImm(asm.R0, 0, asm.DWord),
+		asm.Return(),
+	}
+	prog := &ProgramSpec{
+		Name:         name,
+		Instructions: instructions,
+	}
+	progs := map[string]*ProgramSpec{name: prog}
+	refs := make(map[*ProgramSpec][]string)
+
+	// ensure that flattenInstructions does not allocate memory
+	// if there is no reference for the given program.
+	allocs := testing.AllocsPerRun(5, func() {
+		_ = flattenInstructions(name, progs, refs)
+	})
+	qt.Assert(t, qt.Equals(allocs, float64(0)))
+}
diff --git a/pin/load_test.go b/pin/load_test.go
index a16b794..742355c 100644
--- a/pin/load_test.go
+++ b/pin/load_test.go
@@ -9,6 +9,7 @@ import (
 	"github.com/cilium/ebpf"
 	"github.com/cilium/ebpf/asm"
 	"github.com/cilium/ebpf/internal/testutils"
+	"github.com/cilium/ebpf/internal/testutils/fdtrace"
 )
 
 func mustPinnedProgram(t *testing.T, path string) *ebpf.Program {
@@ -77,9 +78,15 @@ func TestLoad(t *testing.T) {
 
 	m, err := Load(mpath, nil)
 	qt.Assert(t, qt.IsNil(err))
+	defer m.Close()
 	qt.Assert(t, qt.Satisfies(m, testutils.Contains[*ebpf.Map]))
 
 	p, err := Load(ppath, nil)
 	qt.Assert(t, qt.IsNil(err))
+	defer p.Close()
 	qt.Assert(t, qt.Satisfies(p, testutils.Contains[*ebpf.Program]))
 }
+
+func TestMain(m *testing.M) {
+	fdtrace.TestMain(m)
+}
diff --git a/pin/walk_test.go b/pin/walk_test.go
index bde193e..13bf164 100644
--- a/pin/walk_test.go
+++ b/pin/walk_test.go
@@ -27,6 +27,10 @@ func TestWalkDir(t *testing.T) {
 	bpffn := func(path string, d fs.DirEntry, obj Pinner, err error) error {
 		qt.Assert(t, qt.IsNil(err))
 
+		if obj != nil {
+			defer obj.Close()
+		}
+
 		if path == "." {
 			return nil
 		}