- CMP_xx:两侧求值到临时后比较;IF 编成 <cond→t> JF t, L_next <body> JMP L_end 链 - 分支尾部 JMP 与条件 JF 双回填;无 else 时末分支 JF 直接落 L_end - codegen_test:89 断言(用例 04 完整跳转序列),ctest 9/9
413 lines
15 KiB
C++
413 lines
15 KiB
C++
/**
|
||
* @file codegen_test.cpp
|
||
* @brief 寄存器码测试(12.8 切片 1):用例 01/02 出映像
|
||
* @author
|
||
* @date 2026-08-21
|
||
*/
|
||
|
||
#include <cstdio>
|
||
#include <cstring>
|
||
#include <filesystem>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#include "compiler/Codegen.h"
|
||
#include "compiler/Linker.h"
|
||
#include "compiler/Project.h"
|
||
#include "compiler/Typecheck.h"
|
||
#include "isa/Encode.h"
|
||
#include "isa/Image.h"
|
||
|
||
#ifndef REPO_ROOT
|
||
#define REPO_ROOT "."
|
||
#endif
|
||
|
||
static int g_checks = 0;
|
||
|
||
#define CHECK(cond) \
|
||
do { \
|
||
if (!(cond)) { \
|
||
std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
||
return false; \
|
||
} \
|
||
++g_checks; \
|
||
} while (0)
|
||
|
||
// 从用例目录走完整编译管线(解析 → 链接 → 类型检查 → 代码生成)
|
||
static bool compile_case(const char* dir, std::vector<uint8_t>* image, std::string* err) {
|
||
using namespace compiler;
|
||
const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml";
|
||
Project p;
|
||
if (!parse_project(toml, &p, err)) {
|
||
return false;
|
||
}
|
||
std::vector<SourceUnit> units;
|
||
for (const std::string& f : compile_files(p)) {
|
||
SourceUnit u;
|
||
if (!load_unit(p.base_dir + "/" + f, &u, err)) {
|
||
return false;
|
||
}
|
||
units.push_back(std::move(u));
|
||
}
|
||
LinkResult link;
|
||
if (!link_project(p, units, &link, err)) {
|
||
return false;
|
||
}
|
||
if (!check_project(p, units, link, err)) {
|
||
return false;
|
||
}
|
||
return codegen_project(p, units, link, image, err);
|
||
}
|
||
|
||
// 从临时工程(globals.st + main.st + 可含 io 绑定)走完整管线
|
||
static bool compile_src(const char* name, const char* io_extra, const char* globals_st,
|
||
const char* main_st, std::vector<uint8_t>* image,
|
||
std::string* err) {
|
||
using namespace compiler;
|
||
const std::string dir = std::string(REPO_ROOT) + "/build/cg_tmp_" + name;
|
||
std::filesystem::remove_all(dir);
|
||
std::filesystem::create_directories(dir);
|
||
const std::string toml_path = dir + "/project.toml";
|
||
std::FILE* f = std::fopen(toml_path.c_str(), "w");
|
||
std::fprintf(f, "[project]\nname = \"cg\"\nentry = \"program MAIN\"\n"
|
||
"cycle_limit = 1000\ndt_ms = 10\n"
|
||
"[files]\nst = [\"globals.st\", \"main.st\"]\n"
|
||
"[gvl]\nfile = \"globals.st\"\n%s", io_extra);
|
||
std::fclose(f);
|
||
f = std::fopen((dir + "/globals.st").c_str(), "w");
|
||
std::fputs(globals_st, f);
|
||
std::fclose(f);
|
||
f = std::fopen((dir + "/main.st").c_str(), "w");
|
||
std::fputs(main_st, f);
|
||
std::fclose(f);
|
||
|
||
Project p;
|
||
if (!parse_project(toml_path, &p, err)) {
|
||
return false;
|
||
}
|
||
std::vector<SourceUnit> units;
|
||
for (const std::string& file : compile_files(p)) {
|
||
SourceUnit u;
|
||
if (!load_unit(p.base_dir + "/" + file, &u, err)) {
|
||
return false;
|
||
}
|
||
units.push_back(std::move(u));
|
||
}
|
||
LinkResult link;
|
||
if (!link_project(p, units, &link, err)) {
|
||
return false;
|
||
}
|
||
if (!check_project(p, units, link, err)) {
|
||
return false;
|
||
}
|
||
return codegen_project(p, units, link, image, err);
|
||
}
|
||
|
||
// ---- 1. 用例 01:空 MAIN + RET ----
|
||
|
||
static bool test_case01() {
|
||
std::vector<uint8_t> img;
|
||
std::string err;
|
||
CHECK(compile_case("01_empty_main", &img, &err));
|
||
|
||
const isa::ImageView v = isa::ImageView::from(img);
|
||
CHECK(v.ok());
|
||
CHECK(v.header().cycle_limit == 1000);
|
||
CHECK(v.header().dt_ms == 10);
|
||
CHECK(v.header().entry_fn_id == 0);
|
||
CHECK(v.header().n_funcs == 1);
|
||
CHECK(v.header().n_consts == 0);
|
||
CHECK(v.header().n_globals == 0);
|
||
CHECK(v.header().project_hash != isa::kFnvBasis);
|
||
|
||
const isa::FuncRow r = v.func_row(0);
|
||
CHECK(r.nregs == 0);
|
||
CHECK(r.code_len == 1);
|
||
|
||
char buf[32];
|
||
isa::disasm(v.code_bytes()[0], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "RET") == 0);
|
||
return true;
|
||
}
|
||
|
||
// ---- 2. 用例 02:BOOL 赋值(TRUE / FALSE → LOADK)----
|
||
|
||
static bool test_case02() {
|
||
std::vector<uint8_t> img;
|
||
std::string err;
|
||
CHECK(compile_case("02_bool_assign", &img, &err));
|
||
|
||
const isa::ImageView v = isa::ImageView::from(img);
|
||
CHECK(v.ok());
|
||
CHECK(v.header().n_funcs == 1);
|
||
CHECK(v.header().n_consts == 2); // TRUE 与 FALSE
|
||
|
||
const isa::FuncRow r = v.func_row(0);
|
||
CHECK(r.nregs == 2); // a, b
|
||
CHECK(r.code_len == 3);
|
||
|
||
// 从字节码段取指令(按函数行 code_offset 定位)
|
||
const uint8_t* base = v.code_bytes();
|
||
char buf[64];
|
||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[0], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "LOADK r0, 0") == 0); // a := TRUE(常量 0)
|
||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[1], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "LOADK r1, 1") == 0); // b := FALSE(常量 1)
|
||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[2], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "RET") == 0);
|
||
|
||
// 常量表:0 = BOOL 1,1 = BOOL 0
|
||
const isa::ConstEntry c0 = v.const_entry(0);
|
||
CHECK(c0.tag == isa::types::Bool && c0.value == 1);
|
||
const isa::ConstEntry c1 = v.const_entry(1);
|
||
CHECK(c1.tag == isa::types::Bool && c1.value == 0);
|
||
return true;
|
||
}
|
||
|
||
// ---- 3. 用例 09:GVL + VAR_EXTERNAL(全局读取 LOAD_GLOBAL + 初值数据段)----
|
||
|
||
static bool test_case09() {
|
||
std::vector<uint8_t> img;
|
||
std::string err;
|
||
CHECK(compile_case("09_gvl_external", &img, &err));
|
||
|
||
const isa::ImageView v = isa::ImageView::from(img);
|
||
CHECK(v.ok());
|
||
CHECK(v.header().n_globals == 1);
|
||
CHECK(v.data_len() == 2); // INT 2 字节
|
||
CHECK(v.data_bytes()[0] == 5); // G1 初值 5(小端低位)
|
||
CHECK(v.data_bytes()[1] == 0);
|
||
|
||
const isa::FuncRow r = v.func_row(0);
|
||
CHECK(r.nregs == 1); // 仅局部 x
|
||
CHECK(r.code_len == 2);
|
||
|
||
char buf[64];
|
||
const uint8_t* base = v.code_bytes();
|
||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[0], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r0, 0") == 0); // x := G1(槽 0 偏移 0)
|
||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[1], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "RET") == 0);
|
||
return true;
|
||
}
|
||
|
||
// ---- 4. io 绑定:LOAD_I / STORE_Q ----
|
||
|
||
static bool test_io_ops() {
|
||
std::vector<uint8_t> img;
|
||
std::string err;
|
||
const char* io_extra =
|
||
"[[io.input]]\nvar = \"I0_0\"\nchannel = 0\nbit = 0\n"
|
||
"[[io.output]]\nvar = \"Q0_0\"\nchannel = 0\nbit = 0\n";
|
||
const char* globals_st =
|
||
"VAR_GLOBAL\n I0_0 : BOOL;\n Q0_0 : BOOL;\nEND_VAR\n";
|
||
const char* main_st =
|
||
"PROGRAM MAIN\nVAR\n q : BOOL;\nEND_VAR\n"
|
||
" q := I0_0;\n"
|
||
" Q0_0 := q;\n"
|
||
"END_PROGRAM\n";
|
||
CHECK(compile_src("io", io_extra, globals_st, main_st, &img, &err));
|
||
|
||
const isa::ImageView v = isa::ImageView::from(img);
|
||
CHECK(v.ok());
|
||
CHECK(v.header().n_globals == 2);
|
||
|
||
const isa::FuncRow r = v.func_row(0);
|
||
CHECK(r.nregs == 2); // q + 1 临时
|
||
|
||
char buf[64];
|
||
const uint8_t* base = v.code_bytes();
|
||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[0], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "LOAD_I r0, 0") == 0); // q := I0_0(io.input → LOAD_I)
|
||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[1], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "MOVE r1, r0") == 0); // 临时 ← q
|
||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[2], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "STORE_Q r1, 1") == 0); // Q0_0 := 临时(io.output → STORE_Q)
|
||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[3], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "RET") == 0);
|
||
return true;
|
||
}
|
||
|
||
// ---- 5. 混合类型布局:BOOL@0 INT@2 TIME@8,初值入数据段 ----
|
||
|
||
static bool test_layout() {
|
||
std::vector<uint8_t> img;
|
||
std::string err;
|
||
const char* globals_st =
|
||
"VAR_GLOBAL\n"
|
||
" B : BOOL;\n"
|
||
" I : INT := 300;\n"
|
||
" T : TIME := T#10ms;\n"
|
||
"END_VAR\n";
|
||
const char* main_st =
|
||
"PROGRAM MAIN\nVAR\n x : INT;\nEND_VAR\n"
|
||
" x := I;\n"
|
||
"END_PROGRAM\n";
|
||
CHECK(compile_src("layout", "", globals_st, main_st, &img, &err));
|
||
|
||
const isa::ImageView v = isa::ImageView::from(img);
|
||
CHECK(v.ok());
|
||
CHECK(v.header().n_globals == 3);
|
||
CHECK(v.data_len() == 16); // 0 + 2 + 8(对齐)= 16
|
||
CHECK(v.data_bytes()[0] == 0); // B 无初值
|
||
CHECK(v.data_bytes()[2] == 0x2C && v.data_bytes()[3] == 0x01); // I = 300
|
||
CHECK(v.data_bytes()[8] == 10); // T = 10ms
|
||
|
||
char buf[64];
|
||
isa::disasm(reinterpret_cast<const uint32_t*>(v.code_bytes())[0], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "LOAD_GLOBAL r0, 2") == 0); // x := I(INT 偏移 2)
|
||
return true;
|
||
}
|
||
|
||
// ---- 6. 写 io.input → codegen error ----
|
||
|
||
static bool test_write_input() {
|
||
std::vector<uint8_t> img;
|
||
std::string err;
|
||
const char* io_extra =
|
||
"[[io.input]]\nvar = \"I0_0\"\nchannel = 0\nbit = 0\n";
|
||
const char* globals_st =
|
||
"VAR_GLOBAL\n I0_0 : BOOL;\nEND_VAR\n";
|
||
const char* main_st =
|
||
"PROGRAM MAIN\n I0_0 := TRUE;\nEND_PROGRAM\n";
|
||
if (compile_src("winput", io_extra, globals_st, main_st, &img, &err)) {
|
||
std::printf("FAIL write_input: codegen passed\n");
|
||
return false;
|
||
}
|
||
CHECK(err.find("codegen error") == 0);
|
||
CHECK(err.find("cannot write to input") != std::string::npos);
|
||
return true;
|
||
}
|
||
|
||
// ---- 7. 用例 03:短路 AND / OR 必须编跳转 ----
|
||
|
||
static bool test_case03() {
|
||
std::vector<uint8_t> img;
|
||
std::string err;
|
||
CHECK(compile_case("03_short_circuit", &img, &err));
|
||
|
||
const isa::ImageView v = isa::ImageView::from(img);
|
||
CHECK(v.ok());
|
||
const isa::FuncRow r = v.func_row(0);
|
||
CHECK(r.nregs == 4); // a b x + 1 临时
|
||
CHECK(r.code_len == 9);
|
||
|
||
char buf[64];
|
||
const uint8_t* base = v.code_bytes();
|
||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
|
||
// x := a AND b:MOVE r2,r0 / JF r2,+2 / MOVE r3,r1 / MOVE r2,r3
|
||
isa::disasm(ins[0], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "MOVE r2, r0") == 0);
|
||
isa::disasm(ins[1], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "JF r2, +2") == 0);
|
||
isa::disasm(ins[2], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "MOVE r3, r1") == 0);
|
||
isa::disasm(ins[3], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "MOVE r2, r3") == 0);
|
||
// x := a OR b:MOVE r2,r0 / JT r2,+2 / MOVE r3,r1 / MOVE r2,r3
|
||
isa::disasm(ins[4], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "MOVE r2, r0") == 0);
|
||
isa::disasm(ins[5], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "JT r2, +2") == 0);
|
||
isa::disasm(ins[6], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "MOVE r3, r1") == 0);
|
||
isa::disasm(ins[7], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "MOVE r2, r3") == 0);
|
||
isa::disasm(ins[8], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "RET") == 0);
|
||
return true;
|
||
}
|
||
|
||
// ---- 8. NOT ----
|
||
|
||
static bool test_not() {
|
||
std::vector<uint8_t> img;
|
||
std::string err;
|
||
const char* main_st =
|
||
"PROGRAM MAIN\nVAR\n a, x : BOOL;\nEND_VAR\n"
|
||
" x := NOT a;\n"
|
||
"END_PROGRAM\n";
|
||
CHECK(compile_src("not", "", "", main_st, &img, &err));
|
||
|
||
const isa::ImageView v = isa::ImageView::from(img);
|
||
CHECK(v.ok());
|
||
const isa::FuncRow r = v.func_row(0);
|
||
CHECK(r.nregs == 3); // a x + 1 临时
|
||
CHECK(r.code_len == 3);
|
||
|
||
char buf[64];
|
||
const uint8_t* base = v.code_bytes();
|
||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
|
||
isa::disasm(ins[0], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "MOVE r2, r0") == 0); // 临时 ← a
|
||
isa::disasm(ins[1], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "NOT r1, r2") == 0); // x := NOT 临时
|
||
isa::disasm(ins[2], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "RET") == 0);
|
||
return true;
|
||
}
|
||
|
||
// ---- 9. 用例 04:IF / ELSIF / ELSE(CMP + JF + JMP 回填)----
|
||
|
||
static bool test_case04() {
|
||
std::vector<uint8_t> img;
|
||
std::string err;
|
||
CHECK(compile_case("04_if_elsif_else", &img, &err));
|
||
|
||
const isa::ImageView v = isa::ImageView::from(img);
|
||
CHECK(v.ok());
|
||
const isa::FuncRow r = v.func_row(0);
|
||
CHECK(r.nregs == 5); // sel out + 3 临时(CMP 目的 + 两操作数)
|
||
CHECK(r.code_len == 14);
|
||
|
||
char buf[64];
|
||
const uint8_t* base = v.code_bytes();
|
||
const uint32_t* ins = reinterpret_cast<const uint32_t*>(base);
|
||
// IF sel = 0 THEN:sel→r3、0→r4、CMP_EQ r2(常量序:0、10、1、20、30)
|
||
isa::disasm(ins[0], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "MOVE r3, r0") == 0);
|
||
isa::disasm(ins[1], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "LOADK r4, 0") == 0);
|
||
isa::disasm(ins[2], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "CMP_EQ r2, r3, r4") == 0);
|
||
isa::disasm(ins[3], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "JF r2, +2") == 0); // 假 → elsif([6])
|
||
isa::disasm(ins[4], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "LOADK r1, 1") == 0); // out := 10(常量 1)
|
||
isa::disasm(ins[5], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "JMP +7") == 0); // → end([13])
|
||
// ELSIF sel = 1
|
||
isa::disasm(ins[6], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "MOVE r3, r0") == 0);
|
||
isa::disasm(ins[7], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "LOADK r4, 2") == 0);
|
||
isa::disasm(ins[8], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "CMP_EQ r2, r3, r4") == 0);
|
||
isa::disasm(ins[9], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "JF r2, +2") == 0); // 假 → else([12])
|
||
isa::disasm(ins[10], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "LOADK r1, 3") == 0);
|
||
isa::disasm(ins[11], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "JMP +1") == 0); // → end([13])
|
||
// ELSE
|
||
isa::disasm(ins[12], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "LOADK r1, 4") == 0);
|
||
isa::disasm(ins[13], buf, sizeof buf);
|
||
CHECK(std::strcmp(buf, "RET") == 0);
|
||
return true;
|
||
}
|
||
|
||
int main() {
|
||
if (!test_case01()) return 1;
|
||
if (!test_case02()) return 1;
|
||
if (!test_case09()) return 1;
|
||
if (!test_io_ops()) return 1;
|
||
if (!test_layout()) return 1;
|
||
if (!test_write_input()) return 1;
|
||
if (!test_case03()) return 1;
|
||
if (!test_not()) return 1;
|
||
if (!test_case04()) return 1;
|
||
std::printf("codegen_test: %d checks passed\n", g_checks);
|
||
return 0;
|
||
}
|