Skip to content

[SR-15703] Fix missing hop in error path of foreign async throws call #41571

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 2 commits into from
Feb 26, 2022
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
5 changes: 5 additions & 0 deletions lib/SILGen/ExecutorBreadcrumb.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class ExecutorBreadcrumb {
// Emits the hop back sequence, if any, necessary to get back to
// the executor represented by this breadcrumb.
void emit(SILGenFunction &SGF, SILLocation loc);

#ifndef NDEBUG
// FOR ASSERTS ONLY: returns true if calling `emit` will emit a hop-back.
bool needsEmit() const { return mustReturnToExecutor; }
#endif
};

} // namespace Lowering
Expand Down
8 changes: 4 additions & 4 deletions lib/SILGen/ManagedValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,20 +58,20 @@ ManagedValue ManagedValue::copy(SILGenFunction &SGF, SILLocation loc) const {

// Emit an unmanaged copy of this value
// WARNING: Callers of this API should manage the cleanup of this value!
ManagedValue ManagedValue::unmanagedCopy(SILGenFunction &SGF,
SILValue ManagedValue::unmanagedCopy(SILGenFunction &SGF,
SILLocation loc) const {
auto &lowering = SGF.getTypeLowering(getType());
if (lowering.isTrivial())
return *this;
return getValue();

if (getType().isObject()) {
auto copy = SGF.B.emitCopyValueOperation(loc, getValue());
return ManagedValue::forUnmanaged(copy);
return copy;
}

SILValue buf = SGF.emitTemporaryAllocation(loc, getType());
SGF.B.createCopyAddr(loc, getValue(), buf, IsNotTake, IsInitialization);
return ManagedValue::forUnmanaged(buf);
return buf;
}

/// Emit a copy of this value with independent ownership.
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/ManagedValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ class ManagedValue {

/// Returns an unmanaged copy of this value.
/// WARNING: Callers of this API should manage the cleanup of this value!
ManagedValue unmanagedCopy(SILGenFunction &SGF, SILLocation loc) const;
SILValue unmanagedCopy(SILGenFunction &SGF, SILLocation loc) const;

/// Emit a copy of this value with independent ownership into the current
/// formal evaluation scope.
Expand Down
52 changes: 32 additions & 20 deletions lib/SILGen/SILGenApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4414,6 +4414,27 @@ class FixLifetimeDestroyCleanup : public Cleanup {
#endif
}
};

class EmitBreadcrumbCleanup : public Cleanup {
ExecutorBreadcrumb breadcrumb;

public:
EmitBreadcrumbCleanup(ExecutorBreadcrumb &&breadcrumb)
: breadcrumb(std::move(breadcrumb)) {}

void emit(SILGenFunction &SGF, CleanupLocation l,
ForUnwind_t forUnwind) override {
breadcrumb.emit(SGF, l);
}

void dump(SILGenFunction &SGF) const override {
#ifndef NDEBUG
llvm::errs() << "EmitBreadcrumbCleanup "
<< "State:" << getState()
<< "NeedsEmit:" << breadcrumb.needsEmit();
#endif
}
};
} // end anonymous namespace

//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -4573,7 +4594,7 @@ RValue SILGenFunction::emitApply(
// generates `await_async_continuation`.
// Lifetime is extended by creating unmanaged copies here and by pushing the
// cleanups required just before the result plan is generated.
SmallVector<ManagedValue, 8> unmanagedCopies;
SmallVector<SILValue, 8> unmanagedCopies;
if (calleeTypeInfo.foreign.async) {
for (auto arg : args) {
if (arg.hasCleanup()) {
Expand Down Expand Up @@ -4660,32 +4681,23 @@ RValue SILGenFunction::emitApply(
*foreignError, calleeTypeInfo.foreign.async);
}

// For objc async calls, push cleanup to be used on throw paths in the result
// planner.
for (unsigned i : indices(unmanagedCopies)) {
SILValue value = unmanagedCopies[i].getValue();
Cleanups.pushCleanup<FixLifetimeDestroyCleanup>(value);
unmanagedCopies[i] = ManagedValue(value, Cleanups.getTopCleanup());
// For objc async calls, push cleanups to be used on
// both result and throw paths prior to finishing the result plan.
if (calleeTypeInfo.foreign.async) {
for (auto unmanagedCopy : unmanagedCopies) {
Cleanups.pushCleanup<FixLifetimeDestroyCleanup>(unmanagedCopy);
}
// save breadcrumb as a clean-up so it is emitted in result / throw cases.
Cleanups.pushCleanup<EmitBreadcrumbCleanup>(std::move(breadcrumb));
} else {
assert(unmanagedCopies.empty());
}

auto directResultsArray = makeArrayRef(directResults);
RValue result = resultPlan->finish(*this, loc, substResultType,
directResultsArray, bridgedForeignError);
assert(directResultsArray.empty() && "didn't claim all direct results");

// For objc async calls, generate cleanup on the resume path here and forward
// the previously pushed cleanups.
if (calleeTypeInfo.foreign.async) {
for (auto unmanagedCopy : unmanagedCopies) {
auto value = unmanagedCopy.forward(*this);
B.emitFixLifetime(loc, value);
B.emitDestroyOperation(loc, value);
}

// hop back to the current executor
breadcrumb.emit(*this, loc);
}

return result;
}

Expand Down
42 changes: 41 additions & 1 deletion test/SILGen/objc_async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,49 @@ func testSlowServerFromMain(slowServer: SlowServer) async throws {
// CHECK: await_async_continuation [[CONT]] {{.*}}, resume [[RESUME:bb[0-9]+]]
// CHECK: [[RESUME]]:
// CHECK: [[RESULT:%.*]] = load [trivial] [[RESUME_BUF]]
// CHECK: hop_to_executor %6 : $MainActor
// CHECK: fix_lifetime [[COPY]]
// CHECK: destroy_value [[COPY]]
// CHECK: hop_to_executor %6 : $MainActor
// CHECK: dealloc_stack [[RESUME_BUF]]
let _: Int = await slowServer.doSomethingSlow("mail")
}

// CHECK-LABEL: sil {{.*}}@${{.*}}26testThrowingMethodFromMain
@MainActor
func testThrowingMethodFromMain(slowServer: SlowServer) async -> String {
// CHECK: [[RESULT_BUF:%.*]] = alloc_stack $String
// CHECK: [[STRING_ARG:%.*]] = apply {{%.*}}({{%.*}}) : $@convention(method) (@guaranteed String) -> @owned NSString
// CHECK: [[METH:%.*]] = objc_method {{%.*}} : $SlowServer, #SlowServer.doSomethingDangerous!foreign
// CHECK: [[RAW_CONT:%.*]] = get_async_continuation_addr [throws] String, [[RESULT_BUF]] : $*String
// CHECK: [[CONT:%.*]] = struct $UnsafeContinuation<String, Error> ([[RAW_CONT]] : $Builtin.RawUnsafeContinuation)
// CHECK: [[STORE_ALLOC:%.*]] = alloc_stack $@block_storage UnsafeContinuation<String, Error>
// CHECK: [[PROJECTED:%.*]] = project_block_storage [[STORE_ALLOC]] : $*@block_storage
// CHECK: store [[CONT]] to [trivial] [[PROJECTED]] : $*UnsafeContinuation<String, Error>
// CHECK: [[INVOKER:%.*]] = function_ref @$sSo8NSStringCSgSo7NSErrorCSgIeyByy_SSTz_
// CHECK: [[BLOCK:%.*]] = init_block_storage_header [[STORE_ALLOC]] {{.*}}, invoke [[INVOKER]]
// CHECK: [[OPTIONAL_BLK:%.*]] = enum {{.*}}, #Optional.some!enumelt, [[BLOCK]]
// CHECK: %28 = apply [[METH]]([[STRING_ARG]], [[OPTIONAL_BLK]], {{%.*}}) : $@convention(objc_method) (NSString, Optional<@convention(block) (Optional<NSString>, Optional<NSError>) -> ()>, SlowServer) -> ()
// CHECK: [[STRING_ARG_COPY:%.*]] = copy_value [[STRING_ARG]] : $NSString
// CHECK: dealloc_stack [[STORE_ALLOC]] : $*@block_storage UnsafeContinuation<String, Error>
// CHECK: destroy_value [[STRING_ARG]] : $NSString
// CHECK: await_async_continuation [[RAW_CONT]] : $Builtin.RawUnsafeContinuation, resume [[RESUME:bb[0-9]+]], error [[ERROR:bb[0-9]+]]

// CHECK: [[RESUME]]
// CHECK: {{.*}} = load [take] [[RESULT_BUF]] : $*String
// CHECK: hop_to_executor {{%.*}} : $MainActor
// CHECK: fix_lifetime [[STRING_ARG_COPY]] : $NSString
// CHECK: destroy_value [[STRING_ARG_COPY]] : $NSString
// CHECK: dealloc_stack [[RESULT_BUF]] : $*String

// CHECK: [[ERROR]]
// CHECK: hop_to_executor {{%.*}} : $MainActor
// CHECK: fix_lifetime [[STRING_ARG_COPY]] : $NSString
// CHECK: destroy_value [[STRING_ARG_COPY]] : $NSString
// CHECK: dealloc_stack [[RESULT_BUF]] : $*String

do {
return try await slowServer.doSomethingDangerous("run-with-scissors")
} catch {
return "none"
}
}
17 changes: 13 additions & 4 deletions test/SILGen/objc_async_from_swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,28 @@ func testSlowServing(p: SlowServing) async throws {
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
let _: String = await p.requestString()

// CHECK: objc_method {{.*}} $@convention(objc_method) <τ_0_0 where τ_0_0 : SlowServing> (@convention(block) (Optional<NSString>, Optional<NSError>) -> (), τ_0_0) -> ()
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
let _: String = try await p.tryRequestString()

// CHECK: objc_method {{.*}} $@convention(objc_method) <τ_0_0 where τ_0_0 : SlowServing> (@convention(block) (Int, NSString) -> (), τ_0_0) -> ()
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
let _: (Int, String) = await p.requestIntAndString()

// CHECK: objc_method {{.*}} $@convention(objc_method) <τ_0_0 where τ_0_0 : SlowServing> (@convention(block) (Int, Optional<NSString>, Optional<NSError>) -> (), τ_0_0) -> ()
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
// CHECK: builtin "willThrow"
// CHECK-NEXT: hop_to_executor [[GENERIC_EXECUTOR]] :
let _: (Int, String) = try await p.tryRequestIntAndString()
}

// CHECK-LABEL: sil {{.*}}@{{.*}}20testSlowServingAgain
func testSlowServingAgain(p: SlowServing) async throws {
// CHECK: [[GENERIC_EXECUTOR:%.*]] = enum $Optional<Builtin.Executor>, #Optional.none
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
// CHECK: objc_method {{.*}} $@convention(objc_method) <τ_0_0 where τ_0_0 : SlowServing> (@convention(block) (Optional<NSString>, Optional<NSError>) -> (), τ_0_0) -> ()
// CHECK: hop_to_executor [[GENERIC_EXECUTOR]] :
// CHECK: builtin "willThrow"
// CHECK-NEXT: hop_to_executor [[GENERIC_EXECUTOR]] :
let _: String = try await p.tryRequestString()
}

class SlowSwiftServer: NSObject, SlowServing {
// CHECK-LABEL: sil {{.*}} @$s21objc_async_from_swift15SlowSwiftServerC10requestIntSiyYaF
// CHECK: [[GENERIC_EXECUTOR:%.*]] = enum $Optional<Builtin.Executor>, #Optional.none
Expand Down