Skip to content

[clang-c] introduce queries on GCC-style inline assembly statements #143424

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
merged 3 commits into from
Jun 25, 2025
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
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ Non-comprehensive list of changes in this release
``__reference_constructs_from_temporary`` should be used instead. (#GH44056)
- Added `__builtin_get_vtable_pointer` to directly load the primary vtable pointer from a
polymorphic object.
- ``libclang`` receives a family of new bindings to query basic facts about
GCC-style inline assembly blocks, including whether the block is ``volatile``
and its template string following the LLVM IR ``asm`` format. (#GH143424)
- Clang no longer rejects reinterpret_cast conversions between indirect
ARC-managed pointers and other pointer types. The prior behavior was overly
strict and inconsistent with the ARC specification.
Expand Down
125 changes: 124 additions & 1 deletion clang/include/clang-c/Index.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
#define CINDEX_VERSION_MAJOR 0
#define CINDEX_VERSION_MINOR 64

#define CINDEX_VERSION_ENCODE(major, minor) (((major)*10000) + ((minor)*1))
#define CINDEX_VERSION_ENCODE(major, minor) (((major) * 10000) + ((minor) * 1))

#define CINDEX_VERSION \
CINDEX_VERSION_ENCODE(CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR)
Expand Down Expand Up @@ -4495,6 +4495,129 @@ CINDEX_LINKAGE CXStringSet *clang_Cursor_getCXXManglings(CXCursor);
*/
CINDEX_LINKAGE CXStringSet *clang_Cursor_getObjCManglings(CXCursor);

/**
* @}
*/

/**
* \defgroup CINDEX_MODULE Inline Assembly introspection
*
* The functions in this group provide access to information about GCC-style
* inline assembly statements.
*
* @{
*/

/**
* Given a CXCursor_GCCAsmStmt cursor, return the assembly template string.
* As per LLVM IR Assembly Template language, template placeholders for
* inputs and outputs are either of the form $N where N is a decimal number
* as an index into the input-output specification,
* or ${N:M} where N is a decimal number also as an index into the
* input-output specification and M is the template argument modifier.
* The index N in both cases points into the the total inputs and outputs,
* or more specifically, into the list of outputs followed by the inputs,
* starting from index 0 as the first available template argument.
*
* This function also returns a valid empty string if the cursor does not point
* at a GCC inline assembly block.
*
* Users are responsible for releasing the allocation of returned string via
* \c clang_disposeString.
*/

CINDEX_LINKAGE CXString clang_Cursor_getGCCAssemblyTemplate(CXCursor);

/**
* Given a CXCursor_GCCAsmStmt cursor, check if the assembly block has goto
* labels.
* This function also returns 0 if the cursor does not point at a GCC inline
* assembly block.
*/

CINDEX_LINKAGE unsigned clang_Cursor_isGCCAssemblyHasGoto(CXCursor);

/**
* Given a CXCursor_GCCAsmStmt cursor, count the number of outputs.
* This function also returns 0 if the cursor does not point at a GCC inline
* assembly block.
*/

CINDEX_LINKAGE unsigned clang_Cursor_getGCCAssemblyNumOutputs(CXCursor);

/**
* Given a CXCursor_GCCAsmStmt cursor, count the number of inputs.
* This function also returns 0 if the cursor does not point at a GCC inline
* assembly block.
*/

CINDEX_LINKAGE unsigned clang_Cursor_getGCCAssemblyNumInputs(CXCursor);

/**
* Given a CXCursor_GCCAsmStmt cursor, get the constraint and expression cursor
* to the Index-th input.
* This function returns 1 when the cursor points at a GCC inline assembly
* statement, `Index` is within bounds and both the `Constraint` and `Expr` are
* not NULL.
* Otherwise, this function returns 0 but leaves `Constraint` and `Expr`
* intact.
*
* Users are responsible for releasing the allocation of `Constraint` via
* \c clang_disposeString.
*/

CINDEX_LINKAGE unsigned clang_Cursor_getGCCAssemblyInput(CXCursor Cursor,
unsigned Index,
CXString *Constraint,
CXCursor *Expr);

/**
* Given a CXCursor_GCCAsmStmt cursor, get the constraint and expression cursor
* to the Index-th output.
* This function returns 1 when the cursor points at a GCC inline assembly
* statement, `Index` is within bounds and both the `Constraint` and `Expr` are
* not NULL.
* Otherwise, this function returns 0 but leaves `Constraint` and `Expr`
* intact.
*
* Users are responsible for releasing the allocation of `Constraint` via
* \c clang_disposeString.
*/

CINDEX_LINKAGE unsigned clang_Cursor_getGCCAssemblyOutput(CXCursor Cursor,
unsigned Index,
CXString *Constraint,
CXCursor *Expr);

/**
* Given a CXCursor_GCCAsmStmt cursor, count the clobbers in it.
* This function also returns 0 if the cursor does not point at a GCC inline
* assembly block.
*/

CINDEX_LINKAGE unsigned clang_Cursor_getGCCAssemblyNumClobbers(CXCursor Cursor);

/**
* Given a CXCursor_GCCAsmStmt cursor, get the Index-th clobber of it.
* This function returns a valid empty string if the cursor does not point
* at a GCC inline assembly block or `Index` is out of bounds.
*
* Users are responsible for releasing the allocation of returned string via
* \c clang_disposeString.
*/

CINDEX_LINKAGE CXString clang_Cursor_getGCCAssemblyClobber(CXCursor Cursor,
unsigned Index);

/**
* Given a CXCursor_GCCAsmStmt cursor, check if the inline assembly is
* `volatile`.
* This function returns 0 if the cursor does not point at a GCC inline
* assembly block.
*/

CINDEX_LINKAGE unsigned clang_Cursor_isGCCAssemblyVolatile(CXCursor Cursor);

/**
* @}
*/
Expand Down
46 changes: 46 additions & 0 deletions clang/test/Index/inline-assembly.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
static void inline_assembly_template_regardless_of_target_machine() {
int tmp;
asm volatile (
"nop\n"
"a_value %w[v]\n"
"o_value %w[o]"
: [v] "=&r" (tmp)
: [o] "r" (tmp)
: "cc", "memory"
);
}

// RUN: c-index-test -test-inline-assembly %s 2>&1 | FileCheck %s
// CHECK: ===ASM TEMPLATE===
// CHECK: nop
// CHECK: a_value ${0:w}
// CHECK: o_value ${1:w}
// CHECK: ===ASM TEMPLATE END===
// CHECK: volatile: true
// CHECK: Output #0 Constraint (=&r): DeclRefExpr=tmp:2:9
// CHECK: Input #0 Constraint (r): UnexposedExpr=tmp:2:9
// CHECK: Clobber #0: cc
// CHECK: Clobber #1: memory
// CHECK: ===ASM END===

static void inline_assembly_valid_x86_example() {
int tmp;
asm (
"nop\n"
"mov %w[o], %w[v]"
: [v] "=&r" (tmp)
: [o] "r" (tmp)
: "cc", "memory"
);
}

// CHECK: ===ASM TEMPLATE===
// CHECK: nop
// CHECK: mov ${1:w}, ${0:w}
// CHECK: ===ASM TEMPLATE END===
// CHECK: volatile: false
// CHECK: Output #0 Constraint (=&r): DeclRefExpr=tmp:27:9
// CHECK: Input #0 Constraint (r): UnexposedExpr=tmp:27:9
// CHECK: Clobber #0: cc
// CHECK: Clobber #1: memory
// CHECK: ===ASM END===
50 changes: 50 additions & 0 deletions clang/tools/c-index-test/c-index-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -1988,6 +1988,51 @@ static enum CXChildVisitResult PrintDeclAttributes(CXCursor cursor, CXCursor p,
return CXChildVisit_Continue;
}

/******************************************************************************/
/* Inline assembly cursor testing */
/******************************************************************************/

static enum CXChildVisitResult
PrintGCCInlineAssembly(CXCursor cursor, CXCursor p, CXClientData d) {
CXString Constraint, Template, Clobber;
CXCursor Expr;
unsigned hasGoto, i, e;
if (clang_getCursorKind(cursor) != CXCursor_AsmStmt)
return CXChildVisit_Recurse;

hasGoto = clang_Cursor_isGCCAssemblyHasGoto(cursor);
printf("===ASM TEMPLATE%s===\n", hasGoto ? " (WITH GOTO)" : "");
Template = clang_Cursor_getGCCAssemblyTemplate(cursor);
printf("%s", clang_getCString(Template));
clang_disposeString(Template);
printf("\n===ASM TEMPLATE END===\n");

printf("volatile: %s\n",
clang_Cursor_isGCCAssemblyVolatile(cursor) ? "true" : "false");

for (i = 0, e = clang_Cursor_getGCCAssemblyNumOutputs(cursor); i < e; ++i) {
clang_Cursor_getGCCAssemblyOutput(cursor, i, &Constraint, &Expr);
printf("Output #%d Constraint (%s): ", i, clang_getCString(Constraint));
PrintCursor(Expr, NULL);
printf("\n");
clang_disposeString(Constraint);
}
for (i = 0, e = clang_Cursor_getGCCAssemblyNumInputs(cursor); i < e; ++i) {
clang_Cursor_getGCCAssemblyInput(cursor, i, &Constraint, &Expr);
printf("Input #%d Constraint (%s): ", i, clang_getCString(Constraint));
PrintCursor(Expr, NULL);
printf("\n");
clang_disposeString(Constraint);
}
for (i = 0, e = clang_Cursor_getGCCAssemblyNumClobbers(cursor); i < e; ++i) {
Clobber = clang_Cursor_getGCCAssemblyClobber(cursor, i);
printf("Clobber #%d: %s\n", i, clang_getCString(Clobber));
clang_disposeString(Clobber);
}
printf("===ASM END===\n");
return CXChildVisit_Recurse;
}

/******************************************************************************/
/* Target information testing. */
/******************************************************************************/
Expand Down Expand Up @@ -5010,6 +5055,7 @@ static void print_usage(void) {
" c-index-test -test-annotate-tokens=<range> {<args>}*\n"
" c-index-test -test-inclusion-stack-source {<args>}*\n"
" c-index-test -test-inclusion-stack-tu <AST file>\n");
fprintf(stderr, " c-index-test -test-inline-assembly <AST file>\n");
fprintf(stderr,
" c-index-test -test-print-linkage-source {<args>}*\n"
" c-index-test -test-print-visibility {<args>}*\n"
Expand Down Expand Up @@ -5167,6 +5213,10 @@ int cindextest_main(int argc, const char **argv) {
else if (argc > 2 && strstr(argv[1], "-single-symbol-sgf-for=") == argv[1])
return perform_test_single_symbol_sgf(argv[1], argc - 2, argv + 2);

if (argc > 2 && strstr(argv[1], "-test-inline-assembly") == argv[1])
return perform_test_load_source(argc - 2, argv + 2, "all",
PrintGCCInlineAssembly, NULL);

print_usage();
return 1;
}
Expand Down
94 changes: 94 additions & 0 deletions clang/tools/libclang/CIndex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8648,6 +8648,100 @@ void clang_annotateTokens(CXTranslationUnit TU, CXToken *Tokens,
}
}

//===----------------------------------------------------------------------===//
// Operations for querying information of a GCC inline assembly block under a
// cursor.
//===----------------------------------------------------------------------===//
CXString clang_Cursor_getGCCAssemblyTemplate(CXCursor Cursor) {
if (!clang_isStatement(Cursor.kind))
return cxstring::createEmpty();
if (auto const *S = dyn_cast_or_null<GCCAsmStmt>(getCursorStmt(Cursor))) {
ASTContext const &C = getCursorContext(Cursor);
std::string AsmTemplate = S->generateAsmString(C);
return cxstring::createDup(AsmTemplate);
}
return cxstring::createEmpty();
}

unsigned clang_Cursor_isGCCAssemblyHasGoto(CXCursor Cursor) {
if (!clang_isStatement(Cursor.kind))
return 0;
if (auto const *S = dyn_cast_or_null<GCCAsmStmt>(getCursorStmt(Cursor)))
return S->isAsmGoto();
return 0;
}

unsigned clang_Cursor_getGCCAssemblyNumOutputs(CXCursor Cursor) {
if (!clang_isStatement(Cursor.kind))
return 0;
if (auto const *S = dyn_cast_or_null<GCCAsmStmt>(getCursorStmt(Cursor)))
return S->getNumOutputs();
return 0;
}

unsigned clang_Cursor_getGCCAssemblyNumInputs(CXCursor Cursor) {
if (!clang_isStatement(Cursor.kind))
return 0;
if (auto const *S = dyn_cast_or_null<GCCAsmStmt>(getCursorStmt(Cursor)))
return S->getNumInputs();
return 0;
}

unsigned clang_Cursor_getGCCAssemblyInput(CXCursor Cursor, unsigned Index,
CXString *Constraint,
CXCursor *ExprCursor) {
if (!clang_isStatement(Cursor.kind) || !Constraint || !ExprCursor)
return 0;
if (auto const *S = dyn_cast_or_null<GCCAsmStmt>(getCursorStmt(Cursor));
S && Index < S->getNumInputs()) {
*Constraint = cxstring::createDup(S->getInputConstraint(Index));
*ExprCursor = MakeCXCursor(S->getInputExpr(Index), getCursorDecl(Cursor),
cxcursor::getCursorTU(Cursor));
return 1;
}
return 0;
}

unsigned clang_Cursor_getGCCAssemblyOutput(CXCursor Cursor, unsigned Index,
CXString *Constraint,
CXCursor *ExprCursor) {
if (!clang_isStatement(Cursor.kind) || !Constraint || !ExprCursor)
return 0;
if (auto const *S = dyn_cast_or_null<GCCAsmStmt>(getCursorStmt(Cursor));
S && Index < S->getNumOutputs()) {
*Constraint = cxstring::createDup(S->getOutputConstraint(Index));
*ExprCursor = MakeCXCursor(S->getOutputExpr(Index), getCursorDecl(Cursor),
cxcursor::getCursorTU(Cursor));
return 1;
}
return 0;
}

unsigned clang_Cursor_getGCCAssemblyNumClobbers(CXCursor Cursor) {
if (!clang_isStatement(Cursor.kind))
return 0;
if (auto const *S = dyn_cast_or_null<GCCAsmStmt>(getCursorStmt(Cursor)))
return S->getNumClobbers();
return 0;
}

CXString clang_Cursor_getGCCAssemblyClobber(CXCursor Cursor, unsigned Index) {
if (!clang_isStatement(Cursor.kind))
return cxstring::createEmpty();
if (auto const *S = dyn_cast_or_null<GCCAsmStmt>(getCursorStmt(Cursor));
S && Index < S->getNumClobbers())
return cxstring::createDup(S->getClobber(Index));
return cxstring::createEmpty();
}

unsigned clang_Cursor_isGCCAssemblyVolatile(CXCursor Cursor) {
if (!clang_isStatement(Cursor.kind))
return 0;
if (auto const *S = dyn_cast_or_null<GCCAsmStmt>(getCursorStmt(Cursor)))
return S->isVolatile();
return 0;
}

//===----------------------------------------------------------------------===//
// Operations for querying linkage of a cursor.
//===----------------------------------------------------------------------===//
Expand Down
9 changes: 9 additions & 0 deletions clang/tools/libclang/libclang.map
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,15 @@ LLVM_20 {
LLVM_21 {
global:
clang_getFullyQualifiedName;
clang_Cursor_getGCCAssemblyTemplate;
clang_Cursor_isGCCAssemblyHasGoto;
clang_Cursor_getGCCAssemblyNumOutputs;
clang_Cursor_getGCCAssemblyNumInputs;
clang_Cursor_getGCCAssemblyInput;
clang_Cursor_getGCCAssemblyOutput;
clang_Cursor_getGCCAssemblyNumClobbers;
clang_Cursor_getGCCAssemblyClobber;
clang_Cursor_isGCCAssemblyVolatile;
};

# Example of how to add a new symbol version entry. If you do add a new symbol
Expand Down
Loading