12.8 切片 1:寄存器码骨架(MOVE/LOADK/RET)。
- Codegen.h/cpp:帧寄存器(0 起声明序)、常量表、函数表、小端映像拼装 - 切片 1 范围:PROGRAM 标量 + 字面量 LOADK + 变量 MOVE + RET;其余构造报 codegen error - codegen_test:23 断言(用例 01 空 MAIN 出 RET、02 LOADK r0/r1 + 常量表),ctest 9/9
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @file Codegen.h
|
||||
* @brief 寄存器码生成(12.8,切片 1:MOVE / LOADK / RET)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<SourceUnit>& units,
|
||||
const LinkResult& link, std::vector<uint8_t>* image,
|
||||
std::string* err);
|
||||
}
|
||||
@@ -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: <msg>" 返回 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 <cstdio>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<uint8_t>& b, size_t off, uint32_t v) {
|
||||
b[off + 0] = static_cast<uint8_t>(v & 0xFFu);
|
||||
b[off + 1] = static_cast<uint8_t>((v >> 8) & 0xFFu);
|
||||
b[off + 2] = static_cast<uint8_t>((v >> 16) & 0xFFu);
|
||||
b[off + 3] = static_cast<uint8_t>((v >> 24) & 0xFFu);
|
||||
}
|
||||
|
||||
void put_le64(std::vector<uint8_t>& b, size_t off, uint64_t v) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
b[off + i] = static_cast<uint8_t>((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<SourceUnit>& units,
|
||||
const LinkResult& link, std::vector<uint8_t>* 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<isa::Instr> code; // 字节码(函数表 code_offset 相对此段)
|
||||
std::map<std::string, uint8_t> 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<uint64_t>(value)) {
|
||||
return static_cast<uint16_t>(i);
|
||||
}
|
||||
}
|
||||
consts_.push_back({tag, static_cast<uint64_t>(value)});
|
||||
return static_cast<uint16_t>(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<uint32_t>(consts_.size()) * isa::kConstEntrySize;
|
||||
uint32_t off_code = off_funcs +
|
||||
static_cast<uint32_t>(funcs_.size()) * isa::kFuncRowSize;
|
||||
uint32_t code_total = 0;
|
||||
for (const FuncCtx& f : funcs_) {
|
||||
code_total += static_cast<uint32_t>(f.code.size()) * 4;
|
||||
}
|
||||
const uint32_t off_end = off_code + code_total;
|
||||
|
||||
std::vector<uint8_t>& 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<uint32_t>(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<uint32_t>(consts_.size()));
|
||||
put_le32(b, 48, static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(funcs_[i].code.size()) * 4;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 成员 ----
|
||||
const Project& proj_;
|
||||
const std::vector<SourceUnit>& units_;
|
||||
const LinkResult& link_;
|
||||
std::vector<uint8_t>* image_;
|
||||
std::string* err_;
|
||||
std::vector<FuncCtx> funcs_;
|
||||
std::vector<isa::ConstEntry> 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<SourceUnit>& units,
|
||||
const LinkResult& link, std::vector<uint8_t>* image,
|
||||
std::string* err) {
|
||||
Builder b(proj, units, link, image, err);
|
||||
return b.run();
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @file codegen_test.cpp
|
||||
* @brief 寄存器码测试(12.8 切片 1):用例 01/02 出映像
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*/
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#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);
|
||||
}
|
||||
|
||||
// ---- 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;
|
||||
}
|
||||
|
||||
int main() {
|
||||
if (!test_case01()) return 1;
|
||||
if (!test_case02()) return 1;
|
||||
std::printf("codegen_test: %d checks passed\n", g_checks);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user