diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index cc5edf3..da4f0c1 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -1,32 +1,43 @@ /** * @file Codegen.cpp - * @brief 寄存器码生成(12.8,切片 1:MOVE / LOADK / RET) + * @brief 寄存器码生成(12.8,切片 2:+ 全局 / I/Q/M) * @author * @date 2026-08-21 * * @details 设计说明(详见 Doc/compiler/寄存器码.md): - * - 切片 1 范围:PROGRAM 标量变量帧(r0 起按声明序)、字面量 LOADK、 - * 变量间 MOVE、RET;空 MAIN 出映像。其余构造报 codegen error(后续切片展开) + * - 切片 1:PROGRAM 标量变量帧(r0 起按声明序)、字面量 LOADK、 + * 变量间 MOVE、RET;空 MAIN 出映像。 + * - 切片 2:+ 全局数据区(声明序、对齐 BOOL 1B / INT 2B / TIME 8B、 + * 初值入数据段);读全局 LOAD_GLOBAL / io.input 绑定用 LOAD_I; + * 写全局 STORE_GLOBAL / io.output 绑定用 STORE_Q;写 io.input → 拒绝。 + * 其余构造报 codegen error(后续切片展开) * - 常量统一进常量表(BOOL/INT/TIME),const_id = 首次出现序 - * - 映像:头(cycle_limit/dt_ms/工程哈希/入口)+ 常量表 + 函数表 + 字节码, - * 数据段为空(无全局/实例) + * - 指令 slot = 数据区字节偏移;符号表 address 保持逻辑槽号(12.6 冻结) * * 函数清单: * - 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 对外入口 + * - align_up 按 2 的幂宽度向上对齐 + * - width_of 类型名 → 槽宽(bool 1 / int 2 / time 8) + * - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类 + * - Builder::run 逐 POU 建函数 → 布局数据区 → 拼映像 + * - Builder::fail 组装 "codegen error: " 返回 false + * - find_pou 按名查 POU AST + * - build_function 编译一个 POU 的语句体(帧寄存器分配) + * - compile_stmt 语句编译(切片 2:仅赋值,左值可帧寄存器或全局) + * - compile_expr 表达式编译到寄存器(字面量 / 变量读,含全局 LOAD_*) + * - store_target 赋值左值:帧寄存器直写或 STORE_* 到全局槽 + * - load_op / store_op 按 io 绑定选读取/写入操作码 + * - global_offset 逻辑槽号 → 数据区字节偏移 + * - layout_data 布局全局数据区(对齐 + 初值字节) + * - init_of 查全局声明的初值(AST) + * - const_id 取常量表 id(无则追加) + * - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段 + * - codegen_project 对外入口 */ #include "compiler/Codegen.h" +#include #include #include #include @@ -55,6 +66,27 @@ namespace { } } + /** + * @brief 按 2 的幂宽度向上对齐 + * @param v 当前偏移 + * @param w 对齐宽度(1/2/8) + * @return 对齐后的偏移 + */ + uint32_t align_up(uint32_t v, uint32_t w) { + return (v + w - 1) & ~(w - 1); + } + + /** + * @brief 类型名 → 槽宽(对齐规则见 Doc/isa/指令与映像.md) + * @param name "bool" / "int" / "time" + * @return 1 / 2 / 8;未知返回 1 + */ + uint32_t width_of(const std::string& name) { + if (name == "int") return 2; + if (name == "time") return 8; + return 1; // bool 及未知 + } + /** * @brief 代码生成器(切片 1) */ @@ -70,13 +102,30 @@ namespace { */ 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) {} + : proj_(proj), units_(units), link_(link), image_(image), err_(err) { + // io 绑定分类(名已折小写;不创造变量,只影响操作码选择) + for (const isa::IoBinding& b : proj_.io) { + std::string key = b.var; + for (char& ch : key) { + ch = static_cast(std::tolower(static_cast(ch))); + } + if (b.is_input) { + io_input_[key] = true; + } else { + io_output_[key] = true; + } + } + } /** - * @brief 逐 POU 建函数后拼映像 + * @brief 布局数据区 → 逐 POU 建函数 → 拼映像 + * @details 数据区偏移先定(函数编译引用 global_offset) * @return true 成功;false(err 已写,前缀 "codegen error") */ bool run() { + if (!layout_data()) { + return false; + } for (const LinkResult::PouScope& sc : link_.scopes) { const POU* pou = find_pou(sc.name); if (pou == nullptr) { @@ -126,9 +175,31 @@ namespace { std::string name; std::vector code; // 字节码(函数表 code_offset 相对此段) std::map regs; // 变量名 → 帧寄存器 - uint8_t nregs = 0; // 变量数(切片 1 无临时) + uint8_t nlocals = 0; // 变量数 = 临时寄存器起始 + uint8_t nregs = 0; // 峰值(变量 + 临时) + uint8_t temp_used = 0; // 本语句已用临时数(语句结束清零) }; + /** + * @brief 语句开始:临时寄存器基址复用 + * @param f 当前函数 + */ + void begin_stmt(FuncCtx& f) { f.temp_used = 0; } + + /** + * @brief 分配一个临时寄存器(语句内递增) + * @param f 当前函数 + * @return 临时寄存器号(可能更新 nregs 峰值) + */ + uint8_t alloc_temp(FuncCtx& f) { + const uint8_t r = f.nlocals + f.temp_used; + ++f.temp_used; + if (static_cast(f.nlocals) + f.temp_used > f.nregs) { + f.nregs = f.nlocals + f.temp_used; + } + return r; + } + /** * @brief 编译一个 POU * @details 切片 1:仅 PROGRAM + 标量变量(FB 实例/函数留后续切片) @@ -141,6 +212,10 @@ namespace { return fail("only PROGRAM supported in slice 1 ('" + pou.name + "')"); } for (const VarBlock& b : pou.blocks) { + // External / Global 走数据区槽,不占帧寄存器 + if (b.section == VarSection::External || b.section == VarSection::Global) { + continue; + } 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 + "')"); @@ -151,7 +226,9 @@ namespace { f->regs[d.name] = f->nregs++; } } + f->nlocals = f->nregs; for (const Stmt& st : pou.body) { + begin_stmt(*f); if (!compile_stmt(*f, st)) { return false; } @@ -161,41 +238,168 @@ namespace { } /** - * @brief 编译一条语句(切片 1:仅赋值) + * @brief 编译一条语句(切片 2:仅赋值,左值可为帧寄存器或全局) * @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"); + return fail("statement not supported in slice 2"); } const auto it = f.regs.find(st.target); if (it == f.regs.end()) { - return fail("no register for '" + st.target + "'"); + // 全局 / 外部左值 → STORE_* 到数据区 + return store_target(f, st.target, *st.value); } 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) { + return compile_expr(f, *st.value, rd); + } + + /** + * @brief 赋值左值:帧寄存器直写或 STORE_* 到全局槽 + * @param f 当前函数 + * @param target 左值名(全局 / 外部) + * @param value 右值表达式 + * @return true 成功;false(err 已写) + */ + bool store_target(FuncCtx& f, const std::string& target, const Expr& value) { + const auto git = link_.global_index.find(target); + if (git == link_.global_index.end()) { + return fail("no storage for '" + target + "'"); + } + if (io_input_.count(target)) { + return fail("cannot write to input '" + target + "'"); + } + const uint8_t tmp = alloc_temp(f); + if (!compile_expr(f, value, tmp)) { + return false; + } + const uint16_t slot = global_offset(git->second); + f.code.push_back(isa::enc_slot(store_op(target), tmp, slot)); + return true; + } + + /** + * @brief 表达式编译到目标寄存器 + * @details 字面量 → LOADK;帧变量 → MOVE; + * 全局/外部 → LOAD_GLOBAL(io.input 绑定用 LOAD_I) + * @param f 当前函数 + * @param e 表达式 AST + * @param rd 目标寄存器 + * @return true 成功;false(err 已写) + */ + bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) { + if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt || + e.kind == ExprKind::LitTime) { const isa::types::TypeTag tag = - k == ExprKind::LitBool ? isa::types::Bool - : k == ExprKind::LitInt ? isa::types::Int - : isa::types::Time; + e.kind == ExprKind::LitBool ? isa::types::Bool + : e.kind == 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))); + isa::enc_imm(isa::Op::LOADK, rd, const_id(tag, e.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 + "'"); + if (e.kind == ExprKind::VarRef) { + const auto sit = f.regs.find(e.name); + if (sit != f.regs.end()) { + f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, sit->second)); + return true; } - f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, sit->second)); - return true; + const auto git = link_.global_index.find(e.name); + if (git != link_.global_index.end()) { + const uint16_t slot = global_offset(git->second); + f.code.push_back(isa::enc_slot(load_op(e.name), rd, slot)); + return true; + } + return fail("no register or slot for '" + e.name + "'"); } - return fail("expression not supported in slice 1"); + return fail("expression not supported in slice 2"); + } + + /** + * @brief 按 io 绑定选读取操作码 + * @param name 变量名(小写) + * @return io.input 绑定 → LOAD_I;否则 LOAD_GLOBAL + */ + isa::Op load_op(const std::string& name) const { + return io_input_.count(name) ? isa::Op::LOAD_I : isa::Op::LOAD_GLOBAL; + } + + /** + * @brief 按 io 绑定选写入操作码 + * @param name 变量名(小写) + * @return io.output 绑定 → STORE_Q;否则 STORE_GLOBAL + */ + isa::Op store_op(const std::string& name) const { + return io_output_.count(name) ? isa::Op::STORE_Q : isa::Op::STORE_GLOBAL; + } + + /** + * @brief 逻辑槽号 → 数据区字节偏移(layout_data 先行) + * @param slot 全局逻辑槽号(符号表 address) + * @return 数据区字节偏移(u16 可容) + */ + uint16_t global_offset(uint32_t slot) const { + return static_cast(global_offsets_[slot]); + } + + /** + * @brief 布局全局数据区:对齐 + 初值字节 + * @details 槽序 = 声明序(12.6);对齐规则 BOOL 1B / INT 2B / TIME 8B, + * 起点对齐自身宽度;初值取自 GVL 声明的 has_init(无则 0) + * @return true 成功;false(槽溢出等,err 已写) + */ + bool layout_data() { + uint32_t cur = 0; + for (const Symbol& s : link_.globals) { + const uint32_t w = width_of(s.type_name); + cur = align_up(cur, w); + if (cur + w > 0xFFFF) { + return fail("data area exceeds slot range"); + } + global_offsets_.push_back(cur); + bool has_init = false; + int64_t init = 0; + init_of(s.name, &has_init, &init); + data_.resize(cur + w, 0); + if (s.type_name == "bool") { + data_[cur] = static_cast(has_init ? init : 0); + } else if (s.type_name == "int") { + const uint16_t v = static_cast(has_init ? init : 0); + data_[cur] = static_cast(v & 0xFFu); + data_[cur + 1] = static_cast((v >> 8) & 0xFFu); + } else if (s.type_name == "time") { + const uint64_t v = static_cast(has_init ? init : 0); + for (int i = 0; i < 8; ++i) { + data_[cur + i] = static_cast((v >> (8 * i)) & 0xFFu); + } + } + cur += w; + } + return true; + } + + /** + * @brief 查全局声明的初值(AST;gvl 文件顶层段,声明序 = 槽号序) + * @param name 全局名(小写) + * @param has_init 输出是否有初值 + * @param init 输出初值 + */ + void init_of(const std::string& name, bool* has_init, int64_t* init) const { + for (const SourceUnit& u : units_) { + for (const VarBlock& b : u.ast.globals) { + for (const VarDecl& d : b.vars) { + if (d.name == name) { + *has_init = d.has_init; + *init = d.init_value; + return; + } + } + } + } + *has_init = false; + *init = 0; } /** @@ -215,7 +419,9 @@ namespace { } /** - * @brief 拼映像:头 + 常量表 + 函数表 + 字节码(数据段空) + * @brief 拼映像:头 + 常量表 + 函数表 + 字节码 + 数据段 + * @details 段序:const → funcs → code → fb(空)→ data; + * 数据段放全局初值(layout_data 已生成字节) * @return true 成功;false(err 已写) */ bool assemble_image() { @@ -228,7 +434,8 @@ namespace { for (const FuncCtx& f : funcs_) { code_total += static_cast(f.code.size()) * 4; } - const uint32_t off_end = off_code + code_total; + const uint32_t off_data = off_code + code_total; + const uint32_t off_end = off_data + static_cast(data_.size()); std::vector& b = *image_; b.assign(off_end, 0); @@ -250,7 +457,7 @@ namespace { } } put_le32(b, 24, entry); - put_le32(b, 28, 0); // n_globals(切片 1 无全局) + put_le32(b, 28, static_cast(link_.globals.size())); // n_globals put_le32(b, 32, 0); // n_i put_le32(b, 36, 0); // n_q put_le32(b, 40, 0); // n_m @@ -259,8 +466,8 @@ namespace { 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(空) + put_le32(b, 64, off_data); // offset_fb(空,与数据段起点相同) + put_le32(b, 68, off_data); // offset_data for (size_t i = 0; i < consts_.size(); ++i) { const size_t o = off_const + i * isa::kConstEntrySize; @@ -273,7 +480,6 @@ namespace { 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); @@ -287,6 +493,11 @@ namespace { put_le32(b, o + 4, acc); acc += static_cast(funcs_[i].code.size()) * 4; } + + // 数据段:全局初值字节 + for (size_t i = 0; i < data_.size(); ++i) { + b[off_data + i] = data_[i]; + } return true; } @@ -298,6 +509,10 @@ namespace { std::string* err_; std::vector funcs_; std::vector consts_; + std::map io_input_; // io.input 绑定名(小写) + std::map io_output_; // io.output 绑定名(小写) + std::vector global_offsets_; // 逻辑槽号 → 数据区字节偏移 + std::vector data_; // 数据区(全局初值字节) }; } // namespace diff --git a/tests/src/codegen_test.cpp b/tests/src/codegen_test.cpp index a0f6751..820db67 100644 --- a/tests/src/codegen_test.cpp +++ b/tests/src/codegen_test.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -58,6 +59,50 @@ static bool compile_case(const char* dir, std::vector* image, std::stri 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* 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 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() { @@ -119,9 +164,128 @@ static bool test_case02() { return true; } +// ---- 3. 用例 09:GVL + VAR_EXTERNAL(全局读取 LOAD_GLOBAL + 初值数据段)---- + +static bool test_case09() { + std::vector 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(base)[0], buf, sizeof buf); + CHECK(std::strcmp(buf, "LOAD_GLOBAL r0, 0") == 0); // x := G1(槽 0 偏移 0) + isa::disasm(reinterpret_cast(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 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(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(base)[1], buf, sizeof buf); + CHECK(std::strcmp(buf, "MOVE r1, r0") == 0); // 临时 ← q + isa::disasm(reinterpret_cast(base)[2], buf, sizeof buf); + CHECK(std::strcmp(buf, "STORE_Q r1, 1") == 0); // Q0_0 := 临时(io.output → STORE_Q) + isa::disasm(reinterpret_cast(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 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(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 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; +} + 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; std::printf("codegen_test: %d checks passed\n", g_checks); return 0; }