Skip to content

Commit cf3da5f

Browse files
authored
Merge pull request #10302 from jckarter/keypath-indirect-offset
[SR-5036]: Segfault using KeyPath with NSObject
2 parents 49b31fe + fe88bd5 commit cf3da5f

File tree

14 files changed

+238
-71
lines changed

14 files changed

+238
-71
lines changed

include/swift/ABI/KeyPath.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ class KeyPathComponentHeader {
104104
}
105105

106106
constexpr static KeyPathComponentHeader
107-
forStructComponentWithUnresolvedOffset() {
107+
forStructComponentWithUnresolvedFieldOffset() {
108108
return KeyPathComponentHeader(
109109
(_SwiftKeyPathComponentHeader_StructTag
110110
<< _SwiftKeyPathComponentHeader_DiscriminatorShift)
111-
| _SwiftKeyPathComponentHeader_UnresolvedOffsetPayload);
111+
| _SwiftKeyPathComponentHeader_UnresolvedFieldOffsetPayload);
112112
}
113113

114114
constexpr static KeyPathComponentHeader
@@ -128,11 +128,19 @@ class KeyPathComponentHeader {
128128
}
129129

130130
constexpr static KeyPathComponentHeader
131-
forClassComponentWithUnresolvedOffset() {
131+
forClassComponentWithUnresolvedFieldOffset() {
132132
return KeyPathComponentHeader(
133-
(_SwiftKeyPathComponentHeader_StructTag
133+
(_SwiftKeyPathComponentHeader_ClassTag
134+
<< _SwiftKeyPathComponentHeader_DiscriminatorShift)
135+
| _SwiftKeyPathComponentHeader_UnresolvedFieldOffsetPayload);
136+
}
137+
138+
constexpr static KeyPathComponentHeader
139+
forClassComponentWithUnresolvedIndirectOffset() {
140+
return KeyPathComponentHeader(
141+
(_SwiftKeyPathComponentHeader_ClassTag
134142
<< _SwiftKeyPathComponentHeader_DiscriminatorShift)
135-
| _SwiftKeyPathComponentHeader_UnresolvedOffsetPayload);
143+
| _SwiftKeyPathComponentHeader_UnresolvedIndirectOffsetPayload);
136144
}
137145

138146
constexpr static KeyPathComponentHeader

lib/IRGen/GenClass.cpp

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,30 @@ namespace {
181181
// If not, we can have to access stored properties through the field
182182
// offset vector in the instantiated type metadata.
183183
bool ClassHasConcreteLayout = true;
184-
184+
185185
public:
186-
ClassLayoutBuilder(IRGenModule &IGM, SILType classType)
186+
ClassLayoutBuilder(IRGenModule &IGM, SILType classType,
187+
ReferenceCounting refcounting)
187188
: StructLayoutBuilder(IGM)
188189
{
189190
// Start by adding a heap header.
190-
addHeapHeader();
191-
191+
switch (refcounting) {
192+
case swift::irgen::ReferenceCounting::Native:
193+
// For native classes, place a full object header.
194+
addHeapHeader();
195+
break;
196+
case swift::irgen::ReferenceCounting::ObjC:
197+
// For ObjC-inheriting classes, we don't reliably know the size of the
198+
// base class, but NSObject only has an `isa` pointer at most.
199+
addNSObjectHeader();
200+
break;
201+
case swift::irgen::ReferenceCounting::Block:
202+
case swift::irgen::ReferenceCounting::Unknown:
203+
case swift::irgen::ReferenceCounting::Bridge:
204+
case swift::irgen::ReferenceCounting::Error:
205+
llvm_unreachable("not a class refcounting kind");
206+
}
207+
192208
// Next, add the fields for the given class.
193209
auto theClass = classType.getClassOrBoundGenericClass();
194210
assert(theClass);
@@ -382,7 +398,7 @@ void ClassTypeInfo::generateLayout(IRGenModule &IGM, SILType classType) const {
382398
"already generated layout");
383399

384400
// Add the heap header.
385-
ClassLayoutBuilder builder(IGM, classType);
401+
ClassLayoutBuilder builder(IGM, classType, Refcount);
386402

387403
// generateLayout can call itself recursively in order to compute a layout
388404
// for the abstract type. If classType shares an exemplar types with the
@@ -409,7 +425,8 @@ void ClassTypeInfo::generateLayout(IRGenModule &IGM, SILType classType) const {
409425
const StructLayout &
410426
ClassTypeInfo::getLayout(IRGenModule &IGM, SILType classType) const {
411427
// Return the cached layout if available.
412-
if (Layout) return *Layout;
428+
if (Layout)
429+
return *Layout;
413430

414431
generateLayout(IGM, classType);
415432
return *Layout;
@@ -489,7 +506,13 @@ irgen::tryEmitConstantClassFragilePhysicalMemberOffset(IRGenModule &IGM,
489506
}
490507
}
491508

492-
509+
FieldAccess
510+
irgen::getClassFieldAccess(IRGenModule &IGM, SILType baseType, VarDecl *field) {
511+
auto &baseClassTI = IGM.getTypeInfo(baseType).as<ClassTypeInfo>();
512+
auto &classLayout = baseClassTI.getClassLayout(IGM, baseType);
513+
unsigned fieldIndex = classLayout.getFieldIndex(field);
514+
return classLayout.AllFieldAccesses[fieldIndex];
515+
}
493516

494517
OwnedAddress irgen::projectPhysicalClassMemberAddress(IRGenFunction &IGF,
495518
llvm::Value *base,

lib/IRGen/GenClass.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ namespace irgen {
4848
enum class ReferenceCounting : unsigned char;
4949
enum class IsaEncoding : unsigned char;
5050
enum class ClassDeallocationKind : unsigned char;
51+
enum class FieldAccess : uint8_t;
5152

5253
OwnedAddress projectPhysicalClassMemberAddress(IRGenFunction &IGF,
5354
llvm::Value *base,
@@ -138,6 +139,10 @@ namespace irgen {
138139
tryEmitConstantClassFragilePhysicalMemberOffset(IRGenModule &IGM,
139140
SILType baseType,
140141
VarDecl *field);
142+
143+
FieldAccess getClassFieldAccess(IRGenModule &IGM,
144+
SILType baseType,
145+
VarDecl *field);
141146

142147
/// What reference counting mechanism does a class-like type use?
143148
ReferenceCounting getReferenceCountingForType(IRGenModule &IGM,

lib/IRGen/GenKeyPath.cpp

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "IRGenFunction.h"
2626
#include "IRGenModule.h"
2727
#include "ProtocolInfo.h"
28+
#include "StructLayout.h"
2829
#include "llvm/ADT/SetVector.h"
2930
#include "swift/SIL/SILInstruction.h"
3031
#include "swift/SIL/SILLocation.h"
@@ -209,47 +210,98 @@ IRGenModule::getAddrOfKeyPathPattern(KeyPathPattern *pattern,
209210
auto &component = pattern->getComponents()[i];
210211
switch (auto kind = component.getKind()) {
211212
case KeyPathPatternComponent::Kind::StoredProperty: {
212-
// Try to get a constant offset if we can.
213213
auto property = cast<VarDecl>(component.getStoredPropertyDecl());
214-
llvm::Constant *offset;
215-
bool isResolved;
216-
std::tie(offset, isResolved)
217-
= getPropertyOffsetOrIndirectOffset(loweredBaseTy, property);
218-
offset = llvm::ConstantExpr::getTruncOrBitCast(offset, Int32Ty);
219-
bool isStruct = (bool)loweredBaseTy.getStructOrBoundGenericStruct();
220214

221-
// If the projection is a statically known integer, try to pack it into
222-
// the key path payload.
223-
if (isResolved) {
215+
auto addFixedOffset = [&](bool isStruct, llvm::Constant *offset) {
216+
offset = llvm::ConstantExpr::getTruncOrBitCast(offset, Int32Ty);
224217
if (auto offsetInt = dyn_cast_or_null<llvm::ConstantInt>(offset)) {
225218
auto offsetValue = offsetInt->getValue().getZExtValue();
226219
if (KeyPathComponentHeader::offsetCanBeInline(offsetValue)) {
227220
auto header = isStruct
228221
? KeyPathComponentHeader::forStructComponentWithInlineOffset(offsetValue)
229222
: KeyPathComponentHeader::forClassComponentWithInlineOffset(offsetValue);
230223
fields.addInt32(header.getData());
231-
break;
224+
return;
232225
}
233226
}
234-
235227
auto header = isStruct
236228
? KeyPathComponentHeader::forStructComponentWithOutOfLineOffset()
237229
: KeyPathComponentHeader::forClassComponentWithOutOfLineOffset();
238230
fields.addInt32(header.getData());
239-
240231
fields.add(offset);
241-
} else {
242-
// Otherwise, stash the offset of the field offset within the metadata
243-
// object, so we can pull it out at instantiation time.
244-
// TODO: We'll also need a way to handle resilient field offsets, once
245-
// field offset vectors no longer cover all fields in the type.
246-
KeyPathComponentHeader header = isStruct
247-
? KeyPathComponentHeader::forStructComponentWithUnresolvedOffset()
248-
: KeyPathComponentHeader::forClassComponentWithUnresolvedOffset();
232+
};
233+
234+
// For a struct stored property, we may know the fixed offset of the field,
235+
// or we may need to fetch it out of the type's metadata at instantiation
236+
// time.
237+
if (loweredBaseTy.getStructOrBoundGenericStruct()) {
238+
if (auto offset = emitPhysicalStructMemberFixedOffset(*this,
239+
loweredBaseTy,
240+
property)) {
241+
// We have a known constant fixed offset.
242+
addFixedOffset(/*struct*/ true, offset);
243+
break;
244+
}
245+
246+
// If the offset isn't fixed, try instead to get the field offset out
247+
// of the type metadata at instantiation time.
248+
auto fieldOffset = emitPhysicalStructMemberOffsetOfFieldOffset(
249+
*this, loweredBaseTy, property);
250+
fieldOffset = llvm::ConstantExpr::getTruncOrBitCast(fieldOffset,
251+
Int32Ty);
252+
auto header = KeyPathComponentHeader::forStructComponentWithUnresolvedFieldOffset();
249253
fields.addInt32(header.getData());
250-
fields.add(offset);
254+
fields.add(fieldOffset);
255+
break;
256+
}
257+
258+
// For a class, we may know the fixed offset of a field at compile time,
259+
// or we may need to fetch it at instantiation time. Depending on the
260+
// ObjC-ness and resilience of the class hierarchy, there might be a few
261+
// different ways we need to go about this.
262+
if (loweredBaseTy.getClassOrBoundGenericClass()) {
263+
switch (getClassFieldAccess(*this, loweredBaseTy, property)) {
264+
case FieldAccess::ConstantDirect: {
265+
// Known constant fixed offset.
266+
auto offset = tryEmitConstantClassFragilePhysicalMemberOffset(*this,
267+
loweredBaseTy,
268+
property);
269+
assert(offset && "no constant offset for ConstantDirect field?!");
270+
addFixedOffset(/*struct*/ false, offset);
271+
break;
272+
}
273+
case FieldAccess::NonConstantDirect: {
274+
// A constant offset that's determined at class realization time.
275+
// We have to load the offset from a global ivar.
276+
auto header =
277+
KeyPathComponentHeader::forClassComponentWithUnresolvedIndirectOffset();
278+
fields.addInt32(header.getData());
279+
auto offsetVar = getAddrOfFieldOffset(property, /*indirect*/ false,
280+
NotForDefinition);
281+
fields.add(cast<llvm::Constant>(offsetVar.getAddress()));
282+
break;
283+
}
284+
case FieldAccess::ConstantIndirect: {
285+
// An offset that depends on the instance's generic parameterization,
286+
// but whose field offset is at a known vtable offset.
287+
auto header =
288+
KeyPathComponentHeader::forClassComponentWithUnresolvedFieldOffset();
289+
fields.addInt32(header.getData());
290+
auto fieldOffset =
291+
getClassFieldOffset(*this, loweredBaseTy.getClassOrBoundGenericClass(),
292+
property);
293+
fields.addInt32(fieldOffset.getValue());
294+
break;
295+
}
296+
case FieldAccess::NonConstantIndirect:
297+
// An offset that depends on the instance's generic parameterization,
298+
// whose vtable offset is also unknown.
299+
// TODO: This doesn't happen until class resilience is enabled.
300+
llvm_unreachable("not implemented");
301+
}
302+
break;
251303
}
252-
break;
304+
llvm_unreachable("not struct or class");
253305
}
254306
case KeyPathPatternComponent::Kind::GettableProperty:
255307
case KeyPathPatternComponent::Kind::SettableProperty: {

lib/IRGen/StructLayout.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,13 @@ void StructLayoutBuilder::addHeapHeader() {
189189
StructFields.push_back(IGM.RefCountedStructTy);
190190
}
191191

192+
void StructLayoutBuilder::addNSObjectHeader() {
193+
assert(StructFields.empty() && "adding heap header at a non-zero offset");
194+
CurSize = IGM.getPointerSize();
195+
CurAlignment = IGM.getPointerAlignment();
196+
StructFields.push_back(IGM.ObjCClassPtrTy);
197+
}
198+
192199
bool StructLayoutBuilder::addFields(llvm::MutableArrayRef<ElementLayout> elts,
193200
LayoutStrategy strategy) {
194201
// Track whether we've added any storage to our layout.

lib/IRGen/StructLayout.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,12 @@ class StructLayoutBuilder {
233233
StructLayoutBuilder(IRGenModule &IGM) : IGM(IGM) {}
234234

235235
/// Add a swift heap header to the layout. This must be the first
236-
/// call to the layout.
236+
/// thing added to the layout.
237237
void addHeapHeader();
238-
238+
/// Add the NSObject object header to the layout. This must be the first
239+
/// thing added to the layout.
240+
void addNSObjectHeader();
241+
239242
/// Add a number of fields to the layout. The field layouts need
240243
/// only have the TypeInfo set; the rest will be filled out.
241244
///

stdlib/public/SwiftShims/KeyPath.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,12 @@ static const __swift_uint32_t _SwiftKeyPathComponentHeader_OptionalTag
5555
= 3;
5656

5757
static const __swift_uint32_t _SwiftKeyPathComponentHeader_MaximumOffsetPayload
58+
= 0x1FFFFFFCU;
59+
60+
static const __swift_uint32_t _SwiftKeyPathComponentHeader_UnresolvedIndirectOffsetPayload
5861
= 0x1FFFFFFDU;
59-
60-
static const __swift_uint32_t _SwiftKeyPathComponentHeader_UnresolvedOffsetPayload
62+
63+
static const __swift_uint32_t _SwiftKeyPathComponentHeader_UnresolvedFieldOffsetPayload
6164
= 0x1FFFFFFEU;
6265

6366
static const __swift_uint32_t _SwiftKeyPathComponentHeader_OutOfLineOffsetPayload

stdlib/public/core/KeyPath.swift

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -614,8 +614,11 @@ internal struct RawKeyPathComponent {
614614
static var outOfLineOffsetPayload: UInt32 {
615615
return _SwiftKeyPathComponentHeader_OutOfLineOffsetPayload
616616
}
617-
static var unresolvedOffsetPayload: UInt32 {
618-
return _SwiftKeyPathComponentHeader_UnresolvedOffsetPayload
617+
static var unresolvedFieldOffsetPayload: UInt32 {
618+
return _SwiftKeyPathComponentHeader_UnresolvedFieldOffsetPayload
619+
}
620+
static var unresolvedIndirectOffsetPayload: UInt32 {
621+
return _SwiftKeyPathComponentHeader_UnresolvedIndirectOffsetPayload
619622
}
620623
static var computedMutatingFlag: UInt32 {
621624
return _SwiftKeyPathComponentHeader_ComputedMutatingFlag
@@ -1613,10 +1616,13 @@ internal func _getKeyPathClassAndInstanceSize(
16131616
let header = buffer.pop(RawKeyPathComponent.Header.self)
16141617

16151618
func popOffset() {
1616-
if header.payload == RawKeyPathComponent.Header.unresolvedOffsetPayload
1619+
if header.payload == RawKeyPathComponent.Header.unresolvedFieldOffsetPayload
16171620
|| header.payload == RawKeyPathComponent.Header.outOfLineOffsetPayload {
16181621
_ = buffer.pop(UInt32.self)
16191622
}
1623+
if header.payload == RawKeyPathComponent.Header.unresolvedIndirectOffsetPayload {
1624+
_ = buffer.pop(Int.self)
1625+
}
16201626
}
16211627

16221628
switch header.kind {
@@ -1712,6 +1718,12 @@ internal func _instantiateKeyPathBuffer(
17121718
count: origDestData.count - MemoryLayout<KeyPathBuffer.Header>.size)
17131719

17141720
func pushDest<T>(_ value: T) {
1721+
// TODO: If key path patterns were better optimized to try to be constant-
1722+
// memory objects, then it might become profitable to try to avoid writes
1723+
// here in the case when the dest memory contains the value we want to write
1724+
// here so that we don't dirty memory. (In practice the current
1725+
// implementation will always dirty the page when the key path is
1726+
// instantiated.)
17151727
_sanityCheck(_isPOD(T.self))
17161728
var value2 = value
17171729
let size = MemoryLayout<T>.size
@@ -1729,12 +1741,15 @@ internal func _instantiateKeyPathBuffer(
17291741

17301742
// Instantiate components that need it.
17311743
var base: Any.Type = rootType
1744+
// Some pattern forms are pessimistically larger than what we need in the
1745+
// instantiated key path. Keep track of this.
1746+
var shrinkage = 0
17321747
while true {
17331748
let componentAddr = destData.baseAddress.unsafelyUnwrapped
17341749
let header = patternBuffer.pop(RawKeyPathComponent.Header.self)
17351750

17361751
func tryToResolveOffset() {
1737-
if header.payload == RawKeyPathComponent.Header.unresolvedOffsetPayload {
1752+
if header.payload == RawKeyPathComponent.Header.unresolvedFieldOffsetPayload {
17381753
// Look up offset in type metadata. The value in the pattern is the
17391754
// offset within the metadata object.
17401755
let metadataPtr = unsafeBitCast(base, to: UnsafeRawPointer.self)
@@ -1749,6 +1764,21 @@ internal func _instantiateKeyPathBuffer(
17491764
return
17501765
}
17511766

1767+
if header.payload == RawKeyPathComponent.Header.unresolvedIndirectOffsetPayload {
1768+
// Look up offset in the indirectly-referenced variable we have a
1769+
// pointer.
1770+
let offsetVar = patternBuffer.pop(UnsafeRawPointer.self)
1771+
let offsetValue = UInt32(offsetVar.load(as: UInt.self))
1772+
// Rewrite the header for a resolved offset.
1773+
var newHeader = header
1774+
newHeader.payload = RawKeyPathComponent.Header.outOfLineOffsetPayload
1775+
pushDest(newHeader)
1776+
pushDest(offsetValue)
1777+
shrinkage += MemoryLayout<UnsafeRawPointer>.size
1778+
- MemoryLayout<UInt32>.size
1779+
return
1780+
}
1781+
17521782
// Otherwise, just transfer the pre-resolved component.
17531783
pushDest(header)
17541784
if header.payload == RawKeyPathComponent.Header.outOfLineOffsetPayload {
@@ -1846,10 +1876,11 @@ internal func _instantiateKeyPathBuffer(
18461876
}
18471877

18481878
// We should have traversed both buffers.
1849-
_sanityCheck(patternBuffer.data.isEmpty && destData.isEmpty)
1879+
_sanityCheck(patternBuffer.data.isEmpty && destData.count == shrinkage)
18501880

18511881
// Write out the header.
1852-
let destHeader = KeyPathBuffer.Header(size: origPatternBuffer.data.count,
1882+
let destHeader = KeyPathBuffer.Header(
1883+
size: origPatternBuffer.data.count - shrinkage,
18531884
trivial: true, // TODO: nontrivial indexes
18541885
hasReferencePrefix: endOfReferencePrefixComponent != nil)
18551886

0 commit comments

Comments
 (0)