Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4067,6 +4067,47 @@ assignment can happen automatically.
to a variable, have its address taken, or passed into or returned from a
function, because doing so violates bounds safety conventions.

``__builtin_is_modifiable_lvalue``
----------------------------------

``__builtin_is_modifiable_lvalue`` returns true when the argument is an assignable lvalue.

The argument does not have any side-effects resolved.

**Syntax**:

.. code-block:: c

bool __builtin_is_modifiable_lvalue(T expr)

**Examples**:

.. code-block:: c

#define __force_lvalue_expr(x) \
__builtin_choose_expr(__builtin_is_modifiable_lvalue(x), \
x, (void *){ NULL })

#define __free_and_null(__do_free, x) \
({ \
typeof(x) *__ptr = &(x); \
__do_free(*__ptr); \
*__ptr = NULL; \
})
#define __free_and_maybe_null(__how, x) \
__builtin_choose_expr(__builtin_is_modifiable_lvalue(x), \
__free_and_null(__how, __force_lvalue_expr(x)), \
__kfree(x))
#define kfree(x) __free_and_maybe_null(__kfree, x)

**Description**:

When building complex preprocessor macros, it can be helpful to be able
to determine if a given argument is an lvalue to make choices about
how to resolve the given arguments. For example, performing assignments
against an lvalue in a macro becomes possible, but values returned from
function calls can be ignored.

Multiprecision Arithmetic Builtins
----------------------------------

Expand Down
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ Non-comprehensive list of changes in this release

- Support parsing the `cc` operand modifier and alias it to the `c` modifier (#GH127719).
- Added `__builtin_elementwise_exp10`.
- Added `__builtin_is_modifiable_lvalue` to identify assignable arguments in macros.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Added `__builtin_is_modifiable_lvalue` to identify assignable arguments in macros.
- Added `__builtin_is_modifiable_lvalue`.

I’d honestly just omit this since you could just as well use it outside of macros.


New Compiler Flags
------------------
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,13 @@ def IsConstantEvaluated : LangBuiltin<"CXX_LANG"> {
let Prototype = "bool()";
}

def IsLValue : Builtin {
let Spellings = ["__builtin_is_modifiable_lvalue"];
let Attributes = [NoThrow, CustomTypeChecking, UnevaluatedArguments,
Constexpr];
let Prototype = "bool(...)";
}

def IsWithinLifetime : LangBuiltin<"CXX_LANG"> {
let Spellings = ["__builtin_is_within_lifetime"];
let Attributes = [NoThrow, CustomTypeChecking, Consteval];
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3676,6 +3676,10 @@ unsigned FunctionDecl::getBuiltinID(bool ConsiderWrapperFunctions) const {
BuiltinID == Builtin::BI__builtin_counted_by_ref)
return 0;

if (getASTContext().getLangOpts().CPlusPlus &&
BuiltinID == Builtin::BI__builtin_is_modifiable_lvalue)
return 0;

const ASTContext &Context = getASTContext();
if (!Context.BuiltinInfo.isPredefinedLibFunction(BuiltinID))
return BuiltinID;
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12985,6 +12985,16 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
assert(Src.isInt());
return Success((Src.getInt() & (Alignment - 1)) == 0 ? 1 : 0, E);
}
case Builtin::BI__builtin_is_modifiable_lvalue: {
const Expr *Arg = E->getArg(0);
SpeculativeEvaluationRAII SpeculativeEval(Info);
IgnoreSideEffectsRAII Fold(Info);
Comment on lines +12990 to +12991
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SpeculativeEvaluationRAII SpeculativeEval(Info);
IgnoreSideEffectsRAII Fold(Info);

As was already pointed out (I think), we don’t need these here since we’re not doing any evaluating anyway.


SourceLocation OrigLoc = Arg->getExprLoc();
bool IsLValue = Arg->IgnoreCasts()->isModifiableLvalue(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect IgnoreCasts() should be removed here. Otherwise (int)var would be allowed. As we talked about on mastodon. The GCC implemention does not ignore casts.

Info.Ctx, &OrigLoc) == Expr::MLV_Valid;
return Success(IsLValue, E);
}
case Builtin::BI__builtin_align_up: {
APValue Src;
APSInt Alignment;
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2325,6 +2325,7 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
if (BuiltinComplex(TheCall))
return ExprError();
break;
case Builtin::BI__builtin_is_modifiable_lvalue:
case Builtin::BI__builtin_constant_p: {
if (checkArgCount(TheCall, 1))
return true;
Expand Down
50 changes: 50 additions & 0 deletions clang/test/CodeGen/builtin-is-modifiable-lvalue.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -emit-llvm < %s| FileCheck %s

void report(int value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, all of these should just be sema tests using static_assert; maybe 1 or 2 codegen tests just to make sure we can emit it at all would be nice, but that’s it imo.


__attribute__((__noinline__)) int passthru(int a)
{
return a;
}

int global;
const int global_ro;

int checkme(int arg, const int arg_ro)
{
int autovar = 7;

// CHECK: call void @report(i32 noundef 0)
report(__builtin_is_modifiable_lvalue(5));
// CHECK: call void @report(i32 noundef 0)
report(__builtin_is_modifiable_lvalue(checkme));
// CHECK: call void @report(i32 noundef 1)
report(__builtin_is_modifiable_lvalue(arg));
// CHECK: call void @report(i32 noundef 0)
report(__builtin_is_modifiable_lvalue(arg + 5));
// CHECK: call void @report(i32 noundef 0)
report(__builtin_is_modifiable_lvalue(arg_ro));
// CHECK: call void @report(i32 noundef 1)
report(__builtin_is_modifiable_lvalue(autovar));
// CHECK: call void @report(i32 noundef 1)
report(__builtin_is_modifiable_lvalue(global));
// CHECK: call void @report(i32 noundef 0)
report(__builtin_is_modifiable_lvalue(global_ro));
// CHECK: call void @report(i32 noundef 1)
report(__builtin_is_modifiable_lvalue((unsigned char)arg));
// CHECK: call void @report(i32 noundef 0)
report(__builtin_is_modifiable_lvalue(passthru(arg)));
// CHECK: call void @report(i32 noundef 0)
report(__builtin_is_modifiable_lvalue(""));
// CHECK: load
arg++;
// CHECK: call void @report(i32 noundef 0)
// CHECK-NOT: load
report(__builtin_is_modifiable_lvalue(arg++));
// CHECK: call void @report(i32 noundef 0)
// CHECK-NOT: load
report(__builtin_is_modifiable_lvalue(++arg));

// CHECK: load
return arg;
}
17 changes: 17 additions & 0 deletions clang/test/Sema/builtin-is-modifiable-lvalue.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// RUN: %clang_cc1 -std=c99 -fsyntax-only -verify %s
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a pretty bare bones set of tests, I think we can do better.

For example, it has been discussed that there should not be side effects, let's verify that. Above in a comment (int)var was mentioned, we should verify that. How about return values from functions which was also mentioned above, we should verify it is usable in constant expressions etc

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a pretty bare bones set of tests, I think we can do better.

For example, it has been discussed that there should not be side effects, let's verify that. Above in a comment (int)var was mentioned, we should verify that. How about return values from functions which was also mentioned above, we should verify it is usable in constant expressions etc

Agreed. I have not written the testcases for GCC patch yet but I will make sure Kees's clang implementation matches up. Since GCC is in stage 4 and will be for one more month I am not going to submit the GCC patch until then so we have plenty of time to get agreement on the semantics of the builtin and such.


Copy link
Contributor

@frederick-vs-ja frederick-vs-ja Mar 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make more sense to use _Static_assert(__builtin_is_modifiable_lvalue(...), "")?

Also, I think it would be valuable to cover more types.

N1570 6.3.2.1/1, N3467 6.3.3.1/1:

[...] A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const-qualified type.

Edit: Perhaps it's wanted to make this intrinsic accept VLA (and report false) and variably-modified types without evaluating the non-constant array size.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: Perhaps it's wanted to make this intrinsic accept VLA (and report false) and variably-modified types without evaluating the non-constant array size.

That should definitely be possible. I don’t think we really need to evaluate anything here.

int test0(void *ptr) {
return __builtin_is_modifiable_lvalue(); // expected-error {{too few arguments to function call, expected 1, have 0}}
}

int test1(void *ptr) {
return __builtin_is_modifiable_lvalue(ptr); // ok
}

int test2(void *ptr) {
return __builtin_is_modifiable_lvalue(ptr, 5); // expected-error {{too many arguments to function call, expected 1, have 2}}
}

int test_trash(void *ptr) {
return __builtin_is_modifiable_lvalue(trash); // expected-error {{use of undeclared identifier}}
}