Skip to content

Commit 2af7a26

Browse files
committed
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
1 parent 35710ee commit 2af7a26

File tree

2 files changed

+161
-99
lines changed

2 files changed

+161
-99
lines changed

src/pkg/reflect/type.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,3 +1803,94 @@ func toType(t *rtype) Type {
18031803
}
18041804
return t
18051805
}
1806+
1807+
type layoutKey struct {
1808+
t *rtype // function signature
1809+
rcvr *rtype // receiver type, or nil if none
1810+
}
1811+
1812+
var layoutCache struct {
1813+
sync.RWMutex
1814+
m map[layoutKey]*rtype
1815+
}
1816+
1817+
// funcLayout computes a struct type representing the layout of the
1818+
// function arguments and return values for the function type t.
1819+
// If rcvr != nil, rcvr specifies the type of the receiver.
1820+
// The returned type exists only for GC, so we only fill out GC relevant info.
1821+
// Currently, that's just size and the GC program. We also fill in
1822+
// the name for possible debugging use.
1823+
func funcLayout(t *rtype, rcvr *rtype) *rtype {
1824+
if t.Kind() != Func {
1825+
panic("reflect: funcSignature of non-func type")
1826+
}
1827+
k := layoutKey{t, rcvr}
1828+
layoutCache.RLock()
1829+
if x := layoutCache.m[k]; x != nil {
1830+
layoutCache.RUnlock()
1831+
return x
1832+
}
1833+
layoutCache.RUnlock()
1834+
layoutCache.Lock()
1835+
if x := layoutCache.m[k]; x != nil {
1836+
layoutCache.Unlock()
1837+
return x
1838+
}
1839+
1840+
tt := (*funcType)(unsafe.Pointer(t))
1841+
1842+
// compute gc program for arguments
1843+
gc := make([]uintptr, 1) // first entry is size, filled in at the end
1844+
offset := uintptr(0)
1845+
if rcvr != nil {
1846+
// Reflect uses the "interface" calling convention for
1847+
// methods, where receivers take one word of argument
1848+
// space no matter how big they actually are.
1849+
if rcvr.size > ptrSize {
1850+
// we pass a pointer to the receiver.
1851+
gc = append(gc, _GC_PTR, offset, uintptr(rcvr.gc))
1852+
} else if rcvr.pointers() {
1853+
// rcvr is a one-word pointer object. Its gc program
1854+
// is just what we need here.
1855+
gc = appendGCProgram(gc, rcvr)
1856+
}
1857+
offset += ptrSize
1858+
}
1859+
for _, arg := range tt.in {
1860+
offset = align(offset, uintptr(arg.align))
1861+
if arg.pointers() {
1862+
gc = append(gc, _GC_REGION, offset, arg.size, uintptr(arg.gc))
1863+
}
1864+
offset += arg.size
1865+
}
1866+
offset = align(offset, ptrSize)
1867+
for _, res := range tt.out {
1868+
offset = align(offset, uintptr(res.align))
1869+
if res.pointers() {
1870+
gc = append(gc, _GC_REGION, offset, res.size, uintptr(res.gc))
1871+
}
1872+
offset += res.size
1873+
}
1874+
gc = append(gc, _GC_END)
1875+
gc[0] = offset
1876+
1877+
// build dummy rtype holding gc program
1878+
x := new(rtype)
1879+
x.size = offset
1880+
x.gc = unsafe.Pointer(&gc[0])
1881+
var s string
1882+
if rcvr != nil {
1883+
s = "methodargs(" + *rcvr.string + ")(" + *t.string + ")"
1884+
} else {
1885+
s = "funcargs(" + *t.string + ")"
1886+
}
1887+
x.string = &s
1888+
1889+
// cache result for future callers
1890+
if layoutCache.m == nil {
1891+
layoutCache.m = make(map[layoutKey]*rtype)
1892+
}
1893+
layoutCache.m[k] = x
1894+
layoutCache.Unlock()
1895+
return x
1896+
}

src/pkg/reflect/value.go

Lines changed: 70 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -473,11 +473,14 @@ func (v Value) call(op string, in []Value) []Value {
473473
// Get function pointer, type.
474474
t := v.typ
475475
var (
476-
fn unsafe.Pointer
477-
rcvr iword
476+
fn unsafe.Pointer
477+
rcvr Value
478+
rcvrtype *rtype
478479
)
479480
if v.flag&flagMethod != 0 {
480-
t, fn, rcvr = methodReceiver(op, v, int(v.flag)>>flagMethodShift)
481+
rcvrtype = t
482+
rcvr = v
483+
t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift)
481484
} else if v.flag&flagIndir != 0 {
482485
fn = *(*unsafe.Pointer)(v.ptr)
483486
} else {
@@ -556,23 +559,26 @@ func (v Value) call(op string, in []Value) []Value {
556559
}
557560
nout := t.NumOut()
558561

559-
// Compute arg size & allocate.
560-
// This computation is 5g/6g/8g-dependent
561-
// and probably wrong for gccgo, but so
562-
// is most of this function.
563-
size, _, _, _ := frameSize(t, v.flag&flagMethod != 0)
564-
565-
// Copy into args.
566-
//
567-
// TODO(rsc): This will need to be updated for any new garbage collector.
568-
// For now make everything look like a pointer by allocating
569-
// a []unsafe.Pointer.
570-
args := make([]unsafe.Pointer, size/ptrSize)
571-
ptr := unsafe.Pointer(&args[0])
562+
// If the target is methodValueCall, do its work here: add the receiver
563+
// argument and call the real target directly.
564+
// We need to do this here because otherwise we have a situation where
565+
// reflect.callXX calls methodValueCall, neither of which knows the
566+
// layout of the args. That's bad for precise gc & stack copying.
567+
y := (*methodValue)(fn)
568+
if y.fn == methodValueCallCode {
569+
rcvr = y.rcvr
570+
rcvrtype = rcvr.typ
571+
t, fn = methodReceiver("call", rcvr, y.method)
572+
}
573+
574+
// Compute frame type, allocate a chunk of memory for frame
575+
frametype := funcLayout(t, rcvrtype)
576+
args := unsafe_New(frametype)
572577
off := uintptr(0)
573-
if v.flag&flagMethod != 0 {
574-
// Hard-wired first argument.
575-
*(*iword)(ptr) = rcvr
578+
579+
// Copy inputs into args.
580+
if rcvrtype != nil {
581+
storeRcvr(rcvr, args)
576582
off = ptrSize
577583
}
578584
for i, v := range in {
@@ -581,7 +587,7 @@ func (v Value) call(op string, in []Value) []Value {
581587
a := uintptr(targ.align)
582588
off = (off + a - 1) &^ (a - 1)
583589
n := targ.size
584-
addr := unsafe.Pointer(uintptr(ptr) + off)
590+
addr := unsafe.Pointer(uintptr(args) + off)
585591
v = v.assignTo("reflect.Value.Call", targ, (*interface{})(addr))
586592
if v.flag&flagIndir != 0 {
587593
memmove(addr, v.ptr, n)
@@ -594,35 +600,17 @@ func (v Value) call(op string, in []Value) []Value {
594600
}
595601
off = (off + ptrSize - 1) &^ (ptrSize - 1)
596602

597-
// If the target is methodValueCall, do its work here: add the receiver
598-
// argument and call the real target directly.
599-
// We need to do this here because otherwise we have a situation where
600-
// reflect.callXX calls methodValueCall, neither of which knows the
601-
// layout of the args. That's bad for precise gc & stack copying.
602-
y := (*methodValue)(fn)
603-
if y.fn == methodValueCallCode {
604-
_, fn, rcvr = methodReceiver("call", y.rcvr, y.method)
605-
args = append(args, unsafe.Pointer(nil))
606-
copy(args[1:], args)
607-
args[0] = unsafe.Pointer(rcvr)
608-
ptr = unsafe.Pointer(&args[0])
609-
off += ptrSize
610-
size += ptrSize
611-
}
612-
613603
// Call.
614-
call(fn, ptr, uint32(size))
604+
call(fn, args, uint32(frametype.size))
615605

616606
// Copy return values out of args.
617-
//
618-
// TODO(rsc): revisit like above.
619607
ret := make([]Value, nout)
620608
for i := 0; i < nout; i++ {
621609
tv := t.Out(i)
622610
a := uintptr(tv.Align())
623611
off = (off + a - 1) &^ (a - 1)
624612
fl := flagIndir | flag(tv.Kind())<<flagKindShift
625-
ret[i] = Value{tv.common(), unsafe.Pointer(uintptr(ptr) + off), 0, fl}
613+
ret[i] = Value{tv.common(), unsafe.Pointer(uintptr(args) + off), 0, fl}
626614
off += tv.Size()
627615
}
628616

@@ -710,7 +698,9 @@ func callReflect(ctxt *makeFuncImpl, frame unsafe.Pointer) {
710698
// described by v. The Value v may or may not have the
711699
// flagMethod bit set, so the kind cached in v.flag should
712700
// not be used.
713-
func methodReceiver(op string, v Value, methodIndex int) (t *rtype, fn unsafe.Pointer, rcvr iword) {
701+
// The return value t gives the method type signature (without the receiver).
702+
// The return value fn is a pointer to the method code.
703+
func methodReceiver(op string, v Value, methodIndex int) (t *rtype, fn unsafe.Pointer) {
714704
i := methodIndex
715705
if v.typ.Kind() == Interface {
716706
tt := (*interfaceType)(unsafe.Pointer(v.typ))
@@ -721,13 +711,12 @@ func methodReceiver(op string, v Value, methodIndex int) (t *rtype, fn unsafe.Po
721711
if m.pkgPath != nil {
722712
panic("reflect: " + op + " of unexported method")
723713
}
724-
t = m.typ
725714
iface := (*nonEmptyInterface)(v.ptr)
726715
if iface.itab == nil {
727716
panic("reflect: " + op + " of method on nil interface value")
728717
}
729718
fn = unsafe.Pointer(&iface.itab.fun[i])
730-
rcvr = iface.word
719+
t = m.typ
731720
} else {
732721
ut := v.typ.uncommon()
733722
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
739728
}
740729
fn = unsafe.Pointer(&m.ifn)
741730
t = m.mtyp
742-
rcvr = v.iword()
743731
}
744732
return
745733
}
746734

735+
// v is a method receiver. Store at p the word which is used to
736+
// encode that receiver at the start of the argument list.
737+
// Reflect uses the "interface" calling convention for
738+
// methods, which always uses one word to record the receiver.
739+
func storeRcvr(v Value, p unsafe.Pointer) {
740+
t := v.typ
741+
if t.Kind() == Interface {
742+
// the interface data word becomes the receiver word
743+
iface := (*nonEmptyInterface)(v.ptr)
744+
*(*unsafe.Pointer)(p) = unsafe.Pointer(iface.word)
745+
} else if v.flag&flagIndir != 0 {
746+
if t.size > ptrSize {
747+
*(*unsafe.Pointer)(p) = v.ptr
748+
} else if t.pointers() {
749+
*(*unsafe.Pointer)(p) = *(*unsafe.Pointer)(v.ptr)
750+
} else {
751+
*(*uintptr)(p) = loadScalar(v.ptr, t.size)
752+
}
753+
} else if t.pointers() {
754+
*(*unsafe.Pointer)(p) = v.ptr
755+
} else {
756+
*(*uintptr)(p) = v.scalar
757+
}
758+
}
759+
747760
// align returns the result of rounding x up to a multiple of n.
748761
// n must be a power of two.
749762
func align(x, n uintptr) uintptr {
750763
return (x + n - 1) &^ (n - 1)
751764
}
752765

753-
// frameSize returns the sizes of the argument and result frame
754-
// for a function of the given type. The rcvr bool specifies whether
755-
// a one-word receiver should be included in the total.
756-
func frameSize(t *rtype, rcvr bool) (total, in, outOffset, out uintptr) {
757-
if rcvr {
758-
// extra word for receiver interface word
759-
total += ptrSize
760-
}
761-
762-
nin := t.NumIn()
763-
in = -total
764-
for i := 0; i < nin; i++ {
765-
tv := t.In(i)
766-
total = align(total, uintptr(tv.Align()))
767-
total += tv.Size()
768-
}
769-
in += total
770-
total = align(total, ptrSize)
771-
nout := t.NumOut()
772-
outOffset = total
773-
out = -total
774-
for i := 0; i < nout; i++ {
775-
tv := t.Out(i)
776-
total = align(total, uintptr(tv.Align()))
777-
total += tv.Size()
778-
}
779-
out += total
780-
781-
// total must be > 0 in order for &args[0] to be valid.
782-
// the argument copying is going to round it up to
783-
// a multiple of ptrSize anyway, so make it ptrSize to begin with.
784-
if total < ptrSize {
785-
total = ptrSize
786-
}
787-
788-
// round to pointer
789-
total = align(total, ptrSize)
790-
791-
return
792-
}
793-
794766
// callMethod is the call implementation used by a function returned
795767
// by makeMethodValue (used by v.Method(i).Interface()).
796768
// 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) {
803775
// so that the linker can make it work correctly for panic and recover.
804776
// The gc compilers know to do that for the name "reflect.callMethod".
805777
func callMethod(ctxt *methodValue, frame unsafe.Pointer) {
806-
t, fn, rcvr := methodReceiver("call", ctxt.rcvr, ctxt.method)
807-
total, in, outOffset, out := frameSize(t, true)
808-
809-
// Copy into args.
810-
//
811-
// TODO(rsc): This will need to be updated for any new garbage collector.
812-
// For now make everything look like a pointer by allocating
813-
// a []unsafe.Pointer.
814-
args := make([]unsafe.Pointer, total/ptrSize)
815-
args[0] = unsafe.Pointer(rcvr)
816-
base := unsafe.Pointer(&args[0])
817-
memmove(unsafe.Pointer(uintptr(base)+ptrSize), frame, in)
778+
rcvr := ctxt.rcvr
779+
rcvrtype := rcvr.typ
780+
t, fn := methodReceiver("call", rcvr, ctxt.method)
781+
frametype := funcLayout(t, rcvrtype)
782+
783+
// Make a new frame that is one word bigger so we can store the receiver.
784+
args := unsafe_New(frametype)
785+
786+
// Copy in receiver and rest of args.
787+
storeRcvr(rcvr, args)
788+
memmove(unsafe.Pointer(uintptr(args)+ptrSize), frame, frametype.size-ptrSize)
818789

819790
// Call.
820-
call(fn, unsafe.Pointer(&args[0]), uint32(total))
791+
call(fn, args, uint32(frametype.size))
821792

822793
// Copy return values.
823-
memmove(unsafe.Pointer(uintptr(frame)+outOffset-ptrSize), unsafe.Pointer(uintptr(base)+outOffset), out)
794+
memmove(frame, unsafe.Pointer(uintptr(args)+ptrSize), frametype.size-ptrSize)
824795
}
825796

826797
// funcName returns the name of f, for use in error messages.

0 commit comments

Comments
 (0)