方法如何虚拟通用调用执行? [英] how virtual generic method call is implemented?

查看:112
本文介绍了方法如何虚拟通用调用执行?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在CLR如何implementes调用像这样有趣的:

 抽象类A {
    公共抽象无效美孚< T,U,V>();
}

A中的= ...
a.Foo< INT,字符串,小数>(); //< ===?
 

时的这一号召类型参数标记的按键和编译泛型方法的专业化(一个为所有引用类型和不同code对于所有的值类型)的值会导致某种形式的哈希地图查找<? / P>

解决方案

我没找到这么多的准确信息,与其说这答案是根据<一href="http://research.microsoft.com/en-us/um/people/akenn/generics/DesignAndImplementationOfGenerics.pdf"相对=nofollow> .Net的仿制药从2001年优秀论文(甚至在NET 1.0出来了!),一个短信中的后续纸和我从的 SSCLI诉2.0源$ C ​​$ C (尽管我没能找到确切的code调用虚拟泛型方法)。

让我们从简单的开始:怎么叫非通用非虚方法是什么?通过直接调用该方法code,所以编译code包含直接地址。编译器会从方法表法地址(见下文)。它可以是简单吗?好了,差不多了。事实上,方法是JIT编译使得它更复杂一点:什么是真正所谓的要么是code来编译的方法,然后才执行它,如果它没有被编译之中;或者它的一个指令直接调用编译code,如果它已经存在。我会忽略这个细节上进一步

现在,怎么叫非通用的虚方法?类似的多态性像C ++语言中,有来自访问的方法表这个指针(参考)。每个派生类都有自己的方法表及其方法那里。因此,调用虚方法,获得引用(传过来的参数),从那里,拿到引用方法表,看一下正确的入口在它(条目数量是恒定的为特定的功能),并调用c中的入口点到$ C $。通过调用接口的方法稍微复杂一些,但不是有趣的我们。

现在,我们需要了解code共享。 code可在相同方法的两个实例之间共享,如果在类型参数引用类型对应于任何其它引用类型,和值类型是完全一样的。因此,例如 C&LT;字符串&GT; .M&LT; INT&GT;()股价code与 C&LT;对象&gt; .M&LT; INT&GT;( ),而与 C&LT;字符串&GT; .M&LT;字节&GT;()。有型类型参数和方法类型参数之间没有什么区别。 (2001年的原始论文中提到,code也能共享时,这两个参数是结构 s的相同的布局,但我不知道这是真实的实际执行。)

让我们的道路上通用的方法,一个中间步骤:在泛型类型的非泛型方法。因为code共享,我们需要从什么地方得到的类型参数(例如,用于调用code像新的T [] )。出于这个原因,一般类型的每个实例(例如 C&LT;字符串&GT; C&LT;对象&gt; )都有自己的型把手,其中包含的类型参数,也是方法表。普通的方法可以访问此类型的句柄(技术上的结构容易混淆的名为方法表,即使它包含的不仅仅是方法表更多)从这个引用。有两种类型的不能做到这一点的方法:静态方法和方法上值类型。针对这一情况,该类型的句柄传递作为一个隐藏的参数。

有关非虚泛型方法,类型手柄是不够的,所以他们得到不同的隐藏参数,方法描述,包含类型参数。此外,编译器不能存储在普通方法表的实例,因为这是静态的。因此它创建用于一般的方法,它是由型参数索引不同的第二方法表,并从那里获取方法地址,如果它已经与兼容型参数存在,或者创建一个新的条目。

虚拟通用的方法,现在简单:编译器不知道具体类型,所以它必须使用的方法台在运行时。和常规方法表不能使用,所以它必须寻找在仿制方法的特殊方法表。当然,包含类型参数隐藏参数仍然是present。

而这个研究的一个有趣的珍闻了解到:由于JITer是很懒惰,下列(完全没用)code工作:

 对象提起&LT; T&GT;(诠释计数),其中T:新的()
{
    如果(计数== 0)
        返回新T();

    返回电梯&LT;列表&LT; T&GT;&GT;(计数 -  1);
}
 

相当于C ++ code使编译器给了一个堆栈溢出。

I'm interesting in how CLR implementes the calls like this:

abstract class A {
    public abstract void Foo<T, U, V>();
}

A a = ...
a.Foo<int, string, decimal>(); // <=== ?

Is this call cause an some kind of hash map lookup by type parameters tokens as the keys and compiled generic method specialization (one for all reference types and the different code for all the value types) as the values?

解决方案

I didn't find much exact information about this, so much of this answer is based on the excellent paper on .Net generics from 2001 (even before .Net 1.0 came out!), one short note in a follow-up paper and what I gathered from SSCLI v. 2.0 source code (even though I wasn't able to find the exact code for calling virtual generic methods).

Let's start simple: how is a non-generic non-virtual method called? By directly calling the method code, so the compiled code contains direct address. The compiler gets the method address from the method table (see next paragraph). Can it be that simple? Well, almost. The fact that methods are JITed makes it a little more complicated: what is actually called is either code that compiles the method and only then executes it, if it wasn't compiled yet; or it's one instruction that directly calls the compiled code, if it already exists. I'm going to ignore this detail further on.

Now, how is a non-generic virtual method called? Similar to polymorphism in languages like C++, there is a method table accessible from the this pointer (reference). Each derived class has its own method table and its methods there. So, to call a virtual method, get the reference to this (passed in as a parameter), from there, get the reference to the method table, look at the correct entry in it (the entry number is constant for specific function) and call the code the entry points to. Calling methods through interfaces is slightly more complicated, but not interesting for us now.

Now we need to know about code sharing. Code can be shared between two "instances" of the same method, if reference types in type parameters correspond to any other reference types, and value types are exactly the same. So, for example C<string>.M<int>() shares code with C<object>.M<int>(), but not with C<string>.M<byte>(). There is no difference between type type parameters and method type parameters. (The original paper from 2001 mentions that code can be shared also when both parameters are structs with the same layout, but I'm not sure this is true in the actual implementation.)

Let's make an intermediate step on our way to generic methods: non-generic methods in generic types. Because of code sharing, we need to get the type parameters from somewhere (e.g. for calling code like new T[]). For this reason, each instantiation of generic type (e.g. C<string> and C<object>) has its own type handle, which contains the type parameters and also method table. Ordinary methods can access this type handle (technically a structure confusingly called MethodTable, even though it contains more than just the method table) from the this reference. There are two types of methods that can't do that: static methods and methods on value types. For those, the type handle is passed in as a hidden argument.

For non-virtual generic methods, the type handle is not enough and so they get different hidden argument, MethodDesc, that contains the type parameters. Also, the compiler can't store the instantiations in the ordinary method table, because that's static. So it creates a second, different method table for generic methods, which is indexed by type parameters, and gets the method address from there, if it already exists with compatible type parameters, or creates a new entry.

Virtual generic methods are now simple: the compiler doesn't know the concrete type, so it has to use the method table at runtime. And the normal method table can't be used, so it has to look in the special method table for generic methods. Of course, the hidden parameter containing type parameters is still present.

One interesting tidbit learned while researching this: because the JITer is very lazy, the following (completely useless) code works:

object Lift<T>(int count) where T : new()
{
    if (count == 0)
        return new T();

    return Lift<List<T>>(count - 1);
}

The equivalent C++ code causes the compiler to give up with a stack overflow.

这篇关于方法如何虚拟通用调用执行?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆