[llgo] Elide alloca for unused received values in select

Summary: If a receive case in a select statement is not assigned to a named variable, then we can eliminate the alloca and copy at runtime.

Test Plan: lit test added

Reviewers: pcc

Reviewed By: pcc

Subscribers: llvm-commits

Differential Revision: http://reviews.llvm.org/D6785

llvm-svn: 225033
This commit is contained in:
Andrew Wilkins 2014-12-31 03:46:49 +00:00
parent 553185ee4b
commit 75f34af99c
3 changed files with 50 additions and 29 deletions

View File

@ -14,6 +14,7 @@
package irgen package irgen
import ( import (
"llvm.org/llgo/third_party/go.tools/go/ssa"
"llvm.org/llgo/third_party/go.tools/go/types" "llvm.org/llgo/third_party/go.tools/go/types"
"llvm.org/llvm/bindings/go/llvm" "llvm.org/llvm/bindings/go/llvm"
) )
@ -60,16 +61,9 @@ func (fr *frame) chanClose(ch *govalue) {
fr.runtime.builtinClose.call(fr, ch.value) fr.runtime.builtinClose.call(fr, ch.value)
} }
// selectState is equivalent to ssa.SelectState func (fr *frame) chanSelect(sel *ssa.Select) (index, recvOk *govalue, recvElems []*govalue) {
type selectState struct { n := uint64(len(sel.States))
Dir types.ChanDir if !sel.Blocking {
Chan *govalue
Send *govalue
}
func (fr *frame) chanSelect(states []selectState, blocking bool) (index, recvOk *govalue, recvElems []*govalue) {
n := uint64(len(states))
if !blocking {
// non-blocking means there's a default case // non-blocking means there's a default case
n++ n++
} }
@ -77,17 +71,21 @@ func (fr *frame) chanSelect(states []selectState, blocking bool) (index, recvOk
selectp := fr.runtime.newSelect.call(fr, size)[0] selectp := fr.runtime.newSelect.call(fr, size)[0]
// Allocate stack for the values to send and receive. // Allocate stack for the values to send and receive.
// ptrs := make([]llvm.Value, len(sel.States))
// TODO(axw) check if received elements have any users, and for i, state := range sel.States {
// elide stack allocation if not (pass nil to recv2 instead.)
ptrs := make([]llvm.Value, len(states))
for i, state := range states {
chantyp := state.Chan.Type().Underlying().(*types.Chan) chantyp := state.Chan.Type().Underlying().(*types.Chan)
elemtyp := fr.types.ToLLVM(chantyp.Elem()) elemtyp := fr.types.ToLLVM(chantyp.Elem())
ptrs[i] = fr.allocaBuilder.CreateAlloca(elemtyp, "")
if state.Dir == types.SendOnly { if state.Dir == types.SendOnly {
fr.builder.CreateStore(state.Send.value, ptrs[i]) ptrs[i] = fr.allocaBuilder.CreateAlloca(elemtyp, "")
fr.builder.CreateStore(fr.llvmvalue(state.Send), ptrs[i])
} else { } else {
// Only allocate stack space if the received value is used.
used := chanSelectStateUsed(sel, len(recvElems))
if used {
ptrs[i] = fr.allocaBuilder.CreateAlloca(elemtyp, "")
} else {
ptrs[i] = llvm.ConstNull(llvm.PointerType(llvm.Int8Type(), 0))
}
recvElems = append(recvElems, newValue(ptrs[i], chantyp.Elem())) recvElems = append(recvElems, newValue(ptrs[i], chantyp.Elem()))
} }
} }
@ -97,12 +95,12 @@ func (fr *frame) chanSelect(states []selectState, blocking bool) (index, recvOk
if len(recvElems) > 0 { if len(recvElems) > 0 {
receivedp = fr.allocaBuilder.CreateAlloca(fr.types.ToLLVM(types.Typ[types.Bool]), "") receivedp = fr.allocaBuilder.CreateAlloca(fr.types.ToLLVM(types.Typ[types.Bool]), "")
} }
if !blocking { if !sel.Blocking {
// If the default case is chosen, the index must be -1. // If the default case is chosen, the index must be -1.
fr.runtime.selectdefault.call(fr, selectp, llvm.ConstAllOnes(llvm.Int32Type())) fr.runtime.selectdefault.call(fr, selectp, llvm.ConstAllOnes(llvm.Int32Type()))
} }
for i, state := range states { for i, state := range sel.States {
ch := state.Chan.value ch := fr.llvmvalue(state.Chan)
index := llvm.ConstInt(llvm.Int32Type(), uint64(i), false) index := llvm.ConstInt(llvm.Int32Type(), uint64(i), false)
if state.Dir == types.SendOnly { if state.Dir == types.SendOnly {
fr.runtime.selectsend.call(fr, selectp, ch, ptrs[i], index) fr.runtime.selectsend.call(fr, selectp, ch, ptrs[i], index)
@ -121,3 +119,16 @@ func (fr *frame) chanSelect(states []selectState, blocking bool) (index, recvOk
} }
return index, recvOk, recvElems return index, recvOk, recvElems
} }
func chanSelectStateUsed(sel *ssa.Select, recvIndex int) bool {
for _, instr := range *sel.Referrers() {
extract, ok := instr.(*ssa.Extract)
if !ok || extract.Index != (recvIndex+2) {
continue
}
if len(*extract.Referrers()) > 0 {
return true
}
}
return false
}

View File

@ -1152,15 +1152,7 @@ func (fr *frame) instruction(instr ssa.Instruction) {
fr.runDefers() fr.runDefers()
case *ssa.Select: case *ssa.Select:
states := make([]selectState, len(instr.States)) index, recvOk, recvElems := fr.chanSelect(instr)
for i, state := range instr.States {
states[i] = selectState{
Dir: state.Dir,
Chan: fr.value(state.Chan),
Send: fr.value(state.Send),
}
}
index, recvOk, recvElems := fr.chanSelect(states, instr.Blocking)
tuple := append([]*govalue{index, recvOk}, recvElems...) tuple := append([]*govalue{index, recvOk}, recvElems...)
fr.tuples[instr] = tuple fr.tuples[instr] = tuple

18
llgo/test/irgen/select.go Normal file
View File

@ -0,0 +1,18 @@
// RUN: llgo -S -emit-llvm -o - %s | FileCheck %s
package foo
// CHECK-NOT: alloca [1024 x i8]
// CHECK-NOT: alloca [2048 x i8]
// CHECK: alloca [4096 x i8]
func F() {
ch1 := make(chan [1024]byte)
ch2 := make(chan [2048]byte)
ch3 := make(chan [4096]byte)
select {
case <-ch1:
case _ = <-ch2:
case x := <-ch3:
_ = x[0]
}
}