Skip to content

Commit 9a107c5

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 d041d5d commit 9a107c5

File tree

18 files changed

+1230
-42
lines changed

18 files changed

+1230
-42
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2250,6 +2250,48 @@ Query for this feature with ``__has_extension(gnu_asm_constexpr_strings)``.
22502250
asm((std::string_view("nop")) ::: (std::string_view("memory")));
22512251
}
22522252

2253+
Hard Register Operands for ASM Constraints
2254+
==========================================
2255+
2256+
Clang supports the ability to specify specific hardware registers in inline
2257+
assembly constraints via the use of curly braces ``{}``.
2258+
2259+
Prior to clang-19, the only way to associate an inline assembly constraint
2260+
with a specific register is via the local register variable feature (`GCC
2261+
Specifying Registers for Local Variables <https://gcc.gnu.org/onlinedocs/gcc-6.5.0/gcc/Local-Register-Variables.html>`_).
2262+
However, the local register variable association lasts for the entire
2263+
scope of the variable.
2264+
2265+
Hard register operands will instead only apply to the specific inline ASM
2266+
statement which improves readability and solves a few other issues experienced
2267+
by local register variables, such as:
2268+
2269+
* function calls might clobber register variables
2270+
* the constraints for the register operands are superfluous
2271+
* one register variable cannot be used for 2 different inline
2272+
assemblies if the value is expected in different hard regs
2273+
2274+
The code below is an example of an inline assembly statement using local
2275+
register variables.
2276+
2277+
.. code-block:: c++
2278+
2279+
void foo() {
2280+
register int *p1 asm ("r0") = bar();
2281+
register int *p2 asm ("r1") = bar();
2282+
register int *result asm ("r0");
2283+
asm ("sysint" : "=r" (result) : "0" (p1), "r" (p2));
2284+
}
2285+
Below is the same code but using hard register operands.
2286+
2287+
.. code-block:: c++
2288+
2289+
void foo() {
2290+
int *p1 = bar();
2291+
int *p2 = bar();
2292+
int *result;
2293+
asm ("sysint" : "={r0}" (result) : "0" (p1), "{r1}" (p2));
2294+
}
22532295
22542296
Objective-C Features
22552297
====================

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9906,6 +9906,8 @@ let CategoryName = "Inline Assembly Issue" in {
99069906
"more than one input constraint matches the same output '%0'">;
99079907
def err_store_value_to_reg : Error<
99089908
"impossible constraint in asm: cannot store value into a register">;
9909+
def err_asm_hard_reg_variable_duplicate : Error<
9910+
"hard register operand already defined as register variable">;
99099911

99109912
def warn_asm_label_on_auto_decl : Warning<
99119913
"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
@@ -1115,9 +1115,17 @@ class TargetInfo : public TransferrableTargetInfo,
11151115
///
11161116
/// This function is used by Sema in order to diagnose conflicts between
11171117
/// the clobber list and the input/output lists.
1118+
/// The constraint should already by validated in validateHardRegisterAsmConstraint
1119+
/// so just do some basic checking
11181120
virtual StringRef getConstraintRegister(StringRef Constraint,
11191121
StringRef Expression) const {
1120-
return "";
1122+
StringRef Reg = Expression;
1123+
size_t Start = Constraint.find('{');
1124+
size_t End = Constraint.find('}');
1125+
if (Start != StringRef::npos && End != StringRef::npos && End > Start)
1126+
Reg = Constraint.substr(Start + 1, End - Start - 1);
1127+
1128+
return Reg;
11211129
}
11221130

11231131
struct ConstraintInfo {
@@ -1277,6 +1285,14 @@ class TargetInfo : public TransferrableTargetInfo,
12771285
validateAsmConstraint(const char *&Name,
12781286
TargetInfo::ConstraintInfo &info) const = 0;
12791287

1288+
// Validate the "hard register" inline asm constraint. This constraint is
1289+
// of the form {<reg-name>}. This constraint is meant to be used
1290+
// as an alternative for the "register asm" construct to put inline
1291+
// asm operands into specific registers.
1292+
bool
1293+
validateHardRegisterAsmConstraint(const char *&Name,
1294+
TargetInfo::ConstraintInfo &info) const;
1295+
12801296
bool resolveSymbolicName(const char *&Name,
12811297
ArrayRef<ConstraintInfo> OutputConstraints,
12821298
unsigned &Index) const;

clang/lib/Basic/TargetInfo.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,18 @@ bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
831831
case 'E':
832832
case 'F':
833833
break; // Pass them.
834+
case '{': {
835+
// First, check the target parser in case it validates
836+
// the {...} constraint differently.
837+
if (validateAsmConstraint(Name, Info))
838+
return true;
839+
840+
// If not, that's okay, we will try to validate it
841+
// using a target agnostic implementation.
842+
if (!validateHardRegisterAsmConstraint(Name, Info))
843+
return false;
844+
break;
845+
}
834846
}
835847

836848
Name++;
@@ -846,6 +858,36 @@ bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
846858
return Info.allowsMemory() || Info.allowsRegister();
847859
}
848860

861+
bool TargetInfo::validateHardRegisterAsmConstraint(
862+
const char *&Name, TargetInfo::ConstraintInfo &Info) const {
863+
// First, swallow the '{'.
864+
Name++;
865+
866+
// Mark the start of the possible register name.
867+
const char *Start = Name;
868+
869+
// Loop through rest of "Name".
870+
// In this loop, we check whether we have a closing curly brace which
871+
// validates the constraint. Also, this allows us to get the correct bounds to
872+
// set our register name.
873+
while (*Name && *Name != '}')
874+
Name++;
875+
876+
// Missing '}' or if there is anything after '}', return false.
877+
if (!*Name || *(Name + 1))
878+
return false;
879+
880+
// Now we set the register name.
881+
std::string Register(Start, Name - Start);
882+
883+
// We validate whether its a valid register to be used.
884+
if (!isValidGCCRegisterName(Register))
885+
return false;
886+
887+
Info.setAllowsRegister();
888+
return true;
889+
}
890+
849891
bool TargetInfo::resolveSymbolicName(const char *&Name,
850892
ArrayRef<ConstraintInfo> OutputConstraints,
851893
unsigned &Index) const {
@@ -978,6 +1020,18 @@ bool TargetInfo::validateInputConstraint(
9781020
case '!': // Disparage severely.
9791021
case '*': // Ignore for choosing register preferences.
9801022
break; // Pass them.
1023+
case '{': {
1024+
// First, check the target parser in case it validates
1025+
// the {...} constraint differently.
1026+
if (validateAsmConstraint(Name, Info))
1027+
return true;
1028+
1029+
// If not, that's okay, we will try to validate it
1030+
// using a target agnostic implementation.
1031+
if (!validateHardRegisterAsmConstraint(Name, Info))
1032+
return false;
1033+
break;
1034+
}
9811035
}
9821036

9831037
Name++;

clang/lib/Basic/Targets/AArch64.h

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

235-
StringRef getConstraintRegister(StringRef Constraint,
236-
StringRef Expression) const override {
237-
return Expression;
238-
}
239-
240235
int getEHDataRegisterNumber(unsigned RegNo) const override;
241236

242237
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
@@ -205,11 +205,6 @@ class LLVM_LIBRARY_VISIBILITY ARMTargetInfo : public TargetInfo {
205205
std::string &SuggestedModifier) const override;
206206
std::string_view getClobbers() const override;
207207

208-
StringRef getConstraintRegister(StringRef Constraint,
209-
StringRef Expression) const override {
210-
return Expression;
211-
}
212-
213208
CallingConvCheckResult checkCallingConvention(CallingConv CC) const override;
214209

215210
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
@@ -312,7 +312,7 @@ class LLVM_LIBRARY_VISIBILITY X86TargetInfo : public TargetInfo {
312312
return "di";
313313
// In case the constraint is 'r' we need to return Expression
314314
case 'r':
315-
return Expression;
315+
return TargetInfo::getConstraintRegister(Constraint, Expression);
316316
// Double letters Y<x> constraints
317317
case 'Y':
318318
if ((++I != E) && ((*I == '0') || (*I == 'z')))

clang/lib/CodeGen/CGStmt.cpp

Lines changed: 130 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2471,36 +2471,151 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) {
24712471
CaseRangeBlock = SavedCRBlock;
24722472
}
24732473

2474-
/// AddVariableConstraints - Look at AsmExpr and if it is a variable declared
2475-
/// as using a particular register add that as a constraint that will be used
2476-
/// in this asm stmt.
2477-
static std::string
2478-
AddVariableConstraints(const std::string &Constraint, const Expr &AsmExpr,
2479-
const TargetInfo &Target, CodeGenModule &CGM,
2480-
const AsmStmt &Stmt, const bool EarlyClobber,
2481-
std::string *GCCReg = nullptr) {
2474+
static std::string SimplifyConstraint(
2475+
const char *Constraint, const TargetInfo &Target,
2476+
SmallVectorImpl<TargetInfo::ConstraintInfo> *OutCons = nullptr) {
2477+
// If we have only the {...} constraint, do not do any simplifications. This
2478+
// already maps to the lower level LLVM inline assembly IR that tells the
2479+
// backend to allocate a specific register. Any validations would have already
2480+
// been done in the Sema stage or will be done in the AddVariableConstraints
2481+
// function.
2482+
if (Constraint[0] == '{' || (Constraint[0] == '&' && Constraint[1] == '{'))
2483+
return std::string(Constraint);
2484+
2485+
std::string Result;
2486+
2487+
while (*Constraint) {
2488+
switch (*Constraint) {
2489+
default:
2490+
Result += Target.convertConstraint(Constraint);
2491+
break;
2492+
// Ignore these
2493+
case '*':
2494+
case '?':
2495+
case '!':
2496+
case '=': // Will see this and the following in mult-alt constraints.
2497+
case '+':
2498+
break;
2499+
case '#': // Ignore the rest of the constraint alternative.
2500+
while (Constraint[1] && Constraint[1] != ',')
2501+
Constraint++;
2502+
break;
2503+
case '&':
2504+
case '%':
2505+
Result += *Constraint;
2506+
while (Constraint[1] && Constraint[1] == *Constraint)
2507+
Constraint++;
2508+
break;
2509+
case ',':
2510+
Result += "|";
2511+
break;
2512+
case 'g':
2513+
Result += "imr";
2514+
break;
2515+
case '[': {
2516+
assert(OutCons &&
2517+
"Must pass output names to constraints with a symbolic name");
2518+
unsigned Index;
2519+
bool result = Target.resolveSymbolicName(Constraint, *OutCons, Index);
2520+
assert(result && "Could not resolve symbolic name"); (void)result;
2521+
Result += llvm::utostr(Index);
2522+
break;
2523+
}
2524+
}
2525+
2526+
Constraint++;
2527+
}
2528+
2529+
return Result;
2530+
}
2531+
/// Is it valid to apply a register constraint for a variable marked with
2532+
/// the "register asm" construct?
2533+
/// Optionally, if it is determined that we can, we set "Register" to the
2534+
/// regiser name.
2535+
static bool
2536+
ShouldApplyRegisterVariableConstraint(const Expr &AsmExpr,
2537+
std::string *Register = nullptr) {
2538+
24822539
const DeclRefExpr *AsmDeclRef = dyn_cast<DeclRefExpr>(&AsmExpr);
24832540
if (!AsmDeclRef)
2484-
return Constraint;
2541+
return false;
24852542
const ValueDecl &Value = *AsmDeclRef->getDecl();
24862543
const VarDecl *Variable = dyn_cast<VarDecl>(&Value);
24872544
if (!Variable)
2488-
return Constraint;
2545+
return false;
24892546
if (Variable->getStorageClass() != SC_Register)
2490-
return Constraint;
2547+
return false;
24912548
AsmLabelAttr *Attr = Variable->getAttr<AsmLabelAttr>();
24922549
if (!Attr)
2550+
return false;
2551+
2552+
if (Register != nullptr)
2553+
// Set the register to return from Attr.
2554+
*Register = Attr->getLabel().str();
2555+
return true;
2556+
}
2557+
2558+
/// AddVariableConstraints:
2559+
/// Look at AsmExpr and if it is a variable declared as using a particular
2560+
/// register add that as a constraint that will be used in this asm stmt.
2561+
/// Whether it can be used or not is dependent on querying
2562+
/// ShouldApplyRegisterVariableConstraint() Also check whether the "hard
2563+
/// register" inline asm constraint (i.e. "{reg-name}") is specified. If so, add
2564+
/// that as a constraint that will be used in this asm stmt.
2565+
static std::string
2566+
AddVariableConstraints(const std::string &Constraint, const Expr &AsmExpr,
2567+
const TargetInfo &Target, CodeGenModule &CGM,
2568+
const AsmStmt &Stmt, const bool EarlyClobber,
2569+
std::string *GCCReg = nullptr) {
2570+
2571+
// Do we have the "hard register" inline asm constraint.
2572+
bool ApplyHardRegisterConstraint =
2573+
Constraint[0] == '{' || (EarlyClobber && Constraint[1] == '{');
2574+
2575+
// Do we have "register asm" on a variable.
2576+
std::string Reg = "";
2577+
bool ApplyRegisterVariableConstraint =
2578+
ShouldApplyRegisterVariableConstraint(AsmExpr, &Reg);
2579+
2580+
// Diagnose the scenario where we apply both the register variable constraint
2581+
// and a hard register variable constraint as an unsupported error.
2582+
// Why? Because we could have a situation where the register passed in through
2583+
// {...} and the register passed in through the "register asm" construct could
2584+
// be different, and in this case, there's no way for the compiler to know
2585+
// which one to emit.
2586+
if (ApplyHardRegisterConstraint && ApplyRegisterVariableConstraint) {
2587+
CGM.getDiags().Report(AsmExpr.getExprLoc(),
2588+
diag::err_asm_hard_reg_variable_duplicate);
24932589
return Constraint;
2494-
StringRef Register = Attr->getLabel();
2495-
assert(Target.isValidGCCRegisterName(Register));
2590+
}
2591+
2592+
if (!ApplyHardRegisterConstraint && !ApplyRegisterVariableConstraint)
2593+
return Constraint;
2594+
24962595
// We're using validateOutputConstraint here because we only care if
24972596
// this is a register constraint.
24982597
TargetInfo::ConstraintInfo Info(Constraint, "");
2499-
if (Target.validateOutputConstraint(Info) &&
2500-
!Info.allowsRegister()) {
2598+
if (Target.validateOutputConstraint(Info) && !Info.allowsRegister()) {
25012599
CGM.ErrorUnsupported(&Stmt, "__asm__");
25022600
return Constraint;
25032601
}
2602+
2603+
if (ApplyHardRegisterConstraint) {
2604+
int Start = EarlyClobber ? 2 : 1;
2605+
int End = Constraint.find('}');
2606+
Reg = Constraint.substr(Start, End - Start);
2607+
// If we don't have a valid register name, simply return the constraint.
2608+
// For example: There are some targets like X86 that use a constraint such
2609+
// as "@cca", which is validated and then converted into {@cca}. Now this
2610+
// isn't necessarily a "GCC Register", but in terms of emission, it is
2611+
// valid since it lowered appropriately in the X86 backend. For the {..}
2612+
// constraint, we shouldn't be too strict and error out if the register
2613+
// itself isn't a valid "GCC register".
2614+
if (!Target.isValidGCCRegisterName(Reg))
2615+
return Constraint;
2616+
}
2617+
2618+
StringRef Register(Reg);
25042619
// Canonicalize the register here before returning it.
25052620
Register = Target.getNormalizedGCCRegisterName(Register);
25062621
if (GCCReg != nullptr)

0 commit comments

Comments
 (0)