mirror of https://github.com/golang/go.git
reflect: add precise GC info for Call argument frame.
Give proper types to the argument/return areas allocated for reflect calls. Avoid use of iword to manipulate receivers, which may or may not be pointers. Update #6490 R=rsc CC=golang-codereviews https://golang.org/cl/52110044
This commit is contained in:
parent
35710eecd6
commit
2af7a26f1e
|
|
@ -1803,3 +1803,94 @@ func toType(t *rtype) Type {
|
|||
}
|
||||
return t
|
||||
}
|
||||
|
||||
type layoutKey struct {
|
||||
t *rtype // function signature
|
||||
rcvr *rtype // receiver type, or nil if none
|
||||
}
|
||||
|
||||
var layoutCache struct {
|
||||
sync.RWMutex
|
||||
m map[layoutKey]*rtype
|
||||
}
|
||||
|
||||
// funcLayout computes a struct type representing the layout of the
|
||||
// function arguments and return values for the function type t.
|
||||
// If rcvr != nil, rcvr specifies the type of the receiver.
|
||||
// The returned type exists only for GC, so we only fill out GC relevant info.
|
||||
// Currently, that's just size and the GC program. We also fill in
|
||||
// the name for possible debugging use.
|
||||
func funcLayout(t *rtype, rcvr *rtype) *rtype {
|
||||
if t.Kind() != Func {
|
||||
panic("reflect: funcSignature of non-func type")
|
||||
}
|
||||
k := layoutKey{t, rcvr}
|
||||
layoutCache.RLock()
|
||||
if x := layoutCache.m[k]; x != nil {
|
||||
layoutCache.RUnlock()
|
||||
return x
|
||||
}
|
||||
layoutCache.RUnlock()
|
||||
layoutCache.Lock()
|
||||
if x := layoutCache.m[k]; x != nil {
|
||||
layoutCache.Unlock()
|
||||
return x
|
||||
}
|
||||
|
||||
tt := (*funcType)(unsafe.Pointer(t))
|
||||
|
||||
// compute gc program for arguments
|
||||
gc := make([]uintptr, 1) // first entry is size, filled in at the end
|
||||
offset := uintptr(0)
|
||||
if rcvr != nil {
|
||||
// Reflect uses the "interface" calling convention for
|
||||
// methods, where receivers take one word of argument
|
||||
// space no matter how big they actually are.
|
||||
if rcvr.size > ptrSize {
|
||||
// we pass a pointer to the receiver.
|
||||
gc = append(gc, _GC_PTR, offset, uintptr(rcvr.gc))
|
||||
} else if rcvr.pointers() {
|
||||
// rcvr is a one-word pointer object. Its gc program
|
||||
// is just what we need here.
|
||||
gc = appendGCProgram(gc, rcvr)
|
||||
}
|
||||
offset += ptrSize
|
||||
}
|
||||
for _, arg := range tt.in {
|
||||
offset = align(offset, uintptr(arg.align))
|
||||
if arg.pointers() {
|
||||
gc = append(gc, _GC_REGION, offset, arg.size, uintptr(arg.gc))
|
||||
}
|
||||
offset += arg.size
|
||||
}
|
||||
offset = align(offset, ptrSize)
|
||||
for _, res := range tt.out {
|
||||
offset = align(offset, uintptr(res.align))
|
||||
if res.pointers() {
|
||||
gc = append(gc, _GC_REGION, offset, res.size, uintptr(res.gc))
|
||||
}
|
||||
offset += res.size
|
||||
}
|
||||
gc = append(gc, _GC_END)
|
||||
gc[0] = offset
|
||||
|
||||
// build dummy rtype holding gc program
|
||||
x := new(rtype)
|
||||
x.size = offset
|
||||
x.gc = unsafe.Pointer(&gc[0])
|
||||
var s string
|
||||
if rcvr != nil {
|
||||
s = "methodargs(" + *rcvr.string + ")(" + *t.string + ")"
|
||||
} else {
|
||||
s = "funcargs(" + *t.string + ")"
|
||||
}
|
||||
x.string = &s
|
||||
|
||||
// cache result for future callers
|
||||
if layoutCache.m == nil {
|
||||
layoutCache.m = make(map[layoutKey]*rtype)
|
||||
}
|
||||
layoutCache.m[k] = x
|
||||
layoutCache.Unlock()
|
||||
return x
|
||||
}
|
||||
|
|
|
|||
|
|
@ -473,11 +473,14 @@ func (v Value) call(op string, in []Value) []Value {
|
|||
// Get function pointer, type.
|
||||
t := v.typ
|
||||
var (
|
||||
fn unsafe.Pointer
|
||||
rcvr iword
|
||||
fn unsafe.Pointer
|
||||
rcvr Value
|
||||
rcvrtype *rtype
|
||||
)
|
||||
if v.flag&flagMethod != 0 {
|
||||
t, fn, rcvr = methodReceiver(op, v, int(v.flag)>>flagMethodShift)
|
||||
rcvrtype = t
|
||||
rcvr = v
|
||||
t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift)
|
||||
} else if v.flag&flagIndir != 0 {
|
||||
fn = *(*unsafe.Pointer)(v.ptr)
|
||||
} else {
|
||||
|
|
@ -556,23 +559,26 @@ func (v Value) call(op string, in []Value) []Value {
|
|||
}
|
||||
nout := t.NumOut()
|
||||
|
||||
// Compute arg size & allocate.
|
||||
// This computation is 5g/6g/8g-dependent
|
||||
// and probably wrong for gccgo, but so
|
||||
// is most of this function.
|
||||
size, _, _, _ := frameSize(t, v.flag&flagMethod != 0)
|
||||
// If the target is methodValueCall, do its work here: add the receiver
|
||||
// argument and call the real target directly.
|
||||
// We need to do this here because otherwise we have a situation where
|
||||
// reflect.callXX calls methodValueCall, neither of which knows the
|
||||
// layout of the args. That's bad for precise gc & stack copying.
|
||||
y := (*methodValue)(fn)
|
||||
if y.fn == methodValueCallCode {
|
||||
rcvr = y.rcvr
|
||||
rcvrtype = rcvr.typ
|
||||
t, fn = methodReceiver("call", rcvr, y.method)
|
||||
}
|
||||
|
||||
// Copy into args.
|
||||
//
|
||||
// TODO(rsc): This will need to be updated for any new garbage collector.
|
||||
// For now make everything look like a pointer by allocating
|
||||
// a []unsafe.Pointer.
|
||||
args := make([]unsafe.Pointer, size/ptrSize)
|
||||
ptr := unsafe.Pointer(&args[0])
|
||||
// Compute frame type, allocate a chunk of memory for frame
|
||||
frametype := funcLayout(t, rcvrtype)
|
||||
args := unsafe_New(frametype)
|
||||
off := uintptr(0)
|
||||
if v.flag&flagMethod != 0 {
|
||||
// Hard-wired first argument.
|
||||
*(*iword)(ptr) = rcvr
|
||||
|
||||
// Copy inputs into args.
|
||||
if rcvrtype != nil {
|
||||
storeRcvr(rcvr, args)
|
||||
off = ptrSize
|
||||
}
|
||||
for i, v := range in {
|
||||
|
|
@ -581,7 +587,7 @@ func (v Value) call(op string, in []Value) []Value {
|
|||
a := uintptr(targ.align)
|
||||
off = (off + a - 1) &^ (a - 1)
|
||||
n := targ.size
|
||||
addr := unsafe.Pointer(uintptr(ptr) + off)
|
||||
addr := unsafe.Pointer(uintptr(args) + off)
|
||||
v = v.assignTo("reflect.Value.Call", targ, (*interface{})(addr))
|
||||
if v.flag&flagIndir != 0 {
|
||||
memmove(addr, v.ptr, n)
|
||||
|
|
@ -594,35 +600,17 @@ func (v Value) call(op string, in []Value) []Value {
|
|||
}
|
||||
off = (off + ptrSize - 1) &^ (ptrSize - 1)
|
||||
|
||||
// If the target is methodValueCall, do its work here: add the receiver
|
||||
// argument and call the real target directly.
|
||||
// We need to do this here because otherwise we have a situation where
|
||||
// reflect.callXX calls methodValueCall, neither of which knows the
|
||||
// layout of the args. That's bad for precise gc & stack copying.
|
||||
y := (*methodValue)(fn)
|
||||
if y.fn == methodValueCallCode {
|
||||
_, fn, rcvr = methodReceiver("call", y.rcvr, y.method)
|
||||
args = append(args, unsafe.Pointer(nil))
|
||||
copy(args[1:], args)
|
||||
args[0] = unsafe.Pointer(rcvr)
|
||||
ptr = unsafe.Pointer(&args[0])
|
||||
off += ptrSize
|
||||
size += ptrSize
|
||||
}
|
||||
|
||||
// Call.
|
||||
call(fn, ptr, uint32(size))
|
||||
call(fn, args, uint32(frametype.size))
|
||||
|
||||
// Copy return values out of args.
|
||||
//
|
||||
// TODO(rsc): revisit like above.
|
||||
ret := make([]Value, nout)
|
||||
for i := 0; i < nout; i++ {
|
||||
tv := t.Out(i)
|
||||
a := uintptr(tv.Align())
|
||||
off = (off + a - 1) &^ (a - 1)
|
||||
fl := flagIndir | flag(tv.Kind())<<flagKindShift
|
||||
ret[i] = Value{tv.common(), unsafe.Pointer(uintptr(ptr) + off), 0, fl}
|
||||
ret[i] = Value{tv.common(), unsafe.Pointer(uintptr(args) + off), 0, fl}
|
||||
off += tv.Size()
|
||||
}
|
||||
|
||||
|
|
@ -710,7 +698,9 @@ func callReflect(ctxt *makeFuncImpl, frame unsafe.Pointer) {
|
|||
// described by v. The Value v may or may not have the
|
||||
// flagMethod bit set, so the kind cached in v.flag should
|
||||
// not be used.
|
||||
func methodReceiver(op string, v Value, methodIndex int) (t *rtype, fn unsafe.Pointer, rcvr iword) {
|
||||
// The return value t gives the method type signature (without the receiver).
|
||||
// The return value fn is a pointer to the method code.
|
||||
func methodReceiver(op string, v Value, methodIndex int) (t *rtype, fn unsafe.Pointer) {
|
||||
i := methodIndex
|
||||
if v.typ.Kind() == Interface {
|
||||
tt := (*interfaceType)(unsafe.Pointer(v.typ))
|
||||
|
|
@ -721,13 +711,12 @@ func methodReceiver(op string, v Value, methodIndex int) (t *rtype, fn unsafe.Po
|
|||
if m.pkgPath != nil {
|
||||
panic("reflect: " + op + " of unexported method")
|
||||
}
|
||||
t = m.typ
|
||||
iface := (*nonEmptyInterface)(v.ptr)
|
||||
if iface.itab == nil {
|
||||
panic("reflect: " + op + " of method on nil interface value")
|
||||
}
|
||||
fn = unsafe.Pointer(&iface.itab.fun[i])
|
||||
rcvr = iface.word
|
||||
t = m.typ
|
||||
} else {
|
||||
ut := v.typ.uncommon()
|
||||
if ut == nil || i < 0 || i >= len(ut.methods) {
|
||||
|
|
@ -739,58 +728,41 @@ func methodReceiver(op string, v Value, methodIndex int) (t *rtype, fn unsafe.Po
|
|||
}
|
||||
fn = unsafe.Pointer(&m.ifn)
|
||||
t = m.mtyp
|
||||
rcvr = v.iword()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// v is a method receiver. Store at p the word which is used to
|
||||
// encode that receiver at the start of the argument list.
|
||||
// Reflect uses the "interface" calling convention for
|
||||
// methods, which always uses one word to record the receiver.
|
||||
func storeRcvr(v Value, p unsafe.Pointer) {
|
||||
t := v.typ
|
||||
if t.Kind() == Interface {
|
||||
// the interface data word becomes the receiver word
|
||||
iface := (*nonEmptyInterface)(v.ptr)
|
||||
*(*unsafe.Pointer)(p) = unsafe.Pointer(iface.word)
|
||||
} else if v.flag&flagIndir != 0 {
|
||||
if t.size > ptrSize {
|
||||
*(*unsafe.Pointer)(p) = v.ptr
|
||||
} else if t.pointers() {
|
||||
*(*unsafe.Pointer)(p) = *(*unsafe.Pointer)(v.ptr)
|
||||
} else {
|
||||
*(*uintptr)(p) = loadScalar(v.ptr, t.size)
|
||||
}
|
||||
} else if t.pointers() {
|
||||
*(*unsafe.Pointer)(p) = v.ptr
|
||||
} else {
|
||||
*(*uintptr)(p) = v.scalar
|
||||
}
|
||||
}
|
||||
|
||||
// align returns the result of rounding x up to a multiple of n.
|
||||
// n must be a power of two.
|
||||
func align(x, n uintptr) uintptr {
|
||||
return (x + n - 1) &^ (n - 1)
|
||||
}
|
||||
|
||||
// frameSize returns the sizes of the argument and result frame
|
||||
// for a function of the given type. The rcvr bool specifies whether
|
||||
// a one-word receiver should be included in the total.
|
||||
func frameSize(t *rtype, rcvr bool) (total, in, outOffset, out uintptr) {
|
||||
if rcvr {
|
||||
// extra word for receiver interface word
|
||||
total += ptrSize
|
||||
}
|
||||
|
||||
nin := t.NumIn()
|
||||
in = -total
|
||||
for i := 0; i < nin; i++ {
|
||||
tv := t.In(i)
|
||||
total = align(total, uintptr(tv.Align()))
|
||||
total += tv.Size()
|
||||
}
|
||||
in += total
|
||||
total = align(total, ptrSize)
|
||||
nout := t.NumOut()
|
||||
outOffset = total
|
||||
out = -total
|
||||
for i := 0; i < nout; i++ {
|
||||
tv := t.Out(i)
|
||||
total = align(total, uintptr(tv.Align()))
|
||||
total += tv.Size()
|
||||
}
|
||||
out += total
|
||||
|
||||
// total must be > 0 in order for &args[0] to be valid.
|
||||
// the argument copying is going to round it up to
|
||||
// a multiple of ptrSize anyway, so make it ptrSize to begin with.
|
||||
if total < ptrSize {
|
||||
total = ptrSize
|
||||
}
|
||||
|
||||
// round to pointer
|
||||
total = align(total, ptrSize)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// callMethod is the call implementation used by a function returned
|
||||
// by makeMethodValue (used by v.Method(i).Interface()).
|
||||
// It is a streamlined version of the usual reflect call: the caller has
|
||||
|
|
@ -803,24 +775,23 @@ func frameSize(t *rtype, rcvr bool) (total, in, outOffset, out uintptr) {
|
|||
// so that the linker can make it work correctly for panic and recover.
|
||||
// The gc compilers know to do that for the name "reflect.callMethod".
|
||||
func callMethod(ctxt *methodValue, frame unsafe.Pointer) {
|
||||
t, fn, rcvr := methodReceiver("call", ctxt.rcvr, ctxt.method)
|
||||
total, in, outOffset, out := frameSize(t, true)
|
||||
rcvr := ctxt.rcvr
|
||||
rcvrtype := rcvr.typ
|
||||
t, fn := methodReceiver("call", rcvr, ctxt.method)
|
||||
frametype := funcLayout(t, rcvrtype)
|
||||
|
||||
// Copy into args.
|
||||
//
|
||||
// TODO(rsc): This will need to be updated for any new garbage collector.
|
||||
// For now make everything look like a pointer by allocating
|
||||
// a []unsafe.Pointer.
|
||||
args := make([]unsafe.Pointer, total/ptrSize)
|
||||
args[0] = unsafe.Pointer(rcvr)
|
||||
base := unsafe.Pointer(&args[0])
|
||||
memmove(unsafe.Pointer(uintptr(base)+ptrSize), frame, in)
|
||||
// Make a new frame that is one word bigger so we can store the receiver.
|
||||
args := unsafe_New(frametype)
|
||||
|
||||
// Copy in receiver and rest of args.
|
||||
storeRcvr(rcvr, args)
|
||||
memmove(unsafe.Pointer(uintptr(args)+ptrSize), frame, frametype.size-ptrSize)
|
||||
|
||||
// Call.
|
||||
call(fn, unsafe.Pointer(&args[0]), uint32(total))
|
||||
call(fn, args, uint32(frametype.size))
|
||||
|
||||
// Copy return values.
|
||||
memmove(unsafe.Pointer(uintptr(frame)+outOffset-ptrSize), unsafe.Pointer(uintptr(base)+outOffset), out)
|
||||
memmove(frame, unsafe.Pointer(uintptr(args)+ptrSize), frametype.size-ptrSize)
|
||||
}
|
||||
|
||||
// funcName returns the name of f, for use in error messages.
|
||||
|
|
|
|||
Loading…
Reference in New Issue