From 66359a0a26863c5aede7df00d38fb8a15ead6cbf Mon Sep 17 00:00:00 2001 From: chentianya Date: Fri, 21 Aug 2026 11:32:31 +0800 Subject: [PATCH] =?UTF-8?q?12.8=20=E5=88=87=E7=89=87=201=EF=BC=9A=E5=AF=84?= =?UTF-8?q?=E5=AD=98=E5=99=A8=E7=A0=81=E9=AA=A8=E6=9E=B6=EF=BC=88MOVE/LOAD?= =?UTF-8?q?K/RET=EF=BC=89=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Codegen.h/cpp:帧寄存器(0 起声明序)、常量表、函数表、小端映像拼装 - 切片 1 范围:PROGRAM 标量 + 字面量 LOADK + 变量 MOVE + RET;其余构造报 codegen error - codegen_test:23 断言(用例 01 空 MAIN 出 RET、02 LOADK r0/r1 + 常量表),ctest 9/9 --- compiler/CMakeLists.txt | 3 +- compiler/include/compiler/Codegen.h | 25 +++ compiler/src/Codegen.cpp | 321 ++++++++++++++++++++++++++++ tests/CMakeLists.txt | 11 + tests/src/codegen_test.cpp | 127 +++++++++++ 5 files changed, 486 insertions(+), 1 deletion(-) create mode 100644 compiler/include/compiler/Codegen.h create mode 100644 compiler/src/Codegen.cpp create mode 100644 tests/src/codegen_test.cpp diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt index efcffe0..eecfc8e 100644 --- a/compiler/CMakeLists.txt +++ b/compiler/CMakeLists.txt @@ -12,7 +12,8 @@ add_library(compiler STATIC ./src/Project.cpp ./src/Parser.cpp ./src/Linker.cpp - ./src/Typecheck.cpp) + ./src/Typecheck.cpp + ./src/Codegen.cpp) target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include) diff --git a/compiler/include/compiler/Codegen.h b/compiler/include/compiler/Codegen.h new file mode 100644 index 0000000..ee72f53 --- /dev/null +++ b/compiler/include/compiler/Codegen.h @@ -0,0 +1,25 @@ +/** + * @file Codegen.h + * @brief 寄存器码生成(12.8,切片 1:MOVE / LOADK / RET) + * @author + * @date 2026-08-21 + */ + +#pragma once + +#include +#include +#include + +#include "compiler/Linker.h" +#include "compiler/Parser.h" +#include "compiler/Project.h" + +namespace compiler { + + // 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。 + // 失败返回 false,err 前缀 "codegen error"。 + bool codegen_project(const Project& proj, const std::vector& units, + const LinkResult& link, std::vector* image, + std::string* err); +} diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp new file mode 100644 index 0000000..cc5edf3 --- /dev/null +++ b/compiler/src/Codegen.cpp @@ -0,0 +1,321 @@ +/** + * @file Codegen.cpp + * @brief 寄存器码生成(12.8,切片 1:MOVE / LOADK / RET) + * @author + * @date 2026-08-21 + * + * @details 设计说明(详见 Doc/compiler/寄存器码.md): + * - 切片 1 范围:PROGRAM 标量变量帧(r0 起按声明序)、字面量 LOADK、 + * 变量间 MOVE、RET;空 MAIN 出映像。其余构造报 codegen error(后续切片展开) + * - 常量统一进常量表(BOOL/INT/TIME),const_id = 首次出现序 + * - 映像:头(cycle_limit/dt_ms/工程哈希/入口)+ 常量表 + 函数表 + 字节码, + * 数据段为空(无全局/实例) + * + * 函数清单: + * - put_le32 / put_le64 小端写入映像缓冲 + * - Builder::Builder (构造)存工程/源文件/链接结果/输出 + * - Builder::run 逐 POU 建函数 → 拼映像 + * - Builder::fail 组装 "codegen error: " 返回 false + * - build_frame 给 POU 标量变量分配帧寄存器(0 起按声明序) + * - build_function 编译一个 POU 的语句体 + * - compile_stmt 语句编译(切片 1:仅赋值) + * - compile_expr 表达式编译(切片 1:仅字面量 / 变量读) + * - const_id 取常量表 id(无则追加) + * - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + * - codegen_project 对外入口 + */ + +#include "compiler/Codegen.h" + +#include +#include +#include +#include + +#include "isa/Encode.h" +#include "isa/Image.h" +#include "isa/Instr.h" +#include "isa/Types.h" + +namespace compiler { +namespace { + + // ---- 小端写入(Image.cpp 内部实现不可见,这里自带最小版)---- + + void put_le32(std::vector& b, size_t off, uint32_t v) { + b[off + 0] = static_cast(v & 0xFFu); + b[off + 1] = static_cast((v >> 8) & 0xFFu); + b[off + 2] = static_cast((v >> 16) & 0xFFu); + b[off + 3] = static_cast((v >> 24) & 0xFFu); + } + + void put_le64(std::vector& b, size_t off, uint64_t v) { + for (int i = 0; i < 8; ++i) { + b[off + i] = static_cast((v >> (8 * i)) & 0xFFu); + } + } + + /** + * @brief 代码生成器(切片 1) + */ + class Builder { + public: + /** + * @brief 构造生成器 + * @param proj 工程定义(cycle_limit / dt_ms / 哈希用) + * @param units 全部源文件的 AST + * @param link 链接结果(POU 顺序 / 符号) + * @param image 输出映像字节 + * @param err 错误输出;可为 nullptr(静默) + */ + Builder(const Project& proj, const std::vector& units, + const LinkResult& link, std::vector* image, std::string* err) + : proj_(proj), units_(units), link_(link), image_(image), err_(err) {} + + /** + * @brief 逐 POU 建函数后拼映像 + * @return true 成功;false(err 已写,前缀 "codegen error") + */ + bool run() { + for (const LinkResult::PouScope& sc : link_.scopes) { + const POU* pou = find_pou(sc.name); + if (pou == nullptr) { + continue; + } + FuncCtx f; + f.name = sc.name; + if (!build_function(*pou, &f)) { + return false; + } + funcs_.push_back(std::move(f)); + } + return assemble_image(); + } + + private: + /** + * @brief 组装错误消息并返回 false + * @param msg 错误描述(不含前缀) + * @return 恒 false + */ + bool fail(const std::string& msg) { + if (err_) { + *err_ = "codegen error: " + msg; + } + return false; + } + + /** + * @brief 按名查 POU AST + * @param name POU 名(小写) + * @return POU 指针;未找到返回 nullptr + */ + const POU* find_pou(const std::string& name) const { + for (const SourceUnit& u : units_) { + for (const POU& p : u.ast.pous) { + if (p.name == name) { + return &p; + } + } + } + return nullptr; + } + + // 每函数的编译态 + struct FuncCtx { + std::string name; + std::vector code; // 字节码(函数表 code_offset 相对此段) + std::map regs; // 变量名 → 帧寄存器 + uint8_t nregs = 0; // 变量数(切片 1 无临时) + }; + + /** + * @brief 编译一个 POU + * @details 切片 1:仅 PROGRAM + 标量变量(FB 实例/函数留后续切片) + * @param pou POU AST + * @param f 输出函数编译态 + * @return true 成功;false(err 已写) + */ + bool build_function(const POU& pou, FuncCtx* f) { + if (pou.kind != PouKind::Program) { + return fail("only PROGRAM supported in slice 1 ('" + pou.name + "')"); + } + for (const VarBlock& b : pou.blocks) { + for (const VarDecl& d : b.vars) { + if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) { + return fail("FB instance not supported in slice 1 ('" + d.name + "')"); + } + if (f->regs.size() > 255) { + return fail("register overflow in function '" + pou.name + "'"); + } + f->regs[d.name] = f->nregs++; + } + } + for (const Stmt& st : pou.body) { + if (!compile_stmt(*f, st)) { + return false; + } + } + f->code.push_back(isa::enc_ret()); + return true; + } + + /** + * @brief 编译一条语句(切片 1:仅赋值) + * @param f 当前函数 + * @param st 语句 AST + * @return true 成功;false(err 已写) + */ + bool compile_stmt(FuncCtx& f, const Stmt& st) { + if (st.kind != StmtKind::Assign) { + return fail("statement not supported in slice 1"); + } + const auto it = f.regs.find(st.target); + if (it == f.regs.end()) { + return fail("no register for '" + st.target + "'"); + } + const uint8_t rd = it->second; + uint8_t rs = 0; + const ExprKind k = st.value->kind; + if (k == ExprKind::LitBool || k == ExprKind::LitInt || + k == ExprKind::LitTime) { + const isa::types::TypeTag tag = + k == ExprKind::LitBool ? isa::types::Bool + : k == ExprKind::LitInt ? isa::types::Int + : isa::types::Time; + f.code.push_back( + isa::enc_imm(isa::Op::LOADK, rd, const_id(tag, st.value->int_value))); + return true; + } + if (k == ExprKind::VarRef) { + const auto sit = f.regs.find(st.value->name); + if (sit == f.regs.end()) { + return fail("no register for '" + st.value->name + "'"); + } + f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, sit->second)); + return true; + } + return fail("expression not supported in slice 1"); + } + + /** + * @brief 取常量表 id(无则追加) + * @param tag 类型标记(BOOL/INT/TIME) + * @param value 常量值 + * @return const_id(u16) + */ + uint16_t const_id(isa::types::TypeTag tag, int64_t value) { + for (size_t i = 0; i < consts_.size(); ++i) { + if (consts_[i].tag == tag && consts_[i].value == static_cast(value)) { + return static_cast(i); + } + } + consts_.push_back({tag, static_cast(value)}); + return static_cast(consts_.size() - 1); + } + + /** + * @brief 拼映像:头 + 常量表 + 函数表 + 字节码(数据段空) + * @return true 成功;false(err 已写) + */ + bool assemble_image() { + const uint32_t off_const = isa::kHeaderSize; + const uint32_t off_funcs = off_const + + static_cast(consts_.size()) * isa::kConstEntrySize; + uint32_t off_code = off_funcs + + static_cast(funcs_.size()) * isa::kFuncRowSize; + uint32_t code_total = 0; + for (const FuncCtx& f : funcs_) { + code_total += static_cast(f.code.size()) * 4; + } + const uint32_t off_end = off_code + code_total; + + std::vector& b = *image_; + b.assign(off_end, 0); + put_le32(b, 0, isa::kMagic); + put_le32(b, 4, isa::kVersion); + put_le32(b, 8, proj_.cycle_limit); + put_le32(b, 12, proj_.dt_ms); + + uint64_t hash = isa::kFnvBasis; + if (!compute_project_hash(proj_, &hash, err_)) { + return false; + } + put_le64(b, 16, hash); + + uint32_t entry = 0; + for (size_t i = 0; i < funcs_.size(); ++i) { + if (funcs_[i].name == "main") { + entry = static_cast(i); + } + } + put_le32(b, 24, entry); + put_le32(b, 28, 0); // n_globals(切片 1 无全局) + put_le32(b, 32, 0); // n_i + put_le32(b, 36, 0); // n_q + put_le32(b, 40, 0); // n_m + put_le32(b, 44, static_cast(consts_.size())); + put_le32(b, 48, static_cast(funcs_.size())); + put_le32(b, 52, off_const); + put_le32(b, 56, off_funcs); + put_le32(b, 60, off_code); + put_le32(b, 64, off_end); // offset_fb(空) + put_le32(b, 68, off_end); // offset_data(空) + + for (size_t i = 0; i < consts_.size(); ++i) { + const size_t o = off_const + i * isa::kConstEntrySize; + put_le32(b, o, static_cast(consts_[i].tag)); + put_le64(b, o + 4, consts_[i].value); + } + + uint32_t c = off_code; + for (size_t i = 0; i < funcs_.size(); ++i) { + const FuncCtx& f = funcs_[i]; + const size_t o = off_funcs + i * isa::kFuncRowSize; + put_le32(b, o, f.nregs); + put_le32(b, o + 4, 0); // code_offset 相对段起点 = 前序函数长度和 + put_le32(b, o + 8, static_cast(f.code.size())); + for (const isa::Instr in : f.code) { + put_le32(b, c, in); + c += 4; + } + } + // code_offset 回填(相对字节码段起点) + uint32_t acc = 0; + for (size_t i = 0; i < funcs_.size(); ++i) { + const size_t o = off_funcs + i * isa::kFuncRowSize; + put_le32(b, o + 4, acc); + acc += static_cast(funcs_[i].code.size()) * 4; + } + return true; + } + + // ---- 成员 ---- + const Project& proj_; + const std::vector& units_; + const LinkResult& link_; + std::vector* image_; + std::string* err_; + std::vector funcs_; + std::vector consts_; + }; + +} // namespace + +/** + * @brief 编译工程为 .stb 映像字节(对外入口) + * @param proj 工程定义 + * @param units 全部源文件的 AST + * @param link 链接结果(在类型检查成功后调用) + * @param image 输出映像字节 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 失败(err 前缀 "codegen error") + */ +bool codegen_project(const Project& proj, const std::vector& units, + const LinkResult& link, std::vector* image, + std::string* err) { + Builder b(proj, units, link, image, err); + return b.run(); +} + +} // namespace compiler diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 204615e..5ab5034 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -67,3 +67,14 @@ target_compile_definitions(typecheck_test PRIVATE add_test(NAME typecheck_types COMMAND typecheck_test) + +# 寄存器码测试(12.8 切片 1):用例 01/02 出映像(REPO_ROOT 注入源目录绝对路径) +add_executable(codegen_test + ./src/codegen_test.cpp) + +target_link_libraries(codegen_test PRIVATE compiler) +target_compile_definitions(codegen_test PRIVATE + REPO_ROOT="${CMAKE_SOURCE_DIR}") + +add_test(NAME codegen_slice1 + COMMAND codegen_test) diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp new file mode 100644 index 0000000..a0f6751 --- /dev/null +++ b/tests/src/codegen_test.cpp @@ -0,0 +1,127 @@ +/** + * @file codegen_test.cpp + * @brief 寄存器码测试(12.8 切片 1):用例 01/02 出映像 + * @author + * @date 2026-08-21 + */ + +#include +#include +#include +#include + +#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* 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 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); +} + +// ---- 1. 用例 01:空 MAIN + RET ---- + +static bool test_case01() { + std::vector 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 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(base)[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOADK r0, 0") == 0); // a := TRUE(常量 0) + isa::disasm(reinterpret_cast(base)[1], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOADK r1, 1") == 0); // b := FALSE(常量 1) + isa::disasm(reinterpret_cast(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; +} + +int main() { + if (!test_case01()) return 1; + if (!test_case02()) return 1; + std::printf("codegen_test: %d checks passed\n", g_checks); + return 0; +}