This is an archive of the discontinued LLVM Phabricator instance.

Use is.constant intrinsic for __builtin_constant_p
AbandonedPublic

Authored by void on Oct 3 2018, 5:15 PM.

Details

Summary

A __builtin_constant_p may end up with a constant after inlining. Use
the is.constant intrinsic if it's a variable that's in a context where it may
resolve to a constant, e.g., an argument to a function after inlining.

See: https://reviews.llvm.org/D4276

Diff Detail

Repository
rC Clang

Event Timeline

void created this revision.Oct 3 2018, 5:15 PM
void added a comment.Oct 3 2018, 5:19 PM

This is related to D4276, which I believe has been deleted. However, I believe that a modified version of that patch will work.

The idea of this patch is *not* to be fully compatible with gcc's implementation, but to expand clang's implementation of __builtin_constant_p without breaking existing code.

This has been tested by successfully building (and running) the Linux kernel for both ARM and X86.

void edited the summary of this revision. (Show Details)Oct 3 2018, 5:24 PM
lib/Sema/SemaExpr.cpp
15604

../tools/clang/lib/Sema/SemaExpr.cpp:15619:13: warning: private field 'S' is not used [-Wunused-private-field]

Sema &S;
      ^
void updated this revision to Diff 168356.Oct 4 2018, 1:21 PM

Removed unused field.

nickdesaulniers added inline comments.Oct 4 2018, 1:22 PM
lib/Sema/SemaExpr.cpp
15621–15623

std::for_each?

nickdesaulniers added inline comments.Oct 4 2018, 1:24 PM
lib/Sema/SemaExpr.cpp
15621–15623

Sorry, posted that comment as you uploaded the next version. This should be highlighting L15618 to L15620.

void added inline comments.Oct 4 2018, 1:25 PM
lib/Sema/SemaExpr.cpp
15621–15623

That's no used anywhere else in the source code. Maybe there's another mechanism that they use?

nickdesaulniers added inline comments.Oct 4 2018, 1:29 PM
lib/Sema/SemaExpr.cpp
15621–15623

Oh, looks like it was only added to C++17; I think Clang+LLVM use a lower language version. Did C++ stl really not have a way to apply the same function over an iterator until C++17?

nickdesaulniers added inline comments.Oct 4 2018, 1:39 PM
lib/Sema/SemaExpr.cpp
15621–15623

At least a range for would make this more concise:

for (auto& II : *E)
  Visit(II);
rsmith added a subscriber: rsmith.Oct 4 2018, 1:41 PM

Rather than adding a CanDelayEvaluation flag to call expressions, I think it would be substantially better to introduce a new Expr wrapper node for expressions that are required to be constant expressions. (That'd eventually also give us a place to cache the evaluated value of such an expression, where today we recompute it each time it's needed.) Essentially, you would add a ConstExpr node (or similar), teach IgnoreImplicit and friends to step over it, and add it in the places where we semantically require an expression to be a constant expression. This has various benefits: there are other upcoming language features (in C++20) that require this knowledge and don't necessarily have a CallExpr to tie it to, and it gives us a place to stash an evaluated value, and it gives IR generation a simple way to detect expressions that it can constant-evaluate rather than emitting.

When the evaluator steps into such an expression, it can track that it's done so, and ensure that it always produces a constant value for any __builtin_constant_p calls in that scope.

include/clang/AST/Expr.h
2290

It's not reasonable to make all CallExprs sizeof(void*) bytes larger for this. If we really need this, you can track it it in the CallExprBits on the base class. (But you'll also need to extend at least the Serialization and ASTImporter code to handle it, and probably TreeTransform too.)

But I don't think you need this.

lib/AST/ExprConstant.cpp
8162

Hmm, OK. Per https://bugs.llvm.org/show_bug.cgi?id=4898#c38, the correct check would be whether the reason we found the expression to be non-constant was that it tried to read the value of a local variable or function parameter. Eg, for:

void f(int x, int y) {
  bool k = __builtin_constant_p(3 + x < 5 * y);

... we should defer the __builtin_constant_p until after optimization. (And we should never do this if the expression has side-effects.) But given that your goal is merely to improve the status quo, not to exactly match GCC, this may be sufficient.

You should at least check that the variable is non-volatile, though. (More generally, check that Arg doesn't have side-effects.)

8162–8164

Your canDelayEvaluation check does not appear to cover several of the cases we'd need to check for here. Eg:

void f(int n) {
  enum E { a = __builtin_constant_p(n) }; // ok, a == 0, not an error because a's value is non-constant
8164

It's not correct to return false here without producing a diagnostic explaining why the evaluation is non-constant. You can use Info.FFDiag() to produce a default "invalid subexpression" diagnostic, which is probably sufficient given that we won't actually show the diagnostic to the user in most cases.

lib/Sema/SemaExpr.cpp
5694

Why are you marking this? This isn't (necessarily) a context where a constant expression is required. (In C, the overall initializer for a global would be, but a local-scope compound literal would not.)

Also, this should be in the Build function, not in the ActOn function.

5799

Likewise here, I don't see the justification for this.

nickdesaulniers added inline comments.Oct 4 2018, 1:41 PM
test/Sema/builtins.c
125

such as

Otherwise it's possible to read this as the non-constants being resolved to a file scoped array.

void marked 9 inline comments as done.Oct 4 2018, 3:31 PM

Essentially, you would add a ConstExpr node (or similar), teach IgnoreImplicit and friends to step over it

I've been trying to do this to no avail. Here's the code I was working with:

//===----------------------------------------------------------------------===//
// Wrapper Expressions.
//===----------------------------------------------------------------------===//

// ConstExpr is a wrapper class indicating that the expression must be constant.
// This is useful for something like __builtin_constant_p(), which in some
// contexts is required to be constant before generating LLVM IR.
template <typename T>
class ConstExpr : public Expr {
  T *E;

 public:
  ConstExpr(Expr *E) : E(E) {}

  T *getSubExpr() { return E; }
  const T *getSubExpr() const { return E; }

  static bool classof(const Stmt *T) {
    return T->getStmtClass() == ConstExprClass;
  }
};

I did it this way because otherwise I would have to forward all calls to individual expression, which is ugly and made me want to cry. The issue is getting the ConstExprClass variable. Those variables are automatically generated via macro magick, but those macros aren't set up to handle templates.

Do I need to mangle the macros to support templates, or is there another way to achieve this?

lib/AST/ExprConstant.cpp
8162

I had the side effect check in there before. I removed it for some reason. I'll re-add it.

lib/Sema/SemaExpr.cpp
5694

The name may be misleading. It's looking to see if it should mark whether it can delay evaluation or not. I'll change its name to be clearer.

5799

I can't find an equivalent "Build..." for InitList expressions. Could you point me to it?

void updated this revision to Diff 168395.Oct 4 2018, 3:33 PM
void marked an inline comment as done.
void added a comment.Oct 7 2018, 1:44 AM

@rsmith I've been implementing your suggestion, but it's quickly becoming ridiculous. Just to implement a ConstExpr wrapper class, I need to touch ~23 files, virtually all changes being boilerplate code to forward the action to the wrapped expression. And this is before I add the code in this patch that implements the feature. While it would be nice to use LLVM's type system to determine if an expression is expected to be constant, it appears that doing that is much worse than adding the information to the bits field you mentioned.

I don't think that was the approach @rsmith was suggesting; you don't need to wrap every possible kind of expression. Rather, at the point where the expression is required to be constant, add a single expression which wraps the entire expression tree to indicate that. So for "constexpr int c = 10+2;", the expression tree for the initializer is something like ConstExpr(BinaryOperator(IntegerLiteral, IntegerLiteral)).

void added a comment.Oct 8 2018, 12:09 PM

No, I understood that. But the issue is that you then need to specify this new expression class in over 23 different files: various macros, switch statements, etc.

void added a comment.Oct 8 2018, 12:26 PM

Here's a WIP patch to show what I'm talking about.

void updated this revision to Diff 168708.Oct 8 2018, 1:56 PM

I updated the patch that implements putting the "can delay evaluation" bit into the CallExprBitfields. PTAL.

void updated this revision to Diff 168712.Oct 8 2018, 2:54 PM
void abandoned this revision.Oct 30 2018, 10:44 PM