CIL stelem
指令(ECMA 335 [pdf]中的III.4.26)指定为
格式程序集格式说明
A4 stelem typeTok将索引处的数组元素替换为
栈上的价值
堆栈过渡:
…,数组,索引,值->…
我不明白typeTok参数的目的是什么。
原始规格
以下是规范中对typeTok的所有提及:
在说明中:
值的类型必须与指令中的typeTok数组元素兼容。
在“正确性”部分中:
typeTok是有效的typedef
,typeref
或typespec
元数据令牌。
在“可验证性”部分中:
对于某些T[]
,数组的跟踪类型为T
;
值的跟踪类型是与typeTok兼容的数组元素;
typeTok与T
是数组元素兼容的
因此,typeTok不会用于任何东西。它只是必须提供。换句话说,我看到的唯一要求是必须存在满足条件的typeTok。
修改规格
但是,仅需要存在这种typeTok等同于将上述规范部分更改为
在说明中:
值的类型必须与数组的元素类型兼容
在“正确性”部分中,删除上述部分。
在“可验证性”部分中:
对于某些T[]
,数组的跟踪类型为T
;
值的跟踪类型是与T
数组元素兼容的
1如果value是数组的元素类型的aec-,则由于aec-with关系的反射性,可以选择值的类型或数组的元素类型作为满足“原始规范”要求的typeTok ”。相反,如果存在具有给定要求的typeTok,则a-e-c-with关系的可传递性立即产生“修改规范”的要求。
那我想念什么呢?为什么在那里有typeTok参数(以及为什么除了stelem.<type>
以外的stelem.ref
指令还存在)?
最佳答案
Stelem TypeToken
存在以支持非基本类型的ValueType。唯一的其他选择是,如果此操作码不存在,则将这些结构装箱。
有一系列的stelem。*元素。对于图元[i,i1,i2,i4,i8,r4,r8和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个字节的情况。较短的方法有更好的内联机会。过去常常是,如果方法的IL超过32个字节它不会被内联。编辑:
我错了。似乎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>;
此顺序按预期工作,因此我不知道他们为什么选择其他路线。