Skip to content

ABISafeConversionComponent should be a TranslationComponent #61029

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/SILGen/LValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ class PathComponent {
CoroutineAccessorKind, // coroutine accessor
ValueKind, // random base pointer as an lvalue
PhysicalKeyPathApplicationKind, // applying a key path
ABISafeConversionKind, // unchecked_addr_cast

// Logical LValue kinds
GetterSetterKind, // property or subscript getter/setter
Expand All @@ -118,6 +117,7 @@ class PathComponent {
// Translation LValue kinds (a subtype of logical)
OrigToSubstKind, // generic type substitution
SubstToOrigKind, // generic type substitution
UncheckedConversionKind, // unchecked_X_cast

FirstLogicalKind = GetterSetterKind,
FirstTranslationKind = OrigToSubstKind,
Expand Down
3 changes: 2 additions & 1 deletion lib/SILGen/SILGenBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,8 @@ ManagedValue SILGenBuilder::createUncheckedBitCast(SILLocation loc,
// updated.
assert((isa<UncheckedTrivialBitCastInst>(cast) ||
isa<UncheckedRefCastInst>(cast) ||
isa<UncheckedBitwiseCastInst>(cast)) &&
isa<UncheckedBitwiseCastInst>(cast) ||
isa<ConvertFunctionInst>(cast)) &&
"SILGenBuilder is out of sync with SILBuilder.");

// If we have a trivial inst, just return early.
Expand Down
86 changes: 72 additions & 14 deletions lib/SILGen/SILGenLValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2198,26 +2198,82 @@ namespace {
}
};

/// A physical component which performs an unchecked_addr_cast
class ABISafeConversionComponent final : public PhysicalPathComponent {
/// A translation component that performs \c unchecked_*_cast 's as-needed.
class UncheckedConversionComponent final : public TranslationPathComponent {
private:
Type OrigType;

/// \returns the type this component is trying to convert \b to
CanType getTranslatedType() const {
return getTypeData().SubstFormalType->getCanonicalType();
}

/// \returns the type this component is trying to convert \b from
CanType getUntranslatedType() const {
return OrigType->getRValueType()->getCanonicalType();
}

/// perform a conversion of ManagedValue -> ManagedValue
ManagedValue doUncheckedConversion(SILGenFunction &SGF, SILLocation loc,
ManagedValue val, CanType toType) {
auto toTy = SGF.getLoweredType(toType);
auto fromTy = val.getType();

if (fromTy == toTy)
return val; // nothing to do.

// otherwise emit the right kind of cast based on whether it's an address.
assert(fromTy.isAddress() == toTy.isAddress());

if (toTy.isAddress())
return SGF.B.createUncheckedAddrCast(loc, val, toTy);

return SGF.B.createUncheckedBitCast(loc, val, toTy);
}

/// perform a conversion of RValue -> RValue
RValue doUncheckedConversion(SILGenFunction &SGF, SILLocation loc,
RValue &&rv, CanType toType) {
auto val = std::move(rv).getAsSingleValue(SGF, loc);
val = doUncheckedConversion(SGF, loc, val, toType);
return RValue(SGF, loc, toType, val);
}

public:
ABISafeConversionComponent(LValueTypeData typeData)
: PhysicalPathComponent(typeData, ABISafeConversionKind,
/*actorIsolation=*/None) {}
/// \param OrigType is the type we are converting \b from
/// \param typeData will contain the type we are converting \b to
UncheckedConversionComponent(LValueTypeData typeData, Type OrigType)
: TranslationPathComponent(typeData, UncheckedConversionKind),
OrigType(OrigType) {}

ManagedValue project(SILGenFunction &SGF, SILLocation loc,
ManagedValue base) && override {
auto toType = SGF.getLoweredType(getTypeData().SubstFormalType)
.getAddressType();
bool isLoadingPure() const override { return true; }

if (base.getType() == toType)
return base; // nothing to do
/// Used during write operations to convert the value prior to writing to
/// the base.
RValue untranslate(SILGenFunction &SGF, SILLocation loc,
RValue &&rv, SGFContext c) && override {
return doUncheckedConversion(SGF, loc, std::move(rv),
getUntranslatedType());
}

return SGF.B.createUncheckedAddrCast(loc, base, toType);
/// Used during read operations to convert the value after reading the base.
RValue translate(SILGenFunction &SGF, SILLocation loc,
RValue &&rv, SGFContext c) && override {
return doUncheckedConversion(SGF, loc, std::move(rv),
getTranslatedType());
}

std::unique_ptr<LogicalPathComponent>
clone(SILGenFunction &SGF, SILLocation loc) const override {
return std::make_unique<UncheckedConversionComponent>(getTypeData(),
OrigType);
}

void dump(raw_ostream &OS, unsigned indent) const override {
OS.indent(indent) << "ABISafeConversionComponent\n";
OS.indent(indent) << "UncheckedConversionComponent"
<< "\n\tfromType: " << getUntranslatedType()
<< "\n\ttoType: " << getTranslatedType()
<< "\n";
}
};
} // end anonymous namespace
Expand Down Expand Up @@ -3756,7 +3812,9 @@ LValue SILGenLValue::visitABISafeConversionExpr(ABISafeConversionExpr *e,
LValue lval = visitRec(e->getSubExpr(), accessKind, options);
auto typeData = getValueTypeData(SGF, accessKind, e);

lval.add<ABISafeConversionComponent>(typeData);
auto OrigType = e->getSubExpr()->getType();

lval.add<UncheckedConversionComponent>(typeData, OrigType);

return lval;
}
Expand Down
121 changes: 118 additions & 3 deletions test/SILGen/objc_preconcurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
@preconcurrency var sendyHandler: @Sendable () -> Void { get set }
}

@preconcurrency class OldWorld {
@preconcurrency var handler: (@Sendable () -> Void)?
@preconcurrency var mainHandler: (@MainActor () -> Void)?
@preconcurrency var nonOptionalHandler: @Sendable () -> Void = {}
@preconcurrency var nonOptionalMainHandler: @MainActor () -> Void = {}
}

// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency19testDynamicDispatch1p17completionHandleryAA1P_p_yyctF
// CHECK: dynamic_method_br
// CHECK: bb{{[0-9]+}}(%{{[0-9]+}} : $@convention(objc_method) (@convention(block) @Sendable () -> (), @opened
Expand All @@ -19,26 +26,134 @@ func testDynamicDispatch(p: P, completionHandler: @escaping () -> Void) {
}

// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency21testOptionalVarAccessyySo12NSTouchGrassCF
// CHECK: unchecked_addr_cast {{.*}} : $*Optional<@Sendable @callee_guaranteed () -> ()> to $*Optional<@callee_guaranteed () -> ()>
// CHECK: unchecked_bitwise_cast {{.*}} : $Optional<@Sendable @callee_guaranteed () -> ()> to $Optional<@callee_guaranteed () -> ()>
// CHECK: } // end sil function '$s19objc_preconcurrency21testOptionalVarAccessyySo12NSTouchGrassCF'
func testOptionalVarAccess(_ grass: NSTouchGrass) {
grass.cancellationHandler?()
}

// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency33testOptionalVarAccessPartialApplyyyycSgSo12NSTouchGrassCF
// CHECK: unchecked_bitwise_cast {{.*}} : $Optional<@Sendable @callee_guaranteed () -> ()> to $Optional<@callee_guaranteed () -> ()>
// CHECK: } // end sil function '$s19objc_preconcurrency33testOptionalVarAccessPartialApplyyyycSgSo12NSTouchGrassCF'
func testOptionalVarAccessPartialApply(_ grass: NSTouchGrass) -> (() -> Void)? {
let handler = grass.cancellationHandler
if let unwrapped = handler {
unwrapped()
}
return handler
}

// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency16testObjCVarWriteyySo12NSTouchGrassCF
// CHECK: unchecked_bitwise_cast {{.*}} : $Optional<@callee_guaranteed () -> ()> to $Optional<@Sendable @callee_guaranteed () -> ()>
// CHECK: objc_method {{.*}} : $NSTouchGrass, #NSTouchGrass.cancellationHandler!setter.foreign : (NSTouchGrass) -> ((@Sendable () -> ())?) -> (), $@convention(objc_method) (Optional<@convention(block) @Sendable () -> ()>, NSTouchGrass) -> ()
// CHECK: } // end sil function '$s19objc_preconcurrency16testObjCVarWriteyySo12NSTouchGrassCF'
func testObjCVarWrite(_ grass: NSTouchGrass) {
grass.cancellationHandler = {}
}

// the below looks kinda long and wonky, but is expected. for a summary, the steps are:
// 1. objc to native
// 2. Sendable to non-Sendable (major part of this test)
// 3. non-optional to optional
// 4. from non-Sendable to Sendable (major part of this test)
// 5. from native to objc (which involves unwrapping and rewrapping that optional; kinda silly but optimization will clean it up)
//
// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency22testObjCVarWriteAcrossyySo12NSTouchGrassCF
// CHECK: [[GET_EXCEPTION:%[0-9]+]] = objc_method {{.*}} : $NSTouchGrass, #NSTouchGrass.exceptionHandler!getter.foreign
// CHECK: [[SENDABLE_BLOCK:%[0-9]+]] = apply [[GET_EXCEPTION]]({{.*}}) : $@convention(objc_method) (NSTouchGrass) -> @autoreleased @convention(block) @Sendable () -> ()
// << step 1 >>
// CHECK: [[NATIVE_THUNK:%[0-9]+]] = function_ref @$sIeyBh_Iegh_TR : $@convention(thin) @Sendable (@guaranteed @convention(block) @Sendable () -> ()) -> ()
// CHECK: [[NATIVE_SENDABLE_EXCEPTION:%[0-9]+]] = partial_apply [callee_guaranteed] [[NATIVE_THUNK]]([[SENDABLE_BLOCK]])
// << step 2 >>
// CHECK: [[NATIVE_EXCEPTION:%[0-9]+]] = convert_function [[NATIVE_SENDABLE_EXCEPTION]] : $@Sendable @callee_guaranteed () -> () to $@callee_guaranteed () -> ()
// << step 3 >>
// CHECK: [[OPTIONAL_NATIVE_EXCEPTION:%[0-9]+]] = enum $Optional<@callee_guaranteed () -> ()>, #Optional.some!enumelt, [[NATIVE_EXCEPTION]] : $@callee_guaranteed () -> ()
// << step 4 >>
// CHECK: = unchecked_bitwise_cast [[OPTIONAL_NATIVE_EXCEPTION]] : $Optional<@callee_guaranteed () -> ()> to $Optional<@Sendable @callee_guaranteed () -> ()>
// << step 5 >>
// CHECK: switch_enum {{.*}} : $Optional<@Sendable @callee_guaranteed () -> ()>
//
// CHECK: bb1({{.*}} : @owned $@Sendable @callee_guaranteed () -> ()):
// CHECK: init_block_storage_header {{.*}} : $*@block_storage @Sendable @callee_guaranteed () -> ()
// CHECK: } // end sil function '$s19objc_preconcurrency22testObjCVarWriteAcrossyySo12NSTouchGrassCF'
func testObjCVarWriteAcross(_ grass: NSTouchGrass) {
grass.cancellationHandler = grass.exceptionHandler // *slaps roof of assignment* this bad boy can fit so much conversion in it!
}

// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency25testOptionalAssignSetter1yyAA8OldWorldCF
// CHECK: unchecked_bitwise_cast {{.*}} : $Optional<@callee_guaranteed () -> ()> to $Optional<@Sendable @callee_guaranteed () -> ()>
// CHECK: #OldWorld.handler!setter : (OldWorld) -> ((@Sendable () -> ())?) -> ()
// CHECK: } // end sil function '$s19objc_preconcurrency25testOptionalAssignSetter1yyAA8OldWorldCF'
func testOptionalAssignSetter1(_ oldWorld: OldWorld) {
oldWorld.handler = {}
}

// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency25testOptionalAssignSetter2yyAA8OldWorldCF
// CHECK: convert_function {{.*}} : $@callee_guaranteed () -> () to $@Sendable @callee_guaranteed () -> ()
// CHECK: $OldWorld, #OldWorld.nonOptionalHandler!setter : (OldWorld) -> (@escaping @Sendable () -> ()) -> ()
// CHECK: } // end sil function '$s19objc_preconcurrency25testOptionalAssignSetter2yyAA8OldWorldCF'
func testOptionalAssignSetter2(_ oldWorld: OldWorld) {
oldWorld.nonOptionalHandler = {}
}

// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency21testMainHandlerWritesyyAA8OldWorldCF
// CHECK: = unchecked_bitwise_cast {{.*}} : $Optional<@callee_guaranteed () -> ()> to $Optional<@Sendable @callee_guaranteed () -> ()>
// CHECK: = unchecked_bitwise_cast {{.*}} : $Optional<@Sendable @callee_guaranteed () -> ()> to $Optional<@callee_guaranteed () -> ()>
// CHECK: } // end sil function '$s19objc_preconcurrency21testMainHandlerWritesyyAA8OldWorldCF'
func testMainHandlerWrites(_ oldWorld: OldWorld) {
oldWorld.handler = oldWorld.mainHandler
oldWorld.mainHandler = oldWorld.handler
}

// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency32testMainHandlerNonOptionalWritesyyAA8OldWorldCF
// CHECK: = convert_function {{.*}} : $@callee_guaranteed () -> () to $@Sendable @callee_guaranteed () -> ()
// CHECK: = convert_function {{.*}} : $@Sendable @callee_guaranteed () -> () to $@callee_guaranteed () -> ()
// CHECK: } // end sil function '$s19objc_preconcurrency32testMainHandlerNonOptionalWritesyyAA8OldWorldCF'
func testMainHandlerNonOptionalWrites(_ oldWorld: OldWorld) {
oldWorld.nonOptionalHandler = oldWorld.nonOptionalMainHandler
oldWorld.nonOptionalMainHandler = oldWorld.nonOptionalHandler
}

// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency15testMixedWritesyyAA8OldWorldCF
//
// << sendable conversions should be here >>
// CHECK: = unchecked_bitwise_cast {{.*}} : $Optional<@Sendable @callee_guaranteed () -> ()> to $Optional<@callee_guaranteed () -> ()>
// CHECK: = convert_function {{.*}} : $@callee_guaranteed () -> () to $@Sendable @callee_guaranteed () -> ()
//
// << but main actor type mismatches are accepted by SIL >>
// CHECK: [[NO_MAIN_ACTOR:%[0-9]+]] = partial_apply {{.*}} : $@convention(thin) (@guaranteed @callee_guaranteed () -> @out ()) -> ()
// CHECK: [[SETTER:%[0-9]+]] = class_method {{.*}} : $OldWorld, #OldWorld.nonOptionalMainHandler!setter : (OldWorld) -> (@escaping @MainActor () -> ()) -> (), $@convention(method) (@owned @callee_guaranteed () -> (), @guaranteed OldWorld) -> ()
// CHECK: apply [[SETTER]]([[NO_MAIN_ACTOR]]
// CHECK: } // end sil function '$s19objc_preconcurrency15testMixedWritesyyAA8OldWorldCF'
func testMixedWrites(_ oldWorld: OldWorld) {
oldWorld.nonOptionalHandler = oldWorld.handler ?? {}
oldWorld.nonOptionalMainHandler = oldWorld.mainHandler ?? {}
}

func modify(_ v: inout () -> Void) {
v = {}
}

// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency15testInoutAccessyySo12NSTouchGrassCF
// CHECK: unchecked_addr_cast {{.*}} : $*@Sendable @callee_guaranteed () -> () to $*@callee_guaranteed () -> ()
// CHECK: [[BEFORE_MODIFY:%[0-9]+]] = convert_function {{.*}} : $@Sendable @callee_guaranteed () -> () to $@callee_guaranteed () -> ()
// CHECK: store [[BEFORE_MODIFY]] to [init] [[INOUT_ALLOC:%[0-9]+]] : $*@callee_guaranteed () -> ()
// CHECK: [[MODIFY_FN:%[0-9]+]] = function_ref @$s19objc_preconcurrency6modifyyyyyczF : $@convention(thin) (@inout @callee_guaranteed () -> ()) -> ()
// CHECK: = apply [[MODIFY_FN]]([[INOUT_ALLOC]])
// CHECK: [[AFTER_MODIFY:%[0-9]+]] = load [take] [[INOUT_ALLOC]] : $*@callee_guaranteed () -> ()
// CHECK: convert_function [[AFTER_MODIFY]] : $@callee_guaranteed () -> () to $@Sendable @callee_guaranteed () -> ()
// CHECK: } // end sil function '$s19objc_preconcurrency15testInoutAccessyySo12NSTouchGrassCF'
func testInoutAccess(_ grass: NSTouchGrass) {
modify(&grass.exceptionHandler)
}


// CHECK-LABEL: sil hidden [ossa] @$s19objc_preconcurrency21testProtocolVarAccess1pyAA1P_p_tF
// CHECK: unchecked_addr_cast {{.*}} : $*@Sendable @callee_guaranteed () -> () to $*@callee_guaranteed () -> ()
// CHECK: [[BEFORE_MODIFY:%[0-9]+]] = convert_function {{.*}} : $@Sendable @callee_guaranteed () -> () to $@callee_guaranteed () -> ()
// CHECK: store [[BEFORE_MODIFY]] to [init] [[INOUT_ALLOC:%[0-9]+]] : $*@callee_guaranteed () -> ()
// CHECK: [[MODIFY_FN:%[0-9]+]] = function_ref @$s19objc_preconcurrency6modifyyyyyczF : $@convention(thin) (@inout @callee_guaranteed () -> ()) -> ()
// CHECK: = apply [[MODIFY_FN]]([[INOUT_ALLOC]])
// CHECK: [[AFTER_MODIFY:%[0-9]+]] = load [take] [[INOUT_ALLOC]] : $*@callee_guaranteed () -> ()
// CHECK: convert_function [[AFTER_MODIFY]] : $@callee_guaranteed () -> () to $@Sendable @callee_guaranteed () -> ()
// CHECK: } // end sil function '$s19objc_preconcurrency21testProtocolVarAccess1pyAA1P_p_tF'
func testProtocolVarAccess(p: P) {
modify(&p.sendyHandler)
Expand Down