您的位置:首页 > 其它

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

2012-03-29 16:13 316 查看
jpcsp这个模拟器的优化手段实在让人汗颜。

之前说过,他把系统调用功能全部用本地码实现了,也就是在软件需要的时候,调用java语言的实现,而不是跳转到内存中相应位置去解释执行,或者对系统调用代码做动态二进制翻译后去执行翻译结果。

NativeCodeManager,本地码管理器,这个更暴力,把psp上软件用到的库函数都给用本地码替换掉了。

这些需要被替换的代码保存在源码根目录下的Compiler.xml中,包括这段代码的名字(功能性描述),以及对应的,包含实现这个函数功能的类。

比如,贴一个例子:

-<NativeCodeSequence name="memcpy">

<Class>jpcsp.Allegrex.compiler.nativeCode.Memcpy</Class>

<IsReturning>true</IsReturning>

- <CodeInstructions>

-<![CDATA[

0887F4BC:[00A04025]: or $t0, $a1, $zr <=> move $t0, $a1

0887F4C0:[00C02825]: or $a1, $a2, $zr <=> move $a1, $a2

0887F4C4:[01003025]: or $a2, $t0, $zr <=> move $a2, $t0

0887F4C8:[2CA80010]: sltiu $t0, $a1, 16

0887F4CC:[15000022]: bne $t0, $zr, 0x0887F558

0887F4D0:[00803825]: or $a3, $a0, $zr <=> move $a3, $a0

0887F4D4:[00C74025]: or $t0, $a2, $a3

0887F4D8:[31080003]: andi $t0, $t0, 3

0887F4DC:[1500001F]: bne $t0, $zr, 0x0887F55C

0887F4E0:[00A04825]: or $t1, $a1, $zr <=> move $t1, $a1

0887F4E4:[8CC80000]: lw $t0, 0($a2)

0887F4E8:[24C60004]: addiu $a2, $a2, 4

0887F4EC:[ACE80000]: sw $t0, 0($a3)

0887F4F0:[8CC80000]: lw $t0, 0($a2)

0887F4F4:[24E70004]: addiu $a3, $a3, 4

0887F4F8:[24C60004]: addiu $a2, $a2, 4

0887F4FC:[ACE80000]: sw $t0, 0($a3)

0887F500:[8CC80000]: lw $t0, 0($a2)

0887F504:[24E70004]: addiu $a3, $a3, 4

0887F508:[24C60004]: addiu $a2, $a2, 4

0887F50C:[ACE80000]: sw $t0, 0($a3)

0887F510:[8CC80000]: lw $t0, 0($a2)

0887F514:[24E70004]: addiu $a3, $a3, 4

0887F518:[ACE80000]: sw $t0, 0($a3)

0887F51C:[24A5FFF0]: addiu $a1, $a1, -16

0887F520:[24C60004]: addiu $a2, $a2, 4

0887F524:[2CA80010]: sltiu $t0, $a1, 16

0887F528:[1100FFEE]: beq $t0, $zr, 0x0887F4E4

0887F52C:[24E70004]: addiu $a3, $a3, 4

0887F530:[2CA80004]: sltiu $t0, $a1, 4

0887F534:[15000008]: bne $t0, $zr, 0x0887F558

0887F538:[00000000]: nop

0887F53C:[8CC80000]: lw $t0, 0($a2)

0887F540:[24A5FFFC]: addiu $a1, $a1, -4

0887F544:[ACE80000]: sw $t0, 0($a3)

0887F548:[24C60004]: addiu $a2, $a2, 4

0887F54C:[2CA80004]: sltiu $t0, $a1, 4

0887F550:[1100FFFA]: beq $t0, $zr, 0x0887F53C

0887F554:[24E70004]: addiu $a3, $a3, 4

0887F558:[00A04825]: or $t1, $a1, $zr <=> move $t1, $a1

0887F55C:[24A8FFFF]: addiu $t0, $a1, -1

0887F560:[11200009]: beq $t1, $zr, 0x0887F588

0887F564:[01002825]: or $a1, $t0, $zr <=> move $a1, $t0

0887F568:[80C90000]: lb $t1, 0($a2)

0887F56C:[24C60001]: addiu $a2, $a2, 1

0887F570:[A0E90000]: sb $t1, 0($a3)

0887F574:[01004825]: or $t1, $t0, $zr <=> move $t1, $t0

0887F578:[24A8FFFF]: addiu $t0, $a1, -1

0887F57C:[24E70001]: addiu $a3, $a3, 1

0887F580:[1520FFF9]: bne $t1, $zr, 0x0887F568

0887F584:[01002825]: or $a1, $t0, $zr <=> move $a1, $t0

0887F588:[03E00008]: jr $ra

0887F58C:[00801025]: or $v0, $a0, $zr <=> move $v0, $a0

]]>

</CodeInstructions>

</NativeCodeSequence>

可以看到,这个函数的名字是mamcpy,内存拷贝。对应的本地代码在一个Memcpy类(jpcsp.Allegrex.compiler.nativeCode.Memcpy)中实现。这段代码是否返回。最后是这段代码的汇编码。

汇编码以表达形式存放,第二列是指令地址(labelGroup = 2),可以作该行的标号用,第三列是指令的十六进制编码(opcodeGroup = 3),第五列是该指令的掩码(opcodeMaskGroup = 5)。通常没有第5列。文件中也看不到第一列,原因暂时不清楚。

掩码表示比较指令是否一样时,可以忽略掩码中为0的部分。猜想潜在的用途是,只关心指令的类型,指令中的某个域无关紧要,可以用掩码清除掉之后再比较。

初始化时,会把这个文件(Compiler.xml)加载进来,由NativeCodeManager管理。在动态二进制翻译的生成代码开始之前,先调用NativeCodeManager,搜索将要翻译的代码块是否已经记录在案,有记录的话就用本地码,没有才去真正的生成可执行代码。

这些函数的本地码,在模拟器源码中的位置是:jpcsp.Allegrex.compiler.nativeCode。目前这里有三十多个类。

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

具体来说,当需要编译器的实例,调用

Compiler.getInstance()

该方法判定编译器实例是否为空,非空则返回已有的实例,空则调用构建函数

Compiler()

构建函数中,只有一个语句,调用初始化函数

Initialise();

初始化函数中,打开了文件Compiler.xml

configuration = documentBuilder.parse(new File("Compiler.xml"));

从这个文件构建了 本地码管理器:

nativeCodeManager = new NativeCodeManager(configuration.getDocumentElement());

代码管理器的构建函数中,调用加载方法:

load(configuration);

加载函数中,读取所有本地码

NodeList nativeCodeBlocks =
configuration.getElementsByTagName("NativeCodeSequence");

然后提取并载入单个代码块

Element nativeCodeSequence = (Element)
nativeCodeBlocks.item(i);//取得单个代码块

loadNativeCodeSequence(nativeCodeSequence);//载入单个代码块

载入单个代码块的实现:

取得名字(name)和对应本地类(class),用这两个信息构建一个本地码序列nativeCodeSequence

取得nativeCodeSequence的各项属性,并注册进nativeCodeSequence

这些属性包括:是否返回,是否对应一个完整的代码块,函数名(就是对应类中的可执行方法,默认是call,也可以指定其他函数),mips指令序列(关于指令描述的序列,包括掩码,指令十六进制表示),函数需要的参数描述,第几条指令是分支指令,这些代码执行前要做的动作

其中红色字体是数据流,也就是Compiler.xml这个文件打开后,其中数据怎样一步步被提取并解析。红色变量的值,源头上都来自Compiler.xml。

将构建起来的这个nativeCodeSequence,加入到本地码管理器中:

addNativeCodeSequence(nativeCodeSequence);

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

本地码管理器中,维护了两个哈希表,还有一个链表。

其中一个哈希表是前述的从Compiler.xml载入的本地码序列,用第一条指令作哈希索引,索引到的元素是一个指令序列的列表,再对这个列表中逐个匹配。

链表中存放的,是那些载入的代码序列中,首条指令带掩码的。因为带掩码,就不能拿首条指令简单匹配,只能放在这个链表中,逐个匹配。(这是因为,没有掩码就是整个指令32位匹配,这种匹配发生的概率很小,故如果真的配成功,这种成功,与 整体匹配 的相关性较大,也就是此时整体匹配的概率较大。)

这也就是根据指令和代码块来查找本地码的方法的实现:

NativeCodeManager.java

public NativeCodeSequence getNativeCodeSequence(CodeInstruction codeInstruction, CodeBlock codeBlock)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////

总结起来,本文主要涉及两个类,本地码序列NativeCodeSequence,和本地码序列管理器NativeCodeManager。

从Compiler.xml提取到所有的本地码序列,注册进本地码序列管理器维护的列表中。管理器对于本地码序列提供 加载,查找 等方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: