# doc **Repository Path**: advanced_os_ucas/doc ## Basic Information - **Project Name**: doc - **Description**: 2024年春季学期《高级操作系统》课程大作业第8组项目文档 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-05-20 - **Last Updated**: 2024-07-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

Arkcompiler对AssemblerRiscv64 I型部分非特权/特权级指令的支持

张仁号 侯姚旸 覃航
### 复现步骤 提供了一键运行脚本:[ build.sh ](https://gitee.com/advanced_os_ucas/doc/blob/master/scripts/build.sh) 脚本最好在x86机器上运行: ```shell chmod +x build.sh ./build.sh ``` 相关代码在[Gitee仓库](https://gitee.com/advanced_os_ucas/doc) ### 任务简介 Arkcompiler是华为推出的编译器平台,该编译器支持多种编程语言、多种芯片平台的联合编译与运行。 ArkCompiler主要分成两个部分:编译工具链与运行时。 - ArkCompiler的编译工具链以ArkTS/TS/JS源码作为输入,将其编译生成为abc(ArkCompiler Bytecode,即方舟字节码)文件。 - ArkCompiler运行时直接运行字节码文件,实现对应语言规范的语义逻辑。下面是ArkCompiler运行时的架构图: ![arkcompiler_ets_runtime](img/arkcompiler_ets_runtime.png) 实验八主要是实现对12条riscv指令的构建工作。具体来说,编译器会传递指令的rs、rd、imm等参数,我们需要编写相关函数,根据指令类型将上述参数组装成riscv指令以供其他部分的编译器代码调用。 主要是实现对以下指令的支持: ```shell 0000000 shamt rs1 001 rd 0011011 SLLIW 0000000 shamt rs1 101 rd 0011011 SRLIW 0100000 shamt rs1 101 rd 0011011 SRAIW 0000000 shamt rs1 001 rd 0010011 SLLI 0000000 shamt rs1 101 rd 0010011 SRLI 0100000 shamt rs1 101 rd 0010011 SRAI csr rs1 001 rd 1110011 CSRRW csr rs1 010 rd 1110011 CSRRS csr rs1 011 rd 1110011 CSRRC csr zimm 101 rd 1110011 CSRRWI csr zimm 110 rd 1110011 CSRRSI csr zimm 111 rd 1110011 CSRRCI ``` ![输入图片说明](img/imagesimage.png) ![输入图片说明](img/image.png) ### 开发环境搭建 配置实验环境 ~~~bash # 安装repo工具 sudo curl https://gitee.com/oschina/repo/raw/fork_flow/repo-py3 -o /usr/local/bin/repo #如果没有权限,可下载至其他目录,并将其配置到环境变量中 sudo chmod a+x /usr/local/bin/repo pip3 install -i https://repo.huaweicloud.com/repository/pypi/simple requests # 下载源码 repo init -u https://gitee.com/riscv-sig/manifest.git -b weekly -m riscv64_weekly0905_init.xml --no-repo-verify repo sync -c repo forall -c "git lfs pull" # 安装编译器及二进制工具 ./build/prebuilts_download.sh # 全量编译代码 ./build.sh --product-name rk3568 --ccache ~~~ 按照正常步骤构建好环境以后,进入`arkcompiler/ets_runtime` 文件夹,将这个git remote url替换成我们自己的ets_runtime仓库url:git@gitee.com:advanced_os_ucas/arkcompiler_ets_runtime.git。 ```shell # 由于实验八的代码已经被其他人合并,所以我们直接删除官方的ark compiler代码,换成我们自己的 cd arkcompiler/ets_runtime git remote remove origin git remote set-url origin git@gitee.com:advanced_os_ucas/arkcompiler_ets_runtime.git git checkout dev git fetch origin git reset --hard origin/dev git pull origin dev --force ``` 然后就可以基于dev分支创建自己的开发分支了 ### 生成编译数据库compile_command.json,用于代码补全 ```shell cd out/rk3568/ ninja -w dupbuild=warn -t compdb cxx cc > compile_commands.json ``` ### 小工具 #### 1. 将汇编指令汇编成机器码,方便编写测试函数 [assembler.py](https://gitee.com/advanced_os_ucas/doc/blob/master/scripts/assembler.py) 前置条件: ```shell sudo apt install gcc-riscv64-unknown-elf ``` 工具代码 ```C cat > assembler.py << "EOF" #!/usr/bin/env python3 import subprocess import tempfile def assemble_and_disassemble(assembly_instruction, address): # Create a temporary assembly file with tempfile.NamedTemporaryFile(suffix='.s', delete=False) as asm_file: asm_file.write(f".section .text\n.global _start\n_start:\n {assembly_instruction}\n".encode()) asm_filename = asm_file.name # Assemble the assembly file to an object file obj_filename = asm_filename.replace('.s', '.o') subprocess.run(['riscv64-unknown-elf-as', '-o', obj_filename, asm_filename]) # Disassemble the object file to get the machine code result = subprocess.run(['riscv64-unknown-elf-objdump', '-d', obj_filename], capture_output=True, text=True) # Extract the machine code from the objdump output lines = result.stdout.splitlines() for line in lines: if '_start' in line: # The next line contains the machine code and address machine_code_line = lines[lines.index(line) + 1] parts = machine_code_line.split() machine_code = parts[1] return f"{address:08x}:{machine_code} \\t{spli_instruction(assembly_instruction)}\\n" return None def spli_instruction(assembly_instruction): # Split the instruction and operands by the first space parts = assembly_instruction.split(' ', 1) if len(parts) > 1: return f"{parts[0]}\\t{parts[1]}" return assembly_instruction def main(): address = 0x00000000 while True: assembly_instruction = input("Enter the RISC-V assembly instruction: ") machine_code = assemble_and_disassemble(assembly_instruction, address) if machine_code: print(f"Output: {machine_code}") address += 4 else: print("Failed to assemble the instruction.") if __name__ == "__main__": main() EOF ``` 效果如下: ![assembler tool](img/assembler-tool.png) #### 2. 提取CSR信息 目前已分配了 400 多个 CSR,目前 ark compiler 缺少 CSR 名称及对应编码信息,需要从 risc-v 的 spec 中提取并用枚举类型定义它们,手工编码容易出错,编写了一个[脚本](https://gitee.com/advanced_os_ucas/doc/blob/master/scripts/spider.ts)来提取它们。 ##### 编译脚本 ```shell pnpm i pnpm build ``` ##### 运行 下载[riscv-privileged.html](https://github.com/riscv/riscv-isa-manual/releases)并用浏览器打开,在控制台运行编译产物`dist/spider.js`得到 CSR enum 成员。 **生成的代码已被南京学院的 [PR](https://gitee.com/riscv-sig/arkcompiler_ets_runtime/pulls/73 ) 合入 OH RISC-V SIG:weekly_20230905 分支** ### 任务实现思路 #### 项目构建过程 arkcompiler/ets_runtime/BUILD.gn中定义了测试group ```shell group("ark_compiler_unittest") { testonly = true deps = [] deps += ["ecmascript/compiler/tests:host_unittest" ] } ``` 这个group中定义了对host_unittest的依赖 host_unittest的定义如下: ```shell host_unittest_action("AssemblerTest") { module_out_path = module_output_path sources = [ # test file "../assembler/tests/assembler_aarch64_test.cpp", "../assembler/tests/assembler_riscv64_test.cpp", "../assembler/tests/assembler_x64_test.cpp", ] deps = [ "$ark_root/libpandafile:libarkfile_static", "$js_root:libark_jsruntime_test_set", "$js_root/ecmascript/compiler:libark_jsoptimizer_set", sdk_libc_secshared_dep, ] # hiviewdfx libraries external_deps = hiviewdfx_ext_deps deps += hiviewdfx_deps } group("host_unittest") { testonly = true # deps file deps = [ ":AssemblerTestAction" ] } ``` 最终会构建出一个用于测试的可执行文件 #### 编写思路 由于特权级指令和非特权级指令的格式不太统一,这里分成两个部分进行说明: ##### 非特权级指令的实现 在assembler_riscv64.h中定义了生成相关riscv指令的函数 ```C class AssemblerRiscv64 : public Assembler { public: explicit AssemblerRiscv64(Chunk *chunk) : Assembler(chunk) { } ... // 0000000 shamt rs1 001 rd 0011011 SLLIW void Slliw(const Register &rd, const Register &rs1, const Immediate &imm12); // 0000000 shamt rs1 101 rd 0011011 SRLIW void Srliw(const Register &rd, const Register &rs1, const Immediate &imm12); // 0100000 shamt rs1 101 rd 0011011 SRAIW void Sraiw(const Register &rd, const Register &rs1, const Immediate &imm12); // 0000000 shamt rs1 001 rd 0010011 SLLI void Slli(const Register &rd, const Register &rs1, const Immediate &imm12); ... }; ``` 在assembler_riscv64_constants.h中使用宏模版实现上述函数接口 定义opCode 和func code ```C enum opCode { ... opCodeIW = 0x1b, // SLLIW,SRLIW,SRAIW }; enum funct3 { ... funct3Slliw = 0x1000, ... }; enum funct7 { funct7Slliw = 0x0, funct7Srliw = 0x0, funct7Sraiw = 0x40000000, funct7Slli = 0x0, }; enum ShiftOpFunct { ... /* SLLIW = 0x0000101b, */ SLLIW = opCodeIW | funct3Slliw | funct7Slliw, ... }; ``` 修改相关模版宏,增加对I type指令的支持 ```C++ #define EMIT_INSTS \ EMIT_I_TYPE_INSTS(EMIT_I_TYPE_INST) \ ... #define EMIT_I_TYPE_INSTS(V) \ V(Slliw, SLLIW) // I型指令的宏模版 #define EMIT_I_TYPE_INST(INSTNAME, INSTID) \ void AssemblerRiscv64::INSTNAME(const Register &rd, const Register &rs1, const Immediate &imm12) \ { \ uint32_t rd_id = Rd(rd.GetId()); \ uint32_t rs1_id = Rs1(rs1.GetId()); \ uint32_t imm = (imm12.Value() << I_TYPE_imm12_LOWBITS);\ uint32_t code = rd_id | rs1_id | INSTID | imm; \ EmitU32(code); \ } ``` ##### 特权集指令的实现 定义CSR类: ```C class CSR { public: CSR(CSRId reg, RegisterWidth width) : reg_(reg), width_(width) {} CSR(CSRId reg) : reg_(reg) {}; RegisterWidth getWidth() const { return width_; } inline CSRId GetId() const { return reg_; } inline bool operator !=(const CSR &other) { return reg_ != other.GetId(); } inline bool operator ==(const CSR &other) { return reg_ == other.GetId(); } private: CSRId reg_; RegisterWidth width_; }; ``` 定义CSR寄存器编号: ```C enum CSRId : uint16_t { // Unprivileged Floating-Point CSRs FFLAGS = 0x001, FRM = 0x002, FCSR = 0x003, // Unprivileged Counter/Timers CYCLE = 0xC00, TIME = 0xC01, INSTRET = 0xC02, HPMCOUNTER3 = 0xC03, HPMCOUNTER4 = 0xC04, HPMCOUNTER5 = 0xC05, HPMCOUNTER6 = 0xC06, HPMCOUNTER7 = 0xC07, HPMCOUNTER8 = 0xC08, ... } ``` 定义生成相关riscv特权集指令的函数 ```C // csr rs1 001 rd 1110011 void Csrrw(const Register &rd, const Register &rs1, const CSR &csr); // csr rs1 010 rd 1110011 void Csrrs(const Register &rd, const Register &rs1, const CSR &csr); // csr rs1 011 rd 1110011 void Csrrc(const Register &rd, const Register &rs1, const CSR &csr); // csr zimm 101 rd 1110011 void Csrrwi(const Register &rd, const CSR &csr, const Immediate &zimm); // csr zimm 110 rd 1110011 void Csrrsi(const Register &rd, const CSR &csr, const Immediate &zimm); // csr zimm 111 rd 1110011 void Csrrci(const Register &rd, const CSR &csr, const Immediate &zimm); ``` 编写函数实现。由于csr特权指令和非特权指令的格式不太一样,所以特权指令的函数实现就是使用了单独的函数代码,而没有用宏模版生成。 ```C ... void AssemblerRiscv64::Csrrc(const Register &rd, const Register &rs1, const CSR &csr) { uint32_t code = 0; code = (csr.GetId() << I_TYPE_imm12_LOWBITS) | Rs1(rs1.GetId()) | funct3Csrrc | Rd(rd.GetId()) | opCodeIC; EmitU32(code); } ... ``` 测试函数的编写都是统一的格式。在assembler_riscv64_test.cpp中编写测试函数如下: ```C++ HWTEST_F_L0(AssemblerRiscv64Test, Slliw) { std::string expectResult( "00000000:0032931b \tslliw\tt1, t0, 3\n" "00000004:0004129b \tslliw\tt0, s0, 0\n" "00000008:0014931b \tslliw\tt1, s1, 1\n" "0000000c:01f9139b \tslliw\tt2, s2, 31\n"); AssemblerRiscv64 masm(chunk_); // SLLIW: Shift Left Logical Immediate Word __ Slliw(Register(T1), Register(T0), 3); // slliw t1, t0, 3 __ Slliw(Register(T0), Register(S0), 0); // slliw t0, s0, 0 __ Slliw(Register(T1), Register(S1), 1); // slliw t1, s1, 1 __ Slliw(Register(T2), Register(S2), 31); // slliw t2, s2, 31 std::ostringstream oss; DisassembleChunk(TARGET_RISCV64, &masm, oss); ASSERT_EQ(oss.str(), expectResult); } ``` 重新编译并运行测试文件,这时只需要进行模块编译就行了 ```shell ./build.sh --product-name rk3568 --build-target ark_compiler_unittest ./out/rk3568/clang_x64/tests/unittest/arkcompiler/ets_runtime/AssemblerTest ``` ### 任务完成情况 | 大作业具体要求 | 完成情况 | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | 完成arkcompiler环境搭建和源码编译 | 成功在ubuntu中运行整个arkcompiler项目,能够将TS、JS代码编译生成最终的.abc可执行文件 | | 完成RISC-V版本 Assembler类各个接口的实现并提交pr至riscv-sig | 实现了RISC-V相关指令的接口,但是相关任务在7月1号已经被社区提交并合并,所以我们无法再提交pr到riscv-sig | | 完成相应测试用例并提交pr至riscv-sig | 实现了riscv相关指令的测试例,能够成功编译测试例文件并运行。但是相关任务在7月1号已经被社区提交并合并,所以我们无法再提交pr到riscv-sig | | 书面实验报告 | 成功编写实验报告并提交 | - 编译的arkcompiler将ts文件编译成abc文件,并使用vm运行abc文件: ![ts2abc](img/ts2abc.png) - 测试用例执行通过截图: ![unittest](img/unittest.png) ### 贡献 - 张仁号(组长):review/merge各成员的代码,搭建I型指令的assembler框架、测试框架,对SLLIW SRLIW SRAIW SLLI 四条指令的支持,编写文档,小组汇报 - 侯姚旸:实现对SRLI SRAI CSRRW CSRRS四条指令的支持,编写测试用例,文档撰写,ppt制作 - 覃航:实现对CSRRC CSRRWI CSRRSI CSRRCI四条指令的支持,编写CSR信息提取脚本 ### 提交方式 本次实验全程在gitee仓库开发,基于pull request:[ gitee仓库地址 ](https://gitee.com/advanced_os_ucas) **由于实验八的功能已经被社区其他人完成并提交了pr,相关issue列表也已经标记为完成并关闭,所以我们组没有向社区提交pr** ### 实验中遇到的问题和解决方法 #### ark compiler编译时间太长 - 在需要多次修改并编译测试文件的情况下,在执行过一次上述命令以后,可以执行下面的命令直接进行编译,跳过前面相关工具的配置,因为第一次编译已经编译过了,这可以大大减少编译时间: ``` cd oh prebuilts/build-tools/linux-x86/bin/ninja -w dupbuild=warn -C out/rk3568 ark_compiler_unittest ``` #### 测试不方便 - 写了个将汇编指令变成riscv指令的脚本:[assembler.py](./scripts/assembler.py) #### CSR编号手动获取的话,枯燥切易错 - 写了个提取CSR编号的小工具:[spider.ts](./scripts/spider.ts) ### 参考文档 [openharmony官方文档](https://docs.openharmony.cn/pages/v4.0/zh-cn/device-dev/device-dev-guide.md) [openharmony gitee riscv-sig仓库](https://gitee.com/riscv-sig) [相关issue任务列表](https://gitee.com/riscv-sig/arkcompiler_ets_runtime/issues/I8NXZG?from=project-issue)