I find myself, a complete newcomer to compiler and language design,
attempting to make my case to some very smart people. There are a few
points to respond to, but let me first pare my argument down to its
simplest form.
The reflect package is incomplete, as it lacks ArrayOf (issue #5996),
FuncOf, IntrefaceOf, StructOf (issue #5748), and NamedOf.
There are several points which if convincingly argued would persuade
me to drop my case entirely:
1. Go doesn't need a complete reflect system
2. Now is not the time to complete the reflect system
3. The overhead of completing the reflect system outweighs the benefit
On Wed, Jul 9, 2014 at 2:09 PM, Alan Donovan <
adon...@google.com> wrote:
>
> I think Ian is right here: the limitations are easy to understand, the unimplemented case is not obviously useful, and the basic feature enables things that cannot currently be accomplished within the language, period (e.g. writing a Go interpreter that uses the host garbage collector).
>
Ironically enough, this was exactly what I was doing when I started
hacking on reflect. In other words, I want reflect to do everything. I
also adhere to the "if it's worth doing, do it right" sentiment.
To overcome the current hurdle, I would like to make a proposal that
changes the calling convention with minimal impact on the generated
code and performance. But first let me define the problem by
considering the below code.
type X int
func (x X) Ident() X { return x }
type I interface { Ident() X }
func main() {
// Construct a new struct type containing an anonymous field
xt := reflect.TypeOf(X(0))
ft := reflect.StructField{Type: xt} // Anonymous field of type X
st := reflect.StructOf([]reflect.StructField{ft})
// Create a new st, which should satisfy I.
i = reflect.New(st).Elem().Interface().(I)
}
The itable for i is populated with function pointers, in this case
those functions simply delegate the methods of X to the anonymous
struct field of type X. The problem is that these delegates are
specific to the constructed struct type, and therefore cannot be
generated at compile time. Instead, a generic delegate function can be
used, but at a minimum it must determine
1. The value of the underlying type (easy, as functions in the itab
receive this as their first argument)
2. The type of this value
3. The position of the function called in the struct's mtab
4. The type of this function, for computing stack layout (can be
computed from 2 and 3)
My proposal for point 2 is to change the interface calling convention
to require that a pointer to the itab is passed in an agreed register.
Below is a commented version of the x64 instructions generated by gc
for 'i.Ident()', an interface call on i in the above example. See
6g/ggen.c cgen_callinter()
0x01e1 MOVQ 24(SP),CX // REG = 0(REG) -- i.tab
0x01e6 MOVQ 32(SP),AX // 0(SP) = 8(REG) -- i.data
0x01eb MOVQ AX,"".i+120(SP) // What is this, where did it come from?
0x01f0 MOVQ AX,(SP) // 0(SP) = 8(REG) -- i.data, first argument to
function in mtab
0x01f4 MOVQ CX,"".i+112(SP) // And this?
0x01f9 NOP ,
0x01f9 MOVQ 32(CX),BX // REG = 32+offset(REG) -- i.tab->fun[f]
0x01fd PCDATA $0,$16
0x01fd PCDATA $1,$0
0x01fd CALL ,BX // Call method
Note that CX holds the itab pointer, which contains a pointer to the
dynamic type as required. At some call sites, the fn pointer load is
optimised to MOVQ 32(BX),BX, my proposal is simply to make this
optimisation illegal.
Solving point 3, calculating the function's mtab index, is more
intrusive but I see two options.
a. Generate a large number of trampoline functions which each call the
delegate function with a different index argument. Panic if the number
of methods is greater than the number of trampolines.
pros: no code changes, no overhead
cons: binary bloat, ceiling on number of methods
b. Add the Store the itab->mtab mapping in the Itab struct and pass
the offset in another register. It's going to be trashed anyway right?
pros: does not have limitation in number methods
cons:
requires additional constant MOV instruction. I'm certain people on
this thread have a better understanding of the implications here than
I do.
itab->mtab mapping must be stored, this is already computed but not
stored, and would effectively double the size of the Itab struct.
However, this mapping need only be computed for types created via
reflect.
I am of course in favour of option b, as I said I want everything.
Now going back through a couple of concerns that have been raised.
> Adding an almost always useless instruction to every call seems like a mistake.
Agreed, I suppose the counter argument is that this only applies to
interface calls, which already have significant overhead anyway, the
incremental damage wont be huge (I think (TM) )
There is always the other option of using multiple trampolines, or
something fancy like an INC slide.
> I'd really rather not add to reflect in this cycle. We have a lot going on already.
I believe the changes I have proposed above are relatively
non-intrusive, and would not require any change in how existing
functions receive their arguments. At any rate, I am happy to continue
my fork as long as there is support for integrating it at some point
in the future.
Anyway, this email is already long enough. I have made my case and my
proposal, let me know what you think.
--
Carl