Skip to content

Commit a906f43

Browse files
committed
Allow type metadata to be incomplete.
Most of the work of this patch is just propagating metadata states throughout the system, especially local-type-data caching and metadata-path resolution. It took a few design revisions to get both DynamicMetadataRequest and MetadataResponse to a shape that felt right and seemed to make everything easier. The design is laid out pretty clearly (I hope) in the comments on DynamicMetadataRequest and MetadataResponse, so I'm not going to belabor it again here. Instead, I'll list out the work that's still outstanding: - I'm sure there are places we're asking for complete metadata where we could be asking for something weaker. - I need to actually test the runtime behavior to verify that it's breaking the cycles it's supposed to, instead of just not regressing anything else. - I need to add something to the runtime to actually force all the generic arguments of a generic type to be complete before reporting completion. I think we can get away with this for now because all existing types construct themselves completely on the first request, but there might be a race condition there if another asks for the type argument, gets an abstract metadata, and constructs a type with it without ever needing it to be completed. - Non-generic resilient types need to be switched over to an IRGen pattern that supports initialization suspension. - We should probably space out the MetadataStates so that there's some space between Abstract and Complete. - The runtime just calmly sits there, never making progress and permanently blocking any waiting threads, if you actually form an unresolvable metadata dependency cycle. It is possible to set up such a thing in a way that Sema can't diagnose, and we should detect it at runtime. I've set up some infrastructure so that it should be straightforward to diagnose this, but I haven't actually implemented the diagnostic yet. - It's not clear to me that swift_checkMetadataState is really cheap enough that it doesn't make sense to use a cache for type-fulfilled metadata in associated type access functions. Fortunately this is not ABI-affecting, so we can evaluate it anytime. - Type layout really seems like a lot of code now that we sometimes need to call swift_checkMetadataState for generic arguments. Maybe we can have the runtime do this by marking low bits or something, so that a TypeLayoutRef is actually either (1) a TypeLayout, (2) a known layout-complete metadata, or (3) a metadata of unknown state. We could do that later with a flag, but we'll need to at least future-proof by allowing the runtime functions to return a MetadataDependency.
1 parent 583bec3 commit a906f43

39 files changed

+1469
-483
lines changed

lib/IRGen/Fulfillment.cpp

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "IRGenModule.h"
2020

2121
#include "GenericRequirement.h"
22+
#include "MetadataRequest.h"
2223
#include "ProtocolInfo.h"
2324
#include "swift/AST/Decl.h"
2425
#include "swift/AST/ProtocolConformance.h"
@@ -109,6 +110,7 @@ static bool isLeafTypeMetadata(CanType type) {
109110
/// metadata for the given type, false if it might be a subtype
110111
bool FulfillmentMap::searchTypeMetadata(IRGenModule &IGM, CanType type,
111112
IsExact_t isExact,
113+
MetadataState metadataState,
112114
unsigned source, MetadataPath &&path,
113115
const InterestingKeysCallback &keys) {
114116

@@ -118,26 +120,31 @@ bool FulfillmentMap::searchTypeMetadata(IRGenModule &IGM, CanType type,
118120
// If the type isn't a leaf type, also check it as an inexact match.
119121
bool hadFulfillment = false;
120122
if (!isLeafTypeMetadata(type)) {
121-
hadFulfillment |= searchTypeMetadata(IGM, type, IsInexact, source,
122-
MetadataPath(path), keys);
123+
hadFulfillment |= searchTypeMetadata(IGM, type, IsInexact, metadataState,
124+
source, MetadataPath(path), keys);
123125
}
124126

125127
// Add the fulfillment.
126-
hadFulfillment |= addFulfillment({type, nullptr}, source, std::move(path));
128+
hadFulfillment |= addFulfillment({type, nullptr},
129+
source, std::move(path), metadataState);
127130
return hadFulfillment;
128131
}
129132

130-
if (keys.isInterestingType(type)) {
133+
// Search the superclass fields. We can only do this if the metadata
134+
// is complete.
135+
if (metadataState == MetadataState::Complete &&
136+
keys.isInterestingType(type)) {
131137
if (auto superclassTy = keys.getSuperclassBound(type)) {
132-
return searchNominalTypeMetadata(IGM, superclassTy, source,
133-
std::move(path), keys);
138+
return searchNominalTypeMetadata(IGM, superclassTy, metadataState,
139+
source, std::move(path), keys);
134140
}
135141
}
136142

137143
// Inexact metadata will be a problem if we ever try to use this
138144
// to remember that we already have the metadata for something.
139145
if (isa<NominalType>(type) || isa<BoundGenericType>(type)) {
140-
return searchNominalTypeMetadata(IGM, type, source, std::move(path), keys);
146+
return searchNominalTypeMetadata(IGM, type, metadataState,
147+
source, std::move(path), keys);
141148
}
142149

143150
// TODO: tuples
@@ -218,7 +225,8 @@ bool FulfillmentMap::searchWitnessTable(
218225
// If we're not limiting the set of interesting conformances, or if
219226
// this is an interesting conformance, record it.
220227
if (!interestingConformances || interestingConformances->count(protocol)) {
221-
hadFulfillment |= addFulfillment({type, protocol}, source, std::move(path));
228+
hadFulfillment |= addFulfillment({type, protocol}, source,
229+
std::move(path), MetadataState::Complete);
222230
}
223231

224232
return hadFulfillment;
@@ -227,6 +235,7 @@ bool FulfillmentMap::searchWitnessTable(
227235

228236
bool FulfillmentMap::searchNominalTypeMetadata(IRGenModule &IGM,
229237
CanType type,
238+
MetadataState metadataState,
230239
unsigned source,
231240
MetadataPath &&path,
232241
const InterestingKeysCallback &keys) {
@@ -254,10 +263,12 @@ bool FulfillmentMap::searchNominalTypeMetadata(IRGenModule &IGM,
254263

255264
// If the fulfilled value is type metadata, refine the path.
256265
if (!conf) {
266+
auto argState = getPresumedMetadataStateForTypeArgument(metadataState);
257267
MetadataPath argPath = path;
258268
argPath.addNominalTypeArgumentComponent(reqtIndex);
259269
hadFulfillment |=
260-
searchTypeMetadata(IGM, arg, IsExact, source, std::move(argPath), keys);
270+
searchTypeMetadata(IGM, arg, IsExact, argState, source,
271+
std::move(argPath), keys);
261272
return;
262273
}
263274

@@ -280,33 +291,54 @@ bool FulfillmentMap::searchNominalTypeMetadata(IRGenModule &IGM,
280291

281292
/// Testify that there's a fulfillment at the given path.
282293
bool FulfillmentMap::addFulfillment(FulfillmentKey key,
283-
unsigned source, MetadataPath &&path) {
294+
unsigned source,
295+
MetadataPath &&path,
296+
MetadataState metadataState) {
284297
// Only add a fulfillment if we don't have any previous
285298
// fulfillment for that value or if it 's cheaper than the existing
286299
// fulfillment.
287300
auto it = Fulfillments.find(key);
288301
if (it != Fulfillments.end()) {
289-
if (path.cost() >= it->second.Path.cost()) {
302+
// If the new fulfillment is worse than the existing one, ignore it.
303+
auto existingState = it->second.getState();
304+
if (!isAtLeast(metadataState, existingState)) {
305+
return false;
306+
}
307+
308+
// Consider cost only if the fulfillments are equivalent in state.
309+
// TODO: this is potentially suboptimal, but it generally won't matter.
310+
if (metadataState == existingState &&
311+
path.cost() >= it->second.Path.cost()) {
290312
return false;
291313
}
292314

293315
it->second.SourceIndex = source;
294316
it->second.Path = std::move(path);
295317
return true;
296318
} else {
297-
Fulfillments.insert({ key, Fulfillment(source, std::move(path)) });
319+
Fulfillments.insert({ key, Fulfillment(source, std::move(path),
320+
metadataState) });
298321
return true;
299322
}
300323
}
301324

325+
static StringRef getStateName(MetadataState state) {
326+
switch (state) {
327+
case MetadataState::Complete: return "complete";
328+
case MetadataState::LayoutComplete: return "layout-complete";
329+
case MetadataState::Abstract: return "abstract";
330+
}
331+
}
332+
302333
void FulfillmentMap::dump() const {
303334
auto &out = llvm::errs();
304335
for (auto &entry : Fulfillments) {
305336
out << "(" << entry.first.first;
306337
if (auto proto = entry.first.second) {
307338
out << ", " << proto->getNameStr();
308339
}
309-
out << ") => sources[" << entry.second.SourceIndex
340+
out << ") => " << getStateName(entry.second.getState())
341+
<< " at sources[" << entry.second.SourceIndex
310342
<< "]." << entry.second.Path << "\n";
311343
}
312344
}

lib/IRGen/Fulfillment.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,19 @@ namespace irgen {
3232
/// path from the given source.
3333
struct Fulfillment {
3434
Fulfillment() = default;
35-
Fulfillment(unsigned sourceIndex, MetadataPath &&path)
36-
: SourceIndex(sourceIndex), Path(std::move(path)) {}
35+
Fulfillment(unsigned sourceIndex, MetadataPath &&path, MetadataState state)
36+
: SourceIndex(sourceIndex), State(unsigned(state)), Path(std::move(path)) {}
3737

3838
/// The source index.
39-
unsigned SourceIndex;
39+
unsigned SourceIndex : 30;
40+
41+
/// The state of the metadata at the fulfillment.
42+
unsigned State : 2;
4043

4144
/// The path from the source metadata.
4245
MetadataPath Path;
46+
47+
MetadataState getState() const { return MetadataState(State); }
4348
};
4449

4550
class FulfillmentMap {
@@ -90,6 +95,7 @@ class FulfillmentMap {
9095
///
9196
/// \return true if any fulfillments were added by this search.
9297
bool searchTypeMetadata(IRGenModule &IGM, CanType type, IsExact_t isExact,
98+
MetadataState metadataState,
9399
unsigned sourceIndex, MetadataPath &&path,
94100
const InterestingKeysCallback &interestingKeys);
95101

@@ -109,7 +115,8 @@ class FulfillmentMap {
109115
///
110116
/// \return true if the fulfillment was added, which won't happen if there's
111117
/// already a fulfillment that was at least as good
112-
bool addFulfillment(FulfillmentKey key, unsigned source, MetadataPath &&path);
118+
bool addFulfillment(FulfillmentKey key, unsigned source,
119+
MetadataPath &&path, MetadataState state);
113120

114121
const Fulfillment *getTypeMetadata(CanType type) const {
115122
auto it = Fulfillments.find({type, nullptr});
@@ -139,7 +146,8 @@ class FulfillmentMap {
139146

140147
private:
141148
bool searchNominalTypeMetadata(IRGenModule &IGM, CanType type,
142-
unsigned source, MetadataPath &&path,
149+
MetadataState metadataState, unsigned source,
150+
MetadataPath &&path,
143151
const InterestingKeysCallback &keys);
144152

145153
/// Search the given witness table for useful fulfillments.

lib/IRGen/GenArchetype.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ irgen::emitArchetypeTypeMetadataRef(IRGenFunction &IGF,
7373

7474
setTypeMetadataName(IGF.IGM, response.getMetadata(), archetype);
7575

76-
IGF.setScopedLocalTypeMetadata(archetype, request, response);
76+
IGF.setScopedLocalTypeMetadata(archetype, response);
7777

7878
return response;
7979
}
@@ -279,12 +279,15 @@ llvm::Value *irgen::emitArchetypeWitnessTableRef(IRGenFunction &IGF,
279279
}
280280
assert(lastProtocol == protocol);
281281

282-
auto rootWTable = IGF.getLocalTypeData(rootArchetype,
282+
auto rootWTable = IGF.tryGetLocalTypeData(rootArchetype,
283283
LocalTypeDataKind::forAbstractProtocolWitnessTable(rootProtocol));
284+
assert(rootWTable && "root witness table not bound in local context!");
284285

285286
wtable = path.followFromWitnessTable(IGF, rootArchetype,
286287
ProtocolConformanceRef(rootProtocol),
287-
rootWTable, nullptr);
288+
MetadataResponse::forComplete(rootWTable),
289+
/*request*/ MetadataState::Complete,
290+
nullptr).getMetadata();
288291

289292
return wtable;
290293
}
@@ -300,7 +303,7 @@ irgen::emitAssociatedTypeMetadataRef(IRGenFunction &IGF,
300303

301304
// Find the origin's type metadata.
302305
llvm::Value *originMetadata =
303-
emitArchetypeTypeMetadataRef(IGF, origin, MetadataState::Complete)
306+
emitArchetypeTypeMetadataRef(IGF, origin, MetadataState::Abstract)
304307
.getMetadata();
305308

306309
return emitAssociatedTypeMetadataRef(IGF, originMetadata, wtable,
@@ -378,11 +381,11 @@ const TypeInfo *TypeConverter::convertArchetypeType(ArchetypeType *archetype) {
378381

379382
static void setMetadataRef(IRGenFunction &IGF,
380383
ArchetypeType *archetype,
381-
llvm::Value *metadata) {
384+
llvm::Value *metadata,
385+
MetadataState metadataState) {
382386
assert(metadata->getType() == IGF.IGM.TypeMetadataPtrTy);
383-
IGF.setUnscopedLocalTypeData(CanType(archetype),
384-
LocalTypeDataKind::forFormalTypeMetadata(),
385-
metadata);
387+
IGF.setUnscopedLocalTypeMetadata(CanType(archetype),
388+
MetadataResponse::forBounded(metadata, metadataState));
386389
}
387390

388391
static void setWitnessTable(IRGenFunction &IGF,
@@ -401,10 +404,11 @@ static void setWitnessTable(IRGenFunction &IGF,
401404
/// witness value within this scope.
402405
void IRGenFunction::bindArchetype(ArchetypeType *archetype,
403406
llvm::Value *metadata,
407+
MetadataState metadataState,
404408
ArrayRef<llvm::Value*> wtables) {
405409
// Set the metadata pointer.
406410
setTypeMetadataName(IGM, metadata, CanType(archetype));
407-
setMetadataRef(*this, archetype, metadata);
411+
setMetadataRef(*this, archetype, metadata, metadataState);
408412

409413
// Set the protocol witness tables.
410414

lib/IRGen/GenCast.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ FailableCastResult irgen::emitClassIdenticalCast(IRGenFunction &IGF,
115115
targetMetadata
116116
= emitClassHeapMetadataRef(IGF, toType.getSwiftRValueType(),
117117
MetadataValueType::ObjCClass,
118+
MetadataState::Complete,
118119
/*allowUninitialized*/ allowConservative);
119120
}
120121

@@ -248,7 +249,8 @@ void irgen::emitMetatypeDowncast(IRGenFunction &IGF,
248249

249250
// Get the ObjC metadata for the type we're checking.
250251
toMetadata = emitClassHeapMetadataRef(IGF, toMetatype.getInstanceType(),
251-
MetadataValueType::ObjCClass);
252+
MetadataValueType::ObjCClass,
253+
MetadataState::Complete);
252254
switch (mode) {
253255
case CheckedCastMode::Unconditional:
254256
castFn = IGF.IGM.getDynamicCastObjCClassMetatypeUnconditionalFn();

lib/IRGen/GenClass.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -757,13 +757,15 @@ llvm::Value *irgen::emitClassAllocation(IRGenFunction &IGF, SILType selfType,
757757
if (objc) {
758758
llvm::Value *metadata =
759759
emitClassHeapMetadataRef(IGF, classType, MetadataValueType::ObjCClass,
760+
MetadataState::Complete,
760761
/*allow uninitialized*/ true);
761762
StackAllocSize = -1;
762763
return emitObjCAllocObjectCall(IGF, metadata, selfType);
763764
}
764765

765766
llvm::Value *metadata =
766-
emitClassHeapMetadataRef(IGF, classType, MetadataValueType::TypeMetadata);
767+
emitClassHeapMetadataRef(IGF, classType, MetadataValueType::TypeMetadata,
768+
MetadataState::Complete);
767769

768770
// FIXME: Long-term, we clearly need a specialized runtime entry point.
769771
llvm::Value *size, *alignMask;
@@ -2440,15 +2442,17 @@ FunctionPointer irgen::emitVirtualMethodValue(IRGenFunction &IGF,
24402442
// dynamically.
24412443

24422444
metadata = emitClassHeapMetadataRef(IGF, instanceTy.getSwiftRValueType(),
2443-
MetadataValueType::TypeMetadata);
2445+
MetadataValueType::TypeMetadata,
2446+
MetadataState::Complete);
24442447
auto superField = emitAddressOfSuperclassRefInClassMetadata(IGF, metadata);
24452448
metadata = IGF.Builder.CreateLoad(superField);
24462449
} else {
24472450
// Otherwise, we can directly load the statically known superclass's
24482451
// metadata.
24492452
auto superTy = instanceTy.getSuperclass();
24502453
metadata = emitClassHeapMetadataRef(IGF, superTy.getSwiftRValueType(),
2451-
MetadataValueType::TypeMetadata);
2454+
MetadataValueType::TypeMetadata,
2455+
MetadataState::Complete);
24522456
}
24532457
} else {
24542458
if ((isa<FuncDecl>(methodDecl) && cast<FuncDecl>(methodDecl)->isStatic()) ||

0 commit comments

Comments
 (0)