上一篇条件跳转的范围不够了怎么办?扩容!讲述了跳转在汇编层面跳转指令是如何生成机器码的,以及当条件跳转的13位跳转空间不够的时候,是如何依托auipc和jalr来实现32位的条件跳转的。接下来我们来分析一下安卓中如何使用test.cc对编写的assembler进行测试。此处仅对扩容跳转进行测试。
测试源码
TEST_F(AssemblerRiscv64Test, LongBeq) {
riscv64::Riscv64Label label;
__ Beq(riscv64::A0, riscv64::A1, &label); // 条件跳转,但是跳转的范围超过了13位的有效表示数字
constexpr uint32_t kAdduCount1 = (1u << 12) + 1;
for (uint32_t i = 0; i != kAdduCount1; ++i) {
__ Add(riscv64::ZERO, riscv64::ZERO, riscv64::ZERO);
}
__ Bind(&label);
constexpr uint32_t kAdduCount2 = (1u << 12) + 1;
for (uint32_t i = 0; i != kAdduCount2; ++i) {
__ Add(riscv64::ZERO, riscv64::ZERO, riscv64::ZERO);
}
__ Beq(riscv64::A2, riscv64::A3, &label);
// 以上是需要调用assembler来实现机器码的生成,接下来要生成string类型的字符串,以供工具链生成机器码并用来和assembler生成的机器码进行对比
uint32_t offset_forward = 2 + kAdduCount1; // 2: account for auipc and jalr.
// offset_forward表示的是向后跳转的offset, "auipc t2, 0x"获得pc的地址位auipc的起始地址,jalr跳转的是pc+offset的地址,所以想要获得在jalr结束处的pc地址,就要加上auipc和jalr自身的偏移量,此时+2表示为加上两条指令的数量
offset_forward <<= 2; // 将指令的数量左移两位也就是*4,得到了指令数量对应的大小
offset_forward += (offset_forward & 0x800) << 1; // Account for sign extension in jalr.
// 为了防止最高位为1的情况,该部分介绍依然位于文章开头的链接中
uint32_t offset_back = -(kAdduCount2 + 1); // 1: account for bne.
// offset_back表示向前跳转,+1是因为auipc是获得了auipc处的pc地址,在jalr进行跳转的时候,需要将pc地址挪到bne开始处然后加偏移量才是真正的偏移量
offset_back <<= 2;
offset_back += (offset_back & 0x800) << 1; // Account for sign extension in jalr.
/*reg AT =T2*/
std::ostringstream oss;
oss <<
"bne a0, a1, 1f\n"
// beq被替换成三条指令进行测试,在编译器中是无法对汇编指令自动进行优化和扩容的,所以在测试的时候需要将扩容好的汇编指令输入
"auipc t2, 0x" << std::hex << High20Bits(offset_forward) << "\n"
"Jalr zero, 0x" << std::hex << SignExtend64<riscv64::kIImm12Bits>(offset_forward) << "(t2)\n"
"1:\n" <<
// 对于跳转指令来说,只能给其label的跳转,而直接给跳转指令偏移量的跳转是无法进行跳转的,所以此处用三个label替代了原来的一个label
RepeatInsn(kAdduCount1, "add zero, zero, zero\n") <<
"2:\n" <<
RepeatInsn(kAdduCount2, "add zero, zero, zero\n") <<
"bne a2, a3, 3f\n"
"auipc t2, 0x" << std::hex << High20Bits(offset_back) << "\n"
"Jalr zero, 0x" << std::hex << SignExtend64<riscv64::kIImm12Bits>(offset_back) << "(t2)\n"
"3:\n";
std::string expected = oss.str();
DriverStr(expected, "LongBeq");
}
测试方法
在进行完测试代码的编写之后,直接编译utils文件即可,对单独代码的测试可以加上./out/host/linux-x86/nativetest/art_compiler_host_tests/assembler_riscv64_test --gtest_filter=*LongBeq
测试效果如图:
如果出错,如何查找问题
在移植的过程中无论多么简单的问题,想要一次成功是不可能的。如何寻找问题,是关键。当buffer中的机器码和gcc的机器码不一致的时候,会在测试执行的过程中直接输出与两个机器码的对比情况,这个就很烦,二进制代码谁看得懂?
这个问题,谷歌也帮你做好了。在art/compiler/utils/assembler_test_base.h
中有这样两条指令,当测试出错的时候,可以将两种情况的汇编指令打印出。将kKeepDisassembledFiles
设置为true,然后在tmpnam_
处设置路径。
接着就可以查看两种编译器生成的汇编到底不同在哪里,当时是因为gcc处给string赋值的时候,jalr的跳转没有将寄存器设置为0寄存器。
流程大致是以上流程,在mips的基础之上,为了方便条件跳转的测试,将测试代码进行了改进。
void LongBranchCondTwoRegsHelper(void (riscv64::Riscv64Assembler::*f)(riscv64::GpuRegister,
riscv64::GpuRegister,
riscv64::Riscv64Label*),
const std::string& instr_name) {
std::string opposite_name = riscv64::Riscv64Assembler::Branch::OppositeCondition(instr_name);
// 对instr_name取反,该函数为自己定义,但是在定义的时候需要注意,需要将OppositeCondition声明为static
// 因为该函数位于branch类中,但是在调用的时候并没有生成类的对象来调用,而是直接调用了该函数,所以需要将该函数定义为static
riscv64::Riscv64Label label;
(Base::GetAssembler()->*f)(riscv64::A0, riscv64::A1, &label);
// 此时调用模板类,这样可以方便不同类型的传参
// constexpr uint32_t kAdduCount1 = (1u << 20) + 1;
constexpr uint32_t kAdduCount1 = (1u << 20) + 0xfdf;
for (uint32_t i = 0; i != kAdduCount1; ++i) {
__ Add(riscv64::ZERO, riscv64::ZERO, riscv64::ZERO);
}
__ Bind(&label);
constexpr uint32_t kAdduCount2 = (1u << 20) + 0xfdf;
for (uint32_t i = 0; i != kAdduCount2; ++i) {
__ Add(riscv64::ZERO, riscv64::ZERO, riscv64::ZERO);
}
(Base::GetAssembler()->*f)(riscv64::A0, riscv64::A1, &label);
uint32_t offset_forward = 2 + kAdduCount1; // 2: account for auipc and jalr.
offset_forward <<= 2;
offset_forward += (offset_forward & 0x800) << 1; // Account for sign extension in jalr.
uint32_t offset_back = -(kAdduCount2 + 1);
offset_back <<= 2;
offset_back += (offset_back & 0x800) << 1; // Account for sign extension in jalr.
std::ostringstream oss;
oss <<
opposite_name << " a0, a1, 1f\n" <<
"auipc t2, 0x" << std::hex << High20Bits(offset_forward) << "\n"
"jalr zero, 0x" << std::hex << SignExtend64<12>(offset_forward) << "(t2)\n" <<
"1:\n" <<
RepeatInsn(kAdduCount1, "add zero, zero, zero\n") <<
"2:\n" <<
RepeatInsn(kAdduCount2, "add zero, zero, zero\n") <<
opposite_name << " a0, a1, 3f\n" <<
"auipc t2, 0x" << std::hex << High20Bits(offset_back) << "\n"
"jalr zero, 0x" << std::hex << SignExtend64<12>(offset_back) << "(t2)\n" <<
"3:\n" ;
std::string expected = oss.str();
std::string LongCondBranch = "Long" + instr_name;
// longCondBranch只是工具链生成的二进制文件的名字,用以区分不用测试生成的文件
DriverStr(expected, LongCondBranch);
}
TEST_F(AssemblerRiscv64Test, LongBeq){
LongBranchCondTwoRegsHelper(&riscv64::Riscv64Assembler::Beq, "beq");
}
TEST_F(AssemblerRiscv64Test, LongBne){
LongBranchCondTwoRegsHelper(&riscv64::Riscv64Assembler::Bne, "bne");
}