-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[Clang] Make __builtin_assume_dereferenceable constexpr #169869
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fc8d8c7
4ca8c6a
33529fe
1509ebf
9b4d9a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| // RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++14 -emit-llvm -o - %s | FileCheck %s | ||
|
|
||
| int b = 10; | ||
| const int f = (__builtin_assume_dereferenceable((char*)&b + 1, 3), 12); | ||
| int use_f = f; | ||
|
|
||
| constexpr int g = 20; | ||
| const int h = (__builtin_assume_dereferenceable((char*)&g + 1, 2), 42); | ||
| int use_h = h; | ||
|
|
||
| constexpr char arr[] = "hello"; | ||
| constexpr const char* ptr = arr + 1; | ||
| constexpr int fully_constexpr() { | ||
| __builtin_assume_dereferenceable(ptr, 2); | ||
| return 100; | ||
| } | ||
| constexpr int i = fully_constexpr(); | ||
| int use_i = i; | ||
|
|
||
| void test_integral_ptr() { | ||
| __builtin_assume_dereferenceable((int*)0x1234, 4); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this example is incorrect, sorry. There are no readable bytes after that address. |
||
| } | ||
|
|
||
| void test_nullptr() { | ||
| __builtin_assume_dereferenceable(nullptr, 0); | ||
| } | ||
|
|
||
| void test_zero_size() { | ||
| int x = 10; | ||
| __builtin_assume_dereferenceable(&x, 0); | ||
| } | ||
|
|
||
| void test_function_ptr() { | ||
| __builtin_assume_dereferenceable((void*)&test_zero_size, 8); | ||
| } | ||
|
|
||
| // CHECK: @use_i = global i32 100 | ||
| // | ||
| // CHECK: @{{_Z[0-9]+}}test_integral_ptrv | ||
| // CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr inttoptr (i64 4660 to ptr), i64 4) ] | ||
| // | ||
| // CHECK: @{{_Z[0-9]+}}test_nullptrv | ||
| // CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr null, i64 0) ] | ||
| // | ||
| // CHECK: @{{_Z[0-9]+}}test_zero_sizev | ||
| // CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr {{%.*}}, i64 0) ] | ||
| // | ||
| // CHECK: @{{_Z[0-9]+}}test_function_ptrv | ||
| // CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr @{{_Z[0-9]+}}test_zero_sizev, i64 8) ] | ||
| // | ||
| // CHECK: __cxx_global_var_init | ||
| // CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr getelementptr inbounds (i8, ptr @b, i64 1), i64 3) ] | ||
| // | ||
| // CHECK: __cxx_global_var_init | ||
| // CHECK: call void @llvm.assume(i1 true) [ "dereferenceable"(ptr getelementptr inbounds (i8, ptr @{{_ZL[0-9]+}}g, i64 1), i64 2) ] | ||
| // CHECK: store i32 42, ptr @{{_ZL[0-9]+}}h | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| // RUN: %clang_cc1 -fsyntax-only -verify -std=c++14 -triple x86_64-unknown-unknown %s | ||
| // RUN: %clang_cc1 -fsyntax-only -verify -std=c++14 -triple x86_64-unknown-unknown %s -fexperimental-new-constant-interpreter | ||
|
|
||
| constexpr bool test_constexpr_valid() { | ||
| constexpr int arr[10] = {}; | ||
| __builtin_assume_dereferenceable(arr, 40); | ||
| return true; | ||
| } | ||
| static_assert(test_constexpr_valid(), ""); | ||
|
|
||
| constexpr bool test_constexpr_partial() { | ||
| constexpr int arr[10] = {}; | ||
| __builtin_assume_dereferenceable(&arr[5], 20); | ||
| return true; | ||
| } | ||
| static_assert(test_constexpr_partial(), ""); | ||
|
|
||
| constexpr bool test_constexpr_nullptr() { // expected-error {{constexpr function never produces a constant expression}} | ||
| __builtin_assume_dereferenceable(nullptr, 4); // expected-note 2{{read of dereferenced null pointer is not allowed in a constant expression}} | ||
| return true; | ||
| } | ||
| static_assert(test_constexpr_nullptr(), ""); // expected-error {{not an integral constant expression}} expected-note {{in call to}} | ||
|
|
||
| constexpr bool test_constexpr_too_large() { // expected-error {{constexpr function never produces a constant expression}} | ||
| constexpr int arr[10] = {}; | ||
| __builtin_assume_dereferenceable(arr, 100); // expected-note 2{{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} | ||
| return true; | ||
| } | ||
| static_assert(test_constexpr_too_large(), ""); // expected-error {{not an integral constant expression}} expected-note {{in call to}} | ||
|
|
||
| constexpr bool test_single_var() { | ||
| constexpr int single_var = 42; | ||
| __builtin_assume_dereferenceable(&single_var, 4); | ||
| return true; | ||
| } | ||
| static_assert(test_single_var(), ""); | ||
|
|
||
| constexpr bool test_exact_boundary() { | ||
| constexpr int arr[10] = {}; | ||
| __builtin_assume_dereferenceable(&arr[9], 4); | ||
| return true; | ||
| } | ||
| static_assert(test_exact_boundary(), ""); | ||
|
|
||
| constexpr bool test_one_over() { // expected-error {{constexpr function never produces a constant expression}} | ||
| constexpr int arr[10] = {}; | ||
| __builtin_assume_dereferenceable(&arr[9], 5); // expected-note 2{{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} | ||
| return true; | ||
| } | ||
| static_assert(test_one_over(), ""); // expected-error {{not an integral constant expression}} expected-note {{in call to}} | ||
|
|
||
| constexpr bool test_zero_size() { | ||
| constexpr int arr[10] = {}; | ||
| __builtin_assume_dereferenceable(arr, 0); | ||
| return true; | ||
| } | ||
| static_assert(test_zero_size(), ""); | ||
|
|
||
| constexpr bool test_struct_member() { | ||
| struct S { | ||
| int x; | ||
| int y; | ||
| }; | ||
| constexpr S s = {1, 2}; | ||
| __builtin_assume_dereferenceable(&s.x, 4); | ||
| return true; | ||
| } | ||
| static_assert(test_struct_member(), ""); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're only testing constant contexts. const int f = (__builtin_assume_dereferenceable((int*)123, 4), 12);
int a = f;should we get a global initializer? I can read 4 bytes from that pointer, right? I'm not 100% sure how this builtin is supposed to work in any case. I initially thought it would return the status but if it doesn't, does it make sense to just ignore it in constant expressions? All actual reads will be checked as usual. @philnik777?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be a valid implementation to just ignore the builtin I guess, but I'd be much happier if it actually checked that bytes are dereferenceable. That's quite useful to catch problems in loops that can return early. e.g. In general my goal is to catch all the UB possible during constant evaluation that the compiler can optimize on. With I'm not quite sure what you're asking in your example. It's not a constant expression at this point, is it?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, I thought the builtin meant "we can dereference that pointer and read X bytes from it", not "starting from that pointer, X bytes can be read" (the latter meaning we can basically to a memcpy from it). What about int b = 10;
const int f = (__builtin_assume_dereferenceable((char*)&b + 1, 3), 12);
int a = f;, shouldn't that work?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that should work if you were able to inspect the byte representation of objects during constant evaluation. It's definitely a correct use of the attribute though.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the example is not a valid constexpr <source>:3:16: note: subexpression not valid in a constant expression
3 | const int f = (__builtin_assume_dereferenceable((char*)&b + 1, 3), 12);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:10:20: error: constexpr variable 'f' must be initialized by a constant expression
10 | constexpr int f = (__builtin_assume_dereferenceable((char*)&b + 1, 3), 12);
| ~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 errors generated.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. after making it constexpr constexpr_assume_dereferenceable.cpp:5:55: note: cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression
5 | constexpr int f = (__builtin_assume_dereferenceable((char*)&b + 1, 3), 12);
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Casts like that are allowed in non-constant contexts and I believe they can also happen when in You have to leave |
||
|
|
||
| constexpr bool test_range_valid() { | ||
| constexpr int range_data[5] = {1, 2, 3, 4, 5}; | ||
| __builtin_assume_dereferenceable(range_data, 5 * sizeof(int)); | ||
| return range_data[0] == 1; | ||
| } | ||
| static_assert(test_range_valid(), ""); | ||
|
|
||
| constexpr bool test_range_invalid() { // expected-error {{constexpr function never produces a constant expression}} | ||
| constexpr int range_data[5] = {1, 2, 3, 4, 5}; | ||
| __builtin_assume_dereferenceable(range_data, 6 * sizeof(int)); // expected-note 2{{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} | ||
| return true; | ||
| } | ||
| static_assert(test_range_invalid(), ""); // expected-error {{not an integral constant expression}} expected-note {{in call to}} | ||
|
|
||
| constexpr int arr1[10] = {}; | ||
| constexpr int valid = (__builtin_assume_dereferenceable(arr1, 40), 12); | ||
|
|
||
| constexpr int invalid = (__builtin_assume_dereferenceable((int*)123, 4), 12); // expected-error {{constexpr variable 'invalid' must be initialized by a constant expression}} expected-note {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} | ||
|
|
||
| constexpr int arr2[5] = {1, 2, 3, 4, 5}; | ||
| constexpr int too_large = (__builtin_assume_dereferenceable(arr2, 6 * sizeof(int)), 12); // expected-error {{constexpr variable 'too_large' must be initialized by a constant expression}} expected-note {{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} | ||
|
|
||
| constexpr int null = (__builtin_assume_dereferenceable(nullptr, 4), 12); // expected-error {{constexpr variable 'null' must be initialized by a constant expression}} expected-note {{read of dereferenced null pointer is not allowed in a constant expression}} | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing RUN line here.