Skip to content

Commit 4b779dd

Browse files
committed
Allow adding no_preserve_tags if we can see the defining VarDecl
If we can see a non-pointer VarDecl, we know that the effective type that is being copied to/from matches the type of the VarDecl. Previously the following code: `int buf[16]; __builtin_memmove(cap, buf, sizeof(*cap));` didn't set the no_preserve_tags attribute, but now we do. There are a few more cases related to member expressions where we could add the attribute but don't yet. For example, for &foo->member if we can see the definition of foo and the entire struct does not contain capabilities. See the no-tag-copy-member-expr.cpp test for more examples and rationale.
1 parent 9ecd29a commit 4b779dd

File tree

4 files changed

+277
-31
lines changed

4 files changed

+277
-31
lines changed

clang/lib/CodeGen/CodeGenTypes.cpp

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,24 @@ CodeGenTypes::copyShouldPreserveTags(const Expr *DestPtr, const Expr *SrcPtr,
10041004
return DstPreserve;
10051005
}
10061006

1007+
static const VarDecl *findUnderlyingVarDecl(const Expr *E) {
1008+
// Note: this is pretty similar to E->getReferencedDeclOfCallee(); and should
1009+
// possibly be moved to Expr.cpp
1010+
const Expr *UnderlyingExpr = E->IgnoreParenImpCasts();
1011+
if (auto *UO = dyn_cast<UnaryOperator>(UnderlyingExpr)) {
1012+
if (UO->getOpcode() == UO_AddrOf) {
1013+
return findUnderlyingVarDecl(UO->getSubExpr());
1014+
}
1015+
} else if (auto DRE = dyn_cast<DeclRefExpr>(UnderlyingExpr)) {
1016+
return dyn_cast<const VarDecl>(DRE->getDecl());
1017+
}
1018+
// TODO: We could improve analysis for MemberExpr, but only if the copy size
1019+
// is <= the size of the member, since memcpy() accross multiple fields is
1020+
// a something that exists (despite not being compatible with sub-object
1021+
// bounds). For now we just look at the declaration of the entire struct
1022+
return nullptr;
1023+
}
1024+
10071025
llvm::PreserveCheriTags
10081026
CodeGenTypes::copyShouldPreserveTags(const Expr *E, Optional<CharUnits> Size) {
10091027
assert(E->getType()->isAnyPointerType());
@@ -1013,14 +1031,27 @@ CodeGenTypes::copyShouldPreserveTags(const Expr *E, Optional<CharUnits> Size) {
10131031
QualType Ty = E->IgnoreParenImpCasts()->getType();
10141032
if (Ty->isAnyPointerType())
10151033
Ty = Ty->getPointeeType();
1016-
// TODO: Find the underlying VarDecl to improve diagnostics
1017-
const VarDecl *UnderlyingVar = nullptr;
1018-
// TODO: this assertion may be overly aggressive.
1019-
assert((!UnderlyingVar || UnderlyingVar->getType() == Ty) &&
1020-
"Passed wrong VarDecl?");
1021-
// If we have an underlying VarDecl, we can assume that the dynamic type of
1022-
// the object is known and can perform more detailed analysis.
1023-
return copyShouldPreserveTagsForPointee(Ty, UnderlyingVar != nullptr, Size);
1034+
bool EffectiveTypeKnown = false;
1035+
const VarDecl *UnderlyingVar = findUnderlyingVarDecl(E);
1036+
if (UnderlyingVar) {
1037+
QualType VarTy = UnderlyingVar->getType();
1038+
assert(!VarTy->isIncompleteType() && "Unexpected incomplete type");
1039+
if (VarTy->isReferenceType()) {
1040+
// If the variable declaration is a C++ reference we can assume that the
1041+
// effective type of the object matches the type of the reference since
1042+
// forming the reference would have been invalid otherwise.
1043+
Ty = VarTy->getPointeeType();
1044+
EffectiveTypeKnown = true;
1045+
} else if (!VarTy->isAnyPointerType()) {
1046+
// If we found a non-pointer declaration that we are copying to/from, use
1047+
// the type of the declaration for the analysis since that defines the
1048+
// effective type. For pointers we can't assume anything since they could
1049+
// be "allocated objects" without a declared type.
1050+
Ty = VarTy;
1051+
EffectiveTypeKnown = true;
1052+
}
1053+
}
1054+
return copyShouldPreserveTagsForPointee(Ty, EffectiveTypeKnown, Size);
10241055
}
10251056

10261057
llvm::PreserveCheriTags CodeGenTypes::copyShouldPreserveTagsForPointee(

clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,18 @@ void test_addrof_char(struct OneCap *cap, char c, __uint128_t u) {
2222
// uint128_t cannot not hold tags -> no need to preserve them since we can see the underlying allocation.
2323
__builtin_memmove(cap, &u, sizeof(u));
2424
// CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}}
25-
// FIXME: We can see the underlying decl, this should not need to preserve tags
26-
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR:#[0-9]+]]{{$}}
27-
// FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}}
25+
// CHECK-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}}
2826
__builtin_memmove(&u, cap, sizeof(u));
2927
// CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}}
30-
// FIXME: We can see the underlying decl, this should not need to preserve tags
31-
// FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}}
32-
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR:#[0-9]+]]{{$}}
28+
// CHECK-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}}
3329
}
3430

3531
void test_small_copy(struct OneCap *cap1, struct OneCap *cap2) {
3632
// CHECK-LABEL: void @test_small_copy(
3733
__builtin_memmove(cap1, cap2, sizeof(*cap1));
3834
// This copy preserves tags
3935
// CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}}
40-
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}}
36+
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR:#[0-9]+]]{{$}}
4137
__builtin_memmove(cap1, cap2, 2);
4238
// This copy is too small -> no need to preserve tags
4339
// CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}}
@@ -57,14 +53,10 @@ void test_addrof_char_buf(struct OneCap *cap, struct strbuf s) {
5753
// FIXME: can we add no_preserve_tags if the programmer didn't add an _Alignas()?
5854
__builtin_memmove(cap, &s, sizeof(s));
5955
// CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}}
60-
// FIXME: We can see the underlying decl, this should not need to preserve tags
61-
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}}
62-
// FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}}
56+
// CHECK-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}}
6357
__builtin_memmove(&s, cap, sizeof(s));
6458
// CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}}
65-
// FIXME: We can see the underlying decl, this should not need to preserve tags
66-
// FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}}
67-
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}}
59+
// CHECK-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}}
6860
}
6961

7062
void test_array_decay(struct OneCap *cap) {
@@ -73,22 +65,18 @@ void test_array_decay(struct OneCap *cap) {
7365
int buf[16];
7466
__builtin_memmove(cap, buf, sizeof(*cap));
7567
// CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 4 {{%[a-z0-9]+}}
76-
// FIXME: We can see the underlying decl, this should not need to preserve tags
77-
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR:#[0-9]+]]{{$}}
78-
// FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}}
68+
// CHECK-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}}
7969

8070
__builtin_memmove(buf, cap, sizeof(*cap));
8171
// CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 4 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}}
82-
// FIXME: We can see the underlying decl, this should not need to preserve tags
83-
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}}
84-
// FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}}
72+
// CHECK-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}}
8573

8674
// char array aligned to one byte -> while it does not contain tags we conservatively assume it might.
8775
// TODO: should we only do this for aligned char arrays?
8876
char buf2[16];
8977
__builtin_memmove(cap, buf2, sizeof(*cap));
9078
// CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}}
91-
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}}
79+
// CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR:#[0-9]+]]{{$}}
9280
// TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}}
9381
__builtin_memmove(buf2, cap, sizeof(*cap));
9482
// CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}}

clang/test/CodeGen/cheri/no-tag-copy-strict-aliasing.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ void no_retain(long **q) {
4040
// probably be aligned so malloc will retain tags at run time).
4141
// CHECK: @no_retain(i64 addrspace(200)* addrspace(200)* [[Q:%.*]]) addrspace(200)
4242
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9]+}}, i8 addrspace(200)* align 8 {{%[0-9]+}}
43-
// CHECK-SAME: , i64 32, i1 false){{$}}
44-
// TODO-CHECK-SAME: , i64 32, i1 false) [[NO_TAGS_ATTR:#.*]]{{$}}
43+
// CHECK-SAME: , i64 32, i1 false) [[NO_TAGS_ATTR:#.*]]{{$}}
4544
// CHECK-NEXT: ret void
4645
}
4746

@@ -61,4 +60,4 @@ void retain_char_array(long **q) {
6160
// CHECK-NEXT: ret void
6261
}
6362

64-
// TODO-CHECK: [[NO_TAGS_ATTR]] = { no_preserve_cheri_tags }
63+
// CHECK: [[NO_TAGS_ATTR]] = { no_preserve_cheri_tags }
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
// RUN: %riscv64_cheri_purecap_cc1 -xc %s -o - -emit-llvm -verify | FileCheck %s --check-prefixes=CHECK,CHECK-C --implicit-check-not=llvm.memcpy.
2+
// RUN: %riscv64_cheri_purecap_cc1 -xc++ %s -o - -emit-llvm -verify | FileCheck %s --check-prefixes=CHECK,CHECK-CXX --implicit-check-not=llvm.memcpy.
3+
// expected-no-diagnostics
4+
/// Check that we look at the entire structure when deciding whether to add the
5+
/// no_preserve_tags attribute for memcpy() calls on struct members: the memcpy
6+
/// could extend past this field and copy adjacent instances of the same struct.
7+
8+
struct Nested {
9+
int i;
10+
long l;
11+
float f;
12+
};
13+
14+
struct TestWithCap {
15+
char *ptr;
16+
__uint128_t not_a_cap;
17+
long array[4];
18+
struct Nested n;
19+
char *ptr2;
20+
};
21+
22+
struct TestNoCap {
23+
__uint128_t not_a_cap;
24+
struct Nested n;
25+
long array[4];
26+
long pad;
27+
};
28+
29+
void test_member_expr_byval(void *buf, struct TestWithCap t, struct TestNoCap t2) {
30+
// CHECK-C-LABEL: void @test_member_expr_byval(
31+
// CHECK-CXX-LABEL: void @_Z22test_member_expr_byvalPv11TestWithCap9TestNoCap(
32+
33+
// For now we can't add the attribute for calls with member access despite the
34+
// address-of being on one of the non-cap fields, since we must conservatively
35+
// assume that the memcpy extends across all fields of the struct (and possibly
36+
// following instances of that struct for arrays).
37+
// Note: we could look at constant sizes if we wanted to perform a more
38+
// sophisticated analysis, but this is unlikely to be a big win for performance.
39+
__builtin_memcpy(buf, &t, 32);
40+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
41+
// CHECK-SAME: , i64 32, i1 false) [[MUST_PRESERVE_STRUCT_WITHCAP:#[0-9]+]]{{$}}
42+
__builtin_memcpy(buf, &t.ptr, 32);
43+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
44+
// CHECK-SAME: , i64 32, i1 false) [[MUST_PRESERVE_CHARPTR:#[0-9]+]]{{$}}
45+
// No attribute for the following four cases:
46+
__builtin_memcpy(buf, &t.not_a_cap, 32);
47+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
48+
// CHECK-SAME: , i64 32, i1 false){{$}}
49+
__builtin_memcpy(buf, t.array, 32);
50+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
51+
// CHECK-SAME: , i64 32, i1 false){{$}}
52+
__builtin_memcpy(buf, &t.n, 32);
53+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
54+
// CHECK-SAME: , i64 32, i1 false){{$}}
55+
__builtin_memcpy(buf, (&t)->array, 32);
56+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
57+
// CHECK-SAME: , i64 32, i1 false){{$}}
58+
__builtin_memcpy(buf, &(&t)->not_a_cap, 32);
59+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
60+
// CHECK-SAME: , i64 32, i1 false){{$}}
61+
62+
// However, for the struct without capabilities all of these should be safe:
63+
// However, we don't know here that there is no subclass that could have
64+
// capabilities following the current object.
65+
// TODO: If we know the size of the copy <= sizeof(T), we should set the attribute.
66+
__builtin_memcpy(buf, &t2, sizeof(t2));
67+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
68+
// CHECK-SAME: , i64 80, i1 false) [[NO_PRESERVE_TAGS:#[0-9]+]]{{$}}
69+
/// The following call could copy trailing caps:
70+
__builtin_memcpy(buf, &t2, sizeof(t2) + sizeof(void *));
71+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
72+
// CHECK-SAME: , i64 96, i1 false){{$}}
73+
__builtin_memcpy(buf, &t2.not_a_cap, 40);
74+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
75+
// CHECK-SAME: , i64 40, i1 false){{$}}
76+
// TODO-CHECK-SAME: , i64 40, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
77+
__builtin_memcpy(buf, t2.array, 40);
78+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
79+
// CHECK-SAME: , i64 40, i1 false){{$}}
80+
// TODO-CHECK-SAME: , i64 40, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
81+
__builtin_memcpy(buf, &t2.n, 40);
82+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
83+
// CHECK-SAME: , i64 40, i1 false){{$}}
84+
// TODO-CHECK-SAME: , i64 40, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
85+
__builtin_memcpy(buf, (&t2)->array, 40);
86+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
87+
// CHECK-SAME: , i64 40, i1 false){{$}}
88+
// TODO-CHECK-SAME: , i64 40, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
89+
__builtin_memcpy(buf, &(&t2)->not_a_cap, 40);
90+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
91+
// CHECK-SAME: , i64 40, i1 false){{$}}
92+
// TODO-CHECK-SAME: , i64 40, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
93+
94+
// Direct assignment should always the no_preserve_tags attribute (the C2x
95+
// 6.5 memcpy+"Allocated objects have no declared type." case does not apply):
96+
t.n = t2.n;
97+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
98+
// CHECK-SAME: , i64 24, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
99+
}
100+
101+
// To avoid --implicit-check-not error:
102+
// CHECK: declare void @llvm.memcpy.p200i8.p200i8.i64(
103+
104+
void test_member_expr_ptr(void *buf, struct TestWithCap *t, struct TestNoCap *t2) {
105+
// CHECK-C-LABEL: void @test_member_expr_ptr(
106+
// CHECK-CXX-LABEL: void @_Z20test_member_expr_ptrPvP11TestWithCapP9TestNoCap(
107+
108+
// If the target VarDecl is a pointer, we can't assume that it matches the
109+
// underlying effective object type (C2x 6.5) so we have to be conservative
110+
// and not add the attribute (even for non-tag-bearing structs).
111+
// However, we do add the must_preserve_tags metadata calls where we know
112+
// that one of the types holds capabilities (as that is the most conservative
113+
// behaviour).
114+
__builtin_memcpy(buf, t, 32);
115+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
116+
// CHECK-SAME: , i64 32, i1 false) [[MUST_PRESERVE_STRUCT_WITHCAP]]{{$}}
117+
__builtin_memcpy(buf, &t->ptr, 32);
118+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
119+
// CHECK-SAME: , i64 32, i1 false) [[MUST_PRESERVE_CHARPTR]]{{$}}
120+
__builtin_memcpy(buf, &t->not_a_cap, 32);
121+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
122+
// CHECK-SAME: , i64 32, i1 false){{$}}
123+
__builtin_memcpy(buf, &t->array, 32);
124+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
125+
// CHECK-SAME: , i64 32, i1 false){{$}}
126+
__builtin_memcpy(buf, &t->n, 32);
127+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
128+
// CHECK-SAME: , i64 32, i1 false){{$}}
129+
__builtin_memcpy(buf, (*t).array, 32);
130+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
131+
// CHECK-SAME: , i64 32, i1 false){{$}}
132+
__builtin_memcpy(buf, &((*t).not_a_cap), 32);
133+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
134+
// CHECK-SAME: , i64 32, i1 false){{$}}
135+
136+
// We also can't add the attribute for the struct without caps since we don't
137+
// know the type of the underlying memory.
138+
__builtin_memcpy(buf, t2, 40);
139+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
140+
// CHECK-SAME: , i64 40, i1 false){{$}}
141+
__builtin_memcpy(buf, &t2->not_a_cap, 40);
142+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
143+
// CHECK-SAME: , i64 40, i1 false){{$}}
144+
__builtin_memcpy(buf, &t2->array, 40);
145+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
146+
// CHECK-SAME: , i64 40, i1 false){{$}}
147+
__builtin_memcpy(buf, &t2->n, 40);
148+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
149+
// CHECK-SAME: , i64 40, i1 false){{$}}
150+
__builtin_memcpy(buf, (*t2).array, 40);
151+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
152+
// CHECK-SAME: , i64 40, i1 false){{$}}
153+
__builtin_memcpy(buf, &((*t2).not_a_cap), 40);
154+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
155+
// CHECK-SAME: , i64 40, i1 false){{$}}
156+
157+
// Direct assignment should always the no_preserve_tags attribute (the C2x
158+
// 6.5 memcpy+"Allocated objects have no declared type." case does not apply):
159+
t->n = t2->n;
160+
// CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(
161+
// CHECK-SAME: , i64 24, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
162+
}
163+
164+
#ifdef __cplusplus
165+
void test_member_expr_ref(void *buf, struct TestWithCap &t, struct TestNoCap &t2) {
166+
// CHECK-CXX-LABEL: void @_Z20test_member_expr_refPvR11TestWithCapR9TestNoCap(
167+
168+
// For C++ references we can assume that those point to a valid object of
169+
// that type, since otherwise forming the reference would have been invalid.
170+
// For the member references the same caveats apply as in test_member_expr_byval()
171+
__builtin_memcpy(buf, &t, 32);
172+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
173+
// CHECK-CXX-SAME: , i64 32, i1 false) [[MUST_PRESERVE_STRUCT_WITHCAP]]{{$}}
174+
__builtin_memcpy(buf, &t.ptr, 32);
175+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
176+
// CHECK-CXX-SAME: , i64 32, i1 false) [[MUST_PRESERVE_CHARPTR]]{{$}}
177+
__builtin_memcpy(buf, &t.not_a_cap, 32);
178+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
179+
// CHECK-CXX-SAME: , i64 32, i1 false){{$}}
180+
__builtin_memcpy(buf, t.array, 32);
181+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
182+
// CHECK-CXX-SAME: , i64 32, i1 false){{$}}
183+
__builtin_memcpy(buf, &t.n, 32);
184+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
185+
// CHECK-CXX-SAME: , i64 32, i1 false){{$}}
186+
__builtin_memcpy(buf, (&t)->array, 32);
187+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
188+
// CHECK-CXX-SAME: , i64 32, i1 false){{$}}
189+
__builtin_memcpy(buf, &(&t)->not_a_cap, 32);
190+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
191+
// CHECK-CXX-SAME: , i64 32, i1 false){{$}}
192+
193+
// However, for the struct without capabilities all of these should be safe:
194+
__builtin_memcpy(buf, &t2, 40);
195+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
196+
// CHECK-CXX-SAME: , i64 40, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
197+
__builtin_memcpy(buf, &t2.not_a_cap, 40);
198+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
199+
// CHECK-CXX-SAME: , i64 40, i1 false){{$}}
200+
// TODO-CHECK-CXX-SAME: , i64 40, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
201+
__builtin_memcpy(buf, t2.array, 40);
202+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
203+
// CHECK-CXX-SAME: , i64 40, i1 false){{$}}
204+
// TODO-CHECK-CXX-SAME: , i64 40, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
205+
__builtin_memcpy(buf, &t2.n, 40);
206+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
207+
// CHECK-CXX-SAME: , i64 40, i1 false){{$}}
208+
// TODO-CHECK-CXX-SAME: , i64 40, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
209+
__builtin_memcpy(buf, (&t2)->array, 40);
210+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
211+
// CHECK-CXX-SAME: , i64 40, i1 false){{$}}
212+
// TODO-CHECK-CXX-SAME: , i64 40, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
213+
__builtin_memcpy(buf, &(&t2)->not_a_cap, 40);
214+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
215+
// CHECK-CXX-SAME: , i64 40, i1 false){{$}}
216+
// TODO-CHECK-CXX-SAME: , i64 40, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
217+
218+
// Direct assignment should always the no_preserve_tags attribute (the C2x
219+
// 6.5 memcpy+"Allocated objects have no declared type." case does not apply):
220+
t.n = t2.n;
221+
// CHECK-CXX: call void @llvm.memcpy.p200i8.p200i8.i64(
222+
// CHECK-CXX-SAME: , i64 24, i1 false) [[NO_PRESERVE_TAGS]]{{$}}
223+
}
224+
#endif
225+
226+
// CHECK: attributes [[MUST_PRESERVE_STRUCT_WITHCAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct TestWithCap'" }
227+
// CHECK: attributes [[MUST_PRESERVE_CHARPTR]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'char * __capability'" }
228+
// CHECK-C: attributes [[NO_PRESERVE_TAGS]] = { no_preserve_cheri_tags }

0 commit comments

Comments
 (0)