[clang] [llvm] [WIP] Support the inline asm 'a' constraint on PowerPC (PR #141604)

Kamau Bridgeman via cfe-commits cfe-commits at lists.llvm.org
Tue May 27 06:44:14 PDT 2025


https://github.com/kamaub created https://github.com/llvm/llvm-project/pull/141604

This patch adds backend and frontend support for address operand
constraint `a` for GCC style inline assembly. It allows the user
to specify X-FORM addressing mode operations in inline assmebly
without doing a [0 + Reg] index using the y constraint modifier.


>From e4102e3c4ec77e98c9a5efd3c79cd6a7690ba6a1 Mon Sep 17 00:00:00 2001
From: Kamau Bridgeman <kamau.bridgeman at ibm.com>
Date: Tue, 27 May 2025 09:39:07 -0400
Subject: [PATCH] [WIP] Support the inline asm 'a' constraint on PowerPC

This patch adds backend and frontend support for address operand
constraint `a` for GCC style inline assembly. It allows the user
to specify X-FORM addressing mode operations in inline assmebly
without doing a [0 + Reg] index using the y constraint modifier.
---
 clang/lib/Basic/Targets/PPC.h                 |   4 +-
 .../PowerPC/inline-asm-constraints-error.c    |  12 +-
 .../CodeGen/PowerPC/inline-asm-constraints.c  |  66 +++++++++
 llvm/include/llvm/IR/InlineAsm.h              |   3 +
 llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp     |   8 ++
 llvm/lib/Target/PowerPC/PPCISelDAGToDAG.cpp   |   1 +
 llvm/lib/Target/PowerPC/PPCISelLowering.cpp   |   2 +
 llvm/lib/Target/PowerPC/PPCISelLowering.h     |  16 +--
 .../PowerPC/inline-asm-constraints-error.ll   |  26 ++++
 .../CodeGen/PowerPC/inline-asm-constraints.ll | 127 ++++++++++++++++++
 10 files changed, 250 insertions(+), 15 deletions(-)
 create mode 100644 clang/test/CodeGen/PowerPC/inline-asm-constraints.c
 create mode 100644 llvm/test/CodeGen/PowerPC/inline-asm-constraints-error.ll
 create mode 100644 llvm/test/CodeGen/PowerPC/inline-asm-constraints.ll

diff --git a/clang/lib/Basic/Targets/PPC.h b/clang/lib/Basic/Targets/PPC.h
index 17057cef97a57..76fb24ed40050 100644
--- a/clang/lib/Basic/Targets/PPC.h
+++ b/clang/lib/Basic/Targets/PPC.h
@@ -303,8 +303,8 @@ class LLVM_LIBRARY_VISIBILITY PPCTargetInfo : public TargetInfo {
       break;
     case 'a': // Address operand that is an indexed or indirect from a
               // register (`p' is preferable for asm statements)
-              // TODO: Add full support for this constraint
-      return false;
+      Info.setAllowsRegister();
+      break;
     case 'R': // AIX TOC entry
     case 'S': // Constant suitable as a 64-bit mask operand
     case 'T': // Constant suitable as a 32-bit mask operand
diff --git a/clang/test/CodeGen/PowerPC/inline-asm-constraints-error.c b/clang/test/CodeGen/PowerPC/inline-asm-constraints-error.c
index 2f35e52fc0b77..c1bb59ee3c22a 100644
--- a/clang/test/CodeGen/PowerPC/inline-asm-constraints-error.c
+++ b/clang/test/CodeGen/PowerPC/inline-asm-constraints-error.c
@@ -1,9 +1,13 @@
 // RUN: %clang_cc1 -emit-llvm-only -triple powerpc64-ibm-aix-xcoff -verify %s
 // RUN: %clang_cc1 -emit-llvm-only -triple powerpc-ibm-aix-xcoff -verify %s
-// This test case exist to test marking the 'a' inline assembly constraint as
-// unsupported because powerpc previously marked it as supported.
-int foo(int arg){
-  asm goto ("bc 12,2,%l[TEST_LABEL]" : : "a"(&&TEST_LABEL) : : TEST_LABEL); //expected-error {{invalid input constraint 'a' in asm}}
+
+ int labelConstraintError(int arg){
+  asm goto ("bc 12,2,%l[TEST_LABEL]" : : "s"(&&TEST_LABEL) : : TEST_LABEL); //expected-error {{invalid input constraint 's' in asm}}
   return 0;
 TEST_LABEL: return arg + 1;
 }
+
+char wrongAddrConstraint(char* result) {
+  asm ("stb %1,%0" : "a"(result) : "r"('E') :); //expected-error {{invalid output constraint 'a' in asm}}
+  return *result;
+}
diff --git a/clang/test/CodeGen/PowerPC/inline-asm-constraints.c b/clang/test/CodeGen/PowerPC/inline-asm-constraints.c
new file mode 100644
index 0000000000000..ca5a9ffcee0cc
--- /dev/null
+++ b/clang/test/CodeGen/PowerPC/inline-asm-constraints.c
@@ -0,0 +1,66 @@
+// RUN: %clang_cc1 -emit-llvm -triple powerpc64-ibm-aix-xcoff \
+// RUN:   %s -o - | FileCheck %s
+
+char loadAddressAConstrained(char* ptr) {
+// CHECK-LABEL: define{{.*}} i8 @loadAddressAConstrained(ptr noundef %ptr)
+// CHECK:  %1 = call ptr asm "addi $0,$1, 0", "=r,a"(ptr %0)
+  char* result;
+  asm ("addi %0,%1, 0" : "=r"(result) : "a"(ptr) :);
+  return *result;
+}
+
+char loadAddressZyConstrained(char* ptr) {
+// CHECK-LABEL: define{{.*}} i8 @loadAddressZyConstrained(ptr noundef %ptr)
+// CHECK:  %1 = call ptr asm "add $0,${1:y}", "=r,*Z"(ptr elementtype(i8) %0)
+  char* result;
+  asm ("add %0,%y1" : "=r"(result) : "Z"(*ptr) :);
+  return *result;
+}
+
+char xFormRegImmLoadAConstrained(char* ptr) {
+// CHECK-LABEL: define{{.*}} i8 @xFormRegImmLoadAConstrained(ptr noundef %ptr)
+// CHECK:  %1 = call ptr asm "addi $0,$1,$2", "=r,a,I"(ptr %0, i32 10000)
+  char* result;
+  asm ("addi %0,%1,%2" : "=r"(result) : "a"(ptr), "I"(10000) :);
+  return *result;
+}
+
+char loadIndirectAddressZConstrained(char* ptr) {
+// CHECK-LABEL: define{{.*}} i8 @loadIndirectAddressZConstrained(ptr noundef %ptr)
+// CHECK:  %1 = call ptr asm "ld $0,$1", "=r,*Z"(ptr elementtype(i8) %arrayidx)
+  char* result;
+  asm ("ld %0,%1" : "=r"(result) : "Z"(ptr[100]) :);
+  return *result;
+}
+
+char loadIndirectAddressAConstrained(char** ptr, unsigned index) {
+// CHECK-LABEL: define{{.*}} i8 @loadIndirectAddressAConstrained(ptr noundef %ptr, i32 noundef zeroext %index)
+// CHECK:  %2 = call ptr asm "ldx $0,$1,$2", "=r,a,r"(ptr %0, i32 %1)
+  char* result;
+  asm ("ldx %0,%1,%2" : "=r"(result) : "a"(ptr), "r"(index) :);
+  return *result;
+}
+
+char dFormLoadZConstrained(char* ptr) {
+// CHECK-LABEL: define{{.*}} i8 @dFormLoadZConstrained(ptr noundef %ptr)
+// CHECK:  %1 = call i8 asm "lbz $0,$1", "=r,*Z"(ptr elementtype(i8) %arrayidx)
+  char result;
+  asm ("lbz %0,%1" : "=r"(result) : "Z"(ptr[8]) :);
+  return result;
+}
+
+char xFormRegRegLoadZyConstrained(char* ptr, unsigned index) {
+// CHECK-LABEL: define{{.*}} i8 @xFormRegRegLoadZyConstrained(ptr noundef %ptr, i32 noundef zeroext %index)
+// CHECK:  %2 = call i8 asm "lbzx $0, ${1:y}", "=r,*Z"(ptr elementtype(i8) %arrayidx)
+  char result;
+  asm("lbzx %0, %y1" : "=r"(result) : "Z"(ptr[index]) :);
+  return result;
+}
+
+char xFormRegRegLoadAConstrained(char* ptr, unsigned index) {
+// CHECK-LABEL: define{{.*}} i8 @xFormRegRegLoadAConstrained(ptr noundef %ptr, i32 noundef zeroext %index)
+// CHECK:  %2 = call i8 asm "lbzx $0,$1,$2", "=r,a,r"(ptr %0, i32 %1)
+  char result;
+  asm ("lbzx %0,%1,%2" : "=r"(result) : "a"(ptr), "r"(index) :);
+  return result;
+}
diff --git a/llvm/include/llvm/IR/InlineAsm.h b/llvm/include/llvm/IR/InlineAsm.h
index e5f506e5694da..87d68acde624b 100644
--- a/llvm/include/llvm/IR/InlineAsm.h
+++ b/llvm/include/llvm/IR/InlineAsm.h
@@ -264,6 +264,7 @@ class InlineAsm final : public Value {
 
     // Address constraints
     p,
+    a,
     ZQ,
     ZR,
     ZS,
@@ -514,6 +515,8 @@ class InlineAsm final : public Value {
       return "Zy";
     case ConstraintCode::p:
       return "p";
+    case ConstraintCode::a:
+      return "a";
     case ConstraintCode::ZQ:
       return "ZQ";
     case ConstraintCode::ZR:
diff --git a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
index 79e52635a3675..0c02b90747806 100644
--- a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
+++ b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
@@ -442,6 +442,14 @@ bool PPCAsmPrinter::PrintAsmMemoryOperand(const MachineInstr *MI, unsigned OpNo,
   }
 
   assert(MI->getOperand(OpNo).isReg());
+  const MachineOperand &FlagsOP = MI->getOperand(OpNo - 1);
+  if (!FlagsOP.isImm())
+    return true;
+  const InlineAsm::Flag Flags(FlagsOP.getImm());
+  if (Flags.getMemoryConstraintID() == InlineAsm::ConstraintCode::a) {
+    printOperand(MI, OpNo, O);
+    return false;
+  }
   O << "0(";
   printOperand(MI, OpNo, O);
   O << ")";
diff --git a/llvm/lib/Target/PowerPC/PPCISelDAGToDAG.cpp b/llvm/lib/Target/PowerPC/PPCISelDAGToDAG.cpp
index 0907239153226..e4029d3612b9f 100644
--- a/llvm/lib/Target/PowerPC/PPCISelDAGToDAG.cpp
+++ b/llvm/lib/Target/PowerPC/PPCISelDAGToDAG.cpp
@@ -392,6 +392,7 @@ namespace {
         errs() << "ConstraintID: "
                << InlineAsm::getMemConstraintName(ConstraintID) << "\n";
         llvm_unreachable("Unexpected asm memory constraint");
+      case InlineAsm::ConstraintCode::a:
       case InlineAsm::ConstraintCode::es:
       case InlineAsm::ConstraintCode::m:
       case InlineAsm::ConstraintCode::o:
diff --git a/llvm/lib/Target/PowerPC/PPCISelLowering.cpp b/llvm/lib/Target/PowerPC/PPCISelLowering.cpp
index ab78f33f5a630..843aec1ae4b99 100644
--- a/llvm/lib/Target/PowerPC/PPCISelLowering.cpp
+++ b/llvm/lib/Target/PowerPC/PPCISelLowering.cpp
@@ -17184,6 +17184,8 @@ PPCTargetLowering::getConstraintType(StringRef Constraint) const {
   if (Constraint.size() == 1) {
     switch (Constraint[0]) {
     default: break;
+    case 'a':
+      return C_Address;
     case 'b':
     case 'r':
     case 'f':
diff --git a/llvm/lib/Target/PowerPC/PPCISelLowering.h b/llvm/lib/Target/PowerPC/PPCISelLowering.h
index 1f22aa16a89be..0ecb54dcf2f39 100644
--- a/llvm/lib/Target/PowerPC/PPCISelLowering.h
+++ b/llvm/lib/Target/PowerPC/PPCISelLowering.h
@@ -997,15 +997,13 @@ namespace llvm {
 
     InlineAsm::ConstraintCode
     getInlineAsmMemConstraint(StringRef ConstraintCode) const override {
-      if (ConstraintCode == "es")
-        return InlineAsm::ConstraintCode::es;
-      else if (ConstraintCode == "Q")
-        return InlineAsm::ConstraintCode::Q;
-      else if (ConstraintCode == "Z")
-        return InlineAsm::ConstraintCode::Z;
-      else if (ConstraintCode == "Zy")
-        return InlineAsm::ConstraintCode::Zy;
-      return TargetLowering::getInlineAsmMemConstraint(ConstraintCode);
+      return StringSwitch<InlineAsm::ConstraintCode>(ConstraintCode)
+        .Case("es", InlineAsm::ConstraintCode::es)
+        .Case("Q", InlineAsm::ConstraintCode::Q)
+        .Case("Z", InlineAsm::ConstraintCode::Z)
+        .Case("Zy", InlineAsm::ConstraintCode::Zy)
+        .Case("a", InlineAsm::ConstraintCode::a)
+        .Default(TargetLowering::getInlineAsmMemConstraint(ConstraintCode));
     }
 
     void CollectTargetIntrinsicOperands(const CallInst &I,
diff --git a/llvm/test/CodeGen/PowerPC/inline-asm-constraints-error.ll b/llvm/test/CodeGen/PowerPC/inline-asm-constraints-error.ll
new file mode 100644
index 0000000000000..d7fbdfed89ce3
--- /dev/null
+++ b/llvm/test/CodeGen/PowerPC/inline-asm-constraints-error.ll
@@ -0,0 +1,26 @@
+; RUN: not llc -mcpu=pwr8 -mtriple=powerpc64-ibm-aix-xcoff  -filetype=null %s 2>&1 | FileCheck %s
+; XFAIL: *
+
+; CHECK: LLVM ERROR: Do not know how to promote this operator's operand!
+define i8 @charLoadAConstrained(i8 zeroext %ptr, i32 %index) {
+entry:
+  %0 = tail call i16 asm "lbzx $0,$1,$2", "=r,a,r"(i8 %ptr, i32 %index)
+  %conv = trunc i16 %0 to i8
+  ret i8 %conv
+}
+
+define i8 @shortLoadAConstrained(i16 zeroext %ptr, i32 %index) {
+; CHECK: LLVM ERROR: Do not know how to promote this operator's operand!
+entry:
+  %0 = tail call i16 asm "lbzx $0,$1,$2", "=r,a,r"(i16 %ptr, i32 %index)
+  %conv = trunc i16 %0 to i8
+  ret i8 %conv
+}
+
+define i8 @intLoadAConstrained(i32 zeroext %ptr, i32 %index) {
+; CHECK: LLVM ERROR: Do not know how to promote this operator's operand!
+entry:
+  %0 = tail call i16 asm "lbzx $0,$1,$2", "=r,a,r"(i32 %ptr, i32 %index)
+  %conv = trunc i16 %0 to i8
+  ret i8 %conv
+}
diff --git a/llvm/test/CodeGen/PowerPC/inline-asm-constraints.ll b/llvm/test/CodeGen/PowerPC/inline-asm-constraints.ll
new file mode 100644
index 0000000000000..4781bd7d0b235
--- /dev/null
+++ b/llvm/test/CodeGen/PowerPC/inline-asm-constraints.ll
@@ -0,0 +1,127 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc -verify-machineinstrs < %s -mcpu=pwr8 -no-integrated-as \
+; RUN:  -mtriple=powerpc64-ibm-aix-xcoff | FileCheck %s
+; RUN: llc -verify-machineinstrs < %s -mcpu=pwr8 -no-integrated-as \
+; RUN:  -mtriple=powerpc-ibm-aix-xcoff | FileCheck %s
+
+define signext i8 @loadAddressAConstrained(ptr %ptr) {
+; CHECK-LABEL: loadAddressAConstrained:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    addi 3,3, 0
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    lbz 3, 0(3)
+; CHECK-NEXT:    extsb 3, 3
+; CHECK-NEXT:    blr
+entry:
+  %0 = tail call ptr asm "addi $0,$1, 0", "=r,a"(ptr %ptr)
+  %1 = load i8, ptr %0, align 1
+  ret i8 %1
+}
+
+define signext i8 @xFormRegImmLoadAConstrained(ptr %ptr) {
+; CHECK-LABEL: xFormRegImmLoadAConstrained:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    addi 3,3,10000
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    lbz 3, 0(3)
+; CHECK-NEXT:    extsb 3, 3
+; CHECK-NEXT:    blr
+entry:
+  %0 = tail call ptr asm "addi $0,$1,$2", "=r,a,I"(ptr %ptr, i32 10000)
+  %1 = load i8, ptr %0, align 1
+  ret i8 %1
+}
+
+define signext i8 @loadIndirectAddressZConstrained(ptr %ptr) {
+; CHECK-LABEL: loadIndirectAddressZConstrained:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    addi 3, 3, 800
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    ld 3,0(3)
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    lbz 3, 0(3)
+; CHECK-NEXT:    extsb 3, 3
+; CHECK-NEXT:    blr
+entry:
+  %arrayidx = getelementptr inbounds nuw i8, ptr %ptr, i64 800
+  %0 = tail call ptr asm "ld $0,$1", "=r,*Z"(ptr nonnull elementtype(ptr) %arrayidx)
+  %1 = load i8, ptr %0, align 1
+  ret i8 %1
+}
+
+define signext i8 @loadIndirectAddressAConstrained(ptr %ptr, i32 zeroext %index) {
+; CHECK-LABEL: loadIndirectAddressAConstrained:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    ldx 3,3,4
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    lbz 3, 0(3)
+; CHECK-NEXT:    extsb 3, 3
+; CHECK-NEXT:    blr
+entry:
+  %0 = tail call ptr asm "ldx $0,$1,$2", "=r,a,r"(ptr %ptr, i32 zeroext %index)
+  %1 = load i8, ptr %0, align 1
+  ret i8 %1
+}
+
+define signext i8 @dFormLoadZConstrained(ptr %ptr) {
+; CHECK-LABEL: dFormLoadZConstrained:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    addi 3, 3, 8
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    lbz 3,0(3)
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    extsb 3, 3
+; CHECK-NEXT:    blr
+entry:
+  %arrayidx = getelementptr inbounds nuw i8, ptr %ptr, i64 8
+  %0 = tail call i8 asm "lbz $0,$1", "=r,*Z"(ptr nonnull elementtype(i8) %arrayidx)
+  ret i8 %0
+}
+
+define signext i8 @xFormRegRegLoadZyConstrained(ptr %ptr, i32 zeroext %index) {
+; CHECK-LABEL: xFormRegRegLoadZyConstrained:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    add 3, 3, 4
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    lbzx 3, 0, 3
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    extsb 3, 3
+; CHECK-NEXT:    blr
+entry:
+  %idxprom = zext i32 %index to i64
+  %arrayidx = getelementptr inbounds nuw i8, ptr %ptr, i64 %idxprom
+  %0 = tail call i8 asm "lbzx $0, ${1:y}", "=r,*Z"(ptr elementtype(i8) %arrayidx)
+  ret i8 %0
+}
+
+define signext i8 @xFormRegRegLoadAConstrained(ptr %ptr, i32 zeroext %index) {
+; CHECK-LABEL: xFormRegRegLoadAConstrained:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    lbzx 3,3,4
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    extsb 3, 3
+; CHECK-NEXT:    blr
+entry:
+  %0 = tail call i8 asm "lbzx $0,$1,$2", "=r,a,r"(ptr %ptr, i32 %index)
+  ret i8 %0
+}
+
+define i8 @implicitRegImmToRegRegConversion(ptr readnone %ptr, i32 zeroext %index) {
+; CHECK-LABEL: implicitRegImmToRegRegConversion:
+; CHECK:       # %bb.0: # %entry
+; CHECK-NEXT:    add 3, 3, 4
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    lbzx 3, 0, 3
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    blr
+entry:
+  %idx.ext = zext i32 %index to i64
+  %add.ptr = getelementptr inbounds nuw i8, ptr %ptr, i64 %idx.ext
+  %0 = tail call i8 asm "lbzx $0, ${1:y}", "=r,a"(ptr %add.ptr)
+  ret i8 %0
+}
+



More information about the cfe-commits mailing list