[clang] [llvm] RFC: Implementing new mechanism for hard register operands to inline asm as a constraint. (PR #85846)
Tony Tao via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 2 09:03:01 PDT 2026
https://github.com/tltao updated https://github.com/llvm/llvm-project/pull/85846
>From 406dda84e9722327cfe0f182cdcf13ddd1891dc1 Mon Sep 17 00:00:00 2001
From: Tony Tao <tonytao at ca.ibm.com>
Date: Fri, 27 Mar 2026 15:58:51 -0400
Subject: [PATCH 1/4] Revisiting the hard register constraint PR on
phabricator: https://reviews.llvm.org/D105142
The main idea is to allow Clang to support the ability to
specify specific hardware registers in inline assembly
constraints via the use of curly braces ``{}``. As such,
this is mainly a Clang change.
Relevant RFCs posted here
https://lists.llvm.org/pipermail/llvm-dev/2021-June/151370.html
https://gcc.gnu.org/pipermail/gcc/2021-June/236269.html
---
clang/docs/LanguageExtensions.rst | 43 +++
.../clang/Basic/DiagnosticSemaKinds.td | 2 +
clang/include/clang/Basic/TargetInfo.h | 18 +-
clang/lib/Basic/TargetInfo.cpp | 62 +++++
clang/lib/Basic/Targets/AArch64.h | 5 -
clang/lib/Basic/Targets/ARM.h | 5 -
clang/lib/Basic/Targets/RISCV.h | 5 -
clang/lib/Basic/Targets/X86.h | 2 +-
clang/lib/CodeGen/CGStmt.cpp | 136 ++++++++-
clang/test/CodeGen/AArch64/inline-asm.c | 12 +-
.../CodeGen/SystemZ/systemz-inline-asm-02.c | 18 +-
.../test/CodeGen/SystemZ/systemz-inline-asm.c | 19 +-
clang/test/CodeGen/asm-goto.c | 6 +-
clang/test/CodeGen/ms-intrinsics.c | 32 +--
.../CodeGen/x86-asm-register-constraint-mix.c | 62 +++++
.../test/CodeGen/z-hard-register-inline-asm.c | 52 ++++
.../z-multi-hard-register-inline-asm.c | 35 +++
clang/test/Sema/z-hard-register-inline-asm.c | 49 ++++
.../Sema/z-multi-hard-register-inline-asm.c | 30 ++
llvm/test/CodeGen/SystemZ/zos-inline-asm.ll | 261 ++++++++++++++++++
20 files changed, 807 insertions(+), 47 deletions(-)
create mode 100644 clang/test/CodeGen/x86-asm-register-constraint-mix.c
create mode 100644 clang/test/CodeGen/z-hard-register-inline-asm.c
create mode 100644 clang/test/CodeGen/z-multi-hard-register-inline-asm.c
create mode 100644 clang/test/Sema/z-hard-register-inline-asm.c
create mode 100644 clang/test/Sema/z-multi-hard-register-inline-asm.c
create mode 100644 llvm/test/CodeGen/SystemZ/zos-inline-asm.ll
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 582d7847cae44..8bdea18a5fcbd 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -2429,6 +2429,49 @@ Query for this feature with ``__has_extension(gnu_asm_constexpr_strings)``.
asm((std::string_view("nop")) ::: (std::string_view("memory")));
}
+Hard Register Operands for ASM Constraints
+==========================================
+
+Clang supports the ability to specify specific hardware registers in inline
+assembly constraints via the use of curly braces ``{}``. These register names
+are the same names as what would be used in the clobber list.
+
+Prior to clang-19, the only way to associate an inline assembly constraint
+with a specific register is via the local register variable feature (`GCC
+Specifying Registers for Local Variables <https://gcc.gnu.org/onlinedocs/gcc-6.5.0/gcc/Local-Register-Variables.html>`_).
+However, the local register variable association lasts for the entire
+scope of the variable.
+
+Hard register operands will instead only apply to the specific inline ASM
+statement which improves readability and solves a few other issues experienced
+by local register variables, such as:
+
+* the constraints for the register operands are superfluous
+* one register variable cannot be used for 2 different inline
+ assemblies if the value is expected in different hard regs
+
+The code below is an example of an inline assembly statement using local
+register variables:
+
+.. code-block:: c++
+
+ void foo() {
+ register int *p1 asm ("r0") = bar();
+ register int *p2 asm ("r1") = bar();
+ register int *result asm ("r0");
+ asm ("sysint" : "=r" (result) : "0" (p1), "r" (p2));
+ }
+
+Below is the same code but using hard register operands.
+
+.. code-block:: c++
+
+ void foo() {
+ int *p1 = bar();
+ int *p2 = bar();
+ int *result;
+ asm ("sysint" : "={r0}" (result) : "0" (p1), "{r1}" (p2));
+ }
Objective-C Features
====================
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 4d352f1def04b..b4e5cf65f6f70 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10086,6 +10086,8 @@ let CategoryName = "Inline Assembly Issue" in {
"more than one input constraint matches the same output '%0'">;
def err_store_value_to_reg : Error<
"impossible constraint in asm: cannot store value into a register">;
+ def err_asm_hard_reg_variable_duplicate : Error<
+ "hard register operand already defined as register variable">;
def warn_asm_label_on_auto_decl : Warning<
"ignored asm label '%0' on automatic variable">;
diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h
index 9f7d2a17a0f8a..b72c23ff1b65d 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -1123,9 +1123,17 @@ class TargetInfo : public TransferrableTargetInfo,
///
/// This function is used by Sema in order to diagnose conflicts between
/// the clobber list and the input/output lists.
+ /// The constraint should already by validated in
+ /// validateHardRegisterAsmConstraint so just do some basic checking
virtual StringRef getConstraintRegister(StringRef Constraint,
StringRef Expression) const {
- return "";
+ StringRef Reg = Expression;
+ size_t Start = Constraint.find('{');
+ size_t End = Constraint.find('}');
+ if (Start != StringRef::npos && End != StringRef::npos && End > Start)
+ Reg = Constraint.substr(Start + 1, End - Start - 1);
+
+ return Reg;
}
struct ConstraintInfo {
@@ -1286,6 +1294,14 @@ class TargetInfo : public TransferrableTargetInfo,
validateAsmConstraint(const char *&Name,
TargetInfo::ConstraintInfo &info) const = 0;
+ // Validate the "hard register" inline asm constraint. This constraint is
+ // of the form {<reg-name>}. This constraint is meant to be used
+ // as an alternative for the "register asm" construct to put inline
+ // asm operands into specific registers.
+ bool
+ validateHardRegisterAsmConstraint(const char *&Name,
+ TargetInfo::ConstraintInfo &info) const;
+
bool resolveSymbolicName(const char *&Name,
ArrayRef<ConstraintInfo> OutputConstraints,
unsigned &Index) const;
diff --git a/clang/lib/Basic/TargetInfo.cpp b/clang/lib/Basic/TargetInfo.cpp
index e6ae89e0948c5..213b2682ae641 100644
--- a/clang/lib/Basic/TargetInfo.cpp
+++ b/clang/lib/Basic/TargetInfo.cpp
@@ -842,6 +842,18 @@ bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
case 'E':
case 'F':
break; // Pass them.
+ case '{': {
+ // First, check the target parser in case it validates
+ // the {...} constraint differently.
+ if (validateAsmConstraint(Name, Info))
+ return true;
+
+ // If not, that's okay, we will try to validate it
+ // using a target agnostic implementation.
+ if (!validateHardRegisterAsmConstraint(Name, Info))
+ return false;
+ break;
+ }
}
Name++;
@@ -857,6 +869,36 @@ bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
return Info.allowsMemory() || Info.allowsRegister();
}
+bool TargetInfo::validateHardRegisterAsmConstraint(
+ const char *&Name, TargetInfo::ConstraintInfo &Info) const {
+ // First, swallow the '{'.
+ Name++;
+
+ // Mark the start of the possible register name.
+ const char *Start = Name;
+
+ // Loop through rest of "Name".
+ // In this loop, we check whether we have a closing curly brace which
+ // validates the constraint. Also, this allows us to get the correct bounds to
+ // set our register name.
+ while (*Name && *Name != '}')
+ Name++;
+
+ // Missing '}', return false.
+ if (!*Name)
+ return false;
+
+ // Now we set the register name.
+ std::string Register(Start, Name - Start);
+
+ // We validate whether its a valid register to be used.
+ if (!isValidGCCRegisterName(Register))
+ return false;
+
+ Info.setAllowsRegister();
+ return true;
+}
+
bool TargetInfo::resolveSymbolicName(const char *&Name,
ArrayRef<ConstraintInfo> OutputConstraints,
unsigned &Index) const {
@@ -989,6 +1031,18 @@ bool TargetInfo::validateInputConstraint(
case '!': // Disparage severely.
case '*': // Ignore for choosing register preferences.
break; // Pass them.
+ case '{': {
+ // First, check the target parser in case it validates
+ // the {...} constraint differently.
+ if (validateAsmConstraint(Name, Info))
+ return true;
+
+ // If not, that's okay, we will try to validate it
+ // using a target agnostic implementation.
+ if (!validateHardRegisterAsmConstraint(Name, Info))
+ return false;
+ break;
+ }
}
Name++;
@@ -1070,6 +1124,14 @@ void TargetInfo::copyAuxTarget(const TargetInfo *Aux) {
std::string
TargetInfo::simplifyConstraint(StringRef Constraint,
SmallVectorImpl<ConstraintInfo> *OutCons) const {
+ // If we have only the {...} constraint, do not do any simplifications. This
+ // already maps to the lower level LLVM inline assembly IR that tells the
+ // backend to allocate a specific register. Any validations would have already
+ // been done in the Sema stage or will be done in the AddVariableConstraints
+ // function.
+ if (Constraint[0] == '{' || (Constraint[0] == '&' && Constraint[1] == '{'))
+ return std::string(Constraint);
+
std::string Result;
for (const char *I = Constraint.begin(), *E = Constraint.end(); I < E; I++) {
diff --git a/clang/lib/Basic/Targets/AArch64.h b/clang/lib/Basic/Targets/AArch64.h
index 0a29bad81939b..33e14a127cc3a 100644
--- a/clang/lib/Basic/Targets/AArch64.h
+++ b/clang/lib/Basic/Targets/AArch64.h
@@ -249,11 +249,6 @@ class LLVM_LIBRARY_VISIBILITY AArch64TargetInfo : public TargetInfo {
std::string &SuggestedModifier) const override;
std::string_view getClobbers() const override;
- StringRef getConstraintRegister(StringRef Constraint,
- StringRef Expression) const override {
- return Expression;
- }
-
int getEHDataRegisterNumber(unsigned RegNo) const override;
bool validatePointerAuthKey(const llvm::APSInt &value) const override;
diff --git a/clang/lib/Basic/Targets/ARM.h b/clang/lib/Basic/Targets/ARM.h
index 43c4718f4735b..ff5d4bd712c53 100644
--- a/clang/lib/Basic/Targets/ARM.h
+++ b/clang/lib/Basic/Targets/ARM.h
@@ -205,11 +205,6 @@ class LLVM_LIBRARY_VISIBILITY ARMTargetInfo : public TargetInfo {
std::string &SuggestedModifier) const override;
std::string_view getClobbers() const override;
- StringRef getConstraintRegister(StringRef Constraint,
- StringRef Expression) const override {
- return Expression;
- }
-
CallingConvCheckResult checkCallingConvention(CallingConv CC) const override;
int getEHDataRegisterNumber(unsigned RegNo) const override;
diff --git a/clang/lib/Basic/Targets/RISCV.h b/clang/lib/Basic/Targets/RISCV.h
index 685735b54a45b..c152b51fdbe01 100644
--- a/clang/lib/Basic/Targets/RISCV.h
+++ b/clang/lib/Basic/Targets/RISCV.h
@@ -70,11 +70,6 @@ class RISCVTargetInfo : public TargetInfo {
std::string_view getClobbers() const override { return ""; }
- StringRef getConstraintRegister(StringRef Constraint,
- StringRef Expression) const override {
- return Expression;
- }
-
ArrayRef<const char *> getGCCRegNames() const override;
int getEHDataRegisterNumber(unsigned RegNo) const override {
diff --git a/clang/lib/Basic/Targets/X86.h b/clang/lib/Basic/Targets/X86.h
index c7afcc7c86053..fc2db5cf673b8 100644
--- a/clang/lib/Basic/Targets/X86.h
+++ b/clang/lib/Basic/Targets/X86.h
@@ -325,7 +325,7 @@ class LLVM_LIBRARY_VISIBILITY X86TargetInfo : public TargetInfo {
return "di";
// In case the constraint is 'r' we need to return Expression
case 'r':
- return Expression;
+ return TargetInfo::getConstraintRegister(Constraint, Expression);
// Double letters Y<x> constraints
case 'Y':
if ((++I != E) && ((*I == '0') || (*I == 'z')))
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index a923002bec9b6..d4aede2bc1ec6 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -2515,6 +2515,127 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) {
CaseRangeBlock = SavedCRBlock;
}
+/// Is it valid to apply a register constraint for a variable marked with
+/// the "register asm" construct?
+/// Optionally, if it is determined that we can, we set "Register" to the
+/// regiser name.
+static bool
+ShouldApplyRegisterVariableConstraint(const Expr &AsmExpr,
+ std::string *Register = nullptr) {
+
+ const DeclRefExpr *AsmDeclRef = dyn_cast<DeclRefExpr>(&AsmExpr);
+ if (!AsmDeclRef)
+ return false;
+ const ValueDecl &Value = *AsmDeclRef->getDecl();
+ const VarDecl *Variable = dyn_cast<VarDecl>(&Value);
+ if (!Variable)
+ return false;
+ if (Variable->getStorageClass() != SC_Register)
+ return false;
+ AsmLabelAttr *Attr = Variable->getAttr<AsmLabelAttr>();
+ if (!Attr)
+ return false;
+
+ if (Register != nullptr)
+ // Set the register to return from Attr.
+ *Register = Attr->getLabel().str();
+ return true;
+}
+
+/// AddVariableConstraints:
+/// Look at AsmExpr and if it is a variable declared as using a particular
+/// register add that as a constraint that will be used in this asm stmt.
+/// Whether it can be used or not is dependent on querying
+/// ShouldApplyRegisterVariableConstraint() Also check whether the "hard
+/// register" inline asm constraint (i.e. "{reg-name}") is specified. If so, add
+/// that as a constraint that will be used in this asm stmt.
+static std::string
+AddVariableConstraints(const std::string &Constraint, const Expr &AsmExpr,
+ const TargetInfo &Target, CodeGenModule &CGM,
+ const AsmStmt &Stmt, const bool EarlyClobber,
+ SmallVector<std::string> *GCCRegs = nullptr) {
+
+ StringRef Str(Constraint);
+ StringRef::iterator I = Str.begin(), E = Str.end();
+ // Do we have the "hard register" inline asm constraint.
+ StringRef::iterator HardRegStart = std::find(I, E, '{');
+ StringRef::iterator HardRegEnd = std::find(I, E, '}');
+ // Do we have at least one hard register.
+ bool ApplyHardRegisterConstraint =
+ HardRegStart != E && HardRegEnd != E && HardRegEnd > HardRegStart;
+
+ // Do we have "register asm" on a variable.
+ std::string Reg = "";
+ bool ApplyRegisterVariableConstraint =
+ ShouldApplyRegisterVariableConstraint(AsmExpr, &Reg);
+
+ // Diagnose the scenario where we apply both the register variable constraint
+ // and a hard register variable constraint as an unsupported error.
+ // Why? Because we could have a situation where the register passed in through
+ // {...} and the register passed in through the "register asm" construct could
+ // be different, and in this case, there's no way for the compiler to know
+ // which one to emit.
+ if (ApplyHardRegisterConstraint && ApplyRegisterVariableConstraint) {
+ CGM.getDiags().Report(AsmExpr.getExprLoc(),
+ diag::err_asm_hard_reg_variable_duplicate);
+ return Constraint;
+ }
+
+ if (!ApplyHardRegisterConstraint && !ApplyRegisterVariableConstraint)
+ return Constraint;
+
+ // We're using validateOutputConstraint here because we only care if
+ // this is a register constraint.
+ TargetInfo::ConstraintInfo Info(Constraint, "");
+ if (Target.validateOutputConstraint(Info) && !Info.allowsRegister()) {
+ CGM.ErrorUnsupported(&Stmt, "__asm__");
+ return Constraint;
+ }
+
+ if (ApplyRegisterVariableConstraint) {
+ StringRef Register(Reg);
+ assert(Target.isValidGCCRegisterName(Register));
+ // Canonicalize the register here before returning it.
+ Register = Target.getNormalizedGCCRegisterName(Register);
+ if (GCCRegs)
+ GCCRegs->push_back(Register.str());
+ return (EarlyClobber ? "&{" : "{") + Register.str() + "}";
+ }
+
+ std::string NC;
+ while (I != E) {
+ if (*I == '{') {
+ HardRegEnd = std::find(I + 1, E, '}');
+ // No error checking because we already validated this constraint
+ StringRef Register(I + 1, HardRegEnd - I - 1);
+ // If we don't have a valid register name, simply return the constraint.
+ // For example: There are some targets like X86 that use a constraint such
+ // as "@cca", which is validated and then converted into {@cca}. Now this
+ // isn't necessarily a "GCC Register", but in terms of emission, it is
+ // valid since it lowered appropriately in the X86 backend. For the {..}
+ // constraint, we shouldn't be too strict and error out if the register
+ // itself isn't a valid "GCC register".
+ if (!Target.isValidGCCRegisterName(Register))
+ return Constraint;
+
+ // Canonicalize the register here before returning it.
+ Register = Target.getNormalizedGCCRegisterName(Register);
+ // Do not need to worry about early clobber since the symbol should be
+ // copied from the original constraint string
+ NC += "{" + Register.str() + "}";
+
+ if (GCCRegs)
+ GCCRegs->push_back(Register.str());
+
+ I = HardRegEnd + 1;
+ } else {
+ NC += *I;
+ ++I;
+ }
+ }
+ return NC;
+}
+
std::pair<llvm::Value*, llvm::Type *> CodeGenFunction::EmitAsmInputLValue(
const TargetInfo::ConstraintInfo &Info, LValue InputValue,
QualType InputType, std::string &ConstraintStr, SourceLocation Loc) {
@@ -2864,16 +2985,23 @@ void CodeGenFunction::EmitAsmStmt(const AsmStmt &S) {
const Expr *OutExpr = S.getOutputExpr(i);
OutExpr = OutExpr->IgnoreParenNoopCasts(getContext());
- std::string GCCReg;
+ SmallVector<std::string> GCCRegs;
OutputConstraint = S.addVariableConstraints(
OutputConstraint, *OutExpr, getTarget(), Info.earlyClobber(),
[&](const Stmt *UnspStmt, StringRef Msg) {
CGM.ErrorUnsupported(UnspStmt, Msg);
},
&GCCReg);
+ OutputConstraint =
+ AddVariableConstraints(OutputConstraint, *OutExpr, getTarget(), CGM, S,
+ Info.earlyClobber(), &GCCRegs);
// Give an error on multiple outputs to same physreg.
- if (!GCCReg.empty() && !PhysRegOutputs.insert(GCCReg).second)
- CGM.Error(S.getAsmLoc(), "multiple outputs to hard register: " + GCCReg);
+ if (!GCCRegs.empty()) {
+ for (auto R : GCCRegs) {
+ if (!PhysRegOutputs.insert(R).second)
+ CGM.Error(S.getAsmLoc(), "multiple outputs to hard register: " + R);
+ }
+ }
OutputConstraints.push_back(OutputConstraint);
LValue Dest = EmitLValue(OutExpr);
@@ -2984,7 +3112,7 @@ void CodeGenFunction::EmitAsmStmt(const AsmStmt &S) {
std::max((uint64_t)LargestVectorWidth,
VT->getPrimitiveSizeInBits().getKnownMinValue());
// Only tie earlyclobber physregs.
- if (Info.allowsRegister() && (GCCReg.empty() || Info.earlyClobber()))
+ if (Info.allowsRegister() && (GCCRegs.empty() || Info.earlyClobber()))
InOutConstraints += llvm::utostr(i);
else
InOutConstraints += OutputConstraint;
diff --git a/clang/test/CodeGen/AArch64/inline-asm.c b/clang/test/CodeGen/AArch64/inline-asm.c
index 8ddee560b11da..860cc858275ea 100644
--- a/clang/test/CodeGen/AArch64/inline-asm.c
+++ b/clang/test/CodeGen/AArch64/inline-asm.c
@@ -77,7 +77,15 @@ void test_gcc_registers(void) {
void test_tied_earlyclobber(void) {
register int a asm("x1");
- asm("" : "+&r"(a));
+ asm(""
+ : "+&r"(a));
+ // CHECK: call i32 asm "", "=&{x1},0"(i32 %0)
+}
+
+void test_tied_earlyclobber2(void) {
+ int a;
+ asm(""
+ : "+&{x1}"(a));
// CHECK: call i32 asm "", "=&{x1},0"(i32 %0)
}
@@ -102,4 +110,4 @@ void test_sme_constraints(){
asm("movt zt0[3, mul vl], z0" : : : "zt0");
// CHECK: call void asm sideeffect "movt zt0[3, mul vl], z0", "~{zt0}"()
-}
\ No newline at end of file
+}
diff --git a/clang/test/CodeGen/SystemZ/systemz-inline-asm-02.c b/clang/test/CodeGen/SystemZ/systemz-inline-asm-02.c
index 754d7e66f04b2..f0951944cfd22 100644
--- a/clang/test/CodeGen/SystemZ/systemz-inline-asm-02.c
+++ b/clang/test/CodeGen/SystemZ/systemz-inline-asm-02.c
@@ -5,9 +5,23 @@
// Test that an error is given if a physreg is defined by multiple operands.
int test_physreg_defs(void) {
register int l __asm__("r7") = 0;
+ int m;
// CHECK: error: multiple outputs to hard register: r7
- __asm__("" : "+r"(l), "=r"(l));
+ __asm__(""
+ : "+r"(l), "=r"(l));
- return l;
+ // CHECK: error: multiple outputs to hard register: r6
+ __asm__(""
+ : "+{r6}"(m), "={r6}"(m));
+
+ // CHECK: error: multiple outputs to hard register: r6
+ __asm__(""
+ : "+{r1}{r2}{r6}"(m), "={r6}"(m));
+
+ // CHECK: error: multiple outputs to hard register: r6
+ __asm__(""
+ : "+{r6}"(m), "={r6}{r1}{r2}"(m));
+
+ return l + m;
}
diff --git a/clang/test/CodeGen/SystemZ/systemz-inline-asm.c b/clang/test/CodeGen/SystemZ/systemz-inline-asm.c
index e75e92130a6db..ae0230cd8f999 100644
--- a/clang/test/CodeGen/SystemZ/systemz-inline-asm.c
+++ b/clang/test/CodeGen/SystemZ/systemz-inline-asm.c
@@ -142,12 +142,25 @@ long double test_f128(long double f, long double g) {
int test_physregs(void) {
// CHECK-LABEL: define{{.*}} signext i32 @test_physregs()
register int l __asm__("r7") = 0;
+ int m = 0;
// CHECK: call i32 asm "lr $0, $1", "={r7},{r7}"
- __asm__("lr %0, %1" : "+r"(l));
+ __asm__("lr %0, %1"
+ : "+r"(l));
// CHECK: call i32 asm "$0 $1 $2", "={r7},{r7},{r7}"
- __asm__("%0 %1 %2" : "+r"(l) : "r"(l));
+ __asm__("%0 %1 %2"
+ : "+r"(l)
+ : "r"(l));
- return l;
+ // CHECK: call i32 asm "lr $0, $1", "={r6},{r6}"
+ __asm__("lr %0, %1"
+ : "+{r6}"(m));
+
+ // CHECK: call i32 asm "$0 $1 $2", "={r6},{r6},{r6}"
+ __asm__("%0 %1 %2"
+ : "+{r6}"(m)
+ : "{r6}"(m));
+
+ return l + m;
}
diff --git a/clang/test/CodeGen/asm-goto.c b/clang/test/CodeGen/asm-goto.c
index 4037c1b2a3d7a..77bd77615f299 100644
--- a/clang/test/CodeGen/asm-goto.c
+++ b/clang/test/CodeGen/asm-goto.c
@@ -55,14 +55,14 @@ int test3(int out1, int out2) {
int test4(int out1, int out2) {
// CHECK-LABEL: define{{.*}} i32 @test4(
- // CHECK: callbr { i32, i32 } asm sideeffect "jne ${5:l}", "={si},={di},r,0,1,!i,!i
+ // CHECK: callbr { i32, i32 } asm sideeffect "jne ${5:l}", "={si},={di},r,{si},{di},!i,!i
// CHECK: to label %asm.fallthrough [label %label_true.split, label %loop.split]
// CHECK-LABEL: asm.fallthrough:
if (out1 < out2)
asm volatile goto("jne %l5" : "+S"(out1), "+D"(out2) : "r"(out1) :: label_true, loop);
else
asm volatile goto("jne %l7" : "+S"(out1), "+D"(out2) : "r"(out1), "r"(out2) :: label_true, loop);
- // CHECK: callbr { i32, i32 } asm sideeffect "jne ${7:l}", "={si},={di},r,r,0,1,!i,!i
+ // CHECK: callbr { i32, i32 } asm sideeffect "jne ${7:l}", "={si},={di},r,r,{si},{di},!i,!i
// CHECK: to label %asm.fallthrough6 [label %label_true.split11, label %loop.split14]
// CHECK-LABEL: asm.fallthrough6:
return out1 + out2;
@@ -92,7 +92,7 @@ int test5(int addr, int size, int limit) {
int test6(int out1) {
// CHECK-LABEL: define{{.*}} i32 @test6(
- // CHECK: callbr i32 asm sideeffect "testl $0, $0; testl $1, $1; jne ${3:l}", "={si},r,0,!i,!i,{{.*}}
+ // CHECK: callbr i32 asm sideeffect "testl $0, $0; testl $1, $1; jne ${3:l}", "={si},r,{si},!i,!i,{{.*}}
// CHECK: to label %asm.fallthrough [label %label_true.split, label %landing.split]
// CHECK-LABEL: asm.fallthrough:
// CHECK-LABEL: landing:
diff --git a/clang/test/CodeGen/ms-intrinsics.c b/clang/test/CodeGen/ms-intrinsics.c
index 271aced5e0b7c..fa5e580a168af 100644
--- a/clang/test/CodeGen/ms-intrinsics.c
+++ b/clang/test/CodeGen/ms-intrinsics.c
@@ -36,12 +36,12 @@ void test__movsb(unsigned char *Dest, unsigned char *Src, size_t Count) {
return __movsb(Dest, Src, Count);
}
// CHECK-I386-LABEL: define{{.*}} void @test__movsb
-// CHECK-I386: tail call { ptr, ptr, i32 } asm sideeffect "xchg $(%esi, $1$|$1, esi$)\0Arep movsb\0Axchg $(%esi, $1$|$1, esi$)", "={di},=r,={cx},0,1,2,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i32 %Count)
+// CHECK-I386: tail call { ptr, ptr, i32 } asm sideeffect "xchg $(%esi, $1$|$1, esi$)\0Arep movsb\0Axchg $(%esi, $1$|$1, esi$)", "={di},=r,={cx},{di},1,{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i32 %Count)
// CHECK-I386: ret void
// CHECK-I386: }
// CHECK-X64-LABEL: define{{.*}} void @test__movsb
-// CHECK-X64: call { ptr, ptr, i64 } asm sideeffect "rep movsb", "={di},={si},={cx},0,1,2,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i64 %Count)
+// CHECK-X64: call { ptr, ptr, i64 } asm sideeffect "rep movsb", "={di},={si},={cx},{di},{si},{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i64 %Count)
// CHECK-X64: ret void
// CHECK-X64: }
@@ -49,12 +49,12 @@ void test__stosw(unsigned short *Dest, unsigned short Data, size_t Count) {
return __stosw(Dest, Data, Count);
}
// CHECK-I386-LABEL: define{{.*}} void @test__stosw
-// CHECK-I386: call { ptr, i32 } asm sideeffect "rep stosw", "={di},={cx},{ax},0,1,~{memory},~{dirflag},~{fpsr},~{flags}"(i16 %Data, ptr %Dest, i32 %Count)
+// CHECK-I386: call { ptr, i32 } asm sideeffect "rep stosw", "={di},={cx},{ax},{di},{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(i16 %Data, ptr %Dest, i32 %Count)
// CHECK-I386: ret void
// CHECK-I386: }
// CHECK-X64-LABEL: define{{.*}} void @test__stosw
-// CHECK-X64: call { ptr, i64 } asm sideeffect "rep stosw", "={di},={cx},{ax},0,1,~{memory},~{dirflag},~{fpsr},~{flags}"(i16 %Data, ptr %Dest, i64 %Count)
+// CHECK-X64: call { ptr, i64 } asm sideeffect "rep stosw", "={di},={cx},{ax},{di},{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(i16 %Data, ptr %Dest, i64 %Count)
// CHECK-X64: ret void
// CHECK-X64: }
@@ -62,12 +62,12 @@ void test__movsw(unsigned short *Dest, unsigned short *Src, size_t Count) {
return __movsw(Dest, Src, Count);
}
// CHECK-I386-LABEL: define{{.*}} void @test__movsw
-// CHECK-I386: tail call { ptr, ptr, i32 } asm sideeffect "xchg $(%esi, $1$|$1, esi$)\0Arep movsw\0Axchg $(%esi, $1$|$1, esi$)", "={di},=r,={cx},0,1,2,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i32 %Count)
+// CHECK-I386: tail call { ptr, ptr, i32 } asm sideeffect "xchg $(%esi, $1$|$1, esi$)\0Arep movsw\0Axchg $(%esi, $1$|$1, esi$)", "={di},=r,={cx},{di},1,{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i32 %Count)
// CHECK-I386: ret void
// CHECK-I386: }
// CHECK-X64-LABEL: define{{.*}} void @test__movsw
-// CHECK-X64: call { ptr, ptr, i64 } asm sideeffect "rep movsw", "={di},={si},={cx},0,1,2,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i64 %Count)
+// CHECK-X64: call { ptr, ptr, i64 } asm sideeffect "rep movsw", "={di},={si},={cx},{di},{si},{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i64 %Count)
// CHECK-X64: ret void
// CHECK-X64: }
@@ -75,12 +75,12 @@ void test__stosd(unsigned long *Dest, unsigned long Data, size_t Count) {
return __stosd(Dest, Data, Count);
}
// CHECK-I386-LABEL: define{{.*}} void @test__stosd
-// CHECK-I386: call { ptr, i32 } asm sideeffect "rep stos$(l$|d$)", "={di},={cx},{ax},0,1,~{memory},~{dirflag},~{fpsr},~{flags}"(i32 %Data, ptr %Dest, i32 %Count)
+// CHECK-I386: call { ptr, i32 } asm sideeffect "rep stos$(l$|d$)", "={di},={cx},{ax},{di},{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(i32 %Data, ptr %Dest, i32 %Count)
// CHECK-I386: ret void
// CHECK-I386: }
// CHECK-X64-LABEL: define{{.*}} void @test__stosd
-// CHECK-X64: call { ptr, i64 } asm sideeffect "rep stos$(l$|d$)", "={di},={cx},{ax},0,1,~{memory},~{dirflag},~{fpsr},~{flags}"(i32 %Data, ptr %Dest, i64 %Count)
+// CHECK-X64: call { ptr, i64 } asm sideeffect "rep stos$(l$|d$)", "={di},={cx},{ax},{di},{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(i32 %Data, ptr %Dest, i64 %Count)
// CHECK-X64: ret void
// CHECK-X64: }
@@ -88,12 +88,12 @@ void test__movsd(unsigned long *Dest, unsigned long *Src, size_t Count) {
return __movsd(Dest, Src, Count);
}
// CHECK-I386-LABEL: define{{.*}} void @test__movsd
-// CHECK-I386: tail call { ptr, ptr, i32 } asm sideeffect "xchg $(%esi, $1$|$1, esi$)\0Arep movs$(l$|d$)\0Axchg $(%esi, $1$|$1, esi$)", "={di},=r,={cx},0,1,2,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i32 %Count)
+// CHECK-I386: tail call { ptr, ptr, i32 } asm sideeffect "xchg $(%esi, $1$|$1, esi$)\0Arep movs$(l$|d$)\0Axchg $(%esi, $1$|$1, esi$)", "={di},=r,={cx},{di},1,{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i32 %Count)
// CHECK-I386: ret void
// CHECK-I386: }
// CHECK-X64-LABEL: define{{.*}} void @test__movsd
-// CHECK-X64: call { ptr, ptr, i64 } asm sideeffect "rep movs$(l$|d$)", "={di},={si},={cx},0,1,2,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i64 %Count)
+// CHECK-X64: call { ptr, ptr, i64 } asm sideeffect "rep movs$(l$|d$)", "={di},={si},={cx},{di},{si},{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i64 %Count)
// CHECK-X64: ret void
// CHECK-X64: }
@@ -102,7 +102,7 @@ void test__stosq(unsigned __int64 *Dest, unsigned __int64 Data, size_t Count) {
return __stosq(Dest, Data, Count);
}
// CHECK-X64-LABEL: define{{.*}} void @test__stosq
-// CHECK-X64: call { ptr, i64 } asm sideeffect "rep stosq", "={di},={cx},{ax},0,1,~{memory},~{dirflag},~{fpsr},~{flags}"(i64 %Data, ptr %Dest, i64 %Count)
+// CHECK-X64: call { ptr, i64 } asm sideeffect "rep stosq", "={di},={cx},{ax},{di},{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(i64 %Data, ptr %Dest, i64 %Count)
// CHECK-X64: ret void
// CHECK-X64: }
@@ -110,7 +110,7 @@ void test__movsq(unsigned __int64 *Dest, unsigned __int64 *Src, size_t Count) {
return __movsq(Dest, Src, Count);
}
// CHECK-X64-LABEL: define{{.*}} void @test__movsq
-// CHECK-X64: call { ptr, ptr, i64 } asm sideeffect "rep movsq", "={di},={si},={cx},0,1,2,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i64 %Count)
+// CHECK-X64: call { ptr, ptr, i64 } asm sideeffect "rep movsq", "={di},={si},={cx},{di},{si},{cx},~{memory},~{dirflag},~{fpsr},~{flags}"(ptr %Dest, ptr %Src, i64 %Count)
// CHECK-X64: ret void
// CHECK-X64: }
#endif
@@ -698,13 +698,13 @@ long test_InterlockedExchange_HLERelease(long volatile *Target, long Value) {
long test_InterlockedCompareExchange_HLEAcquire(long volatile *Destination,
long Exchange, long Comparand) {
// CHECK-INTEL: define{{.*}} i32 @test_InterlockedCompareExchange_HLEAcquire(ptr{{.*}}%Destination, i32{{[a-z_ ]*}}%Exchange, i32{{[a-z_ ]*}}%Comparand)
-// CHECK-INTEL: call i32 asm sideeffect ".byte 0xf2 ; lock ; cmpxchg $($2, $1$|$1, $2$)", "={ax},=*m,r,0,*m,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr elementtype(i32) %Destination, i32 %Exchange, i32 %Comparand, ptr elementtype(i32) %Destination)
+// CHECK-INTEL: call i32 asm sideeffect ".byte 0xf2 ; lock ; cmpxchg $($2, $1$|$1, $2$)", "={ax},=*m,r,{ax},*m,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr elementtype(i32) %Destination, i32 %Exchange, i32 %Comparand, ptr elementtype(i32) %Destination)
return _InterlockedCompareExchange_HLEAcquire(Destination, Exchange, Comparand);
}
long test_InterlockedCompareExchange_HLERelease(long volatile *Destination,
long Exchange, long Comparand) {
// CHECK-INTEL: define{{.*}} i32 @test_InterlockedCompareExchange_HLERelease(ptr{{.*}}%Destination, i32{{[a-z_ ]*}}%Exchange, i32{{[a-z_ ]*}}%Comparand)
-// CHECK-INTEL: call i32 asm sideeffect ".byte 0xf3 ; lock ; cmpxchg $($2, $1$|$1, $2$)", "={ax},=*m,r,0,*m,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr elementtype(i32) %Destination, i32 %Exchange, i32 %Comparand, ptr elementtype(i32) %Destination)
+// CHECK-INTEL: call i32 asm sideeffect ".byte 0xf3 ; lock ; cmpxchg $($2, $1$|$1, $2$)", "={ax},=*m,r,{ax},*m,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr elementtype(i32) %Destination, i32 %Exchange, i32 %Comparand, ptr elementtype(i32) %Destination)
return _InterlockedCompareExchange_HLERelease(Destination, Exchange, Comparand);
}
#endif
@@ -722,13 +722,13 @@ __int64 test_InterlockedExchange64_HLERelease(__int64 volatile *Target, __int64
__int64 test_InterlockedCompareExchange64_HLEAcquire(__int64 volatile *Destination,
__int64 Exchange, __int64 Comparand) {
// CHECK-X64: define{{.*}} i64 @test_InterlockedCompareExchange64_HLEAcquire(ptr{{.*}}%Destination, i64{{[a-z_ ]*}}%Exchange, i64{{[a-z_ ]*}}%Comparand)
-// CHECK-X64: call i64 asm sideeffect ".byte 0xf2 ; lock ; cmpxchg $($2, $1$|$1, $2$)", "={ax},=*m,r,0,*m,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr elementtype(i64) %Destination, i64 %Exchange, i64 %Comparand, ptr elementtype(i64) %Destination)
+// CHECK-X64: call i64 asm sideeffect ".byte 0xf2 ; lock ; cmpxchg $($2, $1$|$1, $2$)", "={ax},=*m,r,{ax},*m,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr elementtype(i64) %Destination, i64 %Exchange, i64 %Comparand, ptr elementtype(i64) %Destination)
return _InterlockedCompareExchange64_HLEAcquire(Destination, Exchange, Comparand);
}
__int64 test_InterlockedCompareExchange64_HLERelease(__int64 volatile *Destination,
__int64 Exchange, __int64 Comparand) {
// CHECK-X64: define{{.*}} i64 @test_InterlockedCompareExchange64_HLERelease(ptr{{.*}}%Destination, i64{{[a-z_ ]*}}%Exchange, i64{{[a-z_ ]*}}%Comparand)
-// CHECK-X64: call i64 asm sideeffect ".byte 0xf3 ; lock ; cmpxchg $($2, $1$|$1, $2$)", "={ax},=*m,r,0,*m,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr elementtype(i64) %Destination, i64 %Exchange, i64 %Comparand, ptr elementtype(i64) %Destination)
+// CHECK-X64: call i64 asm sideeffect ".byte 0xf3 ; lock ; cmpxchg $($2, $1$|$1, $2$)", "={ax},=*m,r,{ax},*m,~{memory},~{dirflag},~{fpsr},~{flags}"(ptr elementtype(i64) %Destination, i64 %Exchange, i64 %Comparand, ptr elementtype(i64) %Destination)
return _InterlockedCompareExchange64_HLERelease(Destination, Exchange, Comparand);
}
#endif
diff --git a/clang/test/CodeGen/x86-asm-register-constraint-mix.c b/clang/test/CodeGen/x86-asm-register-constraint-mix.c
new file mode 100644
index 0000000000000..038a978349c9a
--- /dev/null
+++ b/clang/test/CodeGen/x86-asm-register-constraint-mix.c
@@ -0,0 +1,62 @@
+// REQUIRES: x86-registered-target
+// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu -O2 -emit-llvm %s -o - | FileCheck %s
+
+unsigned long foo(unsigned long addr, unsigned long a0,
+ unsigned long a1, unsigned long a2,
+ unsigned long a3, unsigned long a4,
+ unsigned long a5) {
+ register unsigned long result asm("rax");
+ register unsigned long addr1 asm("rax") = addr;
+ register unsigned long b0 asm("rdi") = a0;
+ register unsigned long b1 asm("rsi") = a1;
+ register unsigned long b2 asm("rdx") = a2;
+ register unsigned long b3 asm("rcx") = a3;
+ register unsigned long b4 asm("r8") = a4;
+ register unsigned long b5 asm("r9") = a5;
+
+ // CHECK: tail call i64 asm "call *$1", "={rax},{rax},{rdi},{rsi},{rdx},{rcx},{r8},{r9},{rax},~{dirflag},~{fpsr},~{flags}"(i64 %addr, i64 %a0, i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 undef)
+ asm("call *%1"
+ : "+r" (result)
+ : "r"(addr1), "r"(b0), "r"(b1), "r"(b2), "r"(b3), "r"(b4), "r"(b5));
+ return result;
+}
+
+unsigned long foo1(unsigned long addr, unsigned long a0,
+ unsigned long a1, unsigned long a2,
+ unsigned long a3, unsigned long a4,
+ unsigned long a5) {
+ unsigned long result;
+ unsigned long addr1 = addr;
+ unsigned long b0 = a0;
+ unsigned long b1 = a1;
+ unsigned long b2 = a2;
+ unsigned long b3 = a3;
+ unsigned long b4 = a4;
+ unsigned long b5 = a5;
+
+ // CHECK: tail call i64 asm "call *$1", "={rax},{rax},{rdi},{rsi},{rdx},{rcx},{r8},{r9},{rax},~{dirflag},~{fpsr},~{flags}"(i64 %addr, i64 %a0, i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 undef)
+ asm("call *%1"
+ : "+{rax}" (result)
+ : "{rax}"(addr1), "{rdi}"(b0), "{rsi}"(b1), "{rdx}"(b2), "{rcx}"(b3), "{r8}"(b4), "{r9}"(b5));
+ return result;
+}
+
+unsigned long foo2(unsigned long addr, unsigned long a0,
+ unsigned long a1, unsigned long a2,
+ unsigned long a3, unsigned long a4,
+ unsigned long a5) {
+ register unsigned long result asm("rax");
+ unsigned long addr1 = addr;
+ unsigned long b0 = a0;
+ register unsigned long b1 asm ("rsi") = a1;
+ unsigned long b2 = a2;
+ unsigned long b3 = a3;
+ register unsigned long b4 asm ("r8") = a4;
+ unsigned long b5 = a5;
+
+ // CHECK: tail call i64 asm "call *$1", "={rax},{rax},{rdi},{rsi},{rdx},{rcx},{r8},{r9},{rax},~{dirflag},~{fpsr},~{flags}"(i64 %addr, i64 %a0, i64 %a1, i64 %a2, i64 %a3, i64 %a4, i64 %a5, i64 undef)
+ asm("call *%1"
+ : "+r" (result)
+ : "{rax}"(addr1), "{rdi}"(b0), "r"(b1), "{rdx}"(b2), "{rcx}"(b3), "r"(b4), "{r9}"(b5));
+ return result;
+}
diff --git a/clang/test/CodeGen/z-hard-register-inline-asm.c b/clang/test/CodeGen/z-hard-register-inline-asm.c
new file mode 100644
index 0000000000000..bf70af5765f64
--- /dev/null
+++ b/clang/test/CodeGen/z-hard-register-inline-asm.c
@@ -0,0 +1,52 @@
+// RUN: %clang_cc1 -triple s390x-ibm-linux -emit-llvm -o - %s | FileCheck --check-prefix=CHECK-COUNT %s
+// RUN: %clang_cc1 -triple s390x-ibm-zos -emit-llvm -o - %s | FileCheck --check-prefix=CHECK-COUNT-2 %s
+
+void f1() {
+ int a, b;
+ register int c asm("r1");
+ register int d asm("r2");
+
+ // CHECK-COUNT: call i32 asm "lhi $0,5\0A", "={r1}"
+ // CHECK-COUNT-2: call i32 asm "lhi $0,5\0A", "={r1}"
+ __asm("lhi %0,5\n"
+ : "={r1}"(a)
+ :
+ :);
+ __asm("lhi %0,5\n"
+ : "=r"(c)
+ :
+ :);
+
+ // CHECK-COUNT: call i32 asm "lgr $0,$1\0A", "={r1},{r2}"
+ // CHECK-COUNT-2: call i32 asm "lgr $0,$1\0A", "={r1},{r2}"
+ __asm("lgr %0,%1\n"
+ : "={r1}"(a)
+ : "{r2}"(b)
+ :);
+ __asm("lgr %0,%1\n"
+ : "=r"(c)
+ : "r"(d)
+ :);
+
+ // CHECK-COUNT: call i32 asm "lgr $0,$1\0A", "={r1},{r2}"
+ // CHECK-COUNT-2: call i32 asm "lgr $0,$1\0A", "={r1},{r2}"
+ __asm("lgr %0,%1\n"
+ : "={%r1}"(a)
+ : "{%r2}"(b)
+ :);
+ __asm("lgr %0,%1\n"
+ : "={r1}"(a)
+ : "{%r2}"(b)
+ :);
+
+ // CHECK-COUNT: call i32 asm "lgr $0,$1\0A", "=&{r1},{r2}"
+ // CHECK-COUNT-2: call i32 asm "lgr $0,$1\0A", "=&{r1},{r2}"
+ __asm("lgr %0,%1\n"
+ : "=&{r1}"(a)
+ : "{%r2}"(b)
+ :);
+ __asm("lgr %0,%1\n"
+ : "=&r"(c)
+ : "r"(d)
+ :);
+}
diff --git a/clang/test/CodeGen/z-multi-hard-register-inline-asm.c b/clang/test/CodeGen/z-multi-hard-register-inline-asm.c
new file mode 100644
index 0000000000000..1d49d02c0086d
--- /dev/null
+++ b/clang/test/CodeGen/z-multi-hard-register-inline-asm.c
@@ -0,0 +1,35 @@
+// RUN: %clang_cc1 -triple s390x-ibm-linux -emit-llvm -o - %s | FileCheck --check-prefix=CHECK-COUNT %s
+
+void f1() {
+ int a, b;
+
+ // CHECK-COUNT: call i32 asm "lhi $0,5\0A", "={r1}{r2}"
+ __asm("lhi %0,5\n"
+ : "={r1}{r2}"(a)
+ :
+ :);
+
+ // CHECK-COUNT: call i32 asm "lgr $0,$1\0A", "={r1}{r3},{r2}{r4}"
+ __asm("lgr %0,%1\n"
+ : "={r1}{r3}"(a)
+ : "{r2}{r4}"(b)
+ :);
+
+ // CHECK-COUNT: call i32 asm "lgr $0,$1\0A", "={r1}{r3},{r2}{r4}"
+ __asm("lgr %0,%1\n"
+ : "={%r1}{%r3}"(a)
+ : "{%r2}{%r4}"(b)
+ :);
+
+ // CHECK-COUNT: call i32 asm "lgr $0,$1\0A", "=&{r1}{r3},{r2}"
+ __asm("lgr %0,%1\n"
+ : "=&{r1}{r3}"(a)
+ : "{%r2}"(b)
+ :);
+
+ // CHECK-COUNT: call i32 asm "lgr $0,$1\0A", "=r{r1}{r3},{r2}r{r4}"
+ __asm("lgr %0,%1\n"
+ : "=r{r1}{r3}"(a)
+ : "{r2}r{r4}"(b)
+ :);
+}
diff --git a/clang/test/Sema/z-hard-register-inline-asm.c b/clang/test/Sema/z-hard-register-inline-asm.c
new file mode 100644
index 0000000000000..e27ecc1c832b7
--- /dev/null
+++ b/clang/test/Sema/z-hard-register-inline-asm.c
@@ -0,0 +1,49 @@
+// RUN: %clang_cc1 %s -triple s390x-ibm-linux -fsyntax-only -verify
+// RUN: %clang_cc1 %s -triple s390x-ibm-zos -fsyntax-only -verify
+
+void f1() {
+ int a, b;
+ __asm("lhi %0,5\n"
+ : "={r2}"(a)
+ :);
+
+ __asm("lgr %0,%1\n"
+ : "={r2}"(a)
+ : "{r1}"(b));
+
+ __asm("lgr %0,%1\n"
+ : "={r2}"(a)
+ : "{%r1}"(b));
+
+ __asm("lgr %0,%1\n"
+ : "=&{r1}"(a)
+ : "{r2}"(b));
+
+ __asm("lhi %0,5\n"
+ : "={r2"(a) // expected-error {{invalid output constraint '={r2' in asm}}
+ :);
+
+ __asm("lhi %0,5\n"
+ : "={r17}"(a) // expected-error {{invalid output constraint '={r17}' in asm}}
+ :);
+
+ __asm("lhi %0,5\n"
+ : "={}"(a) // expected-error {{invalid output constraint '={}' in asm}}
+ :);
+
+ __asm("lhi %0,5\n"
+ : "=&{r2"(a) // expected-error {{invalid output constraint '=&{r2' in asm}}
+ :);
+
+ __asm("lgr %0,%1\n"
+ : "=r"(a)
+ : "{r1"(b)); // expected-error {{invalid input constraint '{r1' in asm}}
+
+ __asm("lgr %0,%1\n"
+ : "=r"(a)
+ : "{}"(b)); // expected-error {{invalid input constraint '{}' in asm}}
+
+ __asm("lgr %0,%1\n"
+ : "={r1}"(a)
+ : "{r17}"(b)); // expected-error {{invalid input constraint '{r17}' in asm}}
+}
diff --git a/clang/test/Sema/z-multi-hard-register-inline-asm.c b/clang/test/Sema/z-multi-hard-register-inline-asm.c
new file mode 100644
index 0000000000000..385eeaf745302
--- /dev/null
+++ b/clang/test/Sema/z-multi-hard-register-inline-asm.c
@@ -0,0 +1,30 @@
+// RUN: %clang_cc1 %s -triple s390x-ibm-linux -fsyntax-only -verify
+// RUN: %clang_cc1 %s -triple s390x-ibm-zos -fsyntax-only -verify
+
+void f1() {
+ int a, b;
+
+ __asm("lgr %0,%1\n"
+ : "={r2}"(a)
+ : "{r1}{r3}"(b));
+
+ __asm("lgr %0,%1\n"
+ : "={r2}{r1}"(a)
+ : "{r1}"(b));
+
+ __asm("lgr %0,%1\n"
+ : "={r2}"(a)
+ : "{r1}{r3}r4}"(b)); // expected-error {{invalid input constraint '{r1}{r3}r4}' in asm}}
+
+ __asm("lgr %0,%1\n"
+ : "={r2}"(a)
+ : "{r1r3}{r4}"(b)); // expected-error {{invalid input constraint '{r1r3}{r4}' in asm}}
+
+ __asm("lgr %0,%1\n"
+ : "={r1}{r3}r4}"(a) // expected-error {{invalid output constraint '={r1}{r3}r4}' in asm}}
+ : "{r2}"(b));
+
+ __asm("lgr %0,%1\n"
+ : "={r1r3}{r4}"(a) // expected-error {{invalid output constraint '={r1r3}{r4}' in asm}}
+ : "{r2}"(b));
+}
diff --git a/llvm/test/CodeGen/SystemZ/zos-inline-asm.ll b/llvm/test/CodeGen/SystemZ/zos-inline-asm.ll
new file mode 100644
index 0000000000000..4d849730bd827
--- /dev/null
+++ b/llvm/test/CodeGen/SystemZ/zos-inline-asm.ll
@@ -0,0 +1,261 @@
+; RUN: llc -mtriple s390x-ibm-zos < %s | FileCheck %s
+; Source to generate .ll file
+;
+; void f1() {
+; int a, b;
+; __asm(" lhi %0,5\n"
+; : "={r1}"(a)
+; :
+; :);
+;
+; __asm(" lgr %0,%1\n"
+; : "={r1}"(a)
+; : "{r2}"(b)
+; :);
+;
+; __asm(" lgr %0,%1\n"
+; : "=r{r1}{r2}"(a)
+; : "{r4}{r5}"(b)
+; :);
+; }
+;
+; void f2() {
+; int a, m_b;
+; __asm(" stg %1,%0\n"
+; : "=m"(m_b)
+; : "{r1}"(a)
+; :);
+; }
+;
+; void f3() {
+; int r15, r1;
+;
+; __asm(" svc 109\n"
+; : "={r15}"(r15)
+; : "{r1}"(r1), "{r15}"(25)
+; :);
+; }
+;
+; void f4() {
+; ptr parm;
+; long long rc, reason;
+; char *code;
+;
+; __asm(" pc 0(%3)"
+; : "={r0}"(reason), "+{r1}"(parm), "={r15}"(rc)
+; : "r"(code)
+; :);
+; }
+;
+; void f5() {
+;
+; int a;
+; int b;
+; int c;
+;
+; __asm(" lhi %0,10\n"
+; " ar %0,%0\n"
+; : "=&r"(a)
+; :
+; :);
+;
+; __asm(" lhi %0,10\n"
+; " ar %0,%0\n"
+; : "={&r2}"(b)
+; :
+; :);
+;
+; __asm(" lhi %0,10\n"
+; " ar %0,%0\n"
+; : "={&r2}"(c)
+; :
+; :);
+; }
+;
+; void f7() {
+; int a, b, res;
+;
+; a = 2147483640;
+; b = 10;
+;
+; __asm(" alr %0,%1\n"
+; " jo *-4\n"
+; :"=r"(res)
+; :"r"(a), "r"(b)
+; :);
+; }
+;
+; int f8() {
+;
+; int a, b, res;
+; a = b = res = -1;
+;
+; __asm(" lhi 1,5\n"
+; :
+; :
+; : "r1");
+;
+; __asm(" lgr 2,1\n"
+; :
+; :
+; : "r2");
+;
+; __asm(" stg 2,%0\n"
+; :
+; : "r"(res)
+; :);
+;
+; return res;
+; }
+
+define hidden void @f1() {
+; CHECK-LABEL: f1:
+; CHECK: *APP
+; CHECK-NEXT: lhi 1,5
+; CHECK: *NO_APP
+; CHECK: *APP
+; CHECK-NEXT: lgr 1,2
+; CHECK: *NO_APP
+; CHECK: *APP
+; CHECK-NEXT: lgr 0,4
+; CHECK: *NO_APP
+entry:
+ %a = alloca i32, align 4
+ %b = alloca i32, align 4
+ %0 = call i32 asm " lhi $0,5\0A", "={r1}"()
+ store i32 %0, ptr %a, align 4
+ %1 = load i32, ptr %b, align 4
+ %2 = call i32 asm " lgr $0,$1\0A", "={r1},{r2}"(i32 %1)
+ store i32 %2, ptr %a, align 4
+ %3 = load i32, ptr %b, align 4
+ %4 = call i32 asm " lgr $0,$1\0A", "=r{r1}{r2},{r4}{r5}"(i32 %3)
+ store i32 %4, ptr %a, align 4
+ ret void
+}
+
+define hidden void @f2() {
+; CHECK-LABEL: f2:
+; CHECK: *APP
+; CHECK-NEXT: stg 1,{{.*}}(4,0)
+; CHECK: *NO_APP
+entry:
+ %a = alloca i32, align 4
+ %m_b = alloca i32, align 4
+ %0 = load i32, ptr %a, align 4
+ call void asm " stg $1,$0\0A", "=*m,{r1}"(ptr elementtype(i32) %m_b, i32 %0)
+ ret void
+}
+
+define hidden void @f3() {
+; CHECK-LABEL: f3:
+; CHECK: l 1,{{.*}}(4)
+; CHECK: lhi 15,25
+; CHECK: *APP
+; CHECK-NEXT: svc 109
+; CHECK: *NO_APP
+entry:
+ %r15 = alloca i32, align 4
+ %r1 = alloca i32, align 4
+ %0 = load i32, ptr %r1, align 4
+ %1 = call i32 asm " svc 109\0A", "={r15},{r1},{r15}"(i32 %0, i32 25)
+ store i32 %1, ptr %r15, align 4
+ ret void
+}
+
+define hidden void @f4() {
+; CHECK-LABEL: f4:
+; CHECK: *APP
+; CHECK-NEXT: pc 0
+; CHECK: *NO_APP
+; CHECK: stg 0,{{.*}}(4)
+; CHECK-NEXT: stg 1,{{.*}}(4)
+; CHECK-NEXT: stg 15,{{.*}}(4)
+entry:
+ %parm = alloca ptr, align 8
+ %rc = alloca i64, align 8
+ %reason = alloca i64, align 8
+ %code = alloca ptr, align 8
+ %0 = load ptr, ptr %parm, align 8
+ %1 = load ptr, ptr %code, align 8
+ %2 = call { i64, ptr, i64 } asm " pc 0($3)", "={r0},={r1},={r15},r,1"(ptr %1, ptr %0)
+ %asmresult = extractvalue { i64, ptr, i64 } %2, 0
+ %asmresult1 = extractvalue { i64, ptr, i64 } %2, 1
+ %asmresult2 = extractvalue { i64, ptr, i64 } %2, 2
+ store i64 %asmresult, ptr %reason, align 8
+ store ptr %asmresult1, ptr %parm, align 8
+ store i64 %asmresult2, ptr %rc, align 8
+ ret void
+}
+
+define hidden void @f5() {
+; CHECK-LABEL: f5:
+; CHECK: *APP
+; CHECK-NEXT: lhi {{[0-9]}},10
+; CHECK-NEXT: ar {{[0-9]}},{{[0-9]}}
+; CHECK: *NO_APP
+; CHECK: *APP
+; CHECK-NEXT: lhi 2,10
+; CHECK-NEXT: ar 2,2
+; CHECK: *NO_APP
+; CHECK: *APP
+; CHECK-NEXT: lhi 2,10
+; CHECK-NEXT: ar 2,2
+; CHECK: *NO_APP
+entry:
+ %a = alloca i32, align 4
+ %b = alloca i32, align 4
+ %c = alloca i32, align 4
+ %0 = call i32 asm " lhi $0,10\0A ar $0,$0\0A", "=&r"()
+ store i32 %0, ptr %a, align 4
+ %1 = call i32 asm " lhi $0,10\0A ar $0,$0\0A", "=&{r2}"()
+ store i32 %1, ptr %b, align 4
+ %2 = call i32 asm " lhi $0,10\0A ar $0,$0\0A", "=&{r2}"()
+ store i32 %2, ptr %c, align 4
+ ret void
+}
+
+define hidden void @f7() {
+; CHECK-LABEL: f7:
+; CHECK: *APP
+; CHECK-NEXT: alr {{[0-9]}},{{[0-9]}}
+; CHECK-NEXT: {{.*}}:
+; CHECK-NEXT: jo {{.*}}-4
+; CHECK: *NO_APP
+entry:
+ %a = alloca i32, align 4
+ %b = alloca i32, align 4
+ %res = alloca i32, align 4
+ store i32 2147483640, ptr %a, align 4
+ store i32 10, ptr %b, align 4
+ %0 = load i32, ptr %a, align 4
+ %1 = load i32, ptr %b, align 4
+ %2 = call i32 asm " alr $0,$1\0A jo *-4\0A", "=r,r,r"(i32 %0, i32 %1)
+ store i32 %2, ptr %res, align 4
+ ret void
+}
+
+define hidden signext i32 @f8() {
+; CHECK-LABEL: f8:
+; CHECK: *APP
+; CHECK-NEXT: lhi 1,5
+; CHECK: *NO_APP
+; CHECK: *APP
+; CHECK-NEXT: lgr 2,1
+; CHECK: *NO_APP
+; CHECK: *APP
+; CHECK-NEXT: stg 2,{{.*}}(4,0)
+; CHECK: *NO_APP
+; CHECK: lgf 3,{{.*}}(4)
+entry:
+ %a = alloca i32, align 4
+ %b = alloca i32, align 4
+ %res = alloca i32, align 4
+ store i32 -1, ptr %res, align 4
+ store i32 -1, ptr %b, align 4
+ store i32 -1, ptr %a, align 4
+ call void asm sideeffect " lhi 1,5\0A", "~{r1}"()
+ call void asm sideeffect " lgr 2,1\0A", "~{r2}"()
+ call void asm " stg 2,$0\0A", "=*m"(ptr elementtype(i32) %res)
+ %0 = load i32, ptr %res, align 4
+ ret i32 %0
+}
>From c8f1af95c8b4e03f213838f0e868906d56838129 Mon Sep 17 00:00:00 2001
From: Tony Tao <tonytao at ca.ibm.com>
Date: Thu, 2 Apr 2026 11:09:31 -0400
Subject: [PATCH 2/4] Rebasing updates and disable multiple constraints of
different types
---
clang/include/clang/AST/Stmt.h | 7 +-
.../include/clang/Basic/DiagnosticASTKinds.td | 2 +
.../clang/Basic/DiagnosticSemaKinds.td | 2 -
clang/lib/AST/Stmt.cpp | 118 +++++++++++++--
clang/lib/Basic/TargetInfo.cpp | 4 +-
clang/lib/CodeGen/CGStmt.cpp | 140 ++----------------
.../z-multi-hard-register-inline-asm.c | 6 -
llvm/test/CodeGen/SystemZ/zos-inline-asm.ll | 88 ++++-------
8 files changed, 152 insertions(+), 215 deletions(-)
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index 1711d05a16d93..55e8ad01dd562 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -3327,10 +3327,11 @@ class AsmStmt : public Stmt {
/// Look at AsmExpr and if it is a variable declared as using a particular
/// register add that as a constraint that will be used in this asm stmt.
std::string
- addVariableConstraints(StringRef Constraint, const Expr &AsmExpr,
- const TargetInfo &Target, bool EarlyClobber,
+ AddVariableConstraints(ASTContext &C, StringRef Constraint,
+ const Expr &AsmExpr, const TargetInfo &Target,
+ bool EarlyClobber,
UnsupportedConstraintCallbackTy UnsupportedCB,
- std::string *GCCReg = nullptr) const;
+ SmallVector<std::string> *GCCRegs = nullptr) const;
//===--- Output operands ---===//
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index bde418695f647..6f20aefa75ad0 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -450,6 +450,8 @@ let CategoryName = "Inline Assembly Issue" in {
"empty symbolic operand name in inline assembly string">;
def err_asm_invalid_operand_number : Error<
"invalid operand number in inline asm string">;
+ def err_asm_hard_reg_variable_duplicate : Error<
+ "hard register operand already defined as register variable">;
}
// vtable related.
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b4e5cf65f6f70..4d352f1def04b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10086,8 +10086,6 @@ let CategoryName = "Inline Assembly Issue" in {
"more than one input constraint matches the same output '%0'">;
def err_store_value_to_reg : Error<
"impossible constraint in asm: cannot store value into a register">;
- def err_asm_hard_reg_variable_duplicate : Error<
- "hard register operand already defined as register variable">;
def warn_asm_label_on_auto_decl : Warning<
"ignored asm label '%0' on automatic variable">;
diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp
index 15d0e6435aaf3..b3dd72c212252 100644
--- a/clang/lib/AST/Stmt.cpp
+++ b/clang/lib/AST/Stmt.cpp
@@ -455,25 +455,75 @@ AttributedStmt *AttributedStmt::CreateEmpty(const ASTContext &C,
return new (Mem) AttributedStmt(EmptyShell(), NumAttrs);
}
-std::string
-AsmStmt::addVariableConstraints(StringRef Constraint, const Expr &AsmExpr,
- const TargetInfo &Target, bool EarlyClobber,
- UnsupportedConstraintCallbackTy UnsupportedCB,
- std::string *GCCReg) const {
+/// Is it valid to apply a register constraint for a variable marked with
+/// the "register asm" construct?
+/// Optionally, if it is determined that we can, we set "Register" to the
+/// regiser name.
+static bool
+ShouldApplyRegisterVariableConstraint(const Expr &AsmExpr,
+ std::string *Register = nullptr) {
+
const DeclRefExpr *AsmDeclRef = dyn_cast<DeclRefExpr>(&AsmExpr);
if (!AsmDeclRef)
- return Constraint.str();
+ return false;
const ValueDecl &Value = *AsmDeclRef->getDecl();
const VarDecl *Variable = dyn_cast<VarDecl>(&Value);
if (!Variable)
- return Constraint.str();
+ return false;
if (Variable->getStorageClass() != SC_Register)
- return Constraint.str();
+ return false;
AsmLabelAttr *Attr = Variable->getAttr<AsmLabelAttr>();
if (!Attr)
+ return false;
+
+ if (Register != nullptr)
+ // Set the register to return from Attr.
+ *Register = Attr->getLabel().str();
+ return true;
+}
+
+/// AddVariableConstraints:
+/// Look at AsmExpr and if it is a variable declared as using a particular
+/// register add that as a constraint that will be used in this asm stmt.
+/// Whether it can be used or not is dependent on querying
+/// ShouldApplyRegisterVariableConstraint() Also check whether the "hard
+/// register" inline asm constraint (i.e. "{reg-name}") is specified. If so, add
+/// that as a constraint that will be used in this asm stmt.
+std::string
+AsmStmt::AddVariableConstraints(ASTContext &C, StringRef Constraint,
+ const Expr &AsmExpr, const TargetInfo &Target,
+ const bool EarlyClobber,
+ UnsupportedConstraintCallbackTy UnsupportedCB,
+ SmallVector<std::string> *GCCRegs) const {
+
+ StringRef::iterator I = Constraint.begin(), E = Constraint.end();
+ // Do we have the "hard register" inline asm constraint.
+ StringRef::iterator HardRegStart = std::find(I, E, '{');
+ StringRef::iterator HardRegEnd = std::find(I, E, '}');
+ // Do we have at least one hard register.
+ bool ApplyHardRegisterConstraint =
+ HardRegStart != E && HardRegEnd != E && HardRegEnd > HardRegStart;
+
+ // Do we have "register asm" on a variable.
+ std::string Reg = "";
+ bool ApplyRegisterVariableConstraint =
+ ShouldApplyRegisterVariableConstraint(AsmExpr, &Reg);
+
+ // Diagnose the scenario where we apply both the register variable constraint
+ // and a hard register variable constraint as an unsupported error.
+ // Why? Because we could have a situation where the register passed in through
+ // {...} and the register passed in through the "register asm" construct could
+ // be different, and in this case, there's no way for the compiler to know
+ // which one to emit.
+ if (ApplyHardRegisterConstraint && ApplyRegisterVariableConstraint) {
+ C.getDiagnostics().Report(AsmExpr.getExprLoc(),
+ diag::err_asm_hard_reg_variable_duplicate);
return Constraint.str();
- StringRef Register = Attr->getLabel();
- assert(Target.isValidGCCRegisterName(Register));
+ }
+
+ if (!ApplyHardRegisterConstraint && !ApplyRegisterVariableConstraint)
+ return Constraint.str();
+
// We're using validateOutputConstraint here because we only care if
// this is a register constraint.
TargetInfo::ConstraintInfo Info(Constraint, "");
@@ -481,11 +531,49 @@ AsmStmt::addVariableConstraints(StringRef Constraint, const Expr &AsmExpr,
UnsupportedCB(this, "__asm__");
return Constraint.str();
}
- // Canonicalize the register here before returning it.
- Register = Target.getNormalizedGCCRegisterName(Register);
- if (GCCReg != nullptr)
- *GCCReg = Register.str();
- return (EarlyClobber ? "&{" : "{") + Register.str() + "}";
+
+ if (ApplyRegisterVariableConstraint) {
+ StringRef Register(Reg);
+ assert(Target.isValidGCCRegisterName(Register));
+ // Canonicalize the register here before returning it.
+ Register = Target.getNormalizedGCCRegisterName(Register);
+ if (GCCRegs)
+ GCCRegs->push_back(Register.str());
+ return (EarlyClobber ? "&{" : "{") + Register.str() + "}";
+ }
+
+ std::string NC;
+ while (I != E) {
+ if (*I == '{') {
+ HardRegEnd = std::find(I + 1, E, '}');
+ // No error checking because we already validated this constraint
+ StringRef Register(I + 1, HardRegEnd - I - 1);
+ // If we don't have a valid register name, simply return the constraint.
+ // For example: There are some targets like X86 that use a constraint such
+ // as "@cca", which is validated and then converted into {@cca}. Now this
+ // isn't necessarily a "GCC Register", but in terms of emission, it is
+ // valid since it lowered appropriately in the X86 backend. For the {..}
+ // constraint, we shouldn't be too strict and error out if the register
+ // itself isn't a valid "GCC register".
+ if (!Target.isValidGCCRegisterName(Register))
+ return Constraint.str();
+
+ // Canonicalize the register here before returning it.
+ Register = Target.getNormalizedGCCRegisterName(Register);
+ // Do not need to worry about early clobber since the symbol should be
+ // copied from the original constraint string
+ NC += "{" + Register.str() + "}";
+
+ if (GCCRegs)
+ GCCRegs->push_back(Register.str());
+
+ I = HardRegEnd + 1;
+ } else {
+ NC += *I;
+ ++I;
+ }
+ }
+ return NC;
}
std::string AsmStmt::generateAsmString(const ASTContext &C) const {
diff --git a/clang/lib/Basic/TargetInfo.cpp b/clang/lib/Basic/TargetInfo.cpp
index 213b2682ae641..5296c85a280b2 100644
--- a/clang/lib/Basic/TargetInfo.cpp
+++ b/clang/lib/Basic/TargetInfo.cpp
@@ -884,8 +884,8 @@ bool TargetInfo::validateHardRegisterAsmConstraint(
while (*Name && *Name != '}')
Name++;
- // Missing '}', return false.
- if (!*Name)
+ // Missing '}', or containing other non-hard register constraints, return false.
+ if (!*Name || (*(Name + 1) && *(Name + 1) != '{'))
return false;
// Now we set the register name.
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index d4aede2bc1ec6..364ae55393825 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -2515,127 +2515,6 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) {
CaseRangeBlock = SavedCRBlock;
}
-/// Is it valid to apply a register constraint for a variable marked with
-/// the "register asm" construct?
-/// Optionally, if it is determined that we can, we set "Register" to the
-/// regiser name.
-static bool
-ShouldApplyRegisterVariableConstraint(const Expr &AsmExpr,
- std::string *Register = nullptr) {
-
- const DeclRefExpr *AsmDeclRef = dyn_cast<DeclRefExpr>(&AsmExpr);
- if (!AsmDeclRef)
- return false;
- const ValueDecl &Value = *AsmDeclRef->getDecl();
- const VarDecl *Variable = dyn_cast<VarDecl>(&Value);
- if (!Variable)
- return false;
- if (Variable->getStorageClass() != SC_Register)
- return false;
- AsmLabelAttr *Attr = Variable->getAttr<AsmLabelAttr>();
- if (!Attr)
- return false;
-
- if (Register != nullptr)
- // Set the register to return from Attr.
- *Register = Attr->getLabel().str();
- return true;
-}
-
-/// AddVariableConstraints:
-/// Look at AsmExpr and if it is a variable declared as using a particular
-/// register add that as a constraint that will be used in this asm stmt.
-/// Whether it can be used or not is dependent on querying
-/// ShouldApplyRegisterVariableConstraint() Also check whether the "hard
-/// register" inline asm constraint (i.e. "{reg-name}") is specified. If so, add
-/// that as a constraint that will be used in this asm stmt.
-static std::string
-AddVariableConstraints(const std::string &Constraint, const Expr &AsmExpr,
- const TargetInfo &Target, CodeGenModule &CGM,
- const AsmStmt &Stmt, const bool EarlyClobber,
- SmallVector<std::string> *GCCRegs = nullptr) {
-
- StringRef Str(Constraint);
- StringRef::iterator I = Str.begin(), E = Str.end();
- // Do we have the "hard register" inline asm constraint.
- StringRef::iterator HardRegStart = std::find(I, E, '{');
- StringRef::iterator HardRegEnd = std::find(I, E, '}');
- // Do we have at least one hard register.
- bool ApplyHardRegisterConstraint =
- HardRegStart != E && HardRegEnd != E && HardRegEnd > HardRegStart;
-
- // Do we have "register asm" on a variable.
- std::string Reg = "";
- bool ApplyRegisterVariableConstraint =
- ShouldApplyRegisterVariableConstraint(AsmExpr, &Reg);
-
- // Diagnose the scenario where we apply both the register variable constraint
- // and a hard register variable constraint as an unsupported error.
- // Why? Because we could have a situation where the register passed in through
- // {...} and the register passed in through the "register asm" construct could
- // be different, and in this case, there's no way for the compiler to know
- // which one to emit.
- if (ApplyHardRegisterConstraint && ApplyRegisterVariableConstraint) {
- CGM.getDiags().Report(AsmExpr.getExprLoc(),
- diag::err_asm_hard_reg_variable_duplicate);
- return Constraint;
- }
-
- if (!ApplyHardRegisterConstraint && !ApplyRegisterVariableConstraint)
- return Constraint;
-
- // We're using validateOutputConstraint here because we only care if
- // this is a register constraint.
- TargetInfo::ConstraintInfo Info(Constraint, "");
- if (Target.validateOutputConstraint(Info) && !Info.allowsRegister()) {
- CGM.ErrorUnsupported(&Stmt, "__asm__");
- return Constraint;
- }
-
- if (ApplyRegisterVariableConstraint) {
- StringRef Register(Reg);
- assert(Target.isValidGCCRegisterName(Register));
- // Canonicalize the register here before returning it.
- Register = Target.getNormalizedGCCRegisterName(Register);
- if (GCCRegs)
- GCCRegs->push_back(Register.str());
- return (EarlyClobber ? "&{" : "{") + Register.str() + "}";
- }
-
- std::string NC;
- while (I != E) {
- if (*I == '{') {
- HardRegEnd = std::find(I + 1, E, '}');
- // No error checking because we already validated this constraint
- StringRef Register(I + 1, HardRegEnd - I - 1);
- // If we don't have a valid register name, simply return the constraint.
- // For example: There are some targets like X86 that use a constraint such
- // as "@cca", which is validated and then converted into {@cca}. Now this
- // isn't necessarily a "GCC Register", but in terms of emission, it is
- // valid since it lowered appropriately in the X86 backend. For the {..}
- // constraint, we shouldn't be too strict and error out if the register
- // itself isn't a valid "GCC register".
- if (!Target.isValidGCCRegisterName(Register))
- return Constraint;
-
- // Canonicalize the register here before returning it.
- Register = Target.getNormalizedGCCRegisterName(Register);
- // Do not need to worry about early clobber since the symbol should be
- // copied from the original constraint string
- NC += "{" + Register.str() + "}";
-
- if (GCCRegs)
- GCCRegs->push_back(Register.str());
-
- I = HardRegEnd + 1;
- } else {
- NC += *I;
- ++I;
- }
- }
- return NC;
-}
-
std::pair<llvm::Value*, llvm::Type *> CodeGenFunction::EmitAsmInputLValue(
const TargetInfo::ConstraintInfo &Info, LValue InputValue,
QualType InputType, std::string &ConstraintStr, SourceLocation Loc) {
@@ -2986,17 +2865,17 @@ void CodeGenFunction::EmitAsmStmt(const AsmStmt &S) {
OutExpr = OutExpr->IgnoreParenNoopCasts(getContext());
SmallVector<std::string> GCCRegs;
- OutputConstraint = S.addVariableConstraints(
- OutputConstraint, *OutExpr, getTarget(), Info.earlyClobber(),
+ llvm::dbgs() << "TONY getting output constraints\n";
+ OutputConstraint = S.AddVariableConstraints(
+ getContext(), OutputConstraint, *OutExpr, getTarget(),
+ Info.earlyClobber(),
[&](const Stmt *UnspStmt, StringRef Msg) {
CGM.ErrorUnsupported(UnspStmt, Msg);
},
- &GCCReg);
- OutputConstraint =
- AddVariableConstraints(OutputConstraint, *OutExpr, getTarget(), CGM, S,
- Info.earlyClobber(), &GCCRegs);
+ &GCCRegs);
// Give an error on multiple outputs to same physreg.
if (!GCCRegs.empty()) {
+ llvm::dbgs() << "TONY GCCRegs empty\n";
for (auto R : GCCRegs) {
if (!PhysRegOutputs.insert(R).second)
CGM.Error(S.getAsmLoc(), "multiple outputs to hard register: " + R);
@@ -3153,9 +3032,10 @@ void CodeGenFunction::EmitAsmStmt(const AsmStmt &S) {
InputConstraint =
getTarget().simplifyConstraint(InputConstraint, &OutputConstraintInfos);
- InputConstraint = S.addVariableConstraints(
- InputConstraint, *InputExpr->IgnoreParenNoopCasts(getContext()),
- getTarget(), false /* No EarlyClobber */,
+ InputConstraint = S.AddVariableConstraints(
+ getContext(), InputConstraint,
+ *InputExpr->IgnoreParenNoopCasts(getContext()), getTarget(),
+ false /* No EarlyClobber */,
[&](const Stmt *UnspStmt, std::string_view Msg) {
CGM.ErrorUnsupported(UnspStmt, Msg);
});
diff --git a/clang/test/CodeGen/z-multi-hard-register-inline-asm.c b/clang/test/CodeGen/z-multi-hard-register-inline-asm.c
index 1d49d02c0086d..f110bcdac661e 100644
--- a/clang/test/CodeGen/z-multi-hard-register-inline-asm.c
+++ b/clang/test/CodeGen/z-multi-hard-register-inline-asm.c
@@ -26,10 +26,4 @@ void f1() {
: "=&{r1}{r3}"(a)
: "{%r2}"(b)
:);
-
- // CHECK-COUNT: call i32 asm "lgr $0,$1\0A", "=r{r1}{r3},{r2}r{r4}"
- __asm("lgr %0,%1\n"
- : "=r{r1}{r3}"(a)
- : "{r2}r{r4}"(b)
- :);
}
diff --git a/llvm/test/CodeGen/SystemZ/zos-inline-asm.ll b/llvm/test/CodeGen/SystemZ/zos-inline-asm.ll
index 4d849730bd827..a1571b0fc7fc4 100644
--- a/llvm/test/CodeGen/SystemZ/zos-inline-asm.ll
+++ b/llvm/test/CodeGen/SystemZ/zos-inline-asm.ll
@@ -72,7 +72,7 @@
; :);
; }
;
-; void f7() {
+; void f6() {
; int a, b, res;
;
; a = 2147483640;
@@ -85,7 +85,7 @@
; :);
; }
;
-; int f8() {
+; int f7() {
;
; int a, b, res;
; a = b = res = -1;
@@ -109,16 +109,10 @@
; }
define hidden void @f1() {
-; CHECK-LABEL: f1:
-; CHECK: *APP
-; CHECK-NEXT: lhi 1,5
-; CHECK: *NO_APP
-; CHECK: *APP
-; CHECK-NEXT: lgr 1,2
-; CHECK: *NO_APP
-; CHECK: *APP
-; CHECK-NEXT: lgr 0,4
-; CHECK: *NO_APP
+; CHECK-LABEL: f1 DS 0H
+; CHECK: lhi 1,5
+; CHECK: lgr 1,2
+; CHECK: lgr 0,4
entry:
%a = alloca i32, align 4
%b = alloca i32, align 4
@@ -134,10 +128,8 @@ entry:
}
define hidden void @f2() {
-; CHECK-LABEL: f2:
-; CHECK: *APP
-; CHECK-NEXT: stg 1,{{.*}}(4,0)
-; CHECK: *NO_APP
+; CHECK-LABEL: f2 DS 0H
+; CHECK: stg 1,{{.*}}(4,0)
entry:
%a = alloca i32, align 4
%m_b = alloca i32, align 4
@@ -147,12 +139,10 @@ entry:
}
define hidden void @f3() {
-; CHECK-LABEL: f3:
+; CHECK-LABEL: f3 DS 0H
; CHECK: l 1,{{.*}}(4)
; CHECK: lhi 15,25
-; CHECK: *APP
-; CHECK-NEXT: svc 109
-; CHECK: *NO_APP
+; CHECK: svc 109
entry:
%r15 = alloca i32, align 4
%r1 = alloca i32, align 4
@@ -163,13 +153,11 @@ entry:
}
define hidden void @f4() {
-; CHECK-LABEL: f4:
-; CHECK: *APP
-; CHECK-NEXT: pc 0
-; CHECK: *NO_APP
+; CHECK-LABEL: f4 DS 0H
+; CHECK: pc 0
; CHECK: stg 0,{{.*}}(4)
-; CHECK-NEXT: stg 1,{{.*}}(4)
-; CHECK-NEXT: stg 15,{{.*}}(4)
+; CHECK: stg 1,{{.*}}(4)
+; CHECK: stg 15,{{.*}}(4)
entry:
%parm = alloca ptr, align 8
%rc = alloca i64, align 8
@@ -188,19 +176,13 @@ entry:
}
define hidden void @f5() {
-; CHECK-LABEL: f5:
-; CHECK: *APP
-; CHECK-NEXT: lhi {{[0-9]}},10
-; CHECK-NEXT: ar {{[0-9]}},{{[0-9]}}
-; CHECK: *NO_APP
-; CHECK: *APP
-; CHECK-NEXT: lhi 2,10
-; CHECK-NEXT: ar 2,2
-; CHECK: *NO_APP
-; CHECK: *APP
-; CHECK-NEXT: lhi 2,10
-; CHECK-NEXT: ar 2,2
-; CHECK: *NO_APP
+; CHECK-LABEL: f5 DS 0H
+; CHECK: lhi {{[0-9]}},10
+; CHECK: ar {{[0-9]}},{{[0-9]}}
+; CHECK: lhi 2,10
+; CHECK: ar 2,2
+; CHECK: lhi 2,10
+; CHECK: ar 2,2
entry:
%a = alloca i32, align 4
%b = alloca i32, align 4
@@ -214,13 +196,11 @@ entry:
ret void
}
-define hidden void @f7() {
-; CHECK-LABEL: f7:
-; CHECK: *APP
-; CHECK-NEXT: alr {{[0-9]}},{{[0-9]}}
-; CHECK-NEXT: {{.*}}:
-; CHECK-NEXT: jo {{.*}}-4
-; CHECK: *NO_APP
+define hidden void @f6() {
+; CHECK-LABEL: f6 DS 0H
+; CHECK: alr {{[0-9]}},{{[0-9]}}
+; CHECK: {{.*}} DS 0H
+; CHECK: jo {{.*}}-4
entry:
%a = alloca i32, align 4
%b = alloca i32, align 4
@@ -234,17 +214,11 @@ entry:
ret void
}
-define hidden signext i32 @f8() {
-; CHECK-LABEL: f8:
-; CHECK: *APP
-; CHECK-NEXT: lhi 1,5
-; CHECK: *NO_APP
-; CHECK: *APP
-; CHECK-NEXT: lgr 2,1
-; CHECK: *NO_APP
-; CHECK: *APP
-; CHECK-NEXT: stg 2,{{.*}}(4,0)
-; CHECK: *NO_APP
+define hidden signext i32 @f7() {
+; CHECK-LABEL: f7 DS 0H
+; CHECK: lhi 1,5
+; CHECK: lgr 2,1
+; CHECK: stg 2,{{.*}}(4,0)
; CHECK: lgf 3,{{.*}}(4)
entry:
%a = alloca i32, align 4
>From 4aaa30d947a551f6903b0308c5bb50322b44cb13 Mon Sep 17 00:00:00 2001
From: Tony Tao <tonytao at ca.ibm.com>
Date: Thu, 2 Apr 2026 11:54:38 -0400
Subject: [PATCH 3/4] remove debug statement and add additional test case
---
clang/lib/CodeGen/CGStmt.cpp | 2 --
clang/test/Sema/z-multi-hard-register-inline-asm.c | 4 ++++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 364ae55393825..9ae2b86896ac4 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -2865,7 +2865,6 @@ void CodeGenFunction::EmitAsmStmt(const AsmStmt &S) {
OutExpr = OutExpr->IgnoreParenNoopCasts(getContext());
SmallVector<std::string> GCCRegs;
- llvm::dbgs() << "TONY getting output constraints\n";
OutputConstraint = S.AddVariableConstraints(
getContext(), OutputConstraint, *OutExpr, getTarget(),
Info.earlyClobber(),
@@ -2875,7 +2874,6 @@ void CodeGenFunction::EmitAsmStmt(const AsmStmt &S) {
&GCCRegs);
// Give an error on multiple outputs to same physreg.
if (!GCCRegs.empty()) {
- llvm::dbgs() << "TONY GCCRegs empty\n";
for (auto R : GCCRegs) {
if (!PhysRegOutputs.insert(R).second)
CGM.Error(S.getAsmLoc(), "multiple outputs to hard register: " + R);
diff --git a/clang/test/Sema/z-multi-hard-register-inline-asm.c b/clang/test/Sema/z-multi-hard-register-inline-asm.c
index 385eeaf745302..d3ae2e0403aed 100644
--- a/clang/test/Sema/z-multi-hard-register-inline-asm.c
+++ b/clang/test/Sema/z-multi-hard-register-inline-asm.c
@@ -27,4 +27,8 @@ void f1() {
__asm("lgr %0,%1\n"
: "={r1r3}{r4}"(a) // expected-error {{invalid output constraint '={r1r3}{r4}' in asm}}
: "{r2}"(b));
+
+ __asm("lgr %0,%1\n"
+ : "={r1}{r4}r"(a) // expected-error {{invalid output constraint '={r1}{r4}r' in asm}}
+ : "{r2}"(b));
}
>From 37c0b34e359ca5e84471f8c5059dff218ed8b1a9 Mon Sep 17 00:00:00 2001
From: Tony Tao <tonytao at ca.ibm.com>
Date: Thu, 2 Apr 2026 12:02:47 -0400
Subject: [PATCH 4/4] Formatting
---
clang/lib/AST/Stmt.cpp | 2 +-
clang/lib/Basic/TargetInfo.cpp | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp
index b3dd72c212252..970bee47f4122 100644
--- a/clang/lib/AST/Stmt.cpp
+++ b/clang/lib/AST/Stmt.cpp
@@ -508,7 +508,7 @@ AsmStmt::AddVariableConstraints(ASTContext &C, StringRef Constraint,
std::string Reg = "";
bool ApplyRegisterVariableConstraint =
ShouldApplyRegisterVariableConstraint(AsmExpr, &Reg);
-
+
// Diagnose the scenario where we apply both the register variable constraint
// and a hard register variable constraint as an unsupported error.
// Why? Because we could have a situation where the register passed in through
diff --git a/clang/lib/Basic/TargetInfo.cpp b/clang/lib/Basic/TargetInfo.cpp
index 5296c85a280b2..aa507df6bd48e 100644
--- a/clang/lib/Basic/TargetInfo.cpp
+++ b/clang/lib/Basic/TargetInfo.cpp
@@ -884,7 +884,8 @@ bool TargetInfo::validateHardRegisterAsmConstraint(
while (*Name && *Name != '}')
Name++;
- // Missing '}', or containing other non-hard register constraints, return false.
+ // Missing '}', or containing other non-hard register constraints, return
+ // false.
if (!*Name || (*(Name + 1) && *(Name + 1) != '{'))
return false;
More information about the llvm-commits
mailing list