V8-SH4移植项目到目前为止的经验总结 2010.3
- 从IA32实现移植,而不是ARM
- 尽量在指令级实现移植(Assembler、MacroAssembler),这样大部分代码可不做修改
- IA32的通用寄存器名称不变,定义成常量映射为SH4的通用寄存器
- 比较及条件跳转
- 现实:IA32有FLAG寄存器,而SH4只有一个T位
- 从高层概念上把握一个原则:‘条件成立’时,跳或不跳(动作必须固定住)
- IA32的ReverseCondition用于交换src,dst,SH4则不需要
- 对a<b,IA32翻译为cmp(a,b);j(less,target); 而SH4则翻译为cmp_gt(a,b);bt(target);
- IA32里,a翻译为dst目标操作数;而SH4里,a变成了src源操作数
- 如果能够确定IA32的cmp后续只使用j(equal/not_equal, 则可简单翻译为SH4 cmp_eq
- 否则,延迟比较操作,将dst、src操作数(j(less,target)即是dst <(less) src)压栈,在碰到j条件跳转时再pop出执行比较
- 此情况下,原始IA32 cmp与j中间不能够有push/pop栈操作、或根据sp+offset的数据寻址操作!
- 寻址:
- 根据两边的ABI约定,EAX应映射为r0,同时r0在SH4中不应作为临时寄存器使用,避免冲突
- 所有的寻址包装成XxxOperand函数的形式,可在开始时使用临时寄存器,只翻译为@Rn间接寻址方式
- 至于是否需要当offset很小时,使用@(disp,Rn) add(imm8, Rn) @(R0,Rn)待功能实现后优化不迟
- CallRuntime:Runtime函数参数为Argument(argc,argv),但有可能返回64位的ObjectPair
- IA32里,argc、argv参数被压栈,(edx:eax)用于返回64位ObjectPair
- SH4里,argc、argv分别传递给r4、r5,(r1:r0)用于返回64位数据
- *推论:EDX EAX最好直接映射为r1,r0?
- IA32 Call指令
- IA32的call自动将pr(return address)压栈;SH4需要手工加上(在某些情况下,可能不需要)
- StackFrame:某些Stub::Generate,SH4栈上没有pr,需要调整offset
- fixup(isPCRelative=true/fasle时的不同处理)
- C++代码中如何获取当前Frame的pp、fp、sp?(利用pr。。。)
- IA32 call/jmp imm32指令的翻译:
- 相对地址的call/jmp,使用BSRF BRAF;绝对地址,使用JSR JMP
- 注意:要求知道bsrf braf jsr jmp指令的地址,即可得到imm32地址,因此与mov.l的相对位置必须固定
- bf/bt/bsr/bra用于同一个Code内部的分支跳转
- 不使用bf.s bt.s
- 相对地址的call/jmp,使用BSRF BRAF;绝对地址,使用JSR JMP
- SH4 加载32位立即数到寄存器
- 统一的调用入口:mov(const Immediate& src, Register dst);
- 因重定位信息只保存mov指令的地址,为使得计算公式统一,要求当rmode!=NONE时,mov指令应Align(4,2);
- 立即数不管大小,可统一使用mov(const Immediate& src, Register dst);
- 后期优化:仅当rmode==NONE且is_int8(src.value())时,可内部替换为mov_imme(imm8, dst);
- 下列操作不应用汇编指令实现,而应考虑提供外部C++ builtin函数:
- 64位整数的MUL DIV MOD
- CheckFloatOperands:+0 -0 NaN
- v8原始代码的Smi优化:可暂不考虑
- v8翻译js switch语句的快速Jump Table实现:可暂不考虑
- CodeSegment算法
- EnsureSpace::CheckSpace(Assembler* assm, int ins_bytes_needed=2, int imm_bytes_needed=0/4/...);
- 当开始新代码段的第一个立即数加载指令(imm_bytes_needed>0)时,必须确保还有:
- ins_bytes_needed(指令本身大小) + 127(第1个disp值)*4+4/2 + 128*4(假设最多可保存128个立即数) + imm_bytes_needed/4*RelocInfo::kMaxSize ==> ins_bytes_needed+1024+3*imm_bytes_needed
- DataLabel、ShortLabel
- 反汇编:Dissembler::Decode(stdout, start, end);
- 应正确输出立即数,因此内部需要记住mov.l指令对应立即数的位置
- 这里假设一个mov.l指令加载一个立即数,指令总在操作数前面(实际上disp类型是uint8_t)
- 应正确输出立即数,因此内部需要记住mov.l指令对应立即数的位置
- Simulator:Jmp外部C++函数时,仿真为先Call外部C++函数,然后执行一个Return
- v8 C++ builtin函数:原型总是Object* BUILTIN_FUNCTION(int argc, char** argv);
- 也就是说,总是2个参数,SH4下通过r4,r5传递
- 但有可能没有返回值,但无论如何,应注意调用builtin后不应立即改掉r0,这有可能导致一个错误
- v8的js调用builtin函数实现为:先加载目标函数的绝对地址到Rn;延后、在适当时机执行jsr(Rn)
- 测试:首先cctest,然后mjsunit,确保全部通过(0.4.9.3 mjsunit存在已知bug)
- 编码实践指南
- 不要复制粘贴,而是首先提取出公共函数,然后手工敲入调用代码(特别是注意不同的参数)
- 注意临时寄存器的使用,最好是强制要求调用者指定;其次是约定使用规则
- 使用C++代码交叉引用生成工具(最好是能够展开宏代码的)
- 分离大的文件如codegen-sh4.cc,根据功能变成小的容易理解、管理、分配的,且SCM也不大会出现冲突
- 尽快将调试时所做的补丁代码以易于理解维护的方式改写掉
- 使用代码覆盖工具,测试MK23测试代码的条件覆盖情况