From 2af7a26f1eaf2a8640270ac39cfd04d9aaa70ee2 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Wed, 15 Jan 2014 13:56:59 -0800 Subject: [PATCH] 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 --- src/pkg/reflect/type.go | 91 +++++++++++++++++++++ src/pkg/reflect/value.go | 169 ++++++++++++++++----------------------- 2 files changed, 161 insertions(+), 99 deletions(-) diff --git a/src/pkg/reflect/type.go b/src/pkg/reflect/type.go index 0f38781113..51fdc1ecad 100644 --- a/src/pkg/reflect/type.go +++ b/src/pkg/reflect/type.go @@ -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 +} diff --git a/src/pkg/reflect/value.go b/src/pkg/reflect/value.go index 30b5e2a92f..916e219158 100644 --- a/src/pkg/reflect/value.go +++ b/src/pkg/reflect/value.go @@ -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) - - // 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]) + // 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) + } + + // 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())<= 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) - - // 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) + rcvr := ctxt.rcvr + rcvrtype := rcvr.typ + t, fn := methodReceiver("call", rcvr, ctxt.method) + frametype := funcLayout(t, rcvrtype) + + // 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. -- 2.48.1