ILBC 运行时 (ILBC Runtime) 架构
本文是 VMBC / D# 项目 的 系列文章,
有关 VMBC / D# , 见 《我发起并创立了一个 VMBC 的 子项目 D#》(以下简称 《D#》) https://www.cnblogs.com/KSongKing/p/10348190.html 。
ILBC 运行时 架构图 如下:
为了便于讲解, 图中 一些位置 标注了 红色数字 。
ILBC 运行时 包含 3 个 部分: 调度程序 、 InnerC(Byte Code to Native Code) 、 GC 。
1 处, 调度程序 调用 入口程序集 的 ILBC_Main() 函数, 开始执行程序 。
如果 入口程序集 是 ILBC 程序集, 就会 调用 InnerC(Byte Code to Native Code) 编译 ILBC 程序集 为 本地程序集(2 处) 。
ILBC 程序集 就是 ILBC Byte Code 程序集, 本地程序集 就是 本地代码 程序集 。
如果 入口程序集 是 ILBC 程序集, 就直接调用 ILBC_Main() 函数, 开始执行程序 。
3 处 表示 A 程序集 引用了 B 程序集, 在 调度程序 加载 A 程序集 的 时候, 会调用 A 本地程序集 的 ILBC_GetAssembly() 函数,
ILBC_GetAssembly() 函数 之前没有提到, 现在补充上来 。
ILBC_GetAssembly() 函数 会返回 A 程序集 引用 的 程序集 列表, 包含了 这些 程序集 的 名字 。
程序集 列表 是一个 数组, 数组元素 是 一个 字符数组 的 首地址, 这个 字符数组 就是 程序集 的 名字 。
调度程序 会 根据 程序集列表 去 加载 列表 里的 程序集,
假设 A 程序集 引用了 B 程序集, 则 程序集 列表 里有 B, 调度程序 会先把 B 加载到内存, 如果 B 是 本地代码程序集, 则 直接加载到内存, 如果 B 是 ILBC 程序集, 则 先 JIT 编译 为 本地代码程序集, 再加载到内存 。
4 处 表示 ILBC 程序集 JIT 编译 为 本地程序集 后 投入使用 。
把 B 加载到 内存后, 调用 B 的 ILBC_GetMethodList() 函数, 返回 B 的 函数表 首地址, 另一方面, 调度程序 会 调用 A 的 ILBC_GetMethodListList() 函数, 返回 “函数表 列表” 的 首地址, “函数表 列表” 是 一个数组, 数组元素 是 函数表 首地址, 所以是 “函数表 的 列表” 。
这样, 把 B 的 函数表 首地址 存到 函数表 列表 中 B 的 位置, 加载 A 和 “依赖项” B 的 过程 就完成了 。
如果 A 还引用了 其它 程序集, 或者 B 引用了 其它 程序集, 也是 按照 这个 过程 依次加载 。
上面这个 过程 说的有点啰嗦, 没事, 我们先来看一下 InnerC 的架构, 等下再把这个流程 总结一遍 。
InnerC 的 架构如下:
InnerC 分为 2 个 模块 :
1 InnerC to Byte Code
2 Byte Code to Native Code
InnerC to Byte Code 的 职责 是 语法分析 和 类型检查, 语法分析 包含了 语法检查 。
通过 语法分析, 把 C 代码 解析 为 表达式对象树, 然后 对 表达式对象树 进行 类型检查,
类型检查 通过后, 就可以 返回 表达式对象树 了,
表达式对象树 可以直接 传给 Byte Code to Native Code,
Byte Code to Native Code 负责 将 表达式 生成为 目标代码 和 链接(链接外部库), 最终 生成 本地库,
这就是 AOT 编译 。
表达式对象树 也可以 序列化, 序列化 得到的 byte 数组(byte [ ]) 就是 Byte Code, Byte Code 保存为 文件 就是 ILBC 程序集 。
ILBC 程序集 可以 读取为 byte 数组(byte [ ]), byte 数组 反序列化 就是 表达式对象树, 表达式对象树 传给 Byte Code to Native Code 编译为 本地库,
这就是 JIT 编译 。
C 代码 是 第一级 中间代码, Byte Code 是 第二级 中间代码 。
这就是 InnerC 的 架构, 以及 AOT 编译 和 JIT 编译 的 原理 。
我们可以把 C 中间代码 文件 的 扩展名 定义为 .ilc , 意思是 “ILBC C Code”,
把 ILBC 程序集 (Byte Code 文件) 的 扩展名 定义为 .ilb, 意思是 “ILBC Byte Code” 。
本地代码 程序集 的 扩展名 遵循 操作系统 的 规定, 比如 Windows 上 就是 动态链接库 .dll, 因为 本地程序集 就是 操作系统 定义的 动态链接库 。
我们 接下来 把 ILBC 运行时 加载 程序集 和 运行 应用程序 的 流程 总结一下 :
1 调度程序 加载 入口程序集, 如果 入口程序集 是 本地程序集, 就 直接加载到内存,
如果 入口程序集 是 ILBC 程序集, 则 先 JIT 编译, 把 入口程序集 编译为 本地程序集 再加载到内存 。
2 调度程序 调用 入口程序集 的 ILBC_GetAssemblyList() 函数 , ILBC_GetAssemblyList() 函数 返回 AssemblyList 首地址 。
AssemblyList 是一个 数组, 数组元素 是一个 char 数组(char [ ]) 的 首地址, 表示 Assembly 的 名字 (文件名, 不包含扩展名) 。
3 调度程序 用 Assembly 名字 查找 当前目录下 的 程序集, 先查找 本地程序集, 比如 “程序集名字.dll”, 如果找到, 直接加载到内存,
如果找不到 本地程序集, 就找 ILBC 程序集, 比如 “程序集名字.ilb”, 如果找到, 先 JIT 编译 为 本地程序集, 再把 本地程序集 加载到内存 。
如果 ILBC 程序集 也没有找到, 就 报错 “找不到 某某 程序集 。” 。
怎么把 本地程序集 加载到内存 ? 这 遵循 操作系统 提供的 方式, 比如 Windows 把 .dll 库 加载到 应用程序 里的 方式 。
总的来说, 加载程序集 的 流程 如上, 从 入口程序集 开始依次加载, 加载完成后, 调用 入口程序集 的 ILBC_Main() 开始 执行程序 。
另外, ILBC_GetMethodListList() 函数 应该是 ILBC_InitializeMethodListList() , 具体 逻辑 不长, 但讲起来烦琐, 之后看 Demo 代码就清楚了 。
可以看到, ILBC 运行时 加载 程序集 会 将 所有 引用到的 程序集 全部加载 完成, 才会开始 执行程序 。
这是 和 .Net / C# 不同的 , .Net / C# 应该是 用到 这个 程序集 的时候 才会 加载, 用到这个 程序集 是指 第一次 调用到 这个 程序集 里的 类 的时候 。
实际上, .Net / C# 的 动态加载 的 粒度 可能 更细, 可能是 Class 这一级别 的,
我们在 调试 .Net / C# 程序 的 时候 可以 观察到, 只有 第一次 用到 某个 Class 的 时候, 这个 Class 的 静态构造函数 才会被 调用 。
从这一点上来看, .Net / C# 的 动态性 比 ILBC 更强, 更加动态 。
进一步, ILBC 加载 的 单位 是 整个 程序集, 而不是 类(Class), 如果是 本地程序集, 则将 整个 本地程序集 加载到内存,
如果 是 ILBC 程序集, 则 对 整个 ILBC 程序集 进行 JIT 编译, 编译为 本地程序集 后, 再把 整个 本地程序集 加载到内存 。
也因此, D# / ILBC 不提供 类 的 静态构造函数, 而是 提供一个 ILBC_AssemblyLoad() 函数, ILBC 运行时 会在 加载 程序集 完成时 调用 ILBC_AssemblyLoad() 函数, 整个程序集 所有 类 的 初始化 工作 可以在 ILBC_AssemblyLoad() 里 来 完成 。
.Net / C# 的 动态性 需要 更加 复杂 的 设计 和 实现, 这不是 ILBC 的 定位 。
我们可以 探讨 一下, 如果要实现 .Net / C# 的 动态性, 比如 第一次 new 类的对象 或者 第一次调用类的静态方法 时, 加载类(如果 Assembly 未加载 则 先加载 Assembly 再加载 Class) 并 调用 类的静态构造函数 这个 动态加载 怎么实现:
我们可以写一段 伪码:
简单起见, 我们假设 Assembly 已经加载了, 只要 判断 类 是否已加载, 若未加载 则 加载 类 。
编译器 会 把 new 类 的 对象, 以及 调用 类的 静态方法 的 代码 处理成 一段 临时代码, 我们称之为 “链接代码”,
假设 该 类 是 A Class,
伪码如下:
bool ifAClassLoad = false;
if ( ! ifAClassLoad )
{
lock ( ifAClassLoad )
{
if ( ! ifAClassLoad )
{
加载 A Class ;
调用 类 的 静态构造函数 ;
ifAClassLoad = true ;
}
}
}
new () 或者 A.静态方法() ;
按照这个 代码 的 逻辑, 第一次 new A() 或者 调用 A.静态方法() 时, 会 判断 A Class 是否已加载, 如果未加载, 会有一个 线程 通知 CLR 加载 A Class, 其它 线程 等待(如果 有 其它线程 也在 new A() 或者 调用 A.静态方法() 的话), CLR 加载完成后, 就执行 真正的 new A() 或者 A.静态方法() ,
之后, 再 new A() 或者 调用 A.静态方法() 的时候, 在 链接代码 的 第一句,
if ( ! ifAClassLoad )
就可以 判断 出来 A Class 已经加载, 于是就直接执行 new A() 或者 A.静态方法() 。
但 这样的 做法, 每次 new A() 或者 A.静态方法() 都要有 一个 判断, 虽然 只是一个 判断, 但从 微观 上来说, 也造成了 性能消耗 。
这样的 性能消耗, 应该是 “应该被优化掉的” 。
如果 .Net / C# 已经 把 这个 判断 优化掉了, 那么 应该用到了 “修改已经编译好的本地代码” 的 操作, 形象的讲, 就是给 “已经编译好的本地代码” 做了个 “微创手术” 。
具体就是 在 第一次 加载 成功后, .Net CLR 会 把 这段 “链接代码” 替换掉, 替换为 new A() 和 A.静态函数() 的 代码,
在 新的 new A() 和 A.静态函数() 代码中, A() 构造函数 和 A.静态方法() 已经替换为 A Class 加载后的 实际的 函数地址 。
这样, 替换后的 代码 和 访问 同一个 程序集 中的 类 的 代码 是 一样的 。
性能 也和 访问 同一个 程序集 中的 类 一样 。
顺便加一句, 本来 链接代码 中 new A() 和 A.静态函数() 的 部分 还有一个 类似 调用 虚函数 的 查函数表 的 操作, 也被这个 替换 优化掉了 。
这个 技术 很底层, ILBC 不打算 涉及 这个 技术,
ILBC 仍然 把 C 语言 和 C 编译器(InnerC) 看作一个整体, 不会 介入 C 编译器 的 工作细节 。
不过, 从上面的讨论也可以知道, 如果 ILBC 想实现 和 .Net / C# 一样的 “动态特性”, 比如 用到 A Class 的时候 才 加载 A Class, 如果 A Class 所在的 程序集 未加载 则 先加载程序集 再 加载 A Class,
如果要做到 这样 的 动态特性 的话, 简单点 也可以用 上面的 “链接代码” 的 做法, 只是每次调用 new A() 构造函数 和 A.静态方法() 都要多一个
if ( ! ifAClassLoad )
的 判断 了 。
还有 就是 查函数表 的 操作 也是要有的 。
当然, 即使不实现这个 “动态特性”, 查函数表 的 操作 也是有的 。
ILBC 的 动态链接 就 相当于 调用 虚函数 。
不过 即使用了 上面 “链接代码” 的 方式, 也只能 “用到某个 程序集 的 时候 才加载 程序集”, 还不能达到 Class 的 粒度,
因为 上文 也说了, ILBC 是 把 整个 ILBC 程序集 编译成 本地程序集 的,
这是因为 ILBC 程序集 是 C 语言 写的, C 语言 只能 整个项目(程序集) 一起编译, 不能把 里面的 .c 文件 一个一个 拿出来编译 。
就算能把 若干 .c 文件 任意 的 拿出来 编译, 根据 ILBC 规范, 这些 单独 拿出来的 .c 文件 编译成的 程序集 里 必须要 提供 ILBC_GetAssemblyList(), ILBC_InitializeMethodList(), ILBC_Link() 函数, 这就乱套了 。 因为 原本的程序集 已经 为 原本的整个项目 生成了 一份 这些 函数 。
假设 A 引用 B, A 里 编译好的 逻辑 是 引用 B, 现在 把 B 拆成了 若干个 小程序集, 你让 A 怎么引用 ?
ILBC 运行时 (ILBC Runtime) 架构的更多相关文章
- java 常用类库:操作系统System类,运行时环境Runtime
System类: System 类代表Java程序的运行平台,程序不能创建System类的对象, System类提供了一些类变量和类方法,允许直接通过 System 类来调用这些类变量和类方法. Sy ...
- iOS运行时编程(Runtime Programming)和Java的反射机制对比
运行时进行编程,类似Java的反射.运行时编程和Java反射的对比如下: 1.相同点 都可以实现的功能:获取类信息.属性设置获取.类的动态加载(NSClassFromString(@“clas ...
- iOS-浅谈runtime运行时机制-runtime简单使用(转)
转自http://www.cnblogs.com/guoxiao/p/3583432.html 由于OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtim ...
- LoadRunner 学习笔记(2)VuGen运行时设置Run-Time Setting
定义:在Vugen中Run-Time Setting是用来设置脚本运行时所需要的相关选项
- .net core 运行时事件(Runtime Events)
.Net Core 2.2.0 .Net Core 2.2.0已经发布有一段时间了,很多新鲜功能已经有博主介绍了,今天给大家介绍一下运行时事件并附上demo. 运行时事件 通常需要监视运行时服务(如当 ...
- ArcMap运行时出现Runtime Error错误的解决方案
运行ArcMap时弹出错误提示:“Microsoft Visual C++ Runtime Library. Runtime 1.开始->运行->regsvr32 "C:\Pro ...
- Spark运行时的内核架构以及架构思考
一: Spark内核架构 1,Drive是运行程序的时候有main方法,并且会创建SparkContext对象,是程序运行调度的中心,向Master注册程序,然后Master分配资源. 应用程序: A ...
- [转]Loadrunner11之VuGen运行时设置Run-Time Setting
转自:http://www.51testing.com/html/92/450992-248065.html General 1.Run Logic运行逻辑 脚本如何运行的,每个action和acti ...
- iOS开发——高级特性&Runtime运行时特性详解
Runtime运行时特性详解 本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机.主要内容如下: 引言 ...
随机推荐
- 『TensorFlow』0.x_&_1.x版本框架改动汇总
基本数值运算 除法和模运算符(/,//,%)现在匹配 Python(flooring)语义.这也适用于 [tf.div] 和 [tf.mod].要获取基于强制整数截断的行为,可以使用 [tf.trun ...
- 『TensorFlow』one_hot化标签
tf.one_hot(indices, depth):将目标序列转换成one_hot编码 tf.one_hot(indices, depth, on_value=None, off_value=Non ...
- regex back-reference
\<number>表示反向引用,引用group(number)表示的内容 最外层括号的group-id就是1,向内依次增加 ")) # output is [('111', '1 ...
- wincc项目移植和复制解决办法
wincc项目复制 wincc项目不支持直接复制,部分的后台数据库在活跃状态,直接复制wincc项目,会提示跳过活跃状态的数据库,当跳过活跃数据库时,复制的项目也是无效的.在wincc项目管理器中打不 ...
- vue2.0 导出Excel表格数据
1.安装三个依赖包 npm install -S file-saver npm install -S xlsx npm install -D script-loader 2.在项目中创建一个文件夹(比 ...
- Spring Boot + Spring Cloud 实现权限管理系统 配置中心(Config、Bus)
技术背景 如今微服务架构盛行,在分布式系统中,项目日益庞大,子项目日益增多,每个项目都散落着各种配置文件,且随着服务的增加而不断增多.此时,往往某一个基础服务信息变更,都会导致一系列服务的更新和重启, ...
- 用SQL语句创建和删除Access数据库中的表;添加列和删除列
用SQL语句创建和删除Access数据库中的表;添加列和删除列 Posted on 2009-08-11 13:42 yunbo 阅读(1240) 评论(0) 编辑 收藏 用SQL语句创建和删除Acc ...
- HTML5⑥
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...
- <文档学习>AirSim/using_car.md Choosing Your Vehicle: Car or Multirotor
如何在AirSim中使用汽车 默认情况下,AirSim中使用的车型为多转子multirotor. 如果你想使用汽车,那么只需在你的settings.json(https://github.com/Mi ...
- Spring搭建练习遇到的坑
1.Error:(7, 23) java: cannot find symbolsymbol: class ProceedingJoinPointlocation: class com.how2jav ...