diff mbox

Go patch committed: Implement method values in reflect package

Message ID mcry53qj2nk.fsf@iant-glaptop.roam.corp.google.com
State New
Headers show

Commit Message

Ian Lance Taylor Dec. 12, 2013, 1:09 a.m. UTC
This patch to the Go frontend and libgo implements method values in the
reflect package.  Working with method values and reflect now works
correctly, at least on x86.  This changes the type signature for type
methods in the reflect package to match the gc compiler.  That in turn
required changing the reflect package to mark method values with a new
flag, as previously they were detected by the type signature.  The
MakeFunc support needed to create a function that takes a value and
passes a pointer to the method, since all methods take pointers.  It
also needed to create a function that holds a receiver value.  And the
recover code needed to handle these new cases.  Bootstrapped and ran Go
testsuite on x86_64-unknown-linux-gnu.  Committed to mainline and 4.8
branch.

Ian

Comments

Michael Hudson-Doyle Dec. 12, 2013, 8:21 a.m. UTC | #1
Ian Lance Taylor <iant@google.com> writes:

> This patch to the Go frontend and libgo implements method values in the
> reflect package.  Working with method values and reflect now works
> correctly, at least on x86.

Can you give me a test case?  I can try it on a few other architectures
tomorrow.

Cheers,
mwh

> This changes the type signature for type methods in the reflect
> package to match the gc compiler.  That in turn required changing the
> reflect package to mark method values with a new flag, as previously
> they were detected by the type signature.  The MakeFunc support needed
> to create a function that takes a value and passes a pointer to the
> method, since all methods take pointers.  It also needed to create a
> function that holds a receiver value.  And the recover code needed to
> handle these new cases.  Bootstrapped and ran Go testsuite on
> x86_64-unknown-linux-gnu.  Committed to mainline and 4.8 branch.
>
> Ian
Ian Lance Taylor Dec. 12, 2013, 6:18 p.m. UTC | #2
On Thu, Dec 12, 2013 at 12:21 AM, Michael Hudson-Doyle
<michael.hudson@linaro.org> wrote:
> Ian Lance Taylor <iant@google.com> writes:
>
>> This patch to the Go frontend and libgo implements method values in the
>> reflect package.  Working with method values and reflect now works
>> correctly, at least on x86.
>
> Can you give me a test case?  I can try it on a few other architectures
> tomorrow.

The test case is test/recover.go in the master Go sources.  But I know
that that test won't work on other architectures, because currently
reflect.MakeFunc is only implemented for 386 and amd64.

It should be possible to implement reflect.MakeFunc for all
architectures in a somewhat inefficient manner by using libffi's
closure API.  I have not tried that.

It is also of course possible to implement support for any specific
processor as I did for 386 and amd64.  The difficulty depends on the
difficulty of the ABI.  In general it's not too hard but it requires a
clear understanding of ABI details and assembly language programming
with full backtrace information.  It's about 600 lines of code for
amd64.

Ian
Michael Hudson-Doyle Dec. 12, 2013, 11:17 p.m. UTC | #3
Ian Lance Taylor <iant@google.com> writes:

> On Thu, Dec 12, 2013 at 12:21 AM, Michael Hudson-Doyle
> <michael.hudson@linaro.org> wrote:
>> Ian Lance Taylor <iant@google.com> writes:
>>
>>> This patch to the Go frontend and libgo implements method values in the
>>> reflect package.  Working with method values and reflect now works
>>> correctly, at least on x86.
>>
>> Can you give me a test case?  I can try it on a few other architectures
>> tomorrow.
>
> The test case is test/recover.go in the master Go sources.  But I know
> that that test won't work on other architectures, because currently
> reflect.MakeFunc is only implemented for 386 and amd64.

Ah, OK.

> It should be possible to implement reflect.MakeFunc for all
> architectures in a somewhat inefficient manner by using libffi's
> closure API.  I have not tried that.
>
> It is also of course possible to implement support for any specific
> processor as I did for 386 and amd64.  The difficulty depends on the
> difficulty of the ABI.  In general it's not too hard but it requires a
> clear understanding of ABI details and assembly language programming
> with full backtrace information.  It's about 600 lines of code for
> amd64.

OK, I'll add it to a list of things to look at "at some point"...

Cheers,
mwh
diff mbox

Patch

diff -r e165d3ccf1e4 go/types.cc
--- a/go/types.cc	Wed Dec 11 15:38:00 2013 -0800
+++ b/go/types.cc	Wed Dec 11 16:57:53 2013 -0800
@@ -2261,26 +2261,9 @@ 
 
   ++p;
   go_assert(p->is_field_name("typ"));
-  if (!only_value_methods && m->is_value_method())
-    {
-      // This is a value method on a pointer type.  Change the type of
-      // the method to use a pointer receiver.  The implementation
-      // always uses a pointer receiver anyhow.
-      Type* rtype = mtype->receiver()->type();
-      Type* prtype = Type::make_pointer_type(rtype);
-      Typed_identifier* receiver =
-	new Typed_identifier(mtype->receiver()->name(), prtype,
-			     mtype->receiver()->location());
-      mtype = Type::make_function_type(receiver,
-				       (mtype->parameters() == NULL
-					? NULL
-					: mtype->parameters()->copy()),
-				       (mtype->results() == NULL
-					? NULL
-					: mtype->results()->copy()),
-				       mtype->location());
-    }
-  vals->push_back(Expression::make_type_descriptor(mtype, bloc));
+  bool want_pointer_receiver = !only_value_methods && m->is_value_method();
+  nonmethod_type = mtype->copy_with_receiver_as_param(want_pointer_receiver);
+  vals->push_back(Expression::make_type_descriptor(nonmethod_type, bloc));
 
   ++p;
   go_assert(p->is_field_name("tfn"));
@@ -4008,6 +3991,32 @@ 
   return ret;
 }
 
+// Make a copy of a function type with the receiver as the first
+// parameter.
+
+Function_type*
+Function_type::copy_with_receiver_as_param(bool want_pointer_receiver) const
+{
+  go_assert(this->is_method());
+  Typed_identifier_list* new_params = new Typed_identifier_list();
+  Type* rtype = this->receiver_->type();
+  if (want_pointer_receiver)
+    rtype = Type::make_pointer_type(rtype);
+  Typed_identifier receiver(this->receiver_->name(), rtype,
+			    this->receiver_->location());
+  new_params->push_back(receiver);
+  const Typed_identifier_list* orig_params = this->parameters_;
+  if (orig_params != NULL && !orig_params->empty())
+    {
+      for (Typed_identifier_list::const_iterator p = orig_params->begin();
+	   p != orig_params->end();
+	   ++p)
+	new_params->push_back(*p);
+    }
+  return Type::make_function_type(NULL, new_params, this->results_,
+				  this->location_);
+}
+
 // Make a copy of a function type ignoring any receiver and adding a
 // closure parameter.
 
diff -r e165d3ccf1e4 go/types.h
--- a/go/types.h	Wed Dec 11 15:38:00 2013 -0800
+++ b/go/types.h	Wed Dec 11 16:57:53 2013 -0800
@@ -1797,6 +1797,12 @@ 
   Function_type*
   copy_with_receiver(Type*) const;
 
+  // Return a copy of this type with the receiver treated as the first
+  // parameter.  If WANT_POINTER_RECEIVER is true, the receiver is
+  // forced to be a pointer.
+  Function_type*
+  copy_with_receiver_as_param(bool want_pointer_receiver) const;
+
   // Return a copy of this type ignoring any receiver and using dummy
   // names for all parameters.  This is used for thunks for method
   // values.
diff -r e165d3ccf1e4 libgo/go/reflect/all_test.go
--- a/libgo/go/reflect/all_test.go	Wed Dec 11 15:38:00 2013 -0800
+++ b/libgo/go/reflect/all_test.go	Wed Dec 11 16:57:53 2013 -0800
@@ -1631,9 +1631,13 @@ 
 	}
 }
 
-/* Not yet implemented for gccgo
-
 func TestMethodValue(t *testing.T) {
+	switch runtime.GOARCH {
+	case "amd64", "386":
+	default:
+		t.Skip("reflect method values not implemented for " + runtime.GOARCH)
+	}
+
 	p := Point{3, 4}
 	var i int64
 
@@ -1721,8 +1725,6 @@ 
 	}
 }
 
-*/
-
 // Reflect version of $GOROOT/test/method5.go
 
 // Concrete types implementing M method.
@@ -1807,14 +1809,18 @@ 
 func (t4 Tm4) M(x int, b byte) (byte, int) { return b, x + 40 }
 
 func TestMethod5(t *testing.T) {
-	/* Not yet used for gccgo
+	switch runtime.GOARCH {
+	case "amd64", "386":
+	default:
+		t.Skip("reflect method values not implemented for " + runtime.GOARCH)
+	}
+
 	CheckF := func(name string, f func(int, byte) (byte, int), inc int) {
 		b, x := f(1000, 99)
 		if b != 99 || x != 1000+inc {
 			t.Errorf("%s(1000, 99) = %v, %v, want 99, %v", name, b, x, 1000+inc)
 		}
 	}
-	*/
 
 	CheckV := func(name string, i Value, inc int) {
 		bx := i.Method(0).Call([]Value{ValueOf(1000), ValueOf(byte(99))})
@@ -1824,9 +1830,7 @@ 
 			t.Errorf("direct %s.M(1000, 99) = %v, %v, want 99, %v", name, b, x, 1000+inc)
 		}
 
-		/* Not yet implemented for gccgo
 		CheckF(name+".M", i.Method(0).Interface().(func(int, byte) (byte, int)), inc)
-		*/
 	}
 
 	var TinterType = TypeOf(new(Tinter)).Elem()
diff -r e165d3ccf1e4 libgo/go/reflect/makefunc.go
--- a/libgo/go/reflect/makefunc.go	Wed Dec 11 15:38:00 2013 -0800
+++ b/libgo/go/reflect/makefunc.go	Wed Dec 11 16:57:53 2013 -0800
@@ -17,6 +17,11 @@ 
 	code uintptr
 	typ  *funcType
 	fn   func([]Value) []Value
+
+	// For gccgo we use the same entry point for functions and for
+	// method values.
+	method int
+	rcvr   Value
 }
 
 // MakeFunc returns a new function of the given Type
@@ -61,7 +66,7 @@ 
 	dummy := makeFuncStub
 	code := **(**uintptr)(unsafe.Pointer(&dummy))
 
-	impl := &makeFuncImpl{code: code, typ: ftyp, fn: fn}
+	impl := &makeFuncImpl{code: code, typ: ftyp, fn: fn, method: -1}
 
 	return Value{t, unsafe.Pointer(&impl), flag(Func<<flagKindShift) | flagIndir}
 }
@@ -85,15 +90,94 @@ 
 		panic("reflect: internal error: invalid use of makePartialFunc")
 	}
 
+	switch runtime.GOARCH {
+	case "amd64", "386":
+	default:
+		panic("reflect.makeMethodValue not implemented for " + runtime.GOARCH)
+	}
+
 	// Ignoring the flagMethod bit, v describes the receiver, not the method type.
 	fl := v.flag & (flagRO | flagAddr | flagIndir)
 	fl |= flag(v.typ.Kind()) << flagKindShift
 	rcvr := Value{v.typ, v.val, fl}
 
+	// v.Type returns the actual type of the method value.
+	ft := v.Type().(*rtype)
+
+	// Indirect Go func value (dummy) to obtain
+	// actual code address. (A Go func value is a pointer
+	// to a C function pointer. http://golang.org/s/go11func.)
+	dummy := makeFuncStub
+	code := **(**uintptr)(unsafe.Pointer(&dummy))
+
 	// Cause panic if method is not appropriate.
 	// The panic would still happen during the call if we omit this,
 	// but we want Interface() and other operations to fail early.
-	methodReceiver(op, rcvr, int(v.flag)>>flagMethodShift)
+	t, _, _ := methodReceiver(op, rcvr, int(v.flag)>>flagMethodShift)
 
-	panic("reflect makeMethodValue not implemented")
+	fv := &makeFuncImpl{
+		code:   code,
+		typ:    (*funcType)(unsafe.Pointer(t)),
+		method: int(v.flag) >> flagMethodShift,
+		rcvr:   rcvr,
+	}
+
+	return Value{ft, unsafe.Pointer(&fv), v.flag&flagRO | flag(Func)<<flagKindShift | flagIndir}
 }
+
+// makeValueMethod takes a method function and returns a function that
+// takes a value receiver and calls the real method with a pointer to
+// it.
+func makeValueMethod(v Value) Value {
+	typ := v.typ
+	if typ.Kind() != Func {
+		panic("reflect: call of makeValueMethod with non-Func type")
+	}
+	if v.flag&flagMethodFn == 0 {
+		panic("reflect: call of makeValueMethod with non-MethodFn")
+	}
+
+	switch runtime.GOARCH {
+	case "amd64", "386":
+	default:
+		panic("reflect.makeValueMethod not implemented for " + runtime.GOARCH)
+	}
+
+	t := typ.common()
+	ftyp := (*funcType)(unsafe.Pointer(t))
+
+	// Indirect Go func value (dummy) to obtain
+	// actual code address. (A Go func value is a pointer
+	// to a C function pointer. http://golang.org/s/go11func.)
+	dummy := makeFuncStub
+	code := **(**uintptr)(unsafe.Pointer(&dummy))
+
+	impl := &makeFuncImpl{
+		code:   code,
+		typ:    ftyp,
+		method: -2,
+		rcvr:   v,
+	}
+
+	return Value{t, unsafe.Pointer(&impl), flag(Func<<flagKindShift) | flagIndir}
+}
+
+// Call the function represented by a makeFuncImpl.
+func (c *makeFuncImpl) call(in []Value) []Value {
+	if c.method == -1 {
+		return c.fn(in)
+	} else if c.method == -2 {
+		if c.typ.IsVariadic() {
+			return c.rcvr.CallSlice(in)
+		} else {
+			return c.rcvr.Call(in)
+		}
+	} else {
+		m := c.rcvr.Method(c.method)
+		if c.typ.IsVariadic() {
+			return m.CallSlice(in)
+		} else {
+			return m.Call(in)
+		}
+	}
+}
diff -r e165d3ccf1e4 libgo/go/reflect/makefuncgo_386.go
--- a/libgo/go/reflect/makefuncgo_386.go	Wed Dec 11 15:38:00 2013 -0800
+++ b/libgo/go/reflect/makefuncgo_386.go	Wed Dec 11 16:57:53 2013 -0800
@@ -80,7 +80,7 @@ 
 
 	// Call the real function.
 
-	out := c.fn(in)
+	out := c.call(in)
 
 	if len(out) != len(ftyp.out) {
 		panic("reflect: wrong return count from function created by MakeFunc")
diff -r e165d3ccf1e4 libgo/go/reflect/makefuncgo_amd64.go
--- a/libgo/go/reflect/makefuncgo_amd64.go	Wed Dec 11 15:38:00 2013 -0800
+++ b/libgo/go/reflect/makefuncgo_amd64.go	Wed Dec 11 16:57:53 2013 -0800
@@ -319,7 +319,7 @@ 
 	// All the real arguments have been found and turned into
 	// Value's.  Call the real function.
 
-	out := c.fn(in)
+	out := c.call(in)
 
 	if len(out) != len(ftyp.out) {
 		panic("reflect: wrong return count from function created by MakeFunc")
diff -r e165d3ccf1e4 libgo/go/reflect/type.go
--- a/libgo/go/reflect/type.go	Wed Dec 11 15:38:00 2013 -0800
+++ b/libgo/go/reflect/type.go	Wed Dec 11 16:57:53 2013 -0800
@@ -517,7 +517,7 @@ 
 	m.Type = toType(mt)
 	x := new(unsafe.Pointer)
 	*x = unsafe.Pointer(&p.tfn)
-	m.Func = Value{mt, unsafe.Pointer(x), fl | flagIndir}
+	m.Func = Value{mt, unsafe.Pointer(x), fl | flagIndir | flagMethodFn}
 	m.Index = i
 	return
 }
diff -r e165d3ccf1e4 libgo/go/reflect/value.go
--- a/libgo/go/reflect/value.go	Wed Dec 11 15:38:00 2013 -0800
+++ b/libgo/go/reflect/value.go	Wed Dec 11 16:57:53 2013 -0800
@@ -98,6 +98,7 @@ 
 	flagIndir
 	flagAddr
 	flagMethod
+	flagMethodFn         // gccgo: first fn parameter is always pointer
 	flagKindShift        = iota
 	flagKindWidth        = 5 // there are 27 kinds
 	flagKindMask    flag = 1<<flagKindWidth - 1
@@ -433,7 +434,7 @@ 
 	if v.flag&flagMethod != 0 {
 		nin++
 	}
-	firstPointer := len(in) > 0 && t.In(0).Kind() != Ptr && v.flag&flagMethod == 0 && isMethod(v.typ)
+	firstPointer := len(in) > 0 && t.In(0).Kind() != Ptr && v.flag&flagMethodFn != 0
 	params := make([]unsafe.Pointer, nin)
 	off := 0
 	if v.flag&flagMethod != 0 {
@@ -484,33 +485,6 @@ 
 	return ret
 }
 
-// gccgo specific test to see if typ is a method.  We can tell by
-// looking at the string to see if there is a receiver.  We need this
-// because for gccgo all methods take pointer receivers.
-func isMethod(t *rtype) bool {
-	if Kind(t.kind) != Func {
-		return false
-	}
-	s := *t.string
-	parens := 0
-	params := 0
-	sawRet := false
-	for i, c := range s {
-		if c == '(' {
-			if parens == 0 {
-				params++
-			}
-			parens++
-		} else if c == ')' {
-			parens--
-		} else if parens == 0 && c == ' ' && s[i+1] != '(' && !sawRet {
-			params++
-			sawRet = true
-		}
-	}
-	return params > 2
-}
-
 // methodReceiver returns information about the receiver
 // described by v. The Value v may or may not have the
 // flagMethod bit set, so the kind cached in v.flag should
@@ -873,6 +847,16 @@ 
 		v = makeMethodValue("Interface", v)
 	}
 
+	if v.flag&flagMethodFn != 0 {
+		if v.typ.Kind() != Func {
+			panic("reflect: MethodFn of non-Func")
+		}
+		ft := (*funcType)(unsafe.Pointer(v.typ))
+		if ft.in[0].Kind() != Ptr {
+			v = makeValueMethod(v)
+		}
+	}
+
 	k := v.kind()
 	if k == Interface {
 		// Special case: return the element inside the interface.
@@ -1187,8 +1171,7 @@ 
 			// created via reflect have the same underlying code pointer,
 			// so their Pointers are equal. The function used here must
 			// match the one used in makeMethodValue.
-			// This is not properly implemented for gccgo.
-			f := Zero
+			f := makeFuncStub
 			return **(**uintptr)(unsafe.Pointer(&f))
 		}
 		p := v.val
diff -r e165d3ccf1e4 libgo/runtime/go-recover.c
--- a/libgo/runtime/go-recover.c	Wed Dec 11 15:38:00 2013 -0800
+++ b/libgo/runtime/go-recover.c	Wed Dec 11 16:57:53 2013 -0800
@@ -84,6 +84,11 @@ 
   if (name[0] == 'f' && name[1] == 'f' && name[2] == 'i' && name[3] == '_')
     return 1;
 
+  /* We may also be called by reflect.makeFuncImpl.call, for a
+     function created by reflect.MakeFunc.  */
+  if (__builtin_strstr ((const char *) name, "makeFuncImpl") != NULL)
+    return 1;
+
   return 0;
 }