Skip to content

Commit d598590

Browse files
authored
[Clang][ARM][Sema] Reject bad sizes of __builtin_arm_ldrex (#150419)
Depending on the particular version of the AArch32 architecture, load/store exclusive operations might be available for various subset of 8, 16, 32, and 64-bit quantities. Sema knew nothing about this and was accepting all four sizes, leading to a compiler crash at isel time if you used a size not available on the target architecture. Now the Sema checking stage emits a more sensible diagnostic, pointing at the ___location in the code. In order to allow Sema to query the set of supported sizes, I've moved the enum of LDREX_x sizes out of its Arm-specific header into `TargetInfo.h`. Also, in order to allow the diagnostic to specify the correct list of supported sizes, I've filled it with `%select{}`. (The alternative was to make separate error messages for each different list of sizes.)
1 parent fc90685 commit d598590

File tree

10 files changed

+175
-27
lines changed

10 files changed

+175
-27
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9331,8 +9331,28 @@ def err_atomic_builtin_pointer_size : Error<
93319331
"address argument to atomic builtin must be a pointer to 1,2,4,8 or 16 byte "
93329332
"type (%0 invalid)">;
93339333
def err_atomic_exclusive_builtin_pointer_size : Error<
9334-
"address argument to load or store exclusive builtin must be a pointer to"
9335-
" 1,2,4 or 8 byte type (%0 invalid)">;
9334+
"address argument to load or store exclusive builtin must be a pointer to "
9335+
// Because the range of legal sizes for load/store exclusive varies with the
9336+
// Arm architecture version, this error message wants to be able to specify
9337+
// various different subsets of the sizes 1, 2, 4, 8. Rather than make a
9338+
// separate diagnostic for each subset, I've arranged here that _this_ error
9339+
// can display any combination of the sizes. For each size there are two
9340+
// %select parameters: the first chooses whether you need a "," or " or " to
9341+
// separate the number from a previous one (or neither), and the second
9342+
// parameter indicates whether to display the number itself.
9343+
//
9344+
// (The very first of these parameters isn't really necessary, since you
9345+
// never want to start with "," or " or " before the first number in the
9346+
// list, but it keeps it simple to make it look exactly like the other cases,
9347+
// and also allows a loop constructing this diagnostic to handle every case
9348+
// exactly the same.)
9349+
"%select{|,| or }1%select{|1}2"
9350+
"%select{|,| or }3%select{|2}4"
9351+
"%select{|,| or }5%select{|4}6"
9352+
"%select{|,| or }7%select{|8}8"
9353+
" byte type (%0 invalid)">;
9354+
def err_atomic_exclusive_builtin_pointer_size_none : Error<
9355+
"load and store exclusive builtins are not available on this architecture">;
93369356
def err_atomic_builtin_ext_int_size : Error<
93379357
"atomic memory operand must have a power-of-two size">;
93389358
def err_atomic_builtin_bit_int_prohibit : Error<

clang/include/clang/Basic/TargetInfo.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,17 @@ class TargetInfo : public TransferrableTargetInfo,
10711071
/// as Custom Datapath.
10721072
uint32_t getARMCDECoprocMask() const { return ARMCDECoprocMask; }
10731073

1074+
/// For ARM targets returns a mask defining which data sizes are suitable for
1075+
/// __builtin_arm_ldrex and __builtin_arm_strex.
1076+
enum {
1077+
ARM_LDREX_B = (1 << 0), /// byte (8-bit)
1078+
ARM_LDREX_H = (1 << 1), /// half (16-bit)
1079+
ARM_LDREX_W = (1 << 2), /// word (32-bit)
1080+
ARM_LDREX_D = (1 << 3), /// double (64-bit)
1081+
};
1082+
1083+
virtual unsigned getARMLDREXMask() const { return 0; }
1084+
10741085
/// Returns whether the passed in string is a valid clobber in an
10751086
/// inline asm statement.
10761087
///

clang/include/clang/Sema/SemaARM.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ class SemaARM : public SemaBase {
4444

4545
bool CheckImmediateArg(CallExpr *TheCall, unsigned CheckTy, unsigned ArgIdx,
4646
unsigned EltBitWidth, unsigned VecBitWidth);
47-
bool CheckARMBuiltinExclusiveCall(unsigned BuiltinID, CallExpr *TheCall,
48-
unsigned MaxWidth);
47+
bool CheckARMBuiltinExclusiveCall(const TargetInfo &TI, unsigned BuiltinID,
48+
CallExpr *TheCall);
4949
bool CheckNeonBuiltinFunctionCall(const TargetInfo &TI, unsigned BuiltinID,
5050
CallExpr *TheCall);
5151
bool PerformNeonImmChecks(

clang/lib/Basic/Targets/ARM.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -618,21 +618,21 @@ bool ARMTargetInfo::handleTargetFeatures(std::vector<std::string> &Features,
618618
LDREX = 0;
619619
else if (ArchKind == llvm::ARM::ArchKind::ARMV6K ||
620620
ArchKind == llvm::ARM::ArchKind::ARMV6KZ)
621-
LDREX = LDREX_D | LDREX_W | LDREX_H | LDREX_B;
621+
LDREX = ARM_LDREX_D | ARM_LDREX_W | ARM_LDREX_H | ARM_LDREX_B;
622622
else
623-
LDREX = LDREX_W;
623+
LDREX = ARM_LDREX_W;
624624
break;
625625
case 7:
626626
case 8:
627627
if (ArchProfile == llvm::ARM::ProfileKind::M)
628-
LDREX = LDREX_W | LDREX_H | LDREX_B;
628+
LDREX = ARM_LDREX_W | ARM_LDREX_H | ARM_LDREX_B;
629629
else
630-
LDREX = LDREX_D | LDREX_W | LDREX_H | LDREX_B;
630+
LDREX = ARM_LDREX_D | ARM_LDREX_W | ARM_LDREX_H | ARM_LDREX_B;
631631
break;
632632
case 9:
633633
assert(ArchProfile != llvm::ARM::ProfileKind::M &&
634634
"No Armv9-M architectures defined");
635-
LDREX = LDREX_D | LDREX_W | LDREX_H | LDREX_B;
635+
LDREX = ARM_LDREX_D | ARM_LDREX_W | ARM_LDREX_H | ARM_LDREX_B;
636636
}
637637

638638
if (!(FPU & NeonFPU) && FPMath == FP_Neon) {

clang/lib/Basic/Targets/ARM.h

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,6 @@ class LLVM_LIBRARY_VISIBILITY ARMTargetInfo : public TargetInfo {
9898
LLVM_PREFERRED_TYPE(bool)
9999
unsigned HasBTI : 1;
100100

101-
enum {
102-
LDREX_B = (1 << 0), /// byte (8-bit)
103-
LDREX_H = (1 << 1), /// half (16-bit)
104-
LDREX_W = (1 << 2), /// word (32-bit)
105-
LDREX_D = (1 << 3), /// double (64-bit)
106-
};
107-
108101
uint32_t LDREX;
109102

110103
// ACLE 6.5.1 Hardware floating point
@@ -225,6 +218,8 @@ class LLVM_LIBRARY_VISIBILITY ARMTargetInfo : public TargetInfo {
225218

226219
bool hasBitIntType() const override { return true; }
227220

221+
unsigned getARMLDREXMask() const override { return LDREX; }
222+
228223
const char *getBFloat16Mangling() const override { return "u6__bf16"; };
229224

230225
std::pair<unsigned, unsigned> hardwareInterferenceSizes() const override {

clang/lib/Sema/SemaARM.cpp

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -846,9 +846,9 @@ bool SemaARM::CheckARMCoprocessorImmediate(const TargetInfo &TI,
846846
return false;
847847
}
848848

849-
bool SemaARM::CheckARMBuiltinExclusiveCall(unsigned BuiltinID,
850-
CallExpr *TheCall,
851-
unsigned MaxWidth) {
849+
bool SemaARM::CheckARMBuiltinExclusiveCall(const TargetInfo &TI,
850+
unsigned BuiltinID,
851+
CallExpr *TheCall) {
852852
assert((BuiltinID == ARM::BI__builtin_arm_ldrex ||
853853
BuiltinID == ARM::BI__builtin_arm_ldaex ||
854854
BuiltinID == ARM::BI__builtin_arm_strex ||
@@ -923,12 +923,56 @@ bool SemaARM::CheckARMBuiltinExclusiveCall(unsigned BuiltinID,
923923
return true;
924924
}
925925

926-
// But ARM doesn't have instructions to deal with 128-bit versions.
927-
if (Context.getTypeSize(ValType) > MaxWidth) {
928-
assert(MaxWidth == 64 && "Diagnostic unexpectedly inaccurate");
929-
Diag(DRE->getBeginLoc(), diag::err_atomic_exclusive_builtin_pointer_size)
930-
<< PointerArg->getType() << PointerArg->getSourceRange();
931-
return true;
926+
// Check whether the size of the type can be handled atomically on this
927+
// target.
928+
if (!TI.getTriple().isAArch64()) {
929+
unsigned Mask = TI.getARMLDREXMask();
930+
unsigned Bits = Context.getTypeSize(ValType);
931+
bool Supported =
932+
(llvm::isPowerOf2_64(Bits)) && Bits >= 8 && (Mask & (Bits / 8));
933+
934+
if (!Supported) {
935+
// Emit a diagnostic saying that this size isn't available. If _no_ size
936+
// of exclusive access is supported on this target, we emit a diagnostic
937+
// with special wording for that case, but otherwise, we emit
938+
// err_atomic_exclusive_builtin_pointer_size and loop over `Mask` to
939+
// control what subset of sizes it lists as legal.
940+
if (Mask) {
941+
auto D = Diag(DRE->getBeginLoc(),
942+
diag::err_atomic_exclusive_builtin_pointer_size)
943+
<< PointerArg->getType();
944+
bool Started = false;
945+
for (unsigned Size = 1; Size <= 8; Size <<= 1) {
946+
// For each of the sizes 1,2,4,8, pass two integers into the
947+
// diagnostic. The first selects a separator from the previous
948+
// number: 0 for no separator at all, 1 for a comma, 2 for " or "
949+
// which appears before the final number in a list of more than one.
950+
// The second integer just indicates whether we print this size in
951+
// the message at all.
952+
if (!(Mask & Size)) {
953+
// This size isn't one of the supported ones, so emit no separator
954+
// text and don't print the size itself.
955+
D << 0 << 0;
956+
} else {
957+
// This size is supported, so print it, and an appropriate
958+
// separator.
959+
Mask &= ~Size;
960+
if (!Started)
961+
D << 0; // No separator if this is the first size we've printed
962+
else if (Mask)
963+
D << 1; // "," if there's still another size to come
964+
else
965+
D << 2; // " or " if the size we're about to print is the last
966+
D << 1; // print the size itself
967+
Started = true;
968+
}
969+
}
970+
} else {
971+
Diag(DRE->getBeginLoc(),
972+
diag::err_atomic_exclusive_builtin_pointer_size_none)
973+
<< PointerArg->getSourceRange();
974+
}
975+
}
932976
}
933977

934978
switch (ValType.getObjCLifetime()) {
@@ -972,7 +1016,7 @@ bool SemaARM::CheckARMBuiltinFunctionCall(const TargetInfo &TI,
9721016
BuiltinID == ARM::BI__builtin_arm_ldaex ||
9731017
BuiltinID == ARM::BI__builtin_arm_strex ||
9741018
BuiltinID == ARM::BI__builtin_arm_stlex) {
975-
return CheckARMBuiltinExclusiveCall(BuiltinID, TheCall, 64);
1019+
return CheckARMBuiltinExclusiveCall(TI, BuiltinID, TheCall);
9761020
}
9771021

9781022
if (BuiltinID == ARM::BI__builtin_arm_prefetch) {
@@ -1053,7 +1097,7 @@ bool SemaARM::CheckAArch64BuiltinFunctionCall(const TargetInfo &TI,
10531097
BuiltinID == AArch64::BI__builtin_arm_ldaex ||
10541098
BuiltinID == AArch64::BI__builtin_arm_strex ||
10551099
BuiltinID == AArch64::BI__builtin_arm_stlex) {
1056-
return CheckARMBuiltinExclusiveCall(BuiltinID, TheCall, 128);
1100+
return CheckARMBuiltinExclusiveCall(TI, BuiltinID, TheCall);
10571101
}
10581102

10591103
if (BuiltinID == AArch64::BI__builtin_arm_prefetch) {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %clang_cc1 -triple armv7m -fsyntax-only -verify %s
2+
// RUN: %clang_cc1 -triple armv8m.main -fsyntax-only -verify %s
3+
// RUN: %clang_cc1 -triple armv8.1m.main -fsyntax-only -verify %s
4+
5+
// All these architecture versions provide 1-, 2- or 4-byte exclusive accesses,
6+
// but don't have the LDREXD instruction which takes two operand registers and
7+
// performs an 8-byte exclusive access. So the calls with a pointer to long
8+
// long are rejected.
9+
10+
int test_ldrex(char *addr) {
11+
int sum = 0;
12+
sum += __builtin_arm_ldrex(addr);
13+
sum += __builtin_arm_ldrex((short *)addr);
14+
sum += __builtin_arm_ldrex((int *)addr);
15+
sum += __builtin_arm_ldrex((long long *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 1,2 or 4 byte type}}
16+
return sum;
17+
}
18+
19+
int test_strex(char *addr) {
20+
int res = 0;
21+
res |= __builtin_arm_strex(4, addr);
22+
res |= __builtin_arm_strex(42, (short *)addr);
23+
res |= __builtin_arm_strex(42, (int *)addr);
24+
res |= __builtin_arm_strex(42, (long long *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 1,2 or 4 byte type}}
25+
return res;
26+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %clang_cc1 -triple armv6 -fsyntax-only -verify %s
2+
3+
// Armv6 (apart from Armv6-M) provides 4-byte exclusive accesses, but not any
4+
// other size. So only the calls with a pointer to a 32-bit type are accepted.
5+
6+
int test_ldrex(char *addr) {
7+
int sum = 0;
8+
sum += __builtin_arm_ldrex(addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 4 byte type}}
9+
sum += __builtin_arm_ldrex((short *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 4 byte type}}
10+
sum += __builtin_arm_ldrex((int *)addr);
11+
sum += __builtin_arm_ldrex((long long *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 4 byte type}}
12+
return sum;
13+
}
14+
15+
int test_strex(char *addr) {
16+
int res = 0;
17+
res |= __builtin_arm_strex(4, addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 4 byte type}}
18+
res |= __builtin_arm_strex(42, (short *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 4 byte type}}
19+
res |= __builtin_arm_strex(42, (int *)addr);
20+
res |= __builtin_arm_strex(42, (long long *)addr); // expected-error {{address argument to load or store exclusive builtin must be a pointer to 4 byte type}}
21+
return res;
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %clang_cc1 -triple armv6m -fsyntax-only -verify %s
2+
3+
// Armv6-M does not support exclusive loads/stores at all, so all uses of
4+
// __builtin_arm_ldrex and __builtin_arm_strex is forbidden.
5+
6+
int test_ldrex(char *addr) {
7+
int sum = 0;
8+
sum += __builtin_arm_ldrex(addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
9+
sum += __builtin_arm_ldrex((short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
10+
sum += __builtin_arm_ldrex((int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
11+
sum += __builtin_arm_ldrex((long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
12+
return sum;
13+
}
14+
15+
int test_strex(char *addr) {
16+
int res = 0;
17+
res |= __builtin_arm_strex(4, addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
18+
res |= __builtin_arm_strex(42, (short *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
19+
res |= __builtin_arm_strex(42, (int *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
20+
res |= __builtin_arm_strex(42, (long long *)addr); // expected-error {{load and store exclusive builtins are not available on this architecture}}
21+
return res;
22+
}

clang/test/Sema/builtins-arm-exclusive.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
// RUN: %clang_cc1 -triple armv7 -fsyntax-only -verify %s
22

3+
// General tests of __builtin_arm_ldrex and __builtin_arm_strex error checking.
4+
//
5+
// This test is compiled for Armv7-A, which provides exclusive load/store
6+
// instructions for 1-, 2-, 4- and 8-byte quantities. Other Arm architecture
7+
// versions provide subsets of those, requiring different error reporting.
8+
// Those are tested in builtins-arm-exclusive-124.c, builtins-arm-exclusive-4.c
9+
// and builtins-arm-exclusive-none.c.
10+
311
struct Simple {
412
char a, b;
513
};

0 commit comments

Comments
 (0)