Files
Interpreter/tests/src/codegen_test.cpp
T
Admin 6f1bf61c7f 12.8 切片 4:CMP_* 与 IF/ELSIF/ELSE 跳转。
- 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
2026-08-21 11:43:23 +08:00

413 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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. 用例 02BOOL 赋值(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 11 = 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. 用例 09GVL + 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_0io.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 := IINT 偏移 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 bMOVE 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 bMOVE 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. 用例 04IF / ELSIF / ELSECMP + 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 THENsel→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;
}