975 lines
41 KiB
C++
975 lines
41 KiB
C++
/**
|
||
* @file Codegen.cpp
|
||
* @brief 寄存器码生成(配置驱动;compiler 不依赖 isa)
|
||
* @author
|
||
* @date 2026-08-21
|
||
*
|
||
* @details 设计说明(详见 Doc/compiler/寄存器码.md 与 Doc/isa/指令配置.md):
|
||
* - 指令 opcode / 类型 tag / FB 布局全部来自 machine.toml(MachineConfig)
|
||
* - 帧/字面量/MOVE/RET;全局数据区;短路 AND/OR;CMP 与 IF;WHILE 与四则;
|
||
* FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+)
|
||
* - FB 实例 → 数据区实例块(跨周期持久);字段槽号 = 实例基槽 + 字段序号
|
||
* - 用户 FB 调用点内联展开;内建 FB → CAL_* <实例基槽>
|
||
* - 数据段每槽 8 字节定宽,指令 slot = 槽号
|
||
*
|
||
* 函数清单:
|
||
* - Builder::Builder (构造)存工程/源文件/链接结果/机器配置/输出,收集 io 绑定
|
||
* - Builder::run 布局数据区 → 逐 POU 建函数 → 拼映像
|
||
* - Builder::fail / opc 错误 / 按名查配置 opcode
|
||
* - E_rr / E_rrr / E_imm / E_slot / E_jmp / E_jc / E_call / E_ret 指令发射(配置 opcode)
|
||
* - find_pou / find_fn_id 按名查 POU AST / fn_id
|
||
* - build_function 编译一个 POU(帧约定分配;FB 占位)
|
||
* - begin_stmt / alloc_temp 临时寄存器管理
|
||
* - compile_stmt 语句编译(赋值 / IF / WHILE / FB 调用)
|
||
* - compile_if / compile_while / compile_fb_call / compile_fb_inline
|
||
* - store_target / compile_expr 左值存储 / 表达式编译
|
||
* - arith_name / cmp_name / load_name / store_name 操作码名选择
|
||
* - patch_jump 回填跳转偏移(相对下一条)
|
||
* - layout_data / layout_fb_instances / instance_of / init_of
|
||
* - const_id 取常量表 id(tag 来自配置类型表)
|
||
* - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段
|
||
* - codegen_project 对外入口
|
||
*/
|
||
|
||
#include "compiler/Codegen.h"
|
||
|
||
#include <cctype>
|
||
#include <cstdio>
|
||
#include <map>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#include "compiler/Codec.h"
|
||
#include "compiler/MachineConfig.h"
|
||
#include "compiler/Stb.h"
|
||
#include "compiler/TypeInfo.h"
|
||
|
||
namespace compiler {
|
||
namespace {
|
||
|
||
/**
|
||
* @brief 代码生成器(寄存器码)。
|
||
* @details 流程:布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像。
|
||
* 寄存器分配:r0 结果、r1..r7 参数(调用约定区,输入只读)、r8+ 变量/临时;
|
||
* 跳转偏移相对下一条(目标 = 当前 + 1 + off)。失败统一经 fail() 写
|
||
* err(前缀 "codegen error")。
|
||
*/
|
||
class Builder {
|
||
public:
|
||
/**
|
||
* @brief 构造生成器
|
||
* @param proj 工程定义(cycle_limit / dt_ms / 哈希用)
|
||
* @param units 全部源文件的 AST
|
||
* @param link 链接结果(POU 顺序 / 符号)
|
||
* @param cfg 机器定义(machine.toml 强校验后;指令 opcode / 类型 tag / FB 布局)
|
||
* @param image 输出映像字节
|
||
* @param err 错误输出;可为 nullptr(静默)
|
||
*/
|
||
Builder(const Project& proj, const std::vector<SourceUnit>& units,
|
||
const LinkResult& link, const MachineConfig& cfg,
|
||
std::vector<uint8_t>* image, std::string* err)
|
||
: proj_(proj), units_(units), link_(link), cfg_(cfg), image_(image), err_(err) {
|
||
// io 绑定分类(名已折小写;不创造变量,只影响操作码选择)
|
||
for (const IoBinding& b : proj_.io) {
|
||
std::string key = b.var;
|
||
for (char& ch : key) {
|
||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||
}
|
||
if (b.is_input) {
|
||
io_input_[key] = true;
|
||
} else {
|
||
io_output_[key] = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像
|
||
* @return true 成功;false(err 已写,前缀 "codegen error")
|
||
*/
|
||
bool run() {
|
||
if (!layout_data()) {
|
||
return false;
|
||
}
|
||
if (!layout_fb_instances()) {
|
||
return false;
|
||
}
|
||
for (const LinkResult::PouScope& sc : link_.scopes) {
|
||
const POU* pou = find_pou(sc.name);
|
||
if (pou == nullptr) {
|
||
continue;
|
||
}
|
||
FuncCtx f;
|
||
f.name = sc.name;
|
||
f.pou_name = sc.name;
|
||
if (!build_function(*pou, &f)) {
|
||
return false;
|
||
}
|
||
funcs_.push_back(std::move(f));
|
||
}
|
||
return assemble_image();
|
||
}
|
||
|
||
private:
|
||
/**
|
||
* @brief 记录错误并返回失败。
|
||
* @param msg 错误消息(自动加前缀 "codegen error: ")
|
||
* @return 恒 false(便于 return fail(...) 连写)
|
||
*/
|
||
bool fail(const std::string& msg) {
|
||
if (err_) {
|
||
*err_ = "codegen error: " + msg;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* @brief 按名查配置 opcode(MachineConfig 强校验保证存在)
|
||
* @param name 指令名(大写,如 "ADD")
|
||
* @return opcode(找不到返回 0,不应发生)
|
||
*/
|
||
uint8_t opc(const char* name) const {
|
||
const ConfigOp* op = cfg_.find_op(name);
|
||
return op ? static_cast<uint8_t>(op->opcode) : 0;
|
||
}
|
||
|
||
// ---- 指令发射(配置 opcode)----
|
||
/// 双寄存器指令(rd, rs)。
|
||
Instr E_rr(const char* name, uint8_t rd, uint8_t rs) {
|
||
return pack(opc(name), rd, rs, 0);
|
||
}
|
||
/// 三寄存器指令(rd, ra, rb)。
|
||
Instr E_rrr(const char* name, uint8_t rd, uint8_t ra, uint8_t rb) {
|
||
return pack(opc(name), rd, ra, rb);
|
||
}
|
||
/// 立即数指令(imm 拆低/高字节入字段)。
|
||
Instr E_imm(const char* name, uint8_t rd, uint16_t imm) {
|
||
return pack(opc(name), rd, static_cast<uint8_t>(imm & 0xFFu),
|
||
static_cast<uint8_t>((imm >> 8) & 0xFFu));
|
||
}
|
||
/// 槽号指令(slot 复用 imm 字段,u16)。
|
||
Instr E_slot(const char* name, uint8_t rd, uint16_t slot) {
|
||
return E_imm(name, rd, slot);
|
||
}
|
||
/// 无条件跳转(off 相对下一条指令)。
|
||
Instr E_jmp(int16_t off) {
|
||
const uint16_t u = static_cast<uint16_t>(off);
|
||
return pack(opc("JMP"), 0, static_cast<uint8_t>(u & 0xFFu),
|
||
static_cast<uint8_t>((u >> 8) & 0xFFu));
|
||
}
|
||
/// 条件跳转(寄存器 r 为真则跳;off 相对下一条指令)。
|
||
Instr E_jc(const char* name, uint8_t r, int16_t off) {
|
||
const uint16_t u = static_cast<uint16_t>(off);
|
||
return pack(opc(name), r, static_cast<uint8_t>(u & 0xFFu),
|
||
static_cast<uint8_t>((u >> 8) & 0xFFu));
|
||
}
|
||
/// CALL 指令(fn_id 为函数表下标)。
|
||
Instr E_call(uint16_t fn_id) { return E_imm("CALL", 0, fn_id); }
|
||
/// RET 指令。
|
||
Instr E_ret() { return pack(opc("RET"), 0, 0, 0); }
|
||
|
||
/**
|
||
* @brief 按名查 POU AST。
|
||
* @param name POU 名
|
||
* @return 找到返回指针;否则 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;
|
||
}
|
||
|
||
// 每函数的编译态
|
||
/**
|
||
* @brief 单个函数的编译状态。
|
||
* @details 寄存器分配(调用约定):结果 r0、输入 r1..r7、变量与临时 r8+。
|
||
*/
|
||
struct FuncCtx {
|
||
std::string name; ///< 函数名(函数表行用)
|
||
std::string pou_name; ///< 所属 POU 名(实例查找键)
|
||
std::vector<Instr> code; ///< 字节码
|
||
std::map<std::string, uint8_t> regs; ///< 变量名 → 帧寄存器
|
||
uint8_t nlocals = 0; ///< 变量区终点 = 临时寄存器起始
|
||
uint8_t nregs = 0; ///< 寄存器峰值(变量 + 临时)
|
||
uint8_t temp_used = 0; ///< 本语句已用临时数(语句结束清零)
|
||
bool is_function = false; ///< FUNCTION(结果 r0 / 输入只读)
|
||
std::string result_name; ///< FUNCTION 名(结果寄存器映射)
|
||
};
|
||
|
||
// FB 实例:字段名 → 数据区槽号
|
||
/**
|
||
* @brief FB 实例布局。
|
||
* @details 字段槽号 = 实例基槽 + 字段序号(跨周期持久)。
|
||
*/
|
||
struct InstFields {
|
||
std::map<std::string, uint32_t> field_addr; ///< 字段名 → 数据区槽号
|
||
uint32_t base = 0; ///< 实例基槽
|
||
std::string type_name; ///< 实例的 FB 类型名(内建 / 用户)
|
||
};
|
||
|
||
/// 语句开始:清零临时寄存器计数。
|
||
void begin_stmt(FuncCtx& f) { f.temp_used = 0; }
|
||
|
||
/**
|
||
* @brief 分配一个临时寄存器。
|
||
* @param f 函数编译态
|
||
* @return 临时寄存器号(nlocals + 已用数;随用抬高 nregs 峰值)
|
||
*/
|
||
uint8_t alloc_temp(FuncCtx& f) {
|
||
const uint8_t r = f.nlocals + f.temp_used;
|
||
++f.temp_used;
|
||
if (static_cast<uint16_t>(f.nlocals) + f.temp_used > f.nregs) {
|
||
f.nregs = f.nlocals + f.temp_used;
|
||
}
|
||
return r;
|
||
}
|
||
|
||
/**
|
||
* @brief 编译一个 POU
|
||
* @details 支持 PROGRAM / FUNCTION / FUNCTION_BLOCK:
|
||
* FB 不生成业务字节码(调用点内联),只出空函数占位保持 fn_id 一致;
|
||
* 帧约定(见 Doc/compiler/寄存器码.md):结果 r0、输入 r1..r7(只读)、
|
||
* 变量与临时全部从 r8 起
|
||
*/
|
||
bool build_function(const POU& pou, FuncCtx* f) {
|
||
if (pou.kind == PouKind::FunctionBlock) {
|
||
f->nlocals = 8;
|
||
f->nregs = 8;
|
||
f->code.push_back(E_ret());
|
||
return true;
|
||
}
|
||
f->is_function = pou.kind == PouKind::Function;
|
||
f->result_name = pou.name;
|
||
if (f->is_function) {
|
||
f->regs[pou.name] = 0; // 结果寄存器 r0
|
||
uint8_t idx = 1;
|
||
for (const VarBlock& b : pou.blocks) {
|
||
if (b.section != VarSection::Input) {
|
||
continue;
|
||
}
|
||
for (const VarDecl& d : b.vars) {
|
||
f->regs[d.name] = idx++; // 输入 r1..r7
|
||
}
|
||
}
|
||
}
|
||
uint8_t r = 8; // 变量/临时基址(调用约定区 r0..r7 不占用)
|
||
for (const VarBlock& b : pou.blocks) {
|
||
if (b.section == VarSection::External || b.section == VarSection::Global) {
|
||
continue;
|
||
}
|
||
if (f->is_function && b.section == VarSection::Input) {
|
||
continue;
|
||
}
|
||
for (const VarDecl& d : b.vars) {
|
||
if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) {
|
||
continue;
|
||
}
|
||
f->regs[d.name] = r++;
|
||
}
|
||
}
|
||
f->nlocals = r;
|
||
f->nregs = r;
|
||
for (const Stmt& st : pou.body) {
|
||
begin_stmt(*f);
|
||
if (!compile_stmt(*f, st)) {
|
||
return false;
|
||
}
|
||
}
|
||
f->code.push_back(E_ret());
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 编译一条语句。
|
||
* @param f 函数编译态
|
||
* @param st 语句 AST
|
||
* @return true 成功;false(err 已写)
|
||
* @details 支持 IF / WHILE / FB 调用 / 赋值;其他语句报
|
||
* "statement not supported"。内联 FB 体内左值可能是实例字段
|
||
* (走 STORE_GLOBAL);写 FUNCTION 输入报 "cannot write function input"。
|
||
*/
|
||
bool compile_stmt(FuncCtx& f, const Stmt& st) {
|
||
if (st.kind == StmtKind::If) {
|
||
return compile_if(f, st);
|
||
}
|
||
if (st.kind == StmtKind::While) {
|
||
return compile_while(f, st);
|
||
}
|
||
if (st.kind == StmtKind::FbCall) {
|
||
return compile_fb_call(f, st);
|
||
}
|
||
if (st.kind != StmtKind::Assign) {
|
||
return fail("statement not supported");
|
||
}
|
||
// 内联 FB 体内:左值可能是实例字段(STORE_GLOBAL)
|
||
if (inline_fields_) {
|
||
const auto fit = inline_fields_->field_addr.find(st.target);
|
||
if (fit != inline_fields_->field_addr.end()) {
|
||
const uint8_t t = alloc_temp(f);
|
||
if (!compile_expr(f, *st.value, t)) {
|
||
return false;
|
||
}
|
||
f.code.push_back(E_slot("STORE_GLOBAL", t, fit->second));
|
||
return true;
|
||
}
|
||
}
|
||
const auto it = f.regs.find(st.target);
|
||
if (it == f.regs.end()) {
|
||
return store_target(f, st.target, *st.value);
|
||
}
|
||
if (f.is_function && it->second >= 1 && it->second <= 7) {
|
||
return fail("cannot write function input '" + st.target + "'");
|
||
}
|
||
return compile_expr(f, *st.value, it->second);
|
||
}
|
||
|
||
/**
|
||
* @brief 编译 FB 调用语句。
|
||
* @param f 函数编译态
|
||
* @param st FB 调用语句 AST
|
||
* @return true 成功;false(err 已写)
|
||
* @details 实参逐项编译后 STORE_GLOBAL 写实例字段;内建 FB
|
||
* 发 CAL_<TYPE> <实例基槽>;用户 FB 调用点内联展开。
|
||
* 错误:"no instance" / "unknown input" / "no FB type"。
|
||
*/
|
||
bool compile_fb_call(FuncCtx& f, const Stmt& st) {
|
||
const InstFields* inst = instance_of(f.pou_name, st.instance);
|
||
if (inst == nullptr) {
|
||
return fail("no instance '" + st.instance + "' in '" + f.pou_name + "'");
|
||
}
|
||
for (const FbArg& a : st.args) {
|
||
const auto it = inst->field_addr.find(a.name);
|
||
if (it == inst->field_addr.end()) {
|
||
return fail("unknown input '" + a.name + "' for '" + st.instance + "'");
|
||
}
|
||
const uint8_t t = alloc_temp(f);
|
||
if (!compile_expr(f, *a.value, t)) {
|
||
return false;
|
||
}
|
||
f.code.push_back(E_slot("STORE_GLOBAL", t, it->second));
|
||
}
|
||
// 内建 FB:配置里必须有对应 op 行(强校验保证)→ 直接查配置 opcode
|
||
const std::string& tn = inst->type_name;
|
||
if (cfg_.find_op("CAL_" + uppercase_of(tn)) != nullptr &&
|
||
(tn == "ton" || tn == "tof" || tn == "tp" || tn == "ctu" ||
|
||
tn == "ctd" || tn == "ctud" || tn == "r_trig" || tn == "f_trig")) {
|
||
const std::string opname = "CAL_" + uppercase_of(tn);
|
||
f.code.push_back(E_slot(opname.c_str(), 0, inst->base));
|
||
return true;
|
||
}
|
||
const POU* fb = find_pou(inst->type_name);
|
||
if (fb == nullptr) {
|
||
return fail("no FB type '" + inst->type_name + "'");
|
||
}
|
||
return compile_fb_inline(f, *fb, *inst);
|
||
}
|
||
|
||
/// 转大写(内建 FB 名 → 操作码名)。
|
||
static std::string uppercase_of(const std::string& s) {
|
||
std::string out = s;
|
||
for (char& ch : out) {
|
||
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/**
|
||
* @brief 用户 FB 调用点内联展开。
|
||
* @param f 函数编译态
|
||
* @param fb 用户 FB 的 POU AST
|
||
* @param inst 实例布局
|
||
* @return true 成功;false(err 已写)
|
||
* @details 临时置 inline_fields_ 使体内变量引用改查实例字段
|
||
* (编译结束后恢复)。
|
||
*/
|
||
bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) {
|
||
const InstFields* saved = inline_fields_;
|
||
inline_fields_ = &inst;
|
||
for (const Stmt& s : fb.body) {
|
||
begin_stmt(f);
|
||
if (!compile_stmt(f, s)) {
|
||
return false;
|
||
}
|
||
}
|
||
inline_fields_ = saved;
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 编译 WHILE 循环。
|
||
* @param f 函数编译态
|
||
* @param st WHILE 语句 AST
|
||
* @return true 成功;false(err 已写)
|
||
* @details 结构:条件 → JF 跳出 → 体 → JMP 回条件;偏移经
|
||
* patch_jump 回填(相对下一条)。
|
||
*/
|
||
bool compile_while(FuncCtx& f, const Stmt& st) {
|
||
const size_t loop = f.code.size();
|
||
const uint8_t t = alloc_temp(f);
|
||
if (!compile_expr(f, *st.cond, t)) {
|
||
return false;
|
||
}
|
||
const size_t jf_idx = f.code.size();
|
||
f.code.push_back(E_jc("JF", t, 0));
|
||
for (const Stmt& s : st.body) {
|
||
begin_stmt(f);
|
||
if (!compile_stmt(f, s)) {
|
||
return false;
|
||
}
|
||
}
|
||
const size_t jmp_idx = f.code.size();
|
||
f.code.push_back(E_jmp(0));
|
||
patch_jump(f, jmp_idx, loop);
|
||
patch_jump(f, jf_idx, f.code.size());
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 编译 IF / ELSIF / ELSE。
|
||
* @param f 函数编译态
|
||
* @param st IF 语句 AST
|
||
* @return true 成功;false(err 已写)
|
||
* @details 每条分支:条件 → JF 跳下一条 → 体;分支间 JMP 跳
|
||
* 公共结束点;偏移经 patch_jump 回填。
|
||
*/
|
||
bool compile_if(FuncCtx& f, const Stmt& st) {
|
||
std::vector<std::pair<const Expr*, const std::vector<Stmt>*>> branches;
|
||
branches.push_back({st.cond.get(), &st.body});
|
||
for (const IfBranch& b : st.elsifs) {
|
||
branches.push_back({b.cond.get(), &b.body});
|
||
}
|
||
const bool has_else = !st.else_body.empty();
|
||
std::vector<size_t> end_jmps;
|
||
|
||
for (size_t i = 0; i < branches.size(); ++i) {
|
||
const uint8_t t = alloc_temp(f);
|
||
if (!compile_expr(f, *branches[i].first, t)) {
|
||
return false;
|
||
}
|
||
const size_t jf_idx = f.code.size();
|
||
f.code.push_back(E_jc("JF", t, 0));
|
||
for (const Stmt& s : *branches[i].second) {
|
||
begin_stmt(f);
|
||
if (!compile_stmt(f, s)) {
|
||
return false;
|
||
}
|
||
}
|
||
if (i + 1 < branches.size() || has_else) {
|
||
const size_t jmp_idx = f.code.size();
|
||
f.code.push_back(E_jmp(0));
|
||
end_jmps.push_back(jmp_idx);
|
||
}
|
||
patch_jump(f, jf_idx, f.code.size());
|
||
}
|
||
if (has_else) {
|
||
for (const Stmt& s : st.else_body) {
|
||
begin_stmt(f);
|
||
if (!compile_stmt(f, s)) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
const size_t end = f.code.size();
|
||
for (const size_t j : end_jmps) {
|
||
patch_jump(f, j, end);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 编译左值存储(全局槽)。
|
||
* @param f 函数编译态
|
||
* @param target 目标名
|
||
* @param value 表达式
|
||
* @return true 成功;false(err 已写)
|
||
* @details 目标槽号在 link_.global_index 中查;I/O 输入禁止写
|
||
* ("cannot write to input");存储操作码经 store_name 选择
|
||
* (I/O 输出 STORE_Q,其余 STORE_GLOBAL)。
|
||
* 错误:"no storage for ..." / "cannot write to input ..."。
|
||
*/
|
||
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;
|
||
}
|
||
f.code.push_back(E_slot(store_name(target), tmp, static_cast<uint16_t>(git->second)));
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 编译表达式到目标寄存器。
|
||
* @param f 函数编译态
|
||
* @param e 表达式 AST
|
||
* @param rd 目标寄存器号
|
||
* @return true 成功;false(err 已写)
|
||
* @details 支持:字面量(LOADK,tag 来自配置类型表)、变量/字段引用、
|
||
* 四则、比较(cmp_name)、NOT、短路 AND/OR(JF/JT)、函数调用
|
||
* (实参 MOVE 到 r1..r7 后 CALL,结果 MOVE 回 rd)。
|
||
* 错误:"no register or slot" / "no instance" / "unknown field" /
|
||
* "too many arguments (max 7)" / "no fn_id" / "expression not supported"。
|
||
*/
|
||
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 char* type_name = e.kind == ExprKind::LitBool ? "BOOL"
|
||
: e.kind == ExprKind::LitInt ? "INT"
|
||
: "TIME";
|
||
f.code.push_back(E_imm("LOADK", rd, const_id(type_name, e.int_value)));
|
||
return true;
|
||
}
|
||
if (e.kind == ExprKind::VarRef) {
|
||
if (inline_fields_) {
|
||
const auto fit = inline_fields_->field_addr.find(e.name);
|
||
if (fit != inline_fields_->field_addr.end()) {
|
||
f.code.push_back(E_slot("LOAD_GLOBAL", rd, fit->second));
|
||
return true;
|
||
}
|
||
}
|
||
const auto sit = f.regs.find(e.name);
|
||
if (sit != f.regs.end()) {
|
||
f.code.push_back(E_rr("MOVE", rd, sit->second));
|
||
return true;
|
||
}
|
||
const auto git = link_.global_index.find(e.name);
|
||
if (git != link_.global_index.end()) {
|
||
f.code.push_back(
|
||
E_slot(load_name(e.name), rd, static_cast<uint16_t>(git->second)));
|
||
return true;
|
||
}
|
||
return fail("no register or slot for '" + e.name + "'");
|
||
}
|
||
if (e.kind == ExprKind::Field) {
|
||
const InstFields* inst = instance_of(f.pou_name, e.name);
|
||
if (inst == nullptr) {
|
||
return fail("no instance '" + e.name + "' in '" + f.pou_name + "'");
|
||
}
|
||
const auto fit = inst->field_addr.find(e.field);
|
||
if (fit == inst->field_addr.end()) {
|
||
return fail("unknown field '" + e.field + "' for '" + e.name + "'");
|
||
}
|
||
f.code.push_back(E_slot("LOAD_GLOBAL", rd, fit->second));
|
||
return true;
|
||
}
|
||
if (e.kind == ExprKind::Add || e.kind == ExprKind::Sub ||
|
||
e.kind == ExprKind::Mul || e.kind == ExprKind::Div) {
|
||
const uint8_t l = alloc_temp(f);
|
||
if (!compile_expr(f, *e.lhs, l)) {
|
||
return false;
|
||
}
|
||
const uint8_t r = alloc_temp(f);
|
||
if (!compile_expr(f, *e.rhs, r)) {
|
||
return false;
|
||
}
|
||
f.code.push_back(E_rrr(arith_name(e.kind), rd, l, r));
|
||
return true;
|
||
}
|
||
if (e.kind == ExprKind::Cmp) {
|
||
const uint8_t l = alloc_temp(f);
|
||
if (!compile_expr(f, *e.lhs, l)) {
|
||
return false;
|
||
}
|
||
const uint8_t r = alloc_temp(f);
|
||
if (!compile_expr(f, *e.rhs, r)) {
|
||
return false;
|
||
}
|
||
f.code.push_back(E_rrr(cmp_name(e.op), rd, l, r));
|
||
return true;
|
||
}
|
||
if (e.kind == ExprKind::Not) {
|
||
const uint8_t t = alloc_temp(f);
|
||
if (!compile_expr(f, *e.operand, t)) {
|
||
return false;
|
||
}
|
||
f.code.push_back(E_rr("NOT", rd, t));
|
||
return true;
|
||
}
|
||
if (e.kind == ExprKind::And || e.kind == ExprKind::Or) {
|
||
if (!compile_expr(f, *e.lhs, rd)) {
|
||
return false;
|
||
}
|
||
const char* jname = (e.kind == ExprKind::And) ? "JF" : "JT";
|
||
const size_t jmp_idx = f.code.size();
|
||
f.code.push_back(E_jc(jname, rd, 0));
|
||
const uint8_t t = alloc_temp(f);
|
||
if (!compile_expr(f, *e.rhs, t)) {
|
||
return false;
|
||
}
|
||
f.code.push_back(E_rr("MOVE", rd, t));
|
||
patch_jump(f, jmp_idx, f.code.size());
|
||
return true;
|
||
}
|
||
if (e.kind == ExprKind::Call) {
|
||
if (e.args.size() > 7) {
|
||
return fail("too many arguments (max 7)");
|
||
}
|
||
std::vector<uint8_t> args;
|
||
for (const auto& a : e.args) {
|
||
const uint8_t t = alloc_temp(f);
|
||
if (!compile_expr(f, *a, t)) {
|
||
return false;
|
||
}
|
||
args.push_back(t);
|
||
}
|
||
const int fn_id = find_fn_id(e.name);
|
||
if (fn_id < 0) {
|
||
return fail("no fn_id for '" + e.name + "'");
|
||
}
|
||
for (size_t i = 0; i < args.size(); ++i) {
|
||
f.code.push_back(E_rr("MOVE", static_cast<uint8_t>(1 + i), args[i]));
|
||
}
|
||
f.code.push_back(E_call(static_cast<uint16_t>(fn_id)));
|
||
f.code.push_back(E_rr("MOVE", rd, 0));
|
||
return true;
|
||
}
|
||
return fail("expression not supported");
|
||
}
|
||
|
||
/**
|
||
* @brief 按名查 fn_id(函数表下标)。
|
||
* @param name 函数名
|
||
* @return 找到返回下标;否则 -1
|
||
*/
|
||
int find_fn_id(const std::string& name) const {
|
||
for (size_t i = 0; i < link_.scopes.size(); ++i) {
|
||
if (link_.scopes[i].name == name) {
|
||
return static_cast<int>(i);
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/**
|
||
* @brief 回填跳转偏移。
|
||
* @param f 函数编译态
|
||
* @param idx 跳转指令下标
|
||
* @param target_idx 目标指令下标
|
||
* @details 偏移相对下一条:目标 = 当前 + 1 + off(off 为
|
||
* int16,重写指令的低 16 位)。
|
||
*/
|
||
void patch_jump(FuncCtx& f, size_t idx, size_t target_idx) {
|
||
const int16_t off = static_cast<int16_t>(
|
||
static_cast<int64_t>(target_idx) - (static_cast<int64_t>(idx) + 1));
|
||
const Instr w = f.code[idx];
|
||
const uint16_t u = static_cast<uint16_t>(off);
|
||
f.code[idx] = pack(op_of(w), rd_of(w), static_cast<uint8_t>(u & 0xFFu),
|
||
static_cast<uint8_t>((u >> 8) & 0xFFu));
|
||
}
|
||
|
||
// ---- 操作码名(MachineConfig 已强校验存在)----
|
||
/// 四则运算操作码名(Add→"ADD" 等;未知回退 "ADD")。
|
||
static const char* arith_name(ExprKind k) {
|
||
switch (k) {
|
||
case ExprKind::Add: return "ADD";
|
||
case ExprKind::Sub: return "SUB";
|
||
case ExprKind::Mul: return "MUL";
|
||
case ExprKind::Div: return "DIV";
|
||
default: return "ADD";
|
||
}
|
||
}
|
||
/// 比较操作码名(Eq→"CMP_EQ" 等;未知回退 "CMP_EQ")。
|
||
static const char* cmp_name(BinOp op) {
|
||
switch (op) {
|
||
case BinOp::Eq: return "CMP_EQ";
|
||
case BinOp::Ne: return "CMP_NE";
|
||
case BinOp::Lt: return "CMP_LT";
|
||
case BinOp::Le: return "CMP_LE";
|
||
case BinOp::Gt: return "CMP_GT";
|
||
case BinOp::Ge: return "CMP_GE";
|
||
default: return "CMP_EQ";
|
||
}
|
||
}
|
||
/// 加载操作码选择:I/O 输入走 LOAD_I,其余 LOAD_GLOBAL。
|
||
const char* load_name(const std::string& name) const {
|
||
return io_input_.count(name) ? "LOAD_I" : "LOAD_GLOBAL";
|
||
}
|
||
/// 存储操作码选择:I/O 输出走 STORE_Q,其余 STORE_GLOBAL。
|
||
const char* store_name(const std::string& name) const {
|
||
return io_output_.count(name) ? "STORE_Q" : "STORE_GLOBAL";
|
||
}
|
||
|
||
/**
|
||
* @brief 布局全局数据区。
|
||
* @return true 成功;false(err 已写)
|
||
* @details 每槽 8 字节定宽小端,槽号 × 8 定位;初值按类型元数据
|
||
* 写(宽度 2 写 u16、8 写 u64、否则写 0/1;别名先经 type_meta 解析)。
|
||
* 错误:"data area exceeds slot range" / "no type in machine.toml" /
|
||
* "float initializer not supported yet"。
|
||
*/
|
||
bool layout_data() {
|
||
for (const Symbol& s : link_.globals) {
|
||
if (s.address > 0xFFFF) {
|
||
return fail("data area exceeds slot range");
|
||
}
|
||
bool has_init = false;
|
||
int64_t init = 0;
|
||
init_of(s.name, &has_init, &init);
|
||
const int64_t v = has_init ? init : 0;
|
||
const size_t off = static_cast<size_t>(s.address) * 8;
|
||
data_.resize(off + 8, 0);
|
||
// 初值按类型元数据写(配置别名 → 基元宽度/浮点)
|
||
const TypeMeta tm = type_meta(cfg_, s.type_name);
|
||
if (!tm) {
|
||
return fail("no type in machine.toml for '" + s.type_name + "'");
|
||
}
|
||
if (tm.is_float()) {
|
||
return fail("float initializer not supported yet");
|
||
}
|
||
if (tm.width() == 2) {
|
||
const uint16_t iv = static_cast<uint16_t>(v);
|
||
data_[off] = static_cast<uint8_t>(iv & 0xFFu);
|
||
data_[off + 1] = static_cast<uint8_t>((iv >> 8) & 0xFFu);
|
||
} else if (tm.width() == 8) {
|
||
const uint64_t tv = static_cast<uint64_t>(v);
|
||
for (int i = 0; i < 8; ++i) {
|
||
data_[off + i] = static_cast<uint8_t>((tv >> (8 * i)) & 0xFFu);
|
||
}
|
||
} else {
|
||
data_[off] = v ? 1 : 0;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 布局 FB 实例块(全局区之后顺序追加)。
|
||
* @return true 成功;false(err 已写)
|
||
* @details 实例 key 为 "POU名/实例名";字段槽号 = 基槽 + 字段序号。
|
||
* 错误:"no layout for instance ..."。
|
||
*/
|
||
bool layout_fb_instances() {
|
||
uint32_t cur = static_cast<uint32_t>(data_.size() / 8);
|
||
bool any = false;
|
||
for (const LinkResult::PouScope& sc : link_.scopes) {
|
||
for (const Symbol& s : sc.syms) {
|
||
if (s.kind != SymbolKind::FbInstance) {
|
||
continue;
|
||
}
|
||
const auto lay = sc.fb_instances.find(s.name);
|
||
if (lay == sc.fb_instances.end()) {
|
||
return fail("no layout for instance '" + s.name + "'");
|
||
}
|
||
InstFields inst;
|
||
inst.base = cur;
|
||
inst.type_name = s.type_name;
|
||
for (size_t i = 0; i < lay->second.fields.size(); ++i) {
|
||
inst.field_addr[lay->second.fields[i].name] = cur + i;
|
||
}
|
||
cur += static_cast<uint32_t>(lay->second.fields.size());
|
||
instances_[sc.name + "/" + s.name] = inst;
|
||
any = true;
|
||
}
|
||
}
|
||
if (any) {
|
||
data_.resize(static_cast<size_t>(cur) * 8, 0);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/// 按 "POU名/实例名" 查实例布局;找不到返回 nullptr。
|
||
const InstFields* instance_of(const std::string& pou,
|
||
const std::string& name) const {
|
||
const auto it = instances_.find(pou + "/" + name);
|
||
return it == instances_.end() ? nullptr : &it->second;
|
||
}
|
||
|
||
/**
|
||
* @brief 查全局变量初值(源文件 globals 段)。
|
||
* @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;
|
||
}
|
||
|
||
/**
|
||
* @brief 取常量表 id(无则追加)
|
||
* @param type_name 语言类型名("BOOL"/"INT"/"TIME")→ 配置 tag(契约)
|
||
* @param value 常量值
|
||
* @return const_id(u16)
|
||
*/
|
||
uint16_t const_id(const char* type_name, int64_t value) {
|
||
const TypeMeta tm = type_meta(cfg_, type_name);
|
||
const uint32_t tag = tm ? tm.tag() : 0;
|
||
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 已写)
|
||
* @details 布局:头(104) + 常量表 + 函数表 + 字节码 + 数据段 +
|
||
* SHA-256 文件尾;型号标识 @72;头内各段偏移(52..68)与函数表
|
||
* code_offset 小端写入;SHA-256 对文件尾之前全部内容计算。
|
||
*/
|
||
bool assemble_image() {
|
||
const uint32_t off_const = static_cast<uint32_t>(kHeaderSize);
|
||
const uint32_t off_funcs = off_const +
|
||
static_cast<uint32_t>(consts_.size()) * static_cast<uint32_t>(kConstEntrySize);
|
||
uint32_t off_code = off_funcs +
|
||
static_cast<uint32_t>(funcs_.size()) * static_cast<uint32_t>(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_data = off_code + code_total;
|
||
const uint32_t off_end = off_data + static_cast<uint32_t>(data_.size()) +
|
||
static_cast<uint32_t>(kSha256Size);
|
||
|
||
std::vector<uint8_t>& b = *image_;
|
||
b.assign(off_end, 0);
|
||
put_le32(b, 0, kMagic);
|
||
put_le32(b, 4, kVersion);
|
||
put_le32(b, 8, proj_.cycle_limit);
|
||
put_le32(b, 12, proj_.dt_ms);
|
||
|
||
uint64_t hash = 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, static_cast<uint32_t>(link_.globals.size()));
|
||
put_le32(b, 32, 0);
|
||
put_le32(b, 36, 0);
|
||
put_le32(b, 40, 0);
|
||
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_data);
|
||
put_le32(b, 68, off_data);
|
||
|
||
// 型号标识[32](偏移 72;12.13 修订)
|
||
char mid[kModelIdSize];
|
||
fill_model_id(cfg_.model_name(), cfg_.version(), mid);
|
||
for (size_t i = 0; i < kModelIdSize; ++i) {
|
||
b[72 + i] = static_cast<uint8_t>(mid[i]);
|
||
}
|
||
|
||
for (size_t i = 0; i < consts_.size(); ++i) {
|
||
const size_t o = off_const + i * kConstEntrySize;
|
||
put_le32(b, o, 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 * kFuncRowSize;
|
||
put_le32(b, o, f.nregs);
|
||
put_le32(b, o + 8, static_cast<uint32_t>(f.code.size()));
|
||
for (const Instr in : f.code) {
|
||
put_le32(b, c, in);
|
||
c += 4;
|
||
}
|
||
}
|
||
uint32_t acc = 0;
|
||
for (size_t i = 0; i < funcs_.size(); ++i) {
|
||
const size_t o = off_funcs + i * kFuncRowSize;
|
||
put_le32(b, o + 4, acc);
|
||
acc += static_cast<uint32_t>(funcs_[i].code.size()) * 4;
|
||
}
|
||
|
||
for (size_t i = 0; i < data_.size(); ++i) {
|
||
b[off_data + i] = data_[i];
|
||
}
|
||
|
||
// 文件尾 SHA-256(对文件尾之前全部内容计算)
|
||
const size_t content_len = off_data + data_.size();
|
||
uint8_t digest[kSha256Size];
|
||
sha256(&b[0], content_len, digest);
|
||
for (size_t i = 0; i < kSha256Size; ++i) {
|
||
b[content_len + i] = digest[i];
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/// 小端写 32 位。
|
||
static 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);
|
||
}
|
||
/// 小端写 64 位。
|
||
static 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);
|
||
}
|
||
}
|
||
|
||
// ---- 成员 ----
|
||
const Project& proj_; ///< 工程定义(cycle_limit / dt_ms / 哈希)
|
||
const std::vector<SourceUnit>& units_; ///< 全部源文件 AST
|
||
const LinkResult& link_; ///< 链接结果(POU 顺序 / 全局符号 / FB 布局)
|
||
const MachineConfig& cfg_; ///< 机器定义(opcode / tag / FB 布局)
|
||
std::vector<uint8_t>* image_; ///< 输出映像字节
|
||
std::string* err_; ///< 错误输出(可为 nullptr)
|
||
std::vector<FuncCtx> funcs_; ///< 已编译函数
|
||
std::vector<ConstEntry> consts_; ///< 常量表(tag + value)
|
||
std::map<std::string, bool> io_input_; ///< I/O 输入变量名集合(小写;只影响操作码选择)
|
||
std::map<std::string, bool> io_output_; ///< I/O 输出变量名集合(小写;只影响操作码选择)
|
||
std::vector<uint8_t> data_; ///< 全局数据区字节(每槽 8 字节定宽)
|
||
std::map<std::string, InstFields> instances_; ///< "POU名/实例名" → 实例布局
|
||
const InstFields* inline_fields_ = nullptr; ///< 内联 FB 字段表(非空 = 正在内联 FB 体)
|
||
};
|
||
|
||
} // namespace
|
||
|
||
/**
|
||
* @brief 编译工程为 .stb 映像字节(对外入口)。
|
||
* @param proj 工程定义
|
||
* @param units 全部源文件的 AST
|
||
* @param link 链接结果
|
||
* @param cfg 机器定义(machine.toml)
|
||
* @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, const MachineConfig& cfg,
|
||
std::vector<uint8_t>* image, std::string* err) {
|
||
Builder b(proj, units, link, cfg, image, err);
|
||
return b.run();
|
||
}
|
||
|
||
} // namespace compiler
|