Skip to content

Commit 71c2e79

Browse files
author
Tony Tao
committed
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
1 parent ea72c08 commit 71c2e79

File tree

19 files changed

+1200
-61
lines changed

19 files changed

+1200
-61
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1760,6 +1760,49 @@ references can be used instead of numeric references.
17601760
return -1;
17611761
}
17621762

1763+
Hard Register Operands for ASM Constraints
1764+
==========================================
1765+
1766+
Clang supports the ability to specify specific hardware registers in inline
1767+
assembly constraints via the use of curly braces ``{}``.
1768+
1769+
Prior to clang-19, the only way to associate an inline assembly constraint
1770+
with a specific register is via the local register variable feature (`GCC
1771+
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
1772+
scope of the variable.
1773+
1774+
Hard register operands will instead only apply to the specific inline ASM
1775+
statement which improves readability and solves a few other issues experienced
1776+
by local register variables, such as:
1777+
1778+
* function calls might clobber register variables
1779+
* the constraints for the register operands are superfluous
1780+
* one register variable cannot be used for 2 different inline
1781+
assemblies if the value is expected in different hard regs
1782+
1783+
The code below is an example of an inline assembly statement using local
1784+
register variables.
1785+
1786+
.. code-block:: c++
1787+
1788+
void foo() {
1789+
register int *p1 asm ("r0") = bar();
1790+
register int *p2 asm ("r1") = bar();
1791+
register int *result asm ("r0");
1792+
asm ("sysint" : "=r" (result) : "0" (p1), "r" (p2));
1793+
}
1794+
Below is the same code but using hard register operands.
1795+
1796+
.. code-block:: c++
1797+
1798+
void foo() {
1799+
int *p1 = bar();
1800+
int *p2 = bar();
1801+
int *result;
1802+
asm ("sysint" : "={r0}" (result) : "0" (p1), "{r1}" (p2));
1803+
}
1804+
1805+
17631806
Objective-C Features
17641807
====================
17651808

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9235,6 +9235,8 @@ let CategoryName = "Inline Assembly Issue" in {
92359235
"more than one input constraint matches the same output '%0'">;
92369236
def err_store_value_to_reg : Error<
92379237
"impossible constraint in asm: can't store value into a register">;
9238+
def err_asm_hard_reg_variable_duplicate : Error<
9239+
"hard register operand already defined as register variable">;
92389240

92399241
def warn_asm_label_on_auto_decl : Warning<
92409242
"ignored asm label '%0' on automatic variable">;

clang/include/clang/Basic/TargetInfo.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1043,9 +1043,17 @@ class TargetInfo : public TransferrableTargetInfo,
10431043
///
10441044
/// This function is used by Sema in order to diagnose conflicts between
10451045
/// the clobber list and the input/output lists.
1046+
/// The constraint should already by validated in validateHardRegisterAsmConstraint
1047+
/// so just do some basic checking
10461048
virtual StringRef getConstraintRegister(StringRef Constraint,
10471049
StringRef Expression) const {
1048-
return "";
1050+
StringRef Reg = Expression;
1051+
size_t Start = Constraint.find('{');
1052+
size_t End = Constraint.find('}');
1053+
if (Start != StringRef::npos && End != StringRef::npos && End > Start)
1054+
Reg = Constraint.substr(Start + 1, End - Start - 1);
1055+
1056+
return Reg;
10491057
}
10501058

10511059
struct ConstraintInfo {
@@ -1187,6 +1195,14 @@ class TargetInfo : public TransferrableTargetInfo,
11871195
validateAsmConstraint(const char *&Name,
11881196
TargetInfo::ConstraintInfo &info) const = 0;
11891197

1198+
// Validate the "hard register" inline asm constraint. This constraint is
1199+
// of the form {<reg-name>}. This constraint is meant to be used
1200+
// as an alternative for the "register asm" construct to put inline
1201+
// asm operands into specific registers.
1202+
bool
1203+
validateHardRegisterAsmConstraint(const char *&Name,
1204+
TargetInfo::ConstraintInfo &info) const;
1205+
11901206
bool resolveSymbolicName(const char *&Name,
11911207
ArrayRef<ConstraintInfo> OutputConstraints,
11921208
unsigned &Index) const;

clang/lib/Basic/TargetInfo.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,18 @@ bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
770770
case 'E':
771771
case 'F':
772772
break; // Pass them.
773+
case '{': {
774+
// First, check the target parser in case it validates
775+
// the {...} constraint differently.
776+
if (validateAsmConstraint(Name, Info))
777+
return true;
778+
779+
// If not, that's okay, we will try to validate it
780+
// using a target agnostic implementation.
781+
if (!validateHardRegisterAsmConstraint(Name, Info))
782+
return false;
783+
break;
784+
}
773785
}
774786

775787
Name++;
@@ -785,6 +797,36 @@ bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
785797
return Info.allowsMemory() || Info.allowsRegister();
786798
}
787799

800+
bool TargetInfo::validateHardRegisterAsmConstraint(
801+
const char *&Name, TargetInfo::ConstraintInfo &Info) const {
802+
// First, swallow the '{'.
803+
Name++;
804+
805+
// Mark the start of the possible register name.
806+
const char *Start = Name;
807+
808+
// Loop through rest of "Name".
809+
// In this loop, we check whether we have a closing curly brace which
810+
// validates the constraint. Also, this allows us to get the correct bounds to
811+
// set our register name.
812+
while (*Name && *Name != '}')
813+
Name++;
814+
815+
// Missing '}' or if there is anything after '}', return false.
816+
if (!*Name || *(Name + 1))
817+
return false;
818+
819+
// Now we set the register name.
820+
std::string Register(Start, Name - Start);
821+
822+
// We validate whether its a valid register to be used.
823+
if (!isValidGCCRegisterName(Register))
824+
return false;
825+
826+
Info.setAllowsRegister();
827+
return true;
828+
}
829+
788830
bool TargetInfo::resolveSymbolicName(const char *&Name,
789831
ArrayRef<ConstraintInfo> OutputConstraints,
790832
unsigned &Index) const {
@@ -917,6 +959,18 @@ bool TargetInfo::validateInputConstraint(
917959
case '!': // Disparage severely.
918960
case '*': // Ignore for choosing register preferences.
919961
break; // Pass them.
962+
case '{': {
963+
// First, check the target parser in case it validates
964+
// the {...} constraint differently.
965+
if (validateAsmConstraint(Name, Info))
966+
return true;
967+
968+
// If not, that's okay, we will try to validate it
969+
// using a target agnostic implementation.
970+
if (!validateHardRegisterAsmConstraint(Name, Info))
971+
return false;
972+
break;
973+
}
920974
}
921975

922976
Name++;

clang/lib/Basic/Targets/AArch64.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,11 +188,6 @@ class LLVM_LIBRARY_VISIBILITY AArch64TargetInfo : public TargetInfo {
188188
std::string &SuggestedModifier) const override;
189189
std::string_view getClobbers() const override;
190190

191-
StringRef getConstraintRegister(StringRef Constraint,
192-
StringRef Expression) const override {
193-
return Expression;
194-
}
195-
196191
int getEHDataRegisterNumber(unsigned RegNo) const override;
197192

198193
bool validatePointerAuthKey(const llvm::APSInt &value) const override;

clang/lib/Basic/Targets/ARM.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,6 @@ class LLVM_LIBRARY_VISIBILITY ARMTargetInfo : public TargetInfo {
213213
std::string &SuggestedModifier) const override;
214214
std::string_view getClobbers() const override;
215215

216-
StringRef getConstraintRegister(StringRef Constraint,
217-
StringRef Expression) const override {
218-
return Expression;
219-
}
220-
221216
CallingConvCheckResult checkCallingConvention(CallingConv CC) const override;
222217

223218
int getEHDataRegisterNumber(unsigned RegNo) const override;

clang/lib/Basic/Targets/RISCV.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,6 @@ class RISCVTargetInfo : public TargetInfo {
7070

7171
std::string_view getClobbers() const override { return ""; }
7272

73-
StringRef getConstraintRegister(StringRef Constraint,
74-
StringRef Expression) const override {
75-
return Expression;
76-
}
77-
7873
ArrayRef<const char *> getGCCRegNames() const override;
7974

8075
int getEHDataRegisterNumber(unsigned RegNo) const override {

clang/lib/Basic/Targets/X86.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ class LLVM_LIBRARY_VISIBILITY X86TargetInfo : public TargetInfo {
307307
return "di";
308308
// In case the constraint is 'r' we need to return Expression
309309
case 'r':
310-
return Expression;
310+
return TargetInfo::getConstraintRegister(Constraint, Expression);
311311
// Double letters Y<x> constraints
312312
case 'Y':
313313
if ((++I != E) && ((*I == '0') || (*I == 'z')))

clang/lib/CodeGen/CGStmt.cpp

Lines changed: 83 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2183,9 +2183,17 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) {
21832183
CaseRangeBlock = SavedCRBlock;
21842184
}
21852185

2186-
static std::string
2187-
SimplifyConstraint(const char *Constraint, const TargetInfo &Target,
2188-
SmallVectorImpl<TargetInfo::ConstraintInfo> *OutCons=nullptr) {
2186+
static std::string SimplifyConstraint(
2187+
const char *Constraint, const TargetInfo &Target,
2188+
SmallVectorImpl<TargetInfo::ConstraintInfo> *OutCons = nullptr) {
2189+
// If we have only the {...} constraint, do not do any simplifications. This
2190+
// already maps to the lower level LLVM inline assembly IR that tells the
2191+
// backend to allocate a specific register. Any validations would have already
2192+
// been done in the Sema stage or will be done in the AddVariableConstraints
2193+
// function.
2194+
if (Constraint[0] == '{' || (Constraint[0] == '&' && Constraint[1] == '{'))
2195+
return std::string(Constraint);
2196+
21892197
std::string Result;
21902198

21912199
while (*Constraint) {
@@ -2232,37 +2240,94 @@ SimplifyConstraint(const char *Constraint, const TargetInfo &Target,
22322240

22332241
return Result;
22342242
}
2243+
/// Is it valid to apply a register constraint for a variable marked with
2244+
/// the "register asm" construct?
2245+
/// Optionally, if it is determined that we can, we set "Register" to the
2246+
/// regiser name.
2247+
static bool
2248+
ShouldApplyRegisterVariableConstraint(const Expr &AsmExpr,
2249+
std::string *Register = nullptr) {
22352250

2236-
/// AddVariableConstraints - Look at AsmExpr and if it is a variable declared
2237-
/// as using a particular register add that as a constraint that will be used
2238-
/// in this asm stmt.
2239-
static std::string
2240-
AddVariableConstraints(const std::string &Constraint, const Expr &AsmExpr,
2241-
const TargetInfo &Target, CodeGenModule &CGM,
2242-
const AsmStmt &Stmt, const bool EarlyClobber,
2243-
std::string *GCCReg = nullptr) {
22442251
const DeclRefExpr *AsmDeclRef = dyn_cast<DeclRefExpr>(&AsmExpr);
22452252
if (!AsmDeclRef)
2246-
return Constraint;
2253+
return false;
22472254
const ValueDecl &Value = *AsmDeclRef->getDecl();
22482255
const VarDecl *Variable = dyn_cast<VarDecl>(&Value);
22492256
if (!Variable)
2250-
return Constraint;
2257+
return false;
22512258
if (Variable->getStorageClass() != SC_Register)
2252-
return Constraint;
2259+
return false;
22532260
AsmLabelAttr *Attr = Variable->getAttr<AsmLabelAttr>();
22542261
if (!Attr)
2262+
return false;
2263+
2264+
if (Register != nullptr)
2265+
// Set the register to return from Attr.
2266+
*Register = Attr->getLabel().str();
2267+
return true;
2268+
}
2269+
2270+
/// AddVariableConstraints:
2271+
/// Look at AsmExpr and if it is a variable declared as using a particular
2272+
/// register add that as a constraint that will be used in this asm stmt.
2273+
/// Whether it can be used or not is dependent on querying
2274+
/// ShouldApplyRegisterVariableConstraint() Also check whether the "hard
2275+
/// register" inline asm constraint (i.e. "{reg-name}") is specified. If so, add
2276+
/// that as a constraint that will be used in this asm stmt.
2277+
static std::string
2278+
AddVariableConstraints(const std::string &Constraint, const Expr &AsmExpr,
2279+
const TargetInfo &Target, CodeGenModule &CGM,
2280+
const AsmStmt &Stmt, const bool EarlyClobber,
2281+
std::string *GCCReg = nullptr) {
2282+
2283+
// Do we have the "hard register" inline asm constraint.
2284+
bool ApplyHardRegisterConstraint =
2285+
Constraint[0] == '{' || (EarlyClobber && Constraint[1] == '{');
2286+
2287+
// Do we have "register asm" on a variable.
2288+
std::string Reg = "";
2289+
bool ApplyRegisterVariableConstraint =
2290+
ShouldApplyRegisterVariableConstraint(AsmExpr, &Reg);
2291+
2292+
// Diagnose the scenario where we apply both the register variable constraint
2293+
// and a hard register variable constraint as an unsupported error.
2294+
// Why? Because we could have a situation where the register passed in through
2295+
// {...} and the register passed in through the "register asm" construct could
2296+
// be different, and in this case, there's no way for the compiler to know
2297+
// which one to emit.
2298+
if (ApplyHardRegisterConstraint && ApplyRegisterVariableConstraint) {
2299+
CGM.getDiags().Report(AsmExpr.getExprLoc(),
2300+
diag::err_asm_hard_reg_variable_duplicate);
22552301
return Constraint;
2256-
StringRef Register = Attr->getLabel();
2257-
assert(Target.isValidGCCRegisterName(Register));
2302+
}
2303+
2304+
if (!ApplyHardRegisterConstraint && !ApplyRegisterVariableConstraint)
2305+
return Constraint;
2306+
22582307
// We're using validateOutputConstraint here because we only care if
22592308
// this is a register constraint.
22602309
TargetInfo::ConstraintInfo Info(Constraint, "");
2261-
if (Target.validateOutputConstraint(Info) &&
2262-
!Info.allowsRegister()) {
2310+
if (Target.validateOutputConstraint(Info) && !Info.allowsRegister()) {
22632311
CGM.ErrorUnsupported(&Stmt, "__asm__");
22642312
return Constraint;
22652313
}
2314+
2315+
if (ApplyHardRegisterConstraint) {
2316+
int Start = EarlyClobber ? 2 : 1;
2317+
int End = Constraint.find('}');
2318+
Reg = Constraint.substr(Start, End - Start);
2319+
// If we don't have a valid register name, simply return the constraint.
2320+
// For example: There are some targets like X86 that use a constraint such
2321+
// as "@cca", which is validated and then converted into {@cca}. Now this
2322+
// isn't necessarily a "GCC Register", but in terms of emission, it is
2323+
// valid since it lowered appropriately in the X86 backend. For the {..}
2324+
// constraint, we shouldn't be too strict and error out if the register
2325+
// itself isn't a valid "GCC register".
2326+
if (!Target.isValidGCCRegisterName(Reg))
2327+
return Constraint;
2328+
}
2329+
2330+
StringRef Register(Reg);
22662331
// Canonicalize the register here before returning it.
22672332
Register = Target.getNormalizedGCCRegisterName(Register);
22682333
if (GCCReg != nullptr)

clang/test/CodeGen/SystemZ/systemz-inline-asm-02.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55
// Test that an error is given if a physreg is defined by multiple operands.
66
int test_physreg_defs(void) {
77
register int l __asm__("r7") = 0;
8+
int m;
89

910
// CHECK: error: multiple outputs to hard register: r7
10-
__asm__("" : "+r"(l), "=r"(l));
11+
__asm__(""
12+
: "+r"(l), "=r"(l));
1113

12-
return l;
14+
// CHECK: error: multiple outputs to hard register: r6
15+
__asm__(""
16+
: "+{r6}"(m), "={r6}"(m));
17+
18+
return l + m;
1319
}

0 commit comments

Comments
 (0)