Skip to content

Commit 86365cd

Browse files
authored
Merge pull request #69476 from atrick/fix-conversion-lifetime
Fix SILGen to emit fix_lifetime for inout-to-pointer conversion.
2 parents 7e7eb60 + 966df35 commit 86365cd

File tree

6 files changed

+219
-5
lines changed

6 files changed

+219
-5
lines changed

lib/SILGen/LValue.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,25 @@ struct LLVM_LIBRARY_VISIBILITY UnenforcedFormalAccess : FormalAccess {
633633
void finishImpl(SILGenFunction &SGF) override;
634634
};
635635

636+
// A formal access that keeps an LValue alive across an expression that uses an
637+
// unsafe pointer into that LValue. This supports emitLValueToPointer, which
638+
// handles InoutToPointerExpr. This formal access is nested within whatever
639+
// formal access is needed for the LValue itself and emits a fix_lifetime
640+
// instruction after the apply.
641+
struct LLVM_LIBRARY_VISIBILITY LValueToPointerFormalAccess : FormalAccess {
642+
static SILValue enter(SILGenFunction &SGF, SILLocation loc, SILValue address);
643+
644+
SILValue address;
645+
646+
LValueToPointerFormalAccess(SILLocation loc, SILValue address,
647+
CleanupHandle cleanup)
648+
: FormalAccess(sizeof(*this), FormalAccess::Unenforced, loc, cleanup),
649+
address(address) {}
650+
651+
// Only called at the end formal evaluation scope. Emit fix_lifetime.
652+
void finishImpl(SILGenFunction &SGF) override;
653+
};
654+
636655
} // namespace Lowering
637656
} // namespace swift
638657

lib/SILGen/SILGenExpr.cpp

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5934,6 +5934,67 @@ static void diagnoseImplicitRawConversion(Type sourceTy, Type pointerTy,
59345934
}
59355935
}
59365936

5937+
namespace {
5938+
/// Cleanup to insert fix_lifetime on an LValue address.
5939+
class FixLifetimeLValueCleanup : public Cleanup {
5940+
friend LValueToPointerFormalAccess;
5941+
5942+
FormalEvaluationContext::stable_iterator depth;
5943+
5944+
public:
5945+
FixLifetimeLValueCleanup() : depth() {}
5946+
5947+
LValueToPointerFormalAccess &getFormalAccess(SILGenFunction &SGF) const {
5948+
auto &access = *SGF.FormalEvalContext.find(depth);
5949+
return static_cast<LValueToPointerFormalAccess &>(access);
5950+
}
5951+
5952+
void emit(SILGenFunction &SGF, CleanupLocation l,
5953+
ForUnwind_t forUnwind) override {
5954+
getFormalAccess(SGF).finish(SGF);
5955+
}
5956+
5957+
SILValue getAddress(SILGenFunction &SGF) const {
5958+
return getFormalAccess(SGF).address;
5959+
}
5960+
5961+
void dump(SILGenFunction &SGF) const override {
5962+
#ifndef NDEBUG
5963+
llvm::errs() << "FixLifetimeLValueCleanup "
5964+
<< "State:" << getState() << " "
5965+
<< "Address: " << getAddress(SGF) << "\n";
5966+
#endif
5967+
}
5968+
};
5969+
} // end anonymous namespace
5970+
5971+
SILValue LValueToPointerFormalAccess::enter(SILGenFunction &SGF,
5972+
SILLocation loc,
5973+
SILValue address) {
5974+
auto &lowering = SGF.getTypeLowering(address->getType().getObjectType());
5975+
SILValue pointer = SGF.B.createAddressToPointer(
5976+
loc, address, SILType::getRawPointerType(SGF.getASTContext()),
5977+
/*needsStackProtection=*/ true);
5978+
if (!lowering.isTrivial()) {
5979+
assert(SGF.isInFormalEvaluationScope() &&
5980+
"Must be in formal evaluation scope");
5981+
auto &cleanup = SGF.Cleanups.pushCleanup<FixLifetimeLValueCleanup>();
5982+
CleanupHandle handle = SGF.Cleanups.getTopCleanup();
5983+
SGF.FormalEvalContext.push<LValueToPointerFormalAccess>(loc, address,
5984+
handle);
5985+
cleanup.depth = SGF.FormalEvalContext.stable_begin();
5986+
}
5987+
return pointer;
5988+
}
5989+
5990+
// Address-to-pointer conversion always requires either a fix_lifetime or
5991+
// mark_dependence. Emitting a fix_lifetime immediately after the call as
5992+
// opposed to a mark_dependence allows the lvalue's lifetime to be optimized
5993+
// outside of this narrow scope.
5994+
void LValueToPointerFormalAccess::finishImpl(SILGenFunction &SGF) {
5995+
SGF.B.emitFixLifetime(loc, address);
5996+
}
5997+
59375998
/// Convert an l-value to a pointer type: unsafe, unsafe-mutable, or
59385999
/// autoreleasing-unsafe-mutable.
59396000
ManagedValue SILGenFunction::emitLValueToPointer(SILLocation loc, LValue &&lv,
@@ -5979,9 +6040,8 @@ ManagedValue SILGenFunction::emitLValueToPointer(SILLocation loc, LValue &&lv,
59796040
// Get the lvalue address as a raw pointer.
59806041
SILValue address =
59816042
emitAddressOfLValue(loc, std::move(lv)).getUnmanagedValue();
5982-
address = B.createAddressToPointer(loc, address,
5983-
SILType::getRawPointerType(getASTContext()),
5984-
/*needsStackProtection=*/ true);
6043+
6044+
SILValue pointer = LValueToPointerFormalAccess::enter(*this, loc, address);
59856045

59866046
// Disable nested writeback scopes for any calls evaluated during the
59876047
// conversion intrinsic.
@@ -5996,7 +6056,7 @@ ManagedValue SILGenFunction::emitLValueToPointer(SILLocation loc, LValue &&lv,
59966056
getPointerProtocol());
59976057
return emitApplyOfLibraryIntrinsic(
59986058
loc, converter, subMap,
5999-
ManagedValue::forObjectRValueWithoutOwnership(address),
6059+
ManagedValue::forObjectRValueWithoutOwnership(pointer),
60006060
SGFContext())
60016061
.getAsSingleValue(*this, loc);
60026062
}

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2448,6 +2448,17 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
24482448
return true;
24492449
}
24502450

2451+
if (auto *fixLifetime = dyn_cast<FixLifetimeInst>(op->getUser())) {
2452+
auto leafRange = TypeTreeLeafTypeRange::get(op->get(), getRootAddress());
2453+
if (!leafRange) {
2454+
LLVM_DEBUG(llvm::dbgs() << "Failed to compute leaf range!\n");
2455+
return false;
2456+
}
2457+
2458+
useState.recordLivenessUse(user, *leafRange);
2459+
return true;
2460+
}
2461+
24512462
// If we don't fit into any of those categories, just track as a liveness
24522463
// use. We assume all such uses must only be reads to the memory. So we assert
24532464
// to be careful.

lib/SILOptimizer/Mandatory/MoveOnlyWrappedTypeEliminator.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ struct SILMoveOnlyWrappedTypeEliminatorVisitor
199199
NO_UPDATE_NEEDED(BeginAccess)
200200
NO_UPDATE_NEEDED(EndAccess)
201201
NO_UPDATE_NEEDED(ClassMethod)
202+
NO_UPDATE_NEEDED(FixLifetime)
203+
NO_UPDATE_NEEDED(AddressToPointer)
202204
#undef NO_UPDATE_NEEDED
203205

204206
bool eliminateIdentityCast(SingleValueInstruction *svi) {

test/SILGen/pointer_conversion.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
// RUN: %target-swift-emit-silgen -module-name pointer_conversion -sdk %S/Inputs -I %S/Inputs -enable-source-import %s -enable-objc-interop | %FileCheck %s
32

43
// FIXME: rdar://problem/19648117 Needs splitting objc parts out
@@ -22,6 +21,9 @@ func takesOptConstRawPointer(_ x: UnsafeRawPointer?, and: Int) {}
2221
func takesOptOptConstRawPointer(_ x: UnsafeRawPointer??, and: Int) {}
2322
func takesMutableFunctionPointer(_ x: UnsafeMutablePointer<() -> Void>) {}
2423

24+
@_silgen_name("takeObjectPointer")
25+
func takeObjectPointer(_: UnsafePointer<AnyObject>)
26+
2527
// CHECK-LABEL: sil hidden [ossa] @$s18pointer_conversion0A9ToPointeryySpySiG_SPySiGSvtF
2628
// CHECK: bb0([[MP:%.*]] : $UnsafeMutablePointer<Int>, [[CP:%.*]] : $UnsafePointer<Int>, [[MRP:%.*]] : $UnsafeMutableRawPointer):
2729
func pointerToPointer(_ mp: UnsafeMutablePointer<Int>,
@@ -449,3 +451,31 @@ func optOptStringToOptOptPointer(string: String??) {
449451
// CHECK: br [[SOME_CONT_BB]]([[NO_VALUE]] : $Optional<Optional<UnsafeRawPointer>>, [[NO_OWNER]] : $Optional<AnyObject>)
450452
takesOptOptConstRawPointer(string, and: sideEffect1())
451453
}
454+
455+
final public class Obj {
456+
var object: AnyObject
457+
458+
init(object: AnyObject) { self.object = object }
459+
}
460+
461+
public struct RefObj {
462+
var o: Obj
463+
}
464+
465+
// CHECK-LABEL: sil [ossa] @$s18pointer_conversion20objectFieldToPointer2rcyAA6RefObjV_tF : $@convention(thin) (@guaranteed RefObj) -> () {
466+
// CHECK: bb0(%0 : @guaranteed $RefObj):
467+
// CHECK: [[B:%.*]] = begin_borrow %{{.*}} : $Obj
468+
// CHECK: [[R:%.*]] = ref_element_addr [[B]] : $Obj, #Obj.object
469+
// CHECK: [[A:%.*]] = begin_access [read] [dynamic] [[R]] : $*AnyObject
470+
// CHECK: [[P:%.*]] = address_to_pointer [stack_protection] [[A]] : $*AnyObject to $Builtin.RawPointer
471+
// CHECK: apply %{{.*}}<UnsafePointer<AnyObject>>(%{{.*}}, %7) : $@convention(thin) <τ_0_0 where τ_0_0 : _Pointer> (Builtin.RawPointer) -> @out τ_0_0
472+
// CHECK: apply {{.*}} : $@convention(thin) (UnsafePointer<AnyObject>) -> ()
473+
// CHECK: fix_lifetime [[A]] : $*AnyObject
474+
// CHECK: end_access [[A]] : $*AnyObject
475+
// CHECK: end_borrow [[B]] : $Obj
476+
// CHECK: destroy_value %{{.*}} : $Obj
477+
// CHECK: dealloc_stack %{{.*}} : $*UnsafePointer<AnyObject>
478+
// CHECK-LABEL: } // end sil function '$s18pointer_conversion20objectFieldToPointer2rcyAA6RefObjV_tF'
479+
public func objectFieldToPointer(rc: RefObj) {
480+
takeObjectPointer(&rc.o.object)
481+
}

test/SILOptimizer/pointer_conversion.swift

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,95 @@ public func testWithMemoryRebound<T, Result>(
118118
}
119119
return try body(.init(rawPtr))
120120
}
121+
122+
@_silgen_name("takeRawPointer")
123+
func takeRawPointer(_: UnsafeRawPointer)
124+
125+
@_silgen_name("takeObjectPointer")
126+
func takeObjectPointer(_: UnsafePointer<AnyObject>)
127+
128+
@_silgen_name("takeStringPointer")
129+
func takeStringPointer(_: UnsafePointer<String>)
130+
131+
@_silgen_name("takeDictionaryPointer")
132+
func takeDictionaryPointer(_: UnsafePointer<Dictionary<Int, Int>>)
133+
134+
// Test conversion of a dictionary to a raw pointer. This is not a sane
135+
// conversion, but the compiler must still generate memory-safe code.
136+
//
137+
// A dictionary is an eager-move type, so it will be destroyed at its
138+
// last use. An address_to_pointer operation escapes the pointer, so
139+
// the compiler never sees those pointer uses. The pointer's scope
140+
// must be protected by a fix_lifetime.
141+
//
142+
// NOTE: If this test triggers a compiler error because of the unsafe
143+
// inout conversion, then we have arrived at a better world. Delete
144+
// the test. The eagerMoveToPointer test below is sufficient.
145+
//
146+
// CHECK-LABEL: sil [stack_protection] @$s18pointer_conversion22dictionaryToRawPointeryyF : $@convention(thin) () -> () {
147+
// CHECK: [[A:%.*]] = alloc_stack $Dictionary<Int, Int>, var, name "d"
148+
// CHECK: [[PTR:%.*]] = address_to_pointer [stack_protection] [[A]] : $*Dictionary<Int, Int> to $Builtin.RawPointer
149+
// CHECK: [[UP:%.*]] = struct $UnsafeRawPointer ([[PTR]] : $Builtin.RawPointer)
150+
// CHECK: apply %{{.*}}([[UP]]) : $@convention(thin) (UnsafeRawPointer) -> ()
151+
// CHECK: [[D:%.*]] = load %0 : $*Dictionary<Int, Int>
152+
// CHECK: fix_lifetime [[D]] : $Dictionary<Int, Int>
153+
// CHECK: release_value [[D]] : $Dictionary<Int, Int>
154+
// CHECK: dealloc_stack [[A]] : $*Dictionary<Int, Int>
155+
// CHECK-LABEL: } // end sil function '$s18pointer_conversion22dictionaryToRawPointeryyF'
156+
public func dictionaryToRawPointer() {
157+
var d = [1:1]
158+
takeRawPointer(&d)
159+
}
160+
161+
// Test conversion of a non-trivial eager-move type to a raw pointer.
162+
// This currently only applies to Dictionary, but converting a
163+
// dictionary to a pointer will likely be a compiler error in the
164+
// future. So force an eagerMove type here.
165+
//
166+
// An eager-move type will be destroyed at its last use. An
167+
// address_to_pointer operation escapes the pointer, so the compiler
168+
// never sees those pointer uses. The pointer's scope must be
169+
// protected by a fix_lifetime.
170+
// CHECK-LABEL: sil [stack_protection] @$s18pointer_conversion18eagerMoveToPointer1oyyXln_tF : $@convention(thin) (@owned AnyObject) -> () {
171+
// CHECK: [[A:%.*]] = alloc_stack [moveable_value_debuginfo] $AnyObject, var, name "o"
172+
// CHECK: [[PTR:%.*]] = address_to_pointer [stack_protection] [[A]] : $*AnyObject to $Builtin.RawPointer
173+
// CHECK: [[UP:%.*]] = struct $UnsafePointer<AnyObject> ([[PTR]] : $Builtin.RawPointer)
174+
// CHECK: apply %{{.*}}([[UP]]) : $@convention(thin) (UnsafePointer<AnyObject>) -> ()
175+
// CHECK: [[O:%.*]] = load [[A]] : $*AnyObject
176+
// CHECK: fix_lifetime [[O]] : $AnyObject
177+
// CHECK: strong_release [[O]] : $AnyObject
178+
// CHECK: dealloc_stack [[A]] : $*AnyObject
179+
// CHECK-LABEL: } // end sil function '$s18pointer_conversion18eagerMoveToPointer1oyyXln_tF'
180+
public func eagerMoveToPointer(@_eagerMove o: consuming AnyObject ) {
181+
takeObjectPointer(&o)
182+
}
183+
184+
// CHECK-LABEL: sil [stack_protection] @$s18pointer_conversion15stringToPointer2ssySS_tF : $@convention(thin) (@guaranteed String) -> () {
185+
// CHECK: [[A:%.*]] = alloc_stack $String, var, name "s"
186+
// CHECK: [[PTR:%.*]] = address_to_pointer [stack_protection] [[A]] : $*String to $Builtin.RawPointer
187+
// CHECK: [[UP:%.*]] = struct $UnsafePointer<String> ([[PTR]] : $Builtin.RawPointer)
188+
// CHECK: apply {{.*}}([[UP]]) : $@convention(thin) (UnsafePointer<String>) -> ()
189+
// CHECK: [[S:%.*]] = load [[A]] : $*String
190+
// CHECK: fix_lifetime [[S]] : $String
191+
// CHECK: release_value [[S]] : $String
192+
// CHECK: dealloc_stack [[A]] : $*String
193+
// CHECK-LABEL: } // end sil function '$s18pointer_conversion15stringToPointer2ssySS_tF'
194+
public func stringToPointer(ss: String) {
195+
var s = ss
196+
takeStringPointer(&s)
197+
}
198+
199+
// CHECK-LABEL: sil [stack_protection] @$s18pointer_conversion19dictionaryToPointer2ddySDyS2iG_tF : $@convention(thin) (@guaranteed Dictionary<Int, Int>) -> () {
200+
// CHECK: [[A:%.*]] = alloc_stack $Dictionary<Int, Int>, var, name "d"
201+
// CHECK: [[PTR:%.*]] = address_to_pointer [stack_protection] [[A]] : $*Dictionary<Int, Int> to $Builtin.RawPointer
202+
// CHECK: [[UP:%.*]] = struct $UnsafePointer<Dictionary<Int, Int>> ([[PTR]] : $Builtin.RawPointer)
203+
// CHECK: apply %{{.*}}([[UP]]) : $@convention(thin) (UnsafePointer<Dictionary<Int, Int>>) -> ()
204+
// CHECK: [[D:%.*]] = load [[A]] : $*Dictionary<Int, Int>
205+
// CHECK: fix_lifetime [[D]] : $Dictionary<Int, Int>
206+
// CHECK: release_value [[D]] : $Dictionary<Int, Int>
207+
// CHECK: dealloc_stack [[A]] : $*Dictionary<Int, Int>
208+
// CHECK-LABEL: } // end sil function '$s18pointer_conversion19dictionaryToPointer2ddySDyS2iG_tF'
209+
public func dictionaryToPointer(dd: Dictionary<Int, Int>) {
210+
var d = dd
211+
takeDictionaryPointer(&d)
212+
}

0 commit comments

Comments
 (0)