Skip to content

Commit 38503da

Browse files
committed
[TSan] Don't instrument inout accesses on known-empty types
Empty types (such as structs without stored properties) have a meaningless value for their address. To avoid crashes in the Thread Sanitizer runtime, rather than passing this unspecified value as the address of the inout access, skip emission of the runtime call. The bug allowing unspecified behavior here has been present since we first added TSan support for checking Swift access races -- but codegen changes on arm64 have recently made crashes due to the bug much more likely. rdar://problem/47686212
1 parent af7f1e6 commit 38503da

File tree

4 files changed

+69
-2
lines changed

4 files changed

+69
-2
lines changed

lib/IRGen/GenBuiltin.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ getLoweredTypeAndTypeInfo(IRGenModule &IGM, Type unloweredType) {
120120
/// emitBuiltinCall - Emit a call to a builtin function.
121121
void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
122122
Identifier FnId, SILType resultType,
123+
ArrayRef<SILType> argTypes,
123124
Explosion &args, Explosion &out,
124125
SubstitutionMap substitutions) {
125126
if (Builtin.ID == BuiltinValueKind::COWBufferForReading) {
@@ -1074,7 +1075,18 @@ if (Builtin.ID == BuiltinValueKind::id) { \
10741075

10751076
if (Builtin.ID == BuiltinValueKind::TSanInoutAccess) {
10761077
auto address = args.claimNext();
1077-
IGF.emitTSanInoutAccessCall(address);
1078+
1079+
// The tsanInoutAccess builtin takes a single argument, the address
1080+
// of the accessed storage
1081+
SILType accessedType = argTypes[0];
1082+
1083+
// Empty types (such as structs without stored properties) have a
1084+
// meaningless value for their address. We not should call into the
1085+
// TSan runtime to check for data races on accesses on such addresses.
1086+
if (!IGF.IGM.getTypeInfo(accessedType)
1087+
.isKnownEmpty(ResilienceExpansion::Maximal)) {
1088+
IGF.emitTSanInoutAccessCall(address);
1089+
}
10781090
return;
10791091
}
10801092

lib/IRGen/GenBuiltin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ namespace irgen {
3333
/// Emit a call to a builtin function.
3434
void emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &builtin,
3535
Identifier fnId, SILType resultType,
36+
ArrayRef<SILType> argTypes,
3637
Explosion &args, Explosion &result,
3738
SubstitutionMap substitutions);
3839

lib/IRGen/IRGenSIL.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2682,18 +2682,21 @@ void IRGenSILFunction::visitBuiltinInst(swift::BuiltinInst *i) {
26822682

26832683
auto argValues = i->getArguments();
26842684
Explosion args;
2685+
SmallVector<SILType, 4> argTypes;
26852686

26862687
for (auto idx : indices(argValues)) {
26872688
auto argValue = argValues[idx];
26882689

26892690
// Builtin arguments should never be substituted, so use the value's type
26902691
// as the parameter type.
26912692
emitApplyArgument(*this, argValue, argValue->getType(), args);
2693+
2694+
argTypes.push_back(argValue->getType());
26922695
}
26932696

26942697
Explosion result;
26952698
emitBuiltinCall(*this, builtin, i->getName(), i->getType(),
2696-
args, result, i->getSubstitutions());
2699+
argTypes, args, result, i->getSubstitutions());
26972700

26982701
setLoweredExplosion(i, result);
26992702
}

test/SILGen/tsan_instrumentation.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// REQUIRES: tsan_runtime
22
// RUN: %target-swift-emit-silgen -sanitize=thread %s | %FileCheck %s
3+
// RUN: %target-swift-frontend -sanitize=thread -emit-ir -primary-file %s | %FileCheck --check-prefix=CHECK-LLVM-IR %s
34

45
// TSan is only supported on 64 bit.
56
// REQUIRES: PTRSIZE=64
@@ -63,3 +64,53 @@ func inoutGlobalClassStoredProperty() {
6364
// temporary buffer passed to takesInout().
6465
takesInout(&gClass.storedProperty)
6566
}
67+
68+
// Known-empty types don't have storage, so there is no address
69+
// to pass to the TSan runtime to check for data races on inout accesses.
70+
// In this case, the instrumentation should skip the call to
71+
// __tsan_external_write()
72+
73+
struct ZeroSizedStruct {
74+
mutating
75+
func mutate() { }
76+
}
77+
78+
struct NonEmptyStruct {
79+
var f: Int = 5
80+
mutating
81+
func mutate() { }
82+
}
83+
84+
// CHECK-LLVM-IR-LABEL: testNoInstrumentZeroSizedStruct
85+
func testNoInstrumentZeroSizedStruct() {
86+
var s = ZeroSizedStruct()
87+
88+
// CHECK-LLVM-IR-NOT: tsan_external_write
89+
s.mutate()
90+
}
91+
92+
func takesInout<T>(_ p: inout T) { }
93+
94+
// CHECK-LLVM-IR-LABEL: testNoInstrumentEmptyTuple
95+
func testNoInstrumentEmptyTuple() {
96+
var t: Void = ()
97+
98+
// CHECK-LLVM-IR-NOT: tsan_external_write
99+
takesInout(&t)
100+
}
101+
102+
// CHECK-LLVM-IR-LABEL: testNoInstrumentMutateInoutZeroSizedStruct
103+
func testNoInstrumentMutateInoutZeroSizedStruct(p: inout ZeroSizedStruct) {
104+
105+
// CHECK-LLVM-IR-NOT: tsan_external_write
106+
p.mutate()
107+
}
108+
109+
// CHECK-LLVM-IR-LABEL: testInstrumentNonEmptyStruct
110+
func testInstrumentNonEmptyStruct() {
111+
// Make sure we actually instrument accesses to non-empty structs.
112+
var s = NonEmptyStruct()
113+
114+
// CHECK-LLVM-IR: tsan_external_write
115+
s.mutate()
116+
}

0 commit comments

Comments
 (0)