Skip to content
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

[Clang] Constant Expressions inside of GCC' asm strings #131003

Merged
merged 16 commits into from
Mar 17, 2025
26 changes: 26 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
@@ -1958,6 +1958,32 @@ references can be used instead of numeric references.
return -1;
}


Constexpr strings in GNU ASM statememts
=======================================

In C++11 mode (and greater), Clang supports specifying the template,
constraints, and clobber strings with a parenthesized constant expression
producing an object with the following member functions

.. code-block:: c++

constexpr const char* data() const;
constexpr size_t size() const;

such as ``std::string``, ``std::string_view``, ``std::vector<char>``.
This mechanism follow the same rules as ``static_assert`` messages in
C++26, see ``[dcl.pre]/p12``.

Query for this feature with ``__has_extension(gnu_asm_constexpr_strings)``.

.. code-block:: c++

int foo() {
asm((std::string_view("nop")) ::: (std::string_view("memory")));
}


Objective-C Features
====================

9 changes: 9 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
@@ -74,6 +74,15 @@ What's New in Clang |release|?
C++ Language Changes
--------------------

- Similarly to GCC, Clang now supports constant expressions in
the strings of a GNU ``asm`` statement.

.. code-block:: c++

int foo() {
asm((std::string_view("nop")) ::: (std::string_view("memory")));
}

C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^

4 changes: 4 additions & 0 deletions clang/include/clang/AST/Expr.h
Original file line number Diff line number Diff line change
@@ -787,6 +787,10 @@ class Expr : public ValueStmt {
const Expr *PtrExpression, ASTContext &Ctx,
EvalResult &Status) const;

bool EvaluateCharRangeAsString(APValue &Result, const Expr *SizeExpression,
const Expr *PtrExpression, ASTContext &Ctx,
EvalResult &Status) const;

/// If the current Expr can be evaluated to a pointer to a null-terminated
/// constant string, return the constant string (without the terminating
/// null).
8 changes: 4 additions & 4 deletions clang/include/clang/AST/RecursiveASTVisitor.h
Original file line number Diff line number Diff line change
@@ -2410,15 +2410,15 @@ DEF_TRAVERSE_DECL(ImplicitConceptSpecializationDecl, {
}

DEF_TRAVERSE_STMT(GCCAsmStmt, {
TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getAsmString());
TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getAsmStringExpr());
for (unsigned I = 0, E = S->getNumInputs(); I < E; ++I) {
TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getInputConstraintLiteral(I));
TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getInputConstraintExpr(I));
}
for (unsigned I = 0, E = S->getNumOutputs(); I < E; ++I) {
TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getOutputConstraintLiteral(I));
TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getOutputConstraintExpr(I));
}
for (unsigned I = 0, E = S->getNumClobbers(); I < E; ++I) {
TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getClobberStringLiteral(I));
TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getClobberExpr(I));
}
// children() iterates over inputExpr and outputExpr.
})
60 changes: 28 additions & 32 deletions clang/include/clang/AST/Stmt.h
Original file line number Diff line number Diff line change
@@ -3193,7 +3193,7 @@ class AsmStmt : public Stmt {
/// getOutputConstraint - Return the constraint string for the specified
/// output operand. All output constraints are known to be non-empty (either
/// '=' or '+').
StringRef getOutputConstraint(unsigned i) const;
std::string getOutputConstraint(unsigned i) const;

/// isOutputPlusConstraint - Return true if the specified output constraint
/// is a "+" constraint (which is both an input and an output) or false if it
@@ -3214,14 +3214,14 @@ class AsmStmt : public Stmt {

/// getInputConstraint - Return the specified input constraint. Unlike output
/// constraints, these can be empty.
StringRef getInputConstraint(unsigned i) const;
std::string getInputConstraint(unsigned i) const;

const Expr *getInputExpr(unsigned i) const;

//===--- Other ---===//

unsigned getNumClobbers() const { return NumClobbers; }
StringRef getClobber(unsigned i) const;
std::string getClobber(unsigned i) const;

static bool classof(const Stmt *T) {
return T->getStmtClass() == GCCAsmStmtClass ||
@@ -3302,21 +3302,20 @@ class GCCAsmStmt : public AsmStmt {
friend class ASTStmtReader;

SourceLocation RParenLoc;
StringLiteral *AsmStr;
Expr *AsmStr;

// FIXME: If we wanted to, we could allocate all of these in one big array.
StringLiteral **Constraints = nullptr;
StringLiteral **Clobbers = nullptr;
Expr **Constraints = nullptr;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Another suggestion to add to the lets make these sane bug report, we could probably use ArrayRef for these to make the pointer-pointer less difficult to use.

Expr **Clobbers = nullptr;
IdentifierInfo **Names = nullptr;
unsigned NumLabels = 0;

public:
GCCAsmStmt(const ASTContext &C, SourceLocation asmloc, bool issimple,
bool isvolatile, unsigned numoutputs, unsigned numinputs,
IdentifierInfo **names, StringLiteral **constraints, Expr **exprs,
StringLiteral *asmstr, unsigned numclobbers,
StringLiteral **clobbers, unsigned numlabels,
SourceLocation rparenloc);
IdentifierInfo **names, Expr **constraints, Expr **exprs,
Expr *asmstr, unsigned numclobbers, Expr **clobbers,
unsigned numlabels, SourceLocation rparenloc);

/// Build an empty inline-assembly statement.
explicit GCCAsmStmt(EmptyShell Empty) : AsmStmt(GCCAsmStmtClass, Empty) {}
@@ -3326,9 +3325,11 @@ class GCCAsmStmt : public AsmStmt {

//===--- Asm String Analysis ---===//

const StringLiteral *getAsmString() const { return AsmStr; }
StringLiteral *getAsmString() { return AsmStr; }
void setAsmString(StringLiteral *E) { AsmStr = E; }
const Expr *getAsmStringExpr() const { return AsmStr; }
Expr *getAsmStringExpr() { return AsmStr; }
void setAsmStringExpr(Expr *E) { AsmStr = E; }

std::string getAsmString() const;

/// AsmStringPiece - this is part of a decomposed asm string specification
/// (for use with the AnalyzeAsmString function below). An asm string is
@@ -3397,14 +3398,12 @@ class GCCAsmStmt : public AsmStmt {
return {};
}

StringRef getOutputConstraint(unsigned i) const;
std::string getOutputConstraint(unsigned i) const;

const StringLiteral *getOutputConstraintLiteral(unsigned i) const {
return Constraints[i];
}
StringLiteral *getOutputConstraintLiteral(unsigned i) {
const Expr *getOutputConstraintExpr(unsigned i) const {
return Constraints[i];
}
Expr *getOutputConstraintExpr(unsigned i) { return Constraints[i]; }

Expr *getOutputExpr(unsigned i);

@@ -3425,12 +3424,12 @@ class GCCAsmStmt : public AsmStmt {
return {};
}

StringRef getInputConstraint(unsigned i) const;
std::string getInputConstraint(unsigned i) const;

const StringLiteral *getInputConstraintLiteral(unsigned i) const {
const Expr *getInputConstraintExpr(unsigned i) const {
return Constraints[i + NumOutputs];
}
StringLiteral *getInputConstraintLiteral(unsigned i) {
Expr *getInputConstraintExpr(unsigned i) {
return Constraints[i + NumOutputs];
}

@@ -3441,6 +3440,8 @@ class GCCAsmStmt : public AsmStmt {
return const_cast<GCCAsmStmt*>(this)->getInputExpr(i);
}

static std::string ExtractStringFromGCCAsmStmtComponent(const Expr *E);

//===--- Labels ---===//

bool isAsmGoto() const {
@@ -3489,12 +3490,9 @@ class GCCAsmStmt : public AsmStmt {
private:
void setOutputsAndInputsAndClobbers(const ASTContext &C,
IdentifierInfo **Names,
StringLiteral **Constraints,
Stmt **Exprs,
unsigned NumOutputs,
unsigned NumInputs,
unsigned NumLabels,
StringLiteral **Clobbers,
Expr **Constraints, Stmt **Exprs,
unsigned NumOutputs, unsigned NumInputs,
unsigned NumLabels, Expr **Clobbers,
unsigned NumClobbers);

public:
@@ -3505,12 +3503,10 @@ class GCCAsmStmt : public AsmStmt {
/// This returns -1 if the operand name is invalid.
int getNamedOperand(StringRef SymbolicName) const;

StringRef getClobber(unsigned i) const;
std::string getClobber(unsigned i) const;

StringLiteral *getClobberStringLiteral(unsigned i) { return Clobbers[i]; }
const StringLiteral *getClobberStringLiteral(unsigned i) const {
return Clobbers[i];
}
Expr *getClobberExpr(unsigned i) { return Clobbers[i]; }
const Expr *getClobberExpr(unsigned i) const { return Clobbers[i]; }

SourceLocation getBeginLoc() const LLVM_READONLY { return AsmLoc; }
SourceLocation getEndLoc() const LLVM_READONLY { return RParenLoc; }
5 changes: 4 additions & 1 deletion clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
@@ -336,7 +336,10 @@ def warn_cxx20_compat_label_end_of_compound_statement : Warning<
def err_address_of_label_outside_fn : Error<
"use of address-of-label extension outside of a function body">;
def err_asm_operand_wide_string_literal : Error<
"cannot use %select{unicode|wide|an empty}0 string literal in 'asm'">;
"cannot use %select{unicode|wide}0 string literal in 'asm'">;

def err_asm_expected_string : Error<
"expected string literal %select{or parenthesized constant expression |}0in 'asm'">;
def err_expected_selector_for_method : Error<
"expected selector for Objective-C method">;
def err_expected_property_name : Error<"expected property name">;
36 changes: 23 additions & 13 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
@@ -1663,23 +1663,30 @@ def err_static_assert_requirement_failed : Error<
"static assertion failed due to requirement '%0'%select{: %2|}1">;
def note_expr_evaluates_to : Note<
"expression evaluates to '%0 %1 %2'">;
def err_static_assert_invalid_message : Error<
"the message in a static assertion must be a string literal or an "


def subst_user_defined_msg : TextSubstitution<
"%select{the message|the expression}0 in "
"%select{a static assertion|this asm operand}0">;

def err_user_defined_msg_invalid : Error<
"%sub{subst_user_defined_msg}0 must be a string literal or an "
"object with 'data()' and 'size()' member functions">;
def err_static_assert_missing_member_function : Error<
"the message object in this static assertion is missing %select{"
def err_user_defined_msg_missing_member_function : Error<
"the %select{message|string}0 object in "
"%select{this static assertion|this asm operand}0 is missing %select{"
"a 'size()' member function|"
"a 'data()' member function|"
"'data()' and 'size()' member functions}0">;
def err_static_assert_invalid_mem_fn_ret_ty : Error<
"the message in a static assertion must have a '%select{size|data}0()' member "
"function returning an object convertible to '%select{std::size_t|const char *}0'">;
def warn_static_assert_message_constexpr : Warning<
"the message in this static assertion is not a "
"constant expression">,
"'data()' and 'size()' member functions}1">;
def err_user_defined_msg_invalid_mem_fn_ret_ty : Error<
"%sub{subst_user_defined_msg}0 must have a '%select{size|data}1()' member "
"function returning an object convertible to '%select{std::size_t|const char *}1'">;
def warn_user_defined_msg_constexpr : Warning<
"%select{the message|the expression}0 in "
"%select{this static assertion|this asm operand}0 is not a constant expression">,
DefaultError, InGroup<DiagGroup<"invalid-static-assert-message">>;
def err_static_assert_message_constexpr : Error<
"the message in a static assertion must be produced by a "
def err_user_defined_msg_constexpr : Error<
"%sub{subst_user_defined_msg}0 must be produced by a "
"constant expression">;

def warn_consteval_if_always_true : Warning<
@@ -9514,6 +9521,9 @@ def warn_redefine_extname_not_applied : Warning<

// inline asm.
let CategoryName = "Inline Assembly Issue" in {
def err_asm_operand_empty_string : Error<
"cannot use an empty string literal in 'asm'">;

def err_asm_pmf_through_constraint_not_permitted
: Error<"cannot pass a pointer-to-member through register-constrained "
"inline assembly parameter">;
1 change: 1 addition & 0 deletions clang/include/clang/Basic/Features.def
Original file line number Diff line number Diff line change
@@ -306,6 +306,7 @@ EXTENSION(statement_attributes_with_gnu_syntax, true)
EXTENSION(gnu_asm, LangOpts.GNUAsm)
EXTENSION(gnu_asm_goto_with_outputs, LangOpts.GNUAsm)
EXTENSION(gnu_asm_goto_with_outputs_full, LangOpts.GNUAsm)
EXTENSION(gnu_asm_constexpr_strings, LangOpts.GNUAsm && LangOpts.CPlusPlus11)
EXTENSION(matrix_types, LangOpts.MatrixTypes)
EXTENSION(matrix_types_scalar_division, true)
EXTENSION(cxx_attributes_on_using_declarations, LangOpts.CPlusPlus11)
20 changes: 17 additions & 3 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
@@ -5585,9 +5585,15 @@ class Sema final : public SemaBase {
void ActOnFinishDelayedCXXMethodDeclaration(Scope *S, Decl *Method);
void ActOnFinishDelayedMemberInitializers(Decl *Record);

bool EvaluateStaticAssertMessageAsString(Expr *Message, std::string &Result,
ASTContext &Ctx,
bool ErrorOnInvalidMessage);
enum class StringEvaluationContext { StaticAssert = 0, Asm = 1 };

bool EvaluateAsString(Expr *Message, APValue &Result, ASTContext &Ctx,
StringEvaluationContext EvalContext,
bool ErrorOnInvalidMessage);
bool EvaluateAsString(Expr *Message, std::string &Result, ASTContext &Ctx,
StringEvaluationContext EvalContext,
bool ErrorOnInvalidMessage);

Decl *ActOnStaticAssertDeclaration(SourceLocation StaticAssertLoc,
Expr *AssertExpr, Expr *AssertMessageExpr,
SourceLocation RParenLoc);
@@ -11040,6 +11046,7 @@ class Sema final : public SemaBase {
///@{

public:
ExprResult ActOnGCCAsmStmtString(Expr *Stm, bool ForAsmLabel);
StmtResult ActOnGCCAsmStmt(SourceLocation AsmLoc, bool IsSimple,
bool IsVolatile, unsigned NumOutputs,
unsigned NumInputs, IdentifierInfo **Names,
@@ -15328,6 +15335,13 @@ void Sema::PragmaStack<Sema::AlignPackInfo>::Act(SourceLocation PragmaLocation,
PragmaMsStackAction Action,
llvm::StringRef StackSlotLabel,
AlignPackInfo Value);

inline const StreamingDiagnostic &
operator<<(const StreamingDiagnostic &DB, Sema::StringEvaluationContext Ctx) {
DB << llvm::to_underlying(Ctx);
return DB;
}

} // end namespace clang

#endif
12 changes: 6 additions & 6 deletions clang/lib/AST/ASTImporter.cpp
Original file line number Diff line number Diff line change
@@ -6821,25 +6821,25 @@ ExpectedStmt ASTNodeImporter::VisitGCCAsmStmt(GCCAsmStmt *S) {
Names.push_back(ToII);
}

SmallVector<StringLiteral *, 4> Clobbers;
SmallVector<Expr *, 4> Clobbers;
for (unsigned I = 0, E = S->getNumClobbers(); I != E; I++) {
if (auto ClobberOrErr = import(S->getClobberStringLiteral(I)))
if (auto ClobberOrErr = import(S->getClobberExpr(I)))
Clobbers.push_back(*ClobberOrErr);
else
return ClobberOrErr.takeError();

}

SmallVector<StringLiteral *, 4> Constraints;
SmallVector<Expr *, 4> Constraints;
for (unsigned I = 0, E = S->getNumOutputs(); I != E; I++) {
if (auto OutputOrErr = import(S->getOutputConstraintLiteral(I)))
if (auto OutputOrErr = import(S->getOutputConstraintExpr(I)))
Constraints.push_back(*OutputOrErr);
else
return OutputOrErr.takeError();
}

for (unsigned I = 0, E = S->getNumInputs(); I != E; I++) {
if (auto InputOrErr = import(S->getInputConstraintLiteral(I)))
if (auto InputOrErr = import(S->getInputConstraintExpr(I)))
Constraints.push_back(*InputOrErr);
else
return InputOrErr.takeError();
@@ -6861,7 +6861,7 @@ ExpectedStmt ASTNodeImporter::VisitGCCAsmStmt(GCCAsmStmt *S) {
ExpectedSLoc AsmLocOrErr = import(S->getAsmLoc());
if (!AsmLocOrErr)
return AsmLocOrErr.takeError();
auto AsmStrOrErr = import(S->getAsmString());
auto AsmStrOrErr = import(S->getAsmStringExpr());
if (!AsmStrOrErr)
return AsmStrOrErr.takeError();
ExpectedSLoc RParenLocOrErr = import(S->getRParenLoc());
Loading