- STCompiler --dump-map:编译后打印变量/槽映射(函数局部 r 寄存器、 全局 slot/off/type/func、实例 slot/off/fb/size/字段偏移) - CodegenMap 输出结构(codegen_project 可选参数) - 修复参数循环 bug(i+1<argc 漏掉末尾单参数选项) - 结构自检(临时 stbcheck):13 用例寄存器/func/跳转/槽/长度全部通过 - 13 正例逐条对照全部通过,产出 Doc/compiler/用例反汇编对照.md - 已知缺陷记录:LREAL 字面量装载按 REAL(TODO)
1091 lines
46 KiB
C++
1091 lines
46 KiB
C++
/**
|
||
* @file Codegen.cpp
|
||
* @brief 寄存器码生成(V2:配置驱动 + 变长指令;compiler 不依赖 isa)
|
||
* @author
|
||
* @date 2026-08-21
|
||
*
|
||
* @details 设计说明(详见 Doc/compiler/寄存器码.md 与 Doc/isa/指令与映像.md V2):
|
||
* - 指令 prefix/op/func 全部来自 machine.toml(MachineConfig);发射经 Codec 位号
|
||
* - 数据模型:槽表(8B 引用 {addr,预留})→ 值段(变宽紧凑,1/2/4/8 对齐)
|
||
* - 常量表 8B 原始值(无 tag),LOADK 按 func 解释,位模式去重
|
||
* - 跳转回填字节单位(off32 相对下一条:目标 = 当前指令起点 + 指令长 + off)
|
||
* - 用户 FB 调用点内联展开(字段访问 = LOAD_OFF/STORE_OFF 实例槽 + 字段偏移)
|
||
* - 内建 FB → CAL fb_id, 实例槽(fb_id 查 [[fb]] 表)
|
||
* - max_stack:MAIN + FUNCTION 调用图 DAG 最坏路径 Σ(12 + nregs×8);递归 = 编译期报错
|
||
*
|
||
* 函数清单:
|
||
* - Builder::Builder 构造;run 布局 → 逐 POU 建函数 → 拼映像
|
||
* - emit / emit0 指令发射(查 ConfigOp,Codec 位号写字节)
|
||
* - E_rr/E_rrr/E_imm/E_slot/E_jmp/E_jc/E_call/E_cal/E_ret/E_slot_off
|
||
* - func_of_name 语言类型名 → func 位段值
|
||
* - compile_stmt/if/while/fb_call/fb_inline/store_target/compile_expr(V1 语义保留)
|
||
* - patch_jump 字节化回填
|
||
* - layout_slots_values 槽表 + 值段布局(全局 + FB 实例,对齐 1/2/4/8)
|
||
* - compute_max_stack 调用图 DAG 最长路径 + 环检测
|
||
* - assemble_image V2 头 128B + 段序拼装
|
||
*/
|
||
|
||
#include "compiler/Codegen.h"
|
||
|
||
#include <cctype>
|
||
#include <cstdio>
|
||
#include <cstring>
|
||
#include <functional>
|
||
#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 代码生成器(V2 寄存器码)。
|
||
* @details 流程:布局槽表/值段 → 逐 POU 建函数 → 计算 max_stack → 拼映像。
|
||
* 寄存器分配:r0 结果、r1..r7 参数(调用约定区,输入只读)、r8+ 变量/临时;
|
||
* 跳转偏移字节单位相对下一条。失败统一经 fail() 写 err(前缀 "codegen error")。
|
||
*/
|
||
class Builder {
|
||
public:
|
||
Builder(const Project& proj, const std::vector<SourceUnit>& units,
|
||
const LinkResult& link, const MachineConfig& cfg,
|
||
std::vector<uint8_t>* image, std::string* err, CodegenMap* map)
|
||
: proj_(proj), units_(units), link_(link), cfg_(cfg), image_(image), err_(err),
|
||
map_(map) {
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
bool run() {
|
||
if (!layout_slots_values()) {
|
||
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));
|
||
}
|
||
if (!compute_max_stack()) {
|
||
return false;
|
||
}
|
||
return assemble_image();
|
||
}
|
||
|
||
private:
|
||
bool fail(const std::string& msg) {
|
||
if (err_) {
|
||
*err_ = "codegen error: " + msg;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* @brief 查指令行(MachineConfig 强校验保证存在)。
|
||
* @param name 指令名(大写,如 "ADD")
|
||
* @return 配置行指针(找不到返回 nullptr,不应发生)
|
||
*/
|
||
const ConfigOp* opc(const char* name) const {
|
||
return cfg_.find_op(name);
|
||
}
|
||
|
||
// ---- 指令发射(V2:Codec 位号写字节)----
|
||
|
||
/**
|
||
* @brief 单个函数的编译状态(V2)。
|
||
*/
|
||
struct FuncCtx {
|
||
std::string name; ///< 函数名
|
||
std::string pou_name; ///< 所属 POU 名(实例查找键)
|
||
std::vector<uint8_t> code; ///< 字节码(变长指令,字节流)
|
||
std::map<std::string, uint8_t> regs; ///< 变量名 → 帧寄存器
|
||
std::map<std::string, uint8_t> reg_func; ///< 变量名 → func(类型)
|
||
uint8_t nlocals = 0; ///< 变量区终点 = 临时寄存器起始
|
||
uint8_t nregs = 0; ///< 寄存器峰值
|
||
uint8_t temp_used = 0; ///< 本语句已用临时数
|
||
bool is_function = false; ///< FUNCTION(结果 r0 / 输入只读)
|
||
std::string result_name; ///< FUNCTION 名(结果寄存器映射)
|
||
std::vector<uint32_t> calls; ///< 本函数 CALL 的 fn_id 列表(max_stack 用)
|
||
};
|
||
|
||
|
||
/// 发射一条指令:追加 instr_len 字节到函数码流,返回起始字节偏移。
|
||
size_t emit(FuncCtx& f, const ConfigOp* op) {
|
||
const size_t start = f.code.size();
|
||
f.code.resize(start + kMaxInstrLen, 0);
|
||
set_byte0(f.code.data() + start, op->prefix, op->op);
|
||
if (op->func_mode == "none" || op->func_mode == "width" ||
|
||
op->func_mode == "uint") {
|
||
set_func(f.code.data() + start, 0);
|
||
}
|
||
f.code.resize(start + instr_len(f.code.data() + start));
|
||
return start;
|
||
}
|
||
|
||
/// 双寄存器指令(rd, rs)。
|
||
size_t E_rr(FuncCtx& f, const char* name, uint8_t rd, uint8_t rs) {
|
||
const size_t s = emit(f, opc(name));
|
||
set_rd(f.code.data() + s, rd);
|
||
set_rs1(f.code.data() + s, rs);
|
||
return s;
|
||
}
|
||
/// 三寄存器指令(rd, ra, rb)。
|
||
size_t E_rrr(FuncCtx& f, const char* name, uint8_t rd, uint8_t ra, uint8_t rb) {
|
||
const size_t s = emit(f, opc(name));
|
||
set_rd(f.code.data() + s, rd);
|
||
set_rs1(f.code.data() + s, ra);
|
||
set_rs2(f.code.data() + s, rb);
|
||
return s;
|
||
}
|
||
/// 立即数指令(rd, imm32)。
|
||
size_t E_imm(FuncCtx& f, const char* name, uint8_t rd, uint32_t imm) {
|
||
const size_t s = emit(f, opc(name));
|
||
set_rd(f.code.data() + s, rd);
|
||
set_imm32(f.code.data() + s, imm);
|
||
return s;
|
||
}
|
||
/// 槽号指令(rd, slot32)。
|
||
size_t E_slot(FuncCtx& f, const char* name, uint8_t rd, uint32_t slot) {
|
||
const size_t s = emit(f, opc(name));
|
||
set_rd(f.code.data() + s, rd);
|
||
set_slot32(f.code.data() + s, slot);
|
||
return s;
|
||
}
|
||
/// 无条件跳转(off 字节相对下一条)。
|
||
size_t E_jmp(FuncCtx& f, int32_t off) {
|
||
const size_t s = emit(f, opc("JMP"));
|
||
set_off32(f.code.data() + s, off);
|
||
return s;
|
||
}
|
||
/// 条件跳转(r 为真则跳;off 字节相对下一条,JC 形态 off@55..24)。
|
||
size_t E_jc(FuncCtx& f, const char* name, uint8_t r, int32_t off) {
|
||
const size_t s = emit(f, opc(name));
|
||
set_rd(f.code.data() + s, r);
|
||
set_off32_jc(f.code.data() + s, off);
|
||
return s;
|
||
}
|
||
/// CALL 指令(fn_id32)。
|
||
size_t E_call(FuncCtx& f, uint32_t fn_id) {
|
||
const size_t s = emit(f, opc("CALL"));
|
||
set_fn_id32(f.code.data() + s, fn_id);
|
||
return s;
|
||
}
|
||
/// RET 指令。
|
||
size_t E_ret(FuncCtx& f) {
|
||
const size_t s = emit(f, opc("RET"));
|
||
return s;
|
||
}
|
||
/// 字段访问(实例槽 + 字段偏移,16B SLOT+off)。
|
||
size_t E_slot_off(FuncCtx& f, const char* name, uint8_t rd, uint32_t slot,
|
||
int32_t off) {
|
||
const size_t s = emit(f, opc(name));
|
||
set_rd(f.code.data() + s, rd);
|
||
set_slot32(f.code.data() + s, slot);
|
||
set_off32_hi(f.code.data() + s, off);
|
||
return s;
|
||
}
|
||
/// 内建 FB 调用(fb_id16 + 实例槽32)。
|
||
size_t E_cal(FuncCtx& f, uint16_t fb_id, uint32_t slot) {
|
||
const size_t s = emit(f, opc("CAL"));
|
||
set_fb_id16(f.code.data() + s, fb_id);
|
||
set_cal_slot32(f.code.data() + s, slot);
|
||
return s;
|
||
}
|
||
|
||
/// 语言类型名 → func 位段值(大小写不敏感:符号表存小写,type 表存大写)。
|
||
uint8_t func_of_name(const std::string& type_name) const {
|
||
std::string key = type_name;
|
||
for (char& ch : key) {
|
||
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
|
||
}
|
||
const ConfigType* t = cfg_.find_type(key);
|
||
return t ? static_cast<uint8_t>(t->func) : 0;
|
||
}
|
||
|
||
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 FB 实例布局(V2:槽表条目 + 值区偏移 + 字段偏移)。
|
||
*/
|
||
struct InstFields {
|
||
std::map<std::string, int32_t> field_off; ///< 字段名 → 值区内偏移
|
||
std::map<std::string, uint8_t> field_func; ///< 字段名 → func(类型)
|
||
uint32_t slot = 0; ///< 实例槽号(槽表条目)
|
||
std::string type_name; ///< 实例的 FB 类型名
|
||
};
|
||
|
||
void begin_stmt(FuncCtx& f) { f.temp_used = 0; }
|
||
|
||
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;
|
||
}
|
||
|
||
/// 变量名 → 语言类型名(局部声明;标量 = d.type.name 小写)。
|
||
std::string type_name_of_var(const POU& pou, const std::string& name) const {
|
||
for (const VarBlock& b : pou.blocks) {
|
||
for (const VarDecl& d : b.vars) {
|
||
if (d.name == name) {
|
||
return d.type.kind == TypeKind::Scalar ? d.type.name : "int";
|
||
}
|
||
}
|
||
}
|
||
return "int";
|
||
}
|
||
|
||
bool build_function(const POU& pou, FuncCtx* f) {
|
||
if (pou.kind == PouKind::FunctionBlock) {
|
||
f->nlocals = 8;
|
||
f->nregs = 8;
|
||
E_ret(*f);
|
||
return true;
|
||
}
|
||
f->is_function = pou.kind == PouKind::Function;
|
||
f->result_name = pou.name;
|
||
if (f->is_function) {
|
||
f->regs[pou.name] = 0;
|
||
f->reg_func[pou.name] = func_of_name("INT");
|
||
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++;
|
||
f->reg_func[d.name] = func_of_name(type_name_of_var(pou, d.name));
|
||
}
|
||
}
|
||
}
|
||
uint8_t r = 8;
|
||
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->reg_func[d.name] = func_of_name(type_name_of_var(pou, d.name));
|
||
++r;
|
||
}
|
||
}
|
||
f->nlocals = r;
|
||
f->nregs = r;
|
||
if (map_ != nullptr) {
|
||
std::map<std::string, std::string> vars;
|
||
for (const auto& kv : f->regs) {
|
||
std::string tn = "int";
|
||
const auto tf = f->reg_func.find(kv.first);
|
||
if (tf != f->reg_func.end()) {
|
||
tn = std::to_string(tf->second);
|
||
}
|
||
vars[kv.first] = "r" + std::to_string(kv.second) + " (func " + tn + ")";
|
||
}
|
||
map_->funcs.emplace_back(f->name, vars);
|
||
}
|
||
for (const Stmt& st : pou.body) {
|
||
begin_stmt(*f);
|
||
if (!compile_stmt(*f, st)) {
|
||
return false;
|
||
}
|
||
}
|
||
E_ret(*f);
|
||
return true;
|
||
}
|
||
|
||
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");
|
||
}
|
||
if (inline_fields_) {
|
||
const auto fit = inline_fields_->field_off.find(st.target);
|
||
if (fit != inline_fields_->field_off.end()) {
|
||
const uint8_t t = alloc_temp(f);
|
||
if (!compile_expr(f, *st.value, t)) {
|
||
return false;
|
||
}
|
||
const size_t so = E_slot_off(f, "STORE_OFF", t, inline_fields_->slot,
|
||
fit->second);
|
||
const auto ff = inline_fields_->field_func.find(st.target);
|
||
if (ff != inline_fields_->field_func.end()) {
|
||
set_func(f.code.data() + so, ff->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);
|
||
}
|
||
|
||
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_off.find(a.name);
|
||
if (it == inst->field_off.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;
|
||
}
|
||
const size_t so = E_slot_off(f, "STORE_OFF", t, inst->slot, it->second);
|
||
const auto ff = inst->field_func.find(a.name);
|
||
if (ff != inst->field_func.end()) {
|
||
set_func(f.code.data() + so, ff->second);
|
||
}
|
||
}
|
||
// 内建 FB:fb 表查 id
|
||
const ConfigFb* fb = cfg_.find_fb(uppercase_of(inst->type_name));
|
||
if (fb != nullptr) {
|
||
E_cal(f, static_cast<uint16_t>(fb->fb_id), inst->slot);
|
||
return true;
|
||
}
|
||
const POU* user_fb = find_pou(inst->type_name);
|
||
if (user_fb == nullptr) {
|
||
return fail("no FB type '" + inst->type_name + "'");
|
||
}
|
||
return compile_fb_inline(f, *user_fb, *inst);
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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 = E_jc(f, "JF", t, 0);
|
||
for (const Stmt& s : st.body) {
|
||
begin_stmt(f);
|
||
if (!compile_stmt(f, s)) {
|
||
return false;
|
||
}
|
||
}
|
||
const size_t jmp_idx = E_jmp(f, 0);
|
||
patch_jump(f, jmp_idx, loop);
|
||
patch_jump(f, jf_idx, f.code.size());
|
||
return true;
|
||
}
|
||
|
||
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 = E_jc(f, "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) {
|
||
end_jmps.push_back(E_jmp(f, 0));
|
||
}
|
||
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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
E_slot(f, "STORE", tmp, git->second);
|
||
return true;
|
||
}
|
||
|
||
bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) {
|
||
if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt ||
|
||
e.kind == ExprKind::LitTime || e.kind == ExprKind::LitReal ||
|
||
e.kind == ExprKind::LitDate || e.kind == ExprKind::LitTod ||
|
||
e.kind == ExprKind::LitDt) {
|
||
const char* type_name = "INT";
|
||
int64_t value = e.int_value;
|
||
switch (e.kind) {
|
||
case ExprKind::LitBool: type_name = "BOOL"; break;
|
||
case ExprKind::LitInt: type_name = "INT"; break;
|
||
case ExprKind::LitTime: type_name = "TIME"; break;
|
||
case ExprKind::LitReal: {
|
||
// TODO(V2): 浮点字面量按目标类型装载(REAL/LREAL 位模式不同),
|
||
// 需 compile_expr 携带目标 func;当前按 REAL(float32)装载
|
||
type_name = "REAL";
|
||
float fv = static_cast<float>(e.double_value);
|
||
std::memcpy(&value, &fv, sizeof fv);
|
||
break;
|
||
}
|
||
case ExprKind::LitDate: type_name = "DATE"; break;
|
||
case ExprKind::LitTod: type_name = "TOD"; break;
|
||
case ExprKind::LitDt: type_name = "DT"; break;
|
||
default: break;
|
||
}
|
||
const size_t s = E_imm(f, "LOADK", rd, const_id(value));
|
||
set_func(f.code.data() + s, func_of_name(type_name));
|
||
return true;
|
||
}
|
||
if (e.kind == ExprKind::VarRef) {
|
||
if (inline_fields_) {
|
||
const auto fit = inline_fields_->field_off.find(e.name);
|
||
if (fit != inline_fields_->field_off.end()) {
|
||
const size_t lo = E_slot_off(f, "LOAD_OFF", rd,
|
||
inline_fields_->slot, fit->second);
|
||
const auto ff = inline_fields_->field_func.find(e.name);
|
||
if (ff != inline_fields_->field_func.end()) {
|
||
set_func(f.code.data() + lo, ff->second);
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
const auto sit = f.regs.find(e.name);
|
||
if (sit != f.regs.end()) {
|
||
const size_t s = E_rr(f, "MOVE", rd, sit->second);
|
||
const auto tf = f.reg_func.find(e.name);
|
||
set_func(f.code.data() + s,
|
||
tf != f.reg_func.end() ? tf->second : func_of_name("INT"));
|
||
return true;
|
||
}
|
||
const auto git = link_.global_index.find(e.name);
|
||
if (git != link_.global_index.end()) {
|
||
const size_t s = E_slot(f, "LOAD", rd, git->second);
|
||
set_func(f.code.data() + s, func_of_global(e.name));
|
||
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_off.find(e.field);
|
||
if (fit == inst->field_off.end()) {
|
||
return fail("unknown field '" + e.field + "' for '" + e.name + "'");
|
||
}
|
||
const size_t lo = E_slot_off(f, "LOAD_OFF", rd, inst->slot, fit->second);
|
||
const auto ff = inst->field_func.find(e.field);
|
||
if (ff != inst->field_func.end()) {
|
||
set_func(f.code.data() + lo, ff->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;
|
||
}
|
||
const size_t s = E_rrr(f, arith_name(e.kind), rd, l, r);
|
||
set_func(f.code.data() + s, expr_func(f, *e.lhs, *e.rhs));
|
||
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;
|
||
}
|
||
const size_t s = E_rrr(f, cmp_name(e.op), rd, l, r);
|
||
set_func(f.code.data() + s, expr_func(f, *e.lhs, *e.rhs));
|
||
return true;
|
||
}
|
||
if (e.kind == ExprKind::Not) {
|
||
const uint8_t t = alloc_temp(f);
|
||
if (!compile_expr(f, *e.operand, t)) {
|
||
return false;
|
||
}
|
||
E_rr(f, "NOT", rd, t);
|
||
return true;
|
||
}
|
||
if (e.kind == ExprKind::Neg) {
|
||
const uint8_t t = alloc_temp(f);
|
||
if (!compile_expr(f, *e.operand, t)) {
|
||
return false;
|
||
}
|
||
const size_t s = E_rr(f, "NEG", rd, t);
|
||
set_func(f.code.data() + s, expr_func(f, *e.operand, *e.operand));
|
||
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 = E_jc(f, jname, rd, 0);
|
||
const uint8_t t = alloc_temp(f);
|
||
if (!compile_expr(f, *e.rhs, t)) {
|
||
return false;
|
||
}
|
||
E_rr(f, "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) {
|
||
E_rr(f, "MOVE", static_cast<uint8_t>(1 + i), args[i]);
|
||
}
|
||
f.calls.push_back(static_cast<uint32_t>(fn_id));
|
||
E_call(f, static_cast<uint32_t>(fn_id));
|
||
E_rr(f, "MOVE", rd, 0);
|
||
return true;
|
||
}
|
||
return fail("expression not supported");
|
||
}
|
||
|
||
/// 表达式类型 func(算术/比较用):VarRef 取 reg_func/全局;
|
||
/// 嵌套表达式左右递归;字面量按装载类型(LitInt→INT、LitReal→REAL 等)。
|
||
uint8_t expr_func(const FuncCtx& f, const Expr& l, const Expr& r) const {
|
||
if (l.kind == ExprKind::VarRef) {
|
||
const auto tf = f.reg_func.find(l.name);
|
||
if (tf != f.reg_func.end()) {
|
||
return tf->second;
|
||
}
|
||
return func_of_global(l.name);
|
||
}
|
||
if (l.kind == ExprKind::LitBool || l.kind == ExprKind::LitInt ||
|
||
l.kind == ExprKind::LitTime || l.kind == ExprKind::LitReal ||
|
||
l.kind == ExprKind::LitDate || l.kind == ExprKind::LitTod ||
|
||
l.kind == ExprKind::LitDt) {
|
||
// 左是字面量 → 取右操作数(Typecheck 已定型同型)
|
||
if (r.kind == ExprKind::VarRef) {
|
||
const auto tf = f.reg_func.find(r.name);
|
||
if (tf != f.reg_func.end()) {
|
||
return tf->second;
|
||
}
|
||
return func_of_global(r.name);
|
||
}
|
||
}
|
||
return func_of_name("INT");
|
||
}
|
||
|
||
/// 全局变量 → func(查链接符号类型)。
|
||
uint8_t func_of_global(const std::string& name) const {
|
||
for (const Symbol& s : link_.globals) {
|
||
if (s.name == name) {
|
||
return func_of_name(s.type_name);
|
||
}
|
||
}
|
||
return func_of_name("INT");
|
||
}
|
||
|
||
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_off 目标字节偏移
|
||
* @details off 相对下一条:目标 = 当前起点 + 指令长 + off。
|
||
*/
|
||
void patch_jump(FuncCtx& f, size_t idx, size_t target_off) {
|
||
const int32_t off = static_cast<int32_t>(
|
||
static_cast<int64_t>(target_off) -
|
||
(static_cast<int64_t>(idx) + static_cast<int64_t>(instr_len(f.code.data() + idx))));
|
||
// 按前缀选偏移字段:JMP(010010)off@47..16;JT/JF(010011)off@55..24
|
||
const std::string prefix = prefix_of(f.code.data() + idx);
|
||
if (prefix == "010010") {
|
||
set_off32(f.code.data() + idx, off);
|
||
} else {
|
||
set_off32_jc(f.code.data() + idx, off);
|
||
}
|
||
}
|
||
|
||
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";
|
||
}
|
||
}
|
||
|
||
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";
|
||
}
|
||
}
|
||
|
||
// ---- V2 布局:槽表 → 值段 ----
|
||
|
||
/**
|
||
* @brief 布局槽表与值段。
|
||
* @details 槽表条目:全局(声明序,每变量 1 条)+ FB 实例(每实例 1 条,
|
||
* 指向值区块起点)。值段:全局按类型 1/2/4/8 对齐 + 实例字段按
|
||
* 字段类型对齐连续排。初值写值段(实例字段 0)。
|
||
*/
|
||
bool layout_slots_values() {
|
||
uint32_t cur_addr = 0; // 值段当前偏移
|
||
for (const Symbol& s : link_.globals) {
|
||
const TypeMeta tm = type_meta(cfg_, s.type_name);
|
||
if (!tm) {
|
||
return fail("no type in machine.toml for '" + s.type_name + "'");
|
||
}
|
||
cur_addr = align_up(cur_addr, tm.width());
|
||
slots_.push_back(cur_addr);
|
||
values_.resize(cur_addr + tm.width(), 0);
|
||
bool has_init = false;
|
||
int64_t init = 0;
|
||
init_of(s.name, &has_init, &init);
|
||
const int64_t v = has_init ? init : 0;
|
||
write_value(values_, cur_addr, v, tm.width());
|
||
global_slot_[s.name] = static_cast<uint32_t>(slots_.size() - 1);
|
||
if (map_ != nullptr) {
|
||
map_->globals[s.name] = "slot=" + std::to_string(slots_.size() - 1) +
|
||
" off=" + std::to_string(cur_addr) +
|
||
" type=" + s.type_name +
|
||
" func=" + std::to_string(tm.func());
|
||
}
|
||
cur_addr += tm.width();
|
||
}
|
||
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.slot = static_cast<uint32_t>(slots_.size());
|
||
inst.type_name = s.type_name;
|
||
int32_t off = 0;
|
||
for (const FbField& fd : lay->second.fields) {
|
||
const TypeMeta tm = type_meta(cfg_, fd.type_name);
|
||
if (!tm) {
|
||
return fail("no type for fb field '" + fd.type_name + "'");
|
||
}
|
||
off = static_cast<int32_t>(align_up(off, tm.width()));
|
||
inst.field_off[fd.name] = off;
|
||
inst.field_func[fd.name] = static_cast<uint8_t>(tm.func());
|
||
off += static_cast<int32_t>(tm.width());
|
||
}
|
||
// 实例块对齐到 max(字段对齐)
|
||
uint32_t max_align = 1;
|
||
for (const FbField& fd : lay->second.fields) {
|
||
const TypeMeta tm = type_meta(cfg_, fd.type_name);
|
||
max_align = max_align > tm.width() ? max_align : tm.width();
|
||
}
|
||
cur_addr = align_up(cur_addr, max_align);
|
||
slots_.push_back(cur_addr);
|
||
values_.resize(cur_addr + static_cast<uint32_t>(off), 0);
|
||
instances_[sc.name + "/" + s.name] = inst;
|
||
if (map_ != nullptr) {
|
||
std::string desc = "slot=" + std::to_string(inst.slot) +
|
||
" off=" + std::to_string(cur_addr) +
|
||
" fb=" + inst.type_name +
|
||
" size=" + std::to_string(off) + " ";
|
||
for (const auto& fo : inst.field_off) {
|
||
desc += fo.first + "@" + std::to_string(fo.second) + " ";
|
||
}
|
||
map_->instances[sc.name + "/" + s.name] = desc;
|
||
}
|
||
cur_addr += static_cast<uint32_t>(off);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static uint32_t align_up(uint32_t v, uint32_t a) {
|
||
if (a <= 1) {
|
||
return v;
|
||
}
|
||
return (v + a - 1) / a * a;
|
||
}
|
||
|
||
/// 按宽度写小端值(1/2/4/8 字节)。
|
||
static void write_value(std::vector<uint8_t>& buf, size_t off, int64_t v,
|
||
uint32_t width) {
|
||
for (uint32_t i = 0; i < width; ++i) {
|
||
buf[off + i] = static_cast<uint8_t>((static_cast<uint64_t>(v) >> (8 * i)) & 0xFFu);
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
/// 常量表:8B 原始值位模式去重(无 tag;LOADK 按 func 解释)。
|
||
uint16_t const_id(int64_t value) {
|
||
const uint64_t v = static_cast<uint64_t>(value);
|
||
for (size_t i = 0; i < consts_.size(); ++i) {
|
||
if (consts_[i] == v) {
|
||
return static_cast<uint16_t>(i);
|
||
}
|
||
}
|
||
consts_.push_back(v);
|
||
return static_cast<uint16_t>(consts_.size() - 1);
|
||
}
|
||
|
||
/**
|
||
* @brief max_stack:MAIN + FUNCTION 调用图 DAG 最坏路径 Σ(12 + nregs×8)。
|
||
* @return true 成功;false(调用图有环 → "recursive call")
|
||
*/
|
||
bool compute_max_stack() {
|
||
const size_t n = funcs_.size();
|
||
// 邻接表:fn_id → 被调 fn_id 列表(权重 = 帧需求)
|
||
std::vector<std::vector<uint32_t>> edges(n);
|
||
for (size_t i = 0; i < n; ++i) {
|
||
for (const uint32_t c : funcs_[i].calls) {
|
||
if (c < n) {
|
||
edges[i].push_back(c);
|
||
}
|
||
}
|
||
}
|
||
// 环检测(DFS 三色)——防御(链接层已拒递归)
|
||
std::vector<int> color(n, 0);
|
||
std::vector<bool> in_stack(n, false);
|
||
std::function<bool(size_t)> dfs_cycle = [&](size_t u) -> bool {
|
||
color[u] = 1;
|
||
in_stack[u] = true;
|
||
for (const uint32_t v : edges[u]) {
|
||
if (color[v] == 0) {
|
||
if (dfs_cycle(v)) return true;
|
||
} else if (in_stack[v]) {
|
||
return true;
|
||
}
|
||
}
|
||
in_stack[u] = false;
|
||
return false;
|
||
};
|
||
for (size_t i = 0; i < n; ++i) {
|
||
if (color[i] == 0 && dfs_cycle(i)) {
|
||
return fail("recursive call detected");
|
||
}
|
||
}
|
||
// 最长路径(按字节权重)——DAG 上记忆化
|
||
std::vector<int64_t> memo(n, -1);
|
||
std::function<int64_t(size_t)> best = [&](size_t u) -> int64_t {
|
||
if (memo[u] >= 0) {
|
||
return memo[u];
|
||
}
|
||
int64_t b = 0;
|
||
for (const uint32_t v : edges[u]) {
|
||
const int64_t cand = 12 + static_cast<int64_t>(funcs_[v].nregs) * 8 +
|
||
best(v);
|
||
if (cand > b) {
|
||
b = cand;
|
||
}
|
||
}
|
||
memo[u] = b;
|
||
return b;
|
||
};
|
||
int64_t total = 0;
|
||
for (size_t i = 0; i < n; ++i) {
|
||
const int64_t need = 12 + static_cast<int64_t>(funcs_[i].nregs) * 8 + best(i);
|
||
if (need > total) {
|
||
total = need;
|
||
}
|
||
}
|
||
max_stack_ = static_cast<uint32_t>(total);
|
||
return true;
|
||
}
|
||
|
||
bool assemble_image() {
|
||
// 段布局(V2 头 128B + 常量表 8B + 函数表 12B + 字节码 + 槽表 + 值段 + SHA)
|
||
const uint32_t off_const = static_cast<uint32_t>(kHeaderSize);
|
||
const uint32_t off_funcs =
|
||
off_const + static_cast<uint32_t>(consts_.size()) * kConstEntrySize;
|
||
uint32_t off_code =
|
||
off_funcs + static_cast<uint32_t>(funcs_.size()) * kFuncRowSize;
|
||
uint32_t code_total = 0;
|
||
for (const FuncCtx& f : funcs_) {
|
||
code_total += static_cast<uint32_t>(f.code.size());
|
||
}
|
||
const uint32_t off_slots = align_up(off_code + code_total, 16);
|
||
const uint32_t off_values =
|
||
off_slots + static_cast<uint32_t>(slots_.size()) * kSlotRowSize;
|
||
const uint32_t off_end = off_values + static_cast<uint32_t>(values_.size()) +
|
||
static_cast<uint32_t>(kSha256Size);
|
||
|
||
std::vector<uint8_t>& b = *image_;
|
||
b.assign(off_end, 0);
|
||
put_le32(b, kOffMagic, kMagic);
|
||
put_le32(b, kOffVersion, kVersion);
|
||
put_le32(b, kOffCycleLimit, proj_.cycle_limit);
|
||
put_le32(b, kOffDtMs, proj_.dt_ms);
|
||
|
||
uint64_t hash = kFnvBasis;
|
||
if (!compute_project_hash(proj_, &hash, err_)) {
|
||
return false;
|
||
}
|
||
put_le64(b, kOffProjectHash, 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, kOffEntryFnId, entry);
|
||
put_le32(b, kOffNGlobals, static_cast<uint32_t>(link_.globals.size()));
|
||
put_le32(b, kOffNI, 0);
|
||
put_le32(b, kOffNQ, 0);
|
||
put_le32(b, kOffNM, 0);
|
||
put_le32(b, kOffNConsts, static_cast<uint32_t>(consts_.size()));
|
||
put_le32(b, kOffNFuncs, static_cast<uint32_t>(funcs_.size()));
|
||
put_le32(b, kOffConst, off_const);
|
||
put_le32(b, kOffFuncs, off_funcs);
|
||
put_le32(b, kOffCode, off_code);
|
||
put_le32(b, kOffSlots, off_slots);
|
||
put_le32(b, kOffValues, off_values);
|
||
put_le32(b, kOffNSlots, static_cast<uint32_t>(slots_.size()));
|
||
put_le32(b, kOffValuesSize, static_cast<uint32_t>(values_.size()));
|
||
put_le32(b, kOffMeta, 0);
|
||
put_le32(b, kOffMaxStack, max_stack_);
|
||
|
||
char mid[kModelIdSize];
|
||
fill_model_id(cfg_.model_name(), cfg_.version(), mid);
|
||
for (size_t i = 0; i < kModelIdSize; ++i) {
|
||
b[kOffModelId + i] = static_cast<uint8_t>(mid[i]);
|
||
}
|
||
|
||
// 常量表(8B 原始值)
|
||
for (size_t i = 0; i < consts_.size(); ++i) {
|
||
put_le64(b, off_const + i * kConstEntrySize, consts_[i]);
|
||
}
|
||
|
||
// 函数表(nregs/code_offset/code_len 字节)+ 字节码
|
||
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 + kFuncNregsOff, funcs_[i].nregs);
|
||
put_le32(b, o + kFuncCodeOff, acc);
|
||
put_le32(b, o + kFuncLenOff, static_cast<uint32_t>(funcs_[i].code.size()));
|
||
for (size_t j = 0; j < funcs_[i].code.size(); ++j) {
|
||
b[off_code + acc + j] = funcs_[i].code[j];
|
||
}
|
||
acc += static_cast<uint32_t>(funcs_[i].code.size());
|
||
}
|
||
|
||
// 槽表(addr:u32 + 预留:u32)
|
||
for (size_t i = 0; i < slots_.size(); ++i) {
|
||
put_le32(b, off_slots + i * kSlotRowSize + kSlotAddrOff, slots_[i]);
|
||
put_le32(b, off_slots + i * kSlotRowSize + kSlotPadOff, 0);
|
||
}
|
||
|
||
// 值段
|
||
for (size_t i = 0; i < values_.size(); ++i) {
|
||
b[off_values + i] = values_[i];
|
||
}
|
||
|
||
// SHA-256 文件尾(对文件尾之前全部内容)
|
||
const size_t content_len = off_values + values_.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;
|
||
}
|
||
|
||
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);
|
||
}
|
||
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_;
|
||
const std::vector<SourceUnit>& units_;
|
||
const LinkResult& link_;
|
||
const MachineConfig& cfg_;
|
||
std::vector<uint8_t>* image_;
|
||
std::string* err_;
|
||
std::vector<FuncCtx> funcs_;
|
||
std::vector<uint64_t> consts_; ///< 常量表(8B 原始值,位模式去重)
|
||
std::map<std::string, bool> io_input_;
|
||
std::map<std::string, bool> io_output_;
|
||
std::vector<uint32_t> slots_; ///< 槽表 addr 列表
|
||
std::vector<uint8_t> values_; ///< 值段字节
|
||
std::map<std::string, uint32_t> global_slot_; ///< 全局名 → 槽号
|
||
std::map<std::string, InstFields> instances_; ///< "POU名/实例名" → 实例布局
|
||
const InstFields* inline_fields_ = nullptr; ///< 内联 FB 字段表
|
||
uint32_t max_stack_ = 0; ///< 栈区字节数
|
||
CodegenMap* map_ = nullptr; ///< 变量/槽映射输出(可空)
|
||
};
|
||
|
||
} // namespace
|
||
|
||
/**
|
||
* @brief 编译工程为 .stb 映像字节(对外入口,V2)。
|
||
* @param proj 工程定义
|
||
* @param units 全部源文件的 AST
|
||
* @param link 链接结果
|
||
* @param cfg 机器定义(machine.toml V2)
|
||
* @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, CodegenMap* map) {
|
||
Builder b(proj, units, link, cfg, image, err, map);
|
||
return b.run();
|
||
}
|
||
|
||
} // namespace compiler
|