高级编程语言的每个非平凡实现通过库进行扩展。有些库的 routinues 很简单:复制内存内容,执行不被硬件支持的算术计算。有些更加复杂,比如,堆管理 routines,用于缓冲或者图形 I/O 的库需要管理大量的内部状态。
通常来说,我们使用术语 runtime-system,有时候只使用 runtime,来表示语言实现依赖的库集。有些 runtime 的部分,比如堆管理,建立所有 subroutine 参数的信息,可以被轻松替换。但是其他部分要求对 compiler 或者生成程序有很好的了解。简单情况下,这些知识只是一组 compiler 和 runtime 需要共同遵循的惯例(比如,subroutine 调用顺序)。更复杂的是,compiler 生成程序的 metadata,runtime 必须根据这些来完成自己的工作。比如,跟踪垃圾收集(8.5.3),以来 metadata 来识别程序中的 root pointer(所有全局,静态,和栈指针以及变量引用),以及每个引用的类型和每个分配块。
在前几章已经讨论了 compiler/runtime 集成的例子;我们在 16.1 中回顾一下。列表的长度和复杂性通常意味着 compiler 和 runtime 必须共同开发。
有些语言(显然比如 C)有非常小的 runtime 系统:执行源代码所需的大多数用户级别代码要么由 compiler 直接生成,要么包含在语言独立的库中。有些严重依赖 runtime 系统的语言,比如 C# 依赖 Common Language Infrasturcture(CLI) 标准定义的运行时。
就像所有的 runtime 系统,CLI 依赖 compiler 生成的数据(类型描述,异常处理列表,符号表等)。存在大量的关于 compiler 生成代码的假设(例如,参数传递的约定,同步机制和运行时栈)。但是 compiler 和 runtime 耦合程度还要深: CLI 编程接口非常复杂,以至于完全隐藏了硬件。这种 runtime 被称为 virtual machine。有些虚拟机--比如知名的 JVM--是特定语言的。有些,包括 CLI 显然是为了多种语言发明的。结合 CLI 版本的开发,微软引入了术语 managed code 表示运行在虚拟机上的代码。
虚拟机是使用 compile 技术对 runtime 惯例和操作程序发展趋势的一部分,也是本章的主题。我们在 16.1 中更详细的阐述虚拟机。为了避免模拟非本地指令集的开销,很多虚拟机使用 just in time(JIT) compiler 来将它们的指令集转换为硬件指令集。有些甚至在唤起 compiler 在程序运行起来之后,compile 新发现的 component 或者根据程序的动态发现属性作为输入来优化代码。使用相关技术,有些语言的实现执行二进制翻译将一种机器语言翻译为另一种机器语言【译者注:roseta2 mac translate intel -> m1】,或者二进制重写来优化程序的执行。我们在 16.2 讨论这些形式的机器码 late binding。最后在 16.3 我们讨论 runtime 机制检查和修改运行程序的状态。这些机制被 debugger 或者性能探测分析工具需要。它们可能还支持反射,反射允许程序在运行时检查和推断自己的状态。