Skip to content

[clang][builtin] Implement __builtin_allow_runtime_check #87568

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

Merged
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
48 changes: 48 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3466,6 +3466,54 @@ Query for this feature with ``__has_builtin(__builtin_trap)``.

``__builtin_arm_trap`` is lowered to the ``llvm.aarch64.break`` builtin, and then to ``brk #payload``.

``__builtin_allow_runtime_check``
Copy link
Collaborator

Choose a reason for hiding this comment

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

The docs aren't particularly clear to me -- why would the check at the current program location ever NOT be executed? What strings can be passed in? What compiler options impact it? Will it only accept string literals or are runtime values fine?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've started RFC, as @efriedma-quic suggested
https://discourse.llvm.org/t/rfc-introduce-new-clang-builtin-builtin-allow-runtime-check/78281

It needs to be compiler time lowered, so I think it should literals.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It needs to be compiler time lowered, so I think it should literals.

In that case, I think we want constant expression not just literal, right? e.g.,

constexpr const char *name = "name of check";
if (__builtin_allow_runtime_check(name)) {
}

is still able to be lowered at compile time. The reason I ask is because I'm thinking about template metaprogramming cases where you might want to do something along the lines of:

template <typename Ty>
struct S {
  void mem_fun() {
    if (__builtin_allow_runtime_check(Ty::check_name) && stuff) {
    }
  }
};

but maybe this is not a compelling use case? I don't insist on constant expression support, more just trying to verify that we support the expected usage patterns.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not sure how to do that. I would expected that from __builtin_nan, but it can't do that:
https://godbolt.org/z/hWx47Gqvn

Copy link
Collaborator Author

@vitalybuka vitalybuka Apr 12, 2024

Choose a reason for hiding this comment

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

Similar __builtin_cpu_is, also works only with literals.

The closest thing I see is c++26 static_assert https://en.cppreference.com/w/cpp/language/static_assert
And it's processed in a complicated way EvaluateStaticAssertMessageAsString
https://godbolt.org/z/Gcf74Ysjs

It can try to port that code, but I'd keep as-is. We have a way to improve if use-case is important.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Okay, let's just skip this for now then, thank you!

---------------------------------

``__builtin_allow_runtime_check`` return true if the check at the current
program location should be executed. It is expected to be used to implement
``assert`` like checks which can be safely removed by optimizer.

**Syntax**:

.. code-block:: c++

bool __builtin_allow_runtime_check(const char* kind)

**Example of use**:

.. code-block:: c++

if (__builtin_allow_runtime_check("mycheck") && !ExpensiveCheck()) {
abort();
}

**Description**

``__builtin_allow_runtime_check`` is lowered to ` ``llvm.allow.runtime.check``
<https://llvm.org/docs/LangRef.html#llvm-allow-runtime-check-intrinsic>`_
builtin.

The ``__builtin_allow_runtime_check()`` is expected to be used with control
flow conditions such as in ``if`` to guard expensive runtime checks. The
specific rules for selecting permitted checks can differ and are controlled by
the compiler options.

Flags to control checks:
* ``-mllvm -lower-allow-check-percentile-cutoff-hot=N`` where N is PGO hotness
cutoff in range ``[0, 999999]`` to disallow checks in hot code.
* ``-mllvm -lower-allow-check-random-rate=P`` where P is number in range
``[0.0, 1.0]`` representation probability of keeping a check.
* If both flags are specified, ``-lower-allow-check-random-rate`` takes
precedence.
* If none is specified, ``__builtin_allow_runtime_check`` is lowered as
``true``, allowing all checks.

Parameter ``kind`` is a string literal representing a user selected kind for
guarded check. It's unused now. It will enable kind-specific lowering in future.
E.g. a higher hotness cutoff can be used for more expensive kind of check.

Query for this feature with ``__has_builtin(__builtin_allow_runtime_check)``.

``__builtin_nondeterministic_value``
------------------------------------

Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,12 @@ def Unreachable : Builtin {
let Prototype = "void()";
}

def AllowRuntimeCheck : Builtin {
let Spellings = ["__builtin_allow_runtime_check"];
let Attributes = [NoThrow, Pure, Const];
let Prototype = "bool(char const*)";
}

def ShuffleVector : Builtin {
let Spellings = ["__builtin_shufflevector"];
let Attributes = [NoThrow, Const, CustomTypeChecking];
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3436,6 +3436,15 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
Builder.CreateAssumption(ConstantInt::getTrue(getLLVMContext()), {OBD});
return RValue::get(nullptr);
}
case Builtin::BI__builtin_allow_runtime_check: {
StringRef Kind =
cast<StringLiteral>(E->getArg(0)->IgnoreParenCasts())->getString();
LLVMContext &Ctx = CGM.getLLVMContext();
llvm::Value *Allow = Builder.CreateCall(
CGM.getIntrinsic(llvm::Intrinsic::allow_runtime_check),
llvm::MetadataAsValue::get(Ctx, llvm::MDString::get(Ctx, Kind)));
return RValue::get(Allow);
}
case Builtin::BI__arithmetic_fence: {
// Create the builtin call if FastMath is selected, and the target
// supports the builtin, otherwise just return the argument.
Expand Down
11 changes: 11 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3233,6 +3233,17 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
if (BuiltinCountZeroBitsGeneric(*this, TheCall))
return ExprError();
break;

case Builtin::BI__builtin_allow_runtime_check: {
Expr *Arg = TheCall->getArg(0);
// Check if the argument is a string literal.
if (!isa<StringLiteral>(Arg->IgnoreParenImpCasts())) {
Diag(TheCall->getBeginLoc(), diag::err_expr_not_string_literal)
<< Arg->getSourceRange();
return ExprError();
}
break;
}
}

if (getLangOpts().HLSL && CheckHLSLBuiltinFunctionCall(BuiltinID, TheCall))
Expand Down
29 changes: 29 additions & 0 deletions clang/test/CodeGen/builtin-allow-runtime-check.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 4
// RUN: %clang_cc1 -cc1 -triple x86_64-pc-linux-gnu -emit-llvm -o - %s | FileCheck %s

static_assert(__has_builtin(__builtin_allow_runtime_check), "");

// CHECK-LABEL: define dso_local noundef zeroext i1 @_Z4testv(
// CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
// CHECK-NEXT: entry:
// CHECK-NEXT: [[TMP0:%.*]] = call i1 @llvm.allow.runtime.check(metadata !"mycheck")
// CHECK-NEXT: ret i1 [[TMP0]]
//
bool test() {
return __builtin_allow_runtime_check("mycheck");
}

// CHECK-LABEL: define dso_local noundef zeroext i1 @_Z10test_twicev(
// CHECK-SAME: ) #[[ATTR0]] {
// CHECK-NEXT: entry:
// CHECK-NEXT: [[TMP0:%.*]] = call i1 @llvm.allow.runtime.check(metadata !"mycheck")
// CHECK-NEXT: [[CONV:%.*]] = zext i1 [[TMP0]] to i32
// CHECK-NEXT: [[TMP1:%.*]] = call i1 @llvm.allow.runtime.check(metadata !"mycheck")
// CHECK-NEXT: [[CONV1:%.*]] = zext i1 [[TMP1]] to i32
// CHECK-NEXT: [[OR:%.*]] = or i32 [[CONV]], [[CONV1]]
// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[OR]], 0
// CHECK-NEXT: ret i1 [[TOBOOL]]
//
bool test_twice() {
return __builtin_allow_runtime_check("mycheck") | __builtin_allow_runtime_check("mycheck");
}
24 changes: 24 additions & 0 deletions clang/test/Sema/builtin-allow-runtime-check.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// RUN: %clang_cc1 -fsyntax-only -triple x86_64-pc-linux-gnu -verify %s
// RUN: %clang_cc1 -fsyntax-only -triple aarch64-linux-gnu -verify %s

extern const char *str;

int main(void) {
int r = 0;

r |= __builtin_allow_runtime_check(); // expected-error {{too few arguments to function call}}

r |= __builtin_allow_runtime_check(str); // expected-error {{expression is not a string literal}}

r |= __builtin_allow_runtime_check(5); // expected-error {{incompatible integer to pointer conversion}} expected-error {{expression is not a string literal}}

r |= __builtin_allow_runtime_check("a", "b"); // expected-error {{too many arguments to function call}}

r |= __builtin_allow_runtime_check("");

r |= __builtin_allow_runtime_check("check");

str = __builtin_allow_runtime_check("check2"); // expected-error {{incompatible integer to pointer conversion}}

return r;
}