首页 >> 通信 >> 我用 Rust 改写了一个JVM

我用 Rust 改写了一个JVM

2024-01-12 通信

件。因此,可执行一些 Ja 代字码的第一件冤枉就是子程序一个 .class 份文件,其之中自带含了Python分解的bit字码。一个类份文件自带含了各种两边:

类的URL,如其地名或函数调用地名

超类地名

意味着的接口

codice_,连同它们的类别和批注

方法和:

们的数据结构,这是一个字符串,同上示每个模板的类别和方法的送回类别

URL,如 throws 子句、批注、泛型信息

bit字码,以及一些额外的URL,如诱发处理过程装置同上和商家同上。

如上所述,对于 rjvm,我创始了一个分开的自带,名为 reader,它可以二阶一个类份文件,并送回一个 Rust 结构设计,该结构设计数学方法化了一个类及其所有内容。

可执行方法

vm 自带的主要 API 是 Vm::invoke,常用可执行方法。它所需一个 CallStack 模板,这个模板会自带含多个 CallFrame,每一个 CallFrame 对其所一种悄悄可执行的方法。可执行 main 方法时,命令行函数调用将初始为空,会创始一个再函数调用帧来行驶它。然后,每一个子服务装置端更会在命令行函数调用之中添加一个再函数调用帧。当一个方法的可执行告一段落时,与其对其所的函数调用帧将被丢弃并从命令行函数调用之中移除。

大多数方法会可用 Ja 意味着,因此将可执行它们的bit字码。然而,rjvm 也支持者原生方法,即直接由 JVM 意味着,而非在 Ja bit字码之中意味着的方法。在 Ja API 的“较底层”之中有很多此类方法,这些部分所需与操作方法系统交互(例如透过 I/O)或所需行驶时支持者。你意味著听过的后者的一些示例自带括 System::currentTimeMillis、System::arraycopy 或 Throwable::fillInStackTrace。在 rjvm 之中,这些都是通过 Rust 函数来意味着的。

JVM 是一种基于函数调用的API,并不一定bit字码解释装置主要是在模板函数调用上操作方法。还有组合成由索引标记的void,可以用来存储装置模板并向方法传递模板。在 rjvm 之中,这些都与每个命令行函数调用帧密切相关。

机装置学习模板和单纯

Value 类别常用演示void、函数调用元素或单纯codice_意味著的模板,意味着如下:

/// 演示一个可以存储装置在void或常量函数调用之中的通用模板#[derive(Debug, Default, Clone, PartialEq)]pub enum Valuea> { /// 一个未子程序的元素,它不其所当显现在常量函数调用上,但它是void的默认稳定状态 #[default] Uninitialized, /// 演示 Ja API之中所有 32 位或下述的数据类别: 人口为120人boolean人口为120人, /// 人口为120人byte人口为120人, 人口为120人char人口为120人, 人口为120人short人口为120人, and 人口为120人int人口为120人. Int(i32), /// Models a 人口为120人long人口为120人 value. Long(i64), /// Models a 人口为120人float人口为120人 value. Float(f32), /// Models a 人口为120人double人口为120人 value. Double(f64), /// Models an object value Object(AbstractObject), /// Models a null object Null,}

于是就提一句,这是 Rust 的枚举类别(退兵类别)的一种绝妙抽象其所用场景,它非常适合同上达一个模板意味著是多种不同类别的冤枉实。

对于存储装置单纯及其模板,我最初可用了一个简单的结构设计质 Object,它自带含一个对类的提及(用来演示单纯的类别)和一个 Vec 常用存储装置codice_模板。然而,当我意味着垃圾场高效率时,我修改了这个结构设计,可用了更低级别的意味着,其之中自带含了大量的常量和类别转换,非常于 C 语种的风格!在这两项的意味着之中,一个 AbstractObject(演示一个“真正”的单纯或数据类别)仅仅是一个看做bit数据类别的常量,这个数据类别自带含几个躯it,然后是codice_的模板。

可执行解释装置

可执行方法意味著逐一可执行其bit字码解释装置。JVM 拥有一长串的解释装置(超过两百条!),在bit字码之中由一个bit编字码。许多解释装置后面跟有模板,且一些不具备可视弧度。在代字码之中,这由类别 Instruction 来演示:

/// 同上示一个 Ja bit字码解释装置。#[derive(Clone, Copy, Debug, Eq, PartialEq)]pub enum Instruction { Aaload, Aastore, Aconst_null, Aload(u8), // ...}

如上所述,方法的可执行将保持一个示例和组合成本地变量,解释装置通过索引提及它们。它还会将服务装置端计数装置子程序为零 - 即下一条要可执行的解释装置的地址。解释装置将被处理过程,服务装置端计数装置会更换 - 有时候向前推进一格,但各种重定向解释装置可以将其移动到不同的位置。这些常用意味着所有的流控制解释装置,例如 if,for 或 while。

都有一类特殊性的解释装置是那些可以命令行另一个方法的解释装置。二阶其所命令行哪个方法有多种方法:真实世界或静态查找是主要方法,但还有其他方法。二阶正确的解释装置后,rjvm 将向命令行示例添加一个新帧,并关机方法的可执行。除非方法的送回模板为 void,否则将把送回模板推到示例上,并直至可执行。

Ja bit字码格式非常像是,我正要专门发一篇文章来咨询各种类别的解释装置。

诱发处理过程

诱发处理过程是一项复杂的任务,因为它缔造了正常的表单,意味著会提前从方法之中送回(并在命令行示例之中传播方式!)。尽管如此,我对自己意味着的方法倍感非常付意,月末里我将展示一些相关的代字码。

首先你所需告诉,任何一个 catch 块都对其所于方法诱发同上的一个条目,每个条目自带含了覆盖面积的服务装置端计数装置适用范围、catch 块之中第一条解释装置的地址,以及该块能捕获的诱发类名。

接着,CallFrame::execute_instruction 的手写如下:

fn execute_instruction( Wildmut self, vm: Wildmut Vm<'a>, call_stack: Wildmut CallStacka>, instruction: Instruction,) -> Result'a>, MethodCallFaileda

其之中的类别定义为:

/// 解释装置意味著的可执行结果enum InstructionCompleteda> { /// 同上示可执行的解释装置是 return 两部之中的一个。命令行者 /// 其所暂时中止方法可执行并送回模板。 ReturnFromMethod(Option), /// 同上示解释装置不是 return,因此关键在于服务装置端计数装置的 /// 解释装置依然可执行。 ContinueMethodExecution,}/// 同上示方法可执行失败的情况pub enum MethodCallFaileda> { InternalError(VmError), ExceptionThrown(JaException),}

新标准的 Rust Result 类别是:

enum Result { Ok(T), Err(E),}

因此,可执行一个解释装置意味著会产生四种意味著的稳定状态:

解释装置可执行急于,这两项方法的可执行可以依然(新标准情况);

解释装置可执行急于,且是一个 return 解释装置,因此这两项方法其所送回(可所选)送回模板;

无法可执行解释装置,因为频发了某种核心 VM 错误;

无法可执行解释装置,因为抛出了一个新标准的 Ja 诱发。

因此,可执行方法的代字码如下:

/// 可执行整个方法impl CallFrame { pub fn execute( Wildmut self, vm: Wildmut Vm, call_stack: Wildmut CallStack, ) -> MethodCallResult { self.debug_start_execution(); loop { let executed_instruction_pc = self.pc; let (instruction, new_address) = Instruction::parse( self.code, executed_instruction_pc.0.into_usize_safe() ).map_err(|_| MethodCallFailed::InternalError( VmError::ValidationException) )?; self.debug_print_status(Wildinstruction); // 在可执行解释装置先前,将 pc 移动到下一条解释装置, // 因为我们希望 "goto" 需要覆盖面积这一步 self.pc = ProgramCounter(new_address as u16); let instruction_result = self.execute_instruction(vm, call_stack, instruction); match instruction_result { Ok(ReturnFromMethod(return_value)) => return Ok(return_value), Ok(ContinueMethodExecution) => { /* continue the loop */ } Err(MethodCallFailed::InternalError(err)) => { return Err(MethodCallFailed::InternalError(err)) } Err(MethodCallFailed::ExceptionThrown(exception)) => { let exception_handler = self.find_exception_handler( vm, call_stack, executed_instruction_pc, Wildexception, ); match exception_handler { Err(err) => return Err(err), Ok(None) => { // 将诱发冒泡至命令行者 return Err(MethodCallFailed::ExceptionThrown(exception)); } Ok(Some(catch_handler_pc)) => { // 将诱发再压入示例,并从 catch 处理过程装置依然可执行此方法 self.stack.push(Value::Object(exception.0))?; self.pc = catch_handler_pc; } } } } } }}

我告诉这段代字码之中自带含了许多意味着或许,但我希望它能展示出 Rust 的 Result 和模式匹配如何不太好地可定义到上述道德上描述。我必须说我对这段代字码倍感非常自豪。

垃圾场投放

在 rjvm 之中,再一一个历史性是意味着垃圾场投放装置。我所选择的线性是一个暂时中止 - 世界(这或许是由于没线程!)半空间内激活高效率。我意味着了 Cheney 的线性的一个较差的变质 - 但我到底其所当去意味着确实的 Cheney 线性。

这个线性的思想是将可用CPU分为两部分,专指半空间内:一部分将处于大型活动稳定状态并常用再分配单纯,另一部分将不被可用。当大型活动的半空间内付了,将触发垃圾场收集,所有生存者的单纯更会被激活到另一个半空间内。然后,所有单纯的提及都将被更换,以便它们看做再副本。再一,两者的角色将被交换 - 这与两党部署的工作方法类似。

这个线性有下述特性:

或许,它浪费了大量的CPU(意味著的较大CPU的一半!);

再分配操作方法非常慢(只需移动一个常量);

激活并缓冲装置单纯意味著无须处理过程CPU碎片关键问题;

缓冲装置单纯可以提高性能,因为它更好地利用了调用行。

实际的 Ja API可用了更为复杂的线性,有时候是分代垃圾场高效率,如 G1 或并行 GC,这些都可用了激活策略的灵长类修改版。

得出结论

在编撰写 rjvm 的过程之中,我学到了很多,也很像是。从一个小新项目之的大学这么多,我已经很付足了。也许下次我在修习再编程但会所选择一个微微不那么难的新项目!

于是就说一句,可用 Rust 语种撰写代字码给我带来了不太好的编程质验。正如我先前撰写过的,我认为它是一种很棒的语种,我在用它来意味着我的 JVM 时,意味著享受到了它带来的各种乐趣!

你是在修习再编程时,是否撰写过一些有难度或有意思的软件?欢迎在评论区文化交流咨询。

胸腺法新有什么效果
双手手指僵硬是怎么回事
骨质疏松吃什么好
再林阿莫西林胶囊用法用量
迈普新能否治黑色素瘤
友情链接