patch: new attribute enable_if
Nick Lewycky
nicholas at mxc.ca
Wed Oct 2 02:26:43 PDT 2013
Updated patch attached. Almost all review comments implemented!
Richard Smith wrote:
> Hi Nick,
>
> This looks really promising. Do you yet know whether this is enough to
> implement *all* the functionality of -DFORTIFY_SOURCE?
I asked Geremy to try implementing it with the patch I posted, and he
says he has -DFORTIFY_SOURCE=1 done, and forsees no problems with
-DFORTIFY_SOURCE=2.
> One big thing missing here is documentation. That should serve to both
> motivate the feature (and explain why it's sufficiently valuable for us
> to have and maintain this extension), and to describe how it's intended
> to be used, plus what we actually guarantee in terms of its semantics.
Great point. I've started on this, but I'm not really sure yet what the
semantics ought to be ...
> I'm a bit concerned that the semantics of the attribute currently
> involve "try as hard as you can to fold this to a constant, and even
> ignore side-effects, but if constant-folding it fails, don't choose this
> overload" -- if we change how hard we're willing to try when
> constant-folding in the future, it might break code relying on this
> feature. How essential is it that we support conditions that have
> side-effects?
On the one hand, I will not support anything that actually changes state
inside the expression. On the other hand, imagine trying to express
strlen(foo) inside an enable_if expression in C mode where you can't
write constexpr functions, loops, etc. (No, statement expressions are
not allowed inside the enable_if attribute, I tried that.)
I've asked Geremy to let me know what he's actually using in his
expressions. If he isn't using anything that isn't a language-defined
constant expression then that's the obvious way to go.
> --- include/clang/Sema/TemplateDeduction.h(revision 191171)
> +++ include/clang/Sema/TemplateDeduction.h(working copy)
> @@ -208,6 +209,10 @@
> /// if any.
> Expr *getExpr();
> + /// When FailureKind is ovl_fail_enable_if, the attribute which caused
> + /// this function to not be viable.
> + EnableIfAttr *EnableIfAttribute;
>
> Can you reuse the existing Data pointer for this, rather than growing
> DeductionFailureInfo?
Done.
> --- lib/AST/ExprConstant.cpp(revision 191171)
> +++ lib/AST/ExprConstant.cpp(working copy)
> @@ -5803,26 +5810,35 @@
> //
> // Otherwise, it returns 0.
> //
> + // In the former case, when building with -O, GCC will sometimes
> return 1 if
> + // a constant numeric value is used as an argument to an inline
> function, and
> + // the corresponding parameter is passed to __builtin_constant_p. In the
> + // latter case, it never will. We pretend -O is always specified when
> checking
> + // constexpr function parameters.
> + //
> // FIXME: GCC also intends to return 1 for literals of aggregate
> types, but
> // its support for this does not currently work.
> if (ArgType->isIntegralOrEnumerationType()) {
> - Expr::EvalResult Result;
> - if (!Arg->EvaluateAsRValue(Result, Ctx) || Result.HasSideEffects)
> + llvm::SmallVector<PartialDiagnosticAt, 8> Diag;
> + SpeculativeEvaluationRAII Speculate(Info, &Diag);
> +
> + Info.EvalStatus.HasSideEffects = false;
> + APValue Result;
> + if (!EvaluateAsRValue(Info, Arg, Result) ||
> Info.EvalStatus.HasSideEffects)
> return false;
>
>
> Reusing the current 'Info' here will mean that __builtin_constant_p's
> result will depend on the context as well as its operand. That may be
> what you intended here, but is a big enough change to be worth
> separately calling out (and separately committing). For instance:
>
> constexpr bool f(int n) { return __builtin_constant_p(n); }
> constexpr bool a = f(0);
> bool b = f(0);
> extern int n;
> bool c = f(n);
>
> I think, with your patch, we'll guarantee to initialize 'a' to true and
> 'c' to false, and 'b' may or may not be 'true'. Prior to your patch, all
> three will be false. IIRC, that basically matches g++'s behavior, but
> it'd be good to double-check.
All true. And it does match g++.
I am extremely nervous about changing __builtin_constant_p's behaviour,
even if the result does more closely match gcc in this particular instance.
My ideal would be to have a way to get builtin_constant_p continue to
have access to the function parameters as I need for enable_if
evaluation with no other visible changes. It looks like doing that would
require making a copy of the EvalInfo and that would break all the
CallStackFrames which hold it by reference, so I'd need to copy them
too, yuck.
What do you think I should do here? Try to land this one bit on its own?
Try to make a deep copy of the EvalInfo state? Something more clever?
> You'll also need to turn off the ability to mutate local state during
> the evaluation of the subexpression in C++1y mode.
Can I punt this to a future patch? It might turn out to be a small
number of lines of code, but I'm worried about the cognitive load of
adding yet another "thread foo through ExprConstant" getting into this
patch.
> --- lib/Parse/ParseDecl.cpp(revision 191171)
> +++ lib/Parse/ParseDecl.cpp(working copy)
> @@ -226,6 +228,21 @@
> ParseTypeTagForDatatypeAttribute(*AttrName, AttrNameLoc, Attrs,
> EndLoc);
> return;
> }
> + // These may refer to the function arguments, but need to be parsed
> early to
> + // participate in determining whether it's a redeclaration.
> + llvm::OwningPtr<ParseScope> PrototypeScope;
> + if (AttrName->isStr("enable_if") && D && D->isFunctionDeclarator()) {
> + DeclaratorChunk::FunctionTypeInfo FTI = D->getFunctionTypeInfo();
> + PrototypeScope.reset(new ParseScope(this,
> Scope::FunctionPrototypeScope |
> + Scope::FunctionDeclarationScope |
> + Scope::DeclScope));
> + for (unsigned i = 0; i != FTI.NumArgs; ++i) {
> + ParmVarDecl *Param = cast<ParmVarDecl>(FTI.ArgInfo[i].Param);
> + getCurScope()->AddDecl(Param);
> + if (Param->getDeclName())
> + Actions.IdResolver.AddDecl(Param);
>
> Filling in the scope should be done in Sema/, not in Parse/.
Done.
> Can we avoid redoing the enable_if check in SemaChecking if we've
> already been through overload resolution (and done the checking there)?
Done!
> --- lib/Sema/SemaOverload.cpp(revision 191171)
> +++ lib/Sema/SemaOverload.cpp(working copy)
> @@ -1079,6 +1079,21 @@
> return true;
> }
> + // enable_if attributes are an order-sensitive part of the signature.
> + for (specific_attr_iterator<EnableIfAttr>
> + NewI = New->specific_attr_begin<EnableIfAttr>(),
> + NewE = New->specific_attr_end<EnableIfAttr>(),
> + OldI = Old->specific_attr_begin<EnableIfAttr>(),
> + OldE = Old->specific_attr_end<EnableIfAttr>();
> + NewI != NewE && OldI != OldE; ++NewI, ++OldI) {
> + if (NewI == NewE || OldI == OldE)
> + return true;
> + llvm::FoldingSetNodeID NewID, OldID;
> + NewI->getCond()->Profile(NewID, Context, true);
> + OldI->getCond()->Profile(OldID, Context, true);
> + return !(NewID == OldID);
>
> I don't think you meant to return here in the case where NewID == OldID.
> (You're only checking the first enable_if attribute on each function.)
Whoops! I had that right previously, then broke it during self-review.
> +EnableIfAttr *Sema::CheckEnableIf(FunctionDecl *Function, ArrayRef<Expr
> *> Args,
> + bool MissingImplicitThis) {
> +
> + for (specific_attr_iterator<EnableIfAttr>
> + I = Function->specific_attr_begin<EnableIfAttr>(),
> + E = Function->specific_attr_end<EnableIfAttr>(); I != E; ++I) {
> + APValue Result;
> +
> + SFINAETrap Trap(*this);
> +
> + // Convert the arguments.
> + SmallVector<Expr *, 16> Params;
>
> ConvertedArgs maybe? These are not parameters.
Done.
> --- test/Sema/enable_if.c(revision 0)
> +++ test/Sema/enable_if.c(working copy)
> @@ -0,0 +1,93 @@
> +// RUN: %clang_cc1 %s -verify -Wno-gcc-compat
>
>
> Presumably this warning flag is for the 'attributes on function
> definitions' warning? Maybe we should only provide that for
> GCC-compatible attributes. We already suppress it for the thread-safety
> attributes, IIRC.
Done. We now have Parser::IsAttributeSupportedByGCC.
> [...]
> +// This f() is never chosen because the constant evaluator fails on
> non-const global, but there's no error for that.
> +// FIXME: we should issue an error at the point of the enable_if
> declaration
> +int global;
> +void f(int n) __attribute__((enable_if(global == 0, "chosen when
> 'global' is zero"))); // expected-note{{candidate ignored: chosen when
> 'global' is zero}}
>
>
> You can use isPotentialConstantExpr for the interesting half of this
> (where the enable_if expression is never constant). I'm not sure what
> you want to do with the boring half (where the expression evaluates to
> 'false' -- or 'true' -- independently of the function's parameters'
> values or template arguments).
Thanks for the pointer, I've implemented this.
I'm okay with expressions that evaluate to true or to false. It could be
reasonable if it's expanded from a preprocessor macro or somesuch.
It'd be nice to have warnings for "{none|two or more} of the overloads
are enabled" but I think because of the way we parse we'd get the errors
at the call-sites before we could detect this.
Nick
-------------- next part --------------
A non-text attachment was scrubbed...
Name: enable-if-3.patch
Type: text/x-diff
Size: 41802 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20131002/b1f13eb5/attachment.patch>
More information about the cfe-commits
mailing list