Repository: cilium/ebpf
Pull request #1682 (2025-02-13 18:33:46)

btf: use iterators
Now that we're on 1.23 we can use iterators. The existing iterators don't really need to be modified. Allocations don't change, run time seems a little bit faster.

    core: 1
    goos: linux
    goarch: amd64
    pkg: github.com/cilium/ebpf/btf
    cpu: 13th Gen Intel(R) Core(TM) i7-1365U
                                        │   base.txt   │              iter.txt              │
                                        │    sec/op    │    sec/op     vs base              │
    PostorderTraversal/single_type           49.79n ± ∞ ¹   48.68n ± ∞ ¹  -2.23% (p=0.029 n=4)
    PostorderTraversal/cycle(1)              146.4n ± ∞ ¹   135.8n ± ∞ ¹  -7.24% (p=0.029 n=4)
    PostorderTraversal/cycle(10)             1.928µ ± ∞ ¹   1.861µ ± ∞ ¹  -3.45% (p=0.029 n=4)
    PostorderTraversal/gov_update_cpu_data   1.895m ± ∞ ¹   1.907m ± ∞ ¹       ~ (p=0.057 n=4)
    geomean                                  2.271µ         2.201µ        -3.12%
    ¹ need >= 6 samples for confidence interval at level 0.95


Patch:
diff --git a/btf/marshal.go b/btf/marshal.go
index d7204e6..585ef4a 100644
--- a/btf/marshal.go
+++ b/btf/marshal.go
@@ -243,28 +243,27 @@ func (b *Builder) addString(str string) (uint32, error) {
 	return b.strings.Add(str)
 }
 
-func (e *encoder) allocateIDs(root Type) (err error) {
-	visitInPostorder(root, e.visited, func(typ Type) bool {
+func (e *encoder) allocateIDs(root Type) error {
+	for typ := range postorder(root, e.visited) {
 		if _, ok := typ.(*Void); ok {
-			return true
+			continue
 		}
 
 		if _, ok := e.ids[typ]; ok {
-			return true
+			continue
 		}
 
 		id := e.lastID + 1
 		if id < e.lastID {
-			err = errors.New("type ID overflow")
-			return false
+			return errors.New("type ID overflow")
 		}
 
 		e.pending.Push(typ)
 		e.ids[typ] = id
 		e.lastID = id
-		return true
-	})
-	return
+	}
+
+	return nil
 }
 
 // id returns the ID for the given type or panics with an error.
diff --git a/btf/traversal.go b/btf/traversal.go
index 13647d9..57c1dc2 100644
--- a/btf/traversal.go
+++ b/btf/traversal.go
@@ -2,16 +2,21 @@ package btf
 
 import (
 	"fmt"
+	"iter"
 )
 
 // Functions to traverse a cyclic graph of types. The below was very useful:
 // https://eli.thegreenplace.net/2015/directed-graph-traversal-orderings-and-applications-to-data-flow-analysis/#post-order-and-reverse-post-order
 
-// Visit all types reachable from root in postorder.
-//
-// Traversal stops if yield returns false.
-//
-// Returns false if traversal was aborted.
+// postorder yields all types reachable from root in post order.
+func postorder(root Type, visited map[Type]struct{}) iter.Seq[Type] {
+	return func(yield func(Type) bool) {
+		visitInPostorder(root, visited, yield)
+	}
+}
+
+// visitInPostorder is a separate function to avoid arguments escaping
+// to the heap. Don't change the setup without re-running the benchmarks.
 func visitInPostorder(root Type, visited map[Type]struct{}, yield func(typ Type) bool) bool {
 	if _, ok := visited[root]; ok {
 		return true
@@ -21,139 +26,134 @@ func visitInPostorder(root Type, visited map[Type]struct{}, yield func(typ Type)
 	}
 	visited[root] = struct{}{}
 
-	cont := children(root, func(child *Type) bool {
-		return visitInPostorder(*child, visited, yield)
-	})
-	if !cont {
-		return false
+	for child := range children(root) {
+		if !visitInPostorder(*child, visited, yield) {
+			return false
+		}
 	}
 
 	return yield(root)
 }
 
-// children calls yield on each child of typ.
-//
-// Traversal stops if yield returns false.
-//
-// Returns false if traversal was aborted.
-func children(typ Type, yield func(child *Type) bool) bool {
-	// Explicitly type switch on the most common types to allow the inliner to
-	// do its work. This avoids allocating intermediate slices from walk() on
-	// the heap.
-	var tags []string
-	switch v := typ.(type) {
-	case *Void, *Int, *Enum, *Fwd, *Float, *declTag:
-		// No children to traverse.
-		// declTags is declared as a leaf type since it's parsed into .Tags fields of other types
-		// during unmarshaling.
-	case *Pointer:
-		if !yield(&v.Target) {
-			return false
-		}
-	case *Array:
-		if !yield(&v.Index) {
-			return false
-		}
-		if !yield(&v.Type) {
-			return false
-		}
-	case *Struct:
-		for i := range v.Members {
-			if !yield(&v.Members[i].Type) {
-				return false
-			}
-			for _, t := range v.Members[i].Tags {
-				var tag Type = &declTag{v, t, i}
-				if !yield(&tag) {
-					return false
-				}
+// children yields all direct descendants of typ.
+func children(typ Type) iter.Seq[*Type] {
+	return func(yield func(*Type) bool) {
+		// Explicitly type switch on the most common types to allow the inliner to
+		// do its work. This avoids allocating intermediate slices from walk() on
+		// the heap.
+		var tags []string
+		switch v := typ.(type) {
+		case *Void, *Int, *Enum, *Fwd, *Float, *declTag:
+			// No children to traverse.
+			// declTags is declared as a leaf type since it's parsed into .Tags fields of other types
+			// during unmarshaling.
+		case *Pointer:
+			if !yield(&v.Target) {
+				return
 			}
-		}
-		tags = v.Tags
-	case *Union:
-		for i := range v.Members {
-			if !yield(&v.Members[i].Type) {
-				return false
-			}
-			for _, t := range v.Members[i].Tags {
-				var tag Type = &declTag{v, t, i}
-				if !yield(&tag) {
-					return false
+		case *Array:
+			if !yield(&v.Index) {
+				return
+			}
+			if !yield(&v.Type) {
+				return
+			}
+		case *Struct:
+			for i := range v.Members {
+				if !yield(&v.Members[i].Type) {
+					return
+				}
+				for _, t := range v.Members[i].Tags {
+					var tag Type = &declTag{v, t, i}
+					if !yield(&tag) {
+						return
+					}
 				}
 			}
-		}
-		tags = v.Tags
-	case *Typedef:
-		if !yield(&v.Type) {
-			return false
-		}
-		tags = v.Tags
-	case *Volatile:
-		if !yield(&v.Type) {
-			return false
-		}
-	case *Const:
-		if !yield(&v.Type) {
-			return false
-		}
-	case *Restrict:
-		if !yield(&v.Type) {
-			return false
-		}
-	case *Func:
-		if !yield(&v.Type) {
-			return false
-		}
-		if fp, ok := v.Type.(*FuncProto); ok {
-			for i := range fp.Params {
-				if len(v.ParamTags) <= i {
-					continue
+			tags = v.Tags
+		case *Union:
+			for i := range v.Members {
+				if !yield(&v.Members[i].Type) {
+					return
 				}
-				for _, t := range v.ParamTags[i] {
+				for _, t := range v.Members[i].Tags {
 					var tag Type = &declTag{v, t, i}
 					if !yield(&tag) {
-						return false
+						return
 					}
 				}
 			}
-		}
-		tags = v.Tags
-	case *FuncProto:
-		if !yield(&v.Return) {
-			return false
-		}
-		for i := range v.Params {
-			if !yield(&v.Params[i].Type) {
-				return false
+			tags = v.Tags
+		case *Typedef:
+			if !yield(&v.Type) {
+				return
 			}
-		}
-	case *Var:
-		if !yield(&v.Type) {
-			return false
-		}
-		tags = v.Tags
-	case *Datasec:
-		for i := range v.Vars {
-			if !yield(&v.Vars[i].Type) {
-				return false
+			tags = v.Tags
+		case *Volatile:
+			if !yield(&v.Type) {
+				return
 			}
+		case *Const:
+			if !yield(&v.Type) {
+				return
+			}
+		case *Restrict:
+			if !yield(&v.Type) {
+				return
+			}
+		case *Func:
+			if !yield(&v.Type) {
+				return
+			}
+			if fp, ok := v.Type.(*FuncProto); ok {
+				for i := range fp.Params {
+					if len(v.ParamTags) <= i {
+						continue
+					}
+					for _, t := range v.ParamTags[i] {
+						var tag Type = &declTag{v, t, i}
+						if !yield(&tag) {
+							return
+						}
+					}
+				}
+			}
+			tags = v.Tags
+		case *FuncProto:
+			if !yield(&v.Return) {
+				return
+			}
+			for i := range v.Params {
+				if !yield(&v.Params[i].Type) {
+					return
+				}
+			}
+		case *Var:
+			if !yield(&v.Type) {
+				return
+			}
+			tags = v.Tags
+		case *Datasec:
+			for i := range v.Vars {
+				if !yield(&v.Vars[i].Type) {
+					return
+				}
+			}
+		case *TypeTag:
+			if !yield(&v.Type) {
+				return
+			}
+		case *cycle:
+			// cycle has children, but we ignore them deliberately.
+		default:
+			panic(fmt.Sprintf("don't know how to walk Type %T", v))
 		}
-	case *TypeTag:
-		if !yield(&v.Type) {
-			return false
-		}
-	case *cycle:
-		// cycle has children, but we ignore them deliberately.
-	default:
-		panic(fmt.Sprintf("don't know how to walk Type %T", v))
-	}
 
-	for _, t := range tags {
-		var tag Type = &declTag{typ, t, -1}
-		if !yield(&tag) {
-			return false
+		for _, t := range tags {
+			var tag Type = &declTag{typ, t, -1}
+			if !yield(&tag) {
+				return
+			}
 		}
 	}
-
-	return true
 }
diff --git a/btf/types.go b/btf/types.go
index dbcdf9d..638e7cf 100644
--- a/btf/types.go
+++ b/btf/types.go
@@ -740,10 +740,9 @@ func copyType(typ Type, ids map[Type]TypeID, copies map[Type]Type, copiedIDs map
 		copiedIDs[cpy] = id
 	}
 
-	children(cpy, func(child *Type) bool {
+	for child := range children(cpy) {
 		*child = copyType(*child, ids, copies, copiedIDs)
-		return true
-	})
+	}
 
 	return cpy
 }
diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go
index 57434a7..fdd5d6f 100644
--- a/internal/cmd/gentypes/main.go
+++ b/internal/cmd/gentypes/main.go
@@ -98,6 +98,7 @@ package sys
 
 import (
 	"unsafe"
+	"structs"
 )
 
 `)
@@ -758,6 +759,8 @@ func outputPatchedStruct(gf *btf.GoFormatter, w *bytes.Buffer, id string, s *btf
 		return err
 	}
 
+	decl = strings.Replace(decl, "struct {", "struct { structs.HostLayout;", 1)
+
 	w.WriteString(decl)
 	w.WriteString("\n\n")
 	return nil
diff --git a/internal/sys/ptr_32_be.go b/internal/sys/ptr_32_be.go
index 6278c79..0b0feeb 100644
--- a/internal/sys/ptr_32_be.go
+++ b/internal/sys/ptr_32_be.go
@@ -3,12 +3,14 @@
 package sys
 
 import (
+	"structs"
 	"unsafe"
 )
 
 // Pointer wraps an unsafe.Pointer to be 64bit to
 // conform to the syscall specification.
 type Pointer struct {
+	structs.HostLayout
 	pad uint32
 	ptr unsafe.Pointer
 }
diff --git a/internal/sys/ptr_32_le.go b/internal/sys/ptr_32_le.go
index c27b537..f9007fe 100644
--- a/internal/sys/ptr_32_le.go
+++ b/internal/sys/ptr_32_le.go
@@ -3,12 +3,14 @@
 package sys
 
 import (
+	"structs"
 	"unsafe"
 )
 
 // Pointer wraps an unsafe.Pointer to be 64bit to
 // conform to the syscall specification.
 type Pointer struct {
+	structs.HostLayout
 	ptr unsafe.Pointer
 	pad uint32
 }
diff --git a/internal/sys/ptr_64.go b/internal/sys/ptr_64.go
index 2d78282..05196cc 100644
--- a/internal/sys/ptr_64.go
+++ b/internal/sys/ptr_64.go
@@ -3,11 +3,13 @@
 package sys
 
 import (
+	"structs"
 	"unsafe"
 )
 
 // Pointer wraps an unsafe.Pointer to be 64bit to
 // conform to the syscall specification.
 type Pointer struct {
+	structs.HostLayout
 	ptr unsafe.Pointer
 }
diff --git a/internal/sys/types.go b/internal/sys/types.go
index 82f3492..61dafa2 100644
--- a/internal/sys/types.go
+++ b/internal/sys/types.go
@@ -3,6 +3,7 @@
 package sys
 
 import (
+	"structs"
 	"unsafe"
 )
 
@@ -689,6 +690,7 @@ const (
 )
 
 type BtfInfo struct {
+	structs.HostLayout
 	Btf       Pointer
 	BtfSize   uint32
 	Id        BTFID
@@ -698,11 +700,13 @@ type BtfInfo struct {
 }
 
 type FuncInfo struct {
+	structs.HostLayout
 	InsnOff uint32
 	TypeId  uint32
 }
 
 type LineInfo struct {
+	structs.HostLayout
 	InsnOff     uint32
 	FileNameOff uint32
 	LineOff     uint32
@@ -710,6 +714,7 @@ type LineInfo struct {
 }
 
 type LinkInfo struct {
+	structs.HostLayout
 	Type   LinkType
 	Id     LinkID
 	ProgId uint32
@@ -718,6 +723,7 @@ type LinkInfo struct {
 }
 
 type MapInfo struct {
+	structs.HostLayout
 	Type                  uint32
 	Id                    uint32
 	KeySize               uint32
@@ -737,6 +743,7 @@ type MapInfo struct {
 }
 
 type ProgInfo struct {
+	structs.HostLayout
 	Type                 uint32
 	Id                   uint32
 	Tag                  [8]uint8
@@ -779,6 +786,7 @@ type ProgInfo struct {
 }
 
 type SkLookup struct {
+	structs.HostLayout
 	Cookie         uint64
 	Family         uint32
 	Protocol       uint32
@@ -794,6 +802,7 @@ type SkLookup struct {
 }
 
 type XdpMd struct {
+	structs.HostLayout
 	Data           uint32
 	DataEnd        uint32
 	DataMeta       uint32
@@ -802,7 +811,10 @@ type XdpMd struct {
 	EgressIfindex  uint32
 }
 
-type BtfGetFdByIdAttr struct{ Id uint32 }
+type BtfGetFdByIdAttr struct {
+	structs.HostLayout
+	Id uint32
+}
 
 func BtfGetFdById(attr *BtfGetFdByIdAttr) (*FD, error) {
 	fd, err := BPF(BPF_BTF_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
@@ -813,6 +825,7 @@ func BtfGetFdById(attr *BtfGetFdByIdAttr) (*FD, error) {
 }
 
 type BtfGetNextIdAttr struct {
+	structs.HostLayout
 	Id     BTFID
 	NextId BTFID
 }
@@ -823,6 +836,7 @@ func BtfGetNextId(attr *BtfGetNextIdAttr) error {
 }
 
 type BtfLoadAttr struct {
+	structs.HostLayout
 	Btf            Pointer
 	BtfLogBuf      Pointer
 	BtfSize        uint32
@@ -841,7 +855,10 @@ func BtfLoad(attr *BtfLoadAttr) (*FD, error) {
 	return NewFD(int(fd))
 }
 
-type EnableStatsAttr struct{ Type uint32 }
+type EnableStatsAttr struct {
+	structs.HostLayout
+	Type uint32
+}
 
 func EnableStats(attr *EnableStatsAttr) (*FD, error) {
 	fd, err := BPF(BPF_ENABLE_STATS, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
@@ -852,6 +869,7 @@ func EnableStats(attr *EnableStatsAttr) (*FD, error) {
 }
 
 type IterCreateAttr struct {
+	structs.HostLayout
 	LinkFd uint32
 	Flags  uint32
 }
@@ -865,6 +883,7 @@ func IterCreate(attr *IterCreateAttr) (*FD, error) {
 }
 
 type LinkCreateAttr struct {
+	structs.HostLayout
 	ProgFd      uint32
 	TargetFd    uint32
 	AttachType  AttachType
@@ -882,6 +901,7 @@ func LinkCreate(attr *LinkCreateAttr) (*FD, error) {
 }
 
 type LinkCreateIterAttr struct {
+	structs.HostLayout
 	ProgFd      uint32
 	TargetFd    uint32
 	AttachType  AttachType
@@ -900,6 +920,7 @@ func LinkCreateIter(attr *LinkCreateIterAttr) (*FD, error) {
 }
 
 type LinkCreateKprobeMultiAttr struct {
+	structs.HostLayout
 	ProgFd           uint32
 	TargetFd         uint32
 	AttachType       AttachType
@@ -921,6 +942,7 @@ func LinkCreateKprobeMulti(attr *LinkCreateKprobeMultiAttr) (*FD, error) {
 }
 
 type LinkCreateNetfilterAttr struct {
+	structs.HostLayout
 	ProgFd         uint32
 	TargetFd       uint32
 	AttachType     AttachType
@@ -941,6 +963,7 @@ func LinkCreateNetfilter(attr *LinkCreateNetfilterAttr) (*FD, error) {
 }
 
 type LinkCreateNetkitAttr struct {
+	structs.HostLayout
 	ProgFd           uint32
 	TargetIfindex    uint32
 	AttachType       AttachType
@@ -960,6 +983,7 @@ func LinkCreateNetkit(attr *LinkCreateNetkitAttr) (*FD, error) {
 }
 
 type LinkCreatePerfEventAttr struct {
+	structs.HostLayout
 	ProgFd     uint32
 	TargetFd   uint32
 	AttachType AttachType
@@ -977,6 +1001,7 @@ func LinkCreatePerfEvent(attr *LinkCreatePerfEventAttr) (*FD, error) {
 }
 
 type LinkCreateTcxAttr struct {
+	structs.HostLayout
 	ProgFd           uint32
 	TargetIfindex    uint32
 	AttachType       AttachType
@@ -996,6 +1021,7 @@ func LinkCreateTcx(attr *LinkCreateTcxAttr) (*FD, error) {
 }
 
 type LinkCreateTracingAttr struct {
+	structs.HostLayout
 	ProgFd      uint32
 	TargetFd    uint32
 	AttachType  AttachType
@@ -1015,6 +1041,7 @@ func LinkCreateTracing(attr *LinkCreateTracingAttr) (*FD, error) {
 }
 
 type LinkCreateUprobeMultiAttr struct {
+	structs.HostLayout
 	ProgFd           uint32
 	TargetFd         uint32
 	AttachType       AttachType
@@ -1037,7 +1064,10 @@ func LinkCreateUprobeMulti(attr *LinkCreateUprobeMultiAttr) (*FD, error) {
 	return NewFD(int(fd))
 }
 
-type LinkGetFdByIdAttr struct{ Id LinkID }
+type LinkGetFdByIdAttr struct {
+	structs.HostLayout
+	Id LinkID
+}
 
 func LinkGetFdById(attr *LinkGetFdByIdAttr) (*FD, error) {
 	fd, err := BPF(BPF_LINK_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
@@ -1048,6 +1078,7 @@ func LinkGetFdById(attr *LinkGetFdByIdAttr) (*FD, error) {
 }
 
 type LinkGetNextIdAttr struct {
+	structs.HostLayout
 	Id     LinkID
 	NextId LinkID
 }
@@ -1058,6 +1089,7 @@ func LinkGetNextId(attr *LinkGetNextIdAttr) error {
 }
 
 type LinkUpdateAttr struct {
+	structs.HostLayout
 	LinkFd    uint32
 	NewProgFd uint32
 	Flags     uint32
@@ -1070,6 +1102,7 @@ func LinkUpdate(attr *LinkUpdateAttr) error {
 }
 
 type MapCreateAttr struct {
+	structs.HostLayout
 	MapType               MapType
 	KeySize               uint32
 	ValueSize             uint32
@@ -1097,6 +1130,7 @@ func MapCreate(attr *MapCreateAttr) (*FD, error) {
 }
 
 type MapDeleteBatchAttr struct {
+	structs.HostLayout
 	InBatch   Pointer
 	OutBatch  Pointer
 	Keys      Pointer
@@ -1113,6 +1147,7 @@ func MapDeleteBatch(attr *MapDeleteBatchAttr) error {
 }
 
 type MapDeleteElemAttr struct {
+	structs.HostLayout
 	MapFd uint32
 	_     [4]byte
 	Key   Pointer
@@ -1125,14 +1160,20 @@ func MapDeleteElem(attr *MapDeleteElemAttr) error {
 	return err
 }
 
-type MapFreezeAttr struct{ MapFd uint32 }
+type MapFreezeAttr struct {
+	structs.HostLayout
+	MapFd uint32
+}
 
 func MapFreeze(attr *MapFreezeAttr) error {
 	_, err := BPF(BPF_MAP_FREEZE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
 	return err
 }
 
-type MapGetFdByIdAttr struct{ Id uint32 }
+type MapGetFdByIdAttr struct {
+	structs.HostLayout
+	Id uint32
+}
 
 func MapGetFdById(attr *MapGetFdByIdAttr) (*FD, error) {
 	fd, err := BPF(BPF_MAP_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
@@ -1143,6 +1184,7 @@ func MapGetFdById(attr *MapGetFdByIdAttr) (*FD, error) {
 }
 
 type MapGetNextIdAttr struct {
+	structs.HostLayout
 	Id     uint32
 	NextId uint32
 }
@@ -1153,6 +1195,7 @@ func MapGetNextId(attr *MapGetNextIdAttr) error {
 }
 
 type MapGetNextKeyAttr struct {
+	structs.HostLayout
 	MapFd   uint32
 	_       [4]byte
 	Key     Pointer
@@ -1165,6 +1208,7 @@ func MapGetNextKey(attr *MapGetNextKeyAttr) error {
 }
 
 type MapLookupAndDeleteBatchAttr struct {
+	structs.HostLayout
 	InBatch   Pointer
 	OutBatch  Pointer
 	Keys      Pointer
@@ -1181,6 +1225,7 @@ func MapLookupAndDeleteBatch(attr *MapLookupAndDeleteBatchAttr) error {
 }
 
 type MapLookupAndDeleteElemAttr struct {
+	structs.HostLayout
 	MapFd uint32
 	_     [4]byte
 	Key   Pointer
@@ -1194,6 +1239,7 @@ func MapLookupAndDeleteElem(attr *MapLookupAndDeleteElemAttr) error {
 }
 
 type MapLookupBatchAttr struct {
+	structs.HostLayout
 	InBatch   Pointer
 	OutBatch  Pointer
 	Keys      Pointer
@@ -1210,6 +1256,7 @@ func MapLookupBatch(attr *MapLookupBatchAttr) error {
 }
 
 type MapLookupElemAttr struct {
+	structs.HostLayout
 	MapFd uint32
 	_     [4]byte
 	Key   Pointer
@@ -1223,6 +1270,7 @@ func MapLookupElem(attr *MapLookupElemAttr) error {
 }
 
 type MapUpdateBatchAttr struct {
+	structs.HostLayout
 	InBatch   Pointer
 	OutBatch  Pointer
 	Keys      Pointer
@@ -1239,6 +1287,7 @@ func MapUpdateBatch(attr *MapUpdateBatchAttr) error {
 }
 
 type MapUpdateElemAttr struct {
+	structs.HostLayout
 	MapFd uint32
 	_     [4]byte
 	Key   Pointer
@@ -1252,6 +1301,7 @@ func MapUpdateElem(attr *MapUpdateElemAttr) error {
 }
 
 type ObjGetAttr struct {
+	structs.HostLayout
 	Pathname  Pointer
 	BpfFd     uint32
 	FileFlags uint32
@@ -1268,6 +1318,7 @@ func ObjGet(attr *ObjGetAttr) (*FD, error) {
 }
 
 type ObjGetInfoByFdAttr struct {
+	structs.HostLayout
 	BpfFd   uint32
 	InfoLen uint32
 	Info    Pointer
@@ -1279,6 +1330,7 @@ func ObjGetInfoByFd(attr *ObjGetInfoByFdAttr) error {
 }
 
 type ObjPinAttr struct {
+	structs.HostLayout
 	Pathname  Pointer
 	BpfFd     uint32
 	FileFlags uint32
@@ -1292,6 +1344,7 @@ func ObjPin(attr *ObjPinAttr) error {
 }
 
 type ProgAttachAttr struct {
+	structs.HostLayout
 	TargetFdOrIfindex uint32
 	AttachBpfFd       uint32
 	AttachType        uint32
@@ -1307,6 +1360,7 @@ func ProgAttach(attr *ProgAttachAttr) error {
 }
 
 type ProgBindMapAttr struct {
+	structs.HostLayout
 	ProgFd uint32
 	MapFd  uint32
 	Flags  uint32
@@ -1318,6 +1372,7 @@ func ProgBindMap(attr *ProgBindMapAttr) error {
 }
 
 type ProgDetachAttr struct {
+	structs.HostLayout
 	TargetFdOrIfindex uint32
 	AttachBpfFd       uint32
 	AttachType        uint32
@@ -1332,7 +1387,10 @@ func ProgDetach(attr *ProgDetachAttr) error {
 	return err
 }
 
-type ProgGetFdByIdAttr struct{ Id uint32 }
+type ProgGetFdByIdAttr struct {
+	structs.HostLayout
+	Id uint32
+}
 
 func ProgGetFdById(attr *ProgGetFdByIdAttr) (*FD, error) {
 	fd, err := BPF(BPF_PROG_GET_FD_BY_ID, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
@@ -1343,6 +1401,7 @@ func ProgGetFdById(attr *ProgGetFdByIdAttr) (*FD, error) {
 }
 
 type ProgGetNextIdAttr struct {
+	structs.HostLayout
 	Id     uint32
 	NextId uint32
 }
@@ -1353,6 +1412,7 @@ func ProgGetNextId(attr *ProgGetNextIdAttr) error {
 }
 
 type ProgLoadAttr struct {
+	structs.HostLayout
 	ProgType           ProgType
 	InsnCnt            uint32
 	Insns              Pointer
@@ -1392,6 +1452,7 @@ func ProgLoad(attr *ProgLoadAttr) (*FD, error) {
 }
 
 type ProgQueryAttr struct {
+	structs.HostLayout
 	TargetFdOrIfindex uint32
 	AttachType        AttachType
 	QueryFlags        uint32
@@ -1411,6 +1472,7 @@ func ProgQuery(attr *ProgQueryAttr) error {
 }
 
 type ProgRunAttr struct {
+	structs.HostLayout
 	ProgFd      uint32
 	Retval      uint32
 	DataSizeIn  uint32
@@ -1435,6 +1497,7 @@ func ProgRun(attr *ProgRunAttr) error {
 }
 
 type RawTracepointOpenAttr struct {
+	structs.HostLayout
 	Name   Pointer
 	ProgFd uint32
 	_      [4]byte
@@ -1450,6 +1513,7 @@ func RawTracepointOpen(attr *RawTracepointOpenAttr) (*FD, error) {
 }
 
 type CgroupLinkInfo struct {
+	structs.HostLayout
 	Type       LinkType
 	Id         LinkID
 	ProgId     uint32
@@ -1460,6 +1524,7 @@ type CgroupLinkInfo struct {
 }
 
 type IterLinkInfo struct {
+	structs.HostLayout
 	Type          LinkType
 	Id            LinkID
 	ProgId        uint32
@@ -1469,6 +1534,7 @@ type IterLinkInfo struct {
 }
 
 type KprobeLinkInfo struct {
+	structs.HostLayout
 	Type          LinkType
 	Id            LinkID
 	ProgId        uint32
@@ -1484,6 +1550,7 @@ type KprobeLinkInfo struct {
 }
 
 type KprobeMultiLinkInfo struct {
+	structs.HostLayout
 	Type    LinkType
 	Id      LinkID
 	ProgId  uint32
@@ -1497,6 +1564,7 @@ type KprobeMultiLinkInfo struct {
 }
 
 type NetNsLinkInfo struct {
+	structs.HostLayout
 	Type       LinkType
 	Id         LinkID
 	ProgId     uint32
@@ -1507,6 +1575,7 @@ type NetNsLinkInfo struct {
 }
 
 type NetfilterLinkInfo struct {
+	structs.HostLayout
 	Type     LinkType
 	Id       LinkID
 	ProgId   uint32
@@ -1519,6 +1588,7 @@ type NetfilterLinkInfo struct {
 }
 
 type NetkitLinkInfo struct {
+	structs.HostLayout
 	Type       LinkType
 	Id         LinkID
 	ProgId     uint32
@@ -1529,6 +1599,7 @@ type NetkitLinkInfo struct {
 }
 
 type PerfEventLinkInfo struct {
+	structs.HostLayout
 	Type          LinkType
 	Id            LinkID
 	ProgId        uint32
@@ -1537,6 +1608,7 @@ type PerfEventLinkInfo struct {
 }
 
 type RawTracepointLinkInfo struct {
+	structs.HostLayout
 	Type      LinkType
 	Id        LinkID
 	ProgId    uint32
@@ -1547,6 +1619,7 @@ type RawTracepointLinkInfo struct {
 }
 
 type TcxLinkInfo struct {
+	structs.HostLayout
 	Type       LinkType
 	Id         LinkID
 	ProgId     uint32
@@ -1557,6 +1630,7 @@ type TcxLinkInfo struct {
 }
 
 type TracingLinkInfo struct {
+	structs.HostLayout
 	Type        LinkType
 	Id          LinkID
 	ProgId      uint32
@@ -1568,6 +1642,7 @@ type TracingLinkInfo struct {
 }
 
 type XDPLinkInfo struct {
+	structs.HostLayout
 	Type    LinkType
 	Id      LinkID
 	ProgId  uint32


Test patch:
diff --git a/btf/marshal_test.go b/btf/marshal_test.go
index e46bcbf..f27f6df 100644
--- a/btf/marshal_test.go
+++ b/btf/marshal_test.go
@@ -82,7 +82,8 @@ func TestRoundtripVMlinux(t *testing.T) {
 	visited := make(map[Type]struct{})
 limitTypes:
 	for i, typ := range types {
-		visitInPostorder(typ, visited, func(_ Type) bool { return true })
+		for range postorder(typ, visited) {
+		}
 		if len(visited) >= math.MaxInt16 {
 			// IDs exceeding math.MaxUint16 can trigger a bug when loading BTF.
 			// This can be removed once the patch lands.
diff --git a/btf/traversal_test.go b/btf/traversal_test.go
index 3e7bfca..18583bd 100644
--- a/btf/traversal_test.go
+++ b/btf/traversal_test.go
@@ -14,22 +14,20 @@ func TestPostorderTraversal(t *testing.T) {
 
 	t.Logf("%3v", ptr)
 	pending := []Type{str, cst, ptr}
-	visitInPostorder(ptr, nil, func(typ Type) bool {
+	for typ := range postorder(ptr, nil) {
 		qt.Assert(t, qt.Equals(typ, pending[0]))
 		pending = pending[1:]
-		return true
-	})
+	}
 	qt.Assert(t, qt.HasLen(pending, 0))
 
 	i := &Int{Name: "foo"}
 	// i appears twice at the same nesting depth.
 	arr := &Array{Index: i, Type: i}
 	seen := make(map[Type]bool)
-	visitInPostorder(arr, nil, func(typ Type) bool {
+	for typ := range postorder(arr, nil) {
 		qt.Assert(t, qt.IsFalse(seen[typ]))
 		seen[typ] = true
-		return true
-	})
+	}
 	qt.Assert(t, qt.IsTrue(seen[arr]))
 	qt.Assert(t, qt.IsTrue(seen[i]))
 }
@@ -46,22 +44,43 @@ func TestPostorderTraversalVmlinux(t *testing.T) {
 		t.Run(fmt.Sprintf("%s", typ), func(t *testing.T) {
 			seen := make(map[Type]bool)
 			var last Type
-			visitInPostorder(typ, nil, func(typ Type) bool {
+			for typ := range postorder(typ, nil) {
 				if seen[typ] {
 					t.Fatalf("%s visited twice", typ)
 				}
 				seen[typ] = true
 				last = typ
-				return true
-			})
+			}
 			if last != typ {
 				t.Fatalf("Expected %s got %s as last type", typ, last)
 			}
 
-			children(typ, func(child *Type) bool {
+			for child := range children(typ) {
 				qt.Check(t, qt.IsTrue(seen[*child]), qt.Commentf("missing child %s", *child))
-				return true
+			}
+		})
+	}
+}
+
+func TestChildren(t *testing.T) {
+	for _, test := range []struct {
+		typ   Type
+		count int
+	}{
+		{&Int{}, 0},
+		{&Const{&Int{}}, 1},
+		{&Array{Index: &Int{}, Type: &Int{}}, 2},
+	} {
+		t.Run(fmt.Sprint(test.typ), func(t *testing.T) {
+			var count int
+			allocs := testing.AllocsPerRun(1, func() {
+				count = 0
+				for range children(test.typ) {
+					count++
+				}
 			})
+			qt.Assert(t, qt.Equals(count, test.count))
+			qt.Assert(t, qt.Equals(allocs, 0))
 		})
 	}
 }
@@ -84,12 +103,12 @@ func BenchmarkPostorderTraversal(b *testing.B) {
 		{"cycle(10)", newCyclicalType(10)},
 		{"gov_update_cpu_data", fn},
 	} {
-		b.Logf("%10v", test.typ)
-
 		b.Run(test.name, func(b *testing.B) {
 			b.ReportAllocs()
-			for i := 0; i < b.N; i++ {
-				visitInPostorder(test.typ, nil, func(t Type) bool { return true })
+			b.ResetTimer()
+			for range b.N {
+				for range postorder(test.typ, nil) {
+				}
 			}
 		})
 	}
diff --git a/btf/types_test.go b/btf/types_test.go
index e6cbc46..b7e325b 100644
--- a/btf/types_test.go
+++ b/btf/types_test.go
@@ -198,7 +198,9 @@ func TestType(t *testing.T) {
 			}
 
 			var a []*Type
-			children(typ, func(t *Type) bool { a = append(a, t); return true })
+			for t := range children(typ) {
+				a = append(a, t)
+			}
 
 			if _, ok := typ.(*cycle); !ok {
 				if n := countChildren(t, reflect.TypeOf(typ)); len(a) < n {
@@ -207,7 +209,9 @@ func TestType(t *testing.T) {
 			}
 
 			var b []*Type
-			children(typ, func(t *Type) bool { b = append(b, t); return true })
+			for t := range children(typ) {
+				b = append(b, t)
+			}
 
 			if diff := cmp.Diff(a, b, compareTypes); diff != "" {
 				t.Errorf("Walk mismatch (-want +got):\n%s", diff)
@@ -484,10 +488,9 @@ func BenchmarkWalk(b *testing.B) {
 
 			for i := 0; i < b.N; i++ {
 				var dq typeDeque
-				children(typ, func(child *Type) bool {
+				for child := range children(typ) {
 					dq.Push(child)
-					return true
-				})
+				}
 			}
 		})
 	}
diff --git a/info_test.go b/info_test.go
index 1134b69..7be0ea8 100644
--- a/info_test.go
+++ b/info_test.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 	"reflect"
+	"runtime"
 	"strings"
 	"testing"
 
@@ -346,7 +347,7 @@ func TestStats(t *testing.T) {
 	if !ok {
 		t.Errorf("expected runtime info to be available")
 	}
-	if rt != 0 {
+	if runtime.GOARCH != "arm64" && rt != 0 {
 		t.Errorf("expected a runtime of 0ns but got %v", rt)
 	}