Skip to content

Commit 1137c00

Browse files
committed
[builtin] Add a new SIL builtin convertStrongToUnownedUnsafe()
The signature is: (T, @inout @unowned(unsafe) Optional<T>) -> () The reason for the weird signature is that currently the Builtin infrastructure does not handle results well. The semantics of this builtin is that it enables one to store the first argument into an unowned unsafe address without any reference counting operations. It does this just by SILGening the relevant code. The optimizer chews through this code well, so we get the expected behavior. I also included a small proof of concept to validate that this builtin works as expected.
1 parent 8841d6d commit 1137c00

File tree

7 files changed

+219
-2
lines changed

7 files changed

+219
-2
lines changed

include/swift/AST/Builtins.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,12 @@ BUILTIN_SIL_OPERATION(AllocWithTailElems, "allocWithTailElems", Special)
444444
/// Projects the first tail-allocated element of type E from a class C.
445445
BUILTIN_SIL_OPERATION(ProjectTailElems, "projectTailElems", Special)
446446

447+
/// Unsafely convert a value of type T to an unowned value.
448+
///
449+
/// It has type (T, @inout @unowned(unsafe) T) -> (). The reason for the weird
450+
/// signature is to work around issues with results in SILGen builtin emission.
451+
BUILTIN_SIL_OPERATION(ConvertStrongToUnownedUnsafe, "convertStrongToUnownedUnsafe", Special)
452+
447453
#undef BUILTIN_SIL_OPERATION
448454

449455
// BUILTIN_RUNTIME_CALL - A call into a runtime function.

include/swift/SIL/SILType.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,24 @@ class SILType {
459459
return SILType(getASTType().getReferenceStorageReferent(), getCategory());
460460
}
461461

462+
/// Return the reference ownership of this type if it is a reference storage
463+
/// type. Otherwse, return None.
464+
Optional<ReferenceOwnership> getReferenceStorageOwnership() const {
465+
auto type = getASTType()->getAs<ReferenceStorageType>();
466+
if (!type)
467+
return None;
468+
return type->getOwnership();
469+
}
470+
471+
/// Attempt to wrap the passed in type as a type with reference ownership \p
472+
/// ownership. For simplicity, we always return an address since reference
473+
/// storage types may not be loadable (e.x.: weak ownership).
474+
SILType getReferenceStorageType(const ASTContext &ctx,
475+
ReferenceOwnership ownership) const {
476+
auto *type = ReferenceStorageType::get(getASTType(), ownership, ctx);
477+
return SILType::getPrimitiveAddressType(type->getCanonicalType());
478+
}
479+
462480
/// Transform the function type SILType by replacing all of its interface
463481
/// generic args with the appropriate item from the substitution.
464482
///

lib/AST/Builtins.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,22 @@ static ValueDecl *getGlobalStringTablePointer(ASTContext &Context,
940940
return getBuiltinFunction(Id, {stringType}, Context.TheRawPointerType);
941941
}
942942

943+
static ValueDecl *getConvertStrongToUnownedUnsafe(ASTContext &ctx,
944+
Identifier id) {
945+
// We actually want this:
946+
//
947+
// (T, inout unowned (unsafe) T) -> ()
948+
//
949+
// But for simplicity, we actually accept T, U and do the checking
950+
// in the emission method that everything works up. This is a
951+
// builtin, so we can crash.
952+
BuiltinFunctionBuilder builder(ctx, 2);
953+
builder.addParameter(makeGenericParam(0));
954+
builder.addParameter(makeGenericParam(1), ValueOwnership::InOut);
955+
builder.setResult(makeConcrete(TupleType::getEmpty(ctx)));
956+
return builder.build(id);
957+
}
958+
943959
static ValueDecl *getPoundAssert(ASTContext &Context, Identifier Id) {
944960
auto int1Type = BuiltinIntegerType::get(1, Context);
945961
auto optionalRawPointerType = BoundGenericEnumType::get(
@@ -1996,6 +2012,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
19962012
case BuiltinValueKind::GlobalStringTablePointer:
19972013
return getGlobalStringTablePointer(Context, Id);
19982014

2015+
case BuiltinValueKind::ConvertStrongToUnownedUnsafe:
2016+
return getConvertStrongToUnownedUnsafe(Context, Id);
2017+
19992018
case BuiltinValueKind::PoundAssert:
20002019
return getPoundAssert(Context, Id);
20012020

lib/SIL/ValueOwnership.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,6 @@ UNOWNED_OR_NONE_DEPENDING_ON_RESULT(ZeroInitializer)
547547
BuiltinInst *BI, StringRef Attr) { \
548548
llvm_unreachable("builtin should have been lowered in SILGen"); \
549549
}
550-
551550
#include "swift/AST/Builtins.def"
552551

553552
ValueOwnershipKind

lib/SILGen/LValue.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,16 @@ class LValue {
405405
assert(&component == Path.back().get());
406406
Path.pop_back();
407407
}
408-
408+
409+
/// Pop the last component off this LValue unsafely. Validates that the
410+
/// component is of kind \p kind as a sanity check.
411+
///
412+
/// Please be careful when using this!
413+
void unsafelyDropLastComponent(PathComponent::KindTy kind) & {
414+
assert(kind == Path.back()->getKind());
415+
Path.pop_back();
416+
}
417+
409418
/// Add a new component at the end of the access path of this lvalue.
410419
template <class T, class... As>
411420
void add(As &&... args) {

lib/SILGen/SILGenBuiltin.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,70 @@ emitBuiltinGlobalStringTablePointer(SILGenFunction &SGF, SILLocation loc,
10451045
return SGF.emitManagedRValueWithCleanup(resultVal);
10461046
}
10471047

1048+
/// Emit SIL for the named builtin:
1049+
/// convertStrongToUnownedUnsafe. Unlike the default ownership
1050+
/// convention for named builtins, which is to take (non-trivial)
1051+
/// arguments as Owned, this builtin accepts owned as well as
1052+
/// guaranteed arguments, and hence doesn't require the arguments to
1053+
/// be at +1. Therefore, this builtin is emitted specially.
1054+
///
1055+
/// We assume our convention is (T, @inout @unmanaged Optional<T>) -> ()
1056+
static ManagedValue emitBuiltinConvertStrongToUnownedUnsafe(
1057+
SILGenFunction &SGF, SILLocation loc, SubstitutionMap subs,
1058+
PreparedArguments &&preparedArgs, SGFContext C) {
1059+
auto argsOrError = decomposeArguments(SGF, loc, std::move(preparedArgs), 2);
1060+
if (!argsOrError)
1061+
return ManagedValue::forUnmanaged(SGF.emitEmptyTuple(loc));
1062+
1063+
auto args = *argsOrError;
1064+
1065+
// First get our object at +0 if we can.
1066+
auto object = SGF.emitRValue(args[0], SGFContext::AllowGuaranteedPlusZero)
1067+
.getAsSingleValue(SGF, args[0]);
1068+
1069+
// Borrow it and get the value.
1070+
SILValue objectSrcValue = object.borrow(SGF, loc).getValue();
1071+
1072+
// Then create our inout.
1073+
auto inout = cast<InOutExpr>(args[1]->getSemanticsProvidingExpr());
1074+
auto lv =
1075+
SGF.emitLValue(inout->getSubExpr(), SGFAccessKind::BorrowedAddressRead);
1076+
lv.unsafelyDropLastComponent(PathComponent::OwnershipKind);
1077+
if (!lv.isPhysical() || !lv.isLoadingPure()) {
1078+
llvm::report_fatal_error("Builtin.convertStrongToUnownedUnsafe passed "
1079+
"non-physical, non-pure lvalue as 2nd arg");
1080+
}
1081+
1082+
SILValue inoutDest =
1083+
SGF.emitAddressOfLValue(args[1], std::move(lv)).getLValueAddress();
1084+
SILType destType = inoutDest->getType().getObjectType();
1085+
1086+
// Make sure our types match up as we expect.
1087+
if (objectSrcValue->getType() !=
1088+
destType.getReferenceStorageReferentType().getOptionalObjectType()) {
1089+
llvm::errs()
1090+
<< "Invalid usage of Builtin.convertStrongToUnsafeUnowned. lhsType "
1091+
"must be T and rhsType must be inout unsafe(unowned) Optional<T>"
1092+
<< "lhsType: " << objectSrcValue->getType() << "\n"
1093+
<< "rhsType: " << inoutDest->getType() << "\n";
1094+
llvm::report_fatal_error("standard fatal error msg");
1095+
}
1096+
1097+
// Ok. We have the right types. First convert objectSrcValue to its
1098+
// unowned representation.
1099+
SILType optionalType = SILType::getOptionalType(objectSrcValue->getType());
1100+
SILValue someVal =
1101+
SGF.B.createOptionalSome(loc, objectSrcValue, optionalType);
1102+
1103+
SILType unmanagedOptType = someVal->getType().getReferenceStorageType(
1104+
SGF.getASTContext(), ReferenceOwnership::Unmanaged);
1105+
SILValue unownedObjectSrcValue = SGF.B.createRefToUnmanaged(
1106+
loc, someVal, unmanagedOptType.getObjectType());
1107+
SGF.B.emitStoreValueOperation(loc, unownedObjectSrcValue, inoutDest,
1108+
StoreOwnershipQualifier::Trivial);
1109+
return ManagedValue::forUnmanaged(SGF.emitEmptyTuple(loc));
1110+
}
1111+
10481112
Optional<SpecializedEmitter>
10491113
SpecializedEmitter::forDecl(SILGenModule &SGM, SILDeclRef function) {
10501114
// Only consider standalone declarations in the Builtin module.

test/SILGen/unsafevalue.swift

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// RUN: %target-swift-emit-silgen -parse-stdlib %s -disable-access-control -disable-objc-attr-requires-foundation-module -enable-objc-interop | %FileCheck %s
2+
// RUN: %target-swift-emit-sil -Onone -parse-stdlib %s -disable-access-control -disable-objc-attr-requires-foundation-module -enable-objc-interop | %FileCheck -check-prefix=CANONICAL %s
3+
// RUN: %target-swift-emit-sil -O -parse-stdlib %s -disable-access-control -disable-objc-attr-requires-foundation-module -enable-objc-interop | %FileCheck -check-prefix=OPT %s
4+
5+
import Swift
6+
7+
// Eventually element will be unconstrained, but for testing this builtin, we
8+
// should use it this way.
9+
@frozen
10+
public struct UnsafeValue<Element: AnyObject> {
11+
@usableFromInline
12+
internal unowned(unsafe) var _value: Element?
13+
14+
@_transparent
15+
@inlinable
16+
public init() {
17+
_value = nil
18+
}
19+
20+
// Create a new unmanaged value that owns the underlying value. This unmanaged
21+
// value must after use be deinitialized by calling the function deinitialize()
22+
//
23+
// This will insert a retain that the optimizer can not remove!
24+
@_transparent
25+
@inlinable
26+
public init(copying newValue: __shared Element) {
27+
self.init()
28+
_value = newValue
29+
}
30+
31+
// Create a new unmanaged value that unsafely produces a new
32+
// unmanaged value without introducing any rr traffic.
33+
//
34+
// CHECK-LABEL: sil [transparent] [serialized] [ossa] @$s11unsafevalue11UnsafeValueV14unsafelyAssignACyxGxh_tcfC : $@convention(method) <Element where Element : AnyObject> (@guaranteed Element, @thin UnsafeValue<Element>.Type) -> UnsafeValue<Element> {
35+
// CHECK: bb0([[INPUT_ELEMENT:%.*]] : @guaranteed $Element,
36+
// CHECK: [[BOX:%.*]] = alloc_box
37+
// CHECK: [[UNINIT_BOX:%.*]] = mark_uninitialized [delegatingself] [[BOX]]
38+
// CHECK: [[PROJECT_UNINIT_BOX:%.*]] = project_box [[UNINIT_BOX]]
39+
// CHECK: [[DELEGATING_INIT_RESULT:%.*]] = apply {{%.*}}<Element>(
40+
// CHECK: assign [[DELEGATING_INIT_RESULT]] to [[PROJECT_UNINIT_BOX]]
41+
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT_UNINIT_BOX]]
42+
// CHECK: [[STRUCT_ACCESS:%.*]] = struct_element_addr [[ACCESS]]
43+
// CHECK: [[OPT_INPUT_ELEMENT:%.*]] = enum $Optional<Element>, #Optional.some!enumelt.1, [[INPUT_ELEMENT]]
44+
// CHECK: [[UNMANAGED_OPT_INPUT_ELEMENT:%.*]] = ref_to_unmanaged [[OPT_INPUT_ELEMENT]]
45+
// CHECK: store [[UNMANAGED_OPT_INPUT_ELEMENT]] to [trivial] [[STRUCT_ACCESS]]
46+
// CHECK: end_access [[ACCESS]]
47+
// CHECK: [[RESULT:%.*]] = load [trivial] [[PROJECT_UNINIT_BOX]]
48+
// CHECK: destroy_value [[UNINIT_BOX]]
49+
// CHECK: return [[RESULT]]
50+
// CHECK: } // end sil function '$s11unsafevalue11UnsafeValueV14unsafelyAssignACyxGxh_tcfC'
51+
//
52+
// CANONICAL-LABEL: sil [transparent] [serialized] @$s11unsafevalue11UnsafeValueV14unsafelyAssignACyxGxh_tcfC : $@convention(method) <Element where Element : AnyObject> (@guaranteed Element, @thin UnsafeValue<Element>.Type) -> UnsafeValue<Element> {
53+
// CANONICAL: bb0([[INPUT_ELEMENT:%.*]] : $Element,
54+
// CANONICAL-NEXT: debug_value
55+
// TODO(gottesmm): release_value on a .none shouldn't exist.
56+
// CANONICAL-NEXT: [[NONE:%.*]] = enum $Optional<Element>, #Optional.none!enumelt
57+
// CANONICAL-NEXT: release_value [[NONE]]
58+
// CANONICAL-NEXT: [[ENUM:%.*]] = enum $Optional<Element>, #Optional.some!enumelt.1, [[INPUT_ELEMENT]]
59+
// CANONICAL-NEXT: [[UNMANAGED_ENUM:%.*]] = ref_to_unmanaged [[ENUM]]
60+
// CANONICAL-NEXT: [[RESULT:%.*]] = struct $UnsafeValue<Element> ([[UNMANAGED_ENUM]] : $@sil_unmanaged Optional<Element>)
61+
// CANONICAL-NEXT: tuple
62+
// CANONICAL-NEXT: return [[RESULT]]
63+
// CANONICAL: } // end sil function '$s11unsafevalue11UnsafeValueV14unsafelyAssignACyxGxh_tcfC'
64+
//
65+
// OPT-LABEL: sil [transparent] @$s11unsafevalue11UnsafeValueV14unsafelyAssignACyxGxh_tcfC : $@convention(method) <Element where Element : AnyObject> (@guaranteed Element, @thin UnsafeValue<Element>.Type) -> UnsafeValue<Element> {
66+
// OPT: bb0([[INPUT_ELEMENT:%.*]] : $Element,
67+
// OPT-NEXT: debug_value
68+
// OPT-NEXT: [[ENUM:%.*]] = enum $Optional<Element>, #Optional.some!enumelt.1, [[INPUT_ELEMENT]]
69+
// OPT-NEXT: [[UNMANAGED_ENUM:%.*]] = ref_to_unmanaged [[ENUM]]
70+
// OPT-NEXT: [[RESULT:%.*]] = struct $UnsafeValue<Element> ([[UNMANAGED_ENUM]] : $@sil_unmanaged Optional<Element>)
71+
// OPT-NEXT: return [[RESULT]]
72+
// OPT: } // end sil function '$s11unsafevalue11UnsafeValueV14unsafelyAssignACyxGxh_tcfC'
73+
@_transparent
74+
@inlinable
75+
public init(unsafelyAssign newValue: __shared Element) {
76+
self.init()
77+
Builtin.convertStrongToUnownedUnsafe(newValue, &_value)
78+
}
79+
80+
// Access the underlying value at +0, guaranteeing its lifetime by base.
81+
@_transparent
82+
@inlinable
83+
func withGuaranteeingBase<Base>(base: Base, f: (Element) -> ()) {
84+
// TODO: Once we have a builtin for mark_dependence, fill this in.
85+
}
86+
87+
// If the unmanaged value was initialized with a copy, release the underlying value.
88+
//
89+
// This will insert a release that can not be removed by the optimizer.
90+
@_transparent
91+
@inlinable
92+
mutating func deinitialize() {
93+
_value = nil
94+
}
95+
96+
// Return a new strong reference to the unmanaged value.
97+
//
98+
// This will insert a retain that can not be removed by the optimizer!
99+
@_transparent
100+
@inlinable
101+
public var strongRef: Element { _value.unsafelyUnwrapped }
102+
}

0 commit comments

Comments
 (0)