RISC-V目标代码生成
以下文档将向大家介绍实验五的进阶实验部分,讲解涉及的源码部分以及具体实验任务。
与该阶段有关的文件如下:
Makefile
拷贝
.
|-- CMakeLists.txt
|-- include
| |-- ...
| |-- codegen # 你需要阅读该目录下的文件
| | |-- ASMInstruction.hpp # 描述汇编指令
| | |-- CodeGen.hpp # 后端框架顶层设计,你可能需要修改此文件,e.g.增加context的域
| | |-- CodeGenUtil.hpp # 一些辅助函数及宏的定义
| | `-- Register.hpp # 描述寄存器
|-- ...
|-- src
| |-- codegen
| | |-- CMakeLists.txt
| | |-- CodeGen.cpp # 你需要修改此文件,完成其中的TODO
| | `-- Register.cpp # 你需要阅读此文件了解寄存器及其ABI
|-- ...
`-- tests
|-- ...
|-- 4-code-gen
| |-- autogen # 进阶实验本地测试目录
| | |-- cleanup.sh
| | |-- eval_lab5.sh # 测试脚本
| | |-- log.txt # 测试结果日志
| | `-- testcases # 存放本地测试用例
`-- CMakeLists.txt
实验内容
源码中TODO部分暂时用下面抛出异常代码替代,后续完成TODO后需要删除该代码
C++
拷贝
throw not_implemented_error{__FUNCTION__};
该代码用于定位你当前还需实现的函数,比如当你未完善gen_ret函数时,输入测试用例后执行到此处,就会抛出异常
C++
拷贝
terminate called after throwing an instance of 'not_implemented_error'
what(): gen_ret
Aborted
完成src/codegen/CodeGen.cpp 中的9个TODO,具体位置在文件内搜索TODO即可看到,推荐完成顺序
lv0:根据你的理解实现函数的 epilogue以及函数返回操作(TODO1&TODO2)
lv1:补全load整型变量的情况以及处理浮点数转向整数的情况(TODO3&TODO8)
lv2:完善浮点型全局变量声明及其初始化、完善浮点型全局数组变量声明及其初始化(TODO4&TODO5)
lv3:补全条件跳转操作(TODO6)
lv4:补全各种浮点型变量比较的情况(对应的Light IR:feq/fne/fgt/fge/flt/fle)(TODO7)
lv5:完善数组地址计算(TODO9)
lvx 对应本地测试目录
编译、运行
编译命令如下
Bash
拷贝
$ mkdir build
$ cd build
# 使用 cmake 生成 makefile 等文件
$ cmake ..
# 使用 make 进行编译
$ make
$ sudo make install
如果构建成功,你会在 build 文件夹下找到 sysy 可执行文件,它能将 sysy 文件输出为 .s汇编文件。
运行分为2种情况
生成 汇编文件
编译完一定要 sudo make install,确保sysy 在 $PATH 中。
Bash
拷贝
# 假设 sysy 的路径在你的 $PATH 中,并且你现在在 test.sysy 文件所在目录中
$ sysy -S test.sysy
此时会在同目录下生成同名的 .s 文件,在这里即为 test.s。也可以使用 -o 指定输出文件名,比如 sysy -S test.sysy -o anyname.s,这里 -o anyname.s 与 -S 的顺序可以是任意的。
生成可执行文件
上面生成的 .s 文件用于阅读,如果需要运行,需要调用 clang 编译链接生成二进制文件 test
Bash
拷贝
$ riscv64-unknown-elf-gcc -static test.s ../src/io/io.c -o test
这里是在build目录下执行,具体io库文件(../src/io/io.c)需要替换成你本地的对应路径。
之后可以用qemu运行
Bash
拷贝
$ qemu-riscv64 ./test
# 查看返回值
$ echo $?
ASMInstruction.hpp
#pragma once
#include <cassert>
#include <string>
struct ASMInstruction {
enum InstType { Instruction, Atrribute, Label, Comment } type;
std::string content;
explicit ASMInstruction(std::string s, InstType ty = Instruction)
: type(ty), content(s) {}
std::string format() const {
switch (type) {
case ASMInstruction::Instruction:
case ASMInstruction::Atrribute:
return "\t" + content + "\n";
case ASMInstruction::Label:
return content + ":\n";
case ASMInstruction::Comment:
return "# " + content + "\n";
}
assert(false && "unreachable");
}
};
CodeGen.hpp
#pragma once
#include "ASMInstruction.hpp"
#include "Module.hpp"
#include "Register.hpp"
#include <map>
#include <utility>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <cassert>
#include <cstring>
#include <string>
#define STACK_ALIGN(x) ALIGN(x, 16)
#define CONST_0 ConstantInt::get(0, m)
#define FP "s0"
#define SP "sp"
#define RA_reg "ra"
#define R_USABLE (17 - 3)
#define FR_USABLE (24 - 2)
#define ARG_R 8
using std::map;
using std::pair;
using std::set;
using std::string;
using std::to_string;
using std::vector;
class CodeGen {
public:
explicit CodeGen(Module *module) : m(module) {}
string print() const;
void run();
template <class... Args> void append_inst(Args... arg) {
output.emplace_back(arg...);
}
void append_inst(const char *inst, std::initializer_list<string> args,
ASMInstruction::InstType ty = ASMInstruction::Instruction) {
static const std::unordered_set<std::string> offset_base_insts = {//需要使用 offset(base) 地址寻址格式的指令
"lb", "lbu", "lh", "lhu", "lw", "lwu", "ld",
"sb", "sh", "sw", "sd",
"flw", "fld", "fsw", "fsd"
};
auto content = string(inst) + " ";
if (offset_base_insts.count(inst) && args.size() == 3) {
// 假设格式为 {"rd", "base", "offset"}
auto it = args.begin();
std::string reg = *it++;
std::string base = *it++;
std::string offset = *it;
content += reg + ", " + offset + "(" + base + ")";
} else {
for (const auto &arg : args) {
content += arg + ", ";
}
// 移除最后的 ", "
content.pop_back();
content.pop_back();
}
output.emplace_back(content, ty);
}
private:
void allocate();
// 向寄存器中装载数据
void load_to_greg(Value *, const Reg &);
void load_to_freg(Value *, const FReg &);
void load_from_stack_to_greg(Value *, const Reg &);
// 向寄存器中加载立即数
void load_large_int32(int32_t, const Reg &);
void load_large_int64(int64_t, const Reg &);
void load_float_imm(float, const FReg &);
// 将寄存器中的数据保存回栈上
void store_from_greg(Value *, const Reg &);
void store_from_freg(Value *, const FReg &);
void global_array_int(ConstantArray * init_val);
void global_array_float(ConstantArray * init_val);
void gen_prologue();
void gen_ret();
void gen_br();
void gen_binary();
void gen_float_binary();
void gen_alloca();
void gen_load();
void gen_store();
void gen_icmp();
void gen_fcmp();
void gen_zext();
void gen_call();
void gen_gep();
void gen_sitofp();
void gen_fptosi();
void gen_epilogue();
string tmpregname(int i, bool is_float) const {//只会用到t0 or t1
assert(i == 0 or i == 1);
return (is_float ? "ft" : "t") + to_string(i);
}
static pair<int, int> immRange(int bit, bool u) {
pair<int, int> res;
if (u) {
res.first = 0;
res.second = (1 << bit) - 1;
} else {
bit--;
res.first = -(1 << bit);
res.second = (1 << bit) - 1;
}
return res;
}
static string label_name(BasicBlock *bb) {
return "." + bb->get_parent()->get_name() + "_" + bb->get_name();
}
static string func_exit_label_name(Function *func) {
return func->get_name() + "_exit";
}
static string fcmp_label_name(BasicBlock *bb, unsigned cnt) {
return label_name(bb) + "_fcmp_" + to_string(cnt);
}
void makeSureInRange(string instr_ir,
string reg1,
string reg2,
int imm,
string tinstr,
int tid = 0,
int bits = 12,
bool u = false){
auto treg = tmpregname(tid, false);
assert(treg != reg2 && "it's possible to write tid before reg2's use");
// 获取立即数的合法范围
auto [l, h] = immRange(bits, u);
// 如果立即数在合法范围内,直接使用
if (l <= imm && imm <= h){
append_inst(instr_ir.c_str(), {reg1, reg2, to_string(imm)});
} else {
int imm_high = (imm + (1 << 11)) >> 12; // rounding
int imm_low = imm - (imm_high << 12);
// 强制转为无符号 20-bit
int lui_imm = imm_high & ((1 << 20) - 1);
// 构造指令
append_inst("lui", {treg, to_string(lui_imm)});
append_inst("addi", {treg, treg, to_string(imm_low)});
// 根据类型决定后续操作
if (tinstr == "ldx" || tinstr == "stx") {
append_inst("add", {treg, reg2, treg});
append_inst(instr_ir.c_str(), {reg1, treg, "0"});
} else {
append_inst(tinstr.c_str(), {reg1, treg, reg2});
}
}
}
struct {
/* 随着ir遍历设置 */
Function *func{nullptr}; // 当前函数
Instruction *inst{nullptr}; // 当前指令
BasicBlock *bb{nullptr}; // 当前BB
/* 在allocate()中设置 */
unsigned frame_size{0}; // 当前函数的栈帧大小
std::unordered_map<Value *, int> offset_map{}; // 指针相对 fp 的偏移
std::unordered_map<Value *, int> offset_call{}; // 指针相对 sp 的偏移
unsigned fcmp_cnt{0}; // fcmp 的计数器, 用于创建 fcmp 需要的 label
std::unordered_map<Value *, int> array_start_offset{};
void clear() {
func = nullptr;
inst = nullptr;
bb =nullptr;
fcmp_cnt = 0;
frame_size = 0;
offset_map.clear();
offset_call.clear();
array_start_offset.clear();
}
} context;
Module *m;
std::list<ASMInstruction> output;//输出RISCV
};
CodeGenUtil.hpp
#pragma once
#include <string>
#include <stdexcept>
/* 关于位宽 */
#define IMM_12_MAX 0x7FF
#define IMM_12_MIN -0x800
#define LOW_12_MASK 0x00000FFF
#define LOW_20_MASK 0x000FFFFF
#define LOW_32_MASK 0xFFFFFFFF
inline unsigned ALIGN(unsigned x, unsigned alignment) {
return ((x + (alignment - 1)) & ~(alignment - 1));
}
inline bool IS_IMM_12(int x) { return x <= IMM_12_MAX and x >= IMM_12_MIN; }
/* 栈帧相关 */
#define PROLOGUE_OFFSET_BASE 16 // $ra $fp
#define PROLOGUE_ALIGN 16
/* RISC-V指令 */
// Arithmetic
#define ADD "add"
#define SUB "sub"
#define MUL "mul"
#define DIV "div"
#define SLLI "slli"
#define ADDI "addi"
#define FADD "fadd"
#define FSUB "fsub"
#define FMUL "fmul"
#define FDIV "fdiv"
#define ORI "ori"
#define LI "li"
#define LU12I_W "lui"
// Data transfer (greg <-> freg)
#define GR2FR "fcvt.s.w"
#define FR2GR "fcvt.w.s"
// Memory access
#define LOAD_BYTE "lb"
#define LOAD_WORD "lw"
#define LOAD_DOUBLE "ld"
#define STORE_BYTE "sb"
#define STORE_WORD "sw"
#define STORE_DOUBLE "sd"
#define FLOAD_SINGLE "flw"
#define FSTORE_SINGLE "fsw"
// ASM syntax sugar
#define LOAD_ADDR "la"
// errors
class not_implemented_error : public std::logic_error {
public:
explicit not_implemented_error(std::string &&err_msg = "")
: std::logic_error(err_msg){};
};
class unreachable_error : public std::logic_error {
public:
explicit unreachable_error(std::string &&err_msg = "")
: std::logic_error(err_msg){};
};
Register.hpp
#pragma once
#include <cassert>
#include <string>
struct Reg {
unsigned id;
explicit Reg(unsigned i) : id(i) { assert(i <= 31); }
bool operator==(const Reg &other) { return id == other.id; }
std::string print() const;
static Reg zero() { return Reg(0); }
static Reg ra() { return Reg(1); }
static Reg sp() { return Reg(2); }
static Reg fp() { return Reg(8); }
static Reg a(unsigned i) {
assert(0 <= i and i <= 7);
return Reg(i + 10);
}
static Reg t(unsigned i) {
assert(0 <= i and i <= 6);
if(i < 3)
return Reg(i + 5);
else
return Reg(i + 25);
}
static Reg s(unsigned i) {
assert(0 <= i and i <= 11);
if (i < 2)
return Reg(i + 8);
else
return Reg(i + 16);
}
};
struct FReg {
unsigned id;
explicit FReg(unsigned i) : id(i) { assert(i <= 31); }
bool operator==(const FReg &other) { return id == other.id; }
std::string print() const;
static FReg fa(unsigned i) {
assert(0 <= i and i <= 7);
return FReg(i + 10);
}
static FReg ft(unsigned i) {
assert(0 <= i and i <= 15);
if (i >= 12) return FReg(i + 12);
else if (i >= 8) return FReg(i + 20);
else return FReg(i);
}
static FReg fs(unsigned i) {
assert(0 <= i and i <= 7);
if (i <= 1) return FReg(i + 8);
else return FReg(i + 16);
}
};
Register.cpp
#include "../../include/codegen/Register.hpp"
#include <string>
std::string Reg::print() const {
if (id == 0) {
return "zero";
}
if (id == 1) {
return "ra";
}
if (id == 2) {
return "sp";
}
if (id == 3) {
return "gp";
}
if (id == 4) {
return "tp";
}
if (5 <= id and id <= 7) {
return "t" + std::to_string(id - 5);
}
if (id == 8) {
return "fp";
}
if (id == 9) {
return "s1";
}
if (10 <= id and id <= 17) {
return "a" + std::to_string(id - 10);
}
if (18 <= id and id <= 27) {
return "s" + std::to_string(id - 16);
}
if (28 <= id and id <= 31) {
return "t" + std::to_string(id - 25);
}
assert(false);
}
std::string FReg::print() const {
if (0 <= id and id <= 7) {
return "ft" + std::to_string(id);
}
if (8 <= id and id <= 9) {
return "fs" + std::to_string(id - 8);
}
if (10 <= id and id <= 17) {
return "fa" + std::to_string(id - 10);
}
if (18 <= id and id <= 27) {
return "fs" + std::to_string(id - 16);
}
if (28 <= id and id <= 31) {
return "ft" + std::to_string(id - 20);
}
assert(false);
}
CodeGen.cpp
#include "CodeGen.hpp"
#include "CodeGenUtil.hpp"
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <utility>
#include <vector>
void CodeGen::allocate() {
unsigned offset = PROLOGUE_OFFSET_BASE;
// 为每个参数分配栈空间(如果没有被分配寄存器)
for (auto &arg : context.func->get_args()) {
auto size = arg.get_type()->get_size();
offset = ALIGN(offset + size, size);
context.offset_map[&arg] = -static_cast<int>(offset);
}
// 为每条指令结果分配栈空间
for (auto &bb : context.func->get_basic_blocks()) {
for (auto &instr : bb.get_instructions()) {
// 每个非 void 的定值都分配栈空间
if (not instr.is_void()) {
auto size = instr.get_type()->get_size();
offset = ALIGN(offset + size, size);
context.offset_map[&instr] = -static_cast<int>(offset);
}
// 为数组 alloca 分配额外空间(不对齐)
if (instr.is_alloca()) {
auto *alloca_inst = static_cast<AllocaInst *>(&instr);
auto alloc_size = alloca_inst->get_alloca_type()->get_size();
offset += alloc_size;
context.array_start_offset[alloca_inst] = offset;
}
}
}
// 最终的帧大小对齐为 16 的倍数
context.frame_size = ALIGN(offset, PROLOGUE_ALIGN);
}
void CodeGen::gen_prologue() {
makeSureInRange("addi", SP, SP, -context.frame_size, "add");
makeSureInRange("sd", RA_reg, SP, context.frame_size - 8, "stx");
makeSureInRange("sd", FP, SP, context.frame_size - 16, "stx");
makeSureInRange("addi", FP, SP, context.frame_size, "add");
// 将函数参数转移到栈帧上
int garg_cnt = 0;
int farg_cnt = 0;
for (auto &arg : context.func->get_args()) {
if (arg.get_type()->is_float_type()) {
store_from_freg(&arg, FReg::fa(farg_cnt++));
} else { // int or pointer
store_from_greg(&arg, Reg::a(garg_cnt++));
}
}
}
void CodeGen::gen_epilogue() {
// TODO1:根据你的理解实现函数的 epilogue
// 提示:可能包括的步骤:恢复ra、恢复s0、恢复sp、返回到调用方
throw not_implemented_error{__FUNCTION__};
//TODO1-------------end
}
// 将一个值 val 加载到目标通用寄存器 reg 中
void CodeGen::load_to_greg(Value *val, const Reg ®) {
assert(val->get_type()->is_integer_type() ||
val->get_type()->is_pointer_type());
if (auto *constant = dynamic_cast<ConstantInt *>(val)) {// 如果 val 是一个常数整数
int32_t val = constant->get_value();
if (IS_IMM_12(val)) {
append_inst(ADDI, {reg.print(), "zero", std::to_string(val)});
} else {
load_large_int32(val, reg);// 如果常数太大,用 load_large_int32 处理
}
} else if (auto *global = dynamic_cast<GlobalVariable *>(val)) { // 如果是全局变量,生成地址加载指令
append_inst(LOAD_ADDR, {reg.print(), global->get_name()});
} else { //剩余情况从栈中加载到寄存器
load_from_stack_to_greg(val, reg);
}
}
// 加载一个 32 位大整数到寄存器(通常是伪指令 li 会被展开成 lui+addi)
void CodeGen::load_large_int32(int32_t val, const Reg ®) {
append_inst(LI, {reg.print(), std::to_string(val)});
}
// 加载一个 64 位整数到寄存器,先加载高 32 位并左移,再加载低 32 位
void CodeGen::load_large_int64(int64_t val, const Reg ®) {
auto low_32 = static_cast<int32_t>(val & LOW_32_MASK); // 提取低 32 位
auto high_32 = static_cast<int32_t>(val >> 32); // 提取高 32 位
load_large_int32(high_32, reg);
append_inst(SLLI, {reg.print(), reg.print(), "32"}); // 加载高 32 位并左移 32 位
load_large_int32(low_32, reg);// 覆盖写入低 32 位
}
// 从栈帧中加载某个变量 val 到通用寄存器 reg 中
void CodeGen::load_from_stack_to_greg(Value *val, const Reg ®) {
// 获取该变量在当前函数栈帧中的偏移
auto offset = context.offset_map.at(val);
auto offset_str = std::to_string(offset);
auto *type = val->get_type(); // 获取该变量的类型(用于确定加载指令)
if (IS_IMM_12(offset)) { // 如果 offset 能够用 12 位立即数表示,可以直接使用 offset(fp) 格式访问内存
if (type->is_int1_type()) {
append_inst(LOAD_BYTE, {reg.print(), "fp", offset_str});
} else if (type->is_int32_type()) {
append_inst(LOAD_WORD, {reg.print(), "fp", offset_str});
} else { // Pointer
append_inst(LOAD_DOUBLE, {reg.print(), "fp", offset_str});
}
} else { // 如果偏移过大,不能直接编码到指令中,先将 offset 加载到寄存器
load_large_int64(offset, reg); // reg = offset
append_inst(ADD, {reg.print(), "fp", reg.print()}); // reg = fp + offset
if (type->is_int1_type()) {
append_inst(LOAD_BYTE, {reg.print(), reg.print(), "0"});
} else if (type->is_int32_type()) {
append_inst(LOAD_WORD, {reg.print(), reg.print(), "0"});
} else { // Pointer
append_inst(LOAD_DOUBLE, {reg.print(), reg.print(), "0"});
}
}
}
// 将通用寄存器 reg 中的值存储到 val 对应的栈上位置(以 fp 为基址)
void CodeGen::store_from_greg(Value *val, const Reg ®) {
// 获取该变量在当前函数栈帧中的偏移
auto offset = context.offset_map.at(val);
auto offset_str = std::to_string(offset);
auto *type = val->get_type(); // 获取该变量的类型(用于确定加载指令)
if (IS_IMM_12(offset)) { // 如果 offset 能够用 12 位立即数表示,可以直接使用 offset(fp) 格式访问内存
if (type->is_int1_type()) {
append_inst(STORE_BYTE, {reg.print(), "fp", offset_str});
} else if (type->is_int32_type()) {
append_inst(STORE_WORD, {reg.print(), "fp", offset_str});
} else { // Pointer
append_inst(STORE_DOUBLE, {reg.print(), "fp", offset_str});
}
} else { // 对于 offset 超出立即数范围的情况,需要通过地址计算访问
auto addr = Reg::s(11); // 使用临时寄存器 s11 作为中间地址计算(可更换)
load_large_int64(offset, addr);
append_inst(ADD , {addr.print(), "fp", addr.print()});
if (type->is_int1_type()) {
append_inst(STORE_BYTE, {reg.print(), addr.print(), "0"});
} else if (type->is_int32_type()) {
append_inst(STORE_WORD, {reg.print(), addr.print(), "0"});
} else { // Pointer
append_inst(STORE_DOUBLE, {reg.print(), addr.print(), "0"});
}
}
}
// 将一个浮点类型的 Value 加载到浮点寄存器 freg 中
void CodeGen::load_to_freg(Value *val, const FReg &freg) {
assert(val->get_type()->is_float_type());
if (auto *constant = dynamic_cast<ConstantFP *>(val)) { // 若是浮点常量,加载立即数
float val = constant->get_value();
load_float_imm(val, freg);
} else { // 从栈中加载浮点变量
auto offset = context.offset_map.at(val);
auto offset_str = std::to_string(offset);
if (IS_IMM_12(offset)) {
append_inst(FLOAD_SINGLE, {freg.print(), "fp", offset_str});
} else { // 偏移过大,使用寄存器间接寻址
auto addr = Reg::s(11); // 临时通用寄存器 s11
load_large_int64(offset, addr); // 加载偏移
append_inst(ADD, {addr.print(), "fp", addr.print()}); // addr = fp + offset
append_inst(FLOAD_SINGLE, {freg.print(), addr.print(), "0"}); // 从 addr 加载
}
}
}
// 将 float 常量加载进浮点寄存器 freg
void CodeGen::load_float_imm(float val, const FReg &r) {
int32_t bytes = *reinterpret_cast<int32_t *>(&val); // 将 float 解释为 32 位整数(IEEE 754 bit pattern)
load_large_int32(bytes, Reg::s(11));
append_inst("fmv.s.x", {r.print(), Reg::s(11).print()}); // 使用 fmv.s.x 指令将整数位模式转成 float 放入 freg
}
// 将浮点寄存器 r 中的值存储回栈中 val 对应的位置
void CodeGen::store_from_freg(Value *val, const FReg &r) {
auto offset = context.offset_map.at(val);
if (IS_IMM_12(offset)) {
auto offset_str = std::to_string(offset);
append_inst(FSTORE_SINGLE, {r.print(), "fp", offset_str});
} else { // 偏移过大,需要间接寻址
auto addr = Reg::s(11);
load_large_int64(offset, addr);
append_inst(ADD, {addr.print(), "fp", addr.print()}); // addr = fp + offset
append_inst(FSTORE_SINGLE, {r.print(), addr.print(), "0"}); // 从 r 存到 addr
}
}
void CodeGen::gen_ret() {
// TODO2:函数返回操作,你需要思考如何处理返回值(a0/fa0),如何返回到调用者(可以使用j指令、context中或许有你需要的信息)
throw not_implemented_error{__FUNCTION__};
// TODO2----------------end
}
void CodeGen::gen_br() {
auto *branchInst = static_cast<BranchInst *>(context.inst);
if (branchInst->is_cond_br()) {
// TODO6:补全条件跳转操作
// 提示: 根据条件表达式的结果(reg t1 != 0),选择跳转到 true 分支或 false 分支。
// 你可能会用到blt、j等指令
throw not_implemented_error{__FUNCTION__};
// TODO6-------------------end
} else {
// 无条件跳转
auto *branchbb = static_cast<BasicBlock *>(branchInst->get_operand(0));
append_inst("j " + label_name(branchbb)); // 跳转到目标基本块
}
}
void CodeGen::gen_binary() {
// 分别将左右操作数加载到 t0 t1
load_to_greg(context.inst->get_operand(0), Reg::t(0));
load_to_greg(context.inst->get_operand(1), Reg::t(1));
// 根据指令类型生成汇编
switch (context.inst->get_instr_type()) {
case Instruction::add:
output.emplace_back("add t2, t0, t1");
break;
case Instruction::sub:
output.emplace_back("sub t2, t0, t1");
break;
case Instruction::mul:
output.emplace_back("mul t2, t0, t1");
break;
case Instruction::sdiv:
output.emplace_back("div t2, t0, t1");
break;
case Instruction::srem:
output.emplace_back("remw t2, t0, t1");
break;
default:
assert(false);
}
// 将结果填入栈帧中
store_from_greg(context.inst, Reg::t(2));
}
void CodeGen::gen_alloca() {
auto *alloca_inst = static_cast<AllocaInst *>(context.inst);
auto shuzu_offset = context.array_start_offset[alloca_inst];
std::string temp_reg = "t1";
// 加载偏移量到临时寄存器
load_large_int32(shuzu_offset, Reg::t(0));
// 计算栈地址:fp - shuzu_offset
append_inst(SUB + string(" ") + temp_reg + ", fp, t0");
store_from_greg(context.inst, Reg::t(1));
}
void CodeGen::gen_load() {
auto ptr = context.inst->get_operand(0);//在指针类型auto*和auto没有任何区别
auto *type = context.inst->get_type();
load_to_greg(ptr, Reg::t(0));
std::string sreg ="t0";
if (type->is_float_type()) {
std::string dest="ft0";
append_inst(FLOAD_SINGLE,{dest, sreg, "0"});//ft0=M[t0+0]
store_from_freg(context.inst, FReg::ft(0));
} else {
// TODO3: 补全load整型变量的情况,考虑int1 int32 int64
throw not_implemented_error{__FUNCTION__};
// TODO3----------------end
}
}
void CodeGen::gen_store() {
auto *type = context.inst->get_operand(0)->get_type();//怎么store取决于我们要存的数据是什么类型
auto *ptr = context.inst->get_operand(1);//位置
auto *data = context.inst->get_operand(0);//要存入的值
load_to_greg(ptr, Reg::t(1));
auto pst_reg=std::string("t1");
if (type->is_float_type()) {
load_to_freg(data, FReg::ft(0));
append_inst(FSTORE_SINGLE ,{"ft0", pst_reg , "0"});//M[t1+0]=ft0
} else {
if(type->is_int1_type()){
load_to_greg(data, Reg::t(0));
append_inst("sb "+std::string("t0")+", "+ "0("+pst_reg+")");//M[t1+0]=t0
}else if(type->is_int32_type()){
load_to_greg(data, Reg::t(0));
append_inst("sw "+std::string("t0")+", "+ "0("+pst_reg+")");
}else{
load_to_greg(data, Reg::t(0));
append_inst("sd "+std::string("t0")+", "+ "0("+pst_reg+")");
}
}
}
void CodeGen::gen_icmp() {
//这个指令有两个参数,就是两个参与运算的参数
auto sreg0=std::string("t0");
auto sreg1=std::string("t1");
load_to_greg(context.inst->get_operand(0), Reg::t(0)); // Operand 1
load_to_greg(context.inst->get_operand(1), Reg::t(1)); // Operand 2
auto dest_reg = std::string("t0");
// 根据指令类型生成汇编
switch (context.inst->get_instr_type()) {
case Instruction::eq:
append_inst("slt s11,"+sreg1+","+sreg0);
append_inst("slt t0,"+sreg0+","+sreg1);
append_inst("or t0,t0,s11");
append_inst("addi s11,zero,1");
append_inst("sub "+dest_reg+",s11,t0");
break;
case Instruction::ne:
append_inst("slt s11,"+sreg1+","+sreg0);
append_inst("slt t0,"+sreg0+","+sreg1);
append_inst("or "+dest_reg+",t0,s11");
break;
case Instruction::gt:
append_inst("slt "+dest_reg+","+sreg1+","+sreg0);
break;
case Instruction::ge:
append_inst("slt "+dest_reg+","+sreg0+","+sreg1);
append_inst("addi s11,zero,1");
append_inst("sub "+dest_reg+",s11,"+dest_reg);
break;
case Instruction::lt:
append_inst("slt "+dest_reg+","+sreg0+","+sreg1);
break;
case Instruction::le:
append_inst("slt "+dest_reg+","+sreg1+","+sreg0);
append_inst("addi s11,zero,1");
append_inst("sub "+dest_reg+",s11,"+dest_reg);
break;
default:
assert(false);
}
store_from_greg(context.inst,Reg::t(0));
}
void CodeGen::gen_fcmp() {
// TODO7: 补全各种浮点型变量比较的情况(对应的Light IR:feq/fne/fgt/fge/flt/fle)
// 提示: 你可能会用到 feq.s、flt.s、fle.s、xori等指令
throw not_implemented_error{__FUNCTION__};
// TODO7----------------end
}
void CodeGen::gen_float_binary() {
auto sreg0=std::string("ft0");
auto sreg1=std::string("ft1");
load_to_freg(context.inst->get_operand(0), FReg::ft(0)); // Operand 1
load_to_freg(context.inst->get_operand(1), FReg::ft(1)); // Operand 2
auto dest_reg = std::string("ft0");
// 根据指令类型生成汇编
switch (context.inst->get_instr_type()) {
case Instruction::fadd:
output.emplace_back("fadd.s "+dest_reg+","+sreg0+","+sreg1);
break;
case Instruction::fsub:
output.emplace_back("fsub.s "+dest_reg+","+sreg0+","+sreg1);
break;
case Instruction::fmul:
output.emplace_back("fmul.s "+dest_reg+","+sreg0+","+sreg1);
break;
case Instruction::fdiv:
output.emplace_back("fdiv.s "+dest_reg+","+sreg0+","+sreg1);
break;
default:
assert(false);
}
// 将结果填入栈帧中
store_from_freg(context.inst,FReg::ft(0));
}
void CodeGen::gen_zext() {
auto sreg0=std::string("t0");
auto dest_reg = std::string("t0");
auto *type = context.inst->get_type();
if (type->is_float_type()) {
sreg0=std::string("ft0");
load_to_freg(context.inst->get_operand(0), FReg::ft(0)); // Operand 1
dest_reg=std::string("ft0");
append_inst(GR2FR + string(" ")+sreg0+","+dest_reg);//放到合适的位置
} else {
load_to_greg(context.inst->get_operand(0), Reg::t(0)); // Operand 1
if (type->is_int8_type()) {
append_inst("andi " + dest_reg + ", " + sreg0 + ", 0xff");
} else if (type->is_int16_type()) {
append_inst("andi " + dest_reg + ", " + sreg0 + ", 0xffff");
}else if(sreg0!=dest_reg){
append_inst("add "+dest_reg+", zero, "+sreg0);
}
}
if(type->is_float_type()){
store_from_freg(context.inst,FReg::ft(0));
}else{
store_from_greg(context.inst,Reg::t(0));
}
}
void CodeGen::gen_call() {
auto *callInst = static_cast<CallInst *>(context.inst);
auto retType = callInst->get_function_type()->get_return_type();
int gregs = 0; // 通用寄存器参数计数器
int fregs = 0; // 浮点寄存器参数计数器
// 处理函数参数,按照类型加载到相应的寄存器
for (auto& arg : callInst->get_operands()) {
auto argType = arg->get_type();
if (argType->is_float_type()) {
load_to_freg(arg, FReg::fa(fregs++)); // 加载到浮点寄存器
} else if (argType->is_pointer_type() || argType->is_integer_type()) {
load_to_greg(arg, Reg::a(gregs++)); // 加载到通用寄存器
}
}
// 生成函数调用指令
append_inst("jal " + callInst->get_operand(0)->get_name());
// 根据返回值类型选择寄存器存储返回值
if (retType->is_float_type()) {
store_from_freg(callInst, FReg::fa(0)); // 浮点返回值
} else if (retType->is_integer_type()) {
store_from_greg(callInst, Reg::a(0)); // 整数返回值
}
}
/*
* %op = getelementptr [10 x i32], [10 x i32]* %op, i32 0, i32 %op //多维数组访问
* %op = getelementptr i32, i32* %op, i32 %op //一维数组/直接访问指针
*
* Memory layout
* - ^
* +-----------+ | 低地址
* | arg ptr |---+ | //arg ptr 是你传给 GEP 的起始指针(基准地址)
* +-----------+ | |
* | | | |
* +-----------+ / |
* | |<-- |
* | | \ |
* | | | | //Array 是连续内存的数组区域,GEP 会根据偏移量在这里面计算具体元素地址。
* | Array | | |
* | | | |
* | | | |
* | | | |
* +-----------+ | |
* | Pointer |---+ | //Pointer 表示计算完地址后的结果,即 GEP 的结果(往往是你要访问或存储的内存地址)。
* +-----------+ |
* | | |
* +-----------+ |
* | | |
* +-----------+ |
* | | |
* +-----------+ | 高地址
* +
*/
void CodeGen::gen_gep() {
auto *gepInst = static_cast<GetElementPtrInst *>(context.inst);
int len=gepInst->get_num_operand(); // 操作数个数,包含指针 + 若干维度的下标
std::vector<Value *> ops=gepInst->get_operands(); // 获取所有操作数
//拿到基准地址->拿到值->基准地址修改一下->存回去
if(len>=3){
// TODO9: 完善多维数组地址计算,形如 a[i][j] 、 a[i][j][k]等形式的访问
// 提示:1. 操作数从第二个开始处理即可,第一个操作数是基准指针,后面的操作数都表示下标,
// 2. 具体可以用context.inst->get_operand(j)获取第j+1个操作数。
// 3. 依次处理每一维度下标,将其乘以对应元素大小,累加偏移量。
// 需要考虑元素大小超过imm12范围的情况,比如int a[2][300][300];时,
// 处理第一维时,每个 a[i] 是一个 300x300 的二维数组,共 360000 字节,超过imm12
// 4. 将偏移量加到基准指针上,得到最终地址。并存入当前指令目标变量对应的栈帧位置
throw not_implemented_error{__FUNCTION__};
// TODO9-------------------end
}else{//形如a[i]的访问,或访问指针
auto dest_reg=std::string("t0");
auto *ptr = context.inst->get_operand(0); // 指针
auto ptr_reg=std::string("t1");
load_to_greg(ptr, Reg::t(1)); // 加载基准地址
auto *idx = context.inst->get_operand(1); // 下标
auto idx_reg=std::string("t0");//这个是常数,也就是数组下标
load_to_greg(idx, Reg::t(0)); // 加载下标值
// 以下三条指令实现乘以 4(即元素大小为 4 的简化情况):
append_inst("add s11,"+idx_reg+" , "+idx_reg); // s11 = 2 * idx
append_inst("add s11,s11,s11");// s11 = 4 * idx
// t0 = ptr_reg + s11,即最终地址 = 原始地址 + 偏移
append_inst("add "+dest_reg+",s11,"+ptr_reg);
//把t0里存的最终地址存回栈帧的对应位置
//比如当前IR是 %op0 = getelementptr xxxxxx ,那这里就是把计算得到的地址存到%op0在栈帧中的位置
store_from_greg(context.inst, Reg::t(0));
}
}
void CodeGen::gen_sitofp() {
auto *itfInst = static_cast<SiToFpInst *>(context.inst);
std::vector<Value *> ops=itfInst->get_operands();
auto sreg0=std::string("t0");
load_to_greg(context.inst->get_operand(0),Reg::t(0));
auto dest_reg= std::string("ft0");
append_inst(GR2FR ,{dest_reg, sreg0});
store_from_freg(context.inst,FReg::ft(0));
}
void CodeGen::gen_fptosi() {
// TODO8: 浮点数转向整数,注意向下取整(rtz),你可能会用到指令fcvt.w.s
throw not_implemented_error{__FUNCTION__};
// TODO8--------------------end
}
void CodeGen::global_array_int(ConstantArray * init_val){//全局整型数组变量
/*示例输出 int a[5]={0,1,2,3,4};
.data
.globl a
.align 3
.type a, @object
.size a, 20
a:
.word 0
.word 1
.word 2
.word 3
.word 4
*/
for (unsigned i = 0; i < init_val->get_size_of_array(); i++)
{//获得这一层的大小
Constant *element = init_val->get_element_value(i);
if (!dynamic_cast<ConstantArray *>(element))
{//这个元素已经不再是array了
auto *IntVal = static_cast<ConstantInt *>(element);
append_inst(".word", {std::to_string(IntVal->get_value())},
ASMInstruction::Atrribute);
}else{
//这个元素依然是array,递归下去
auto new_array=static_cast<ConstantArray *>(element);
global_array_int(new_array);
}
}
}
void CodeGen::global_array_float(ConstantArray * init_val){
/*示例输出 float a[3]={1.01,4.11,13.99};
.data
.globl a
.align 3
.type a, @object
.size a, 12
a:
.word 1065437102 //float 1.01
.word 1082361119 //float 4.11
.word 1096800010 //float 13.99
*/
// TODO5-2:完善浮点型全局数组变量初始化
// 提示:可以参考global_array_int的实现
throw not_implemented_error{__FUNCTION__};
// TODO5-2------------------end
}
void CodeGen::run() {
// 确保每个函数中基本块的名字都被设置好
m->set_print_name();
/* 使用 GNU 伪指令为全局变量分配空间
* 你可以使用 `la` 指令将标签 (全局变量) 的地址载入寄存器中, 比如
* 要将 `a` 的地址载入 t0, 只需要 `la t0, a`
* 由于在IR自动化生成阶段,我们为无初始值的全局变量分配了0作为初始值,因此在目标代码生成阶段,全局变量都有初始值
*/
if (!m->get_global_variable().empty()) {
append_inst("Global variables", ASMInstruction::Comment);
/*
* 虽然可以使用 `.bss` 伪指令为未初始化数据分配空间,
* 我们依然显式指定 `.data` 段,这是因为:
*
* - `.data` 更加通用,与标准 RISC-V 编译器行为一致;
* - `.bss` 虽然常用于未初始化数据,但某些旧版本 GNU 汇编器对其支持不完善;
* - 显式使用 `.data` 能更好地控制输出段结构。
*/
append_inst(".text", ASMInstruction::Atrribute);
append_inst(".data",ASMInstruction::Atrribute);
for (auto &global : m->get_global_variable()) { //给全局变量分配空间
if(global.get_type()->get_pointer_element_type()->is_integer_type()){//处理整数型全局变量
auto *IntVal = static_cast<ConstantInt *>(global.get_init());
/* 输出形式示例:
.globl a
.align 2
.type a, @object
.size a, 4
a:
.word 5
*/
auto size =
global.get_type()->get_pointer_element_type()->get_size();
append_inst(".globl", {global.get_name()},
ASMInstruction::Atrribute);
append_inst(".align 2",
ASMInstruction::Atrribute); // 对齐到 4 字节
append_inst(".type", {global.get_name(), "@object"},
ASMInstruction::Atrribute);
append_inst(".size", {global.get_name(), std::to_string(size)},
ASMInstruction::Atrribute);
append_inst(global.get_name(), ASMInstruction::Label);
append_inst(".word", {std::to_string(IntVal->get_value())},
ASMInstruction::Atrribute);
}else if(global.get_type()->get_pointer_element_type()->is_array_type()){ //处理数组类型全局变量
/* 输出形式示例:
.globl a
.data
.align 3
.type a, @object
.size a, 20
a:
.word 0
.word 1
.word 2
.word 3
.word 4
*/
if(global.get_type()->get_pointer_element_type()->get_array_element_type()->is_integer_type()){ //整型数组
auto size =
global.get_type()->get_pointer_element_type()->get_size();
append_inst(".globl", {global.get_name()},
ASMInstruction::Atrribute);
append_inst(".align 3",
ASMInstruction::Atrribute); // 对齐到 8 字节
append_inst(".type", {global.get_name(), "@object"},
ASMInstruction::Atrribute);
append_inst(".size", {global.get_name(), std::to_string(size)},
ASMInstruction::Atrribute);
append_inst(global.get_name(), ASMInstruction::Label);
if(dynamic_cast<ConstantZero *>(global.get_init())){
// 初始化值为 0,使用 `.space` 节省空间
append_inst(".space", {std::to_string(size)}, ASMInstruction::Atrribute);
}else{
//如果不是0
auto *IntVal = static_cast<ConstantArray *>(global.get_init());
global_array_int(IntVal);
}
}else{ //浮点型数组
// TODO5-1:完善浮点型全局数组变量声明及其初始化
// 提示:你可能需要将初始化值不为0的浮点型全局数组变量的处理逻辑封装到global_array_float函数中,因此可能需要完成TODO5-2,即填充global_array_float函数
// 当然你也可以直接在当前else分支内处理,弃用global_array_float函数(对应的TODO5-2也不用完成)
throw not_implemented_error{__FUNCTION__};
// TODO5-1------------------end
}
}else if(global.get_type()->get_pointer_element_type()->is_float_type()){ //浮点型全局变量
/* 输出形式示例: float a=1.01;
.globl a
.align 2
.type a, @object
.size a, 4
a:
.word 1065437102 // float 1.01
*/
// TODO4:完善浮点型全局变量声明及其初始化
// 提示:RISC-V 中没有 .float 指令,需手动将 float 转换为 int 再用 .word 表示原始比特位
// 可以使用 reinterpret_cast<int&>(float) 实现 float → int 的位级转换
throw not_implemented_error{__FUNCTION__};
// TODO4--------------------------end
}
}
}
// 函数代码段
append_inst(".text", ASMInstruction::Atrribute);
append_inst(".align 2",ASMInstruction::Atrribute);
for (auto &func : m->get_functions()) {
if (not func.is_declaration()) {
// 更新 context
context.clear();
context.func = &func;
// 函数信息
append_inst(".globl", {func.get_name()}, ASMInstruction::Atrribute);
append_inst(".type", {func.get_name(), "@function"},
ASMInstruction::Atrribute);
append_inst(func.get_name(), ASMInstruction::Label);
// 分配函数栈帧
allocate();
// 生成 prologue
gen_prologue();
//处理bb
for (auto &bb : func.get_basic_blocks()) {
context.bb = &bb;
append_inst(label_name(context.bb), ASMInstruction::Label);
for (auto &instr : bb.get_instructions()) {
// For debug
append_inst(instr.print(), ASMInstruction::Comment);
context.inst = &instr; // 更新 context
switch (instr.get_instr_type()) {
case Instruction::ret:
gen_ret();
break;
case Instruction::br:
gen_br();
break;
case Instruction::add:
case Instruction::sub:
case Instruction::mul:
case Instruction::sdiv:
case Instruction::srem:
gen_binary();
break;
case Instruction::fadd:
case Instruction::fsub:
case Instruction::fmul:
case Instruction::fdiv:
gen_float_binary();
break;
case Instruction::alloca:
gen_alloca();
break;
case Instruction::load:
gen_load();
break;
case Instruction::store:
gen_store();
break;
case Instruction::ge:
case Instruction::gt:
case Instruction::le:
case Instruction::lt:
case Instruction::eq:
case Instruction::ne:
gen_icmp();
break;
case Instruction::fge:
case Instruction::fgt:
case Instruction::fle:
case Instruction::flt:
case Instruction::feq:
case Instruction::fne:
gen_fcmp();
break;
case Instruction::phi:
break;
case Instruction::call:
gen_call();
break;
case Instruction::getelementptr:
gen_gep();
break;
case Instruction::zext:
gen_zext();
break;
case Instruction::fptosi:
gen_fptosi();
break;
case Instruction::sitofp:
gen_sitofp();
break;
default:
assert(false && "Unhandled instruction type");
}
}
}
// 生成 epilogue
gen_epilogue();
}
}
}
std::string CodeGen::print() const {
std::string result;
for (const auto &inst : output) {
result += inst.format();
}
auto sub = result.find("memset_int");
while (sub != string::npos) {
result.replace(sub, 10, "memset");
sub = result.find("memset_int");
}
sub = result.find("memset_float");
while (sub != string::npos) {
result.replace(sub, 12, "memset");
sub = result.find("memset_float");
}
return result;
}