Skip to content

Commit 5ebda89

Browse files
aykevldeadprogram
authored andcommitted
runtime: rewrite channel implementation
This rewrite simplifies the channel implementation considerably, with 34% less LOC. Perhaps the most important change is the removal of the channel state, which made sense when we had only send and receive operations but only makes things more compliated when multiple select operations can be pending on a single channel. I did this rewrite originally to make it possible to make channels parallelism-safe. The current implementation is not parallelism-safe, but it will be easy to make it so (the main additions will be a channel lock, a global select lock, and an atomic compare-and-swap in chanQueue.pop).
1 parent b7a3fd8 commit 5ebda89

File tree

5 files changed

+383
-562
lines changed

5 files changed

+383
-562
lines changed

compiler/channel.go

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,17 @@ func (b *builder) createChanSend(instr *ssa.Send) {
4141
b.CreateStore(chanValue, valueAlloca)
4242
}
4343

44-
// Allocate blockedlist buffer.
45-
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
46-
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
44+
// Allocate buffer for the channel operation.
45+
channelOp := b.getLLVMRuntimeType("channelOp")
46+
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
4747

4848
// Do the send.
49-
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
49+
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
5050

5151
// End the lifetime of the allocas.
5252
// This also works around a bug in CoroSplit, at least in LLVM 8:
5353
// https://bugs.llvm.org/show_bug.cgi?id=41742
54-
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
54+
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
5555
if !isZeroSize {
5656
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
5757
}
@@ -72,20 +72,20 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
7272
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
7373
}
7474

75-
// Allocate blockedlist buffer.
76-
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
77-
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
75+
// Allocate buffer for the channel operation.
76+
channelOp := b.getLLVMRuntimeType("channelOp")
77+
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
7878

7979
// Do the receive.
80-
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
80+
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
8181
var received llvm.Value
8282
if isZeroSize {
8383
received = llvm.ConstNull(valueType)
8484
} else {
8585
received = b.CreateLoad(valueType, valueAlloca, "chan.received")
8686
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
8787
}
88-
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
88+
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
8989

9090
if unop.CommaOk {
9191
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
@@ -198,26 +198,29 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
198198
if expr.Blocking {
199199
// Stack-allocate operation structures.
200200
// If these were simply created as a slice, they would heap-allocate.
201-
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
202-
chBlockAlloca, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
203-
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
204-
chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{
201+
opsAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelOp"), len(selectStates))
202+
opsAlloca, opsSize := b.createTemporaryAlloca(opsAllocaType, "select.block.alloca")
203+
opsLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
204+
opsPtr := b.CreateGEP(opsAllocaType, opsAlloca, []llvm.Value{
205205
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
206206
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
207207
}, "select.block")
208208

209209
results = b.createRuntimeCall("chanSelect", []llvm.Value{
210210
recvbuf,
211211
statesPtr, statesLen, statesLen, // []chanSelectState
212-
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
212+
opsPtr, opsLen, opsLen, // []channelOp
213213
}, "select.result")
214214

215215
// Terminate the lifetime of the operation structures.
216-
b.emitLifetimeEnd(chBlockAlloca, chBlockSize)
216+
b.emitLifetimeEnd(opsAlloca, opsSize)
217217
} else {
218-
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
218+
opsPtr := llvm.ConstNull(b.dataPtrType)
219+
opsLen := llvm.ConstInt(b.uintptrType, 0, false)
220+
results = b.createRuntimeCall("chanSelect", []llvm.Value{
219221
recvbuf,
220222
statesPtr, statesLen, statesLen, // []chanSelectState
223+
opsPtr, opsLen, opsLen, // []channelOp (nil slice)
221224
}, "select.result")
222225
}
223226

compiler/testdata/channel.ll

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ source_filename = "channel.go"
33
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
44
target triple = "wasm32-unknown-wasi"
55

6-
%runtime.channelBlockedList = type { ptr, ptr, ptr, { ptr, i32, i32 } }
6+
%runtime.channelOp = type { ptr, ptr, i32, ptr }
77
%runtime.chanSelectState = type { ptr, ptr }
88

99
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
@@ -18,64 +18,64 @@ entry:
1818
}
1919

2020
; Function Attrs: nounwind
21-
define hidden void @main.chanIntSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
21+
define hidden void @main.chanIntSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
2222
entry:
23-
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
23+
%chan.op = alloca %runtime.channelOp, align 8
2424
%chan.value = alloca i32, align 4
2525
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value)
2626
store i32 3, ptr %chan.value, align 4
27-
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
28-
call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4
29-
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
27+
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
28+
call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4
29+
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
3030
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value)
3131
ret void
3232
}
3333

3434
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
3535
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3
3636

37-
declare void @runtime.chanSend(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1
37+
declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1
3838

3939
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
4040
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3
4141

4242
; Function Attrs: nounwind
43-
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
43+
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
4444
entry:
45-
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
45+
%chan.op = alloca %runtime.channelOp, align 8
4646
%chan.value = alloca i32, align 4
4747
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value)
48-
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
49-
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4
48+
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
49+
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4
5050
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value)
51-
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
51+
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
5252
ret void
5353
}
5454

55-
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1
55+
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1
5656

5757
; Function Attrs: nounwind
58-
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
58+
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
5959
entry:
60-
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
61-
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
62-
call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4
63-
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
60+
%chan.op = alloca %runtime.channelOp, align 8
61+
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
62+
call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4
63+
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
6464
ret void
6565
}
6666

6767
; Function Attrs: nounwind
68-
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
68+
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
6969
entry:
70-
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
71-
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
72-
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4
73-
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
70+
%chan.op = alloca %runtime.channelOp, align 8
71+
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
72+
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4
73+
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
7474
ret void
7575
}
7676

7777
; Function Attrs: nounwind
78-
define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(32) %ch1, ptr dereferenceable_or_null(32) %ch2, ptr %context) unnamed_addr #2 {
78+
define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(36) %ch1, ptr dereferenceable_or_null(36) %ch2, ptr %context) unnamed_addr #2 {
7979
entry:
8080
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
8181
%select.send.value = alloca i32, align 4
@@ -88,7 +88,7 @@ entry:
8888
store ptr %ch2, ptr %0, align 4
8989
%.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1, i32 1
9090
store ptr null, ptr %.repack3, align 4
91-
%select.result = call { i32, i1 } @runtime.tryChanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr undef) #4
91+
%select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #4
9292
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca)
9393
%1 = extractvalue { i32, i1 } %select.result, 0
9494
%2 = icmp eq i32 %1, 0
@@ -105,7 +105,7 @@ select.body: ; preds = %select.next
105105
br label %select.done
106106
}
107107

108-
declare { i32, i1 } @runtime.tryChanSelect(ptr, ptr, i32, i32, ptr) #1
108+
declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #1
109109

110110
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
111111
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }

compiler/testdata/goroutine-cortex-m-qemu-tasks.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,13 @@ entry:
135135
declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #2
136136

137137
; Function Attrs: nounwind
138-
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #1 {
138+
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
139139
entry:
140140
call void @runtime.chanClose(ptr %ch, ptr undef) #9
141141
ret void
142142
}
143143

144-
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #2
144+
declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #2
145145

146146
; Function Attrs: nounwind
147147
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {

compiler/testdata/goroutine-wasm-asyncify.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,13 @@ entry:
144144
declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #1
145145

146146
; Function Attrs: nounwind
147-
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
147+
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
148148
entry:
149149
call void @runtime.chanClose(ptr %ch, ptr undef) #9
150150
ret void
151151
}
152152

153-
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #1
153+
declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1
154154

155155
; Function Attrs: nounwind
156156
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 {

0 commit comments

Comments
 (0)