Commit 873aaa59 authored by Keith Randall's avatar Keith Randall

reflect: Remove imprecise techniques from channel/select operations.

Reflect used to communicate to the runtime using interface words,
which is bad for precise GC because sometimes iwords hold a pointer
and sometimes they don't.  This change rewrites channel and select
operations to always pass pointers to the runtime.

reflect.Select gets somewhat more expensive, as we now do an allocation
per receive case instead of one allocation whose size is the max of
all the received types.  This seems unavoidable to get preciseness
(unless we move the allocation into selectgo, which is a much bigger
change).

Fixes #6490

R=golang-codereviews, dvyukov, rsc
CC=golang-codereviews
https://golang.org/cl/52900043
parent 36477291
...@@ -212,51 +212,11 @@ func methodName() string { ...@@ -212,51 +212,11 @@ func methodName() string {
// bigger than a pointer, its word is a pointer to v's data. // bigger than a pointer, its word is a pointer to v's data.
// Otherwise, its word holds the data stored // Otherwise, its word holds the data stored
// in its leading bytes (so is not a pointer). // in its leading bytes (so is not a pointer).
// Because the value sometimes holds a pointer, we use // This type is very dangerous for the garbage collector because
// unsafe.Pointer to represent it, so that if iword appears // it must be treated conservatively. We try to never expose it
// in a struct, the garbage collector knows that might be // to the GC here so that GC remains precise.
// a pointer.
// TODO: get rid of all occurrences of iword (except in the interface decls below?)
// We want to get rid of the "feature" that an unsafe.Pointer is sometimes a pointer
// and sometimes a uintptr.
type iword unsafe.Pointer type iword unsafe.Pointer
// Get an iword that represents this value.
// TODO: this function goes away at some point
func (v Value) iword() iword {
t := v.typ
if t == nil {
return iword(nil)
}
if v.flag&flagIndir != 0 {
if v.typ.size > ptrSize {
return iword(v.ptr)
}
// Have indirect but want direct word.
if t.pointers() {
return iword(*(*unsafe.Pointer)(v.ptr))
}
return iword(loadScalar(v.ptr, v.typ.size))
}
if t.pointers() {
return iword(v.ptr)
}
return iword(v.scalar)
}
// Build a Value from a type/iword pair, plus any extra flags.
// TODO: this function goes away at some point
func fromIword(t *rtype, w iword, fl flag) Value {
fl |= flag(t.Kind()) << flagKindShift
if t.size > ptrSize {
return Value{t, unsafe.Pointer(w), 0, fl | flagIndir}
} else if t.pointers() {
return Value{t, unsafe.Pointer(w), 0, fl}
} else {
return Value{t, nil, uintptr(w), fl}
}
}
// loadScalar loads n bytes at p from memory into a uintptr // loadScalar loads n bytes at p from memory into a uintptr
// that forms the second word of an interface. The data // that forms the second word of an interface. The data
// must be non-pointer in nature. // must be non-pointer in nature.
...@@ -1458,9 +1418,21 @@ func (v Value) recv(nb bool) (val Value, ok bool) { ...@@ -1458,9 +1418,21 @@ func (v Value) recv(nb bool) (val Value, ok bool) {
if ChanDir(tt.dir)&RecvDir == 0 { if ChanDir(tt.dir)&RecvDir == 0 {
panic("reflect: recv on send-only channel") panic("reflect: recv on send-only channel")
} }
word, selected, ok := chanrecv(v.typ, v.pointer(), nb) t := tt.elem
if selected { val = Value{t, nil, 0, flag(t.Kind()) << flagKindShift}
val = fromIword(tt.elem, word, 0) var p unsafe.Pointer
if t.size > ptrSize {
p = unsafe_New(t)
val.ptr = p
val.flag |= flagIndir
} else if t.pointers() {
p = unsafe.Pointer(&val.ptr)
} else {
p = unsafe.Pointer(&val.scalar)
}
selected, ok := chanrecv(v.typ, v.pointer(), nb, p)
if !selected {
val = Value{}
} }
return return
} }
...@@ -1483,7 +1455,15 @@ func (v Value) send(x Value, nb bool) (selected bool) { ...@@ -1483,7 +1455,15 @@ func (v Value) send(x Value, nb bool) (selected bool) {
} }
x.mustBeExported() x.mustBeExported()
x = x.assignTo("reflect.Value.Send", tt.elem, nil) x = x.assignTo("reflect.Value.Send", tt.elem, nil)
return chansend(v.typ, v.pointer(), x.iword(), nb) var p unsafe.Pointer
if x.flag&flagIndir != 0 {
p = x.ptr
} else if x.typ.pointers() {
p = unsafe.Pointer(&x.ptr)
} else {
p = unsafe.Pointer(&x.scalar)
}
return chansend(v.typ, v.pointer(), p, nb)
} }
// Set assigns x to the value v. // Set assigns x to the value v.
...@@ -2051,15 +2031,16 @@ func Copy(dst, src Value) int { ...@@ -2051,15 +2031,16 @@ func Copy(dst, src Value) int {
type runtimeSelect struct { type runtimeSelect struct {
dir uintptr // 0, SendDir, or RecvDir dir uintptr // 0, SendDir, or RecvDir
typ *rtype // channel type typ *rtype // channel type
ch iword // interface word for channel ch unsafe.Pointer // channel
val iword // interface word for value (for SendDir) val unsafe.Pointer // ptr to data (SendDir) or ptr to receive buffer (RecvDir)
} }
// rselect runs a select. It returns the index of the chosen case, // rselect runs a select. It returns the index of the chosen case.
// and if the case was a receive, the interface word of the received // If the case was a receive, val is filled in with the received value.
// value and the conventional OK bool to indicate whether the receive // The conventional OK bool indicates whether the receive corresponds
// corresponds to a sent value. // to a sent value.
func rselect([]runtimeSelect) (chosen int, recv iword, recvOK bool) //go:noescape
func rselect([]runtimeSelect) (chosen int, recvOK bool)
// A SelectDir describes the communication direction of a select case. // A SelectDir describes the communication direction of a select case.
type SelectDir int type SelectDir int
...@@ -2139,7 +2120,7 @@ func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) { ...@@ -2139,7 +2120,7 @@ func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) {
if ChanDir(tt.dir)&SendDir == 0 { if ChanDir(tt.dir)&SendDir == 0 {
panic("reflect.Select: SendDir case using recv-only channel") panic("reflect.Select: SendDir case using recv-only channel")
} }
rc.ch = ch.iword() rc.ch = ch.pointer()
rc.typ = &tt.rtype rc.typ = &tt.rtype
v := c.Send v := c.Send
if !v.IsValid() { if !v.IsValid() {
...@@ -2147,7 +2128,13 @@ func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) { ...@@ -2147,7 +2128,13 @@ func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) {
} }
v.mustBeExported() v.mustBeExported()
v = v.assignTo("reflect.Select", tt.elem, nil) v = v.assignTo("reflect.Select", tt.elem, nil)
rc.val = v.iword() if v.flag&flagIndir != 0 {
rc.val = v.ptr
} else if v.typ.pointers() {
rc.val = unsafe.Pointer(&v.ptr)
} else {
rc.val = unsafe.Pointer(&v.scalar)
}
case SelectRecv: case SelectRecv:
if c.Send.IsValid() { if c.Send.IsValid() {
...@@ -2160,18 +2147,28 @@ func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) { ...@@ -2160,18 +2147,28 @@ func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) {
ch.mustBe(Chan) ch.mustBe(Chan)
ch.mustBeExported() ch.mustBeExported()
tt := (*chanType)(unsafe.Pointer(ch.typ)) tt := (*chanType)(unsafe.Pointer(ch.typ))
rc.typ = &tt.rtype
if ChanDir(tt.dir)&RecvDir == 0 { if ChanDir(tt.dir)&RecvDir == 0 {
panic("reflect.Select: RecvDir case using send-only channel") panic("reflect.Select: RecvDir case using send-only channel")
} }
rc.ch = ch.iword() rc.ch = ch.pointer()
rc.typ = &tt.rtype
rc.val = unsafe_New(tt.elem)
} }
} }
chosen, word, recvOK := rselect(runcases) chosen, recvOK = rselect(runcases)
if runcases[chosen].dir == uintptr(SelectRecv) { if runcases[chosen].dir == uintptr(SelectRecv) {
tt := (*chanType)(unsafe.Pointer(runcases[chosen].typ)) tt := (*chanType)(unsafe.Pointer(runcases[chosen].typ))
recv = fromIword(tt.elem, word, 0) t := tt.elem
p := runcases[chosen].val
fl := flag(t.Kind()) << flagKindShift
if t.size > ptrSize {
recv = Value{t, p, 0, fl | flagIndir}
} else if t.pointers() {
recv = Value{t, *(*unsafe.Pointer)(p), 0, fl}
} else {
recv = Value{t, nil, loadScalar(p, t.size), fl}
}
} }
return chosen, recv, recvOK return chosen, recv, recvOK
} }
...@@ -2624,8 +2621,12 @@ func cvtI2I(v Value, typ Type) Value { ...@@ -2624,8 +2621,12 @@ func cvtI2I(v Value, typ Type) Value {
func chancap(ch unsafe.Pointer) int func chancap(ch unsafe.Pointer) int
func chanclose(ch unsafe.Pointer) func chanclose(ch unsafe.Pointer)
func chanlen(ch unsafe.Pointer) int func chanlen(ch unsafe.Pointer) int
func chanrecv(t *rtype, ch unsafe.Pointer, nb bool) (val iword, selected, received bool)
func chansend(t *rtype, ch unsafe.Pointer, val iword, nb bool) bool //go:noescape
func chanrecv(t *rtype, ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, received bool)
//go:noescape
func chansend(t *rtype, ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool
func makechan(typ *rtype, size uint64) (ch unsafe.Pointer) func makechan(typ *rtype, size uint64) (ch unsafe.Pointer)
func makemap(t *rtype) (m unsafe.Pointer) func makemap(t *rtype) (m unsafe.Pointer)
......
...@@ -541,18 +541,16 @@ runtime·selectnbrecv2(ChanType *t, byte *v, bool *received, Hchan *c, bool sele ...@@ -541,18 +541,16 @@ runtime·selectnbrecv2(ChanType *t, byte *v, bool *received, Hchan *c, bool sele
} }
// For reflect: // For reflect:
// func chansend(c chan, val iword, nb bool) (selected bool) // func chansend(c chan, val *any, nb bool) (selected bool)
// where an iword is the same word an interface value would use: // where val points to the data to be sent.
// the actual data if it fits, or else a pointer to the data.
// //
// The "uintptr selected" is really "bool selected" but saying // The "uintptr selected" is really "bool selected" but saying
// uintptr gets us the right alignment for the output parameter block. // uintptr gets us the right alignment for the output parameter block.
#pragma textflag NOSPLIT #pragma textflag NOSPLIT
void void
reflect·chansend(ChanType *t, Hchan *c, uintptr val, bool nb, uintptr selected) reflect·chansend(ChanType *t, Hchan *c, byte *val, bool nb, uintptr selected)
{ {
bool *sp; bool *sp;
byte *vp;
if(nb) { if(nb) {
selected = false; selected = false;
...@@ -562,21 +560,16 @@ reflect·chansend(ChanType *t, Hchan *c, uintptr val, bool nb, uintptr selected) ...@@ -562,21 +560,16 @@ reflect·chansend(ChanType *t, Hchan *c, uintptr val, bool nb, uintptr selected)
FLUSH(&selected); FLUSH(&selected);
sp = nil; sp = nil;
} }
if(t->elem->size <= sizeof(val)) runtime·chansend(t, c, val, sp, runtime·getcallerpc(&t));
vp = (byte*)&val;
else
vp = (byte*)val;
runtime·chansend(t, c, vp, sp, runtime·getcallerpc(&t));
} }
// For reflect: // For reflect:
// func chanrecv(c chan, nb bool) (val iword, selected, received bool) // func chanrecv(c chan, nb bool, val *any) (selected, received bool)
// where an iword is the same word an interface value would use: // where val points to a data area that will be filled in with the
// the actual data if it fits, or else a pointer to the data. // received value. val must have the size and type of the channel element type.
void void
reflect·chanrecv(ChanType *t, Hchan *c, bool nb, uintptr val, bool selected, bool received) reflect·chanrecv(ChanType *t, Hchan *c, bool nb, byte *val, bool selected, bool received)
{ {
byte *vp;
bool *sp; bool *sp;
if(nb) { if(nb) {
...@@ -589,15 +582,7 @@ reflect·chanrecv(ChanType *t, Hchan *c, bool nb, uintptr val, bool selected, bo ...@@ -589,15 +582,7 @@ reflect·chanrecv(ChanType *t, Hchan *c, bool nb, uintptr val, bool selected, bo
} }
received = false; received = false;
FLUSH(&received); FLUSH(&received);
if(t->elem->size <= sizeof(val)) { runtime·chanrecv(t, c, val, sp, &received);
val = 0;
vp = (byte*)&val;
} else {
vp = runtime·mal(t->elem->size);
val = (uintptr)vp;
FLUSH(&val);
}
runtime·chanrecv(t, c, vp, sp, &received);
} }
static void newselect(int32, Select**); static void newselect(int32, Select**);
...@@ -1150,7 +1135,7 @@ struct runtimeSelect ...@@ -1150,7 +1135,7 @@ struct runtimeSelect
uintptr dir; uintptr dir;
ChanType *typ; ChanType *typ;
Hchan *ch; Hchan *ch;
uintptr val; byte *val;
}; };
// This enum must match ../reflect/value.go:/SelectDir. // This enum must match ../reflect/value.go:/SelectDir.
...@@ -1160,32 +1145,18 @@ enum SelectDir { ...@@ -1160,32 +1145,18 @@ enum SelectDir {
SelectDefault, SelectDefault,
}; };
// func rselect(cases []runtimeSelect) (chosen int, word uintptr, recvOK bool) // func rselect(cases []runtimeSelect) (chosen int, recvOK bool)
void void
reflect·rselect(Slice cases, intgo chosen, uintptr word, bool recvOK) reflect·rselect(Slice cases, intgo chosen, bool recvOK)
{ {
int32 i; int32 i;
Select *sel; Select *sel;
runtimeSelect* rcase, *rc; runtimeSelect* rcase, *rc;
void *elem;
void *recvptr;
uintptr maxsize;
chosen = -1; chosen = -1;
word = 0;
recvOK = false; recvOK = false;
maxsize = 0;
rcase = (runtimeSelect*)cases.array; rcase = (runtimeSelect*)cases.array;
for(i=0; i<cases.len; i++) {
rc = &rcase[i];
if(rc->dir == SelectRecv && rc->ch != nil && maxsize < rc->typ->elem->size)
maxsize = rc->typ->elem->size;
}
recvptr = nil;
if(maxsize > sizeof(void*))
recvptr = runtime·mal(maxsize);
newselect(cases.len, &sel); newselect(cases.len, &sel);
for(i=0; i<cases.len; i++) { for(i=0; i<cases.len; i++) {
...@@ -1197,30 +1168,19 @@ reflect·rselect(Slice cases, intgo chosen, uintptr word, bool recvOK) ...@@ -1197,30 +1168,19 @@ reflect·rselect(Slice cases, intgo chosen, uintptr word, bool recvOK)
case SelectSend: case SelectSend:
if(rc->ch == nil) if(rc->ch == nil)
break; break;
if(rc->typ->elem->size > sizeof(void*)) selectsend(sel, rc->ch, (void*)i, rc->val, 0);
elem = (void*)rc->val;
else
elem = (void*)&rc->val;
selectsend(sel, rc->ch, (void*)i, elem, 0);
break; break;
case SelectRecv: case SelectRecv:
if(rc->ch == nil) if(rc->ch == nil)
break; break;
if(rc->typ->elem->size > sizeof(void*)) selectrecv(sel, rc->ch, (void*)i, rc->val, &recvOK, 0);
elem = recvptr;
else
elem = &word;
selectrecv(sel, rc->ch, (void*)i, elem, &recvOK, 0);
break; break;
} }
} }
chosen = (intgo)(uintptr)selectgo(&sel); chosen = (intgo)(uintptr)selectgo(&sel);
if(rcase[chosen].dir == SelectRecv && rcase[chosen].typ->elem->size > sizeof(void*))
word = (uintptr)recvptr;
FLUSH(&chosen); FLUSH(&chosen);
FLUSH(&word);
FLUSH(&recvOK); FLUSH(&recvOK);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment