aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2021-09-08 17:07:10 +0200
committerRon Evans <[email protected]>2021-09-09 11:24:52 +0200
commit6315db21f7d85f4766589d5b4c8ca6c54aa8e7a4 (patch)
treea37c12cc9c9a118e2e788b3626c59fdf0769de8f
parent485a9284e74ea9ff8f0f37f2402ae80448b33f10 (diff)
downloadtinygo-6315db21f7d85f4766589d5b4c8ca6c54aa8e7a4.tar.gz
tinygo-6315db21f7d85f4766589d5b4c8ca6c54aa8e7a4.zip
compiler: avoid zero-sized alloca in channel operations
This works around a bug in LLVM (https://bugs.llvm.org/show_bug.cgi?id=49916) but seems like a good change in general.
-rw-r--r--compiler/channel.go37
-rw-r--r--compiler/compiler.go2
-rw-r--r--compiler/compiler_test.go1
-rw-r--r--compiler/testdata/channel.go25
-rw-r--r--compiler/testdata/channel.ll114
5 files changed, 169 insertions, 10 deletions
diff --git a/compiler/channel.go b/compiler/channel.go
index a036fbf45..a9886e100 100644
--- a/compiler/channel.go
+++ b/compiler/channel.go
@@ -32,8 +32,15 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// store value-to-send
valueType := b.getLLVMType(instr.X.Type())
- valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
- b.CreateStore(chanValue, valueAlloca)
+ isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
+ var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
+ if isZeroSize {
+ valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
+ valueAllocaCast = llvm.ConstNull(b.i8ptrType)
+ } else {
+ valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
+ b.CreateStore(chanValue, valueAlloca)
+ }
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
@@ -46,7 +53,9 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
- b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
+ if !isZeroSize {
+ b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
+ }
}
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
@@ -56,7 +65,14 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
ch := b.getValue(unop.X)
// Allocate memory to receive into.
- valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
+ isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
+ var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
+ if isZeroSize {
+ valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
+ valueAllocaCast = llvm.ConstNull(b.i8ptrType)
+ } else {
+ valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
+ }
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
@@ -64,9 +80,14 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
// Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
- received := b.CreateLoad(valueAlloca, "chan.received")
+ var received llvm.Value
+ if isZeroSize {
+ received = llvm.ConstNull(valueType)
+ } else {
+ received = b.CreateLoad(valueAlloca, "chan.received")
+ b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
+ }
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
- b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
@@ -116,7 +137,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// determine the receive buffer size and alignment.
recvbufSize := uint64(0)
recvbufAlign := 0
- hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
@@ -133,7 +153,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align
}
- hasReceives = true
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
@@ -150,7 +169,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.i8ptrType)
- if hasReceives {
+ if recvbufSize != 0 {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
diff --git a/compiler/compiler.go b/compiler/compiler.go
index c0ad39d46..1ca798d19 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -23,7 +23,7 @@ import (
// Version of the compiler pacakge. Must be incremented each time the compiler
// package changes in a way that affects the generated LLVM module.
// This version is independent of the TinyGo version number.
-const Version = 18 // last change: fix duplicated named structs
+const Version = 19 // last change: fix channel ops with zero values
func init() {
llvm.InitializeAllTargets()
diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go
index 0fb9fa913..a4ac0f7b5 100644
--- a/compiler/compiler_test.go
+++ b/compiler/compiler_test.go
@@ -51,6 +51,7 @@ func TestCompiler(t *testing.T) {
{"pragma.go", ""},
{"goroutine.go", "wasm"},
{"goroutine.go", "cortex-m-qemu"},
+ {"channel.go", ""},
{"intrinsics.go", "cortex-m-qemu"},
{"intrinsics.go", "wasm"},
}
diff --git a/compiler/testdata/channel.go b/compiler/testdata/channel.go
new file mode 100644
index 000000000..ecc837a71
--- /dev/null
+++ b/compiler/testdata/channel.go
@@ -0,0 +1,25 @@
+package main
+
+func chanIntSend(ch chan int) {
+ ch <- 3
+}
+
+func chanIntRecv(ch chan int) {
+ <-ch
+}
+
+func chanZeroSend(ch chan struct{}) {
+ ch <- struct{}{}
+}
+
+func chanZeroRecv(ch chan struct{}) {
+ <-ch
+}
+
+func selectZeroRecv(ch1 chan int, ch2 chan struct{}) {
+ select {
+ case ch1 <- 1:
+ case <-ch2:
+ default:
+ }
+}
diff --git a/compiler/testdata/channel.ll b/compiler/testdata/channel.ll
new file mode 100644
index 000000000..0d33d330b
--- /dev/null
+++ b/compiler/testdata/channel.ll
@@ -0,0 +1,114 @@
+; ModuleID = 'channel.go'
+source_filename = "channel.go"
+target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
+target triple = "wasm32--wasi"
+
+%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
+%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
+%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
+%"internal/task.state" = type { i8* }
+%runtime.chanSelectState = type { %runtime.channel*, i8* }
+
+declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
+
+define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
+entry:
+ ret void
+}
+
+define hidden void @main.chanIntSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
+entry:
+ %chan.blockedList = alloca %runtime.channelBlockedList, align 8
+ %chan.value = alloca i32, align 4
+ %chan.value.bitcast = bitcast i32* %chan.value to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
+ store i32 3, i32* %chan.value, align 4
+ %chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
+ call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
+ call void @runtime.chanSend(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
+ call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
+ ret void
+}
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #0
+
+declare void @runtime.chanSend(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
+
+; Function Attrs: argmemonly nounwind willreturn
+declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #0
+
+define hidden void @main.chanIntRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
+entry:
+ %chan.blockedList = alloca %runtime.channelBlockedList, align 8
+ %chan.value = alloca i32, align 4
+ %chan.value.bitcast = bitcast i32* %chan.value to i8*
+ call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
+ %chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
+ call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
+ %0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
+ call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
+ call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
+ ret void
+}
+
+declare i1 @runtime.chanRecv(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
+
+define hidden void @main.chanZeroSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
+entry:
+ %chan.blockedList = alloca %runtime.channelBlockedList, align 8
+ %chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
+ call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
+ call void @runtime.chanSend(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
+ call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
+ ret void
+}
+
+define hidden void @main.chanZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
+entry:
+ %chan.blockedList = alloca %runtime.channelBlockedList, align 8
+ %chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
+ call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
+ %0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
+ call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
+ ret void
+}
+
+define hidden void @main.selectZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch1, %runtime.channel* dereferenceable_or_null(32) %ch2, i8* %context, i8* %parentHandle) unnamed_addr {
+entry:
+ %select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
+ %select.send.value = alloca i32, align 4
+ store i32 1, i32* %select.send.value, align 4
+ %select.states.alloca.bitcast = bitcast [2 x %runtime.chanSelectState]* %select.states.alloca to i8*
+ call void @llvm.lifetime.start.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
+ %.repack = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 0
+ store %runtime.channel* %ch1, %runtime.channel** %.repack, align 8
+ %.repack1 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 1
+ %0 = bitcast i8** %.repack1 to i32**
+ store i32* %select.send.value, i32** %0, align 4
+ %.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 0
+ store %runtime.channel* %ch2, %runtime.channel** %.repack3, align 8
+ %.repack4 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 1
+ store i8* null, i8** %.repack4, align 4
+ %select.states = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0
+ %select.result = call { i32, i1 } @runtime.tryChanSelect(i8* undef, %runtime.chanSelectState* nonnull %select.states, i32 2, i32 2, i8* undef, i8* null)
+ call void @llvm.lifetime.end.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
+ %1 = extractvalue { i32, i1 } %select.result, 0
+ %2 = icmp eq i32 %1, 0
+ br i1 %2, label %select.done, label %select.next
+
+select.done: ; preds = %select.body, %select.next, %entry
+ ret void
+
+select.next: ; preds = %entry
+ %3 = icmp eq i32 %1, 1
+ br i1 %3, label %select.body, label %select.done
+
+select.body: ; preds = %select.next
+ br label %select.done
+}
+
+declare { i32, i1 } @runtime.tryChanSelect(i8*, %runtime.chanSelectState*, i32, i32, i8*, i8*)
+
+attributes #0 = { argmemonly nounwind willreturn }