Skip to content

Commit 2133fee

Browse files
committed
Support swifterror in coroutine lowering.
The support for swifterror allocas should work in all lowerings. The support for swifterror arguments only really works in a lowering with prototypes where you can ensure that the prototype also has a swifterror argument; I'm not really sure how it could possibly be made to work in the switch lowering. llvm-svn: 368795
1 parent dc4668e commit 2133fee

File tree

4 files changed

+381
-0
lines changed

4 files changed

+381
-0
lines changed

llvm/lib/Transforms/Coroutines/CoroFrame.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "llvm/Support/MathExtras.h"
2929
#include "llvm/Support/circular_raw_ostream.h"
3030
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
31+
#include "llvm/Transforms/Utils/PromoteMemToReg.h"
3132

3233
using namespace llvm;
3334

@@ -1110,11 +1111,176 @@ static Instruction *lowerNonLocalAlloca(CoroAllocaAllocInst *AI,
11101111
return cast<Instruction>(Alloc);
11111112
}
11121113

1114+
/// Get the current swifterror value.
1115+
static Value *emitGetSwiftErrorValue(IRBuilder<> &Builder, Type *ValueTy,
1116+
coro::Shape &Shape) {
1117+
// Make a fake function pointer as a sort of intrinsic.
1118+
auto FnTy = FunctionType::get(ValueTy, {}, false);
1119+
auto Fn = ConstantPointerNull::get(FnTy->getPointerTo());
1120+
1121+
auto Call = Builder.CreateCall(Fn, {});
1122+
Shape.SwiftErrorOps.push_back(Call);
1123+
1124+
return Call;
1125+
}
1126+
1127+
/// Set the given value as the current swifterror value.
1128+
///
1129+
/// Returns a slot that can be used as a swifterror slot.
1130+
static Value *emitSetSwiftErrorValue(IRBuilder<> &Builder, Value *V,
1131+
coro::Shape &Shape) {
1132+
// Make a fake function pointer as a sort of intrinsic.
1133+
auto FnTy = FunctionType::get(V->getType()->getPointerTo(),
1134+
{V->getType()}, false);
1135+
auto Fn = ConstantPointerNull::get(FnTy->getPointerTo());
1136+
1137+
auto Call = Builder.CreateCall(Fn, { V });
1138+
Shape.SwiftErrorOps.push_back(Call);
1139+
1140+
return Call;
1141+
}
1142+
1143+
/// Set the swifterror value from the given alloca before a call,
1144+
/// then put in back in the alloca afterwards.
1145+
///
1146+
/// Returns an address that will stand in for the swifterror slot
1147+
/// until splitting.
1148+
static Value *emitSetAndGetSwiftErrorValueAround(Instruction *Call,
1149+
AllocaInst *Alloca,
1150+
coro::Shape &Shape) {
1151+
auto ValueTy = Alloca->getAllocatedType();
1152+
IRBuilder<> Builder(Call);
1153+
1154+
// Load the current value from the alloca and set it as the
1155+
// swifterror value.
1156+
auto ValueBeforeCall = Builder.CreateLoad(ValueTy, Alloca);
1157+
auto Addr = emitSetSwiftErrorValue(Builder, ValueBeforeCall, Shape);
1158+
1159+
// Move to after the call. Since swifterror only has a guaranteed
1160+
// value on normal exits, we can ignore implicit and explicit unwind
1161+
// edges.
1162+
if (isa<CallInst>(Call)) {
1163+
Builder.SetInsertPoint(Call->getNextNode());
1164+
} else {
1165+
auto Invoke = cast<InvokeInst>(Call);
1166+
Builder.SetInsertPoint(Invoke->getNormalDest()->getFirstNonPHIOrDbg());
1167+
}
1168+
1169+
// Get the current swifterror value and store it to the alloca.
1170+
auto ValueAfterCall = emitGetSwiftErrorValue(Builder, ValueTy, Shape);
1171+
Builder.CreateStore(ValueAfterCall, Alloca);
1172+
1173+
return Addr;
1174+
}
1175+
1176+
/// Eliminate a formerly-swifterror alloca by inserting the get/set
1177+
/// intrinsics and attempting to MemToReg the alloca away.
1178+
static void eliminateSwiftErrorAlloca(Function &F, AllocaInst *Alloca,
1179+
coro::Shape &Shape) {
1180+
for (auto UI = Alloca->use_begin(), UE = Alloca->use_end(); UI != UE; ) {
1181+
// We're likely changing the use list, so use a mutation-safe
1182+
// iteration pattern.
1183+
auto &Use = *UI;
1184+
++UI;
1185+
1186+
// swifterror values can only be used in very specific ways.
1187+
// We take advantage of that here.
1188+
auto User = Use.getUser();
1189+
if (isa<LoadInst>(User) || isa<StoreInst>(User))
1190+
continue;
1191+
1192+
assert(isa<CallInst>(User) || isa<InvokeInst>(User));
1193+
auto Call = cast<Instruction>(User);
1194+
1195+
auto Addr = emitSetAndGetSwiftErrorValueAround(Call, Alloca, Shape);
1196+
1197+
// Use the returned slot address as the call argument.
1198+
Use.set(Addr);
1199+
}
1200+
1201+
// All the uses should be loads and stores now.
1202+
assert(isAllocaPromotable(Alloca));
1203+
}
1204+
1205+
/// "Eliminate" a swifterror argument by reducing it to the alloca case
1206+
/// and then loading and storing in the prologue and epilog.
1207+
///
1208+
/// The argument keeps the swifterror flag.
1209+
static void eliminateSwiftErrorArgument(Function &F, Argument &Arg,
1210+
coro::Shape &Shape,
1211+
SmallVectorImpl<AllocaInst*> &AllocasToPromote) {
1212+
IRBuilder<> Builder(F.getEntryBlock().getFirstNonPHIOrDbg());
1213+
1214+
auto ArgTy = cast<PointerType>(Arg.getType());
1215+
auto ValueTy = ArgTy->getElementType();
1216+
1217+
// Reduce to the alloca case:
1218+
1219+
// Create an alloca and replace all uses of the arg with it.
1220+
auto Alloca = Builder.CreateAlloca(ValueTy, ArgTy->getAddressSpace());
1221+
Arg.replaceAllUsesWith(Alloca);
1222+
1223+
// Set an initial value in the alloca. swifterror is always null on entry.
1224+
auto InitialValue = Constant::getNullValue(ValueTy);
1225+
Builder.CreateStore(InitialValue, Alloca);
1226+
1227+
// Find all the suspends in the function and save and restore around them.
1228+
for (auto Suspend : Shape.CoroSuspends) {
1229+
(void) emitSetAndGetSwiftErrorValueAround(Suspend, Alloca, Shape);
1230+
}
1231+
1232+
// Find all the coro.ends in the function and restore the error value.
1233+
for (auto End : Shape.CoroEnds) {
1234+
Builder.SetInsertPoint(End);
1235+
auto FinalValue = Builder.CreateLoad(ValueTy, Alloca);
1236+
(void) emitSetSwiftErrorValue(Builder, FinalValue, Shape);
1237+
}
1238+
1239+
// Now we can use the alloca logic.
1240+
AllocasToPromote.push_back(Alloca);
1241+
eliminateSwiftErrorAlloca(F, Alloca, Shape);
1242+
}
1243+
1244+
/// Eliminate all problematic uses of swifterror arguments and allocas
1245+
/// from the function. We'll fix them up later when splitting the function.
1246+
static void eliminateSwiftError(Function &F, coro::Shape &Shape) {
1247+
SmallVector<AllocaInst*, 4> AllocasToPromote;
1248+
1249+
// Look for a swifterror argument.
1250+
for (auto &Arg : F.args()) {
1251+
if (!Arg.hasSwiftErrorAttr()) continue;
1252+
1253+
eliminateSwiftErrorArgument(F, Arg, Shape, AllocasToPromote);
1254+
break;
1255+
}
1256+
1257+
// Look for swifterror allocas.
1258+
for (auto &Inst : F.getEntryBlock()) {
1259+
auto Alloca = dyn_cast<AllocaInst>(&Inst);
1260+
if (!Alloca || !Alloca->isSwiftError()) continue;
1261+
1262+
// Clear the swifterror flag.
1263+
Alloca->setSwiftError(false);
1264+
1265+
AllocasToPromote.push_back(Alloca);
1266+
eliminateSwiftErrorAlloca(F, Alloca, Shape);
1267+
}
1268+
1269+
// If we have any allocas to promote, compute a dominator tree and
1270+
// promote them en masse.
1271+
if (!AllocasToPromote.empty()) {
1272+
DominatorTree DT(F);
1273+
PromoteMemToReg(AllocasToPromote, DT);
1274+
}
1275+
}
1276+
11131277
void coro::buildCoroutineFrame(Function &F, Shape &Shape) {
11141278
// Lower coro.dbg.declare to coro.dbg.value, since we are going to rewrite
11151279
// access to local variables.
11161280
LowerDbgDeclare(F);
11171281

1282+
eliminateSwiftError(F, Shape);
1283+
11181284
if (Shape.ABI == coro::ABI::Switch &&
11191285
Shape.SwitchLowering.PromiseAlloca) {
11201286
Shape.getSwitchCoroId()->clearPromise();

llvm/lib/Transforms/Coroutines/CoroInternal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ struct LLVM_LIBRARY_VISIBILITY Shape {
8989
SmallVector<CoroEndInst *, 4> CoroEnds;
9090
SmallVector<CoroSizeInst *, 2> CoroSizes;
9191
SmallVector<AnyCoroSuspendInst *, 4> CoroSuspends;
92+
SmallVector<CallInst*, 2> SwiftErrorOps;
9293

9394
// Field indexes for special fields in the switch lowering.
9495
struct SwitchFieldIndex {

llvm/lib/Transforms/Coroutines/CoroSplit.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class CoroCloner {
9797
ValueToValueMapTy VMap;
9898
IRBuilder<> Builder;
9999
Value *NewFramePtr = nullptr;
100+
Value *SwiftErrorSlot = nullptr;
100101

101102
/// The active suspend instruction; meaningful only for continuation ABIs.
102103
AnyCoroSuspendInst *ActiveSuspend = nullptr;
@@ -147,6 +148,7 @@ class CoroCloner {
147148
void replaceRetconSuspendUses();
148149
void replaceCoroSuspends();
149150
void replaceCoroEnds();
151+
void replaceSwiftErrorOps();
150152
void handleFinalSuspend();
151153
void maybeFreeContinuationStorage();
152154
};
@@ -490,6 +492,68 @@ void CoroCloner::replaceCoroEnds() {
490492
}
491493
}
492494

495+
static void replaceSwiftErrorOps(Function &F, coro::Shape &Shape,
496+
ValueToValueMapTy *VMap) {
497+
Value *CachedSlot = nullptr;
498+
auto getSwiftErrorSlot = [&](Type *ValueTy) -> Value * {
499+
if (CachedSlot) {
500+
assert(CachedSlot->getType()->getPointerElementType() == ValueTy &&
501+
"multiple swifterror slots in function with different types");
502+
return CachedSlot;
503+
}
504+
505+
// Check if the function has a swifterror argument.
506+
for (auto &Arg : F.args()) {
507+
if (Arg.isSwiftError()) {
508+
CachedSlot = &Arg;
509+
assert(Arg.getType()->getPointerElementType() == ValueTy &&
510+
"swifterror argument does not have expected type");
511+
return &Arg;
512+
}
513+
}
514+
515+
// Create a swifterror alloca.
516+
IRBuilder<> Builder(F.getEntryBlock().getFirstNonPHIOrDbg());
517+
auto Alloca = Builder.CreateAlloca(ValueTy);
518+
Alloca->setSwiftError(true);
519+
520+
CachedSlot = Alloca;
521+
return Alloca;
522+
};
523+
524+
for (CallInst *Op : Shape.SwiftErrorOps) {
525+
auto MappedOp = VMap ? cast<CallInst>((*VMap)[Op]) : Op;
526+
IRBuilder<> Builder(MappedOp);
527+
528+
// If there are no arguments, this is a 'get' operation.
529+
Value *MappedResult;
530+
if (Op->getNumArgOperands() == 0) {
531+
auto ValueTy = Op->getType();
532+
auto Slot = getSwiftErrorSlot(ValueTy);
533+
MappedResult = Builder.CreateLoad(ValueTy, Slot);
534+
} else {
535+
assert(Op->getNumArgOperands() == 1);
536+
auto Value = MappedOp->getArgOperand(0);
537+
auto ValueTy = Value->getType();
538+
auto Slot = getSwiftErrorSlot(ValueTy);
539+
Builder.CreateStore(Value, Slot);
540+
MappedResult = Slot;
541+
}
542+
543+
MappedOp->replaceAllUsesWith(MappedResult);
544+
MappedOp->eraseFromParent();
545+
}
546+
547+
// If we're updating the original function, we've invalidated SwiftErrorOps.
548+
if (VMap == nullptr) {
549+
Shape.SwiftErrorOps.clear();
550+
}
551+
}
552+
553+
void CoroCloner::replaceSwiftErrorOps() {
554+
::replaceSwiftErrorOps(*NewF, Shape, &VMap);
555+
}
556+
493557
void CoroCloner::replaceEntryBlock() {
494558
// In the original function, the AllocaSpillBlock is a block immediately
495559
// following the allocation of the frame object which defines GEPs for
@@ -691,6 +755,9 @@ void CoroCloner::create() {
691755
// Handle suspends.
692756
replaceCoroSuspends();
693757

758+
// Handle swifterror.
759+
replaceSwiftErrorOps();
760+
694761
// Remove coro.end intrinsics.
695762
replaceCoroEnds();
696763

@@ -1364,6 +1431,10 @@ static void splitCoroutine(Function &F, CallGraph &CG, CallGraphSCC &SCC) {
13641431
splitCoroutine(F, Shape, Clones);
13651432
}
13661433

1434+
// Replace all the swifterror operations in the original function.
1435+
// This invalidates SwiftErrorOps in the Shape.
1436+
replaceSwiftErrorOps(F, Shape, nullptr);
1437+
13671438
removeCoroEnds(Shape, &CG);
13681439
postSplitCleanup(F);
13691440

0 commit comments

Comments
 (0)