Skip to content

Commit b6f0ff7

Browse files
Merge pull request #36256 from adrian-prantl/async-lifetime
Enable lifetime extension for local variables and function arguments …
2 parents c4f6fc3 + e51bcdb commit b6f0ff7

File tree

3 files changed

+133
-3
lines changed

3 files changed

+133
-3
lines changed

lib/IRGen/IRGenSIL.cpp

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,10 @@ class IRGenSILFunction :
401401
llvm::SmallDenseMap<StackSlotKey, Address, 8> ShadowStackSlots;
402402
llvm::SmallDenseMap<llvm::Value *, Address, 8> TaskAllocStackSlots;
403403
llvm::SmallDenseMap<Decl *, SmallString<4>, 8> AnonymousVariables;
404+
/// To avoid inserting elements into ValueDomPoints twice.
405+
llvm::SmallDenseSet<llvm::Instruction *, 8> ValueVariables;
406+
/// Holds the DominancePoint of values that are storage for a source variable.
407+
SmallVector<std::pair<llvm::Instruction *, DominancePoint>, 8> ValueDomPoints;
404408
unsigned NumAnonVars = 0;
405409

406410
/// Accumulative amount of allocated bytes on the stack. Used to limit the
@@ -691,6 +695,83 @@ class IRGenSILFunction :
691695
Size, llvm::MaybeAlign(AI->getAlignment()));
692696
}
693697

698+
/// Try to emit an inline assembly gadget which extends the lifetime of
699+
/// \p Var. Returns whether or not this was successful.
700+
bool emitLifetimeExtendingUse(llvm::Value *Var) {
701+
llvm::Type *ArgTys;
702+
auto *Ty = Var->getType();
703+
// Vectors, Pointers and Floats are expected to fit into a register.
704+
if (Ty->isPointerTy() || Ty->isFloatingPointTy() || Ty->isVectorTy())
705+
ArgTys = {Ty};
706+
else {
707+
// If this is not a scalar or vector type, we can't handle it.
708+
if (isa<llvm::StructType>(Ty))
709+
return false;
710+
// The storage is guaranteed to be no larger than the register width.
711+
// Extend the storage so it would fit into a register.
712+
llvm::Type *IntTy;
713+
switch (IGM.getClangASTContext().getTargetInfo().getRegisterWidth()) {
714+
case 64:
715+
IntTy = IGM.Int64Ty;
716+
break;
717+
case 32:
718+
IntTy = IGM.Int32Ty;
719+
break;
720+
default:
721+
llvm_unreachable("unsupported register width");
722+
}
723+
ArgTys = {IntTy};
724+
Var = Var->getType()->getIntegerBitWidth() < IntTy->getIntegerBitWidth()
725+
? Builder.CreateZExtOrBitCast(Var, IntTy)
726+
: Builder.CreateTruncOrBitCast(Var, IntTy);
727+
}
728+
// Emit an empty inline assembler expression depending on the register.
729+
auto *AsmFnTy = llvm::FunctionType::get(IGM.VoidTy, ArgTys, false);
730+
auto *InlineAsm = llvm::InlineAsm::get(AsmFnTy, "", "r", true);
731+
Builder.CreateAsmCall(InlineAsm, Var);
732+
return true;
733+
}
734+
735+
/// At -Onone, forcibly keep all LLVM values that are tracked by
736+
/// debug variables alive by inserting an empty inline assembler
737+
/// expression depending on the value in the blocks dominated by the
738+
/// value.
739+
///
740+
/// This is used only in async functions.
741+
void emitDebugVariableRangeExtension(const SILBasicBlock *CurBB) {
742+
if (IGM.IRGen.Opts.shouldOptimize())
743+
return;
744+
for (auto &Variable : ValueDomPoints) {
745+
llvm::Instruction *Var = Variable.first;
746+
DominancePoint VarDominancePoint = Variable.second;
747+
if (getActiveDominancePoint() == VarDominancePoint ||
748+
isActiveDominancePointDominatedBy(VarDominancePoint)) {
749+
bool ExtendedLifetime = emitLifetimeExtendingUse(Var);
750+
if (!ExtendedLifetime)
751+
continue;
752+
753+
// Propagate dbg.values for Var into the current basic block. Note
754+
// that this shouldn't be necessary. LiveDebugValues should be doing
755+
// this but can't in general because it currently only tracks register
756+
// locations.
757+
llvm::BasicBlock *BB = Var->getParent();
758+
llvm::BasicBlock *CurBB = Builder.GetInsertBlock();
759+
if (BB == CurBB)
760+
// The current basic block must be a successor of the dbg.value().
761+
continue;
762+
763+
llvm::SmallVector<llvm::DbgValueInst *, 4> DbgValues;
764+
llvm::findDbgValues(DbgValues, Var);
765+
for (auto *DVI : DbgValues)
766+
if (DVI->getParent() == BB)
767+
IGM.DebugInfo->getBuilder().insertDbgValueIntrinsic(
768+
DVI->getValue(), DVI->getVariable(), DVI->getExpression(),
769+
DVI->getDebugLoc(), &*CurBB->getFirstInsertionPt());
770+
}
771+
}
772+
}
773+
774+
694775
/// Account for bugs in LLVM.
695776
///
696777
/// - When a variable is spilled into a stack slot, LiveDebugValues fails to
@@ -872,6 +953,14 @@ class IRGenSILFunction :
872953
// turned off for async functions, because they make it impossible to track
873954
// debug info during coroutine splitting. Instead we are relying on LLVM's
874955
// CoroSplit.cpp to emit shadow copies.
956+
957+
// Mark variables in async functions for lifetime extension, so they get
958+
// spilled into the async context.
959+
if (!IGM.IRGen.Opts.shouldOptimize() && CurSILFn->isAsync())
960+
if (auto *Value = dyn_cast<llvm::Instruction>(Storage))
961+
if (emitLifetimeExtendingUse(Value))
962+
if (ValueVariables.insert(Value).second)
963+
ValueDomPoints.push_back({Value, getActiveDominancePoint()});
875964
if (IGM.IRGen.Opts.DisableDebuggerShadowCopies ||
876965
IGM.IRGen.Opts.shouldOptimize() || IsAnonymous ||
877966
(CurSILFn->isAsync() && VarInfo.ArgNo) ||
@@ -905,6 +994,15 @@ class IRGenSILFunction :
905994
(CurSILFn->isAsync() && VarInfo.ArgNo)) {
906995
auto vals = e.claimAll();
907996
copy.append(vals.begin(), vals.end());
997+
998+
// Mark variables in async functions for lifetime extension, so they get
999+
// spilled into the async context.
1000+
if (!IGM.IRGen.Opts.shouldOptimize() && CurSILFn->isAsync())
1001+
if (vals.begin() != vals.end())
1002+
if (auto *Value = dyn_cast<llvm::Instruction>(vals.front()))
1003+
if (emitLifetimeExtendingUse(Value))
1004+
if (ValueVariables.insert(Value).second)
1005+
ValueDomPoints.push_back({Value, getActiveDominancePoint()});
9081006
return;
9091007
}
9101008

@@ -2271,6 +2369,8 @@ void IRGenSILFunction::visitSILBasicBlock(SILBasicBlock *BB) {
22712369
IGM.DebugInfo->setCurrentLoc(
22722370
Builder, DS, RegularLocation::getAutoGeneratedLocation());
22732371
}
2372+
if (isa<TermInst>(&I))
2373+
emitDebugVariableRangeExtension(BB);
22742374
}
22752375
visit(&I);
22762376
}
@@ -4867,7 +4967,6 @@ void IRGenSILFunction::emitDebugInfoForAllocStack(AllocStackInst *i,
48674967

48684968
void IRGenSILFunction::visitAllocStackInst(swift::AllocStackInst *i) {
48694969
const TypeInfo &type = getTypeInfo(i->getElementType());
4870-
48714970
// Derive name from SIL location.
48724971
StringRef dbgname;
48734972
VarDecl *Decl = i->getDecl();

test/DebugInfo/async-direct-arg.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// RUN: %target-swift-frontend %s -emit-ir -g -o - \
22
// RUN: -module-name a -enable-experimental-concurrency \
3-
// RUN: -parse-as-library | %FileCheck %s --check-prefix=CHECK \
4-
// RUN: --check-prefix=CHECK-%target-cpu
3+
// RUN: -parse-as-library | %FileCheck %s --check-prefix=CHECK
54
// REQUIRES: concurrency
65

76
// UNSUPPORTED: CPU=arm64e
@@ -10,6 +9,8 @@
109
// argument.
1110

1211
// CHECK-LABEL: define {{.*}} void @"$s1a3fibyS2iYF.resume.0"
12+
// CHECK: call void @llvm.dbg.declare
13+
// CHECK: call void @llvm.dbg.declare
1314
// CHECK: call void @llvm.dbg.declare(metadata {{.*}}%2, metadata ![[X0:[0-9]+]], {{.*}}!DIExpression(DW_OP
1415
// CHECK-LABEL: define {{.*}} void @"$s1a3fibyS2iYF.resume.1"
1516
// FIXME: call void @llvm.dbg.declare(metadata {{.*}}%2, metadata ![[X1:[0-9]+]], {{.*}}!DIExpression(DW_OP
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// RUN: %target-swift-frontend %s -emit-ir -g -o - \
2+
// RUN: -module-name a -enable-experimental-concurrency \
3+
// RUN: | %FileCheck %s --check-prefix=CHECK
4+
// REQUIRES: concurrency
5+
6+
// UNSUPPORTED: CPU=arm64e
7+
8+
// Test that lifetime extension preserves a dbg.declare for "n" in the resume
9+
// funclet.
10+
11+
// CHECK-LABEL: define {{.*}} void @"$s1a4fiboyS2iYF.resume.0"
12+
// CHECK-NEXT: entryresume.0:
13+
// CHECK-NEXT: call void @llvm.dbg.declare(metadata {{.*}}%2, metadata ![[R:[0-9]+]], {{.*}}!DIExpression(DW_OP
14+
// CHECK-NEXT: call void @llvm.dbg.declare(metadata {{.*}}%2, metadata ![[LHS:[0-9]+]], {{.*}}!DIExpression(DW_OP
15+
// CHECK-NEXT: call void @llvm.dbg.declare(metadata {{.*}}%2, metadata ![[RHS:[0-9]+]], {{.*}}!DIExpression(DW_OP
16+
// CHECK-NEXT: call void @llvm.dbg.declare(metadata {{.*}}%2, metadata ![[N:[0-9]+]], {{.*}}!DIExpression(DW_OP
17+
// CHECK-NOT: {{ ret }}
18+
// CHECK: call void asm sideeffect ""
19+
// CHECK: ![[R]] = !DILocalVariable(name: "retval"
20+
// CHECK: ![[LHS]] = !DILocalVariable(name: "lhs"
21+
// CHECK: ![[RHS]] = !DILocalVariable(name: "rhs"
22+
// CHECK: ![[N]] = !DILocalVariable(name: "n"
23+
public func fibo(_ n: Int) async -> Int {
24+
var retval = n
25+
if retval < 2 { return 1 }
26+
retval = retval - 1
27+
let lhs = await fibo(retval - 1)
28+
let rhs = await fibo(retval - 2)
29+
return lhs + rhs + retval
30+
}

0 commit comments

Comments
 (0)