Skip to content

Commit 13cbe0d

Browse files
committed
adding the @_compilerInitialized attribute
This attribute is designed for let-bound variables whose initializing assignment is synthesized by the compiler. This assignment is expected to happen at some point before DefiniteInitialization has run, which is the pass that verifies whether the compiler truly initialized the variable. I generally expect that this will never be a user-facing feature, and that the synthesized assignment happens in SILGen.
1 parent 6c0cfac commit 13cbe0d

File tree

11 files changed

+170
-3
lines changed

11 files changed

+170
-3
lines changed

include/swift/AST/Attr.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@ SIMPLE_DECL_ATTR(objcMembers, ObjCMembers,
265265
OnClass |
266266
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
267267
34)
268+
CONTEXTUAL_SIMPLE_DECL_ATTR(_compilerInitialized, CompilerInitialized,
269+
OnVar |
270+
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
271+
35)
268272
CONTEXTUAL_SIMPLE_DECL_ATTR(__consuming, Consuming,
269273
OnFunc | OnAccessor |
270274
DeclModifier |

include/swift/AST/DiagnosticsSIL.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ ERROR(return_from_init_without_initing_stored_properties,none,
211211
"return from initializer without initializing all"
212212
" stored properties", ())
213213

214+
ERROR(explicit_store_of_compilerinitialized,none,
215+
"illegal assignment to '@_compilerInitialized' storage", ())
214216
ERROR(variable_function_use_uninit,none,
215217
"%select{variable|constant}1 '%0' used by function definition before"
216218
" being initialized",

include/swift/AST/DiagnosticsSema.def

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3203,6 +3203,17 @@ ERROR(escaping_non_function_parameter,none,
32033203
ERROR(escaping_optional_type_argument, none,
32043204
"closure is already escaping in optional type argument", ())
32053205

3206+
// @_compilerInitialized attribute
3207+
ERROR(incompatible_compilerinitialized_var,none,
3208+
"'@_compilerInitialized' attribute only applies to "
3209+
"let-bound stored properties without a default value", ())
3210+
ERROR(protocol_compilerinitialized,none,
3211+
"'@_compilerInitialized' is not currently supported in protocols", ())
3212+
ERROR(optional_compilerinitialized,none,
3213+
"'@_compilerInitialized' cannot be applied to an Optional let", ())
3214+
ERROR(instancemember_compilerinitialized,none,
3215+
"'@_compilerInitialized' can only be applied to a non-static class or actor member", ())
3216+
32063217
// @_nonEphemeral attribute
32073218
ERROR(non_ephemeral_non_pointer_type,none,
32083219
"@_nonEphemeral attribute only applies to pointer types", ())

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,10 @@ class ElementUseCollector {
524524
/// of this.
525525
bool InEnumSubElement = false;
526526

527+
/// If true, then encountered uses correspond to a VarDecl marked
528+
/// as \p @_compilerInitialized
529+
bool InCompilerInitializedField = false;
530+
527531
public:
528532
ElementUseCollector(const DIMemoryObjectInfo &TheMemory,
529533
DIElementUseInfo &UseInfo,
@@ -665,6 +669,16 @@ static SILValue getAccessedPointer(SILValue Pointer) {
665669
return Pointer;
666670
}
667671

672+
static DIUseKind verifyCompilerInitialized(SILValue pointer,
673+
SILInstruction *write,
674+
DIUseKind origKind) {
675+
// if the write is _not_ auto-generated, it's a bad store.
676+
if (!write->getLoc().isAutoGenerated())
677+
return DIUseKind::BadExplicitStore;
678+
679+
return origKind;
680+
}
681+
668682
void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) {
669683
assert(Pointer->getType().isAddress() &&
670684
"Walked through the pointer to the value?");
@@ -733,6 +747,11 @@ void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) {
733747
else
734748
Kind = DIUseKind::Initialization;
735749

750+
// If it's a non-synthesized write to a @_compilerInitialized field,
751+
// indicate that in the Kind.
752+
if (LLVM_UNLIKELY(InCompilerInitializedField))
753+
Kind = verifyCompilerInitialized(Pointer, User, Kind);
754+
736755
addElementUses(BaseEltNo, PointeeType, User, Kind);
737756
continue;
738757
}
@@ -1403,9 +1422,15 @@ void ElementUseCollector::collectClassSelfUses(
14031422
if (EltNumbering.count(REAI->getField()) != 0) {
14041423
assert(EltNumbering.count(REAI->getField()) &&
14051424
"ref_element_addr not a local field?");
1425+
1426+
// Remember whether the field is marked '@_compilerInitialized'
1427+
const bool hasCompInit =
1428+
REAI->getField()->getAttrs().hasAttribute<CompilerInitializedAttr>();
1429+
llvm::SaveAndRestore<bool> X1(InCompilerInitializedField, hasCompInit);
1430+
14061431
// Recursively collect uses of the fields. Note that fields of the class
14071432
// could be tuples, so they may be tracked as independent elements.
1408-
llvm::SaveAndRestore<bool> X(IsSelfOfNonDelegatingInitializer, false);
1433+
llvm::SaveAndRestore<bool> X2(IsSelfOfNonDelegatingInitializer, false);
14091434
collectUses(REAI, EltNumbering[REAI->getField()]);
14101435
continue;
14111436
}

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@ enum DIUseKind {
274274
/// The instruction is a store to a member of a larger struct value.
275275
PartialStore,
276276

277+
/// This instruction is an init, assignment, or store to a
278+
/// @_compilerInitialized field that was _not_ automatically generated
279+
BadExplicitStore,
280+
277281
/// An 'inout' argument of a function application.
278282
InOutArgument,
279283

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ namespace {
537537
void diagnoseRefElementAddr(RefElementAddrInst *REI);
538538
bool diagnoseMethodCall(const DIMemoryUse &Use,
539539
bool SuperInitDone);
540+
void diagnoseBadExplicitStore(SILInstruction *Inst);
540541

541542
bool isBlockIsReachableFromEntry(const SILBasicBlock *BB);
542543
};
@@ -574,6 +575,7 @@ LifetimeChecker::LifetimeChecker(const DIMemoryObjectInfo &TheMemory,
574575
case DIUseKind::InOutSelfArgument:
575576
case DIUseKind::PartialStore:
576577
case DIUseKind::SelfInit:
578+
case DIUseKind::BadExplicitStore:
577579
break;
578580
}
579581

@@ -777,6 +779,13 @@ void LifetimeChecker::diagnoseInitError(const DIMemoryUse &Use,
777779
diagnose(Module, TheMemory.getLoc(), diag::variable_defined_here, isLet);
778780
}
779781

782+
void LifetimeChecker::diagnoseBadExplicitStore(SILInstruction *Inst) {
783+
if (!shouldEmitError(Inst))
784+
return;
785+
786+
diagnose(Module, Inst->getLoc(), diag::explicit_store_of_compilerinitialized);
787+
}
788+
780789
/// Determines whether the given function is a constructor that belogs to a
781790
/// distributed actor declaration.
782791
static bool isDistributedActorCtor(SILFunction &F) {
@@ -1138,6 +1147,10 @@ void LifetimeChecker::doIt() {
11381147
case DIUseKind::TypeOfSelf:
11391148
handleTypeOfSelfUse(Use);
11401149
break;
1150+
1151+
case DIUseKind::BadExplicitStore:
1152+
diagnoseBadExplicitStore(Inst);
1153+
break;
11411154
}
11421155
}
11431156

lib/Sema/TypeCheckAttr.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
316316

317317
void visitUnsafeInheritExecutorAttr(UnsafeInheritExecutorAttr *attr);
318318

319+
void visitCompilerInitializedAttr(CompilerInitializedAttr *attr);
320+
319321
void checkBackDeployAttrs(ArrayRef<BackDeployAttr *> Attrs);
320322

321323
void visitKnownToBeLocalAttr(KnownToBeLocalAttr *attr);
@@ -5959,6 +5961,48 @@ void AttributeChecker::visitUnsafeInheritExecutorAttr(
59595961
}
59605962
}
59615963

5964+
void AttributeChecker::visitCompilerInitializedAttr(
5965+
CompilerInitializedAttr *attr) {
5966+
auto var = cast<VarDecl>(D);
5967+
5968+
// For now, ban its use within protocols. I could imagine supporting it
5969+
// by saying that witnesses must also be compiler-initialized, but I can't
5970+
// think of a use case for that right now.
5971+
if (auto ctx = var->getDeclContext()) {
5972+
if (isa<ProtocolDecl>(ctx) && var->isProtocolRequirement()) {
5973+
diagnose(attr->getLocation(), diag::protocol_compilerinitialized);
5974+
return;
5975+
}
5976+
}
5977+
5978+
// Must be a let-bound stored property without an initial value.
5979+
// The fact that it's let-bound generally simplifies the implementation
5980+
// of this attribute in definite initialization, since we don't need to
5981+
// reason about whether the compiler made the first assignment to the var,
5982+
// etc.
5983+
if (var->hasInitialValue()
5984+
|| !var->isOrdinaryStoredProperty()
5985+
|| !var->isLet()) {
5986+
diagnose(attr->getLocation(), diag::incompatible_compilerinitialized_var);
5987+
return;
5988+
}
5989+
5990+
// Because optionals are implicitly initialized to nil according to the
5991+
// language, this attribute doesn't make sense on optionals.
5992+
if (var->getType()->isOptional()) {
5993+
diagnose(attr->getLocation(), diag::optional_compilerinitialized);
5994+
return;
5995+
}
5996+
5997+
// To keep things even more simple in definite initialization, restrict
5998+
// the attribute to class/actor instance members only. This means we can
5999+
// focus just on the initialization in the init.
6000+
if (!(var->getDeclContext()->getSelfClassDecl() && var->isInstanceMember())) {
6001+
diagnose(attr->getLocation(), diag::instancemember_compilerinitialized);
6002+
return;
6003+
}
6004+
}
6005+
59626006
namespace {
59636007

59646008
class ClosureAttributeChecker

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,6 +1576,7 @@ namespace {
15761576
UNINTERESTING_ATTR(KnownToBeLocal)
15771577

15781578
UNINTERESTING_ATTR(UnsafeInheritExecutor)
1579+
UNINTERESTING_ATTR(CompilerInitialized)
15791580

15801581
#undef UNINTERESTING_ATTR
15811582

lib/Serialization/ModuleFormat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5656
/// describe what change you made. The content of this comment isn't important;
5757
/// it just ensures a conflict if two people change the module format.
5858
/// Don't worry about adhering to the 80-column limit for this line.
59-
const uint16_t SWIFTMODULE_VERSION_MINOR = 680; // Ignore export_as in XRef
59+
const uint16_t SWIFTMODULE_VERSION_MINOR = 681; // Add @_compilerInitialized attr
6060

6161
/// A standard hash seed used for all string hashes in a serialized module.
6262
///

test/SILOptimizer/definite_init.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend -emit-sil %s -o /dev/null
1+
// RUN: %target-swift-frontend -emit-sil -verify %s -o /dev/null
22

33
class SomeClass {}
44

@@ -53,3 +53,10 @@ func tuple_test() -> Int {
5353

5454
return t.1+t.0 // No diagnostic, everything is fully initialized.
5555
}
56+
57+
class CheckCompilerInitAttr {
58+
@_compilerInitialized let poster: Int
59+
init() {
60+
poster = 10 // expected-error {{illegal assignment to '@_compilerInitialized' storage}}
61+
}
62+
}

0 commit comments

Comments
 (0)