Skip to content

Commit ebf148f

Browse files
committed
btf: use recursion in coreAreTypesCompatible
coreAreTypesCompatible currently uses two manually maintained stacks to traverse two types to compare. The original idea was that using stacks would somehow be more efficient or it'd be easier to understand than the recursive approach. Time has shown that this was probably not true. Using the stacks is both harder to understand and probably less performant than using recursion. This is because the runtime maintains a stack for us, which can be reused across many invocations. Allocating on the (goroutine) stack is also incredibly cheap. Rewrite coreAreTypesCompatible to use recursion to traverse the two types. Instead of using walkType we simply open code the switch cases for Pointer, Array and FuncProto. This isn't much code and gets rid of a bunch of complexity and allocations, especially for FuncParam. The depth check is removed completely. It's a hold over from libbpf, which probably uses it to avoid stack overflow. This isn't necessary in Go, since overflowing the stack is guaranteed to panic. Instead, we deal with cyclical types by maintaining a visited map. Signed-off-by: Lorenz Bauer <[email protected]>
1 parent 65fb774 commit ebf148f

File tree

2 files changed

+52
-81
lines changed

2 files changed

+52
-81
lines changed

btf/core.go

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ func coreCalculateFixup(relo *CORERelocation, target Type, targetID TypeID, bo b
374374
return zero, fmt.Errorf("unexpected accessor %v", relo.accessor)
375375
}
376376

377-
err := coreAreTypesCompatible(local, target)
377+
err := CheckTypeCompatibility(local, target)
378378
if errors.Is(err, errIncompatibleTypes) {
379379
return poison()
380380
}
@@ -901,7 +901,11 @@ func coreFindEnumValue(local Type, localAcc coreAccessor, target Type) (localVal
901901
//
902902
// Only layout compatibility is checked, ignoring names of the root type.
903903
func CheckTypeCompatibility(localType Type, targetType Type) error {
904-
return coreAreTypesCompatible(localType, targetType)
904+
return coreAreTypesCompatible(localType, targetType, nil)
905+
}
906+
907+
type pair struct {
908+
A, B Type
905909
}
906910

907911
/* The comment below is from bpf_core_types_are_compat in libbpf.c:
@@ -927,59 +931,60 @@ func CheckTypeCompatibility(localType Type, targetType Type) error {
927931
*
928932
* Returns errIncompatibleTypes if types are not compatible.
929933
*/
930-
func coreAreTypesCompatible(localType Type, targetType Type) error {
934+
func coreAreTypesCompatible(localType Type, targetType Type, visited map[pair]struct{}) error {
935+
localType = UnderlyingType(localType)
936+
targetType = UnderlyingType(targetType)
931937

932-
var (
933-
localTs, targetTs typeDeque
934-
l, t = &localType, &targetType
935-
depth = 0
936-
)
938+
if reflect.TypeOf(localType) != reflect.TypeOf(targetType) {
939+
return fmt.Errorf("type mismatch between %v and %v: %w", localType, targetType, errIncompatibleTypes)
940+
}
937941

938-
for ; l != nil && t != nil; l, t = localTs.Shift(), targetTs.Shift() {
939-
if depth >= maxResolveDepth {
940-
return errors.New("types are nested too deep")
941-
}
942+
if _, ok := visited[pair{localType, targetType}]; ok {
943+
return nil
944+
}
945+
if visited == nil {
946+
visited = make(map[pair]struct{})
947+
}
948+
visited[pair{localType, targetType}] = struct{}{}
942949

943-
localType = UnderlyingType(*l)
944-
targetType = UnderlyingType(*t)
950+
switch lv := localType.(type) {
951+
case *Void, *Struct, *Union, *Enum, *Fwd, *Int:
952+
return nil
945953

946-
if reflect.TypeOf(localType) != reflect.TypeOf(targetType) {
947-
return fmt.Errorf("type mismatch between %v and %v: %w", localType, targetType, errIncompatibleTypes)
948-
}
954+
case *Pointer:
955+
tv := targetType.(*Pointer)
956+
return coreAreTypesCompatible(lv.Target, tv.Target, visited)
949957

950-
switch lv := (localType).(type) {
951-
case *Void, *Struct, *Union, *Enum, *Fwd, *Int:
952-
// Nothing to do here
958+
case *Array:
959+
tv := targetType.(*Array)
960+
if err := coreAreTypesCompatible(lv.Index, tv.Index, visited); err != nil {
961+
return err
962+
}
953963

954-
case *Pointer, *Array:
955-
depth++
956-
walkType(localType, localTs.Push)
957-
walkType(targetType, targetTs.Push)
964+
return coreAreTypesCompatible(lv.Type, tv.Type, visited)
958965

959-
case *FuncProto:
960-
tv := targetType.(*FuncProto)
961-
if len(lv.Params) != len(tv.Params) {
962-
return fmt.Errorf("function param mismatch: %w", errIncompatibleTypes)
963-
}
966+
case *FuncProto:
967+
tv := targetType.(*FuncProto)
968+
if err := coreAreTypesCompatible(lv.Return, tv.Return, visited); err != nil {
969+
return err
970+
}
964971

965-
depth++
966-
walkType(localType, localTs.Push)
967-
walkType(targetType, targetTs.Push)
972+
if len(lv.Params) != len(tv.Params) {
973+
return fmt.Errorf("function param mismatch: %w", errIncompatibleTypes)
974+
}
968975

969-
default:
970-
return fmt.Errorf("unsupported type %T", localType)
976+
for i, localParam := range lv.Params {
977+
targetParam := tv.Params[i]
978+
if err := coreAreTypesCompatible(localParam.Type, targetParam.Type, visited); err != nil {
979+
return err
980+
}
971981
}
972-
}
973982

974-
if l != nil {
975-
return fmt.Errorf("dangling local type %T", *l)
976-
}
983+
return nil
977984

978-
if t != nil {
979-
return fmt.Errorf("dangling target type %T", *t)
985+
default:
986+
return fmt.Errorf("unsupported type %T", localType)
980987
}
981-
982-
return nil
983988
}
984989

985990
/* coreAreMembersCompatible checks two types for field-based relocation compatibility.

btf/core_test.go

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,42 +17,6 @@ import (
1717
)
1818

1919
func TestCheckTypeCompatibility(t *testing.T) {
20-
tests := []struct {
21-
a, b Type
22-
compatible bool
23-
}{
24-
{&FuncProto{Return: &Typedef{Type: &Int{}}}, &FuncProto{Return: &Int{}}, true},
25-
{&FuncProto{Return: &Typedef{Type: &Int{}}}, &FuncProto{Return: &Void{}}, false},
26-
}
27-
for _, test := range tests {
28-
err := CheckTypeCompatibility(test.a, test.b)
29-
if test.compatible {
30-
if err != nil {
31-
t.Errorf("Expected types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
32-
continue
33-
}
34-
} else {
35-
if !errors.Is(err, errIncompatibleTypes) {
36-
t.Errorf("Expected types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
37-
continue
38-
}
39-
}
40-
41-
err = CheckTypeCompatibility(test.b, test.a)
42-
if test.compatible {
43-
if err != nil {
44-
t.Errorf("Expected reversed types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
45-
}
46-
} else {
47-
if !errors.Is(err, errIncompatibleTypes) {
48-
t.Errorf("Expected reversed types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
49-
}
50-
}
51-
}
52-
53-
}
54-
55-
func TestCOREAreTypesCompatible(t *testing.T) {
5620
tests := []struct {
5721
a, b Type
5822
compatible bool
@@ -84,10 +48,12 @@ func TestCOREAreTypesCompatible(t *testing.T) {
8448
&FuncProto{Return: &Void{}, Params: []FuncParam{{Type: &Void{}}}},
8549
false,
8650
},
51+
{&FuncProto{Return: &Typedef{Type: &Int{}}}, &FuncProto{Return: &Int{}}, true},
52+
{&FuncProto{Return: &Typedef{Type: &Int{}}}, &FuncProto{Return: &Void{}}, false},
8753
}
8854

8955
for _, test := range tests {
90-
err := coreAreTypesCompatible(test.a, test.b)
56+
err := CheckTypeCompatibility(test.a, test.b)
9157
if test.compatible {
9258
if err != nil {
9359
t.Errorf("Expected types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
@@ -100,7 +66,7 @@ func TestCOREAreTypesCompatible(t *testing.T) {
10066
}
10167
}
10268

103-
err = coreAreTypesCompatible(test.b, test.a)
69+
err = CheckTypeCompatibility(test.b, test.a)
10470
if test.compatible {
10571
if err != nil {
10672
t.Errorf("Expected reversed types to be compatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
@@ -113,7 +79,7 @@ func TestCOREAreTypesCompatible(t *testing.T) {
11379
}
11480

11581
for _, invalid := range []Type{&Var{}, &Datasec{}} {
116-
err := coreAreTypesCompatible(invalid, invalid)
82+
err := CheckTypeCompatibility(invalid, invalid)
11783
if errors.Is(err, errIncompatibleTypes) {
11884
t.Errorf("Expected an error for %T, not errIncompatibleTypes", invalid)
11985
} else if err == nil {

0 commit comments

Comments
 (0)