ILBC 規範

 

本文是 VMBC / D# 項目 的 系列文章,html

 

有關 VMBC / D# , 見 《我發起並創立了一個 VMBC 的 子項目 D#》(如下簡稱 《D#》) http://www.javashuo.com/article/p-zziqptgy-s.html  。java

 

ILBC 系列文章 收錄在 《ILBC 白皮書》   http://www.javashuo.com/article/p-bsuuysoc-bo.html    。算法

 

 

ILBC 規範:編程

 

加載程序集:數組

ILBC 程序集 有 2 種, 安全

1  Byte Code 程序集,   擴展名 爲  .ilb,   表示  「ILBC Byte Code」  。性能優化

2  Native Code 程序集, 擴展名 遵循  操做系統 定義的 動態連接庫 規範, 好比 Windows 上就是 .dll 文件,閉包

    Native Code 程序集  就是  操做系統 定義的 動態連接庫  。架構

 

假設 操做系統 是 Windows,  程序集 名字 是 A,  加載 A 的 過程 是:併發

在 當前目錄 下 先查找  A.ilb, 若存在 則 JIT 編譯 A.ilb 爲 本地代碼 A.dll, 加載 A.dll,

若找不到 A.ilb, 則找 A.dll, 若存在 則 加載 A.dll 。

加載 本地庫  A.dll  的 方式 遵循 操做系統 定義的 動態連接 規範  。

 

JIT 編譯 A.ilb 爲 本地代碼 並 加載 的 過程 能夠在 內存中 完成,  不必定要 生成 文件 A.dll  (若是 技術上 能夠實現 在 內存 中加載的話)。

 

高級語言(D#) 編譯 的 過程:

高級語言(D#) 編譯 有  2 種方式,

 

1  AOT,  高級語言(D#) 編譯器 先根據 高級語言(D#) 源代碼  生成    C 語言 中間代碼,  再由  InnerC (InnerC to Byte Code)  編譯爲表達式樹, 再由  InnerC(Byte Code to Native Code) 把 表達式樹 生成爲  Native Code  。  Native Code 是一個 本地庫, 好比  .dll  。

 

2  JIT ,   高級語言(D#) 編譯器 先根據 高級語言(D#) 源代碼  生成    C 語言 中間代碼,  再由  InnerC (InnerC to Byte Code)  編譯爲表達式樹, 把 表達式樹 序列化 獲得 Byte Code, 將 Byte Code 保存爲 ilb 文件 即 獲得 Byte Code 程序集(.ilb)  。

    .ilb  在 運行的時候 由  ILBC 運行時 的   InnerC (Byte Code to Native Code)   把  Byte Code  反序列化 爲 表達式樹, 再把 表達式樹 編譯爲  Native Code  。

 

把 Native Code 程序集 加載到 應用程序 後,  ILBC 運行時 會 調用 程序集 的 ILBC_Load() 函數,  ILBC_Load() 會 建立一個 ILBC_Assembly 結構體, 並返回這個 結構體 的 指針,  ILBC_Assembly 結構體 包含了 程序集 的 元數據 信息,  相似  .Net / C# 中 的  System.Reflection.Assembly   。

元數據 就是 一堆 結構體(Struct),  這些 Struct 及 ILBC_Load() 函數 的 代碼是由 高級語言(D#)編譯器 生成,   代碼以下:

 

struct    ILBC_Assembly

{

         ILBC_ClassLoader    classLoaderList  [ n ]   ;        //  n 是 程序集 中 Class 的 數量, 由 高級語言(D#) 編譯器 在 編譯時 指定

 

         //    classLoader    包含了 加載  Class  的 函數 的 函數指針 (保存在   load  字段 裏) 

         //    每一個 Class 有一個 classLoader,

         //    classLoaderList   是 保存  classLoader  的 數組,

         //    在 ILBC 運行時 加載 Class 時 會調用  classLoader.load  保存的 函數指針 指向 的 函數, 具體內容見下文

         //    Class 加載完成獲得的  Type 對象 保存在  type 字段 裏

}

 

struct    ILBC_ClassLoader

{

            char  *      className    ;          //   Class 名字

            void  *       load     ;            //   加載 Class 的 函數 的 函數指針

            ILBC_Type  *       type   =   0  ;         //   加載 Class 完成後把  Type 對象 保存在這裏

}

 

 

 

struct    ILBC_Type

{

          char  *           name    ;         //   Class 名字

          int      size     ;           //   Class 佔用的 空間大小(字節數)

          ILBC_Field     fieldList  [ n ]  ;          //  n 是 Class 中 Field 的 數量, 由 高級語言(D#) 編譯器 在 編譯時 指定

          int     fieldCount   ;          //    C 語言數組 的 長度 須要 本身記錄

          ILBC_Method     methodList    [ n ]  ;          //   n 是 Class 中 Method 的 數量, 由 高級語言(D#) 編譯器 在 編譯時 指定

          int     methodCount   ;          //    C 語言數組 的 長度 須要 本身記錄

}

 

struct    ILBC_Field

{

         char     name   [ n ]  =   "字段名"  ;       //  n 應和 字段名字符串 的 字節數 相等, n 由 高級語言(D#) 編譯器 在 編譯時 指定

         int         size;            //  字段 佔用的 字節數

         int         offset;          //  字段 相對於  ILBC_Field 結構體 的 首地址 的 偏移量

         //    ILBC_Type  *      type  ;

         char  *       type  ;           //   type  不能 聲明爲   ILBC_Type  或者  ILBC_Type  *   類型, 由於會形成 Type 和 Field 之間的 循環引用,

                                               //   因此先聲明爲  char  *  (字符串),   保存 Type 的名字,  經過  GetFieldType()  之類 的 方法 來返回  Type 對象,

                                               //   Type 對象 就至關於 這裏的    ILBC_Type  或者  ILBC_Type  *    。

}

 

struct    ILBC_Method

{

         char     name   [ n ]  =   "方法名";       //  n 應和 方法名字符串 的 字節數 相等, n 由 高級語言(D#) 編譯器 在 編譯時 指定

         ILBC_Argument  *   argList    [ n ]  ;       //   n 是 方法 中 參數 的 數量, 由 高級語言(D#) 編譯器 在 編譯時 指定

         Type  *   returnValue  ;        //   返回值 類型

         void *    funcPtr  ;         //   Method 對應的 函數指針

}

 

struct    ILBC_Argument

{

         char     name   [ n ]  =   "參數名";       //  n 應和 參數名字符串 的 字節數 相等, n 由 高級語言(D#) 編譯器 在 編譯時 指定

         ILBC_Type  *      type;        //    參數類型

}

 

看到這裏, 是否是跟 C# 反射裏的  AssemblyInfo, Type, FieldInfo, MethodInfo  很像 ?

是的, ILBC 也要支持 完整的 元數據 架構,  元數據 用於 動態連接 和 反射  。

 

接下來 是   ILBC_Load()    相關的 代碼:

假設 程序集 名字 是 B,  包含了  Person 類 和 Animal 類  2 個 類,  Person 類 有  2 個字段  name,   age, 有  2 個方法  Sing(0,  Smile() ,

 

void *      ILBC_ClassLoaderList_B   [ 2 ]    ;        //   數組長度  2  表示  B 程序集 包含了  2 個 類

 

ILBC_Assembly  *      ILBC_Load()

{

             ILBC_Assembly  *     assembly   =    ILBC_gcNew(    sizeof (   ILBC_Assembly   )    )    ;

 

           

             assembly.classLoaderList  [ 0 ].className   =   "Person"    ; 

             assembly.classLoaderList  [ 0 ].load   =   &   ILBC_LoadClass_B_Person    ;

 

             assembly.classLoaderList  [ 1 ].className   =   "Animal"    ; 

             assembly.classLoaderList  [ 1 ].load   =   &   ILBC_LoadClass_B_Animal    ;

 

             return        assembly     ;

}

 

ILBC_Type *       ILBC_LoadClass_B_Person()

{

            ILBC_Type  *      type    =    ILBC_gcNew  (   sizeof (  ILBC_Type  )    );        

            //   ILBC_gcNew( )  是  ILBC 提供的一個 庫函數, 用於 在 堆 裏申請一塊空間, 這裏是在 堆 裏 建立一個   ILBC_Type   結構體

 

             type.name = "Person";

             type.size  =   8;          //     Class 佔用的 空間大小(字節數),  name 字段是 char * 類型, 假設 指針 是 32 位 地址, 佔用 4 個 字節, age 是 int 類型, 假設是 32 位整數, 佔用  4 個字節,  那麼  Class 的 佔用字節數 就是  4 + 4 = 8,  即  size = 8; ,   size 是由 編譯器 計算決定的

 

             type.fieldList [ 0 ].name = "name";

             type.fieldList [ 0 ].size =    //   String 是 引用類型, 因此這裏是 引用 的 Size

             type.fieldList [ 0 ].type = "String";      //    假設 基礎庫 提供了  String  類型

 

             type.fieldList [ 1 ].name = "age";

             type.fieldList [ 1 ].size = 4;     //    假設  int  是  32 位 整數類型

             type.fieldList [ 1 ].type = "Int32";     //    假設  int  是  32 位 整數類型,  且 基礎庫 提供的  32 位 整數類型 是  Int32

 

             type.methodList [ 0 ].name = "Sing";

             //   由於 Sing() 方法 沒有 參數, 因此  argList [ 0 ]   長度爲 0,  不用 初始化

             type.methodList [ 0 ].funcPtr   =   &  ILBC_Class_B_Sing;       //   ILBC_Class_B_Sing  是  Sing() 方法 對應的 函數, 由 編譯器 生成

 

             type.methodList [ 1 ].name = "Smile";

             //   由於 Smile() 方法 沒有 參數, 因此  argList [ 0 ]   長度爲 0,  不用 初始化

             type.methodList [ 1 ].funcPtr   =   &  ILBC_Class_B_Smile;       //   ILBC_Class_B_Smile  是  Smile() 方法 對應的 函數, 由 編譯器 生成

 

             return      type;

}

 

ILBC_LoadClass_B_Animal()  函數    和    ILBC_LoadClass_B_Person()  函數     相似 。

 

當 程序 中 第一次 用到 程序集 時, ILBC 運行時(調度程序) 纔會 加載 程序集,

第一次 用到 程序集 是指 第一次 用到 程序集 裏的 類,

第一次 用到 類 是指   第一次 建立對象( new 類() )  或者  第一次 調用靜態方法( 類.靜態方法() )  、 第一次 訪問靜態字段( 類.靜態字段 )    這 3 種狀況  。

 

類 也是 在 第一次 用到 時 加載,

固然, 第一次 加載 程序集 是 必定會 加載一個 類, 但 其它 的 類 會在 用到 時 才加載  。

加載類 完成時 會調用 類 的 靜態構造函數 。

 

調度程序 加載完 程序集 後, 會把 程序集 的 ILBC_Load()  返回的  ILBC_Assembly 結構體 的 指針 保存到一個 名字是 ILBC_AssemblyList 的 鏈表 裏,

新加載 的 程序集 的 ILBC_Assembly 結構體 的 指針 會 追加到 這個 鏈表 裏 。

ILBC_AssemblyList   是 調度程序 裏 的  一個 全局變量:

 

ILBC_LinkedList  *     ILBC_AssemblyList      ;

 

ILBC_LinkedList  是一個 鏈表 實現,   ILBC_LinkedList  自己是一個 結構體, 定義見下文, 再配合一些 向鏈表追加元素 、刪除元素 等函數 就是 一個 鏈表 實現, 函數 的 部分 略  。

 

struct     ILBC_LinkedList

{

            ILBC_LinkedListNode  *    first    ;          //   鏈表 頭指針

            ILBC_LinkedListNode  *    last    ;          //   鏈表 尾指針

}

 

struct    ILBC_LinkedListNode

{

             ILBC_LinkedListNode   *          before    ;        //   上一個 節點

             ILBC_LinkedListNode   *          next       ;        //    下一個 節點

             void   *             element       ;              //     節點包含的元素, 就是 實際存放 的 數據

 

假設 有 A 、B   2 個 程序集,  A 引用了 B,

B 中 包含 Class Person,   Person  有 構造函數  Person() {  }   ,   那麼, A 中  new Person()  的 代碼 會被 編譯成:

 

void   *      ILBC_Class_Person_Constructor   =   0   ;        //    這是   A  裏的 全局變量,  表示  Person 的 構造函數 的 函數指針,  0 表示 空指針, 也表示 未初始化

 

……

 

//     代碼 中 調用  Person 類 構造函數 的 代碼

//     ILBC_Class_Person  是  高級語言(D#) 編譯器 生成的 表示 Person 類 的 Struct, 包含了 Person 類 的 字段

 

if    (   !   ILBC_ifClassInit_Person    )

{

             ILBC_Init_Linked_Class_Person()   ;         //    初始化  Person 類

}

 

//   ILBC_Linked_ClassSize_Person  是一個 全局變量, 表示 Person 類 佔用的 空間大小(字節數)

void  *       person    =     ILBC_gcNew(   ILBC_Linked_ClassSize_Person   );

 

//   Person 類 初始化 後,  構造函數 指針  ILBC_Linked_Class_Person_Constructor  就被 初始化 了(填入了  Person 構造函數 的 地址), 就能夠調用了

ILBC_Linked_Class_Person_Constructor  (   person   );            //   調用 Person 類 構造函數, 把  person 結構體 指針 傳給 構造函數 進行 初始化

 

調用  Person 類的 靜態字段 和 靜態方法 的 代碼 和 上面 相似, 只須要把 最後一句 代碼 換成:

 

字段類型  變量   =   *  ILBC_Linked_Class_Person_靜態字段名    ;      //   訪問 靜態字段

ILBC_Linked_Class_Person_靜態函數名 (    參數列表     )    ;           //  調用 靜態函數

 

ILBC_ifClassInit_Person   是一個 全局變量, 表示  Person 類 是否 已經 初始化, 定義以下:

 

char    ILBC_ifClassInit_Person    =   0    ;

 

B 程序集 的  Person 類 在 A 程序集 裏的 「初始化」  是指  完成了 Person 類 在 A 裏的 連接工做,  初始化 完成後, A 的 代碼 就能夠 訪問 Person 類 了 。

訪問 Person 類 包括 建立對象(new Person() )、調用函數 、訪問字段 。

連接工做 包括    

類連接, 向 A 裏定義好的 保存 Person 類 的 佔用空間大小(Size (字節數)) 的 全局變量 寫入 類 的 佔用空間大小(Size (字節數)),

字段連接 是 向 A 裏定義好的 保存  Person 類的 各個字段的偏移量 的 變量  寫入  字段的偏移量,

函數連接 是 向 A 裏定義好的  保存  Person 類 的 各個方法 的 函數地址(函數指針) 的 變量  寫入  函數地址, 包括 構造函數 和 成員函數 。

 

ILBC_Linked_Class_Person_Constructor   是 一個 全局變量, 表示   Person 類 的 構造函數 的 函數指針,定義以下:

 

void  *         ILBC_Linked_Class_Person_Constructor     ;

 

ILBC_Init_Linked_Class_Person ()     的 代碼以下:

 

ILBC_Init_Linked_Class_Person () 

{

             lock (    ILBC_ifClassInit_Person    )

             {

                        if    (   !   ILBC_ifClassInit_Person   )

                        {

                                     ILBC_Type  *    type    =    ILBC_Runtime_GetType( "B",   "Person" )   ;        //   參數 "B" 表示 程序集 名字,  "Person"  表示 類 名

 

                                     ILBC_Linked_ClassSize_Person   =    type.size   ;

 

                                     //    ILBC_Linked_Class_Person_name  是 保存 Person 類 name 字段 偏移量 的 全局變量, 由 編譯器 生成, 值 須要在 加載類 的 時候 初始化, 也就是 下面的 代碼 裏 初始化

                                     //    ILBC_Linked_ClassFieldType_Person_name 是 保存 Person 類 name 字段 類型(類型名字) 的 常量, 由 編譯器 生成, 值 由 編譯器 給出, 值 就是 name 字段 的 類型 的 名字

                                     ILBC_Init_Linked_Class_Field(  &  ILBC_Linked_Class_Person_name,   ILBC_Linked_ClassFieldType_Person_name,   "name",      type  );       //   初始化  name 字段 的 偏移量

                                     ILBC_Init_Linked_Class_Field(  &  ILBC_Linked_Class_Person_age,   ILBC_Linked_ClassFieldType_Person_age,   "age",     type  );       //   初始化  age 字段 的 偏移量

 

                                     //   若是有 靜態字段, 也是 一樣的 初始化, 不過 靜態字段 應該 不是 初始化 偏移量, 而是 直接 是 地址,

                                     //   靜態字段 的 指針變量  好比    「變量類型 *    ILBC_Linked_Class_Person_靜態字段名   ;」

                                     

                                     ILBC_Init_Linked_Class_Person_Constructor(  type  );      //   初始化  構造函數 的 函數指針

 

                                     ILBC_Init_Linked_Class_Method(  &  ILBC_Linked_Class_Person_Sing,    "Sing",   type  );      //   初始化 Sing() 函數 的 函數指針

                                     ILBC_Init_Linked_Class_Method(  &  ILBC_Linked_Class_Person_Smile    "Smile",   type  );      //   初始化  Smile() 函數 的 函數指針

 

                                     //   若是有 靜態方法, 也是 一樣的 初始化,  靜態方法 的 指針變量 好比   「void  *   ILBC_Init_Linked_Class_Person_靜態方法名   ;」

 

                                     ILBC_ifClassInit_Person  =  1   ;

                        }

             }

}

 

void      ILBC_Init_Linked_Class_Field( int *  fieldOffsetVar,   char *  fieldType,   char *  name, ILBC_Type *  type )

{

               for (int i = 0;  i<type.fieldCount;  i++)

               {

                              ILBC_Field  *    field   =   &  type.fieldList [ i ];

                              if ( field.name  ==  name )     //  這句代碼是 僞碼 , 意思是 判斷  2 個字符串 是否相等

                              {

                                             //    咱們這裏 判斷 類型 是否 相同 是 不嚴格的, 只是 判斷 了 名字

                                             //    這裏 涉及到 類型檢查 和 類型安全,  詳細討論 見 文章 最後 總結 部分

                                             if ( field.type  ! =  fieldType )     //  這句代碼是 僞碼 , 意思是 判斷  2 個字符串 是否相等

                                                     throw new Exception ( "名字爲 " + name + " 的 字段 的 類型 與 引用 的 元數據 裏的 類型 不符 。" );      //  這句代碼 是 僞碼, 應該是 函數 增長一個 errorCode 參數, 經過 errorCode 參數返回異常

 

                                             * fieldOffsetVar  =  field -> offset;

                                             return  ;

                              }

                }

                throw new Exception( "找不到名字是 " + name + " 的 字段 。" );       //  這句代碼 是 僞碼, 應該是 函數 增長一個 errorCode 參數, 經過 errorCode 參數返回異常

}

 

void     ILBC_Init_Linked_Class_Method ( void *  funcPtrVar,    char *  name,   ILBC_Type *  type ) 

{

               for (int i = 0;  i<type.methodCount;  i++)

               {

                              ILBC_Method  *    method   =   &  type.methodList [ i ];

                              if ( method.name  ==  name )     //  這句代碼是 僞碼 , 意思是 判斷  2 個字符串 是否相等

                              {

                                             * funcPtrVar  =  method -> funcPtr;

                                             return  ;

                              }

                }

                throw new Exception( "找不到名字是 " + name + " 的 方法 。" );       //  這句代碼 也是 僞碼, 應該是 函數 增長一個 errorCode 參數, 經過 errorCode 參數返回異常

}

 

相關的 全局變量 / 常量 總結以下:

 

char    ILBC_ifClassInit_Person    =    0    ;          //     Person 類 是否 已 初始化

int       ILBC_Linked_ClassSize_Person    ;        //     Person 類 佔用的 空間大小(字節數), 值 由 編譯器 在 編譯 A 項目時 根據 B 的 元數據 給出

int       ILBC_Linked_Class_Person_name    ;     //     Person 類 name 字段 的 偏移量

int       ILBC_Linked_Class_Person_age    ;     //     Person 類 age 字段 的 偏移量

const     char *       ILBC_Linked_ClassFieldType_Person_name    ;     //     Person 類 name 字段 的 類型(類型名字)

const     char *       ILBC_Linked_ClassFieldType_Person_age    ;     //     Person 類 age 字段 的 類型(類型名字)

void  *         ILBC_Linked_Class_Person_Constructor     ;         //     Person 類 的 構造函數 函數指針

void  *         ILBC_Linked_Class_Person_Sing     ;         //     Person 類 的 Sing 方法 函數指針

void  *         ILBC_Linked_Class_Person_Smile     ;         //     Person 類 的 Smile 方法 函數指針

 

看到這裏, 你們可能會問, 若是 構造函數 和 方法 有 重載 怎麼辦 ?

確實 有這個問題,  這個 須要 再做 進一步 的 細化設計,  如今 先 略過  。

 

ILBC_Runtime_GetType()  函數 的 定義以下:

 

ILBC_Type  *        ILBC_Runtime_GetType(  char *  assemblyName,    char *  typeName  )

{

               先在    ILBC_AssemblyList    中查找  名字 是 assemblyName 的 程序集 是否已存在,

               若是 不存在, 就先 加載 程序集,

               加載程序集 的 過程 上文 中 提過, 就是 先把 程序集 加載 到 應用程序, 再調用 程序集 的   ILBC_Load()  函數, 返回一個  ILBC_Assembly 結構體 的 指針,

               調度程序 把 這個 結構體 指針 保存 到   ILBC_AssemblyList   這個 鏈表 裏  。

 

               找到 程序集 後,  再在   assembly.classLoaderList   裏 找  名字 是  className  的  classLoader,

               找到  classLoader  之後,  看   classLoader.type 字段 是不是 空指針(0), 若是是, 就說明  Class  尚未 加載,

               就 加載 Class,  加載 Class 獲得的 Type 對象 就存放在   classLoader.type  字段 裏  。

               加載 Class 的 過程 上文中 講述過, 假設 加載 B 程序集 的 Person 對象,

               就是調用 B 程序集 裏的    ILBC_LoadClass_B_Person()  函數,  該 函數 加載 Person 類, 並返回 表示  Person 類 的 Type 對象  的  ILBC_Type  結構體 的 指針 。

 

               調用  類  的 靜態構造函數             *************   這裏 加個 着重號, 類 加載 完成後 調用 類 的 靜態構造函數

 

               返回    ILBC_Type  結構體    的 指針  。

}

 

訪問 Person 對象 的 字段 的 代碼 是:

 

void  *     person    ;

 

……

 

char  *    name   =    *  (   person  +  ILBC_Linked_Class_Person_name   )   ;

int    age   =    *  (   person  +  ILBC_Linked_Class_Person_age   )   ;

 

調用 Person 對象 的 方法 的 代碼 是:

 

void  *    person    ;

 

ILBC_Linked_Class_Person_Sing (   person   )   ;         //   調用  Sing()  方法,  person 參數 是 this 指針

ILBC_Linked_Class_Person_Smile (   person   )   ;         //   調用  Smile()  方法,  person 參數 是 this 指針

 

總結一下:

ILBC 的 連接 是 相似  .Net / C#  的 動態連接,

ILBC 的 連接 以 程序集 爲 單位,  採用 延遲加載(Lazy Load) 的方式, 只有用到 程序集 的時候才加載, 「用到」 是指 第一次 用到 程序集 裏的 類(Class) 。

將 程序集 加載 到 應用程序 之後, 對 程序集 裏的 類(Class) 也採用  延遲加載(Lazy Load) 的方式,

第一次 用到 類 的 時候纔會 初始化 類 的 連接表,   連接表 初始化 完成後, 就 能夠 調用 類 了, 包括  建立對象,訪問 字段 和 方法  。

 

連接表 不是 一個 「表」,  而是 一堆 全局變量 / 常量,  就是 上文 中 列舉出的 全局變量 / 常量, 這裏再列舉出來看看:

 

char    ILBC_ifClassInit_Person    =    0    ;          //     Person 類 是否 已 初始化

int       ILBC_Linked_ClassSize_Person    ;        //     Person 類 佔用的 空間大小(字節數), 值 由 編譯器 在 編譯 A 項目時 根據 B 的 元數據 給出

int       ILBC_Linked_Class_Person_name    ;     //     Person 類 name 字段 的 偏移量

int       ILBC_Linked_Class_Person_age    ;     //     Person 類 age 字段 的 偏移量

const     char *       ILBC_Linked_ClassFieldType_Person_name    ;     //     Person 類 name 字段 的 類型(類型名字)

const     char *       ILBC_Linked_ClassFieldType_Person_age    ;     //     Person 類 age 字段 的 類型(類型名字)

void  *         ILBC_Linked_Class_Person_Constructor     ;         //     Person 類 的 構造函數 函數指針

void  *         ILBC_Linked_Class_Person_Sing     ;         //     Person 類 的 Sing 方法 函數指針

void  *         ILBC_Linked_Class_Person_Smile     ;         //     Person 類 的 Smile 方法 函數指針

 

這些 全局變量 是 A 裏 定義 的, 是  A 裏 引用 B 的 連接表  。

注意,  Class 的 加載 是 在 ILBC 運行時 裏 進行的, 一個 Class 的 加載 對於 整個 應用程序 只進行一次,

Class 的 連接表 初始化(Init) 是 和 程序集 相關的,  假設有   A 、B 、C  3 個 程序集 引用了  D 程序集,

那麼 當 A 用到 D 的時候, 會 初始化 A 裏 引用 D 的 連接表,

當 B 用到 D 的時候, 會 初始化 B 裏 引用 D 的 連接表,

當 C 用到 D 的時候, 會 初始化 C 裏 引用 D 的 連接表 。

 

連接表 是 屬於 程序集 的, 假設 A 引用了 B C D, 那麼 A 裏 會有 B C D  的 連接表,

也就是說 上面的 全局變量 會在 A 裏 聲明  3 組,  分別 對應  B C D 程序集 。

 

說到這裏, 咱們會發現, 上面的 全局變量 的 命名 沒有 包含 程序集 的 名字, 好比  ILBC_Linked_Class_Person_name,

這個 表示  Person 類 的 name 字段 的 偏移量,

可是 並無 表示出 Person 類 是 哪個 程序集 的 。

 

因此, 應該 給  變量 增長一個 分隔符(鏈接符) 來 分隔(鏈接)  各項信息,

咱們規定,  InnerC  應支持 在  變量名 裏 使用  "<>"  字符串, 這樣可使用  "<>"  來 分隔(鏈接)  各項信息 。

 

注意, 是  "<>"  字符串, 不是  "<",  也不是  ">" ,  也不是 "< …… >"   ,

好比,    a<>b   這個 變量名 是 合法的,   a<b  是 不合法 的,  a>b  是 不合法的,  a<b>c  這個變量名 也是 不合法的 。

 

ILBC_Linked_Class_Person_name   能夠 這樣 來 表示:

ILBC_Linked<>B<>Person<>name   ,   這表示   連接(引用) 的 B 程序集 的 Person 類 的 name 字段 的 偏移量

 

"<>"  字符串 在 D# 裏 是 不能用於 程序集 名字空間 類 字段 方法 的 名字 的,  因此能夠在   C 中間語言  裏 用在 變量名 裏 做爲  分隔符(鏈接符) 。

 

ILBC 運行時 調度程序 應提供 如下 函數:

 

ILBC_Type  *       ILBC_Runtime_GetType(  char *  assemblyName,     char *  typeName  ) 

該函數用於 返回 指定的 程序集名 的 程序集 中 指定的 類名 的 類 的 Type 對象

ILBC_Type   是  調度程序 中 定義的 結構體,  爲了能讓  程序集 訪問, 須要 高級語言(D#)編譯器  引用  調度程序 發佈 的 頭文件(.h 文件),

這個 頭文件 咱們 能夠命名爲   ILBC_Runtime.h ,  裏面 會 包含  ILBC_Assembly 、ILBC_ClassLoader 、ILBC_Type 、ILBC_Field 、ILBC_Method 、ILBC_Argument  等 結構體 定義 。

 

void *       ILBC_Runtime_heapNew (     int size     )

該函數用於   從 堆 裏 分配 一塊 指定大小 的 內存塊, 參數 size 是 內存塊 大小(字節數) 。  返回值 是 內存塊 指針 。

ILBC 運行時 本身實現了一個 堆 和 GC 。

 

固然 對應的 還會有一個      void   ILBC_Runtime_heapFree (   void *   ptr,     int size   )       函數,

C 語言 裏的      void  free(void *ptr);    是沒有 size 參數的,  So  。

沒事, 這個能夠保留討論 。

 

ILBC 程序集 應提供 如下 函數:

 

ILBC_Assembly  *        ILBC_Load()  

該函數 在 ILBC 運行時 調度程序 加載 程序集 時 調用,  負責 程序集 的 初始化 工做,

包括  建立一個   ILBC_Assembly 結構體, 並 初始化 ILBC_Assembly 結構體 的 classLoaderList  字段, 能夠參考 上文 代碼 。

 

ILBC 運行時 調度程序 接收到 程序集 的  ILBC_Load() 函數 返回的  ILBC_Assembly 結構體 指針 後, 會 將 該指針 保存到   ILBC_AssemblyList   中,

ILBC_Assembly   是  調度程序  裏的一個 全局變量, 是一個 鏈表 。

 

說到 鏈表, 調度程序 裏 保存  Assembly 的 列表  ILBC_AssemblyList  是 鏈表,

Assembly 裏 保存 Type 的 列表 classLoaderList  是 數組,

Type 裏 保存 Field 、Method 的 列表  fieldList,   methodList  也是 數組,

 

而 上文 中 根據 名字 查找   Field 、Method  的算法是 遍歷 數組,  查找   Assembly 、Type  的部分雖然沒有直接用代碼寫出來, 但應該是 遍歷 鏈表 / 數組 。

從 性能優化 的 角度 來看,  根據 名字 查找  成員(Assembly,  Type,  Field,  Method  等) 應該 優化 爲 查找   Hash 表,

這個 優化 關係 到 加載 程序集 和 類 的 效率, 也是 反射 的 效率 。

 

動態連接 程序集, 加載 程序集 和 類, 就是一個 反射 的 過程  。

 

相傳    .Net  2.0   對  反射 性能 進行了優化, 使得 反射 性能 獲得了 明顯的 提高,  大概 也是 加入了 Hash 表 吧 !    哈哈哈 。

而    .Net   對 反射 進行了 優化, 理論上 自己 就是 提高了  動態連接 程序集 、加載 程序集 和 類 的 效率,  也就是 提高了  .Net 運行 應用程序 的 效率 。

 

在   .Net / C# 裏, Hash 表 可使用  Dictionary, 但在 IL  裏, 估計 得 本身寫一個  。

不過 這也是一件 好玩的事情,

我接下來 會 寫一篇 文章 《本身寫一個  Hash 表》 。

《本身寫一個  Hash 表》  這篇文章已經寫好了, 見    http://www.javashuo.com/article/p-ervzhqtj-dk.html    。

 

調度程序 的  ILBC_Runtime_GetType() 、 ILBC_Runtime_heapNew() 、 ILBC_Runtime_heapFree()   和   程序集 的  ILBC_Link()   這  4 個 函數 是 操做系統 動態連接庫 規範 定義 的 動態連接庫 導出函數  。

這麼考慮 主要是 以前 並未打算 本身實現一個  C 編譯器,

但 如今 既然 咱們要本身 實現一個  C 編譯器(InnerC),  那麼 這些就 不成問題了,

這  4 個 函數 能夠 用 咱們本身 定義的 規則 來 訪問  。

 

好比, 咱們能夠 定義 在 調度程序 的 開頭 的 一段字節 來 保存   ILBC_Runtime_GetType() 、 ILBC_Runtime_heapNew() 、 ILBC_Runtime_heapFree()   這 3 個 函數 的 地址,  在 程序集 的 開頭 的 一段字節 來 保存   ILBC_Link()  函數 的 地址 。

這樣, 調度程序 和 程序集 之間 就能夠經過 函數指針 來 調用 接口函數,  速度很快 。

 

但 若是要這樣的話, 調度程序 和 程序集 應該是 同構 的, 同構 是指 同一種語言 、同一個編譯器 編譯 產生的 本地代碼 。

因此, 調度程序 也應該是 用 InnerC  編寫 和 編譯 生成的 。

 

這麼一來, InnerC  的 地位 就 很重要了  。  ^^

InnerC  是   ILBC  的  基礎  。

 

不過 這樣一來, InnerC  可能也須要 支持 結構體, 否則 很差寫 。 呵呵 。

 

這樣的話, ILBC 本地代碼 程序集 就 不須要 是 操做系統 定義的 動態連接庫,  而是 按照  ILBC 規範 編譯成的 本地代碼, 咱們能夠把 這種 按照 ILBC 規範 編譯成的 本地代碼 程序集 的 擴展名 命名爲  「.iln」,  表示  「ILBC Native Code」  。

 

關於 泛型, 忽然想到, 泛型 純粹 是 編譯期 檢查, 除此之外 什麼 都 不用作, 頂多爲 每一個 泛型類型 生成一個 具體類型, 經過 具體類型 能夠獲取 泛型參數類型 就能夠了 。

但 泛型 確實能 提升性能, 由於 泛型 不須要 運行期類型轉換(Cast),

運行期 類型轉換 就是 一堆    if  else  ,

咱們能夠看看 編譯後 生成的代碼,

 

源代碼:

 

B  b = new B();

A  a = (A) b  ;

 

編譯後的代碼:

 

B  b = new B();

A  a;

 

Type aType = typeof(A) ;

Type bType = typeof(B);

 

if   ( aType == bType )

       a.ptr = b.ptr  ;        //   這句是 僞碼, 表示 b 引用 的 指針值 賦給 a 引用

else if  ( aType 是 bType 的 父類)

       a.ptr = b.ptr  ;

else if  (  其它 轉型 規則  )

       a.ptr = b.ptr  ;        //   或者 其它 轉型方式, 好比 拆箱裝箱

else

       throw new CastException( "沒法將 " + bType + " 的 對象 轉換爲 " + aType + " 。" )  ;

 

而 泛型 是這樣:

 

List<string> strList = new List<string>();

strList [ 0 ] = "aa" ;

 

string s = strList [ 0 ];

 

編譯後的代碼:

 

List<string> strList = new List<string>();

strList [ 0 ] = "aa" ;

 

string s;

s.ptr = strList [ 0 ].ptr;      //  指針 直接 賦值

 

由於 編譯期 已經作過 類型檢查, 因此 引用 的 指針直接賦值, 因此 泛型 沒有 性能損耗 。

固然, JIT 編譯器 須要爲 泛型類型 生成 具體類型, 使得 泛型類型 能夠按照 CLR 的 規則 「是一個 正常的 類型」, 經過 具體類型 能夠獲取 泛型參數類型 。

泛型類型?  具體類型?   泛型參數類型?

有點繞 。

 

假設有     class A<T>   ,

那麼,  A<T>  叫 泛型類型,

A<string>   叫  具體類型,

T ,  叫  泛型參數類型,  好比  A<string>  的 泛型參數類型 是  string   。

 

對於   ILBC,  具體類型 能夠在  C 中間代碼 裏 生成   。

 

再來看看 基礎類型,

基礎類型 包括 值類型 、數組 、String,

ILBC  會 內置實現 基礎類型,

值類型 包括  int,  long,  float,  double,  char   等,   這些 類型 在  C 語言 裏 都有 對應的類型, 可是爲了實現  「一切皆對象」, 即 全部類型, 包括 值類型 和 引用類型 都從 object 繼承 這個 架構,  還須要 對  C 語言 裏的 int, long, float, double, char  等 作一個包裝, 用一個 結構體(Struct) 來把   int, long, float, double, char  等 包起來 。

包起來之後, 爲了提升執行效率, 編譯器 還須要 對 代碼 進行一些 優化, 對於 棧 裏 分配 的 int, long, float, double, char  等 的 加減乘除 等 運算 就 直接用  C 語言 的 int, long, float, double, char  等 的 加減乘除 等 運算, 即 不用 結構體 包起來, 而是 直接編譯爲  C 語言 裏  的 int, long, float, double, char  等  。

而 對於  

 

void  Foo( object  o )

{

           Type t = o.GetType() ;

}

 

這樣的代碼, 由於 參數 o 多是 任意類型,  因此 傳給 參數 o 的 int 類型 就 應該是 包裝過的 int,  也就是 一個 結構體,  好比:

 

struct    Int32

{

            int    val   ;         //    值

            string     typeName    ;        //  類型名字, 或者 廣義的來講, 這個 字段 表示 類型信息

}

 

Object 的 GetType()   方法 經過 這個 字段 返回   Type   對象  。

 

而 對於     typeof(int)    則 能夠在 編譯器 編譯爲 Hard Code 返回   Int32  的  Type  對象  。

 

又好比  對於   Convert.ChangeType( object o,  Type t )    方法,

假設 參數 o 要傳一個 int 類型的話,  也須要 傳 包裝過的 int 類型, 也就是 上文 定義的    struct  Int32   。

 

因此, InnerC  的  InnerC to Byte Code  模塊, 除了 語法分析器, 又增長了一個模塊,  優化器  。

So  ……

 

語法分析器 產生表達式對象樹 後, 把 表達式樹 傳給 優化器, 優化器 能夠 閱讀 表達式樹, 發現能夠優化 的 地方 能夠修改 表達式樹,

修改後的 表達式樹 就是 優化後的 表達式樹,  再 傳給   Byte Code to Native Code,  編譯爲 本地代碼  。

 

能夠把   優化後 的 表達式樹 再 逆向爲  C 代碼, 這樣就能夠 看到 優化後 的  C 中間代碼 。

InnerC  的   InnerC to Byte Code   能夠提供 逆向 的 功能  。

 

再來看 結構體(Struct),

D# / ILBC  不打算 提供 結構體, 由於 結構體 沒什麼用  。  ^^

提供 結構體 會讓   ILBC  的 設計 變得 複雜, 增長了 研發成本 。

固然 結構體 使用 棧空間,  減小了 堆 管理 和 GC 的 工做,  可是 從 線程 的角度來看, 棧 比較大的話 線程切換 的 性能消耗 可能 也 比較大 。  看你怎麼看了  ~  。

出於 動態連接 的 要求,  .Net / C#  的 結構體 應該不是 在 編譯期 靜態分配內存空間 的, 而是 在 運行期 分配空間, 由於 結構體 保存 在 棧 裏, 因此 是 動態分配 棧 空間 。

因此,  .Net / C#  裏 建立 結構體 也是用  new 關鍵字 。

 

D# / ILBC  的 DateTime 類型 是一個 引用類型(Class), 是一個 能夠用 D# 寫的 普通的 引用類型(Class) 。

.Net / C#  的 DateTime 是 值類型,  我估計   .Net / C#   如今 想把  DateTime  改爲 Class, 可是 改不過來了 。   哈哈哈哈。

 

如 上文所述, D# / ILBC  提供 的 基礎類型 是 基礎類型 值類型 、數組 、String, 值類型 包括  int,  long,  float,  double,  char   等,

基礎類型 由  D# / ILBC   內置實現  。

其它類型 由  D#  編寫, 包括  DateTime  及 基礎庫 裏的 各類類型 。

 

說到 基礎庫, 就會想到 和 本地代碼 的 交互性,  就是 訪問 本地代碼,

在  .Net / C#  裏, 託管代碼 和 本地代碼 之間 的 交互 使用  P / Invoke ,

對於  D# / ILBC,  會提供這樣一些接口:

1   指針

2   申請一段 非託管內存, 非託管內存 不會由 GC 回收, 須要 手動回收

3   回收一段 非託管內存

 

有了 這 3 個 接口, 基本上就夠了, 能夠 訪問 非託管代碼 了 。

非託管內存 和 託管內存 同屬一個堆,  只是 GC 不會回收 非託管內存 。

 

 再來看 類型檢查 和 類型安全,

上文中 初始化 連接表 的 字段偏移量 時 會對 字段類型 進行 檢查, A 程序集 在 運行期 連接 的 B 程序集 的 Person 類 的 字段類型 應該 和 A 程序集 在 編譯期 引用 的 B 程序集 的 Person 類 的 類型一致, 不然 認爲 類型不匹配, 不容許連接, 也就是 不容許 使用 如今 的 Person 類 。

 

爲何要進行 類型檢查 ?

若是 類型不匹配, 會發生 訪問了不應訪問的內存 的 錯誤, 這種 錯誤 難以排查, 產生的 結果 是 意想不到 的,

這也是 java, .Net 這類 虛擬機(運行時) 出現 要 解決的 問題 吧 !

java, .Net 這類 虛擬機(運行時) 經過 運行期 類型檢查 來 實現 類型安全, 避免 類型錯誤 致使 訪問了錯誤的內存 。

 

.Net / C#  對 類型 的 檢查 是 嚴格準確 的, 全部類型 最終會 歸結到  基礎類型(值類型   數組   String),

而 基礎類型 都是  .Net  內置類型, 是 強名稱 的, 能夠 嚴格 的 檢查,

推而廣之, .Net 基礎庫 都是 強名稱 的, 能夠 準確 的 檢查 類型,

對於 開發人員 本身編寫 的 類, 也能夠 根據 字段 逐一校驗, 實際加載 的 程序集 的 類 的 字段 應包含 大於等於 編譯時 引用的 程序集 的 類 的 字段, 字段 名字 和 類型 必須 匹配, 好比 編譯時 引用 的 Person 類 的 name 字段 是 String 類, 那麼 運行期 加載的 B 的 Person 類 也應該要有 name 字段, 且 類型 應該是 String, 不然 認爲 類型 不匹配 。

 

咱們 上文 對 字段 類型 的 檢查 是 不嚴格 的, 只是 檢查 類型 的 名字 。

 

應該注意的是, 強名稱 類型檢查 不表明 內存安全, 強名稱 只是 驗證 程序集(類) 的 身份, 可是 類 若是 自己 存在 Bug, 也會發生 訪問了 自身對象 之外 的 內存 的 問題 。

可是, 因爲 數組 做爲 基礎類型 提供, 數組 中 會判斷 「索引 是否 超出 數組界限」, 因此, 開發者 寫的 代碼 通常 應該不會發生 訪問內存越界(訪問了 自身對象 之外 的 內存) 的 問題 。

固然 這僅限於 託管代碼, 對於 非託管代碼, 由於 指針 的 存在, 因此有可能發生  訪問內存越界  的 問題  。

.Net / C#  解決 這個問題的作法是, 把 指針 用  IntPtr 類型  封裝起來, 不容許修改, 只是做爲一個 常量數值 傳遞  。

 

另外一方面, 若是 Class Size(類佔用的空間大小(Size)) 、 字段偏移量 、 方法的函數地址   這 3 項 元數據 都是 動態連接 的話,

類型檢查 其實 也沒什麼 好查的 。 ^^

由於 這 3 項 元數據 都是 來源於 同一個 類, 是 自洽 的, 若是發生了 訪問內存越界 的問題, 是 類 自身代碼 的 邏輯問題  。

 

強名稱 檢查 是 驗證 程序集(類) 的 身份  。

 

爲何要 動態連接  Class Size(類佔用的空間大小(Size)) 、 字段偏移量 ?

這是爲了 兼容性, 好比, B 程序集 的 Person 類 如今有 name, age  2 個 字段,  後來又加了一個  favour 字段, 這樣就改變了 Class Size,

name,   age 的 偏移量 也可能會發生改變,

可是 應該 讓 原來 引用了 B 程序集 的 應用程序 能 繼續 正常 使用 Person 類,

因此 須要 動態連接 Class Size 和 字段偏移量 。

 

考慮到 軟件 被 攻擊 和 破解 的 風險, 能夠考慮 加入 像  .Net / C#  同樣的  強名稱程序集 的 功能  。

不過若是 是 AOT 編譯 的話, 即便沒有 強名稱, 要 破解 也沒有那麼容易, 由於 AOT 編譯 生成的是 本地代碼  。 ^^

 

咱們上面說 程序集 和 類型 的 名字, 好比 調用  ILBC_Runtime_GetType( "B",   "Person" )   函數 返回  Person  的  ILBC_Type 結構體 指針,

"B" 是 程序集 名字, "Person" 是 類 名,

這段代碼 是 舉例, 咱們給  程序集 名字 和 類型 的 名字  下一個 定義:

 

程序集 名字 是 程序集 文件 的 文件名(不包含 擴展名),

類型 的 全名(Full Name) 是 「名字空間.類名」,  這個 和 C# 同樣  。

 

假設 名字空間 是 「B」, 則 Person 類 的 全名 是 「B.Person」,

上文 調用     ILBC_Runtime_GetType( "B",   "Person" )  函數 的 類名 應該是 類 的 全名  「B.Person」  。

 

若是 D# / ILBC  支持 強名稱 程序集, 則 對於 強名稱 程序集, Full Name 中 還會包含 強名稱 版本信息, 能夠認爲 和 .Net / C# 同樣  。

 

咱們再詳細說明一下 高級語言(D#)編譯 的 過程,

高級語言(D#) 編譯 會生成  2 個文件,

 

1   元數據 文件,

2   程序集 文件

 

上文中 沒有 交代  元數據 文件,

元數據 文件 保存了 程序集 的 元數據 信息, 包括 類, 類的字段(字段名 、字段類型), 方法(方法簽名),

高級語言(D#) 編譯器 能夠 根據 元數據 知道 程序集 有 哪些成員(類, 類的字段, 類的方法),

這樣能夠用於 開發時 的 智能提示, 以及 編譯時 的 類型檢查  。

最重要 的 是 高級語言(D#) 編譯器 須要 根據 元數據 生成 程序集 中 加載 Class 的 代碼,

加載 Class 的 代碼     就是 上文中的      ILBC_Type *       ILBC_LoadClass_B_Person()    函數 ,

這個 函數 就是 「Class Loader」,  是 保存在  ILBC_Assembly  結構體 的  classLoaderList  字段中,

classLoaderList  是 一個 數組,  元素 是 ILBC_ClassLoader  結構體,  ILBC_ClassLoader 結構體 的 load 字段 就是 保存  「Class Loader」  函數 的 函數指針 的 字段  。

 

程序集 文件 多是 Byte Code 程序集, 也多是 本地代碼 程序集,

若是是 JIT 編譯方式, 就是  Byte Code 程序集,

若是是 AOT 編譯方式, 就是  本地代碼 程序集,

 

高級語言(D#) 編譯器 編譯時 只須要 元數據 文件, 不須要 程序集 文件,

應用程序 運行的時候 只須要 程序集 文件, 不須要 元數據 文件 。

 

元數據 文件  就像是  C 語言 的 頭文件 。

 

因此,  ILBC  涉及的 文件 會有 這麼幾種:

1   元數據 文件

2   C  中間代碼 文件,  這個 不是 必需 的, 可是 做爲 調試 研究 學習, 能夠生成出來  。

3   Byte Code 程序集 文件,

4   本地代碼 程序集 文件,

 

咱們 能夠 對 這 4 種 文件 命名 擴展名:

1   元數據 文件,  擴展名  「.ild」, 表示   「ILBC  Meta  Data」,

2   C  中間代碼 文件,  擴展名  「.ilc」, 表示   「ILBC  C  Code」,

3   Byte Code 程序集 文件,  擴展名  「.ilb」, 表示   「ILBC  Byte  Code」,

4   本地代碼 程序集 文件,  擴展名  「.iln」, 表示   「ILBC  Native  Code」,

 

好的,  ILBC 規範 暫時 就寫這麼多 ,

接下來的 計劃 是    堆 、 GC 、 InnerC 語法分析器  。

 

有 網友 提出 不須要 沿襲 傳統的 面向對象 方式, 而是能夠用和 Rust 類似的方式,

我下面 寫一段代碼 把這種方式 描述一下:

 

class  C1

{

          int  f1;

          string f2;

}

 

void M1( C1 this )

{

         ……

}

 

void M2( C1 this)

{

         ……

}

 

這就是 C1 類 的 定義,  方法 定義在 外面, 相似  C# 的 擴展方法,

這至關於 傳統的 面向對象 裏  C1 類 有 2 個 方法(M1(),   M2()),

 

咱們在 定義 一個 C2 類, 讓 C2 「繼承」 C1 類:

 

class C2  :  C1

{

}

 

再把 M1() 的 定義 改一下:

 

void M1( C2 C1 this )

{

         ……

}

 

this 參數 的 類型 加入了 C2,  由  C2  C1  共同做爲  this 參數 的 類型,

這樣  C2  就 繼承 了  C1  的   M1()  方法,,,   注意 只 繼承了 M1()  方法, 沒有 繼承  M2()  方法 。

 

C2  能夠 添加 本身 的 字段,  也能夠 多繼承,  固然 若是 「父類」 之間有 重名 的 字段, 就 不能 同時繼承 有 重名 字段 的 父類 。

C2  也能夠 添加 本身 的 方法,  事實上 這也不能 說是 本身 的 方法, 這個 方法 不只僅 能在 「父子」 類 之間 共享,

也能在 「毫無關係」 的 類 之間 共享, 只要 方法 內 對 this 引用 的 字段 在 類 裏 存在就行 。

 

這種 作法 確實  挺 呵呵 的,   但也 很爽 。

這種作法 我稱之爲  「靜態綁定」,  由於 和 Javascript 的  「動態綁定」  類似,  只不過 這是 在 編譯期 進行的,  因此叫 「靜態綁定」  。

同時, 從 編譯期  「靜態」  的 角度,  又和  泛型 很像  。

 

網友 說 這種作法  「只須要 結構體 和 擴展方法 就行, 不須要 類 。」  ,

確實,  就是這樣, 只要有 結構體 和 擴展方法 就能夠  。

說的 直 一點,  只要有 結構體 和 函數 就能夠 。

 

我要 呵呵 了,  這算是     面向過程 -> 面向對象 -> 面向過程    麼 ?

 

通過後來的 討論 和 思考, D#  仍是不打算這樣作, D#  的 目標 是 實現一個 經典 的 簡潔 的 面向對象 語言 。

D#  會 支持 簡潔 的 面向對象 和 函數式  。

簡潔 的 面向對象 包括    單繼承 、接口 、抽象類 / 抽象方法 / 虛方法,

函數式 是 閉包 。

 

不過, 關於 上述 的 「靜態綁定」 的 作法, 卻是 討論清楚 了, 「綁定」 有 3 種:

1   靜態綁定, 在 編譯期 爲 每一個 綁定 生成一份 方法(函數) 代碼,  每一份 函數 代碼 邏輯相同, 區別是 訪問 對象 字段 的 偏移量 。

2   靜態綁定, 方法(函數) 只有一份, 但在 編譯期 爲 每一個 綁定 生成一段 綁定代碼, 綁定代碼 的 邏輯 是 把 對象 字段 的 偏移量 轉換爲 函數 裏 對應的 偏移量 。

3   動態綁定, 在 運行期 爲 綁定 生成 綁定代碼 。

 

關於  堆  和  GC,  個人 想法 是這樣:

GC 根據  2 張 表 來 回收 對象(內存),

1   引用表

2   對象表

 

這 2 張表 其實是 鏈表,

每次 new 對象 的 時候, 會把 對象 添加 到 對象表 裏,

每次 給 引用 賦值 的 時候, 會把 引用 添加 到 引用表 裏,

 

每次 引用 超出 做用域, 或者 引用 被賦值 爲 null 時, 會 將 引用 從 引用表 裏 刪除,  固然 這段代碼 是 編譯器 生成的 。

這樣, GC  回收 對象(內存) 的 時候, 就 先 掃描 引用表, 對 引用表 裏 的 引用 指向 的 對象, 在 對象表 裏 作一個標記, 表示 這個 對象 還在使用,

掃描完 引用表 後, 掃描 對象表, 若是 對象 未被標記 還在使用, 就表示  已經沒有 引用 在 指向 對象, 能夠 回收對象 。

 

而 要 在 每次 給 引用 賦值 的 時候 把 引用 添加到 引用表, 須要 lock 引用表, 把 對象 添加到 對象表 也須要 lock  對象表 。

 

lock  會 帶來 性能損耗, 經過 測試 能夠看到, C# 中 lock 的 時間 花費 大約 是 new 的 3 倍 (new 應該要 查找 和 修改 堆表, 因此 應該 也有 lock),

執行次數 比較小時, 小於 3,  好比  10 萬次, 

執行次數 比較大時, 大於 3,  好比  1 億次,

 

因此, 看起來, C#  的 new 的 lock 的 效率 比  lock 關鍵字 的 lock 的 效率 高,

或者說, 若是 咱們 用 上述 的 架構, 給 引用 賦值 時 把 引用 添加到 引用表, 使用 lock 關鍵字 來 實現  lock,

這樣 對 性能 的 影響 很大,  只要 想一想 給 引用 賦值 的 性能花費 比 new 還大 就 知道 了,

 

從 測試結果 上來看, new 的 執行 應該是 指令級 的, 大概在 5 個 指令 之內 就能夠完成, 

對於  .Net / C#  這樣有 GC 的 語言, 應該 只須要 從 剩餘空間 中 分配 內存塊 就能夠, 不須要 像  C / C++  那樣 用 樹操做 查找 最接近 要 分配 的 內存塊 大小 的 空閒空間,

再加上  lock 的 時間,  所有加起來 大概 在  5 個 指令 之內,

lock  大概 佔  2 個 指令,  開始 lock 佔 1 個 指令, 結束 lock 佔 1 個 指令,

固然 這些 是 估算  。

 

因此 能夠看出來,  .Net / C#  的  new 操做 對 堆表 的 lock 是 指令級 的, 不是調用 操做系統 的 lock 原語, 

這樣 的 目的 是 讓 new 的 操做 很快, 接近 O(1),

對於  ILBC  而言, 若是 採用 給 引用 賦值 時 修改 引用表, new 對象 時 修改 對象表,

那麼, 修改 引用表 和 對象表 的 操做 也應該 接近 O(1),  就是 像  .Net / C# 的  new  同樣, 這樣纔有足夠的效率 。

這就是說, 修改 引用表 和 對象表 的  lock 也要像  .Net / C# 的 new 對 堆表 的 lock 同樣, 是 指令級 的 。

這就須要 咱們 本身 來 實現一個  lock,  而不是使用 操做系統 的 lock 原語 。

 

怎麼來 實現 本身的 一個  lock  ?

根據 網上 查閱 的 結果, 光從 軟件 層面 是 不行 的,  光從 C 語言 層面 也不行,  須要 硬件 的 支持 和 彙編 編程 。

能夠參考  《聊聊C++中的原子操做》  https://baijiahao.baidu.com/s?id=1609585581486387645&wfr=spider&for=pc  ,

《java併發中的原子變量和原子操做以及CAS介紹》  https://blog.csdn.net/wxw520zdh/article/details/53731146  ,

文中提到  「CAS  ……  雖然看似複雜,但倒是 Java 5 併發機制優於原有鎖機制的根本。」  ,

 

而 CAS 是 經過 CPU 提供的 CMPXCHG  指令 支持,  能夠參考  《cpu cmpxchg 指令理解 (CAS)》  https://blog.csdn.net/xiuye2015/article/details/53406432 ,

 

因此 咱們能夠 用 CMPXCHG 指令 來實現  lock ,   原理 是 這樣:

在 內存 裏用一個 字 來 存儲  lock 標誌(flag), 若是 是 64 位 處理器, 則 字長 是 64, 即  8 個 字節(Byte),

簡化起見, 咱們 就 不 考慮  32 位 處理器 了,  只 考慮  64 位 處理器 。

 

當要 lock 時, 用  CMPXCHG 指令 比較 flag 是否 等於 0, 若是相等 則 將 當前線程 ID 複製到 flag, 這表示 當前線程 得到了 鎖, 接着執行 鎖 裏 要執行 的 操做 就行 。

若是 不等於 0,  則  CMPXCHG  指令 會把  當前  flag  的 值 複製到 指定 的 寄存器 裏, 檢查 寄存器 裏 的 flag 值 是否 是 當前線程 ID, 若是 是, 表示 在 當前線程 的 鎖 範圍內, 接着執行 鎖 裏 要 執行 的 操做 就行 。

若是 flag 值 不等於 當前線程 ID, 表示 當前鎖 由 別的 線程 佔有, 則 當前線程 掛起, 掛起前 會把 指令計數器 再次指向 上述 檢查鎖 的 指令, 下次 恢復運行 時, 會 從新執行 上述 檢查鎖 的 操做 。

 

咱們能夠用 多個 字 來表示 多個 lock,  好比 用 一個字 表示 引用表 lock, 一個字 表示 對象表 lock, 一個字 表示 堆表 lock, 等等 。

固然, 爲了提升效率, 對象表 lock 和 堆表 lock 大概 能夠 合爲一個 lock, 由於  修改 對象表 和 堆表 都 發生在  new 操做 的 時候, 能夠把  new 操做 做爲一個 原子操做, 只用 一個 lock,  這樣,  new 操做 包含的  2 個步驟      修改 對象表  和  修改 堆表      都在 一個 lock 裏 進行 。

 

這種作法 相比 操做系統 的 lock 原語, 可能更簡單, 可是 功能 也 相對侷限, 好比 不能支持 嵌套 lock,  以及 必須 預先 爲 每一種 lock 分配一個 字, 而 操做系統 lock 是 能夠 動態 lock 的,  好比 C# 中 只要 調用 Monitor.Enter()  方法 就能夠 開始 lock, 一般 咱們 是用 lock 關鍵字, 這在 編譯期 被 編譯器 處理爲 Monitor.Enter() 和 Monitor.Exit()  方法對, 可是 若是 在 運行期 調用   Monitor.Enter()  方法, 也是 能夠 開始 lock 的 。

 

操做系統 的  lock  可能 是 利用了 虛擬內存,  或者說  存儲管理部件, 只須要 在 存儲管理 的 鎖表 裏 設置 要鎖定 的 地址, 存儲管理 部件 會判斷 是否容許 訪問 該地址 。

設置 鎖表 的 原理 是, 在 鎖表 裏 設置 當前線程 ID 和 要鎖定的地址, 若是 相同 的  線程 ID + 鎖定地址  已經 存在, 則 設置失敗, 設置失敗 則 線程掛起, 等下次 恢復運行 時 再接着設置 。

設置成功 則 表示 當前線程 得到 對 指定地址 的 鎖, 存儲管理部件 將 只容許 當前線程 訪問 指定地址, 不容許 其它線程 訪問 指定地址 。

 

事實上, 咱們 用 CMPXCHG 指令 的 作法 也能夠 實現 和 操做系統 相似 的 效果, 包括 動態的鎖定 任意 的 對象(不須要 預先 分配字), 也 支持 嵌套 lock, 

這須要 在 object 類(全部 引用類型 的 基類) 裏 加入一個  lock  字段,  當咱們 lock 某個 對象 時, 會先看 lock 字段 是否等於 0, 若是 等於 0, 則 寫入 當前線程號, 這樣 就 得到了 對 該 對象 的 鎖, 若是 不等於 0, 則 比較 是否等於 當前 線程 ID, 若是 等於, 表示 對象 被 當前對象 鎖定, 因而接着執行 鎖定 裏 的 操做, 若是 不等, 表示 對象 被 其它線程 鎖定, 則 當前線程 掛起, 等下次 恢復運行 時, 重複上述過程 。

這個過程 和 上面敘述的 利用  CMPXCHG 指令 實現 鎖 的 過程 是同樣的, 但不用 預先 分配 字, 用 object 的 lock 字段 做爲 這個 「字」 就能夠 。

判斷 object 的 lock 字段 是否 等於 0, 若 等於 則 寫入 當前 線程號, 返回 true, 不然 lock 字段不變, 返回 false, 這個操做是 「原子操做」, 這個 原子操做  就是  CMPXCHG 指令  實現的 。

 

但 用 咱們的 作法 有一個條件, 就是 須要在 全部 (可能 併發) 訪問 對象 的 地方 都 加上 lock,

而 操做系統 的 鎖 則 沒必要需, 操做系統 因爲是利用 虛擬內存(存儲管理部件) 實現的, 因此 在 代碼 的 a 處 加了 lock, b 處 不加 lock, 但 a 處 鎖定 對象, 則 b 處 將不能訪問 。

雖然如此, 咱們在 使用 操做系統 lock 的 時候, 一般 也會在 a 處 和 b 處 都 加上 lock, 這是爲了 設計意圖 的 須要, 咱們 須要 a 和 b 嚴格的 同步(互斥)通訊, 就 須要 給 a 處 和 b 處 都 加上 lock 。

 

我把 咱們 的 作法 稱爲  「IL Lock」 ,  用 關鍵字  illock  表示,

把 操做系統 的 lock 稱爲  「System Lock」,  用 關鍵字  syslock  表示,

在  D#  中,   使用   IL Lock   能夠這樣寫:

 

illock  ( obj )

{

          ……

}

 

使用   System Lock   能夠這樣寫:

 

syslock  ( obj )

{

          ……

}

 

理論上, 咱們能夠提倡 使用   IL Lock,   這樣能夠 得到 比   System Lock    更高 的 性能 。  ^^

 

好的,  堆 和 GC  的 部分 基本 理清 了,  接下來 會開始  InnerC 語法分析器  。

到 目前爲止,  InnerC 在 ILBC 的 地位 變得重要,  InnerC 會是 ILBC 的 內核模塊 。

InnerC  支持 基礎類型(int, long, float, double, char),  if else,  for,  while,  函數,  指針,  數組,  結構體,

InnerC  不保證 支持 Ansi C  的 所有標準,

InnerC  還會有一些 新的 特性:

1   對 void *  類型 的 函數指針 不檢查 函數簽名, 能夠調用任意的參數列表 和 返回任意的返回值, 固然調用了 不匹配 的 參數列表 就 會發生 錯誤, 可能致使 程序 崩潰, 這個 特性 是用在  C 中間代碼 裏, 不建議 開發人員 使用 。

對於 聲明瞭 函數簽名 的 函數指針, 仍然 會 檢查 調用的參數列表 及 返回值 是否 符合 函數簽名(指針類型), 開發人員 應使用 這種方式, 保證 安全性 。

2   爲了便於實現一些 動態特性 和 對 本地代碼 訪問 的 靈活性, InnerC 支持 用 函數指針 調用 動態的參數列表, 參數列表 是 一個 數組,  相似  .Net / C# 的 反射, 把 參數 放在 數組 裏 傳給  MethodInfo.Invoke( object[] args )  方法 。

初步構想 能夠 增長一個  invoke  關鍵字, 能夠用於 函數指針 的 函數調用, 好比:

 

void *     funcPtr   ;

void *     args   ;

……

( *  funcPtr )  (  invoke  args  )   ;           //  調用  funcPtr  指向 的 函數,  參數列表 是  args

 

3   新增   casif  關鍵字 以 支持   casif  語句 。

casif  語句 相似  if 語句, 但 判斷條件 是 經過  CMPXCHG 指令 實現的 CAS 原子操做, CAS 全稱 「Compare and Swap」  。

casif  語句 格式 以下:

 

casif  ( 參數1,    參數2,     參數3 )

{

            語句塊 1

}

else

{

            語句塊 2

}

 

參數1 是一個 變量 或者 常量, 參數2 是 一個 指針, 參數3 是 一個 變量 或者 常量,

當 參數1 和 參數2 指向 的 值 相等 時, 把 參數3 的 值 複製到 參數2 指向 的 存儲單元, 並認爲 判斷條件 成立, 執行 語句塊 1 。

不然 認爲 判斷條件 不成立, 執行 語句塊 2 。

 

其實 上面說的 用  CMPXCHG 指令 實現  IL Lock  的 作法 還有一點問題, 其實 不須要 向 對象 的 lock 字段 寫入 當前線程 ID, 只要 寫入 1 就能夠, 1 表示 對象 被 鎖定, 0 表示 對象 未被鎖定 。

這樣 邏輯 就 更 簡化了 。

 

對   引用表  對象表  堆表  的  lock  都會 統一使用  IL Lock  。

 

暫時先寫到這裏,    ILBC  目前計劃 發展  2 門 高級語言,  D#  和  c3 ,   c3 由 一位 網友 提出, 參考《c3 語言草案》  https://note.youdao.com/ynoteshare1/index.html?id=bec52576b45ec0d918a95f75db0ea68e&type=note#/      。

 

內容有點多, 因此後面的內容放到了 《ILBC 規範 2》  http://www.javashuo.com/article/p-uqmiarbb-g.html    。

相關文章
相關標籤/搜索