今天,我们从CodeInstruction. compile(CompilerContextcontext, MethodVisitor mv)这个函数说起。

其中,CompilerContext是编译时刻的现场,比如当前正在编译哪个函数,当前正在编译哪条指令,等等这样的信息。在编译某些指令时,需要知道这些信息。举例说,我们正在处理一条分支指令,那么需要从编译时刻上下文查询当前正在编译的codeBlock,以便取得分支目标位置的指令标号。

MethodVisitor用于书写java字节码。Jpcsp的翻译引擎为每个函数生成一个类,然后生成这个类的一个exec方法,这样运行时刻可以用翻译得到的exec方法来代替原有的mips编码的函数。MethodVisitor正是在书写这个exec方法。

这个函数很短,全部贴出来:

public voidcompile(CompilerContext context, MethodVisitor mv)

{

startCompile(context,mv);//将contex的当前指令置为这个指令

if (isBranching()) {                        //分支指令

compileBranch(context,mv);         //其中调用getBranchingOpcode,这里有路径处理了延迟槽中的指令

}

else if (insn == Instructions.JR) {       //跳转指令

compileJr(context, mv);

}

else if (insn == Instructions.JALR) {     //跳转并链接

compileJalr(context, mv);

}

else {

insn.compile(context,getOpcode());//非跳转指令

}

context.endInstruction();

}

首先,startCompile(context, mv),在编译上下文中记录当前指令,该函数中的核心语句:

context.setCodeInstruction(this);

if (hasLabel()) {

mv.visitLabel(getLabel());//如果该指令有标号,先在字节码中记录标号

}

然后,可以看到,对branch,jr,jalr这三类指令做了特殊处理,其他指令则直接调用insn.compile(context, getOpcode())去生成代码了。这里的其他指令,通常就是功能性的指令,其compile方法实现相应(运算)功能即可。

注意,在jpcsp中,jump和jal指令都作为branch指令处理,jr和jalr则另行处理。

在mips指令集中,分支指令有很多种,包括带likely的,和带link的。分支指令是相对当前位置做跳转。带likely的,如果分支发生,则延迟槽中指令有效,否则延迟槽中指令要无效掉。Link就是链接,这样的分支指令是保存了返回地址的,返回地址保存在31号寄存器(ra)中。

Branch指令的处理流程是,加载分支条件相关的寄存器,然后编译延迟槽指令,之后再加一个条件跳转。

来看compileBranch函数:

private voidcompileBranch(CompilerContext context, MethodVisitor mv)

{

int branchingOpcode = getBranchingOpcode(context, mv);

if (branchingOpcode != Opcodes.NOP)

{

CodeInstructionbranchingToCodeInstruction = context.getCodeBlock().getCodeInstruction(getBranchingTo());

if (branchingToCodeInstruction != null)

{

context.visitJump(branchingOpcode,branchingToCodeInstruction);

}

else

{

context.visitJump(branchingOpcode,getBranchingTo());

}

}

}

首先,调用了getBranchOpcode函数。该函数做的事情是,确定分支类型(将mips的分支指令转换为java字节码的分支指令,包括一些简单优化),为特定类型的分支指令,加载寄存器。然后,编译延迟槽指令,并返回一个java字节码形式的分支指令。注意,分支指令只是被识别并返回,而没有生成在exec函数中。生成操作在前述这个compileBranch函数中。

回到compileBranch函数,如果经getBranchOpcode函数优化过后,分支指令仍然需要生成(取得的branchOpcode不为NOP),就生成这条指令。这里先从当前codeBlock中查询分支的目标位置:

CodeInstructionbranchingToCodeInstruction =

context.getCodeBlock().getCodeInstruction(getBranchingTo())

如果能找到,说明是一个分支指令,生成这个分支指令即可,java字节码中的分支指令中,目标位置有标号作为参数:

context.visitJump(branchingOpcode,branchingToCodeInstruction);

注意传进去的是目标位置的codeInstruction,所以这个visitJump可以取得目标位置指令的标号。实际就是这样实现的,代码就不贴了。

如果找不到,说明是j或者jal指令,长跳转,跳转到了当前函数之外,应该是函数调用:

context.visitJump(branchingOpcode,getBranchingTo());

在这个visitJump函数中,传入的参数是目标位置的地址。此时生成的核心代码是一个函数调用指令,去调用RuntimeContext.jump:

mv.visitMethodInsn(Opcodes.INVOKESTATIC,runtimeContextInternalName, "jump", "(III)I");

RuntimeContext.jump会根据地址去查询(或编译)对应的函数(codeBlock),然后调用其exec方法。

这里有一个问题:j是不带链接的,jalr是带链接的。所以j的目标位置那个函数,其返回时,也意味着当前这个函数(包含j指令的这个函数)的返回。而Jalr返回,则表示返回到当前函数,也就是jalr指令的延迟槽之后的位置。

为了应对这个问题,在jpcsp中,为exec方法提供三个参数和一个返回值。注意,exec方法对应了一个mips函数,这个mips函数的传参和接收参数的过程都已经包含在mips汇编码中了,这些操作对应的指令也被翻译引擎逐条翻译。而现在说的exec方法的参数和返回值,是面向模拟器的运行时上下文。三个参数分别是:

Int returnAddress

Int alternativeReturnAddress

boolean isJump

其中前两个是返回地址。isJump表示,这个exec方法对应的函数,是通过一个不带链接的指令调用的。

bal指令允许调用的目标函数返回到当前函数的返回地址,jal不允许这样的更改,所以传入的返回地址有两个。

///

xsb:这个调用和传参过程在CompilerContext.java中:

public voidvisitCall(int address, int returnAddress, int returnRegister, booleanuseAltervativeReturnAddress)

该函数传递两个返回地址的顺序似乎反了,本函数原来的返回地址应该是多出来的备选项,延迟槽之后的地址才是本该使用的选项。好在使用上应该没影响,两个返回地址地位对等。

/

返回值是一个地址,表示下一步要跳转到哪里。

通常mips中从一个函数返回,是使用jr ra这样的语句。

对于一个长跳转,如果跳转的目标位置是返回地址,表示当前mips函数的返回,则exec方法返回这个跳转的目标地址,是返回到运行时上下文,运行时上下文中此时检查返回值,如果和最初传入的返回地址相等,表示一次mips函数的返回。

对于长跳转,如果其所处的exec方法接收的isJump为true,则表示这个函数是通过一个不带链接的跳转指令,调用了RuntimeContext.jump,然后在此处调用的。此时不论跳转的目标位置是哪里,只要不带链接,就可以直接返回自己期望的跳转地址了。返回到了运行时上下文,在运行时上下文中来判定下一步跳转的地址,是否是不带链接的跳转指令传入的期望的返回地址。如果是,则mips函数返回。如果不是,则在此处继续根据期望的目标地址,来调用exec方法。

///

这里的逻辑稍微有点乱了,需要重新梳理一下。

注意,下文中各个标识符中,带不同数字后缀表示同一个方法的不同实例,而不是表示不同方法。

初始时,不论通过何种方式,总之一个函数开始运行了。

函数中会遇到不带链接的长跳转指令,以及带链接的长跳转指令。

对于不带链接的长跳转指令(记为j_1),会调用RuntimeContex.jump,传的参数包括期望的目标位置,以及期望的返回地址。RuntimeContex.jump中有一个while循环(记为loop_1)。循环体中的内容是调用目标位置的exec_1方法。传入isJump为true,告知该方法是通过一个不带链接的长跳转指令(j_1)而被调用的。

对于带链接的长跳转指令(记为jalr_1),会调用RuntimeContex.call,该函数也是调用目标位置的exec_2方法,但是传入isJump为false,告知该方法是通过一个带链接的长跳转指令(jalr_1)而被调用。

于是,对于接收到isJump参数的exec方法,其中遇到不带链接的长跳转(记为j_2)时,根据isJump分两种情况:

1.如果isJump为true,表示这个exec_1方法是通过一个不带链接的长跳转(记为j_1)进入,所以此时该exec方法中遇到的不带链接长跳转(j_2),不论其跳转目标如何,都可以直接返回这个期望跳转到的目标位置。是返回到了前述的loop_1,在loop_1中判定期望跳转的目标位置是否是之前传入的期望的返回值。如果是,则表示一次mips函数的返回,要处理一下内存中的函数栈。如果不是,则表示继续跳转,也就是继续调用目标位置的exec_3方法,并传入isJump为true。

2.如果isJump为false,表示这个exec方法是通过一个带链接的长跳转(jalr_1)进入的,此时要在这个exec方法中检查(j_1的)目标位置是否是返回地址,如果是则返回(到达RuntimeContex.call中调用该exec_2的位置),然后进一步返回到最初的jal_1延迟槽之后的位置。如果j_1的目标职位不是返回地址,则调用RuntimeContex.jump

/

希望这个问题已经说清楚了。。。综上,对于不带链接的长跳转指令,其翻译逻辑应该是:

if(isJump)

return jumpTargetAddress;

else if(jumpTargetAddress==returnAddreee)

return jumpTargetAddress

else

jumpTargetAddress= RuntimeContext.jump(jumpTargetAddress,returnAddress)

这个逻辑对应源码中的位置是CompileContext.visitJump()

/

前文描述了jpcsp中对分支指令,jr,jalr这三类指令的处理。

特别指出的是,j,jal,bal这三种指令都被包含在了CodeInstruction.compileBranch中处理。其中j的处理逻辑同jr,只是跳转的目标地址来源不同。jal的逻辑却是选择了和bal相同的逻辑,是直接生成了调用目标位置exec方法的java字节码。做的优化是,会先检查是否目标位置对应本地码,有对应则直接调用本地码。

xsb:这里似乎包含潜在的问题,因为对于跳转的目标位置,可能并不是已经编译好了,这样会导致运行时找不到期望的那个exec方法。jalr就不会有这样的问题,因为他把函数的调用交给了RuntimeContex,如果目标位置还没有编译,RuntimeContex调用的getExecutable方法会去呼叫编译器编译。也可能exec方法本身可以catch并处理目标位置没编译好的exception,对这种可能性并不看好。

总结:本文描述了jpcsp翻译引擎中,对于各种分支和跳转指令的处理逻辑。其他指令的处理逻辑都比较局部,没有和其他地方的复杂关联,就不做阐述了。

JPCSP源码解读15:动态二进制翻译3(翻译引擎最终章)相关推荐

  1. JPCSP源码解读14:动态二进制翻译2

    JPCSP源码解读14:动态二进制翻译2 IExecutable 上一篇中提到,我们现在有CodeInstruction,代表单条指令,以及其两个子类,分别代表无分支基本块和本地码序列.另外,有cla ...

  2. jpcsp源码解读9:指令的抽象描述与指令的译码

    本文尝试说明jpcsp中译码器单元的实现方式. / 首先是对指令的一个抽象描述,Instruction类: public static abstract class Instruction / jav ...

  3. jpcsp源码解读之一:源码的获取与编译,以及psp详尽硬件信息文档

    是我心血来潮的想法,要解读一下psp模拟器的源码,并添加详尽的中文注释.这个博客则成为文档. 本文面向java语言零基础的程序员,因为我本人的java基础就是零. 水平所限,疏漏错误之处欢迎指正.也欢 ...

  4. jpcsp源码解读6:PSF文件

    当你运行了模拟器,通过模拟器菜单选择并加载一个umd镜像,模拟器就用这个umd镜像实例化一个UmdIsoReader(见上一篇,源码解读5). 通过这个UmdIsoReader,从光盘提取的第一个文件 ...

  5. jpcsp源码解读5:umd光盘镜像(.iso)

    这次的状况稍显复杂. 首先说一下umd光盘镜像文件的内部组织方式.注意,这些内容全部是从源码解读而来,而不是来自关于这种文件格式的标准文档. java科普之文件操作: fileReader = new ...

  6. jpcsp源码解读13:动态二进制翻译1

    注意,本文不区分 编译和翻译.在本文中,他们表示同一个意思. 首先回顾一下,之前已经说明了,我们有cpu状态,就是cpu中的各个寄存器,还有内存,以及更改这些寄存器的接口函数.另外,我们有译码器(De ...

  7. jpcsp源码解读12:本地码管理器与Compiler.xml

    jpcsp这个模拟器的优化手段实在让人汗颜. 之前说过,他把系统调用功能全部用本地码实现了,也就是在软件需要的时候,调用java语言的实现,而不是跳转到内存中相应位置去解释执行,或者对系统调用代码做动 ...

  8. jpcsp源码解读之二:main函数与jpcsp的初始化流程

    虽然这个软件是用java语言编写,面向对象,可是总要有个开始的入口,这里关心的就是,main函数在哪里. 似乎java中也可以没有main函数,也可能是我的错误认识.暂且不管,jpcsp中是有main ...

  9. jpcsp源码解读10:指令的执行

    这次要说的是处理器类: public class Processor 主要的成员变量: public CpuState cpu = new CpuState(); public static fina ...

最新文章

  1. ios app 砸壳
  2. MySQL高级-索引是个什么东西?explain到底怎么用-MySQL查询优化大全
  3. 如何获得SAP CRM SalesOrder里involved party的详细信息
  4. .net core 多版本如何选择
  5. easypoi list中的map导出_java导出excel(easypoi)
  6. 微信小程序点餐系统源码(微信点餐软件外卖系统)搭建
  7. 程序员桌面都这么秀?网友:用砖头当杯垫这样合适吗?留着吓谁
  8. 关于QTTabBar的使用
  9. 直播系统源码开发经验分享
  10. js2D物理引擎插件
  11. 计算机文档加密如何解锁,bitlocker怎么解锁_bitlocker解锁方法
  12. 极速office(word)如何在方框内打钩
  13. spring clude ---服务网关组件Netflix Zuul
  14. mysql查询当前用户中所有的表空间_oracle 查看用户所在的表空间
  15. javascript组合模式创建对象
  16. Unsupported format, or corrupt file: Expected BOF record; found b‘2021\xc4\xea\xca\xfd‘
  17. unity2020新特性_Unity Hackweek 2020 –无论我们身在何处,
  18. 干货!如何建立数据标签体系
  19. cocos2dx:重力加速度,自由落体:利用update()就能快速实现精灵自由落体运动
  20. Android资料集合

热门文章

  1. 未来刷脸支付突破线上线下一体化的瓶颈
  2. 2022熔化焊接与热切割考试题库及答案
  3. ps切片导出时将切片选项选择为“所有用户切片”
  4. 我居然在中秋节收到了七龙珠?! 能召唤神龙吗?
  5. 干货|计算机专业大学生毕业后找工作应该考哪些证书
  6. spring框架--全面详解(学习笔记)
  7. 如何方便快捷地从杂乱地址中提取省市区?
  8. pytorch每日一学13(torch.spares_coo_tensor())创建稀疏矩阵
  9. 什么是项目管理系统?
  10. 深入理解空间坐标系的矩阵变换