From a8d3c02b2650863c325e3f7da10a8358422485e4 Mon Sep 17 00:00:00 2001 From: David Spickett Date: Wed, 20 Jul 2022 09:37:24 +0000 Subject: [PATCH 1/5] [llvm] Fix some test failures with EXPENSIVE_CHECKS and libstdc++ DebugLocEntry assumes that it either contains 1 item that has no fragment or many items that all have fragments (see the assert in addValues). When EXPENSIVE_CHECKS is enabled, _GLIBCXX_DEBUG is defined. On a few machines I've checked, this causes std::sort to call the comparator even if there is only 1 item to sort. Perhaps to check that it is implemented properly ordering wise, I didn't find out exactly why. operator< for a DbgValueLoc will crash if this happens because the optional Fragment is empty. Compiler/linker/optimisation level seems to make this happen or not. So I've seen this happen on x86 Ubuntu but the buildbot for release EXPENSIVE_CHECKS did not have this issue. Add an explicit check whether we have 1 item. Reviewed By: aprantl Differential Revision: https://reviews.llvm.org/D130156 (cherry picked from commit a0ccba5e192b14341b66ad57bc646a3af7418409) --- llvm/lib/CodeGen/AsmPrinter/DebugLocEntry.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/llvm/lib/CodeGen/AsmPrinter/DebugLocEntry.h b/llvm/lib/CodeGen/AsmPrinter/DebugLocEntry.h index 62ebadaf3cbeb..1102f40ed73df 100644 --- a/llvm/lib/CodeGen/AsmPrinter/DebugLocEntry.h +++ b/llvm/lib/CodeGen/AsmPrinter/DebugLocEntry.h @@ -214,6 +214,11 @@ class DebugLocEntry { // Sort the pieces by offset. // Remove any duplicate entries by dropping all but the first. void sortUniqueValues() { + // Values is either 1 item that does not have a fragment, or many items + // that all do. No need to sort if the former and also prevents operator< + // being called on a non fragment item when _GLIBCXX_DEBUG is defined. + if (Values.size() == 1) + return; llvm::sort(Values); Values.erase(std::unique(Values.begin(), Values.end(), [](const DbgValueLoc &A, const DbgValueLoc &B) { From f376284bc95957d3f79a2ced99a293c469a51678 Mon Sep 17 00:00:00 2001 From: Alex Richardson Date: Sat, 10 Oct 2020 15:39:13 +0100 Subject: [PATCH 2/5] Add tests for clang setting the no_preserve_cheri_tags attribute These tests highlight some places where we can easily add the no_preserve_tags attribute to allow inlining small copies. --- .../no-tag-copy-attribute-nocap-struct.c | 147 +++++++++++++++++ .../cheri/no-tag-copy-attribute-with-caps.c | 156 ++++++++++++++++++ 2 files changed, 303 insertions(+) create mode 100644 clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c create mode 100644 clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c diff --git a/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c b/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c new file mode 100644 index 0000000000000..b5c7888353531 --- /dev/null +++ b/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c @@ -0,0 +1,147 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --function-signature +/// Check that we can inline memcpy() calls for small structures that are known +/// not to contain capabilities. Previously we assumed that all copies >= sizeof(capability) +/// can contain capabilities and we therefore fell back to calling memcpy if the +/// alignment was less than >= alignof(capability). +/// TODO: include some end-to-end testing to ensure that the frontend and backend changes allow +/// eliding the memcpy() call that we were previously force to make? +// RUN: %riscv64_cheri_purecap_cc1 %s -emit-llvm -o - -O0 | FileCheck %s + +#if __has_feature(capabilities) +#define CAP_SIZE sizeof(void *__capability) +#else +#define CAP_SIZE (2 * sizeof(long)) +#endif + +void *memcpy(void *, const void *, unsigned long); + +// Test case from lib/libc/sys/__vdso_gettimeofday.c binuptime +struct bintime { + long sec; + long frac; +}; +struct vdso_timehands { + /* some more fields */ + struct bintime th_offset; + struct bintime th_boottime; +}; + +void test_binuptime_assign(struct bintime *bt, struct vdso_timehands *th) { + *bt = th->th_offset; + // CHECK-LABEL: void @test_binuptime_assign( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} + // FIXME-SAME: i64 16, i1 false) [[NO_PRESERVE_TAGS_ATTR:#[0-9]+]]{{$}} +} + +struct cap_size_buffer { + int data[CAP_SIZE / sizeof(int)]; +}; + +void test_cap_size_buffer_copy(struct cap_size_buffer *a, struct cap_size_buffer *b) { + *a = *b; + // CHECK-LABEL: void @test_cap_size_buffer_copy( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 4 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 4 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} + // FIXME-SAME: i64 16, i1 false) [[NO_PRESERVE_TAGS_ATTR]]{{$}} +} + +union large_union_align_half_cap_size { + struct cap_size_buffer data[4]; + int i; + float f; + long l; +}; + +void test_bigger_union_copy(union large_union_align_half_cap_size *a, union large_union_align_half_cap_size *b) { + // No tags in this union -> can inline the memcpy() + *a = *b; + // CHECK-LABEL: void @test_bigger_union_copy( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 64, i1 false){{$}} + // FIXME-SAME: i64 64, i1 false) [[NO_PRESERVE_TAGS_ATTR]]{{$}} +} + +union large_union_with_char_array { + struct cap_size_buffer data; + int i; + float f; + long l; + char bigdata[4 * CAP_SIZE]; +}; + +void test_union_with_char_array(union large_union_with_char_array *a, union large_union_with_char_array *b) { + // There are no tags in this union, but it contains a char[], so we can't inline this memcpy safely. + // TODO: Since the char[] is not sufficiently aligned to store capabilities we could still add the attribute + *a = *b; + // CHECK-LABEL: void @test_union_with_char_array( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 64, i1 false){{$}} +} + +void test_align_copy_voidptr(void *a, void *b) { + // void* could contain caps but we don't add the attribute and rely on the backend to decide + memcpy(a, b, CAP_SIZE); + // CHECK-LABEL: void @test_align_copy_voidptr( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} +} + +void test_align_copy_charptr(char *a, char *b) { + // char* could contain caps since it's (unfortunately) basically the same as void*, + // but again we don't add the attribute and rely on the backend to decide + memcpy(a, b, CAP_SIZE); + // CHECK-LABEL: void @test_align_copy_charptr( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} +} + +void test_align_copy_longptr(long *a, long *b) { + // Note: here we don't infer the no-preserve tags (yet) + memcpy(a, b, CAP_SIZE); + // CHECK-LABEL: void @test_align_copy_longptr( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} + // FIXME-SAME: [[NO_PRESERVE_TAGS_ATTR]]{{$}} +} + +#if __has_feature(capabilities) +void test_align_copy_capptr(unsigned __intcap *a, unsigned __intcap *b) { + memcpy(a, b, CAP_SIZE); + // CHECK-LABEL: void @test_align_copy_capptr( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 16 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 16, i1 false) [[MUST_PRESERVE_TAGS_ATTR:#[0-9]+]]{{$}} +} +#endif + +struct fwddecl; + +void test_align_copy_fwd_declared(struct fwddecl *a, struct fwddecl *b) { + // We don't know if src contains capabilities -> don't add the attribute no-preserve + // attribute but also don't add the must-preserve attribute and let the backend decide + memcpy(a, b, CAP_SIZE); + // CHECK-LABEL: void @test_align_copy_fwd_declared( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} +} + +void test_align_copy_fwd_declared_2(void *a, struct fwddecl *b) { + // We don't know if src contains capabilities -> don't add the attribute no-preserve + // attribute but also don't add the must-preserve attribute and let the backend decide + memcpy(a, b, CAP_SIZE); + // CHECK-LABEL: void @test_align_copy_fwd_declared_2( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} +} + +void test_align_copy_fwd_declared_dst_notag(long *a, struct fwddecl *b) { + // We don't know if src contains capabilities, but the destination can't contain tags + // CHECK-LABEL: void @test_align_copy_fwd_declared_dst_notag( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} + memcpy(a, b, CAP_SIZE); +} + +// CHECK: attributes #0 = { +// FIXME-DAG: attributes [[NO_PRESERVE_TAGS_ATTR]] = { no_preserve_cheri_tags } +// CHECK-DAG: attributes [[MUST_PRESERVE_TAGS_ATTR]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'unsigned __intcap'" } diff --git a/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c b/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c new file mode 100644 index 0000000000000..4ae8ba719a9c0 --- /dev/null +++ b/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c @@ -0,0 +1,156 @@ +// REQUIRES: riscv-registered-target +// RUN: %riscv64_cheri_purecap_cc1 %s -o - -emit-llvm | FileCheck %s +// Diagnostics are only emitted when generating assembly (with optimizations) +// RUN: %riscv64_cheri_purecap_cc1 -debug-info-kind=standalone %s -o /dev/null -O1 -S -verify + +struct OneCap { + void *__capability b; +}; + +// define void @test_addrof_char(%struct.OneCap addrspace(200)* {{%[a-z0-9]+}}, i8 signext {{%[a-z0-9]+}}, i128 {{%[a-z0-9]+}}) addrspace(200) #0 { + +void test_addrof_char(struct OneCap *cap, char c, __uint128_t u) { + // CHECK-LABEL: void @test_addrof_char( + // Since this is an address-of expression we should be able to detect that + // the source does not contain tags + __builtin_memmove(cap, &c, sizeof(c)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9.]+}} + // CHECK-SAME: , i64 1, i1 false){{$}} + // FIXME-SAME: , i64 1, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} + __builtin_memmove(&c, cap, sizeof(c)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}.addr, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 1, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR:#[0-9]+]]{{$}} + // FIXME-SAME: , i64 1, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} + __builtin_memmove(cap, &u, sizeof(u)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false){{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} + + __builtin_memmove(&u, cap, sizeof(u)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} +} + +void test_small_copy(struct OneCap *cap1, struct OneCap *cap2) { + // CHECK-LABEL: void @test_small_copy( + __builtin_memmove(cap1, cap2, sizeof(*cap1)); + // This copy preserves tags + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + __builtin_memmove(cap1, cap2, 2); + // TODO :This copy is too small -> should not preserve tags + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 2, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} + // FIXME-SAME: , i64 2, i1 false) [[NO_PRESERVE_ATTR]]{{$}} +} + +struct strbuf { + char data[16]; +}; + +void test_addrof_char_buf(struct OneCap *cap, struct strbuf s) { + // CHECK-LABEL: void @test_addrof_char_buf( + + // Since this is an address-of expression we should be able to detect that + // the source does not contain tags. However, the struct contains a char[] + // so we have to conservatively assume that it might be used to hold tags. + // FIXME: can we add no_preserve_tags if the programmer didn't add an _Alignas()? + __builtin_memmove(cap, &s, sizeof(s)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false){{$}} + // TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}} + + __builtin_memmove(&s, cap, sizeof(s)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} + // TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}} +} + +void test_array_decay(struct OneCap *cap) { + // CHECK-LABEL: void @test_array_decay( + // array-decay -> We can tell that the buffer does not contain tags since it's not a char[] + int buf[16]; + __builtin_memmove(cap, buf, sizeof(*cap)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 4 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false){{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}} + __builtin_memmove(buf, cap, sizeof(*cap)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 4 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + + // char array aligned to one byte -> while it does not contain tags we conservatively assume it might. + // TODO: should we only do this for aligned char arrays? + char buf2[16]; + __builtin_memmove(cap, buf2, sizeof(*cap)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false){{$}} + // TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + __builtin_memmove(buf2, cap, sizeof(*cap)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} + // TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + + // We can't add the attribute for aligned char array decay since those are + // often used for buffers that contain anything. + _Alignas(void *__capability) char aligned_char_buf[16]; + __builtin_memmove(cap, aligned_char_buf, sizeof(*cap)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false){{$}} + __builtin_memmove(aligned_char_buf, cap, sizeof(*cap)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} +} + +void test_string_constant(struct OneCap *cap) { + // CHECK-LABEL: void @test_string_constant( + // Same for string -> char* + __builtin_memmove(cap, "abcdefghijklmnopqrstuvwxyz", sizeof(*cap)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 getelementptr inbounds ([27 x i8], [27 x i8] addrspace(200)* @.str + // CHECK-SAME: , i64 0 + // CHECK-SAME: , i64 0) + // CHECK-SAME: , i64 16, i1 false){{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} +} + +void test_void_buffer(struct OneCap *cap, void *buf) { + // CHECK-LABEL: void @test_void_buffer( + // A void* means unknown contents and therefore we preserve tags. + __builtin_memmove(cap, buf, sizeof(*cap)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false){{$}} + __builtin_memmove(buf, cap, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} +} + +void test_char_buffer(struct OneCap *cap, char *buf) { + // CHECK-LABEL: void @test_char_buffer( + // We have to also assume that char* means unknown contents. + __builtin_memmove(cap, buf, sizeof(*cap)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false){{$}} + __builtin_memmove(buf, cap, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} +} + +void test_int_buffer(struct OneCap *cap, int *buf) { + // CHECK-LABEL: void @test_int_buffer( + // However, for int* we can assume it does not contain tags. + __builtin_memmove(cap, buf, sizeof(*cap)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 4 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false){{$}} + __builtin_memmove(buf, cap, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 4 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 4 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} +} + +// CHECK: attributes #0 = { +// CHECK-DAG: attributes [[MUST_PRESERVE_WITH_TYPE_ATTR]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct OneCap'" } +// FIXME-DAG: attributes [[NO_PRESERVE_ATTR]] = { no_preserve_cheri_tags } From a5967a63054e2b901a77342ed38d366a3f7b7cd6 Mon Sep 17 00:00:00 2001 From: Alex Richardson Date: Thu, 21 Oct 2021 17:20:48 +0100 Subject: [PATCH 3/5] Set no_preserve_tags for copies of structs without capabilities This allows inlining of structure assignments for structs that are at least capability size but do not contain any capabilities (e.g. `struct { long a; long b; }`). We can also set the attribute for all trivial auto var-init cases since those patterns never contain valid capabilities. Due to C's effective type rules, we have to be careful when setting the attribute and only perform the type-base tag-preservation analysis if we know the effective type. For example, marking a memcpy() to/from `long*` as not tag-preserving could result in tag stripping for code that uses type casts. Such code is correct even under strict aliasing rules since the first store to a memory location determines the type. Example from https://github.com/CTSRD-CHERI/llvm-project/pull/506: ``` void *malloc(__SIZE_TYPE__); void *memcpy(void *, const void *, __SIZE_TYPE__); void foo(long **p, long **q) { *p = malloc(32); *q = malloc(32); (*p)[0] = 1; (*p)[1] = 2; *(void (**)(long **, long **))(*p + 2) = &foo; memcpy(*q, *p, 32); } ``` Despite the memcpy() argument being a long* (and therefore intuitively not tag preserving), we can't add the attribute since we don't actually know the type of the underlying object (malloc creates an allocated with no declared type). From C99: ``` The effective type of an object for an access to its stored value is the declared type of the object, if any (footnote 75: Allocated objects have no declared type). If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one. For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access. ``` There is another important caveat: we have to conservatively assume that the copy affects adjacent data (e.g. C++ subclass fields) that could hold capabilities if we don't know the copy size. If the copy size is <= sizeof(T), we can mark copies as non-tag-preserving since it cannot affect trailing fields (even if we are actually copying a subclass). We are also conservative if the structure contains an array of type ((un)signed) char or std::byte since those are often used to store arbitrary data (including capabilities). We could make this check more strict and require the array to be capability aligned, but that could be done as a follow-up change. --- clang/include/clang/AST/ASTContext.h | 11 ++ clang/lib/AST/ASTContext.cpp | 56 ++++++ clang/lib/CodeGen/CGBuiltin.cpp | 106 +++++------ clang/lib/CodeGen/CGClass.cpp | 5 +- clang/lib/CodeGen/CGDecl.cpp | 14 +- clang/lib/CodeGen/CGExprAgg.cpp | 14 +- clang/lib/CodeGen/CodeGenTypes.cpp | 128 +++++++++++++ clang/lib/CodeGen/CodeGenTypes.h | 37 ++++ .../CodeGen/cheri/cheri-inregs-param-info.c | 15 +- .../CodeGen/cheri/cheri-struct-return-value.c | 25 ++- clang/test/CodeGen/cheri/memcpy-unaligned.c | 180 +++++++++--------- .../no-tag-copy-attribute-nocap-struct.c | 37 ++-- .../cheri/no-tag-copy-attribute-with-caps.c | 89 ++++++--- .../cheri/no-tag-copy-strict-aliasing.c | 64 +++++++ .../cheri/subobject-bounds-structure-array.c | 22 ++- .../cheri/no-tag-copy-copy-ctor.cpp | 146 ++++++++++++++ .../test/CodeGenCXX/trivial-auto-var-init.cpp | 29 ++- .../Mips/cheri/memcpy-unaligned-addrinfo.c | 92 ++++++--- 18 files changed, 831 insertions(+), 239 deletions(-) create mode 100644 clang/test/CodeGen/cheri/no-tag-copy-strict-aliasing.c create mode 100644 clang/test/CodeGenCXX/cheri/no-tag-copy-copy-ctor.cpp diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 97a6391bc8a0f..bfd106991f085 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -2210,6 +2210,7 @@ class ASTContext : public RefCountedBase { private: /// Map storing whether a type contains capabilities. mutable llvm::DenseMap ContainsCapabilities; + mutable llvm::DenseMap CannotContainCapabilities; CanQualType getFromTargetType(unsigned Type) const; TypeInfo getTypeInfoImpl(const Type *T) const; @@ -2478,6 +2479,16 @@ class ASTContext : public RefCountedBase { /// capability or an aggregate type that contains one or more capabilities. bool containsCapabilities(QualType Ty) const; + /// Returns true if the record type cannot contain capabilities. + /// NB: this is a conservative analysis that treats overaligned char arrays as + /// potentially containing capabilities. + bool cannotContainCapabilities(const RecordDecl *RD) const; + /// Returns true if the type is a scalar type that has a representationa + /// that cannot be used to (legally) store capabilities. + /// NB: this is a conservative analysis that treats overaligned char arrays as + /// potentially containing capabilities. + bool cannotContainCapabilities(QualType Ty) const; + /// Return true if the specified type has unique object representations /// according to (C++17 [meta.unary.prop]p9) bool hasUniqueObjectRepresentations(QualType Ty) const; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 2cb8fd537f063..92ed559992ed8 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -11730,6 +11730,62 @@ bool ASTContext::containsCapabilities(QualType Ty) const { return Ret; } +bool ASTContext::cannotContainCapabilities(const RecordDecl *RD) const { + for (auto i = RD->field_begin(), e = RD->field_end(); i != e; ++i) { + const QualType Ty = i->getType(); + if (Ty->isCHERICapabilityType(*this)) + return false; + else if (const RecordType *RT = Ty->getAs()) { + if (!cannotContainCapabilities(RT->getDecl())) + return false; + } else if (!cannotContainCapabilities(Ty)) + return false; + } + // In the case of C++ classes, also check base classes + if (const CXXRecordDecl *CRD = dyn_cast(RD)) { + for (auto i = CRD->bases_begin(), e = CRD->bases_end(); i != e; ++i) { + const QualType Ty = i->getType(); + if (const RecordType *RT = Ty->getAs()) + if (!cannotContainCapabilities(RT->getDecl())) + return false; + } + } else if (RD->field_empty()) { + // A struct without fields could be used as an opaque type -> assume it + // might contain capabilities + return false; + } + return true; // Check all types that could contain capabilities +} + +bool ASTContext::cannotContainCapabilities(QualType Ty) const { + // If we've already looked up this type, then return the cached value. + auto Cached = CannotContainCapabilities.find(Ty.getAsOpaquePtr()); + if (Cached != CannotContainCapabilities.end()) + return Cached->second; + // Don't bother caching the trivial cases. + if (containsCapabilities(Ty)) + return false; + if (Ty->isArrayType()) { + QualType ElTy(Ty->getBaseElementTypeUnsafe(), 0); + // We have to be conservative here and assume that (unsigned) char[] as + // well as std::byte can be used for buffers that store capabilities. + // TODO: we could restrict this to buffers that are large enough and + // sufficiently aligned to store a capability. + if (ElTy->isCharType() || ElTy->isStdByteType()) + return false; + return cannotContainCapabilities(ElTy); + } + const RecordType *RT = Ty->getAs(); + if (!RT) { + // Not a record type, and the check above ensured this is not a capability + // type, so this type can't contain capabilities. + return true; + } + bool Ret = cannotContainCapabilities(RT->getDecl()); + CannotContainCapabilities[Ty.getAsOpaquePtr()] = Ret; + return Ret; +} + QualType ASTContext::getCorrespondingSaturatedType(QualType Ty) const { assert(Ty->isFixedPointType()); diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index e38260116099f..a12e4a080676b 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -2164,18 +2164,17 @@ RValue CodeGenFunction::emitRotate(const CallExpr *E, bool IsRotateRight) { } // Diagnose misaligned copies (memmove/memcpy) of source types that contain -// capabilities to a dst buffer that is less than capability aligned. -// This can result in tags being lost at runtime if the buffer is not actually -// capability aligned. Furthermore, if the user adds a __builtin_assume_aligned() -// or a cast to a capability we can assume it is capability aligned an use -// csc/clc if the memcpy()/memmove() is expanded inline. +// capabilities to a dst buffer that is less than capability aligned. This can +// result in tags being lost at runtime if the buffer is not actually capability +// aligned. Another benefit of this diagnostic is that it can cause the the user +// to add __builtin_assume_aligned() or a cast to a capability. This allows us +// to potentially expand the memcpy()/memmove() inline. // TODO: maybe there needs to be an attribute __memmove_like__ or similar to // indicate that a function behaves like memmove/memcpy and we can use that // to diagnose unaligned copies. -static void -diagnoseMisalignedCapabiliyCopyDest(CodeGenFunction &CGF, StringRef Function, - const Expr *Src, const CharUnits DstAlignCU, - AnyMemTransferInst *MemInst = nullptr) { +static void checkCapabilityCopy(CodeGenFunction &CGF, StringRef Function, + const Expr *Src, const CharUnits DstAlignCU, + AnyMemTransferInst *MemInst = nullptr) { // we want the real type not the implicit conversion to void* // TODO: ignore the first explicit cast to void*? auto UnderlyingSrcTy = Src->IgnoreParenImpCasts()->getType(); @@ -2187,18 +2186,15 @@ diagnoseMisalignedCapabiliyCopyDest(CodeGenFunction &CGF, StringRef Function, if (!Ctx.containsCapabilities(UnderlyingSrcTy)) return; - // Add a must_preserve_cheri_tags attribute to the memcpy/memmove - // intrinsic to ensure that the backend will not lower it to an inlined - // sequence of 1/2/4/8 byte loads and stores which would strip the tag bits. - // TODO: a clc/csc that works on unaligned data but traps for a csc - // with a tagged value and unaligned address could also prevent tags - // from being lost. + // If we have a memory intrinsic, we let the backend diagnose this issue + // since the clang frontend rarely has enough information to correctly infer + // the alignment. if (MemInst) { - // If we have a memory intrinsic let the backend diagnose this issue: - // First, tell the backend that this copy must preserve tags - MemInst->addAttribute(llvm::AttributeList::FunctionIndex, - llvm::Attribute::MustPreserveCheriTags); - // And also tell it what the underlying type was for improved diagnostics. + // No need to diagnose anything if we aren't preserving tags. + if (MemInst->shouldPreserveCheriTags() == PreserveCheriTags::Unnecessary) + return; + // Add a "frontend-memtransfer-type" attribute to the intrinsic + // to ensure that the backend can diagnose misaligned capability copies. std::string TypeName = UnderlyingSrcTy.getAsString(); std::string CanonicalStr = UnderlyingSrcTy.getCanonicalType().getAsString(); if (CanonicalStr != TypeName) @@ -2255,23 +2251,20 @@ diagnoseMisalignedCapabiliyCopyDest(CodeGenFunction &CGF, StringRef Function, } } -static void diagnoseMisalignedCapabiliyCopyDest(CodeGenFunction &CGF, - StringRef Function, - const Expr *Src, CallInst *CI) { +static void checkCapabilityCopy(CodeGenFunction &CGF, StringRef Function, + const Expr *Src, CallInst *CI) { AnyMemTransferInst *MemInst = cast(CI); - diagnoseMisalignedCapabiliyCopyDest( - CGF, Function, Src, CharUnits::fromQuantity(MemInst->getDestAlignment()), - MemInst); + checkCapabilityCopy(CGF, Function, Src, + CharUnits::fromQuantity(MemInst->getDestAlignment()), + MemInst); } -static void diagnoseMisalignedCapabiliyCopyDest(CodeGenFunction &CGF, - StringRef Function, - const Expr *Src, - const Expr *Dst) { +static void checkCapabilityCopy(CodeGenFunction &CGF, StringRef Function, + const Expr *Src, const Expr *Dst) { auto UnderlyingDstTy = QualType( Dst->IgnoreImpCasts()->getType()->getPointeeOrArrayElementType(), 0); - diagnoseMisalignedCapabiliyCopyDest( - CGF, Function, Src, CGF.CGM.getNaturalTypeAlignment(UnderlyingDstTy)); + checkCapabilityCopy(CGF, Function, Src, + CGF.CGM.getNaturalTypeAlignment(UnderlyingDstTy)); } // Map math builtins for long-double to f128 version. @@ -3490,9 +3483,11 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, E->getArg(0)->getExprLoc(), FD, 0); EmitNonNullArgCheck(RValue::get(Src.getPointer()), E->getArg(1)->getType(), E->getArg(1)->getExprLoc(), FD, 1); - auto CI = Builder.CreateMemCpy(Dest, Src, SizeVal, - llvm::PreserveCheriTags::TODO, false); - diagnoseMisalignedCapabiliyCopyDest(*this, "memcpy", E->getArg(1), CI); + auto CI = Builder.CreateMemCpy( + Dest, Src, SizeVal, + getTypes().copyShouldPreserveTags(E->getArg(0), E->getArg(1), SizeVal), + false); + checkCapabilityCopy(*this, "memcpy", E->getArg(1), CI); if (BuiltinID == Builtin::BImempcpy || BuiltinID == Builtin::BI__builtin_mempcpy) return RValue::get(Builder.CreateInBoundsGEP(Dest.getElementType(), @@ -3511,7 +3506,10 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, E->getArg(0)->getExprLoc(), FD, 0); EmitNonNullArgCheck(RValue::get(Src.getPointer()), E->getArg(1)->getType(), E->getArg(1)->getExprLoc(), FD, 1); - Builder.CreateMemCpyInline(Dest, Src, Size); + Builder.CreateMemCpyInline( + Dest, Src, Size, + getTypes().copyShouldPreserveTags(E->getArg(0), E->getArg(1), + CharUnits::fromQuantity(Size))); return RValue::get(nullptr); } @@ -3524,23 +3522,23 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, Expr::EvalResult SizeResult, DstSizeResult; if (!E->getArg(2)->EvaluateAsInt(SizeResult, CGM.getContext()) || !E->getArg(3)->EvaluateAsInt(DstSizeResult, CGM.getContext())) { - diagnoseMisalignedCapabiliyCopyDest(*this, "__memcpy_chk", E->getArg(1), - E->getArg(0)); + checkCapabilityCopy(*this, "__memcpy_chk", E->getArg(1), E->getArg(0)); break; } llvm::APSInt Size = SizeResult.Val.getInt(); llvm::APSInt DstSize = DstSizeResult.Val.getInt(); if (Size.ugt(DstSize)) { - diagnoseMisalignedCapabiliyCopyDest(*this, "__memcpy_chk", E->getArg(1), - E->getArg(0)); + checkCapabilityCopy(*this, "__memcpy_chk", E->getArg(1), E->getArg(0)); break; } Address Dest = EmitPointerWithAlignment(E->getArg(0)); Address Src = EmitPointerWithAlignment(E->getArg(1)); Value *SizeVal = llvm::ConstantInt::get(Builder.getContext(), Size); - auto CI = Builder.CreateMemCpy(Dest, Src, SizeVal, - llvm::PreserveCheriTags::TODO, false); - diagnoseMisalignedCapabiliyCopyDest(*this, "memcpy", E->getArg(1), CI); + auto CI = Builder.CreateMemCpy( + Dest, Src, SizeVal, + getTypes().copyShouldPreserveTags(E->getArg(0), E->getArg(1), SizeVal), + false); + checkCapabilityCopy(*this, "memcpy", E->getArg(1), CI); return RValue::get(Dest.getPointer(), Dest.getAlignment().getQuantity()); } @@ -3559,23 +3557,23 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, Expr::EvalResult SizeResult, DstSizeResult; if (!E->getArg(2)->EvaluateAsInt(SizeResult, CGM.getContext()) || !E->getArg(3)->EvaluateAsInt(DstSizeResult, CGM.getContext())) { - diagnoseMisalignedCapabiliyCopyDest(*this, "__memmove_chk", E->getArg(1), - E->getArg(0)); + checkCapabilityCopy(*this, "__memmove_chk", E->getArg(1), E->getArg(0)); break; } llvm::APSInt Size = SizeResult.Val.getInt(); llvm::APSInt DstSize = DstSizeResult.Val.getInt(); if (Size.ugt(DstSize)) { - diagnoseMisalignedCapabiliyCopyDest(*this, "__memmove_chk", E->getArg(1), - E->getArg(0)); + checkCapabilityCopy(*this, "__memmove_chk", E->getArg(1), E->getArg(0)); break; } Address Dest = EmitPointerWithAlignment(E->getArg(0)); Address Src = EmitPointerWithAlignment(E->getArg(1)); Value *SizeVal = llvm::ConstantInt::get(Builder.getContext(), Size); - auto CI = Builder.CreateMemMove(Dest, Src, SizeVal, - llvm::PreserveCheriTags::TODO, false); - diagnoseMisalignedCapabiliyCopyDest(*this, "memmove", E->getArg(1), CI); + auto CI = Builder.CreateMemMove( + Dest, Src, SizeVal, + getTypes().copyShouldPreserveTags(E->getArg(0), E->getArg(1), SizeVal), + false); + checkCapabilityCopy(*this, "memmove", E->getArg(1), CI); return RValue::get(Dest.getPointer(), Dest.getAlignment().getQuantity()); } @@ -3588,9 +3586,11 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, E->getArg(0)->getExprLoc(), FD, 0); EmitNonNullArgCheck(RValue::get(Src.getPointer()), E->getArg(1)->getType(), E->getArg(1)->getExprLoc(), FD, 1); - auto CI = Builder.CreateMemMove(Dest, Src, SizeVal, - llvm::PreserveCheriTags::TODO, false); - diagnoseMisalignedCapabiliyCopyDest(*this, "memmove", E->getArg(1), CI); + auto CI = Builder.CreateMemMove( + Dest, Src, SizeVal, + getTypes().copyShouldPreserveTags(E->getArg(0), E->getArg(1), SizeVal), + false); + checkCapabilityCopy(*this, "memmove", E->getArg(1), CI); return RValue::get(Dest.getPointer(), Dest.getAlignment().getQuantity()); } case Builtin::BImemset: diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index c665c8569d988..53e0c73fc11d8 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -980,10 +980,13 @@ namespace { LValue SrcLV = CGF.MakeNaturalAlignAddrLValue(SrcPtr, RecordTy); LValue Src = CGF.EmitLValueForFieldInitialization(SrcLV, FirstField); + // We can pass EffectiveTypeKnown=true since this a C++ field copy. + auto PreserveTags = CGF.getTypes().copyShouldPreserveTagsForPointee( + RecordTy, /*EffectiveTypeKnown=*/true, MemcpySize); emitMemcpyIR( Dest.isBitField() ? Dest.getBitFieldAddress() : Dest.getAddress(CGF), Src.isBitField() ? Src.getBitFieldAddress() : Src.getAddress(CGF), - MemcpySize, llvm::PreserveCheriTags::TODO); + MemcpySize, PreserveTags); reset(); } diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index 22d9c0058f077..6de4da6c4c1e2 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -1265,12 +1265,18 @@ static void emitStoresForConstant(CodeGenModule &CGM, const VarDecl &D, } } - // Copy from a global. + // Copy from a global (and therefore the effective type of the variable is + // known). + auto PreserveTags = + IsAutoInit || !ContainsCaps + ? llvm::PreserveCheriTags::Unnecessary + : CGM.getTypes().copyShouldPreserveTagsForPointee( + D.getType(), /*EffectiveTypeKnown=*/true, SizeVal); auto *I = Builder.CreateMemCpy(Loc, createUnnamedGlobalForMemcpyFrom( CGM, D, Builder, constant, Loc.getAlignment()), - SizeVal, llvm::PreserveCheriTags::TODO, isVolatile); + SizeVal, PreserveTags, isVolatile); if (IsAutoInit) I->addAnnotationMetadata("auto-init"); } @@ -1799,11 +1805,13 @@ void CodeGenFunction::emitZeroOrPatternForAutoVarInit(QualType type, llvm::PHINode *Cur = Builder.CreatePHI(Begin.getType(), 2, "vla.cur"); Cur->addIncoming(Begin.getPointer(), OriginBB); CharUnits CurAlign = Loc.getAlignment().alignmentOfArrayElement(EltSize); + // Pattern init never writes valid tags, so we can pass + // PreserveCheriTags::Unnecessary to the CreateMemCpy() call auto *I = Builder.CreateMemCpy( Address(Cur, CurAlign), createUnnamedGlobalForMemcpyFrom(CGM, D, Builder, Constant, ConstantAlign), - BaseSizeInChars, llvm::PreserveCheriTags::TODO, isVolatile); + BaseSizeInChars, llvm::PreserveCheriTags::Unnecessary, isVolatile); I->addAnnotationMetadata("auto-init"); llvm::Value *Next = Builder.CreateInBoundsGEP(Int8Ty, Cur, BaseSizeInChars, "vla.next"); diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp index 5a1bbbef20123..30f14f2204c0a 100644 --- a/clang/lib/CodeGen/CGExprAgg.cpp +++ b/clang/lib/CodeGen/CGExprAgg.cpp @@ -747,7 +747,9 @@ void AggExprEmitter::VisitCastExpr(CastExpr *E) { llvm::Value *SizeVal = llvm::ConstantInt::get( CGF.SizeTy, CGF.getContext().getTypeSizeInChars(E->getType()).getQuantity()); - Builder.CreateMemCpy(DestAddress, SourceAddress, SizeVal); + Builder.CreateMemCpy( + DestAddress, SourceAddress, SizeVal, + CGF.getTypes().copyShouldPreserveTags(E, E->getSubExpr(), SizeVal)); break; } @@ -2168,9 +2170,13 @@ void CodeGenFunction::EmitAggregateCopy(LValue Dest, LValue Src, QualType Ty, } } } - - auto Inst = Builder.CreateMemCpy(DestPtr, SrcPtr, SizeVal, - llvm::PreserveCheriTags::TODO, isVolatile); + // Note: this is used for expressions such as x = y, and not memcpy() calls, + // so according to C2x 6.5 "the effective type of the object is simply + // the type of the lvalue used for the access." + auto PreserveTags = getTypes().copyShouldPreserveTagsForPointee( + Ty, /*EffectiveTypeKnown=*/true, SizeVal); + auto Inst = + Builder.CreateMemCpy(DestPtr, SrcPtr, SizeVal, PreserveTags, isVolatile); // Determine the metadata to describe the position of any padding in this // memcpy, as well as the TBAA tags for the members of the struct, in case diff --git a/clang/lib/CodeGen/CodeGenTypes.cpp b/clang/lib/CodeGen/CodeGenTypes.cpp index 08c723a023572..89b480db65d1b 100644 --- a/clang/lib/CodeGen/CodeGenTypes.cpp +++ b/clang/lib/CodeGen/CodeGenTypes.cpp @@ -952,6 +952,134 @@ bool CodeGenTypes::isZeroInitializable(QualType T) { return true; } +static bool copiesAtMostTypeSize(const QualType Ty, const ASTContext &Context, + Optional Size) { + if (!Size) + return false; + auto TypeSize = Context.getTypeSizeInCharsIfKnown(Ty); + return TypeSize && Size <= TypeSize; +} + +llvm::PreserveCheriTags +CodeGenTypes::copyShouldPreserveTags(const Expr *DestPtr, const Expr *SrcPtr, + Optional Size) { + // Don't add the no_preserve_tags/must_preserve_tags attribute for non-CHERI + // targets to avoid changing tests and to avoid compile-time impact. + if (!Context.getTargetInfo().SupportsCapabilities()) + return llvm::PreserveCheriTags::Unknown; + auto DstPreserve = copyShouldPreserveTags(DestPtr, Size); + if (DstPreserve == llvm::PreserveCheriTags::Unnecessary) { + // If the destination does not need to preserve tags, we know that we don't + // need to retain tags even if the source is a capability type. + return llvm::PreserveCheriTags::Unnecessary; + } + assert(DstPreserve == llvm::PreserveCheriTags::Required || + DstPreserve == llvm::PreserveCheriTags::Unknown); + auto SrcPreserve = copyShouldPreserveTags(SrcPtr, Size); + if (SrcPreserve == llvm::PreserveCheriTags::Unnecessary) { + // If the copy source never contains capabilities, we don't need to retain + // tags even if the destination is contains capabilities. + return llvm::PreserveCheriTags::Unnecessary; + } + if (SrcPreserve == llvm::PreserveCheriTags::Required) { + // If the source is capability-bearing but the destination is Unknown, we + // assume that we should be preserving tags. + // TODO: if this is too conservative, we could probably use the destination + // value (Unknown or Required) instead, but that will result in fewer + // (potentially) underaligned copies being diagnosed. + return llvm::PreserveCheriTags::Required; + } + assert(SrcPreserve == llvm::PreserveCheriTags::Unknown); + // Source preservation kind is unknown -> use the destination value. + return DstPreserve; +} + +llvm::PreserveCheriTags +CodeGenTypes::copyShouldPreserveTags(const Expr *E, Optional Size) { + assert(E->getType()->isAnyPointerType()); + // Ignore the implicit cast to void* for the memcpy call. + // Note: IgnoreParenImpCasts() might strip function/array-to-pointer decay + // so we can't always call getPointeeType(). + QualType Ty = E->IgnoreParenImpCasts()->getType(); + if (Ty->isAnyPointerType()) + Ty = Ty->getPointeeType(); + // TODO: Find the underlying VarDecl to improve diagnostics + const VarDecl *UnderlyingVar = nullptr; + // TODO: this assertion may be overly aggressive. + assert((!UnderlyingVar || UnderlyingVar->getType() == Ty) && + "Passed wrong VarDecl?"); + // If we have an underlying VarDecl, we can assume that the dynamic type of + // the object is known and can perform more detailed analysis. + return copyShouldPreserveTagsForPointee(Ty, UnderlyingVar != nullptr, Size); +} + +llvm::PreserveCheriTags CodeGenTypes::copyShouldPreserveTagsForPointee( + QualType Pointee, bool EffectiveTypeKnown, Optional Size) { + // Don't add the no_preserve_tags/must_preserve_tags attribute for non-CHERI + // targets to avoid changing tests and to avoid compile-time impact. + if (!Context.getTargetInfo().SupportsCapabilities()) + return llvm::PreserveCheriTags::Unknown; + assert(!Pointee.isNull() && "Should only be called for valid types"); + if (Context.containsCapabilities(Pointee)) { + // If this is a capability type or a structure/union containing + // capabilities, we clearly need to retain tag bits when copying. + // TODO: we should consider removing the require attribute since the + // backends have to be conservative on a missing no_preserve_cheri_tags + // anyway, so not having the attribute should be equivalent to "Required". + return llvm::PreserveCheriTags::Required; + } else if (!EffectiveTypeKnown) { + // If we don't know the underlying type of the copy (i.e. we just have a + // pointer without any additional context), we cannot assume that the actual + // object at that location matches the type of the pointer, so we have to + // conservatively return Unknown if containsCapabilities() was false. + // This is needed because C's strict aliasing rules are not based on the + // type of the pointer but rather based on the type of what was last stored. + // See C2x 6.5: + // "If a value is copied into an object having no declared type using memcpy + // or memmove, or is copied as an array of character type, then the + // effective type of the modified object for that access and for subsequent + // accesses that do not modify the value is the effective type of the object + // from which the value is copied, if it has one. For all other accesses to + // an object having no declared type, the effective type of the object is + // simply the type of the lvalue used for the access." + // And importantly: "Allocated objects have no declared type.", so unless + // we know what the underlying VarDecl is, we cannot use the type of the + // expression to determine whether it could hold tags. + return llvm::PreserveCheriTags::Unknown; + } else if (Pointee->isIncompleteType()) { + // We don't know if incomplete types contain capabilities, so be + // conservative and assume that they might. + return llvm::PreserveCheriTags::Unknown; + } + + if (!copiesAtMostTypeSize(Pointee, Context, Size)) { + // We can only mark the copy as non-tag-preserving is we know the size + // remains within the bounds of the copied type. For example with C++ + // classes, there could be a subclass that contains capabilities, so we have + // to be conservative unless the class is final. Similarly, we have to be + // conservative in C as the struct could be embedded inside another + // structure and the copy could affect adjacent capability data. + return llvm::PreserveCheriTags::Unknown; + } + + if (Context.cannotContainCapabilities(Pointee)) { + // If the type cannot contain capabilities and we are copying at most + // sizeof(type), then we can use a non-tag-preserving copy. + // This is useful to optimize cases such as assignment of something like + // `struct { long a; long b; }`: + // In this case the type is as large as a capability but the known + // alignment is only 4/8 bytes, so the backend needs to be conservative and + // assume that the memcpy might copy capabilities. By annotating the + // memcpy/memmove intrinsic, the backend can emit non-capability loads + // inline instead of having to call the library function. + return llvm::PreserveCheriTags::Unnecessary; + } + // This is some other type that might contain capabilities (e.g. char[]) or + // a copy that covers a larger region than the size of the underlying type. + // In this case, fall back to the default (retaining tags if possible). + return llvm::PreserveCheriTags::Unknown; +} + bool CodeGenTypes::isZeroInitializable(const RecordDecl *RD) { return getCGRecordLayout(RD).isZeroInitializable(); } diff --git a/clang/lib/CodeGen/CodeGenTypes.h b/clang/lib/CodeGen/CodeGenTypes.h index 99bf091606a22..23cc0e8d72996 100644 --- a/clang/lib/CodeGen/CodeGenTypes.h +++ b/clang/lib/CodeGen/CodeGenTypes.h @@ -25,6 +25,7 @@ class DataLayout; class Type; class LLVMContext; class StructType; +enum class PreserveCheriTags; } namespace clang { @@ -303,6 +304,34 @@ class CodeGenTypes { /// zero-initialized (in the C++ sense) with an LLVM zeroinitializer. bool isZeroInitializable(const RecordDecl *RD); + /// Return whether a copy (e.g. memcpy/memmove) where the destination is a + /// pointer to DestType may need to preserve CHERI tags (i.e. needs to call + /// the copy function at run time if the alignment is not greater than the + /// alignment of the destination buffer. + /// This function attempts to determine the effective type of the source and + /// destination values (C2x 6.5p6) by checking for the underlying storage + /// (e.g. a referenced VarDecl) and performing a more conservative analysis + /// if this is not the case. + llvm::PreserveCheriTags copyShouldPreserveTags(const Expr *DestPtr, + const Expr *SrcPtr, + Optional Size); + llvm::PreserveCheriTags copyShouldPreserveTags(const Expr *DestPtr, + const Expr *SrcPtr, + const llvm::Value *Size) { + return copyShouldPreserveTags(DestPtr, SrcPtr, copySizeInCharUnits(Size)); + } + /// Same as the copyShouldPreserveTags(), but expects CopyTy to be the + /// pointee type rather than the type of the buffer pointer. + llvm::PreserveCheriTags + copyShouldPreserveTagsForPointee(QualType CopyTy, bool EffectiveTypeKnown, + Optional Size); + llvm::PreserveCheriTags + copyShouldPreserveTagsForPointee(QualType CopyTy, bool EffectiveTypeKnown, + const llvm::Value *Size) { + return copyShouldPreserveTagsForPointee(CopyTy, EffectiveTypeKnown, + copySizeInCharUnits(Size)); + } + bool isRecordLayoutComplete(const Type *Ty) const; bool noRecordsBeingLaidOut() const { return RecordsBeingLaidOut.empty(); @@ -311,6 +340,14 @@ class CodeGenTypes { return RecordsBeingLaidOut.count(Ty); } +private: + llvm::PreserveCheriTags copyShouldPreserveTags(const Expr *E, + Optional Size); + Optional copySizeInCharUnits(const llvm::Value *Size) { + if (auto *ConstSize = llvm::dyn_cast_or_null(Size)) + return CharUnits::fromQuantity(ConstSize->getSExtValue()); + return None; + } }; } // end namespace CodeGen diff --git a/clang/test/CodeGen/cheri/cheri-inregs-param-info.c b/clang/test/CodeGen/cheri/cheri-inregs-param-info.c index 91cb39587c813..1f9006ccc3b82 100644 --- a/clang/test/CodeGen/cheri/cheri-inregs-param-info.c +++ b/clang/test/CodeGen/cheri/cheri-inregs-param-info.c @@ -1,6 +1,7 @@ // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --function-signature -// RUN: %cheri_purecap_cc1 -emit-llvm -O2 %s -o - | FileCheck %s -// RUN: %cheri_purecap_cc1 -S -O2 %s -o - | FileCheck %s --check-prefix ASM +// REQUIRES: mips-registered-target +// RUN: %cheri_purecap_cc1 -emit-llvm -O2 %s -o - -verify | FileCheck %s +// RUN: %cheri_purecap_cc1 -S -O2 %s -o - -verify | FileCheck %s --check-prefix ASM // This used to crash with an assertion: // void llvm::CCState::getInRegsParamInfo(unsigned int, unsigned int &, unsigned int &) const: Assertion `InRegsParamRecordIndex < ByValRegs.size() && "Wrong ByVal parameter index"' failed. @@ -8,18 +9,22 @@ // ASM: clcbi $c12, %capcall20(memcpy)( typedef struct { int err_msg[1024]; } Dwarf_Error; extern Dwarf_Error a; -void fn2(); +void fn2(); // expected-note{{candidate function declaration needs parameter types}} // CHECK-LABEL: define {{[^@]+}}@fn1 // CHECK-SAME: () local_unnamed_addr addrspace(200) #[[ATTR0:[0-9]+]] { // CHECK-NEXT: entry: // CHECK-NEXT: [[BYVAL_TEMP:%.*]] = alloca [[STRUCT_DWARF_ERROR:%.*]], align 8, addrspace(200) // CHECK-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_DWARF_ERROR]] addrspace(200)* [[BYVAL_TEMP]] to i8 addrspace(200)* // CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 4096, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR3:[0-9]+]] -// CHECK-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(4096) [[TMP0]], i8 addrspace(200)* noundef nonnull align 4 dereferenceable(4096) bitcast ([[STRUCT_DWARF_ERROR]] addrspace(200)* @a to i8 addrspace(200)*), i64 4096, i1 false), !tbaa.struct !2 +// CHECK-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(4096) [[TMP0]], i8 addrspace(200)* noundef nonnull align 4 dereferenceable(4096) bitcast ([[STRUCT_DWARF_ERROR]] addrspace(200)* @a to i8 addrspace(200)*), i64 4096, i1 false) #[[ATTR4:[0-9]+]], !tbaa.struct !2 // CHECK-NEXT: tail call void bitcast (void (...) addrspace(200)* @fn2 to void ([[STRUCT_DWARF_ERROR]] addrspace(200)*) addrspace(200)*)([[STRUCT_DWARF_ERROR]] addrspace(200)* nonnull byval([[STRUCT_DWARF_ERROR]]) align 8 [[BYVAL_TEMP]]) #[[ATTR3]] // CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 4096, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR3]] // CHECK-NEXT: ret void // void fn1() { - fn2(a); + fn2(a); // expected-warning{{call to function 'fn2' with no prototype may lead to run-time stack corruption}} + // expected-note@-1{{Calling functions without prototypes is dangerous}} } + +// UTC_ARGS: --disable +// CHECK: attributes #[[ATTR4]] = { no_preserve_cheri_tags } diff --git a/clang/test/CodeGen/cheri/cheri-struct-return-value.c b/clang/test/CodeGen/cheri/cheri-struct-return-value.c index 6ee6b7bed475f..45cf0d98a6445 100644 --- a/clang/test/CodeGen/cheri/cheri-struct-return-value.c +++ b/clang/test/CodeGen/cheri/cheri-struct-return-value.c @@ -1,5 +1,6 @@ // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --function-signature // REQUIRES: mips-registered-target +// FIXME: this should not be a clang test since it's checking ASM output // RUN: %cheri_purecap_cc1 -std=c11 -O2 -emit-llvm -o - %s | %cheri_FileCheck %s // RUN: %cheri_purecap_cc1 -mllvm -cheri-cap-table-abi=pcrel -std=c11 -O2 -S -o - %s | %cheri_FileCheck -check-prefixes=ASM,CHERI128-ASM %s @@ -141,18 +142,23 @@ typedef struct { // CHECK-SAME: ([[STRUCT_THREELONGS:%.*]] addrspace(200)* noalias nocapture sret([[STRUCT_THREELONGS]]) align 8 [[AGG_RESULT:%.*]]) local_unnamed_addr addrspace(200) #[[ATTR3:[0-9]+]] { // CHECK-NEXT: entry: // CHECK-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_THREELONGS]] addrspace(200)* [[AGG_RESULT]] to i8 addrspace(200)* -// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(24) [[TMP0]], i8 addrspace(200)* noundef nonnull align 8 dereferenceable(24) bitcast ([[STRUCT_THREELONGS]] addrspace(200)* @__const.three_longs.t to i8 addrspace(200)*), i64 24, i1 false) +// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(24) [[TMP0]], i8 addrspace(200)* noundef nonnull align 8 dereferenceable(24) bitcast ([[STRUCT_THREELONGS]] addrspace(200)* @__const.three_longs.t to i8 addrspace(200)*), i64 24, i1 false) #[[ATTR6:[0-9]+]] // CHECK-NEXT: ret void // ThreeLongs three_longs() { ThreeLongs t = { 1, 2, 3 }; return t; // ASM-LABEL: three_longs - // Clang uses a memcpy from a global for cheri128. - // For cheri256 it inlined the memcpy from a global (since it is smaller than 1 cap) - // CHERI128-ASM: clcbi $c4, %captab20(.L__const.three_longs.t)($c{{.+}}) - // CHERI128-ASM: clcbi $c12, %capcall20(memcpy)($c{{.+}}) - + // Clang uses a memcpy from a global for cheri128 (and it can be inlined despite being less than cap aligned + // since longs don't contain tags): + // CHERI128-ASM: clcbi $c1, %captab20(.L__const.three_longs.t)($c1) + // CHERI128-ASM-NEXT: cld $1, $zero, 16($c1) + // CHERI128-ASM-NEXT: cld $2, $zero, 8($c1) + // CHERI128-ASM-NEXT: cld $3, $zero, 0($c1) + // CHERI128-ASM-NEXT: csd $1, $zero, 16($c3) + // CHERI128-ASM-NEXT: csd $2, $zero, 8($c3) + // CHERI128-ASM-NEXT: cjr $c17 + // CHERI128-ASM-NEXT: csd $3, $zero, 0($c3) } typedef struct { @@ -193,7 +199,7 @@ extern IntAndLong extern_int_and_long(); // CHECK-LABEL: define {{[^@]+}}@read_int_and_long_1 // CHECK-SAME: () local_unnamed_addr addrspace(200) #[[ATTR4:[0-9]+]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[CALL:%.*]] = tail call inreg { i64, i64 } bitcast ({ i64, i64 } (...) addrspace(200)* @extern_int_and_long to { i64, i64 } () addrspace(200)*)() #[[ATTR6:[0-9]+]] +// CHECK-NEXT: [[CALL:%.*]] = tail call inreg { i64, i64 } bitcast ({ i64, i64 } (...) addrspace(200)* @extern_int_and_long to { i64, i64 } () addrspace(200)*)() #[[ATTR7:[0-9]+]] // CHECK-NEXT: [[TMP0:%.*]] = extractvalue { i64, i64 } [[CALL]], 0 // CHECK-NEXT: [[COERCE_SROA_0_0_EXTRACT_SHIFT:%.*]] = lshr i64 [[TMP0]], 32 // CHECK-NEXT: [[COERCE_SROA_0_0_EXTRACT_TRUNC:%.*]] = trunc i64 [[COERCE_SROA_0_0_EXTRACT_SHIFT]] to i32 @@ -217,7 +223,7 @@ int read_int_and_long_1() { // CHECK-LABEL: define {{[^@]+}}@read_int_and_long_2 // CHECK-SAME: () local_unnamed_addr addrspace(200) #[[ATTR4]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[CALL:%.*]] = tail call inreg { i64, i64 } bitcast ({ i64, i64 } (...) addrspace(200)* @extern_int_and_long to { i64, i64 } () addrspace(200)*)() #[[ATTR6]] +// CHECK-NEXT: [[CALL:%.*]] = tail call inreg { i64, i64 } bitcast ({ i64, i64 } (...) addrspace(200)* @extern_int_and_long to { i64, i64 } () addrspace(200)*)() #[[ATTR7]] // CHECK-NEXT: [[TMP0:%.*]] = extractvalue { i64, i64 } [[CALL]], 1 // CHECK-NEXT: ret i64 [[TMP0]] // @@ -607,3 +613,6 @@ Int128AndCap int128_and_cap(Int128AndCap in) { // ASM-NEXT: cjr $c17 // ASM-NEXT: csc $c1, $zero, 16($c3) } + +// UTC_ARGS: --disabble +// CHECK: attributes #[[ATTR6]] = { no_preserve_cheri_tags } diff --git a/clang/test/CodeGen/cheri/memcpy-unaligned.c b/clang/test/CodeGen/cheri/memcpy-unaligned.c index 6924b93d7b8d4..f5bfb176eaaf9 100644 --- a/clang/test/CodeGen/cheri/memcpy-unaligned.c +++ b/clang/test/CodeGen/cheri/memcpy-unaligned.c @@ -1,31 +1,36 @@ // REQUIRES: mips-registered-target +// Note: some alignment attributes are lower than expected since +// __attribute__((align_value(2))) is not propagated to the +// llvm.memcpy intrinsic until later optimization passes. // RUN: %cheri128_purecap_cc1 -O0 -o - -emit-llvm %s -w | FileCheck %s -// RUN: %cheri128_purecap_cc1 -DBUILTIN -O0 -o - -emit-llvm %s -w | FileCheck %s // This diagnostic is disabled at -O0 -> must check at -O2 // RUN: %cheri128_purecap_cc1 -debug-info-kind=standalone -O2 -S -o /dev/null %s -verify -// RUN: %cheri128_purecap_cc1 -debug-info-kind=standalone -DBUILTIN -O2 -S -o /dev/null %s -verify // If we are using -Werror this warning should not fail the build, only if it is explicitly added: // RUN: %cheri128_purecap_cc1 -O2 -o /dev/null -S %s -Werror 2> /dev/null // RUN: not %cheri128_purecap_cc1 -O2 -o /dev/null -S %s -Werror=cheri-misaligned 2>/dev/null -#ifdef BUILTIN -#define memcpy __builtin_memcpy -#define memmove __builtin_memmove -#else -void * memcpy(void *, const void *, unsigned long); -void * memmove(void *, const void *, unsigned long); -#endif +void *memcpy(void *, const void *, unsigned long); +void *memmove(void *, const void *, unsigned long); typedef unsigned __intcap a; void *b; - +typedef void *__attribute__((align_value(2))) align2_ptr; +typedef void *__attribute__((align_value(4))) align4_ptr; +typedef void *__attribute__((align_value(8))) align8_ptr; +typedef void *__attribute__((align_value(sizeof(void *__capability)))) align_cap_ptr; unsigned __intcap get_cap(void); -void test_dst_unliagned_src_cap_memcpy(void* align1, short* align2, int* align4, long* align8, void** align_cap, a* src) { - // CHECK-LABEL @test_dst_unliagned_src_cap_memcpy( +void test_dst_unliagned_src_cap_memcpy(void *align1, align2_ptr align2, align4_ptr align4, align8_ptr align8, align_cap_ptr align_cap, a *src) { + // CHECK-LABEL: @test_dst_unliagned_src_cap_memcpy( + // CHECK-SAME: i8 addrspace(200)* %align1, + // CHECK-SAME: i8 addrspace(200)* align 2 %align2, + // CHECK-SAME: i8 addrspace(200)* align 4 %align4, + // CHECK-SAME: i8 addrspace(200)* align 8 %align8, + // CHECK-SAME: i8 addrspace(200)* align 16 %align_cap, + // CHECK-SAME: i8 addrspace(200)* addrspace(200)* %src) memcpy(align1, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} @@ -37,23 +42,24 @@ void test_dst_unliagned_src_cap_memcpy(void* align1, short* align2, int* align4, // expected-warning@-1{{memcpy operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 2 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memcpy(align4, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 4 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 4 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memcpy(align8, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] - memcpy(align_cap, src, sizeof(*src)); // this is fine! + memcpy(align_cap, src, sizeof(*src)); // this is fine (and must preserve tags) // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 16 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } struct without_cap { @@ -63,85 +69,87 @@ struct without_cap { struct with_cap { struct without_cap a; - void* cap; + void *cap; }; -void test_no_warn_for_non_caps(short* align2, int not_a_cap, unsigned __intcap* capptr, - struct with_cap* struct_with_cap, struct without_cap* struct_without_cap) { - // CHECK-LABEL @test_no_warn_for_non_caps( +void test_no_warn_for_non_caps(short *align2, align2_ptr align2_not_short, int not_a_cap, unsigned __intcap *capptr, + struct with_cap *struct_with_cap, struct without_cap *struct_without_cap) { + // CHECK-LABEL: @test_no_warn_for_non_caps( - memcpy(align2, ¬_a_cap, sizeof(not_a_cap)); // no warning + memcpy(align2, ¬_a_cap, sizeof(not_a_cap)); // no warning // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 4, i1 false) + // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 4, i1 false){{$}} + // FIXME-SAME: [[NO_PRESERVE_TAGS_ATTRIB:#[0-9]+]]{{$}} memcpy(align2, struct_without_cap, sizeof(*struct_without_cap)); // no warning // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 8, i1 false) + // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 8, i1 false){{$}} + // FIXME-SAME: [[NO_PRESERVE_TAGS_ATTRIB:#[0-9]+]]{{$}} memcpy(align2, capptr, sizeof(*capptr)); // expected-warning@-1{{memcpy operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 2 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP:#[0-9]+]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP:#[0-9]+]]{{$}} - memcpy(align2, struct_with_cap, sizeof(*struct_with_cap)); + memcpy(align2_not_short, struct_with_cap, sizeof(*struct_with_cap)); // expected-warning@-1{{memcpy operation with capability argument 'struct with_cap' and underaligned destination (aligned to 2 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 32, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_STRUCT_WITH_CAP:#[0-9]+]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 32, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_STRUCT_WITH_CAP:#[0-9]+]]{{$}} } - -void test_dst_unliagned_src_cap_memmove(void* align1, short* align2, int* align4, long* align8, void** align_cap, a* src) { - // CHECK-LABEL @test_dst_unliagned_src_cap_memcpy( +void test_dst_unliagned_src_cap_memmove(void *align1, align2_ptr align2, align4_ptr align4, align8_ptr align8, align_cap_ptr align_cap, a *src) { + // CHECK-LABEL: @test_dst_unliagned_src_cap_memmove( memmove(align1, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove(align2, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 2 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove(align4, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 4 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 4 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove(align8, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove(align_cap, src, sizeof(*src)); // this is fine! // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 16 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } -#define memcpy_chk(x,y,z) __builtin___memcpy_chk(x,y,z, __builtin_object_size(x,0)) -#define memcpy_chk_inbounds(x,y,z) __builtin___memcpy_chk(x,y,z, z) -#define memmove_chk(x,y,z) __builtin___memmove_chk(x,y,z, __builtin_object_size(x,0)) -#define memmove_chk_inbounds(x,y,z) __builtin___memmove_chk(x,y,z, z) +#define memcpy_chk(x, y, z) __builtin___memcpy_chk(x, y, z, __builtin_object_size(x, 0)) +#define memcpy_chk_inbounds(x, y, z) __builtin___memcpy_chk(x, y, z, z) +#define memmove_chk(x, y, z) __builtin___memmove_chk(x, y, z, __builtin_object_size(x, 0)) +#define memmove_chk_inbounds(x, y, z) __builtin___memmove_chk(x, y, z, z) -void test_memcpy_chk(void* align1, long* align8, void** align_cap, a* src) { - // CHECK-LABEL @test_memcpy_chk( +void test_memcpy_chk(void *align1, align8_ptr align8, void **align_cap, a *src) { + // CHECK-LABEL: @test_memcpy_chk( memcpy_chk(align1, src, sizeof(*src)); // expected-warning@-1{{__memcpy_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call i8 addrspace(200)* @__memcpy_chk( memcpy_chk(align8, src, sizeof(*src)); - // expected-warning@-1{{__memcpy_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} + // expected-warning@-1{{__memcpy_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call i8 addrspace(200)* @__memcpy_chk( @@ -154,103 +162,105 @@ void test_memcpy_chk(void* align1, long* align8, void** align_cap, a* src) { // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memcpy_chk_inbounds(align8, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memcpy_chk_inbounds(align_cap, src, sizeof(*src)); // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 16 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } -void test_memmove_chk(void* align1, long* align8, void** align_cap, a* src) { - // CHECK-LABEL @test_memmove_chk( +void test_memmove_chk(void *align1, align8_ptr align8, void **align_cap, a *src) { + // CHECK-LABEL: @test_memmove_chk( memmove_chk(align1, src, sizeof(*src)); // expected-warning@-1{{__memmove_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call i8 addrspace(200)* @__memmove_chk( memmove_chk(align8, src, sizeof(*src)); - // expected-warning@-1{{__memmove_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} + // expected-warning@-1{{__memmove_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} + // FIXME: diagnostic should say 8 bytes, but we don't handle the align_value // CHECK: call i8 addrspace(200)* @__memmove_chk( memmove_chk(align_cap, src, sizeof(*src)); // no warning // CHECK: call i8 addrspace(200)* @__memmove_chk( - // these are always turned into a memmove: memmove_chk_inbounds(align1, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove_chk_inbounds(align8, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove_chk_inbounds(align_cap, src, sizeof(*src)); // no warning // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 16 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } -void test_builtin_assume_aligned_fix_1(long *align8, long *align8_again, char *align1, char *align1_again, unsigned __intcap* src) { +void test_builtin_assume_aligned_fix_1(align8_ptr align8, align8_ptr align8_again, char *align1, char *align1_again, unsigned __intcap *src) { // CHECK-LABEL: @test_builtin_assume_aligned_fix_1( memcpy(align8, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memmove(align8_again, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] - + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memcpy(align1, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memmove(align1_again, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} } -void test_builtin_assume_aligned_fix_2(long *align8, long *align8_again, char *align1, char *align1_again, unsigned __intcap* src) { +void test_builtin_assume_aligned_fix_2(long *align8, long *align8_again, char *align1, char *align1_again, unsigned __intcap *src) { // CHECK-LABEL: @test_builtin_assume_aligned_fix_2( - // should not warn if we add __builtin_assume_aligned or cast to (u)intcap_t + // should not warn if we add __builtin_assume_aligned or cast to (u)intcap // But this only works at -O1 or higher: memcpy(__builtin_assume_aligned(align8, sizeof(void *)), src, sizeof(*src)); // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memmove(__builtin_assume_aligned(align1, sizeof(void *)), src, sizeof(*src)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memmove((__intcap *)align1_again, src, sizeof(*src)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 16 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} } -void test_builtin_assume_aligned_fix_3(long *align8, unsigned __intcap* src) { +void test_builtin_assume_aligned_fix_3(long *align8, unsigned __intcap *src) { // CHECK-LABEL: @test_builtin_assume_aligned_fix_3( // Check that we inferred align 8 in the warning here: memcpy(__builtin_assume_aligned(align8, sizeof(long)), src, sizeof(*src)); @@ -258,19 +268,18 @@ void test_builtin_assume_aligned_fix_3(long *align8, unsigned __intcap* src) { // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memmove(__builtin_assume_aligned(align8, sizeof(long)), src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} } - extern unsigned __intcap foo_array[10][2]; -void do_stuff(char* buf); +void do_stuff(char *buf); void test_no_crash_with_array(void) { // CHECK-LABEL: @test_no_crash_with_array( char buffer[1234]; @@ -279,11 +288,11 @@ void test_no_crash_with_array(void) { // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 {{.+}}, i64 320, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} do_stuff(buffer); // So that buffer is not optimized away } -void test_builtin_assume_aligned_intermediate_var(char *align1, char *align1_again, a* src) { +void test_builtin_assume_aligned_intermediate_var(char *align1, char *align1_again, a *src) { // CHECK-LABEL: @test_builtin_assume_aligned_intermediate_var( // this should still warn: void *align4 = __builtin_assume_aligned(align1, 4); @@ -292,16 +301,16 @@ void test_builtin_assume_aligned_intermediate_var(char *align1, char *align1_aga // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} void *align32 = __builtin_assume_aligned(align1_again, 32); memcpy(align32, src, sizeof(*src)); // this is fine // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } -void test_builtin_assume_aligned_memmove_intermediate_var(char *align1, char *align1_again, a* src) { +void test_builtin_assume_aligned_memmove_intermediate_var(char *align1, char *align1_again, a *src) { // CHECK-LABEL: @test_builtin_assume_aligned_memmove_intermediate_var( // this should still warn: void *align4 = __builtin_assume_aligned(align1, 4); @@ -310,15 +319,16 @@ void test_builtin_assume_aligned_memmove_intermediate_var(char *align1, char *al // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} void *align32 = __builtin_assume_aligned(align1_again, 32); memmove(align32, src, sizeof(*src)); // this is fine // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } -// CHECK: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_A]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'a' (aka 'unsigned __intcap')" } -// CHECK: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'unsigned __intcap'" } -// CHECK: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_STRUCT_WITH_CAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct with_cap'" } +// FIXME-DAG: attributes [[NO_PRESERVE_TAGS_ATTRIB]] = { no_preserve_cheri_tags } +// CHECK-DAG: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_A]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'a' (aka 'unsigned __intcap')" } +// CHECK-DAG: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_STRUCT_WITH_CAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct with_cap'" } +// CHECK-DAG: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'unsigned __intcap'" } diff --git a/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c b/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c index b5c7888353531..9c2a3f114b73b 100644 --- a/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c +++ b/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c @@ -3,9 +3,8 @@ /// not to contain capabilities. Previously we assumed that all copies >= sizeof(capability) /// can contain capabilities and we therefore fell back to calling memcpy if the /// alignment was less than >= alignof(capability). -/// TODO: include some end-to-end testing to ensure that the frontend and backend changes allow -/// eliding the memcpy() call that we were previously force to make? -// RUN: %riscv64_cheri_purecap_cc1 %s -emit-llvm -o - -O0 | FileCheck %s +// RUN: %riscv64_cheri_purecap_cc1 %s -emit-llvm -o - -O0 | FileCheck %s --check-prefixes=CHECK +// RUN: %riscv64_cheri_purecap_cc1 %s -relaxed-aliasing -emit-llvm -o - -O0 | FileCheck %s --check-prefixes=CHECK #if __has_feature(capabilities) #define CAP_SIZE sizeof(void *__capability) @@ -28,10 +27,12 @@ struct vdso_timehands { void test_binuptime_assign(struct bintime *bt, struct vdso_timehands *th) { *bt = th->th_offset; + // We should always be able add the no_preserve_tags attribute for this assignment + // since this falls under C2x 6.5 "For all other accesses to an object having no declared type, + // the effective type of the object is simply the type of the lvalue used for the access." // CHECK-LABEL: void @test_binuptime_assign( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, - // CHECK-SAME: i64 16, i1 false){{$}} - // FIXME-SAME: i64 16, i1 false) [[NO_PRESERVE_TAGS_ATTR:#[0-9]+]]{{$}} + // CHECK-SAME: i64 16, i1 false) [[NO_PRESERVE_TAGS_ATTR:#[0-9]+]]{{$}} } struct cap_size_buffer { @@ -42,8 +43,7 @@ void test_cap_size_buffer_copy(struct cap_size_buffer *a, struct cap_size_buffer *a = *b; // CHECK-LABEL: void @test_cap_size_buffer_copy( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 4 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 4 {{%[0-9a-zA-Z.]+}}, - // CHECK-SAME: i64 16, i1 false){{$}} - // FIXME-SAME: i64 16, i1 false) [[NO_PRESERVE_TAGS_ATTR]]{{$}} + // CHECK-SAME: i64 16, i1 false) [[NO_PRESERVE_TAGS_ATTR]]{{$}} } union large_union_align_half_cap_size { @@ -54,12 +54,13 @@ union large_union_align_half_cap_size { }; void test_bigger_union_copy(union large_union_align_half_cap_size *a, union large_union_align_half_cap_size *b) { - // No tags in this union -> can inline the memcpy() + // No tags in this union -> can inline the memcpy(). + // Note: we can do this even with -fno-strict-aliasing since this is a direct assignment. + // XXX: Do we need an option to treats underaligned char[] members as potentially tag-bearing? *a = *b; // CHECK-LABEL: void @test_bigger_union_copy( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, - // CHECK-SAME: i64 64, i1 false){{$}} - // FIXME-SAME: i64 64, i1 false) [[NO_PRESERVE_TAGS_ATTR]]{{$}} + // CHECK-SAME: i64 64, i1 false) [[NO_PRESERVE_TAGS_ATTR]]{{$}} } union large_union_with_char_array { @@ -80,7 +81,7 @@ void test_union_with_char_array(union large_union_with_char_array *a, union larg } void test_align_copy_voidptr(void *a, void *b) { - // void* could contain caps but we don't add the attribute and rely on the backend to decide + // void* could contain caps so we don't add the attribute and rely on the backend to decide memcpy(a, b, CAP_SIZE); // CHECK-LABEL: void @test_align_copy_voidptr( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, @@ -89,7 +90,7 @@ void test_align_copy_voidptr(void *a, void *b) { void test_align_copy_charptr(char *a, char *b) { // char* could contain caps since it's (unfortunately) basically the same as void*, - // but again we don't add the attribute and rely on the backend to decide + // so again we don't add the attribute and rely on the backend to decide memcpy(a, b, CAP_SIZE); // CHECK-LABEL: void @test_align_copy_charptr( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, @@ -97,12 +98,12 @@ void test_align_copy_charptr(char *a, char *b) { } void test_align_copy_longptr(long *a, long *b) { - // Note: here we don't infer the no-preserve tags (yet) + // We don't know the effective type of the underlying objects, so we can't add + // no_preserve_cheri_tags despite both pointeee types being non-tag-carrying. memcpy(a, b, CAP_SIZE); // CHECK-LABEL: void @test_align_copy_longptr( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, // CHECK-SAME: i64 16, i1 false){{$}} - // FIXME-SAME: [[NO_PRESERVE_TAGS_ATTR]]{{$}} } #if __has_feature(capabilities) @@ -135,13 +136,17 @@ void test_align_copy_fwd_declared_2(void *a, struct fwddecl *b) { } void test_align_copy_fwd_declared_dst_notag(long *a, struct fwddecl *b) { - // We don't know if src contains capabilities, but the destination can't contain tags + // We don't know if src contains capabilities, and we also can't assume this + // for the destination since we don't know the effective type of the underlying object. // CHECK-LABEL: void @test_align_copy_fwd_declared_dst_notag( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, // CHECK-SAME: i64 16, i1 false){{$}} memcpy(a, b, CAP_SIZE); + // Note: if you look at the assembly output for this call, it + // still uses memcpy despite the attribute. b is only aligned to one byte and + // expanding it would be too costly on an architecture without fast unaligned loads/stores. } // CHECK: attributes #0 = { -// FIXME-DAG: attributes [[NO_PRESERVE_TAGS_ATTR]] = { no_preserve_cheri_tags } +// CHECK-DAG: attributes [[NO_PRESERVE_TAGS_ATTR]] = { no_preserve_cheri_tags } // CHECK-DAG: attributes [[MUST_PRESERVE_TAGS_ATTR]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'unsigned __intcap'" } diff --git a/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c b/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c index 4ae8ba719a9c0..5be072b5d3ea9 100644 --- a/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c +++ b/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c @@ -1,5 +1,6 @@ // REQUIRES: riscv-registered-target -// RUN: %riscv64_cheri_purecap_cc1 %s -o - -emit-llvm | FileCheck %s +// RUN: %riscv64_cheri_purecap_cc1 %s -o - -emit-llvm | FileCheck %s --check-prefixes=CHECK +// RUN: %riscv64_cheri_purecap_cc1 %s -o - -emit-llvm -relaxed-aliasing | FileCheck %s --check-prefixes=CHECK // Diagnostics are only emitted when generating assembly (with optimizations) // RUN: %riscv64_cheri_purecap_cc1 -debug-info-kind=standalone %s -o /dev/null -O1 -S -verify @@ -7,29 +8,30 @@ struct OneCap { void *__capability b; }; -// define void @test_addrof_char(%struct.OneCap addrspace(200)* {{%[a-z0-9]+}}, i8 signext {{%[a-z0-9]+}}, i128 {{%[a-z0-9]+}}) addrspace(200) #0 { - void test_addrof_char(struct OneCap *cap, char c, __uint128_t u) { // CHECK-LABEL: void @test_addrof_char( // Since this is an address-of expression we should be able to detect that // the source does not contain tags __builtin_memmove(cap, &c, sizeof(c)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9.]+}} - // CHECK-SAME: , i64 1, i1 false){{$}} + // CHECK-SAME: , i64 1, i1 false) [[MUST_PRESERVE_ATTR:#[0-9]+]]{{$}} // FIXME-SAME: , i64 1, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} __builtin_memmove(&c, cap, sizeof(c)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}.addr, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 1, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR:#[0-9]+]]{{$}} + // CHECK-SAME: , i64 1, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR:#[0-9]+]]{{$}} // FIXME-SAME: , i64 1, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} + + // uint128_t cannot not hold tags -> no need to preserve them since we can see the underlying allocation. __builtin_memmove(cap, &u, sizeof(u)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} - // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} - + // FIXME: We can see the underlying decl, this should not need to preserve tags + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} __builtin_memmove(&u, cap, sizeof(u)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} + // FIXME: We can see the underlying decl, this should not need to preserve tags // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR:#[0-9]+]]{{$}} } void test_small_copy(struct OneCap *cap1, struct OneCap *cap2) { @@ -38,7 +40,6 @@ void test_small_copy(struct OneCap *cap1, struct OneCap *cap2) { // This copy preserves tags // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} - // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} __builtin_memmove(cap1, cap2, 2); // TODO :This copy is too small -> should not preserve tags // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} @@ -59,13 +60,14 @@ void test_addrof_char_buf(struct OneCap *cap, struct strbuf s) { // FIXME: can we add no_preserve_tags if the programmer didn't add an _Alignas()? __builtin_memmove(cap, &s, sizeof(s)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} - // TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}} - + // FIXME: We can see the underlying decl, this should not need to preserve tags + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}} __builtin_memmove(&s, cap, sizeof(s)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // FIXME: We can see the underlying decl, this should not need to preserve tags + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} - // TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}} } void test_array_decay(struct OneCap *cap) { @@ -74,10 +76,13 @@ void test_array_decay(struct OneCap *cap) { int buf[16]; __builtin_memmove(cap, buf, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 4 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} - // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}} + // FIXME: We can see the underlying decl, this should not need to preserve tags + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR:#[0-9]+]]{{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + __builtin_memmove(buf, cap, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 4 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // FIXME: We can see the underlying decl, this should not need to preserve tags // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} @@ -86,7 +91,7 @@ void test_array_decay(struct OneCap *cap) { char buf2[16]; __builtin_memmove(cap, buf2, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} // TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} __builtin_memmove(buf2, cap, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} @@ -98,7 +103,7 @@ void test_array_decay(struct OneCap *cap) { _Alignas(void *__capability) char aligned_char_buf[16]; __builtin_memmove(cap, aligned_char_buf, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} __builtin_memmove(aligned_char_buf, cap, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} @@ -108,10 +113,11 @@ void test_string_constant(struct OneCap *cap) { // CHECK-LABEL: void @test_string_constant( // Same for string -> char* __builtin_memmove(cap, "abcdefghijklmnopqrstuvwxyz", sizeof(*cap)); + // expected-warning@-1{{memcpy operation with capability argument and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 getelementptr inbounds ([27 x i8], [27 x i8] addrspace(200)* @.str // CHECK-SAME: , i64 0 // CHECK-SAME: , i64 0) - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} } @@ -119,8 +125,9 @@ void test_void_buffer(struct OneCap *cap, void *buf) { // CHECK-LABEL: void @test_void_buffer( // A void* means unknown contents and therefore we preserve tags. __builtin_memmove(cap, buf, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} __builtin_memmove(buf, cap, sizeof(*cap)); // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} @@ -131,8 +138,35 @@ void test_char_buffer(struct OneCap *cap, char *buf) { // CHECK-LABEL: void @test_char_buffer( // We have to also assume that char* means unknown contents. __builtin_memmove(cap, buf, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} + __builtin_memmove(buf, cap, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} +} + +void test_uchar_buffer(struct OneCap *cap, unsigned char *buf) { + // CHECK-LABEL: void @test_uchar_buffer( + // We also assume that unsigned char* means unknown contents. + __builtin_memmove(cap, buf, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} + __builtin_memmove(buf, cap, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} +} + +void test_u8_buffer(struct OneCap *cap, __UINT8_TYPE__ *buf) { + // CHECK-LABEL: void @test_u8_buffer( + // Same for uint8_t (it's almost certainly defined as unsigned char). + __builtin_memmove(cap, buf, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} __builtin_memmove(buf, cap, sizeof(*cap)); // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} @@ -141,16 +175,23 @@ void test_char_buffer(struct OneCap *cap, char *buf) { void test_int_buffer(struct OneCap *cap, int *buf) { // CHECK-LABEL: void @test_int_buffer( - // However, for int* we can assume it does not contain tags. + // Note: we cannot assume the int buffer is free of tags since C's rules + // depend on the type stored to that memory location last and not the type of + // the pointer. + // FIXME: shouldn't print __builtin_memmove(cap, buf, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument and underaligned destination (aligned to 4 bytes) may be inefficient or result in CHERI tags bits being stripped}} + // expected-note@-2{{use __builtin_assume_aligned()}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 4 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} __builtin_memmove(buf, cap, sizeof(*cap)); - // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 4 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 4 bytes) may be inefficient or result in CHERI tags bits being stripped}} + // expected-note@-2{{use __builtin_assume_aligned()}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 4 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} } // CHECK: attributes #0 = { +// CHECK-DAG: attributes [[MUST_PRESERVE_ATTR]] = { must_preserve_cheri_tags } // CHECK-DAG: attributes [[MUST_PRESERVE_WITH_TYPE_ATTR]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct OneCap'" } // FIXME-DAG: attributes [[NO_PRESERVE_ATTR]] = { no_preserve_cheri_tags } diff --git a/clang/test/CodeGen/cheri/no-tag-copy-strict-aliasing.c b/clang/test/CodeGen/cheri/no-tag-copy-strict-aliasing.c new file mode 100644 index 0000000000000..66d1fb2a2a1b4 --- /dev/null +++ b/clang/test/CodeGen/cheri/no-tag-copy-strict-aliasing.c @@ -0,0 +1,64 @@ +// RUN: %riscv64_cheri_purecap_cc1 %s -emit-llvm -o - -O0 | FileCheck %s --check-prefixes=CHECK +// RUN: %riscv64_cheri_purecap_cc1 %s -relaxed-aliasing -emit-llvm -o - -O0 | FileCheck %s --check-prefixes=CHECK +/// Check that we don't add the no_preserve_cheri_tags attribute based on the +/// pointee type of the memcpy since that could break certain valid (albeit +/// dubious) code that relies on tag-preservation for types such as long* +/// See C2x 6.5p6 ("effective type") for more detail. + +void *malloc(__SIZE_TYPE__); +void *memcpy(void *, const void *, __SIZE_TYPE__); +void foo(long **p, long **q); + +void must_retain(long **p, long **q) { + *p = malloc(32); + *q = malloc(32); + (*p)[0] = 1; + (*p)[1] = 2; + // Note: Despite the pointer being a long*, the C standard states that the + // first store to a malloc'd memory location defines the type of the memory + // for strict-aliasing purposes. + // Therefore, this cast and store is fine and we need to retain tags + // in the memcpy below (i.e. we can't add no_preserve_cheri_tags). + *(void (**)(long **, long **))(*p + 2) = &foo; + memcpy(*q, *p, 32); + // CHECK: @must_retain(i64 addrspace(200)* addrspace(200)* [[P:%.*]], i64 addrspace(200)* addrspace(200)* [[Q:%.*]]) addrspace(200) + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9]+}}, i8 addrspace(200)* align 8 {{%[0-9]+}} + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +void no_retain(long **q) { + long p[4]; + *q = malloc(32); + p[0] = 1; + p[1] = 2; + *(void (**)(long **, long **))(p + 2) = &foo; + memcpy(*q, p, 32); + // Since we can see that p is a long[4] stack-allocated variable, the object + // type is long[4] and therefore the memory should not be used to store tags. + // We can therefore omit the copy of tags (although in practise it will + // probably be aligned so malloc will retain tags at run time). + // CHECK: @no_retain(i64 addrspace(200)* addrspace(200)* [[Q:%.*]]) addrspace(200) + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9]+}}, i8 addrspace(200)* align 8 {{%[0-9]+}} + // CHECK-SAME: , i64 32, i1 false){{$}} + // TODO-CHECK-SAME: , i64 32, i1 false) [[NO_TAGS_ATTR:#.*]]{{$}} + // CHECK-NEXT: ret void +} + +void retain_char_array(long **q) { + // The C standard treats character arrays specially and therefore we must + // assume that those can hold tags (even if they aren't sufficiently aligned + // for holding capabilities). + _Alignas(8) char p[32]; + *q = malloc(32); + ((long *)p)[0] = 1; + ((long *)p)[1] = 2; + *(void (**)(long **, long **))(p + 16) = &foo; + memcpy(*q, p, 32); + // CHECK: @retain_char_array(i64 addrspace(200)* addrspace(200)* [[Q:%.*]]) addrspace(200) + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[a-z0-9]+}}, i8 addrspace(200)* align 8 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +// TODO-CHECK: [[NO_TAGS_ATTR]] = { no_preserve_cheri_tags } diff --git a/clang/test/CodeGen/cheri/subobject-bounds-structure-array.c b/clang/test/CodeGen/cheri/subobject-bounds-structure-array.c index b6ce89b040285..921029f543f48 100644 --- a/clang/test/CodeGen/cheri/subobject-bounds-structure-array.c +++ b/clang/test/CodeGen/cheri/subobject-bounds-structure-array.c @@ -45,7 +45,7 @@ int test_struct_with_array1(struct_with_array *s, long index) { // CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_STRUCT_WITH_ARRAY]], [[STRUCT_STRUCT_WITH_ARRAY]] addrspace(200)* [[S]], i64 [[INDEX]] // CHECK-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_STRUCT_WITH_ARRAY]] addrspace(200)* [[AGG_RESULT]] to i8 addrspace(200)* // CHECK-NEXT: [[TMP1:%.*]] = bitcast [[STRUCT_STRUCT_WITH_ARRAY]] addrspace(200)* [[ARRAYIDX]] to i8 addrspace(200)* -// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(56) [[TMP0]], i8 addrspace(200)* noundef nonnull align 8 dereferenceable(56) [[TMP1]], i64 56, i1 false), !tbaa.struct !6 +// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(56) [[TMP0]], i8 addrspace(200)* noundef nonnull align 8 dereferenceable(56) [[TMP1]], i64 56, i1 false) #[[ATTR10:[0-9]+]], !tbaa.struct !6 // CHECK-NEXT: ret void // struct_with_array test_struct_with_array2(struct_with_array *s, long index) { @@ -78,7 +78,7 @@ int test_struct_with_ptr1(struct_with_ptr *s, long index) { // CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_STRUCT_WITH_PTR]], [[STRUCT_STRUCT_WITH_PTR]] addrspace(200)* [[S]], i64 [[INDEX]] // CHECK-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_STRUCT_WITH_PTR]] addrspace(200)* [[AGG_RESULT]] to i8 addrspace(200)* // CHECK-NEXT: [[TMP1:%.*]] = bitcast [[STRUCT_STRUCT_WITH_PTR]] addrspace(200)* [[ARRAYIDX]] to i8 addrspace(200)* -// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 16 dereferenceable(48) [[TMP0]], i8 addrspace(200)* noundef nonnull align 16 dereferenceable(48) [[TMP1]], i64 48, i1 false), !tbaa.struct !13 +// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 16 dereferenceable(48) [[TMP0]], i8 addrspace(200)* noundef nonnull align 16 dereferenceable(48) [[TMP1]], i64 48, i1 false) #[[ATTR11:[0-9]+]], !tbaa.struct !13 // CHECK-NEXT: ret void // struct_with_ptr test_struct_with_ptr2(struct_with_ptr *s, long index) { @@ -182,7 +182,7 @@ int test_fake_vla2(struct_fake_vla2 *s, long index) { // CHECK-NEXT: [[CUR_LEN:%.*]] = call i64 @llvm.cheri.cap.length.get.i64(i8 addrspace(200)* nonnull [[TMP1]]) // CHECK-NEXT: [[REMAINING_BYTES:%.*]] = sub i64 [[CUR_LEN]], [[CUR_OFFSET]] // CHECK-NEXT: [[TMP2:%.*]] = call i8 addrspace(200)* @llvm.cheri.cap.bounds.set.i64(i8 addrspace(200)* nonnull [[TMP1]], i64 [[REMAINING_BYTES]]) -// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP2]]) #[[ATTR10:[0-9]+]] +// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP2]]) #[[ATTR12:[0-9]+]] // CHECK-NEXT: [[CUR_OFFSET4:%.*]] = call i64 @llvm.cheri.cap.offset.get.i64(i8 addrspace(200)* [[TMP2]]) // CHECK-NEXT: [[CUR_LEN5:%.*]] = call i64 @llvm.cheri.cap.length.get.i64(i8 addrspace(200)* [[TMP2]]) // CHECK-NEXT: [[REMAINING_BYTES6:%.*]] = sub i64 [[CUR_LEN5]], [[CUR_OFFSET4]] @@ -524,14 +524,14 @@ typedef struct { // CHECK-NEXT: entry: // CHECK-NEXT: [[ARRAY23:%.*]] = alloca [100 x i8], align 1, addrspace(200) // CHECK-NEXT: [[ARRAY23_SUB:%.*]] = getelementptr inbounds [100 x i8], [100 x i8] addrspace(200)* [[ARRAY23]], i64 0, i64 0 -// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR10]] +// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR12]] // CHECK-NEXT: [[TMP0:%.*]] = call i8 addrspace(200)* @llvm.cheri.cap.bounds.set.i64(i8 addrspace(200)* nonnull [[ARRAY23_SUB]], i64 100) // CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [100 x i8], [100 x i8] addrspace(200)* [[ARRAY23]], i64 0, i64 80 // CHECK-NEXT: [[TMP1:%.*]] = call i8 addrspace(200)* @llvm.cheri.cap.bounds.set.i64(i8 addrspace(200)* nonnull [[ARRAYIDX]], i64 10) // CHECK-NEXT: [[ARRAYIDX2:%.*]] = getelementptr inbounds i8, i8 addrspace(200)* [[TMP1]], i64 [[INDEX]] // CHECK-NEXT: store i8 65, i8 addrspace(200)* [[ARRAYIDX2]], align 1, !tbaa [[TBAA9]] -// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP0]]) #[[ATTR10]] -// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR10]] +// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP0]]) #[[ATTR12]] +// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR12]] // CHECK-NEXT: ret i32 0 // int test28a(long index) { @@ -565,15 +565,15 @@ int test28b(my_struct28 **array1, long index) { // CHECK-NEXT: entry: // CHECK-NEXT: [[ARRAY23:%.*]] = alloca [100 x i8], align 1, addrspace(200) // CHECK-NEXT: [[ARRAY23_SUB:%.*]] = getelementptr inbounds [100 x i8], [100 x i8] addrspace(200)* [[ARRAY23]], i64 0, i64 0 -// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR10]] +// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR12]] // CHECK-NEXT: [[TMP0:%.*]] = call i8 addrspace(200)* @llvm.cheri.cap.bounds.set.i64(i8 addrspace(200)* nonnull [[ARRAY23_SUB]], i64 100) // CHECK-NEXT: [[SUBSCRIPT_WITH_BOUNDS:%.*]] = bitcast i8 addrspace(200)* [[TMP0]] to [5 x %struct.my_struct28] addrspace(200)* // CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds [5 x %struct.my_struct28], [5 x %struct.my_struct28] addrspace(200)* [[SUBSCRIPT_WITH_BOUNDS]], i64 0, i64 [[INDEX1]], i32 0, i64 0 // CHECK-NEXT: [[TMP2:%.*]] = call i8 addrspace(200)* @llvm.cheri.cap.bounds.set.i64(i8 addrspace(200)* [[TMP1]], i64 10) // CHECK-NEXT: [[ARRAYIDX2:%.*]] = getelementptr inbounds i8, i8 addrspace(200)* [[TMP2]], i64 [[INDEX2]] // CHECK-NEXT: store i8 65, i8 addrspace(200)* [[ARRAYIDX2]], align 1, !tbaa [[TBAA9]] -// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP0]]) #[[ATTR10]] -// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR10]] +// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP0]]) #[[ATTR12]] +// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR12]] // CHECK-NEXT: ret i32 0 // int test28c(long index1, long index2) { @@ -601,3 +601,7 @@ int test28d(my_struct28 **array1, long index1, long index2) { // expected-remark@-1{{not setting bounds for array subscript on 'my_struct28 **' (array subscript on non-array type)}} return 0; } + +// UTC_ARGS: --disable +// CHECK: attributes #[[ATTR10]] = { no_preserve_cheri_tags } +// CHECK: attributes #[[ATTR11]] = { must_preserve_cheri_tags } diff --git a/clang/test/CodeGenCXX/cheri/no-tag-copy-copy-ctor.cpp b/clang/test/CodeGenCXX/cheri/no-tag-copy-copy-ctor.cpp new file mode 100644 index 0000000000000..c4c614da60786 --- /dev/null +++ b/clang/test/CodeGenCXX/cheri/no-tag-copy-copy-ctor.cpp @@ -0,0 +1,146 @@ +// RUN: %riscv64_cheri_purecap_cc1 %s -emit-llvm -o - | FileCheck %s +// RUN: %riscv64_cheri_purecap_cc1 %s -relaxed-aliasing -emit-llvm -o -| FileCheck %s +/// Check that we add the no_preserve_tags/must_preserve_tags attribute to +/// C++ copy constructors. +/// NB: Unfortunately, we have to special-case char[] fields even though +/// they will usually hold string data and not capabilities. + +struct TestThreeLongsFinal final { + long a; + long b; + long c; + TestThreeLongsFinal(const TestThreeLongsFinal &) = default; +}; + +TestThreeLongsFinal test_copy_ctor_longs_final(const TestThreeLongsFinal &t) { + // Since this type only contains a long[] (and is final) we don't need to preserve tags when copying + return t; + // CHECK-LABEL: @_Z26test_copy_ctor_longs_finalRK19TestThreeLongsFinal( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 8 {{%[0-9]+}}, i8 addrspace(200)* align 8 {{%[0-9]+}} + // CHECK-SAME: , i64 24, i1 false) [[NO_TAGS_ATTR:#[0-9]+]]{{$}} + // CHECK-NEXT: ret void +} + +struct TestLongArray { + long array[5]; + TestLongArray(const TestLongArray &) = default; +}; + +TestLongArray test_copy_ctor_long_array(const TestLongArray &t) { + // Since this type only contains a long[] we don't need to preserve tags when copying + return t; + // CHECK-LABEL: @_Z25test_copy_ctor_long_arrayRK13TestLongArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 8 {{%[0-9]+}}, i8 addrspace(200)* align 8 {{%[0-9]+}} + // CHECK-SAME: , i64 40, i1 false) [[NO_TAGS_ATTR:#[0-9]+]] + // CHECK-NEXT: ret void +} + +struct TestCharPtr { + char *cap; + long array[5]; + TestCharPtr(const TestCharPtr &) = default; +}; + +TestCharPtr test_copy_ctor_with_ptr(const TestCharPtr &t) { + // Since this type only contains a char[] we must preserve tags when copying + return t; + // CHECK-LABEL: @_Z23test_copy_ctor_with_ptrRK11TestCharPtr( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 16 {{%[0-9]+}}, i8 addrspace(200)* align 16 {{%[0-9]+}} + // CHECK-SAME: , i64 64, i1 false) [[MUST_PRESERVE_TAGS_ATTR:#[0-9]+]] + // CHECK-NEXT: ret void +} + +struct TestOveralignedCharArray { + // NB: we have to assume this array can be used to store tags. + alignas(32) char array[32]; + TestOveralignedCharArray(const TestOveralignedCharArray &) = default; +}; + +TestOveralignedCharArray test_copy_ctor_overaligned_char_array(const TestOveralignedCharArray &t) { + return t; + // CHECK-LABEL: @_Z37test_copy_ctor_overaligned_char_arrayRK24TestOveralignedCharArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 32 {{%[0-9]+}}, i8 addrspace(200)* align 32 {{%[0-9]+}} + // NB: we have to be conservative here and not set the attribute since there is + // an overaligned char[] that could potentially hold capabilities. + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +struct TestOveralignedUCharArray { + // NB: we have to assume this array can be used to store tags. + alignas(32) unsigned char array[32]; + TestOveralignedUCharArray(const TestOveralignedUCharArray &) = default; +}; + +TestOveralignedUCharArray test_copy_ctor_overaligned_uchar_array(const TestOveralignedUCharArray &t) { + return t; + // CHECK-LABEL: @_Z38test_copy_ctor_overaligned_uchar_arrayRK25TestOveralignedUCharArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 32 {{%[0-9]+}}, i8 addrspace(200)* align 32 {{%[0-9]+}} + // NB: we have to be conservative here and not set the attribute since there is + // an overaligned unsigned char[] that could potentially hold capabilities. + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +struct TestOveralignedSCharArray { + // NB: we have to assume this array can be used to store tags. + alignas(32) signed char array[32]; + TestOveralignedSCharArray(const TestOveralignedSCharArray &) = default; +}; + +TestOveralignedSCharArray test_copy_ctor_overaligned_schar_array(const TestOveralignedSCharArray &t) { + return t; + // CHECK-LABEL: @_Z38test_copy_ctor_overaligned_schar_arrayRK25TestOveralignedSCharArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 32 {{%[0-9]+}}, i8 addrspace(200)* align 32 {{%[0-9]+}} + // NB: we have to be conservative here and not set the attribute since there is + // an overaligned signed char[] that could potentially hold capabilities. + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +namespace std { +enum class byte : unsigned char {}; +} + +struct TestOveralignedStdByteArray { + // NB: we have to assume this array can be used to store tags. + alignas(32) std::byte array[32]; + TestOveralignedStdByteArray(const TestOveralignedStdByteArray &) = default; +}; + +TestOveralignedStdByteArray _Z41test_copy_ctor_overaligned_std_byte_arrayRK27TestOveralignedStdByteArray(const TestOveralignedStdByteArray &t) { + return t; + // CHECK-LABEL: @_Z76_Z41test_copy_ctor_overaligned_std_byte_arrayRK27TestOveralignedStdByteArrayRK27TestOveralignedStdByteArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 32 {{%[0-9]+}}, i8 addrspace(200)* align 32 {{%[0-9]+}} + // NB: we have to be conservative here and not set the attribute since there is + // an overaligned std::byte[] that could potentially hold capabilities. + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +// NB: we conservatively assume that char[] could be used to hold tags. +// TODO: should we assume this cannot be used to store tags since it is underaligned? +struct TestCharArray { + char array[32]; + TestCharArray(const TestCharArray &) = default; +}; + +TestCharArray test_copy_ctor_unaligned_char_array(const TestCharArray &t) { + return t; + // CHECK-LABEL: @_Z35test_copy_ctor_unaligned_char_arrayRK13TestCharArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 1 {{%[0-9]+}}, i8 addrspace(200)* align 1 {{%[0-9]+}} + // CHECK-SAME: , i64 32, i1 false){{$}} + // TODO-SAME: , i64 32, i1 false) [[NO_TAGS_ATTR]] + // CHECK-NEXT: ret void +} + +// CHECK: [[NO_TAGS_ATTR]] = { no_preserve_cheri_tags } +// CHECK: [[MUST_PRESERVE_TAGS_ATTR]] = { must_preserve_cheri_tags } diff --git a/clang/test/CodeGenCXX/trivial-auto-var-init.cpp b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp index 513222cb3f1d1..04e920f244de1 100644 --- a/clang/test/CodeGenCXX/trivial-auto-var-init.cpp +++ b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp @@ -1,6 +1,8 @@ // RUN: %clang_cc1 -triple x86_64-unknown-unknown -fblocks %s -emit-llvm -o - | FileCheck %s -check-prefix=UNINIT -// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fblocks -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s -check-prefix=PATTERN -// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fblocks -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s -check-prefix=ZERO +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fblocks -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s --check-prefixes=PATTERN,PATTERN-NOCHERI +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fblocks -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s --check-prefixes=ZERO,ZERO-NOCHERI +// RUN: %riscv64_cheri_cc1 -fblocks -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s --check-prefixes=PATTERN,PATTERN-CHERI +// RUN: %riscv64_cheri_cc1 -fblocks -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s --check-prefixes=ZERO,ZERO-CHERI // None of the synthesized globals should contain `undef`. // PATTERN-NOT: undef @@ -140,7 +142,8 @@ void test_switch(int i) { // UNINIT-LABEL: test_vla( // ZERO-LABEL: test_vla( // ZERO: %[[SIZE:[0-9]+]] = mul nuw i64 %{{.*}}, 4 -// ZERO: call void @llvm.memset{{.*}}(i8* align 16 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] +// ZERO-NOCHERI: call void @llvm.memset{{.*}}(i8* align 16 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] +// ZERO-CHERI: call void @llvm.memset{{.*}}(i8* align 4 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] // PATTERN-LABEL: test_vla( // PATTERN: %vla.iszerosized = icmp eq i64 %{{.*}}, 0 // PATTERN: br i1 %vla.iszerosized, label %vla-init.cont, label %vla-setup.loop @@ -151,7 +154,8 @@ void test_switch(int i) { // PATTERN: br label %vla-init.loop // PATTERN: vla-init.loop: // PATTERN: %vla.cur = phi i8* [ %vla.begin, %vla-setup.loop ], [ %vla.next, %vla-init.loop ] -// PATTERN: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_vla.vla {{.*}}), !annotation [[AUTO_INIT:!.+]] +// PATTERN-NOCHERI: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_vla.vla {{.*}}), !annotation [[AUTO_INIT:!.+]] +// PATTERN-CHERI: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_vla.vla {{.*}}) [[NOTAGS:#[0-9]+]], !annotation [[AUTO_INIT:!.+]] // PATTERN: %vla.next = getelementptr inbounds i8, i8* %vla.cur, i64 4 // PATTERN: %vla-init.isdone = icmp eq i8* %vla.next, %vla.end // PATTERN: br i1 %vla-init.isdone, label %vla-init.cont, label %vla-init.loop @@ -204,7 +208,8 @@ void test_alloca_with_align(int size) { // UNINIT-LABEL: test_struct_vla( // ZERO-LABEL: test_struct_vla( // ZERO: %[[SIZE:[0-9]+]] = mul nuw i64 %{{.*}}, 16 -// ZERO: call void @llvm.memset{{.*}}(i8* align 16 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] +// ZERO-NOCHERI: call void @llvm.memset{{.*}}(i8* align 16 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] +// ZERO-CHERI: call void @llvm.memset{{.*}}(i8* align 8 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] // PATTERN-LABEL: test_struct_vla( // PATTERN: %vla.iszerosized = icmp eq i64 %{{.*}}, 0 // PATTERN: br i1 %vla.iszerosized, label %vla-init.cont, label %vla-setup.loop @@ -215,7 +220,8 @@ void test_alloca_with_align(int size) { // PATTERN: br label %vla-init.loop // PATTERN: vla-init.loop: // PATTERN: %vla.cur = phi i8* [ %vla.begin, %vla-setup.loop ], [ %vla.next, %vla-init.loop ] -// PATTERN: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_struct_vla.vla {{.*}}), !annotation [[AUTO_INIT:!.+]] +// PATTERN-NOCHERI: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_struct_vla.vla {{.*}}), !annotation [[AUTO_INIT:!.+]] +// PATTERN-CHERI: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_struct_vla.vla {{.*}}) [[NOTAGS:#[0-9]+]], !annotation [[AUTO_INIT:!.+]] // PATTERN: %vla.next = getelementptr inbounds i8, i8* %vla.cur, i64 16 // PATTERN: %vla-init.isdone = icmp eq i8* %vla.next, %vla.end // PATTERN: br i1 %vla-init.isdone, label %vla-init.cont, label %vla-init.loop @@ -282,10 +288,12 @@ void test_huge_small_init() { // UNINIT-LABEL: test_huge_larger_init( // ZERO-LABEL: test_huge_larger_init( -// ZERO: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, +// ZERO-CHERI: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, i1 false) [[NOTAGS:#[0-9]+]]{{$}} +// ZERO-NOCHERI: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, i1 false){{$}} // ZERO-NOT: !annotation // PATTERN-LABEL: test_huge_larger_init( -// PATTERN: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, +// PATTERN-CHERI: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, i1 false) [[NOTAGS]]{{$}} +// PATTERN-NOCHERI: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, i1 false){{$}} // PATTERN-NOT: !annotation void test_huge_larger_init() { char big[65536] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; @@ -294,4 +302,7 @@ void test_huge_larger_init() { } // extern "C" -// CHECK: [[AUTO_INIT]] = !{ !"auto-init" } +// PATTERN-CHERI: attributes [[NOTAGS]] = { no_preserve_cheri_tags } +// ZERO-CHERI: attributes [[NOTAGS]] = { no_preserve_cheri_tags } +// PATTERN: [[AUTO_INIT]] = !{!"auto-init"} +// ZERO: [[AUTO_INIT]] = !{!"auto-init"} diff --git a/llvm/test/CodeGen/Mips/cheri/memcpy-unaligned-addrinfo.c b/llvm/test/CodeGen/Mips/cheri/memcpy-unaligned-addrinfo.c index 9a4c8e9726262..ffe47c7d8893a 100644 --- a/llvm/test/CodeGen/Mips/cheri/memcpy-unaligned-addrinfo.c +++ b/llvm/test/CodeGen/Mips/cheri/memcpy-unaligned-addrinfo.c @@ -1,13 +1,19 @@ // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --function-signature -// RUN: %cheri128_purecap_cc1 %s -emit-llvm -O0 -o - | FileCheck %s -check-prefix OPTNONE -// RUN: %cheri128_purecap_cc1 %s -emit-llvm -O2 -o - | FileCheck %s -// RUN: %cheri128_purecap_cc1 %s -S -O2 -o - -verify -debug-info-kind=standalone | FileCheck %s -check-prefix ASM +// RUN: %cheri_purecap_cc1 %s -emit-llvm -O0 -o - -verify=expected,frontend | FileCheck %s -check-prefix OPTNONE +// RUN: %cheri_purecap_cc1 %s -emit-llvm -O2 -o - -verify=expected,frontend | FileCheck %s +// Note: debug info only enabled to ensure the -verify warning message is correct +// The -debug-info-kind=standalone flag is needed for precise location of diagnostics +// RUN: %cheri_purecap_cc1 %s -S -O2 -o - -debug-info-kind=standalone -verify=backend,expected | FileCheck %s -check-prefix ASM // REQUIRES: clang -// FIXME -// This test is checking a load of detail in the debug info and I can't tell -// what it actually wants to be testing, so I've disabled the middle test. -// It's also a horrible layering violation having a test that depends on clang -// in the LLVM back end. +// Check that the (potentially) tag-preserving copy is that is turned into an +// underaligned capability load/store by SROA results in a memcpy() call at run time. +// This happens because SelectionDAG expands unaligned capability loads/stores to memcpy calls. +// It may seem sensible to not perform this transform in SROA, but that appeared +// to trigger the WebKit miscompile in the first place... +/// FIXME: This test for https://github.com/CTSRD-CHERI/llvm-project/issues/301 is +/// terrible but splitting it into C -> IR and IR -> ASM is awkward + +// frontend-no-diagnostics struct addrinfo { char *b; @@ -21,7 +27,7 @@ struct addrinfo { // OPTNONE-NEXT: store i8 addrspace(200)* [[A]], i8 addrspace(200)* addrspace(200)* [[A_ADDR]], align 16 // OPTNONE-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_ADDRINFO]] addrspace(200)* [[RETVAL]] to i8 addrspace(200)* // OPTNONE-NEXT: [[TMP1:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[A_ADDR]], align 16 -// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 16 [[TMP0]], i8 addrspace(200)* align 1 [[TMP1]], i64 16, i1 false) +// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 16 [[TMP0]], i8 addrspace(200)* align 1 [[TMP1]], i64 16, i1 false) #[[ATTR3:[0-9]+]] // OPTNONE-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds [[STRUCT_ADDRINFO]], [[STRUCT_ADDRINFO]] addrspace(200)* [[RETVAL]], i32 0, i32 0 // OPTNONE-NEXT: [[TMP2:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[COERCE_DIVE]] to { i8 addrspace(200)* } addrspace(200)* // OPTNONE-NEXT: [[TMP3:%.*]] = load { i8 addrspace(200)* }, { i8 addrspace(200)* } addrspace(200)* [[TMP2]], align 16 @@ -38,12 +44,12 @@ struct addrinfo { struct addrinfo c(char *a) { struct addrinfo d; __builtin_memcpy(&d, a, sizeof(struct addrinfo)); - // expected-warning@-1{{found underaligned load of capability type (aligned to 1 bytes instead of 16). Will use memcpy() instead of capability load to preserve tags if it is aligned correctly at runtime}} - // expected-note@-2{{use __builtin_assume_aligned() or cast}} + // backend-warning@-1{{found underaligned load of capability type (aligned to 1 bytes instead of 16). Will use memcpy() instead of capability load to preserve tags if it is aligned correctly at runtime}} + // backend-note@-2{{use __builtin_assume_aligned() or cast}} return d; // ASM: .Ltmp1: - // ASM-NEXT: .loc 1 40 3 prologue_end + // ASM-NEXT: .loc 1 [[#@LINE-6]] 3 prologue_end // ASM-NEXT: csetbounds $c4, $c3, 16 // ASM-NEXT: clcbi $c12, %capcall20(memcpy)($c1) // ASM-NEXT: csetbounds $c3, $c11, 16 @@ -66,7 +72,7 @@ void do_stuff(struct group *g); // OPTNONE-NEXT: store i8 addrspace(200)* [[A]], i8 addrspace(200)* addrspace(200)* [[A_ADDR]], align 16 // OPTNONE-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [16 x i8], [16 x i8] addrspace(200)* [[BUFFER]], i64 0, i64 0 // OPTNONE-NEXT: [[TMP0:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[A_ADDR]] to i8 addrspace(200)* -// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[ARRAYDECAY]], i8 addrspace(200)* align 16 [[TMP0]], i64 16, i1 false) #[[ATTR3:[0-9]+]] +// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[ARRAYDECAY]], i8 addrspace(200)* align 16 [[TMP0]], i64 16, i1 false) #[[ATTR4:[0-9]+]] // OPTNONE-NEXT: [[ARRAYDECAY1:%.*]] = getelementptr inbounds [16 x i8], [16 x i8] addrspace(200)* [[BUFFER]], i64 0, i64 0 // OPTNONE-NEXT: [[TMP1:%.*]] = bitcast i8 addrspace(200)* [[ARRAYDECAY1]] to [[STRUCT_GROUP]] addrspace(200)* // OPTNONE-NEXT: store [[STRUCT_GROUP]] addrspace(200)* [[TMP1]], [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[G]], align 16 @@ -79,11 +85,11 @@ void do_stuff(struct group *g); // CHECK-NEXT: entry: // CHECK-NEXT: [[BUFFER:%.*]] = alloca i8 addrspace(200)*, align 16, addrspace(200) // CHECK-NEXT: [[TMP0:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[BUFFER]] to i8 addrspace(200)* -// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 16, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR5:[0-9]+]] +// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 16, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR4:[0-9]+]] // CHECK-NEXT: store i8 addrspace(200)* [[A]], i8 addrspace(200)* addrspace(200)* [[BUFFER]], align 16 // CHECK-NEXT: [[TMP1:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[BUFFER]] to [[STRUCT_GROUP:%.*]] addrspace(200)* -// CHECK-NEXT: call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* nonnull [[TMP1]]) #[[ATTR5]] -// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 16, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR5]] +// CHECK-NEXT: call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* nonnull [[TMP1]]) #[[ATTR4]] +// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 16, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR4]] // CHECK-NEXT: ret void // void copy_group(const char *a) { @@ -105,7 +111,7 @@ void copy_group(const char *a) { // OPTNONE-NEXT: store i8 addrspace(200)* [[BUFFER]], i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 // OPTNONE-NEXT: [[TMP0:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 // OPTNONE-NEXT: [[TMP1:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[A_ADDR]] to i8 addrspace(200)* -// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[TMP0]], i8 addrspace(200)* align 16 [[TMP1]], i64 16, i1 false) #[[ATTR3]] +// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[TMP0]], i8 addrspace(200)* align 16 [[TMP1]], i64 16, i1 false) #[[ATTR4]] // OPTNONE-NEXT: [[TMP2:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 // OPTNONE-NEXT: [[TMP3:%.*]] = bitcast i8 addrspace(200)* [[TMP2]] to [[STRUCT_GROUP]] addrspace(200)* // OPTNONE-NEXT: store [[STRUCT_GROUP]] addrspace(200)* [[TMP3]], [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[G]], align 16 @@ -119,7 +125,7 @@ void copy_group(const char *a) { // CHECK-NEXT: [[A_ADDR_0_BUFFER_ADDR_0__SROA_CAST:%.*]] = bitcast i8 addrspace(200)* [[BUFFER]] to i8 addrspace(200)* addrspace(200)* // CHECK-NEXT: store i8 addrspace(200)* [[A]], i8 addrspace(200)* addrspace(200)* [[A_ADDR_0_BUFFER_ADDR_0__SROA_CAST]], align 1 // CHECK-NEXT: [[TMP0:%.*]] = bitcast i8 addrspace(200)* [[BUFFER]] to [[STRUCT_GROUP:%.*]] addrspace(200)* -// CHECK-NEXT: tail call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP0]]) #[[ATTR5]] +// CHECK-NEXT: tail call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP0]]) #[[ATTR4]] // CHECK-NEXT: ret void // void copy_group2(const char *a, char *buffer) { @@ -127,8 +133,8 @@ void copy_group2(const char *a, char *buffer) { // derived from the unaligned memcpy used in getgrent // Note: this will result in an unaligned memcpy __builtin_memcpy(buffer, &a, sizeof(char *)); - // expected-warning@-1{{found underaligned store of capability type (aligned to 1 bytes instead of 16). Will use memcpy() instead of capability load to preserve tags if it is aligned correctly at runtime}} - // expected-note@-2{{use __builtin_assume_aligned()}} + // backend-warning@-1{{found underaligned store of capability type (aligned to 1 bytes instead of 16). Will use memcpy() instead of capability load to preserve tags if it is aligned correctly at runtime}} + // backend-note@-2{{if you know that the pointer is actually aligned to capability size}} struct group *g = (struct group *)buffer; do_stuff(g); } @@ -147,7 +153,7 @@ void copy_group2(const char *a, char *buffer) { // OPTNONE-NEXT: [[TMP0:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 // OPTNONE-NEXT: [[TMP1:%.*]] = bitcast [[STRUCT_GROUP]] addrspace(200)* [[A]] to i8 addrspace(200)* // OPTNONE-NEXT: [[TMP2:%.*]] = load i64, i64 addrspace(200)* [[SIZE_ADDR]], align 8 -// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[TMP0]], i8 addrspace(200)* align 16 [[TMP1]], i64 [[TMP2]], i1 false) #[[ATTR4:[0-9]+]] +// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[TMP0]], i8 addrspace(200)* align 16 [[TMP1]], i64 [[TMP2]], i1 false) #[[ATTR5:[0-9]+]] // OPTNONE-NEXT: [[TMP3:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 // OPTNONE-NEXT: [[TMP4:%.*]] = bitcast i8 addrspace(200)* [[TMP3]] to [[STRUCT_GROUP]] addrspace(200)* // OPTNONE-NEXT: store [[STRUCT_GROUP]] addrspace(200)* [[TMP4]], [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[G]], align 16 @@ -163,15 +169,57 @@ void copy_group2(const char *a, char *buffer) { // CHECK-NEXT: [[A_SROA_0_0__SROA_CAST4:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[A_SROA_0]] to i8 addrspace(200)* // CHECK-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[BUFFER]], i8 addrspace(200)* nonnull align 16 [[A_SROA_0_0__SROA_CAST4]], i64 [[SIZE]], i1 false) #[[ATTR6:[0-9]+]] // CHECK-NEXT: [[TMP0:%.*]] = bitcast i8 addrspace(200)* [[BUFFER]] to [[STRUCT_GROUP:%.*]] addrspace(200)* -// CHECK-NEXT: tail call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP0]]) #[[ATTR5]] +// CHECK-NEXT: tail call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP0]]) #[[ATTR4]] // CHECK-NEXT: ret void // void copy_group3(char *buffer, struct group a, long size) { // derived from the unaligned memcpy used in getgrent + // No warning here since the size is not constant and always needs a memcpy (TODO: maybe we should still warn?) __builtin_memcpy(buffer, &a, size); struct group *g = (struct group *)buffer; do_stuff(g); } +// OPTNONE-LABEL: define {{[^@]+}}@copy_group4 +// OPTNONE-SAME: (i8 addrspace(200)* [[BUFFER:%.*]], [[STRUCT_GROUP:%.*]] addrspace(200)* [[A:%.*]]) addrspace(200) #[[ATTR0]] { +// OPTNONE-NEXT: entry: +// OPTNONE-NEXT: [[BUFFER_ADDR:%.*]] = alloca i8 addrspace(200)*, align 16, addrspace(200) +// OPTNONE-NEXT: [[A_ADDR:%.*]] = alloca [[STRUCT_GROUP]] addrspace(200)*, align 16, addrspace(200) +// OPTNONE-NEXT: [[G:%.*]] = alloca [[STRUCT_GROUP]] addrspace(200)*, align 16, addrspace(200) +// OPTNONE-NEXT: store i8 addrspace(200)* [[BUFFER]], i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 +// OPTNONE-NEXT: store [[STRUCT_GROUP]] addrspace(200)* [[A]], [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[A_ADDR]], align 16 +// OPTNONE-NEXT: [[TMP0:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 +// OPTNONE-NEXT: [[TMP1:%.*]] = load [[STRUCT_GROUP]] addrspace(200)*, [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[A_ADDR]], align 16 +// OPTNONE-NEXT: [[TMP2:%.*]] = bitcast [[STRUCT_GROUP]] addrspace(200)* [[TMP1]] to i8 addrspace(200)* +// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[TMP0]], i8 addrspace(200)* align 16 [[TMP2]], i64 48, i1 false) #[[ATTR5]] +// OPTNONE-NEXT: [[TMP3:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 +// OPTNONE-NEXT: [[TMP4:%.*]] = bitcast i8 addrspace(200)* [[TMP3]] to [[STRUCT_GROUP]] addrspace(200)* +// OPTNONE-NEXT: store [[STRUCT_GROUP]] addrspace(200)* [[TMP4]], [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[G]], align 16 +// OPTNONE-NEXT: [[TMP5:%.*]] = load [[STRUCT_GROUP]] addrspace(200)*, [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[G]], align 16 +// OPTNONE-NEXT: call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP5]]) +// OPTNONE-NEXT: ret void +// +// CHECK-LABEL: define {{[^@]+}}@copy_group4 +// CHECK-SAME: (i8 addrspace(200)* [[BUFFER:%.*]], [[STRUCT_GROUP:%.*]] addrspace(200)* nocapture readonly [[A:%.*]]) local_unnamed_addr addrspace(200) #[[ATTR2]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_GROUP]] addrspace(200)* [[A]] to i8 addrspace(200)* +// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 1 dereferenceable(48) [[BUFFER]], i8 addrspace(200)* noundef nonnull align 16 dereferenceable(48) [[TMP0]], i64 48, i1 false) #[[ATTR6]] +// CHECK-NEXT: [[TMP1:%.*]] = bitcast i8 addrspace(200)* [[BUFFER]] to [[STRUCT_GROUP]] addrspace(200)* +// CHECK-NEXT: tail call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP1]]) #[[ATTR4]] +// CHECK-NEXT: ret void +// +void copy_group4(char *buffer, struct group *a) { + // derived from the unaligned memcpy used in getgrent + __builtin_memcpy(buffer, a, sizeof(struct group) * 3); + // backend-warning@-1{{memcpy operation with capability argument 'struct group' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} + // backend-note@-2{{if you know that the pointer is actually aligned to capability size}} + struct group *g = (struct group *)buffer; + do_stuff(g); +} + // UTC_ARGS: --disable +// OPTNONE: attributes #[[ATTR3]] = { must_preserve_cheri_tags } +// OPTNONE: attributes #[[ATTR4]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'const char * __capability'" } +// OPTNONE: attributes #[[ATTR5]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct group'" } // CHECK: attributes #[[ATTR6]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct group'" } +// UTC_ARGS: --enable From 26fe50afd76659c093701d920ecfbb0d6c361b1b Mon Sep 17 00:00:00 2001 From: Alex Richardson Date: Thu, 1 Sep 2022 15:15:59 +0000 Subject: [PATCH 4/5] Memory copies of less than cap size do not need to preserve tags --- clang/lib/CodeGen/CodeGenTypes.cpp | 14 ++++++++++++++ clang/test/CodeGen/cheri/memcpy-unaligned.c | 18 +++++++----------- .../cheri/no-tag-copy-attribute-with-caps.c | 17 +++++++---------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/clang/lib/CodeGen/CodeGenTypes.cpp b/clang/lib/CodeGen/CodeGenTypes.cpp index 89b480db65d1b..f04a656eeab7b 100644 --- a/clang/lib/CodeGen/CodeGenTypes.cpp +++ b/clang/lib/CodeGen/CodeGenTypes.cpp @@ -952,6 +952,12 @@ bool CodeGenTypes::isZeroInitializable(QualType T) { return true; } +static bool isLessThanCapSize(const ASTContext &Context, + Optional Size) { + return Size && *Size < Context.toCharUnitsFromBits( + Context.getTargetInfo().getCHERICapabilityWidth()); +} + static bool copiesAtMostTypeSize(const QualType Ty, const ASTContext &Context, Optional Size) { if (!Size) @@ -967,6 +973,10 @@ CodeGenTypes::copyShouldPreserveTags(const Expr *DestPtr, const Expr *SrcPtr, // targets to avoid changing tests and to avoid compile-time impact. if (!Context.getTargetInfo().SupportsCapabilities()) return llvm::PreserveCheriTags::Unknown; + if (isLessThanCapSize(Context, Size)) { + // Copies smaller than capability size do not need to preserve tag bits. + return llvm::PreserveCheriTags::Unnecessary; + } auto DstPreserve = copyShouldPreserveTags(DestPtr, Size); if (DstPreserve == llvm::PreserveCheriTags::Unnecessary) { // If the destination does not need to preserve tags, we know that we don't @@ -1019,6 +1029,10 @@ llvm::PreserveCheriTags CodeGenTypes::copyShouldPreserveTagsForPointee( // targets to avoid changing tests and to avoid compile-time impact. if (!Context.getTargetInfo().SupportsCapabilities()) return llvm::PreserveCheriTags::Unknown; + if (isLessThanCapSize(Context, Size)) { + // Copies smaller than capability size do not need to preserve tag bits. + return llvm::PreserveCheriTags::Unnecessary; + } assert(!Pointee.isNull() && "Should only be called for valid types"); if (Context.containsCapabilities(Pointee)) { // If this is a capability type or a structure/union containing diff --git a/clang/test/CodeGen/cheri/memcpy-unaligned.c b/clang/test/CodeGen/cheri/memcpy-unaligned.c index f5bfb176eaaf9..a938696bdc899 100644 --- a/clang/test/CodeGen/cheri/memcpy-unaligned.c +++ b/clang/test/CodeGen/cheri/memcpy-unaligned.c @@ -36,7 +36,7 @@ void test_dst_unliagned_src_cap_memcpy(void *align1, align2_ptr align2, align4_p // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A:#[0-9]+]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A:#[0-9]+]]{{$}} memcpy(align2, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 2 bytes) may be inefficient or result in CHERI tags bits being stripped}} @@ -78,27 +78,23 @@ void test_no_warn_for_non_caps(short *align2, align2_ptr align2_not_short, int n memcpy(align2, ¬_a_cap, sizeof(not_a_cap)); // no warning // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 4, i1 false){{$}} - // FIXME-SAME: [[NO_PRESERVE_TAGS_ATTRIB:#[0-9]+]]{{$}} + // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 4, i1 false) [[NO_PRESERVE_TAGS_ATTRIB:#[0-9]+]]{{$}} memcpy(align2, struct_without_cap, sizeof(*struct_without_cap)); // no warning // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 8, i1 false){{$}} - // FIXME-SAME: [[NO_PRESERVE_TAGS_ATTRIB:#[0-9]+]]{{$}} + // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 8, i1 false) [[NO_PRESERVE_TAGS_ATTRIB:#[0-9]+]]{{$}} memcpy(align2, capptr, sizeof(*capptr)); // expected-warning@-1{{memcpy operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 2 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP:#[0-9]+]]{{$}} + // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP:#[0-9]+]]{{$}} - memcpy(align2_not_short, struct_with_cap, sizeof(*struct_with_cap)); + memcpy(align2, struct_with_cap, sizeof(*struct_with_cap)); // expected-warning@-1{{memcpy operation with capability argument 'struct with_cap' and underaligned destination (aligned to 2 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 32, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_STRUCT_WITH_CAP:#[0-9]+]]{{$}} + // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 32, i1 false) [[PRESERVE_TAGS_ATTRIB_TYPE_STRUCT_WITH_CAP:#[0-9]+]]{{$}} } void test_dst_unliagned_src_cap_memmove(void *align1, align2_ptr align2, align4_ptr align4, align8_ptr align8, align_cap_ptr align_cap, a *src) { @@ -328,7 +324,7 @@ void test_builtin_assume_aligned_memmove_intermediate_var(char *align1, char *al // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } -// FIXME-DAG: attributes [[NO_PRESERVE_TAGS_ATTRIB]] = { no_preserve_cheri_tags } +// CHECK-DAG: attributes [[NO_PRESERVE_TAGS_ATTRIB]] = { no_preserve_cheri_tags } // CHECK-DAG: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_A]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'a' (aka 'unsigned __intcap')" } // CHECK-DAG: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_STRUCT_WITH_CAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct with_cap'" } // CHECK-DAG: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'unsigned __intcap'" } diff --git a/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c b/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c index 5be072b5d3ea9..9071dc7e8818a 100644 --- a/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c +++ b/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c @@ -14,19 +14,17 @@ void test_addrof_char(struct OneCap *cap, char c, __uint128_t u) { // the source does not contain tags __builtin_memmove(cap, &c, sizeof(c)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9.]+}} - // CHECK-SAME: , i64 1, i1 false) [[MUST_PRESERVE_ATTR:#[0-9]+]]{{$}} - // FIXME-SAME: , i64 1, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} + // CHECK-SAME: , i64 1, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]] __builtin_memmove(&c, cap, sizeof(c)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}.addr, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 1, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR:#[0-9]+]]{{$}} - // FIXME-SAME: , i64 1, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} + // CHECK-SAME: , i64 1, i1 false) [[NO_PRESERVE_ATTR]]{{$}} // uint128_t cannot not hold tags -> no need to preserve them since we can see the underlying allocation. __builtin_memmove(cap, &u, sizeof(u)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} // FIXME: We can see the underlying decl, this should not need to preserve tags - // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} - // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR:#[0-9]+]]{{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} __builtin_memmove(&u, cap, sizeof(u)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} // FIXME: We can see the underlying decl, this should not need to preserve tags @@ -41,10 +39,9 @@ void test_small_copy(struct OneCap *cap1, struct OneCap *cap2) { // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} __builtin_memmove(cap1, cap2, 2); - // TODO :This copy is too small -> should not preserve tags + // This copy is too small -> no need to preserve tags // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 2, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} - // FIXME-SAME: , i64 2, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + // CHECK-SAME: , i64 2, i1 false) [[NO_PRESERVE_ATTR]]{{$}} } struct strbuf { @@ -194,4 +191,4 @@ void test_int_buffer(struct OneCap *cap, int *buf) { // CHECK: attributes #0 = { // CHECK-DAG: attributes [[MUST_PRESERVE_ATTR]] = { must_preserve_cheri_tags } // CHECK-DAG: attributes [[MUST_PRESERVE_WITH_TYPE_ATTR]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct OneCap'" } -// FIXME-DAG: attributes [[NO_PRESERVE_ATTR]] = { no_preserve_cheri_tags } +// CHECK-DAG: attributes [[NO_PRESERVE_ATTR]] = { no_preserve_cheri_tags } From aa535e21488bfd117bab42054fa391fd499d9a1b Mon Sep 17 00:00:00 2001 From: Alex Richardson Date: Fri, 7 Oct 2022 09:59:24 +0000 Subject: [PATCH 5/5] Memory copies from string literals do not need to preserve tags --- clang/lib/CodeGen/CodeGenTypes.cpp | 5 +++++ .../CodeGen/cheri/no-tag-copy-attribute-with-caps.c | 10 +++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/clang/lib/CodeGen/CodeGenTypes.cpp b/clang/lib/CodeGen/CodeGenTypes.cpp index f04a656eeab7b..d5c9eb6cd465a 100644 --- a/clang/lib/CodeGen/CodeGenTypes.cpp +++ b/clang/lib/CodeGen/CodeGenTypes.cpp @@ -1013,6 +1013,11 @@ CodeGenTypes::copyShouldPreserveTags(const Expr *E, Optional Size) { QualType Ty = E->IgnoreParenImpCasts()->getType(); if (Ty->isAnyPointerType()) Ty = Ty->getPointeeType(); + const Expr *UnderlyingExpr = E->IgnoreParenCasts(); + if (const auto *SL = dyn_cast(UnderlyingExpr)) { + // String literals can never contain tag bits. + return llvm::PreserveCheriTags::Unnecessary; + } // TODO: Find the underlying VarDecl to improve diagnostics const VarDecl *UnderlyingVar = nullptr; // TODO: this assertion may be overly aggressive. diff --git a/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c b/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c index 9071dc7e8818a..c9a40af666246 100644 --- a/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c +++ b/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c @@ -110,12 +110,16 @@ void test_string_constant(struct OneCap *cap) { // CHECK-LABEL: void @test_string_constant( // Same for string -> char* __builtin_memmove(cap, "abcdefghijklmnopqrstuvwxyz", sizeof(*cap)); - // expected-warning@-1{{memcpy operation with capability argument and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 getelementptr inbounds ([27 x i8], [27 x i8] addrspace(200)* @.str // CHECK-SAME: , i64 0 // CHECK-SAME: , i64 0) - // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} - // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + // CHECK-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + /// Check that the explicit cast does not affect the analysis. + __builtin_memmove(cap, (void *)"abcdefghijklmnopqrstuvwxyz", sizeof(*cap)); + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 getelementptr inbounds ([27 x i8], [27 x i8] addrspace(200)* @.str + // CHECK-SAME: , i64 0 + // CHECK-SAME: , i64 0) + // CHECK-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} } void test_void_buffer(struct OneCap *cap, void *buf) {