Tag: Mono

什么是PrivateImplementationDetails

Posted by – 2009/01/18

解析一个.NET程序集时发现了一段<PrivateImplementationDetails>{A_GUID}模样的代码,这是.NET编译器对某些指令做得优化,经分析会在如下情况下产生。

优化数组创建

var int1 = new[] { 1, 2, 3, 4, 5, 6 };

在解析上面的代码时,发现其产生了一些由编译器控制的IL代码

IL_0008:  ldc.i4.6
IL_0009:  newarr     [mscorlib]System.Int32
IL_000e:  dup
IL_000f:  ldtoken    field valuetype '<PrivateImplementationDetails>{393AE015-A550-4A61-9BFF-186AFB6A4D12}'/'__StaticArrayInitTypeSize=24' '<PrivateImplementationDetails>{393AE015-A550-4A61-9BFF-186AFB6A4D12}'::'$$method0x60000f8-1'
IL_0014:  call       void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array, valuetype [mscorlib]System.RuntimeFieldHandle)
IL_0019:  stloc.0

如果没有使用初始化器

var int1 = new int[6];

生成的IL代码如下

IL_0008:  ldc.i4.6
IL_0009:  newarr     [mscorlib]System.Int32
IL_000e:  stloc.0

上面是创建值类型数组的情况,如果是引用类型的数组呢?

var obj1 = new object[3];
var obj2 = new[] {new object(), "test", 1};

生成的IL代码如下

IL_0001:  ldc.i4.3
IL_0002:  newarr     [mscorlib]System.Object
IL_0007:  stloc.0
IL_0008:  ldc.i4.3
IL_0009:  newarr     [mscorlib]System.Object
IL_000e:  stloc.2
IL_000f:  ldloc.2
IL_0010:  ldc.i4.0
IL_0011:  newobj     instance void [mscorlib]System.Object::.ctor()
IL_0016:  stelem.ref
IL_0017:  ldloc.2
IL_0018:  ldc.i4.1
IL_0019:  ldstr      "test"
IL_001e:  stelem.ref
IL_001f:  ldloc.2
IL_0020:  ldc.i4.2
IL_0021:  ldc.i4.1
IL_0022:  box        [mscorlib]System.Int32
IL_0027:  stelem.ref
IL_0028:  ldloc.2
IL_0029:  stloc.1

从下图可以看到,在产生的.NET程序集中有一个<PrivateImplementationDetails>开头的Module

PrivateImplementationDetails

<PrivateImplementationDetails>{0BAF451E-34D3-4B3B-8567-B578D84A6965}是这个编译器产生的Module的名字,{0BAF451E-34D3-4B3B-8567-B578D84A6965}是这个dll的MVID(Module Version Identifier)。

那么为什么要通过调用System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray这个静态方法来初始化数组呢?通过这位同学试验可以得出结论这是为提高效率而做出的编译时优化。

那什么情况下.NET编译器会产生PrivateImplementationDetails这种机制呢,我还没找到一个完整的列表。除数组外我还发现大的switch表也会产生PrivateImplementationDetails,并简单的试出了启用这种机制的case数量的临界值。

优化case比较多的switch

在代码中加入两个方法SwitchWith6Cases()和SwitchWith7Cases()

public void SwitchWith6Cases()
{
    var param = "a";
    switch (param)
    {
        case "a": break;
        case "b": break;
        case "c": break;
        case "d": break;
        case "e": break;
        case "f": break;
    }
}
 
public void SwitchWith7Cases()
{
    var param = "a";
    switch (param)
    {
        case "a": break;
        case "b": break;
        case "c": break;
        case "d": break;
        case "e": break;
        case "f": break;
        case "g": break;
    }
}

前者生成的代码如下,基本符合C#代码的逻辑

.method public hidebysig instance void  SwitchWith6Cases() cil managed
{
  // Code size       105 (0x69)
  .maxstack  2
  .locals init ([0] string param,
           [1] string CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldstr      "a"
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  stloc.1
  IL_0009:  ldloc.1
  IL_000a:  brfalse.s  IL_0068
  IL_000c:  ldloc.1
  IL_000d:  ldstr      "a"
  IL_0012:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_0017:  brtrue.s   IL_005c
  IL_0019:  ldloc.1
  IL_001a:  ldstr      "b"
  IL_001f:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_0024:  brtrue.s   IL_005e
  IL_0026:  ldloc.1
  IL_0027:  ldstr      "c"
  IL_002c:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_0031:  brtrue.s   IL_0060
  IL_0033:  ldloc.1
  IL_0034:  ldstr      "d"
  IL_0039:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_003e:  brtrue.s   IL_0062
  IL_0040:  ldloc.1
  IL_0041:  ldstr      "e"
  IL_0046:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_004b:  brtrue.s   IL_0064
  IL_004d:  ldloc.1
  IL_004e:  ldstr      "f"
  IL_0053:  call       bool [mscorlib]System.String::op_Equality(string, string)
  IL_0058:  brtrue.s   IL_0066
  IL_005a:  br.s       IL_0068
  IL_005c:  br.s       IL_0068
  IL_005e:  br.s       IL_0068
  IL_0060:  br.s       IL_0068
  IL_0062:  br.s       IL_0068
  IL_0064:  br.s       IL_0068
  IL_0066:  br.s       IL_0068
  IL_0068:  ret
} // end of method SwitchSample::SwitchWith6Cases

而当switch有超过6个case的时候,所生成的IL代码如下。

.method public hidebysig instance void  SwitchWith7Cases() cil managed
{
  // Code size       189 (0xbd)
  .maxstack  4
  .locals init ([0] string param,
           [1] string CS$4$0000,
           [2] int32 CS$0$0001)
  IL_0000:  nop
  IL_0001:  ldstr      "a"
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  stloc.1
  IL_0009:  ldloc.1
  IL_000a:  brfalse    IL_00bc
  IL_000f:  volatile.
  IL_0011:  ldsfld     class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '<PrivateImplementationDetails>{E6F6F304-0E96-4AD2-AF7A-7D08372A387B}'::'$$method0x60000fd-1'
  IL_0016:  brtrue.s   IL_0079
  IL_0018:  ldc.i4.7
  IL_0019:  newobj     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::.ctor(int32)
  IL_001e:  dup
  IL_001f:  ldstr      "a"
  IL_0024:  ldc.i4.0
  IL_0025:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_002a:  dup
  IL_002b:  ldstr      "b"
  IL_0030:  ldc.i4.1
  IL_0031:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_0036:  dup
  IL_0037:  ldstr      "c"
  IL_003c:  ldc.i4.2
  IL_003d:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_0042:  dup
  IL_0043:  ldstr      "d"
  IL_0048:  ldc.i4.3
  IL_0049:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_004e:  dup
  IL_004f:  ldstr      "e"
  IL_0054:  ldc.i4.4
  IL_0055:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_005a:  dup
  IL_005b:  ldstr      "f"
  IL_0060:  ldc.i4.5
  IL_0061:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_0066:  dup
  IL_0067:  ldstr      "g"
  IL_006c:  ldc.i4.6
  IL_006d:  call       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0, !1)
  IL_0072:  volatile.
  IL_0074:  stsfld     class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '<PrivateImplementationDetails>{E6F6F304-0E96-4AD2-AF7A-7D08372A387B}'::'$$method0x60000fd-1'
  IL_0079:  volatile.
  IL_007b:  ldsfld     class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '<PrivateImplementationDetails>{E6F6F304-0E96-4AD2-AF7A-7D08372A387B}'::'$$method0x60000fd-1'
  IL_0080:  ldloc.1
  IL_0081:  ldloca.s   CS$0$0001
  IL_0083:  call       instance bool class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32>::TryGetValue(!0,
                                                                                                                        !1&)
  IL_0088:  brfalse.s  IL_00bc
  IL_008a:  ldloc.2
  IL_008b:  switch     ( 
                        IL_00ae,
                        IL_00b0,
                        IL_00b2,
                        IL_00b4,
                        IL_00b6,
                        IL_00b8,
                        IL_00ba)
  IL_00ac:  br.s       IL_00bc
  IL_00ae:  br.s       IL_00bc
  IL_00b0:  br.s       IL_00bc
  IL_00b2:  br.s       IL_00bc
  IL_00b4:  br.s       IL_00bc
  IL_00b6:  br.s       IL_00bc
  IL_00b8:  br.s       IL_00bc
  IL_00ba:  br.s       IL_00bc
  IL_00bc:  ret
} // end of method SwitchSample::SwitchWith7Cases

这时候在<PrivateImplementationDetails>*模块中会有如下的一个叫做$$method0×60000fd-1的静态字段,辅助switch的case匹配。

.field static assembly class [mscorlib]System.Collections.Generic.Dictionary`2<string,int32> '$$method0x60000fd-1'

通过Dictionary<string,int32>::TryGetValue方法去查找匹配的case。O(1)的效率在case多的情况下比线性O(n)的查找快的多。

使用Mono.Cecil读写.NET程序集

Posted by – 2008/12/10

使用Reflexil修改.NET程序集
http://www.codeproject.com/KB/msil/reflexil.aspx

基于Cecil的项目合集
http://groups.google.com/group/mono-cecil/web/projects-using-cecil

DimeCast上一个视频演示通过Cecil向程序集中注入代码MSDN关于IL指令的文档

与Mono.Cecil类似的项目

Posted by – 2008/12/08

与Cecil类似的项目:

System.Reflection – .NET内置的反射工具

Zonnon – A language and compiler experiment

CCI(Common Compiler Infrastructure) – 没有太多信息。

Java平台有著名的工具ASM,详见其开发者文档。ASM对操作Generic Collection也有很好的实现。

Mono相关的项目

Posted by – 2008/12/08

有幸在上周五六参加Apache Meetup和Barcamp活动中遇到了Novell公司的Alex Lau。期间他提到了Mono Accecibility这个项目。我在构建NTE时用到了Mono Cecil来读取ECMA IL格式的指令,所以对Mono的相关项目颇有好感。

所以再次罗列一下网站资源:

  • Cecil – A .net assembly manipulation library
  • Accessibility – aims to enable Windows applications to be fully accessible on Linux
  • Gendarme – rule-based tool to inspect .Net assemblies(which using Cecil)

一个视频Miguel de Icaza谈Mono

如果有机会应该参与一些Mono相关的工作。Google也参与了Mono相关的项目

.NET Testability Explorer

Posted by – 2008/12/03

在负责一个Unit Testing on CAB的内部培训项目期间,我阅读了Miško Hevery的一系列文章,他目前在Google担任敏捷教练。我对他的Testability Explorer(简称GTE) 产生了浓厚兴趣。

虽然Java和.Net世界已经有一些类似工具存在,但GTE这个项目实现功能比较专一,输出结果也直接易懂。相对NDepend强大但相对复杂和FxCop官方但略显强硬,GTE这个项目从形式上来说更有亲和力。所以我和Joe同学在Google Code上开始了一个新项目: .NET Testability Explorer(简称NTE)。
功能受众是期望对输出的.NET Assembly进行分析的开发人员,报告中提供的信息可以帮助他们对设计进行调整,从而使code base更容易测试和维护。

计划是,首先实现圈复杂度的度量,然后实现输出类似GTE形式的报告。希望能够在09年农历新年前发出第一个版本。
目前TE的成本模型支持对全复杂度、全局变量和笛米特法则的计算。

技术层面可以参考Gendarme这个项目,一个基于Mono.Cecil的代码分析工具。

Updates 20081221
项目分成两部分:IL分析,基于规则的成本模型计算

IL分析:目前IL分析方面的技术调研实现了类型,字段,方法级别的读取。剩下最大的一部分是方法中instruction的decompose,这部分估计占全部的40%工作量。但是我需要从成本模型分析的方向进行分析,确定在decompose过程中所需要收集的信息,所以先完成大概10%遍历所有instruction构建一个草图模型。目标是实现最简单的圈复杂度的计算。
成本模型:完成大概10%-20%的基础模型构建,支持全复杂度的计算。
下一步,重构设计使其能支持更多的规则考量,并增强其可扩展性。
目前从代码量来看完成了TE全部功能的1/6,考虑到TE支持对C++语言的分析,折半其工作量,目前大概完成了全部工作的1/3。

目标定于在完成一半的时候提交代码到Google Code,并发布第一个可运行的版本。时间定于08年圣诞节期间。新的计划是春节之前,发布第一个可运行的版本,并将代码提交。