Skip to content

Commit 8f26329

Browse files
committed
Builtins to support copy-on-write SIL instructions
* Builtin.COWBufferForReading -> ref_element_addr [immutable] / ref_tail_addr [immutable] * Builtin.beginCOWmutation -> begin_cow_mutation * Builtin.endCOWmutation -> end_cow_mutation
1 parent d57ee13 commit 8f26329

File tree

12 files changed

+219
-3
lines changed

12 files changed

+219
-3
lines changed

include/swift/AST/Builtins.def

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,29 @@ BUILTIN_SIL_OPERATION(IsUnique, "isUnique", Special)
429429
/// BridgeObject to be treated as a native object by the runtime.
430430
BUILTIN_SIL_OPERATION(IsUnique_native, "isUnique_native", Special)
431431

432+
/// beginCOWMutation<T : AnyObject>(inout T) -> Int1
433+
///
434+
/// Begins a copy-on-write mutation for a buffer reference which is passed as
435+
/// inout argument. It returns a true if the buffer is uniquely referenced.
436+
/// In this case the buffer may be mutated after calling this builtin.
437+
///
438+
/// The beginCOWMutation builtin is very similar to isUnique. It just translates
439+
/// to a different SIL instruction (begin_cow_mutation), which is the preferred
440+
/// representation of COW in SIL.
441+
BUILTIN_SIL_OPERATION(BeginCOWMutation, "beginCOWMutation", Special)
442+
443+
/// beginCOWMutation_native<T : AnyObject>(inout T) -> Int1
444+
///
445+
/// Like beginCOWMutation, but it's assumed that T has native Swift reference
446+
/// counting.
447+
BUILTIN_SIL_OPERATION(BeginCOWMutation_native, "beginCOWMutation_native", Special)
448+
449+
/// endCOWMutation<T : AnyObject>(inout T)
450+
///
451+
/// Ends a copy-on-write mutation for a buffer reference which is passed as
452+
/// inout argument. After calling this builtin, the buffer must not be mutated.
453+
BUILTIN_SIL_OPERATION(EndCOWMutation, "endCOWMutation", Special)
454+
432455
/// bindMemory : <T> (Builtin.RawPointer, Builtin.Word, T.Type) -> ()
433456
BUILTIN_SIL_OPERATION(BindMemory, "bindMemory", Special)
434457

@@ -651,6 +674,12 @@ BUILTIN_MISC_OPERATION(AssignCopyArrayFrontToBack, "assignCopyArrayFrontToBack",
651674
BUILTIN_MISC_OPERATION(AssignCopyArrayBackToFront, "assignCopyArrayBackToFront", "", Special)
652675
BUILTIN_MISC_OPERATION(AssignTakeArray, "assignTakeArray", "", Special)
653676

677+
/// COWBufferForReading has type <T: AnyObject> T -> T
678+
///
679+
/// Returns the buffer reference which is passed as argument.
680+
/// This builtin indicates to the optimizer that the buffer is not mutable.
681+
BUILTIN_MISC_OPERATION(COWBufferForReading, "COWBufferForReading", "n", Special)
682+
654683
// unsafeGuaranteed has type <T: AnyObject> T -> (T, Builtin.Int8)
655684
BUILTIN_MISC_OPERATION(UnsafeGuaranteed, "unsafeGuaranteed", "", Special)
656685

lib/AST/Builtins.cpp

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,13 +431,26 @@ createGenericParam(ASTContext &ctx, const char *name, unsigned index) {
431431

432432
/// Create a generic parameter list with multiple generic parameters.
433433
static GenericParamList *getGenericParams(ASTContext &ctx,
434-
unsigned numParameters) {
434+
unsigned numParameters,
435+
bool isAnyObject) {
435436
assert(numParameters <= llvm::array_lengthof(GenericParamNames));
436437

437438
SmallVector<GenericTypeParamDecl*, 2> genericParams;
438439
for (unsigned i = 0; i != numParameters; ++i)
439440
genericParams.push_back(createGenericParam(ctx, GenericParamNames[i], i));
440441

442+
443+
if (isAnyObject) {
444+
CanType ao = ctx.getAnyObjectType();
445+
SmallVector<RequirementRepr, 1> req;
446+
req.push_back(RequirementRepr::getTypeConstraint(TypeLoc::withoutLoc(genericParams[0]->getInterfaceType()), SourceLoc(),
447+
TypeLoc::withoutLoc(ao)));
448+
449+
auto paramList = GenericParamList::create(ctx, SourceLoc(), genericParams,
450+
SourceLoc(), req, SourceLoc());
451+
return paramList;
452+
}
453+
441454
auto paramList = GenericParamList::create(ctx, SourceLoc(), genericParams,
442455
SourceLoc());
443456
return paramList;
@@ -460,9 +473,10 @@ namespace {
460473
SmallVector<Requirement, 2> addedRequirements;
461474

462475
public:
463-
BuiltinFunctionBuilder(ASTContext &ctx, unsigned numGenericParams = 1)
476+
BuiltinFunctionBuilder(ASTContext &ctx, unsigned numGenericParams = 1,
477+
bool isAnyObject = false)
464478
: Context(ctx) {
465-
TheGenericParamList = getGenericParams(ctx, numGenericParams);
479+
TheGenericParamList = getGenericParams(ctx, numGenericParams, isAnyObject);
466480
for (auto gp : TheGenericParamList->getParams()) {
467481
genericParamTypes.push_back(
468482
gp->getDeclaredInterfaceType()->castTo<GenericTypeParamType>());
@@ -645,6 +659,14 @@ static ValueDecl *getIsUniqueOperation(ASTContext &Context, Identifier Id) {
645659
return builder.build(Id);
646660
}
647661

662+
static ValueDecl *getEndCOWMutation(ASTContext &Context, Identifier Id) {
663+
// <T> (@inout T) -> ()
664+
BuiltinFunctionBuilder builder(Context);
665+
builder.addParameter(makeGenericParam(), ValueOwnership::InOut);
666+
builder.setResult(makeConcrete(TupleType::getEmpty(Context)));
667+
return builder.build(Id);
668+
}
669+
648670
static ValueDecl *getBindMemoryOperation(ASTContext &Context, Identifier Id) {
649671
BuiltinFunctionBuilder builder(Context);
650672
builder.addParameter(makeConcrete(Context.TheRawPointerType));
@@ -908,6 +930,16 @@ static ValueDecl *getValueToBridgeObject(ASTContext &C, Identifier Id) {
908930
return builder.build(Id);
909931
}
910932

933+
static ValueDecl *getCOWBufferForReading(ASTContext &C, Identifier Id) {
934+
// <T : AnyObject> T -> T
935+
//
936+
BuiltinFunctionBuilder builder(C, 1, true);
937+
auto T = makeGenericParam();
938+
builder.addParameter(T);
939+
builder.setResult(T);
940+
return builder.build(Id);
941+
}
942+
911943
static ValueDecl *getUnsafeGuaranteed(ASTContext &C, Identifier Id) {
912944
// <T : AnyObject> T -> (T, Int8Ty)
913945
//
@@ -2249,9 +2281,16 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
22492281

22502282
case BuiltinValueKind::IsUnique:
22512283
case BuiltinValueKind::IsUnique_native:
2284+
case BuiltinValueKind::BeginCOWMutation:
2285+
case BuiltinValueKind::BeginCOWMutation_native:
22522286
if (!Types.empty()) return nullptr;
2287+
// BeginCOWMutation has the same signature as IsUnique.
22532288
return getIsUniqueOperation(Context, Id);
22542289

2290+
case BuiltinValueKind::EndCOWMutation:
2291+
if (!Types.empty()) return nullptr;
2292+
return getEndCOWMutation(Context, Id);
2293+
22552294
case BuiltinValueKind::BindMemory:
22562295
if (!Types.empty()) return nullptr;
22572296
return getBindMemoryOperation(Context, Id);
@@ -2380,6 +2419,10 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
23802419
if (!Types.empty())
23812420
return nullptr;
23822421
return getValueToBridgeObject(Context, Id);
2422+
2423+
case BuiltinValueKind::COWBufferForReading:
2424+
return getCOWBufferForReading(Context, Id);
2425+
23832426
case BuiltinValueKind::UnsafeGuaranteed:
23842427
return getUnsafeGuaranteed(Context, Id);
23852428

lib/IRGen/GenBuiltin.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
122122
Identifier FnId, SILType resultType,
123123
Explosion &args, Explosion &out,
124124
SubstitutionMap substitutions) {
125+
if (Builtin.ID == BuiltinValueKind::COWBufferForReading) {
126+
// Just forward the incoming argument.
127+
assert(args.size() == 1 && "Expecting one incoming argument");
128+
out = std::move(args);
129+
return;
130+
}
131+
125132
if (Builtin.ID == BuiltinValueKind::UnsafeGuaranteedEnd) {
126133
// Just consume the incoming argument.
127134
assert(args.size() == 1 && "Expecting one incoming argument");

lib/SIL/IR/OperandOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,7 @@ ANY_OWNERSHIP_BUILTIN(TypePtrAuthDiscriminator)
10231023
ValueOwnershipKind::OWNERSHIP, \
10241024
UseLifetimeConstraint::USE_LIFETIME_CONSTRAINT); \
10251025
}
1026+
CONSTANT_OWNERSHIP_BUILTIN(Owned, MustBeInvalidated, COWBufferForReading)
10261027
CONSTANT_OWNERSHIP_BUILTIN(Owned, MustBeInvalidated, UnsafeGuaranteed)
10271028
#undef CONSTANT_OWNERSHIP_BUILTIN
10281029

lib/SIL/IR/ValueOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ struct ValueOwnershipKindBuiltinVisitor
392392
}
393393
// This returns a value at +1 that is destroyed strictly /after/ the
394394
// UnsafeGuaranteedEnd. This provides the guarantee that we want.
395+
CONSTANT_OWNERSHIP_BUILTIN(Owned, COWBufferForReading)
395396
CONSTANT_OWNERSHIP_BUILTIN(Owned, UnsafeGuaranteed)
396397
CONSTANT_OWNERSHIP_BUILTIN(None, AShr)
397398
CONSTANT_OWNERSHIP_BUILTIN(None, GenericAShr)

lib/SIL/Utils/MemAccessUtils.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ static void visitBuiltinAddress(BuiltinInst *builtin,
528528
case BuiltinValueKind::Unreachable:
529529
case BuiltinValueKind::CondUnreachable:
530530
case BuiltinValueKind::DestroyArray:
531+
case BuiltinValueKind::COWBufferForReading:
531532
case BuiltinValueKind::UnsafeGuaranteed:
532533
case BuiltinValueKind::UnsafeGuaranteedEnd:
533534
case BuiltinValueKind::Swift3ImplicitObjCEntrypoint:

lib/SILGen/SILGenBuiltin.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,60 @@ emitBuiltinIsUnique_native(SILGenFunction &SGF,
901901
return ManagedValue::forUnmanaged(result);
902902
}
903903

904+
static ManagedValue
905+
emitBuiltinBeginCOWMutation(SILGenFunction &SGF,
906+
SILLocation loc,
907+
SubstitutionMap subs,
908+
ArrayRef<ManagedValue> args,
909+
SGFContext C) {
910+
911+
assert(subs.getReplacementTypes().size() == 1 &&
912+
"BeginCOWMutation should have one sub.");
913+
assert(args.size() == 1 && "isUnique_native should have one arg.");
914+
915+
SILValue refAddr = args[0].getValue();
916+
auto *ref = SGF.B.createLoad(loc, refAddr, LoadOwnershipQualifier::Take);
917+
BeginCOWMutationInst *beginCOW = SGF.B.createBeginCOWMutation(loc, ref, /*isNative*/ false);
918+
SGF.B.createStore(loc, beginCOW->getBufferResult(), refAddr, StoreOwnershipQualifier::Init);
919+
return ManagedValue::forUnmanaged(beginCOW->getUniquenessResult());
920+
}
921+
922+
static ManagedValue
923+
emitBuiltinBeginCOWMutation_native(SILGenFunction &SGF,
924+
SILLocation loc,
925+
SubstitutionMap subs,
926+
ArrayRef<ManagedValue> args,
927+
SGFContext C) {
928+
929+
assert(subs.getReplacementTypes().size() == 1 &&
930+
"BeginCOWMutation should have one sub.");
931+
assert(args.size() == 1 && "isUnique_native should have one arg.");
932+
933+
SILValue refAddr = args[0].getValue();
934+
auto *ref = SGF.B.createLoad(loc, refAddr, LoadOwnershipQualifier::Take);
935+
BeginCOWMutationInst *beginCOW = SGF.B.createBeginCOWMutation(loc, ref, /*isNative*/ true);
936+
SGF.B.createStore(loc, beginCOW->getBufferResult(), refAddr, StoreOwnershipQualifier::Init);
937+
return ManagedValue::forUnmanaged(beginCOW->getUniquenessResult());
938+
}
939+
940+
static ManagedValue
941+
emitBuiltinEndCOWMutation(SILGenFunction &SGF,
942+
SILLocation loc,
943+
SubstitutionMap subs,
944+
ArrayRef<ManagedValue> args,
945+
SGFContext C) {
946+
947+
assert(subs.getReplacementTypes().size() == 1 &&
948+
"EndCOWMutation should have one sub.");
949+
assert(args.size() == 1 && "isUnique_native should have one arg.");
950+
951+
SILValue refAddr = args[0].getValue();
952+
auto ref = SGF.B.createLoad(loc, refAddr, LoadOwnershipQualifier::Take);
953+
auto endRef = SGF.B.createEndCOWMutation(loc, ref);
954+
SGF.B.createStore(loc, endRef, refAddr, StoreOwnershipQualifier::Init);
955+
return ManagedValue::forUnmanaged(SGF.emitEmptyTuple(loc));
956+
}
957+
904958
static ManagedValue emitBuiltinBindMemory(SILGenFunction &SGF,
905959
SILLocation loc,
906960
SubstitutionMap subs,

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ class SILCombiner :
225225
// Optimize the "isConcrete" builtin.
226226
SILInstruction *optimizeBuiltinIsConcrete(BuiltinInst *I);
227227

228+
SILInstruction *optimizeBuiltinCOWBufferForReading(BuiltinInst *BI);
229+
228230
// Optimize the "trunc_N1_M2" builtin. if N1 is a result of "zext_M1_*" and
229231
// the following holds true: N1 > M1 and M2>= M1
230232
SILInstruction *optimizeBuiltinTruncOrBitCast(BuiltinInst *I);

lib/SILOptimizer/SILCombiner/SILCombinerBuiltinVisitors.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,49 @@ SILInstruction *SILCombiner::optimizeBuiltinIsConcrete(BuiltinInst *BI) {
109109
return Builder.createIntegerLiteral(BI->getLoc(), BI->getType(), 1);
110110
}
111111

112+
/// Replace
113+
/// \code
114+
/// %b = builtin "COWBufferForReading" %r
115+
/// %a = ref_element_addr %b
116+
/// \endcode
117+
/// with
118+
/// \code
119+
/// %a = ref_element_addr [immutable] %r
120+
/// \endcode
121+
/// The same for ref_tail_addr.
122+
SILInstruction *SILCombiner::optimizeBuiltinCOWBufferForReading(BuiltinInst *BI) {
123+
auto useIter = BI->use_begin();
124+
while (useIter != BI->use_end()) {
125+
auto nextIter = std::next(useIter);
126+
SILInstruction *user = useIter->getUser();
127+
SILValue ref = BI->getOperand(0);
128+
switch (user->getKind()) {
129+
case SILInstructionKind::RefElementAddrInst: {
130+
auto *REAI = cast<RefElementAddrInst>(user);
131+
REAI->setOperand(ref);
132+
REAI->setImmutable();
133+
break;
134+
}
135+
case SILInstructionKind::RefTailAddrInst: {
136+
auto *RTAI = cast<RefTailAddrInst>(user);
137+
RTAI->setOperand(ref);
138+
RTAI->setImmutable();
139+
break;
140+
}
141+
case SILInstructionKind::StrongReleaseInst:
142+
cast<StrongReleaseInst>(user)->setOperand(ref);
143+
break;
144+
default:
145+
break;
146+
}
147+
useIter = nextIter;
148+
}
149+
// If there are unknown users, keep the builtin, and IRGen will handle it.
150+
if (BI->use_empty())
151+
return eraseInstFromFunction(*BI);
152+
return nullptr;
153+
}
154+
112155
static unsigned getTypeWidth(SILType Ty) {
113156
if (auto BuiltinIntTy = Ty.getAs<BuiltinIntegerType>()) {
114157
if (BuiltinIntTy->isFixedWidth()) {
@@ -541,6 +584,8 @@ SILInstruction *SILCombiner::visitBuiltinInst(BuiltinInst *I) {
541584
return optimizeBuiltinCanBeObjCClass(I);
542585
if (I->getBuiltinInfo().ID == BuiltinValueKind::IsConcrete)
543586
return optimizeBuiltinIsConcrete(I);
587+
if (I->getBuiltinInfo().ID == BuiltinValueKind::COWBufferForReading)
588+
return optimizeBuiltinCOWBufferForReading(I);
544589
if (I->getBuiltinInfo().ID == BuiltinValueKind::TakeArrayFrontToBack ||
545590
I->getBuiltinInfo().ID == BuiltinValueKind::TakeArrayBackToFront ||
546591
I->getBuiltinInfo().ID == BuiltinValueKind::TakeArrayNoAlias ||

lib/SILOptimizer/Transforms/AccessEnforcementReleaseSinking.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ static bool isBarrier(SILInstruction *inst) {
138138
case BuiltinValueKind::PoundAssert:
139139
case BuiltinValueKind::TypePtrAuthDiscriminator:
140140
case BuiltinValueKind::GlobalStringTablePointer:
141+
case BuiltinValueKind::COWBufferForReading:
141142
return false;
142143

143144
// Handle some rare builtins that may be sensitive to object lifetime

test/IRGen/builtins.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,12 @@ func isUniqueIUO(_ ref: inout Builtin.NativeObject?) -> Bool {
683683
return Builtin.isUnique(&iuo)
684684
}
685685

686+
// CHECK-LABEL: define hidden {{.*}} @"$s8builtins19COWBufferForReadingyAA1CCADnF"
687+
// CHECK: ret %T8builtins1CC* %0
688+
func COWBufferForReading(_ ref: __owned C) -> C {
689+
return Builtin.COWBufferForReading(ref)
690+
}
691+
686692
// CHECK-LABEL: define {{.*}} @{{.*}}generic_ispod_test
687693
func generic_ispod_test<T>(_: T) {
688694
// CHECK: [[T0:%.*]] = getelementptr inbounds %swift.vwtable, %swift.vwtable* [[T:%.*]], i32 10

test/SILGen/builtins.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,32 @@ func castBitPatternFromBridgeObject(_ bo: Builtin.BridgeObject) -> Builtin.Word
600600
return Builtin.castBitPatternFromBridgeObject(bo)
601601
}
602602

603+
// CHECK-LABEL: sil hidden [ossa] @$s8builtins16beginCOWMutationySbAA1CCzF
604+
// CHECK: [[L:%.*]] = load [take] [[ADDR:%[0-9]*]]
605+
// CHECK: ([[U:%.*]], [[B:%.*]]) = begin_cow_mutation [[L]]
606+
// CHECK: store [[B]] to [init] [[ADDR]]
607+
// CHECK: apply {{%[0-9]*}}([[U]]
608+
func beginCOWMutation(_ c: inout C) -> Bool {
609+
return Bool(_builtinBooleanLiteral: Builtin.beginCOWMutation(&c))
610+
}
611+
612+
// CHECK-LABEL: sil hidden [ossa] @$s8builtins23beginCOWMutation_nativeySbAA1CCzF
613+
// CHECK: [[L:%.*]] = load [take] [[ADDR:%[0-9]*]]
614+
// CHECK: ([[U:%.*]], [[B:%.*]]) = begin_cow_mutation [native] [[L]]
615+
// CHECK: store [[B]] to [init] [[ADDR]]
616+
// CHECK: apply {{%[0-9]*}}([[U]]
617+
func beginCOWMutation_native(_ c: inout C) -> Bool {
618+
return Bool(_builtinBooleanLiteral: Builtin.beginCOWMutation_native(&c))
619+
}
620+
621+
// CHECK-LABEL: sil hidden [ossa] @$s8builtins14endCOWMutationyyAA1CCzF
622+
// CHECK: [[L:%.*]] = load [take] [[ADDR:%[0-9]*]]
623+
// CHECK: [[B:%.*]] = end_cow_mutation [[L]]
624+
// CHECK: store [[B]] to [init] [[ADDR]]
625+
func endCOWMutation(_ c: inout C) {
626+
Builtin.endCOWMutation(&c)
627+
}
628+
603629
// ----------------------------------------------------------------------------
604630
// isUnique variants
605631
// ----------------------------------------------------------------------------

0 commit comments

Comments
 (0)