Skip to content

Commit c1bc798

Browse files
committed
[Coroutine] Remain alignment information when merging frame variables
Summary: This is to address bug48712. The solution in this patch is that when we want to merge two variable a into the storage frame of variable b only if the alignment of a is multiple of b. There may be other strategies. But now I think they are hard to handle and benefit little. Or we can implement them in the future. Test-plan: check-llvm Reviewers: jmorse, lxfind, junparser Differential Revision: https://reviews.llvm.org/D94891
1 parent a6a72df commit c1bc798

File tree

3 files changed

+178
-2
lines changed

3 files changed

+178
-2
lines changed

llvm/lib/Transforms/Coroutines/CoroFrame.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -596,9 +596,21 @@ void FrameTypeBuilder::addFieldForAllocas(const Function &F,
596596
// NonOverlappedAllocaSet.
597597
for (auto &AllocaSet : NonOverlapedAllocas) {
598598
assert(!AllocaSet.empty() && "Processing Alloca Set is not empty.\n");
599-
bool CouldMerge = none_of(AllocaSet, [&](auto Iter) {
599+
bool NoInference = none_of(AllocaSet, [&](auto Iter) {
600600
return IsAllocaInferenre(Alloca, Iter);
601601
});
602+
// If the alignment of A is multiple of the alignment of B, the address
603+
// of A should satisfy the requirement for aligning for B.
604+
//
605+
// There may be other more fine-grained strategies to handle the alignment
606+
// infomation during the merging process. But it seems hard to handle
607+
// these strategies and benefit little.
608+
bool Alignable = [&]() -> bool {
609+
auto *LargestAlloca = *AllocaSet.begin();
610+
return LargestAlloca->getAlign().value() % Alloca->getAlign().value() ==
611+
0;
612+
}();
613+
bool CouldMerge = NoInference && Alignable;
602614
if (!CouldMerge)
603615
continue;
604616
AllocaIndex[Alloca] = AllocaIndex[*AllocaSet.begin()];
@@ -1120,7 +1132,11 @@ static Instruction *insertSpills(const FrameDataInfo &FrameData,
11201132
if (isa<AllocaInst>(Orig)) {
11211133
// If the type of GEP is not equal to the type of AllocaInst, it implies
11221134
// that the AllocaInst may be reused in the Frame slot of other
1123-
// AllocaInst. So we cast the GEP to the type of AllocaInst.
1135+
// AllocaInst. So We cast GEP to the AllocaInst here to re-use
1136+
// the Frame storage.
1137+
//
1138+
// Note: If we change the strategy dealing with alignment, we need to refine
1139+
// this casting.
11241140
if (GEP->getResultElementType() != Orig->getType())
11251141
return Builder.CreateBitCast(GEP, Orig->getType(),
11261142
Orig->getName() + Twine(".cast"));
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
; Tests that variables of different type with incompatible alignment in a Corotuine whose lifetime
2+
; range is not overlapping each other should not re-use the same slot in Coroutine frame.
3+
; RUN: opt < %s -coro-split -reuse-storage-in-coroutine-frame -S | FileCheck %s
4+
; RUN: opt < %s -passes=coro-split -reuse-storage-in-coroutine-frame -S | FileCheck %s
5+
%"struct.task::promise_type" = type { i8 }
6+
%struct.awaitable = type { i8 }
7+
%struct.big_structure = type { [500 x i8] }
8+
%struct.big_structure.2 = type { [300 x i8] }
9+
declare i8* @malloc(i64)
10+
declare void @consume(%struct.big_structure*)
11+
declare void @consume.2(%struct.big_structure.2*)
12+
define void @a(i1 zeroext %cond) "coroutine.presplit"="1" {
13+
entry:
14+
%__promise = alloca %"struct.task::promise_type", align 1
15+
%a = alloca %struct.big_structure, align 1
16+
%ref.tmp7 = alloca %struct.awaitable, align 1
17+
%b = alloca %struct.big_structure.2, align 32
18+
%ref.tmp18 = alloca %struct.awaitable, align 1
19+
%0 = getelementptr inbounds %"struct.task::promise_type", %"struct.task::promise_type"* %__promise, i64 0, i32 0
20+
%1 = call token @llvm.coro.id(i32 16, i8* nonnull %0, i8* bitcast (void (i1)* @a to i8*), i8* null)
21+
br label %init.ready
22+
init.ready:
23+
%2 = call noalias nonnull i8* @llvm.coro.begin(token %1, i8* null)
24+
call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull %0)
25+
br i1 %cond, label %if.then, label %if.else
26+
if.then:
27+
%3 = getelementptr inbounds %struct.big_structure, %struct.big_structure* %a, i64 0, i32 0, i64 0
28+
call void @llvm.lifetime.start.p0i8(i64 500, i8* nonnull %3)
29+
call void @consume(%struct.big_structure* nonnull %a)
30+
%save = call token @llvm.coro.save(i8* null)
31+
%suspend = call i8 @llvm.coro.suspend(token %save, i1 false)
32+
switch i8 %suspend, label %coro.ret [
33+
i8 0, label %await.ready
34+
i8 1, label %cleanup1
35+
]
36+
await.ready:
37+
call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %3)
38+
br label %cleanup1
39+
if.else:
40+
%4 = getelementptr inbounds %struct.big_structure.2, %struct.big_structure.2* %b, i64 0, i32 0, i64 0
41+
call void @llvm.lifetime.start.p0i8(i64 300, i8* nonnull %4)
42+
call void @consume.2(%struct.big_structure.2* nonnull %b)
43+
%save2 = call token @llvm.coro.save(i8* null)
44+
%suspend2 = call i8 @llvm.coro.suspend(token %save2, i1 false)
45+
switch i8 %suspend2, label %coro.ret [
46+
i8 0, label %await2.ready
47+
i8 1, label %cleanup2
48+
]
49+
await2.ready:
50+
call void @llvm.lifetime.end.p0i8(i64 300, i8* nonnull %4)
51+
br label %cleanup2
52+
cleanup1:
53+
call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %3)
54+
br label %cleanup
55+
cleanup2:
56+
call void @llvm.lifetime.end.p0i8(i64 300, i8* nonnull %4)
57+
br label %cleanup
58+
cleanup:
59+
call i8* @llvm.coro.free(token %1, i8* %2)
60+
br label %coro.ret
61+
coro.ret:
62+
call i1 @llvm.coro.end(i8* null, i1 false)
63+
ret void
64+
}
65+
; CHECK: %a.Frame = type { void (%a.Frame*)*, void (%a.Frame*)*, %"struct.task::promise_type", %struct.big_structure, i1, [26 x i8], %struct.big_structure.2 }
66+
; CHECK-LABEL: @a.resume(
67+
; CHECK: %[[A:.*]] = getelementptr inbounds %a.Frame, %a.Frame* %FramePtr, i32 0, i32 3
68+
; CHECK: %[[A:.*]] = getelementptr inbounds %a.Frame, %a.Frame* %FramePtr, i32 0, i32 6
69+
70+
declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*)
71+
declare i1 @llvm.coro.alloc(token) #3
72+
declare i64 @llvm.coro.size.i64() #5
73+
declare i8* @llvm.coro.begin(token, i8* writeonly) #3
74+
declare token @llvm.coro.save(i8*) #3
75+
declare i8* @llvm.coro.frame() #5
76+
declare i8 @llvm.coro.suspend(token, i1) #3
77+
declare i8* @llvm.coro.free(token, i8* nocapture readonly) #2
78+
declare i1 @llvm.coro.end(i8*, i1) #3
79+
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #4
80+
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #4
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
; Tests that variables of different type with incompatible alignment in a Corotuine whose
2+
; lifetime range is not overlapping each other re-use the same slot in CorotuineFrame.
3+
; RUN: opt < %s -coro-split -reuse-storage-in-coroutine-frame -S | FileCheck %s
4+
; RUN: opt < %s -passes=coro-split -reuse-storage-in-coroutine-frame -S | FileCheck %s
5+
%"struct.task::promise_type" = type { i8 }
6+
%struct.awaitable = type { i8 }
7+
%struct.big_structure = type { [500 x i8] }
8+
%struct.big_structure.2 = type { [400 x i8] }
9+
declare i8* @malloc(i64)
10+
declare void @consume(%struct.big_structure*)
11+
declare void @consume.2(%struct.big_structure.2*)
12+
define void @a(i1 zeroext %cond) "coroutine.presplit"="1" {
13+
entry:
14+
%__promise = alloca %"struct.task::promise_type", align 1
15+
%a = alloca %struct.big_structure, align 32
16+
%ref.tmp7 = alloca %struct.awaitable, align 1
17+
%b = alloca %struct.big_structure.2, align 16
18+
%ref.tmp18 = alloca %struct.awaitable, align 1
19+
%0 = getelementptr inbounds %"struct.task::promise_type", %"struct.task::promise_type"* %__promise, i64 0, i32 0
20+
%1 = call token @llvm.coro.id(i32 16, i8* nonnull %0, i8* bitcast (void (i1)* @a to i8*), i8* null)
21+
br label %init.ready
22+
init.ready:
23+
%2 = call noalias nonnull i8* @llvm.coro.begin(token %1, i8* null)
24+
call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull %0)
25+
br i1 %cond, label %if.then, label %if.else
26+
if.then:
27+
%3 = getelementptr inbounds %struct.big_structure, %struct.big_structure* %a, i64 0, i32 0, i64 0
28+
call void @llvm.lifetime.start.p0i8(i64 500, i8* nonnull %3)
29+
call void @consume(%struct.big_structure* nonnull %a)
30+
%save = call token @llvm.coro.save(i8* null)
31+
%suspend = call i8 @llvm.coro.suspend(token %save, i1 false)
32+
switch i8 %suspend, label %coro.ret [
33+
i8 0, label %await.ready
34+
i8 1, label %cleanup1
35+
]
36+
await.ready:
37+
call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %3)
38+
br label %cleanup1
39+
if.else:
40+
%4 = getelementptr inbounds %struct.big_structure.2, %struct.big_structure.2* %b, i64 0, i32 0, i64 0
41+
call void @llvm.lifetime.start.p0i8(i64 400, i8* nonnull %4)
42+
call void @consume.2(%struct.big_structure.2* nonnull %b)
43+
%save2 = call token @llvm.coro.save(i8* null)
44+
%suspend2 = call i8 @llvm.coro.suspend(token %save2, i1 false)
45+
switch i8 %suspend2, label %coro.ret [
46+
i8 0, label %await2.ready
47+
i8 1, label %cleanup2
48+
]
49+
await2.ready:
50+
call void @llvm.lifetime.end.p0i8(i64 400, i8* nonnull %4)
51+
br label %cleanup2
52+
cleanup1:
53+
call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %3)
54+
br label %cleanup
55+
cleanup2:
56+
call void @llvm.lifetime.end.p0i8(i64 400, i8* nonnull %4)
57+
br label %cleanup
58+
cleanup:
59+
call i8* @llvm.coro.free(token %1, i8* %2)
60+
br label %coro.ret
61+
coro.ret:
62+
call i1 @llvm.coro.end(i8* null, i1 false)
63+
ret void
64+
}
65+
; CHECK: %a.Frame = type { void (%a.Frame*)*, void (%a.Frame*)*, %"struct.task::promise_type", i1, [14 x i8], %struct.big_structure }
66+
; CHECK-LABEL: @a.resume(
67+
; CHECK: %[[A:.*]] = getelementptr inbounds %a.Frame, %a.Frame* %FramePtr, i32 0, i32 3
68+
; CHECK: %[[A:.*]] = getelementptr inbounds %a.Frame, %a.Frame* %FramePtr, i32 0, i32 5
69+
70+
declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*)
71+
declare i1 @llvm.coro.alloc(token) #3
72+
declare i64 @llvm.coro.size.i64() #5
73+
declare i8* @llvm.coro.begin(token, i8* writeonly) #3
74+
declare token @llvm.coro.save(i8*) #3
75+
declare i8* @llvm.coro.frame() #5
76+
declare i8 @llvm.coro.suspend(token, i1) #3
77+
declare i8* @llvm.coro.free(token, i8* nocapture readonly) #2
78+
declare i1 @llvm.coro.end(i8*, i1) #3
79+
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #4
80+
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #4

0 commit comments

Comments
 (0)