【问题标题】:What is the purpose of the typeTok argument to the CIL's stelem instruction?CIL stelem 指令的 typeTok 参数的目的是什么?
【发布时间】:2013-11-13 10:06:56
【问题描述】:

CIL stelem 指令(ECMA 335 [pdf] 中的 III.4.26)指定为

格式汇编格式说明
A4  stelem typeTokindex 处的数组元素替换为
                                在栈上

堆栈转换:
…,数组,索引,值,-> …

我不明白 typeTok 参数的目的是什么。

原始规格

以下是规范中对typeTok的全部提及:

  • 在描述中:

    值的类型必须是指令中的array-element-compatible-with typeTok

  • 在“正确性”部分:

    typeTok 应为有效的 typedeftypereftypespec 元数据令牌。

  • 在“可验证性”部分:

    • array的跟踪类型是T[],对于一些T

    • value的跟踪类型是array-element-compatible-with typeTok;

    • typeTokarray-element-compatible-with T

所以 typeTok 不用于任何事情;它只需要提供。换句话说,我看到的唯一要求是满足条件的typeTok 必须exist

修改规范

然而,仅仅要求存在这样的 typeTok 就相当于1 将上述规范部分更改为

  • 在描述中:

    value的类型必须是array-element-compatible-witharray的元素类型

  • 在“正确性”部分,删除上述部分。

  • 在“可验证性”部分:

    • array的跟踪类型是T[],对于一些T

    • value的跟踪类型是array-element-compatible-withT


1如果valuea-e-c-witharray的元素类型,那么因为a-e-c-with 关系的自反性,可以选择 value 的类型或数组的元素类型作为满足要求的 typeTok的“原始规格”。相反,如果存在具有给定要求的 typeTok,则 a-e-c-with 关系的传递性立即产生“Modified spec”的要求。


那么我错过了什么?为什么有 typeTok 参数(以及为什么除了stelem.ref 之外的stelem.<type> 指令甚至存在)?

【问题讨论】:

  • 很难回答,这条 IL 指令只在通用代码中使用过。抖动和验证者很容易在他们的代码中使用令牌来执行检查并决定生成什么代码。无论是为了让他们更轻松,还是真的有必要处理数组协方差,这都是我无法带回家的,它们是 .NET 的难点。第 I.8.7.1 章令人头疼。好奇心还是真正的问题?
  • 好奇心,主要是。试图深入研究 IL,这引起了我在阅读规范时的注意。
  • 通常存在原始类型的速记版本,因此您不必使用类型令牌。这导致它减少了 4 个字节。较短的方法通常有更高的内联机会。
  • @MichaelB 我知道这一点,但如果长版本的类型参数不起作用,那么速记版本同样没用(长版本可能同样短)。
  • 从技术上讲,您可以像 CLR 演示的那样对每个数组索引使用 ldelema,然后使用 stind 或 stobj。它的方式更加臃肿。它们的存在是因为 CLR 验证程序也在检查意图。序列ldloc.0,ldc.i4.3,ldc.i4.8stelem.ref 显然是错误的,因为您需要在 int 进入插槽之前将其装箱。我认为目的是针对非原始结构和泛型。但也许是因为非基元在数组中的使用不多(也许它们在泛型上下文中使用得更多),所以序列从来没有被优化过?

标签: arrays cil


【解决方案1】:

Stelem TypeToken 的存在是为了支持非原始的 ValueType。如果此操作码不存在,唯一的其他选择是装箱这些结构。

有一系列stelem.* 元素。对于原语 [i,i1,i2,i4,i8,r4,r8,and ref]

原始元素告诉它期望堆栈上有一个特定大小的元素并且应该读取它,ref 表示有一个对象引用。现在,不是原语的struct 怎么样。您可能会说只使用其中一个相同大小的原语。毕竟,这就是它对Enum 数组所做的事情。考虑DateTimeOffSet。它是 12 个字节,因此您不能使用现有的原语之一。不得不装箱来存储这些元素的数组会很糟糕。

存在的另一个操作码是stelem.any,它用于通用代码。这只是一个简码,以防TypeToken 可能是对class 类型的引用。你总是可以使用stelem.any,但如果typetoken由原语处理,那么使用额外的4个字节是浪费的。

CIL opcodes 需要类型信息的人总是将它们作为操作数,即使根据堆栈上的其他元素应该很明显。这可能只是为了让 CLR 团队的生活更轻松。 (考虑box 需要一个操作码)。这也可能有助于发射器避免犯错。 例如

ldc.i4.8
box typetoken(long)
//whoops we clearly need to conv.i8 before we can box this as a long. 

为什么存在简写版本(例如 Stelem.i4 只是 stelem typetoken(int32)?它们的存在是为了少 4 个字节。较短的方法有更好的内联机会。过去是,如果一个方法超过 32 IL 的字节数不会被内联。

编辑: 我错了。似乎 C# 通常会获取结构地址元素的地址并存储它们。从技术上讲,您可以使用 stelem TypeToken (因为这是在通用情况下发出的),但似乎 VS 团队没有。

var dynmethod = new DynamicMethod("test", typeof(void), new[] { typeof(DateTimeOffset[]), typeof(DateTimeOffset) });
        var gen = dynmethod.GetILGenerator();
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Ldc_I4_0);
        gen.Emit(OpCodes.Ldarg_1);
        gen.Emit(OpCodes.Stelem, typeof(DateTimeOffset));
        gen.Emit(OpCodes.Ret);
        var d=dynmethod.CreateDelegate(typeof(Action<DateTimeOffset[], DateTimeOffset>)) as Action<DateTimeOffset[],DateTimeOffset>;

这个序列按预期工作,所以我不知道他们为什么选择另一条路线。

【讨论】:

    猜你喜欢
    • 2010-09-19
    • 2011-11-12
    • 1970-01-01
    • 2021-06-21
    • 2011-04-02
    • 2011-02-17
    • 2012-10-05
    • 2020-11-12
    • 2021-08-29
    相关资源
    最近更新 更多