Repository: cilium/ebpf

Issue:
Support for CORE type matches relocation
### Describe the bug

Type matches relocation are currently not supported.

`bpf_core_type_matches()` (using `BPF_TYPE_MATCHES`) is defined in libbpf:
https://github.com/libbpf/libbpf/blob/v1.3.0/src/bpf_core_read.h#L194

Type match relocations was added in llvm/clang 15:
https://github.com/llvm/llvm-project/commit/d129ac27e872e189363e3d8cd6bc4e2152cd73ec

Example of usage:
https://github.com/torvalds/linux/blob/v6.6/tools/perf/util/bpf_skel/lock_contention.bpf.c#L215

Relocations of type BPF_CORE_TYPE_MATCHES are explained in:
https://docs.kernel.org/bpf/llvm_reloc.html

Added in Linux 6.0:
- https://github.com/torvalds/linux/commit/3c660a5d86f4c01cf641bfea004a49f5860a5bed
- https://github.com/torvalds/linux/commit/633e7ceb2cbbae9b2f5ca69106b0de65728c5988
- https://github.com/torvalds/linux/commit/ec6209c8d42f815bc3bef10934637ca92114cd1b

cilium/ebpf supports the following relocations:
```c
enum bpf_core_relo_kind {
...
       BPF_CORE_ENUMVAL_VALUE     = 11, /* enum value integer value */
       BPF_CORE_TYPE_MATCHES      = 12, /* type match in target kernel */
};
```
It stops at `BPF_CORE_ENUMVAL_VALUE` / `reloEnumvalValue`:
https://github.com/cilium/ebpf/blob/v0.13.0/btf/core.go#L121

cc @eiffel-fl @mauriciovasquezbernal

### How to reproduce

Code inspired from:
* https://github.com/inspektor-gadget/inspektor-gadget/pull/2531#discussion_r1499080402
* https://github.com/inspektor-gadget/inspektor-gadget/pull/2542:

```c
// https://github.com/torvalds/linux/commit/a54895fa057c67700270777f7661d8d3c7fda88a
// -       TP_PROTO(struct request_queue *q, struct request *rq),
// +       TP_PROTO(struct request *rq),
typedef void (*btf_trace_block_rq_insert___new)(void *, struct request *);
typedef void (*btf_trace_block_rq_insert___old)(void *, struct request_queue *q, struct request *);
typedef void (*btf_trace_block_rq_issue___new)(void *, struct request *);
typedef void (*btf_trace_block_rq_issue___old)(void *, struct request_queue *q, struct request *);

static void (*bpf_unreachable)(void) = (void *) 0xbad2310;

SEC("raw_tp/block_rq_insert")
int ig_profio_ins(u64 *ctx)
{
        if (bpf_core_type_matches(btf_trace_block_rq_insert___new)) {
                // After commit a54895fa (v5.11-rc1)
                return trace_rq_start((void *)ctx[0], true);
        } else if (bpf_core_type_matches(btf_trace_block_rq_insert___old)) {
                // Before commit a54895fa (v5.11-rc1)
                return trace_rq_start((void *)ctx[1], false);
        } else {
                // Couldn't detect block/block_rq_insert tracepoint
                bpf_unreachable();
                return 0;
        }
}
```

cilium/ebpf gives the following error:

> installing tracer: loading ebpf program: field IgProfioIns: program ig_profio_ins: apply CO-RE relocations: relocate Typedef:"btf_trace_block_rq_insert___new": target Typedef:"btf_trace_block_rq_insert": unknown: not supported


### Version information

github.com/cilium/ebpf v0.13.2

Hints from issue comments:

Solution patch:
diff --git a/btf/core.go b/btf/core.go
index 4a19791..ee89f98 100644
--- a/btf/core.go
+++ b/btf/core.go
@@ -138,10 +138,11 @@ const (
 	reloTypeSize                        /* type size in bytes */
 	reloEnumvalExists                   /* enum value existence in target kernel */
 	reloEnumvalValue                    /* enum value integer value */
+	reloTypeMatches                     /* type matches kernel type */
 )
 
 func (k coreKind) checksForExistence() bool {
-	return k == reloEnumvalExists || k == reloTypeExists || k == reloFieldExists
+	return k == reloEnumvalExists || k == reloTypeExists || k == reloFieldExists || k == reloTypeMatches
 }
 
 func (k coreKind) String() string {
@@ -170,8 +171,10 @@ func (k coreKind) String() string {
 		return "enumval_exists"
 	case reloEnumvalValue:
 		return "enumval_value"
+	case reloTypeMatches:
+		return "type_matches"
 	default:
-		return "unknown"
+		return fmt.Sprintf("unknown (%d)", k)
 	}
 }
 
@@ -368,6 +371,21 @@ func coreCalculateFixup(relo *CORERelocation, target Type, bo binary.ByteOrder,
 	local := relo.typ
 
 	switch relo.kind {
+	case reloTypeMatches:
+		if len(relo.accessor) > 1 || relo.accessor[0] != 0 {
+			return zero, fmt.Errorf("unexpected accessor %v", relo.accessor)
+		}
+
+		err := coreTypesMatch(local, target, nil)
+		if errors.Is(err, errIncompatibleTypes) {
+			return poison()
+		}
+		if err != nil {
+			return zero, err
+		}
+
+		return fixup(1, 1)
+
 	case reloTypeIDTarget, reloTypeSize, reloTypeExists:
 		if len(relo.accessor) > 1 || relo.accessor[0] != 0 {
 			return zero, fmt.Errorf("unexpected accessor %v", relo.accessor)
@@ -1024,19 +1042,6 @@ func coreAreMembersCompatible(localType Type, targetType Type) error {
 	localType = UnderlyingType(localType)
 	targetType = UnderlyingType(targetType)
 
-	doNamesMatch := func(a, b string) error {
-		if a == "" || b == "" {
-			// allow anonymous and named type to match
-			return nil
-		}
-
-		if newEssentialName(a) == newEssentialName(b) {
-			return nil
-		}
-
-		return fmt.Errorf("names don't match: %w", errImpossibleRelocation)
-	}
-
 	_, lok := localType.(composite)
 	_, tok := targetType.(composite)
 	if lok && tok {
@@ -1053,13 +1058,204 @@ func coreAreMembersCompatible(localType Type, targetType Type) error {
 
 	case *Enum:
 		tv := targetType.(*Enum)
-		return doNamesMatch(lv.Name, tv.Name)
+		if !coreEssentialNamesMatch(lv.Name, tv.Name) {
+			return fmt.Errorf("names %q and %q don't match: %w", lv.Name, tv.Name, errImpossibleRelocation)
+		}
+
+		return nil
 
 	case *Fwd:
 		tv := targetType.(*Fwd)
-		return doNamesMatch(lv.Name, tv.Name)
+		if !coreEssentialNamesMatch(lv.Name, tv.Name) {
+			return fmt.Errorf("names %q and %q don't match: %w", lv.Name, tv.Name, errImpossibleRelocation)
+		}
+
+		return nil
 
 	default:
 		return fmt.Errorf("type %s: %w", localType, ErrNotSupported)
 	}
 }
+
+// coreEssentialNamesMatch compares two names while ignoring their flavour suffix.
+//
+// This should only be used on names which are in the global scope, like struct
+// names, typedefs or enum values.
+func coreEssentialNamesMatch(a, b string) bool {
+	if a == "" || b == "" {
+		// allow anonymous and named type to match
+		return true
+	}
+
+	return newEssentialName(a) == newEssentialName(b)
+}
+
+/* The comment below is from __bpf_core_types_match in relo_core.c:
+ *
+ * Check that two types "match". This function assumes that root types were
+ * already checked for name match.
+ *
+ * The matching relation is defined as follows:
+ * - modifiers and typedefs are stripped (and, hence, effectively ignored)
+ * - generally speaking types need to be of same kind (struct vs. struct, union
+ *   vs. union, etc.)
+ *   - exceptions are struct/union behind a pointer which could also match a
+ *     forward declaration of a struct or union, respectively, and enum vs.
+ *     enum64 (see below)
+ * Then, depending on type:
+ * - integers:
+ *   - match if size and signedness match
+ * - arrays & pointers:
+ *   - target types are recursively matched
+ * - structs & unions:
+ *   - local members need to exist in target with the same name
+ *   - for each member we recursively check match unless it is already behind a
+ *     pointer, in which case we only check matching names and compatible kind
+ * - enums:
+ *   - local variants have to have a match in target by symbolic name (but not
+ *     numeric value)
+ *   - size has to match (but enum may match enum64 and vice versa)
+ * - function pointers:
+ *   - number and position of arguments in local type has to match target
+ *   - for each argument and the return value we recursively check match
+ */
+func coreTypesMatch(localType Type, targetType Type, visited map[pair]struct{}) error {
+	localType = UnderlyingType(localType)
+	targetType = UnderlyingType(targetType)
+
+	if !coreEssentialNamesMatch(localType.TypeName(), targetType.TypeName()) {
+		return fmt.Errorf("type name %q don't match %q: %w", localType.TypeName(), targetType.TypeName(), errIncompatibleTypes)
+	}
+
+	if reflect.TypeOf(localType) != reflect.TypeOf(targetType) {
+		return fmt.Errorf("type mismatch between %v and %v: %w", localType, targetType, errIncompatibleTypes)
+	}
+
+	if _, ok := visited[pair{localType, targetType}]; ok {
+		return nil
+	}
+	if visited == nil {
+		visited = make(map[pair]struct{})
+	}
+	visited[pair{localType, targetType}] = struct{}{}
+
+	switch lv := (localType).(type) {
+	case *Void:
+
+	case *Fwd:
+		if targetType.(*Fwd).Kind != lv.Kind {
+			return fmt.Errorf("fwd kind mismatch between %v and %v: %w", localType, targetType, errIncompatibleTypes)
+		}
+
+	case *Enum:
+		return coreEnumsMatch(lv, targetType.(*Enum))
+
+	case composite:
+		tv := targetType.(composite)
+
+		if len(lv.members()) > len(tv.members()) {
+			return errIncompatibleTypes
+		}
+
+		localMembers := lv.members()
+		targetMembers := map[string]Member{}
+		for _, member := range tv.members() {
+			targetMembers[member.Name] = member
+		}
+
+		for _, localMember := range localMembers {
+			targetMember, found := targetMembers[localMember.Name]
+			if !found {
+				return fmt.Errorf("no field %q in %v: %w", localMember.Name, targetType, errIncompatibleTypes)
+			}
+
+			err := coreTypesMatch(localMember.Type, targetMember.Type, visited)
+			if err != nil {
+				return err
+			}
+		}
+
+	case *Int:
+		if !coreEncodingMatches(lv, targetType.(*Int)) {
+			return fmt.Errorf("int mismatch between %v and %v: %w", localType, targetType, errIncompatibleTypes)
+		}
+
+	case *Pointer:
+		tv := targetType.(*Pointer)
+
+		// Allow a pointer to a forward declaration to match a struct
+		// or union.
+		if fwd, ok := As[*Fwd](lv.Target); ok && fwd.matches(tv.Target) {
+			return nil
+		}
+
+		if fwd, ok := As[*Fwd](tv.Target); ok && fwd.matches(lv.Target) {
+			return nil
+		}
+
+		return coreTypesMatch(lv.Target, tv.Target, visited)
+
+	case *Array:
+		tv := targetType.(*Array)
+
+		if lv.Nelems != tv.Nelems {
+			return fmt.Errorf("array mismatch between %v and %v: %w", localType, targetType, errIncompatibleTypes)
+		}
+
+		return coreTypesMatch(lv.Type, tv.Type, visited)
+
+	case *FuncProto:
+		tv := targetType.(*FuncProto)
+
+		if len(lv.Params) != len(tv.Params) {
+			return fmt.Errorf("function param mismatch: %w", errIncompatibleTypes)
+		}
+
+		for i, lparam := range lv.Params {
+			if err := coreTypesMatch(lparam.Type, tv.Params[i].Type, visited); err != nil {
+				return err
+			}
+		}
+
+		return coreTypesMatch(lv.Return, tv.Return, visited)
+
+	default:
+		return fmt.Errorf("unsupported type %T", localType)
+	}
+
+	return nil
+}
+
+// coreEncodingMatches returns true if both ints have the same size and signedness.
+// All encodings other than `Signed` are considered unsigned.
+func coreEncodingMatches(local, target *Int) bool {
+	return local.Size == target.Size && (local.Encoding == Signed) == (target.Encoding == Signed)
+}
+
+// coreEnumsMatch checks two enums match, which is considered to be the case if the following is true:
+// - size has to match (but enum may match enum64 and vice versa)
+// - local variants have to have a match in target by symbolic name (but not numeric value)
+func coreEnumsMatch(local *Enum, target *Enum) error {
+	if local.Size != target.Size {
+		return fmt.Errorf("size mismatch between %v and %v: %w", local, target, errIncompatibleTypes)
+	}
+
+	// If there are more values in the local than the target, there must be at least one value in the local
+	// that isn't in the target, and therefor the types are incompatible.
+	if len(local.Values) > len(target.Values) {
+		return fmt.Errorf("local has more values than target: %w", errIncompatibleTypes)
+	}
+
+outer:
+	for _, lv := range local.Values {
+		for _, rv := range target.Values {
+			if coreEssentialNamesMatch(lv.Name, rv.Name) {
+				continue outer
+			}
+		}
+
+		return fmt.Errorf("no match for %v in %v: %w", lv, target, errIncompatibleTypes)
+	}
+
+	return nil
+}
diff --git a/btf/types.go b/btf/types.go
index 48dc388..3cb9184 100644
--- a/btf/types.go
+++ b/btf/types.go
@@ -318,6 +318,18 @@ func (f *Fwd) copy() Type {
 	return &cpy
 }
 
+func (f *Fwd) matches(typ Type) bool {
+	if _, ok := As[*Struct](typ); ok && f.Kind == FwdStruct {
+		return true
+	}
+
+	if _, ok := As[*Union](typ); ok && f.Kind == FwdUnion {
+		return true
+	}
+
+	return false
+}
+
 // Typedef is an alias of a Type.
 type Typedef struct {
 	Name string



Test patch:
diff --git a/btf/core_test.go b/btf/core_test.go
index 0c39d3f..3d58355 100644
--- a/btf/core_test.go
+++ b/btf/core_test.go
@@ -705,6 +705,230 @@ func BenchmarkCORESkBuff(b *testing.B) {
 	}
 }
 
+func TestCORETypesMatch(t *testing.T) {
+	tests := []struct {
+		a, b       Type
+		match      bool
+		reversible bool
+	}{
+		{&Void{}, &Void{}, true, true},
+		{&Int{Size: 32}, &Int{Size: 32}, true, true},
+		{&Int{Size: 64}, &Int{Size: 32}, false, true},
+		{&Int{Size: 32}, &Int{Size: 32, Encoding: Signed}, false, true},
+		{&Fwd{Name: "a"}, &Fwd{Name: "a"}, true, true},
+		{&Fwd{Name: "a"}, &Fwd{Name: "b___new"}, false, true},
+		{&Fwd{Name: "a"}, &Fwd{Name: "a___new"}, true, true},
+		{&Fwd{Name: "a"}, &Struct{Name: "a___new"}, false, true},
+		{&Fwd{Name: "a"}, &Union{Name: "a___new"}, false, true},
+		{&Fwd{Name: "a", Kind: FwdStruct}, &Fwd{Name: "a___new", Kind: FwdUnion}, false, true},
+		{&Pointer{&Fwd{Name: "a", Kind: FwdStruct}}, &Pointer{&Struct{Name: "a___new"}}, true, true},
+		{&Pointer{&Fwd{Name: "a", Kind: FwdUnion}}, &Pointer{&Union{Name: "a___new"}}, true, true},
+		{&Pointer{&Fwd{Name: "a", Kind: FwdStruct}}, &Pointer{&Union{Name: "a___new"}}, false, true},
+		{&Struct{Name: "a___new"}, &Union{Name: "a___new"}, false, true},
+		{&Pointer{&Struct{Name: "a"}}, &Pointer{&Union{Name: "a___new"}}, false, true},
+		{
+			&Struct{Name: "a", Members: []Member{
+				{Name: "foo", Type: &Int{}},
+			}},
+			&Struct{Name: "a___new", Members: []Member{
+				{Name: "foo", Type: &Int{}},
+			}},
+			true,
+			true,
+		},
+		{
+			&Struct{Name: "a", Members: []Member{
+				{Name: "foo", Type: &Int{}},
+			}},
+			&Struct{Name: "a___new", Members: []Member{
+				{Name: "foo", Type: &Int{}},
+				{Name: "bar", Type: &Int{}},
+			}},
+			true,
+			false,
+		},
+		{
+			&Struct{Name: "a", Members: []Member{
+				{Name: "foo", Type: &Int{}},
+				{Name: "bar", Type: &Int{}},
+			}},
+			&Struct{Name: "a___new", Members: []Member{
+				{Name: "foo", Type: &Int{}},
+			}},
+			false,
+			false,
+		},
+		{
+			&Struct{Name: "a", Members: []Member{
+				{Name: "bar", Type: &Int{}},
+			}},
+			&Struct{Name: "a___new", Members: []Member{
+				{Name: "foo", Type: &Int{}},
+			}},
+			false,
+			false,
+		},
+		{
+			&Struct{Name: "a", Members: []Member{
+				{Name: "foo", Type: &Int{Encoding: Signed}},
+			}},
+			&Struct{Name: "a___new", Members: []Member{
+				{Name: "foo", Type: &Int{}},
+			}},
+			false,
+			false,
+		},
+		{
+			&Enum{Name: "a", Values: []EnumValue{
+				{"foo", 1},
+			}},
+			&Enum{Name: "a___new", Values: []EnumValue{
+				{"foo", 1},
+			}},
+			true,
+			true,
+		},
+		{
+			&Enum{Name: "a", Values: []EnumValue{
+				{"foo", 1},
+			}},
+			&Enum{Name: "a___new", Values: []EnumValue{
+				{"foo", 1},
+				{"bar", 2},
+			}},
+			true,
+			false,
+		},
+		{
+			&Enum{Name: "a", Values: []EnumValue{
+				{"foo", 1},
+				{"bar", 2},
+			}},
+			&Enum{Name: "a___new", Values: []EnumValue{
+				{"foo", 1},
+			}},
+			false,
+			false,
+		},
+		{
+			&Enum{Name: "a", Values: []EnumValue{
+				{"foo", 1},
+			}},
+			&Enum{Name: "a___new", Values: []EnumValue{
+				{"bar", 1},
+			}},
+			false,
+			false,
+		},
+		{
+			&Enum{Name: "a", Values: []EnumValue{
+				{"foo", 1},
+			}, Size: 1},
+			&Enum{Name: "a___new", Values: []EnumValue{
+				{"foo", 1},
+			}, Size: 2},
+			false,
+			false,
+		},
+		{
+			&Array{Type: &Int{}, Nelems: 2},
+			&Array{Type: &Int{}, Nelems: 2},
+			true,
+			true,
+		},
+		{
+			&Array{Type: &Int{}, Nelems: 3},
+			&Array{Type: &Int{}, Nelems: 2},
+			false,
+			true,
+		},
+		{
+			&Array{Type: &Void{}, Nelems: 2},
+			&Array{Type: &Int{}, Nelems: 2},
+			false,
+			true,
+		},
+		{
+			&FuncProto{Return: &Int{}, Params: []FuncParam{
+				{Name: "foo", Type: &Int{}},
+			}},
+			&FuncProto{Return: &Int{}, Params: []FuncParam{
+				{Name: "bar", Type: &Int{}},
+			}},
+			true,
+			true,
+		},
+		{
+			&FuncProto{Return: &Int{}, Params: []FuncParam{
+				{Name: "foo", Type: &Int{}},
+			}},
+			&FuncProto{Return: &Int{}, Params: []FuncParam{
+				{Name: "bar", Type: &Int{}},
+				{Name: "baz", Type: &Int{}},
+			}},
+			false,
+			true,
+		},
+		{
+			&FuncProto{Return: &Void{}, Params: []FuncParam{
+				{Name: "foo", Type: &Int{}},
+			}},
+			&FuncProto{Return: &Int{}, Params: []FuncParam{
+				{Name: "bar", Type: &Int{}},
+			}},
+			false,
+			true,
+		},
+		{
+			&FuncProto{Return: &Void{}, Params: []FuncParam{
+				{Name: "bar", Type: &Int{Encoding: Signed}},
+			}},
+			&FuncProto{Return: &Int{}, Params: []FuncParam{
+				{Name: "bar", Type: &Int{}},
+			}},
+			false,
+			true,
+		},
+	}
+
+	for _, test := range tests {
+		err := coreTypesMatch(test.a, test.b, nil)
+		if test.match {
+			if err != nil {
+				t.Errorf("Expected types to match: %s\na = %#v\nb = %#v", err, test.a, test.b)
+				continue
+			}
+		} else {
+			if !errors.Is(err, errIncompatibleTypes) {
+				t.Errorf("Expected types to be incompatible: \na = %#v\nb = %#v", test.a, test.b)
+				continue
+			}
+		}
+
+		if test.reversible {
+			err = coreTypesMatch(test.b, test.a, nil)
+			if test.match {
+				if err != nil {
+					t.Errorf("Expected reversed types to match: %s\na = %#v\nb = %#v", err, test.a, test.b)
+				}
+			} else {
+				if !errors.Is(err, errIncompatibleTypes) {
+					t.Errorf("Expected reversed types to be incompatible: %s\na = %#v\nb = %#v", err, test.a, test.b)
+				}
+			}
+		}
+	}
+
+	for _, invalid := range []Type{&Var{}, &Datasec{}} {
+		err := coreTypesMatch(invalid, invalid, nil)
+		if errors.Is(err, errIncompatibleTypes) {
+			t.Errorf("Expected an error for %T, not errIncompatibleTypes", invalid)
+		} else if err == nil {
+			t.Errorf("Expected an error for %T", invalid)
+		}
+	}
+}
+
 // dummyTypeID returns 0, nil for any passed type.
 func dummyTypeID(Type) (TypeID, error) {
 	return 0, nil
diff --git a/btf/testdata/bpf_core_read.h b/btf/testdata/bpf_core_read.h
index 09ebe3d..9b26f1e 100644
--- a/btf/testdata/bpf_core_read.h
+++ b/btf/testdata/bpf_core_read.h
@@ -27,8 +27,9 @@ enum bpf_type_id_kind {
 
 /* second argument to __builtin_preserve_type_info() built-in */
 enum bpf_type_info_kind {
-	BPF_TYPE_EXISTS = 0,		/* type existence in target kernel */
+	BPF_TYPE_EXISTS = 0,	/* type existence in target kernel */
 	BPF_TYPE_SIZE = 1,		/* type size in target kernel */
+	BPF_TYPE_MATCHES = 2, 	/* type match in target kernel */
 };
 
 /* second argument to __builtin_preserve_enum_value() built-in */
@@ -154,6 +155,17 @@ enum bpf_enum_value_kind {
 #define bpf_core_type_exists(type)					    \
 	__builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_EXISTS)
 
+/*
+ * Convenience macro to check that provided named type
+ * (struct/union/enum/typedef) "matches" that in a target kernel.
+ * Returns:
+ *    1, if the type matches in the target kernel's BTF;
+ *    0, if the type does not match any in the target kernel
+ */
+#define bpf_core_type_matches(type)					    \
+	__builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_MATCHES)
+
+
 /*
  * Convenience macro to get the byte size of a provided named type
  * (struct/union/enum/typedef) in a target kernel.
diff --git a/btf/testdata/relocs-eb.elf b/btf/testdata/relocs-eb.elf
index 7ae44da..7fc2336 100644
Binary files a/btf/testdata/relocs-eb.elf and b/btf/testdata/relocs-eb.elf differ
diff --git a/btf/testdata/relocs-el.elf b/btf/testdata/relocs-el.elf
index d3ef666..f16f829 100644
Binary files a/btf/testdata/relocs-el.elf and b/btf/testdata/relocs-el.elf differ
diff --git a/btf/testdata/relocs.c b/btf/testdata/relocs.c
index 7991901..137452b 100644
--- a/btf/testdata/relocs.c
+++ b/btf/testdata/relocs.c
@@ -97,6 +97,13 @@ __section("socket/type_ids") int type_ids() {
 		} \
 	})
 
+#define type_matches(expr) \
+	({ \
+		if (!bpf_core_type_matches(expr)) { \
+			return __LINE__; \
+		} \
+	})
+
 __section("socket/types") int types() {
 	type_exists(struct s);
 	type_exists(s_t);
@@ -125,6 +132,19 @@ __section("socket/types") int types() {
 	type_size_matches(const u_t);
 	type_size_matches(volatile u_t);
 
+	type_matches(struct s);
+	type_matches(s_t);
+	type_matches(const s_t);
+	type_matches(volatile s_t);
+	type_matches(enum e);
+	type_matches(e_t);
+	type_matches(const e_t);
+	type_matches(volatile e_t);
+	type_matches(union u);
+	type_matches(u_t);
+	type_matches(const u_t);
+	type_matches(volatile u_t);
+
 	return 0;
 }