阶段 B 步骤 4:compiler 配置驱动化,去除 isa 依赖。
- Project.h 自带 IoBinding;Stb.h/cpp:compiler 侧 .stb 规范(常量/ConstEntry/FNV/StbView/读写/sidecar) - Codec.h/cpp:指令字 pack/字段/配置驱动 disasm(format 表驱动) - Codegen:opcode 全查 MachineConfig(E_* 发射器)、常量 tag 查配置类型、初值按基元宽度写、 映像拼装用 Stb 常量;codegen_project 增加 cfg 参数 - main.cpp:--machine <path>(编译/反汇编必填),--disasm 用 StbView+Codec - CMake:compiler 不再链接 isa(仅 toml++ 私有);测试补链 isa(自身断言用) - 验证:20 用例字节不变(hash 0xc3fe4ae74ad45900 相同)、ctest 12/12、缺 --machine 报错退出
This commit is contained in:
@@ -7,6 +7,7 @@ project(Compiler
|
|||||||
DESCRIPTION "ST / toml 编译器")
|
DESCRIPTION "ST / toml 编译器")
|
||||||
|
|
||||||
# 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里
|
# 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里
|
||||||
|
# 指令 opcode / 类型 / FB 布局来自 machine.toml(MachineConfig),不依赖 isa
|
||||||
add_library(compiler STATIC
|
add_library(compiler STATIC
|
||||||
./src/Lexer.cpp
|
./src/Lexer.cpp
|
||||||
./src/Project.cpp
|
./src/Project.cpp
|
||||||
@@ -14,11 +15,12 @@ add_library(compiler STATIC
|
|||||||
./src/Linker.cpp
|
./src/Linker.cpp
|
||||||
./src/Typecheck.cpp
|
./src/Typecheck.cpp
|
||||||
./src/Codegen.cpp
|
./src/Codegen.cpp
|
||||||
./src/MachineConfig.cpp)
|
./src/MachineConfig.cpp
|
||||||
|
./src/Stb.cpp
|
||||||
|
./src/Codec.cpp)
|
||||||
|
|
||||||
target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include)
|
target_include_directories(compiler PRIVATE ${CMAKE_SOURCE_DIR}/third_party/tomlplusplus/include)
|
||||||
target_link_libraries(compiler PUBLIC isa)
|
|
||||||
|
|
||||||
# 可执行入口:STCompiler <project.toml> -o <name>.stb
|
# 可执行入口:STCompiler <project.toml> -o <name>.stb
|
||||||
add_executable(STCompiler
|
add_executable(STCompiler
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* @file Codec.h
|
||||||
|
* @brief 指令字编解码 + 配置驱动反汇编(编译器侧)
|
||||||
|
* @author
|
||||||
|
* @date 2026-08-21
|
||||||
|
*
|
||||||
|
* @details 编译器不依赖 isa:指令字布局([op:8|rd:8|a:8|b:8] 小端)与
|
||||||
|
* 反汇编输出(按 machine.toml 的 format/参数名)全部由本模块 + MachineConfig 提供,
|
||||||
|
* 保证与执行器(isa)产生的字节一致。
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "compiler/MachineConfig.h"
|
||||||
|
|
||||||
|
namespace compiler {
|
||||||
|
|
||||||
|
// 指令字:[op:8 | rd:8 | a:8 | b:8],小端 u32
|
||||||
|
typedef uint32_t Instr;
|
||||||
|
|
||||||
|
Instr pack(uint8_t opcode, uint8_t rd, uint8_t a, uint8_t b);
|
||||||
|
|
||||||
|
uint8_t op_of(Instr w);
|
||||||
|
uint8_t rd_of(Instr w);
|
||||||
|
uint8_t a_of(Instr w);
|
||||||
|
uint8_t b_of(Instr w);
|
||||||
|
uint16_t imm16_of(Instr w); // a|b 拼 u16(const_id / slot / fn_id)
|
||||||
|
int16_t off16_of(Instr w); // a|b 有符号偏移(相对下一条)
|
||||||
|
|
||||||
|
// 配置驱动反汇编:按 machine.toml 的 format 输出一行文本。
|
||||||
|
// 未知操作码 → "??? 0x<hex>"。输出格式与 Doc/isa/指令与映像.md 一致。
|
||||||
|
void disasm(const MachineConfig& cfg, Instr w, char* out, size_t cap);
|
||||||
|
}
|
||||||
@@ -12,14 +12,16 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "compiler/Linker.h"
|
#include "compiler/Linker.h"
|
||||||
|
#include "compiler/MachineConfig.h"
|
||||||
#include "compiler/Parser.h"
|
#include "compiler/Parser.h"
|
||||||
#include "compiler/Project.h"
|
#include "compiler/Project.h"
|
||||||
|
|
||||||
namespace compiler {
|
namespace compiler {
|
||||||
|
|
||||||
// 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。
|
// 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。
|
||||||
|
// 指令 opcode / 类型 tag / FB 布局全部来自 machine.toml(cfg)。
|
||||||
// 失败返回 false,err 前缀 "codegen error"。
|
// 失败返回 false,err 前缀 "codegen error"。
|
||||||
bool codegen_project(const Project& proj, const std::vector<SourceUnit>& units,
|
bool codegen_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||||
const LinkResult& link, std::vector<uint8_t>* image,
|
const LinkResult& link, const MachineConfig& cfg,
|
||||||
std::string* err);
|
std::vector<uint8_t>* image, std::string* err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,17 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "isa/Image.h"
|
|
||||||
|
|
||||||
namespace compiler {
|
namespace compiler {
|
||||||
|
|
||||||
|
// I/O 绑定(project.toml [[io.*]];不创造变量,slot 由链接结果解析)
|
||||||
|
struct IoBinding {
|
||||||
|
std::string var;
|
||||||
|
uint32_t slot = 0;
|
||||||
|
uint32_t channel = 0;
|
||||||
|
uint32_t bit = 0;
|
||||||
|
bool is_input = false; // true = [[io.input]],false = [[io.output]]
|
||||||
|
};
|
||||||
|
|
||||||
// 与 Doc/初步计划.md 12.1 的 toml 字段表一一对应。
|
// 与 Doc/初步计划.md 12.1 的 toml 字段表一一对应。
|
||||||
// 字段白名单 / 必填 / io 完整性在 parse_project 内校验。
|
// 字段白名单 / 必填 / io 完整性在 parse_project 内校验。
|
||||||
struct Project {
|
struct Project {
|
||||||
@@ -24,7 +31,7 @@ namespace compiler {
|
|||||||
uint32_t dt_ms = 0; // 必填 > 0
|
uint32_t dt_ms = 0; // 必填 > 0
|
||||||
std::vector<std::string> files_st; // [files] 必填非空
|
std::vector<std::string> files_st; // [files] 必填非空
|
||||||
std::string gvl_file; // [gvl] 可选;无则空串
|
std::string gvl_file; // [gvl] 可选;无则空串
|
||||||
std::vector<isa::IoBinding> io; // [[io.*]] 可选;slot 12.6 才解析,此处填 0
|
std::vector<IoBinding> io; // [[io.*]] 可选;slot 12.6 才解析,此处填 0
|
||||||
std::string base_dir; // toml 所在目录(解析相对路径用)
|
std::string base_dir; // toml 所在目录(解析相对路径用)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,6 +44,6 @@ namespace compiler {
|
|||||||
std::vector<std::string> compile_files(const Project& p);
|
std::vector<std::string> compile_files(const Project& p);
|
||||||
|
|
||||||
// 校验全部文件存在并计算工程哈希:集合按路径排序,对内容做 FNV-1a 64 增量;
|
// 校验全部文件存在并计算工程哈希:集合按路径排序,对内容做 FNV-1a 64 增量;
|
||||||
// 空集合 = basis(isa::kFnvBasis)。缺文件报错前缀 "file missing"。
|
// 空集合 = basis(Stb::kFnvBasis)。缺文件报错前缀 "file missing"。
|
||||||
bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err);
|
bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* @file Stb.h
|
||||||
|
* @brief 编译器自带的 .stb 映像规范(写侧)+ 只读视图 + FNV-1a
|
||||||
|
* @author
|
||||||
|
* @date 2026-08-21
|
||||||
|
*
|
||||||
|
* @details 执行器(vm)各有自己的读写实现;格式契约见 Doc/isa/指令与映像.md。
|
||||||
|
* 12.13 修订(型号标识[32] 头 104 字节 + SHA-256 文件尾)在后续步骤落地。
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "compiler/Project.h"
|
||||||
|
|
||||||
|
namespace compiler {
|
||||||
|
|
||||||
|
// ---- .stb 格式常量(契约)----
|
||||||
|
static const uint32_t kMagic = 0x43545353u; // "STSC" 小端
|
||||||
|
static const uint32_t kVersion = 1;
|
||||||
|
static const size_t kHeaderSize = 72;
|
||||||
|
static const size_t kConstEntrySize = 12;
|
||||||
|
static const size_t kFuncRowSize = 12;
|
||||||
|
static const uint64_t kFnvBasis = 0xcbf29ce484222325ull;
|
||||||
|
static const uint64_t kFnvPrime = 0x100000001b3ull;
|
||||||
|
|
||||||
|
// 常量表一行(tag:0=BOOL、1=INT、2=TIME,契约)
|
||||||
|
struct ConstEntry {
|
||||||
|
uint32_t tag = 0;
|
||||||
|
uint64_t value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// FNV-1a 64(工程哈希,非密码学)
|
||||||
|
uint64_t fnv1a64_update(uint64_t h, const uint8_t* data, size_t len);
|
||||||
|
uint64_t fnv1a64(const uint8_t* data, size_t len);
|
||||||
|
|
||||||
|
// 只读视图(--disasm 用;校验魔数/版本/段边界)
|
||||||
|
class StbView {
|
||||||
|
public:
|
||||||
|
struct FuncRow {
|
||||||
|
uint32_t nregs = 0;
|
||||||
|
uint32_t code_offset = 0;
|
||||||
|
uint32_t code_len = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static StbView from(const uint8_t* buf, size_t len);
|
||||||
|
static StbView from(const std::vector<uint8_t>& buf);
|
||||||
|
|
||||||
|
bool ok() const { return ok_; }
|
||||||
|
const std::string& error() const { return err_; }
|
||||||
|
|
||||||
|
uint32_t cycle_limit() const { return cycle_limit_; }
|
||||||
|
uint32_t dt_ms() const { return dt_ms_; }
|
||||||
|
uint64_t project_hash() const { return project_hash_; }
|
||||||
|
uint32_t entry_fn_id() const { return entry_fn_id_; }
|
||||||
|
uint32_t n_globals() const { return n_globals_; }
|
||||||
|
uint32_t n_consts() const { return n_consts_; }
|
||||||
|
uint32_t n_funcs() const { return n_funcs_; }
|
||||||
|
uint32_t offset_code() const { return offset_code_; }
|
||||||
|
uint32_t offset_fb() const { return offset_fb_; }
|
||||||
|
uint32_t offset_data() const { return offset_data_; }
|
||||||
|
|
||||||
|
ConstEntry const_entry(size_t i) const;
|
||||||
|
FuncRow func_row(size_t i) const;
|
||||||
|
const uint8_t* code_bytes() const;
|
||||||
|
size_t code_len() const;
|
||||||
|
const uint8_t* data_bytes() const;
|
||||||
|
size_t data_len() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
StbView() : buf_(0), len_(0) {}
|
||||||
|
|
||||||
|
uint32_t offs_of_const() const; // offset_const(from 已校验段起点)
|
||||||
|
|
||||||
|
const uint8_t* buf_;
|
||||||
|
size_t len_;
|
||||||
|
bool ok_ = false;
|
||||||
|
std::string err_;
|
||||||
|
uint32_t cycle_limit_ = 0;
|
||||||
|
uint32_t dt_ms_ = 0;
|
||||||
|
uint64_t project_hash_ = 0;
|
||||||
|
uint32_t entry_fn_id_ = 0;
|
||||||
|
uint32_t n_globals_ = 0;
|
||||||
|
uint32_t n_consts_ = 0;
|
||||||
|
uint32_t n_funcs_ = 0;
|
||||||
|
uint32_t offset_code_ = 0;
|
||||||
|
uint32_t offset_fb_ = 0;
|
||||||
|
uint32_t offset_data_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 文件读写(纯字节;校验交给 StbView)
|
||||||
|
bool read_stb_file(const char* path, std::vector<uint8_t>* out, std::string* err);
|
||||||
|
bool write_stb_file(const char* path, const std::vector<uint8_t>& img, std::string* err);
|
||||||
|
|
||||||
|
// sidecar 生成与写文件(I/O 绑定 var → 槽号 → channel/bit)
|
||||||
|
std::string make_sidecar(const std::vector<IoBinding>& bindings);
|
||||||
|
bool write_sidecar_file(const char* path, const std::vector<IoBinding>& bindings,
|
||||||
|
std::string* err);
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* @file Codec.cpp
|
||||||
|
* @brief 指令字编解码 + 配置驱动反汇编(编译器侧)
|
||||||
|
* @author
|
||||||
|
* @date 2026-08-21
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "compiler/Codec.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace compiler {
|
||||||
|
|
||||||
|
Instr pack(uint8_t opcode, uint8_t rd, uint8_t a, uint8_t b) {
|
||||||
|
return static_cast<uint32_t>(opcode)
|
||||||
|
| (static_cast<uint32_t>(rd) << 8)
|
||||||
|
| (static_cast<uint32_t>(a) << 16)
|
||||||
|
| (static_cast<uint32_t>(b) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t op_of(Instr w) { return static_cast<uint8_t>(w & 0xFFu); }
|
||||||
|
uint8_t rd_of(Instr w) { return static_cast<uint8_t>((w >> 8) & 0xFFu); }
|
||||||
|
uint8_t a_of(Instr w) { return static_cast<uint8_t>((w >> 16) & 0xFFu); }
|
||||||
|
uint8_t b_of(Instr w) { return static_cast<uint8_t>((w >> 24) & 0xFFu); }
|
||||||
|
|
||||||
|
uint16_t imm16_of(Instr w) {
|
||||||
|
return static_cast<uint16_t>(a_of(w) | (static_cast<uint16_t>(b_of(w)) << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t off16_of(Instr w) { return static_cast<int16_t>(imm16_of(w)); }
|
||||||
|
|
||||||
|
void disasm(const MachineConfig& cfg, Instr w, char* out, size_t cap) {
|
||||||
|
if (cap == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
out[0] = '\0';
|
||||||
|
const ConfigOp* op = cfg.find_op_by_code(op_of(w));
|
||||||
|
if (op == nullptr) {
|
||||||
|
snprintf(out, cap, "??? 0x%08x", static_cast<unsigned>(w));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const uint8_t rd = rd_of(w);
|
||||||
|
const std::string& f = op->format;
|
||||||
|
if (f == "RR") {
|
||||||
|
snprintf(out, cap, "%s r%u, r%u", op->name.c_str(), static_cast<unsigned>(rd),
|
||||||
|
static_cast<unsigned>(a_of(w)));
|
||||||
|
} else if (f == "RRR") {
|
||||||
|
snprintf(out, cap, "%s r%u, r%u, r%u", op->name.c_str(), static_cast<unsigned>(rd),
|
||||||
|
static_cast<unsigned>(a_of(w)), static_cast<unsigned>(b_of(w)));
|
||||||
|
} else if (f == "IMM" || f == "SLOT") {
|
||||||
|
snprintf(out, cap, "%s r%u, %u", op->name.c_str(), static_cast<unsigned>(rd),
|
||||||
|
static_cast<unsigned>(imm16_of(w)));
|
||||||
|
} else if (f == "JMP") {
|
||||||
|
snprintf(out, cap, "%s %+d", op->name.c_str(), static_cast<int>(off16_of(w)));
|
||||||
|
} else if (f == "JC") {
|
||||||
|
snprintf(out, cap, "%s r%u, %+d", op->name.c_str(), static_cast<unsigned>(rd),
|
||||||
|
static_cast<int>(off16_of(w)));
|
||||||
|
} else if (f == "CALL" || f == "CAL") {
|
||||||
|
snprintf(out, cap, "%s %u", op->name.c_str(), static_cast<unsigned>(imm16_of(w)));
|
||||||
|
} else { // NONE
|
||||||
|
snprintf(out, cap, "%s", op->name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace compiler
|
||||||
+199
-336
@@ -1,38 +1,32 @@
|
|||||||
/**
|
/**
|
||||||
* @file Codegen.cpp
|
* @file Codegen.cpp
|
||||||
* @brief 寄存器码生成(12.8,切片 7:+ FB 实例、内联展开、CAL_*)
|
* @brief 寄存器码生成(配置驱动;compiler 不依赖 isa)
|
||||||
* @author
|
* @author
|
||||||
* @date 2026-08-21
|
* @date 2026-08-21
|
||||||
*
|
*
|
||||||
* @details 设计说明(详见 Doc/compiler/寄存器码.md):
|
* @details 设计说明(详见 Doc/compiler/寄存器码.md 与 Doc/isa/指令配置.md):
|
||||||
* - 切片 1~6:帧/字面量/MOVE/RET;全局数据区;短路 AND/OR;CMP 与 IF;WHILE 与四则;
|
* - 指令 opcode / 类型 tag / FB 布局全部来自 machine.toml(MachineConfig)
|
||||||
* FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+)。
|
* - 帧/字面量/MOVE/RET;全局数据区;短路 AND/OR;CMP 与 IF;WHILE 与四则;
|
||||||
* - 切片 7:FB 实例 → 数据区实例块(跨周期持久);字段槽号 = 实例基槽 + 字段序号
|
* FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+)
|
||||||
* (编译期算死);字段读写走 LOAD_GLOBAL / STORE_GLOBAL;
|
* - FB 实例 → 数据区实例块(跨周期持久);字段槽号 = 实例基槽 + 字段序号
|
||||||
* 内建 TON/TOF/CTU → 实参写字段后 CAL_* <实例基槽>;
|
* - 用户 FB 调用点内联展开;内建 FB → CAL_* <实例基槽>
|
||||||
* 用户 FB → 调用点内联展开(实参写字段 → FB 体以实例字段为变量编译)
|
* - 数据段每槽 8 字节定宽,指令 slot = 槽号
|
||||||
* - 方案 a(12.9 前置):数据段**每槽 8 字节定宽**,指令 slot = 槽号(无映射)
|
|
||||||
*
|
*
|
||||||
* 函数清单:
|
* 函数清单:
|
||||||
* - put_le32 / put_le64 小端写入映像缓冲
|
* - Builder::Builder (构造)存工程/源文件/链接结果/机器配置/输出,收集 io 绑定
|
||||||
* - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类
|
* - Builder::run 布局数据区 → 逐 POU 建函数 → 拼映像
|
||||||
* - Builder::run 布局数据区(全局 + 实例)→ 逐 POU 建函数 → 拼映像
|
* - Builder::fail / opc 错误 / 按名查配置 opcode
|
||||||
* - Builder::fail 组装 "codegen error: <msg>" 返回 false
|
* - 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
|
* - find_pou / find_fn_id 按名查 POU AST / fn_id
|
||||||
* - build_function 编译一个 POU(PROGRAM/FUNCTION/FB 骨架;帧约定分配)
|
* - build_function 编译一个 POU(帧约定分配;FB 占位)
|
||||||
* - begin_stmt / alloc_temp 临时寄存器:语句内递增、语句结束复用基址
|
* - begin_stmt / alloc_temp 临时寄存器管理
|
||||||
* - compile_stmt 语句编译(赋值 / IF / WHILE / FB 调用)
|
* - compile_stmt 语句编译(赋值 / IF / WHILE / FB 调用)
|
||||||
* - compile_if / compile_while IF 链 / WHILE 回环
|
* - compile_if / compile_while / compile_fb_call / compile_fb_inline
|
||||||
* - compile_fb_call FB 调用:内建 CAL_* 或用户内联展开
|
* - store_target / compile_expr 左值存储 / 表达式编译
|
||||||
* - compile_fb_inline 内联编译用户 FB 体(实例字段为变量)
|
* - arith_name / cmp_name / load_name / store_name 操作码名选择
|
||||||
* - store_target 赋值左值:STORE_* 到全局槽(或内联模式实例字段)
|
* - patch_jump 回填跳转偏移(相对下一条)
|
||||||
* - compile_expr 表达式编译到寄存器(含 Field 字段读)
|
* - layout_data / layout_fb_instances / instance_of / init_of
|
||||||
* - cmp_op / arith_op / load_op / store_op 操作码选择
|
* - const_id 取常量表 id(tag 来自配置类型表)
|
||||||
* - global_offset 槽号恒等(方案 a 无映射)
|
|
||||||
* - instance_of 实例 → 字段槽号表
|
|
||||||
* - patch_jump 回填跳转偏移(相对下一条指令)
|
|
||||||
* - layout_data / layout_fb_instances / init_of 数据区布局(8 字节定宽槽)
|
|
||||||
* - const_id 取常量表 id(无则追加)
|
|
||||||
* - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段
|
* - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段
|
||||||
* - codegen_project 对外入口
|
* - codegen_project 对外入口
|
||||||
*/
|
*/
|
||||||
@@ -45,31 +39,15 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "isa/Encode.h"
|
#include "compiler/Codec.h"
|
||||||
#include "isa/Image.h"
|
#include "compiler/MachineConfig.h"
|
||||||
#include "isa/Instr.h"
|
#include "compiler/Stb.h"
|
||||||
#include "isa/Types.h"
|
|
||||||
|
|
||||||
namespace compiler {
|
namespace compiler {
|
||||||
namespace {
|
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)
|
* @brief 代码生成器
|
||||||
*/
|
*/
|
||||||
class Builder {
|
class Builder {
|
||||||
public:
|
public:
|
||||||
@@ -78,14 +56,16 @@ namespace {
|
|||||||
* @param proj 工程定义(cycle_limit / dt_ms / 哈希用)
|
* @param proj 工程定义(cycle_limit / dt_ms / 哈希用)
|
||||||
* @param units 全部源文件的 AST
|
* @param units 全部源文件的 AST
|
||||||
* @param link 链接结果(POU 顺序 / 符号)
|
* @param link 链接结果(POU 顺序 / 符号)
|
||||||
|
* @param cfg 机器定义(machine.toml 强校验后;指令 opcode / 类型 tag / FB 布局)
|
||||||
* @param image 输出映像字节
|
* @param image 输出映像字节
|
||||||
* @param err 错误输出;可为 nullptr(静默)
|
* @param err 错误输出;可为 nullptr(静默)
|
||||||
*/
|
*/
|
||||||
Builder(const Project& proj, const std::vector<SourceUnit>& units,
|
Builder(const Project& proj, const std::vector<SourceUnit>& units,
|
||||||
const LinkResult& link, std::vector<uint8_t>* image, std::string* err)
|
const LinkResult& link, const MachineConfig& cfg,
|
||||||
: proj_(proj), units_(units), link_(link), image_(image), err_(err) {
|
std::vector<uint8_t>* image, std::string* err)
|
||||||
|
: proj_(proj), units_(units), link_(link), cfg_(cfg), image_(image), err_(err) {
|
||||||
// io 绑定分类(名已折小写;不创造变量,只影响操作码选择)
|
// io 绑定分类(名已折小写;不创造变量,只影响操作码选择)
|
||||||
for (const isa::IoBinding& b : proj_.io) {
|
for (const IoBinding& b : proj_.io) {
|
||||||
std::string key = b.var;
|
std::string key = b.var;
|
||||||
for (char& ch : key) {
|
for (char& ch : key) {
|
||||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||||
@@ -100,7 +80,6 @@ namespace {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像
|
* @brief 布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像
|
||||||
* @details 偏移先定(函数编译引用 global_offset / instance_of)
|
|
||||||
* @return true 成功;false(err 已写,前缀 "codegen error")
|
* @return true 成功;false(err 已写,前缀 "codegen error")
|
||||||
*/
|
*/
|
||||||
bool run() {
|
bool run() {
|
||||||
@@ -127,11 +106,6 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
|
||||||
* @brief 组装错误消息并返回 false
|
|
||||||
* @param msg 错误描述(不含前缀)
|
|
||||||
* @return 恒 false
|
|
||||||
*/
|
|
||||||
bool fail(const std::string& msg) {
|
bool fail(const std::string& msg) {
|
||||||
if (err_) {
|
if (err_) {
|
||||||
*err_ = "codegen error: " + msg;
|
*err_ = "codegen error: " + msg;
|
||||||
@@ -140,10 +114,42 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 按名查 POU AST
|
* @brief 按名查配置 opcode(MachineConfig 强校验保证存在)
|
||||||
* @param name POU 名(小写)
|
* @param name 指令名(大写,如 "ADD")
|
||||||
* @return POU 指针;未找到返回 nullptr
|
* @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)----
|
||||||
|
Instr E_rr(const char* name, uint8_t rd, uint8_t rs) {
|
||||||
|
return pack(opc(name), rd, rs, 0);
|
||||||
|
}
|
||||||
|
Instr E_rrr(const char* name, uint8_t rd, uint8_t ra, uint8_t rb) {
|
||||||
|
return pack(opc(name), rd, ra, rb);
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
Instr E_slot(const char* name, uint8_t rd, uint16_t slot) {
|
||||||
|
return E_imm(name, rd, slot);
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
Instr E_call(uint16_t fn_id) { return E_imm("CALL", 0, fn_id); }
|
||||||
|
Instr E_ret() { return pack(opc("RET"), 0, 0, 0); }
|
||||||
|
|
||||||
const POU* find_pou(const std::string& name) const {
|
const POU* find_pou(const std::string& name) const {
|
||||||
for (const SourceUnit& u : units_) {
|
for (const SourceUnit& u : units_) {
|
||||||
for (const POU& p : u.ast.pous) {
|
for (const POU& p : u.ast.pous) {
|
||||||
@@ -159,33 +165,24 @@ namespace {
|
|||||||
struct FuncCtx {
|
struct FuncCtx {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string pou_name; // 所属 POU 名(实例查找键)
|
std::string pou_name; // 所属 POU 名(实例查找键)
|
||||||
std::vector<isa::Instr> code; // 字节码(函数表 code_offset 相对此段)
|
std::vector<Instr> code; // 字节码
|
||||||
std::map<std::string, uint8_t> regs; // 变量名 → 帧寄存器
|
std::map<std::string, uint8_t> regs; // 变量名 → 帧寄存器
|
||||||
uint8_t nlocals = 0; // 变量区终点 = 临时寄存器起始
|
uint8_t nlocals = 0; // 变量区终点 = 临时寄存器起始
|
||||||
uint8_t nregs = 0; // 峰值(变量 + 临时)
|
uint8_t nregs = 0; // 峰值(变量 + 临时)
|
||||||
uint8_t temp_used = 0; // 本语句已用临时数(语句结束清零)
|
uint8_t temp_used = 0; // 本语句已用临时数(语句结束清零)
|
||||||
bool is_function = false; // FUNCTION(结果 r0 / 输入只读)
|
bool is_function = false; // FUNCTION(结果 r0 / 输入只读)
|
||||||
std::string result_name; // FUNCTION 名(结果寄存器映射)
|
std::string result_name; // FUNCTION 名(结果寄存器映射)
|
||||||
};
|
};
|
||||||
|
|
||||||
// FB 实例:字段名 → 数据区字节地址
|
// FB 实例:字段名 → 数据区槽号
|
||||||
struct InstFields {
|
struct InstFields {
|
||||||
std::map<std::string, uint32_t> field_addr;
|
std::map<std::string, uint32_t> field_addr;
|
||||||
uint32_t base = 0;
|
uint32_t base = 0;
|
||||||
std::string type_name; // 实例的 FB 类型名(内建 / 用户)
|
std::string type_name; // 实例的 FB 类型名(内建 / 用户)
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 语句开始:临时寄存器基址复用
|
|
||||||
* @param f 当前函数
|
|
||||||
*/
|
|
||||||
void begin_stmt(FuncCtx& f) { f.temp_used = 0; }
|
void begin_stmt(FuncCtx& f) { f.temp_used = 0; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 分配一个临时寄存器(语句内递增)
|
|
||||||
* @param f 当前函数
|
|
||||||
* @return 临时寄存器号(可能更新 nregs 峰值)
|
|
||||||
*/
|
|
||||||
uint8_t alloc_temp(FuncCtx& f) {
|
uint8_t alloc_temp(FuncCtx& f) {
|
||||||
const uint8_t r = f.nlocals + f.temp_used;
|
const uint8_t r = f.nlocals + f.temp_used;
|
||||||
++f.temp_used;
|
++f.temp_used;
|
||||||
@@ -201,16 +198,12 @@ namespace {
|
|||||||
* FB 不生成业务字节码(调用点内联),只出空函数占位保持 fn_id 一致;
|
* FB 不生成业务字节码(调用点内联),只出空函数占位保持 fn_id 一致;
|
||||||
* 帧约定(见 Doc/compiler/寄存器码.md):结果 r0、输入 r1..r7(只读)、
|
* 帧约定(见 Doc/compiler/寄存器码.md):结果 r0、输入 r1..r7(只读)、
|
||||||
* 变量与临时全部从 r8 起
|
* 变量与临时全部从 r8 起
|
||||||
* @param pou POU AST
|
|
||||||
* @param f 输出函数编译态
|
|
||||||
* @return true 成功;false(err 已写)
|
|
||||||
*/
|
*/
|
||||||
bool build_function(const POU& pou, FuncCtx* f) {
|
bool build_function(const POU& pou, FuncCtx* f) {
|
||||||
if (pou.kind == PouKind::FunctionBlock) {
|
if (pou.kind == PouKind::FunctionBlock) {
|
||||||
// FB 内联展开(见 compile_fb_call),此处只占 fn_id
|
|
||||||
f->nlocals = 8;
|
f->nlocals = 8;
|
||||||
f->nregs = 8;
|
f->nregs = 8;
|
||||||
f->code.push_back(isa::enc_ret());
|
f->code.push_back(E_ret());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
f->is_function = pou.kind == PouKind::Function;
|
f->is_function = pou.kind == PouKind::Function;
|
||||||
@@ -229,16 +222,13 @@ namespace {
|
|||||||
}
|
}
|
||||||
uint8_t r = 8; // 变量/临时基址(调用约定区 r0..r7 不占用)
|
uint8_t r = 8; // 变量/临时基址(调用约定区 r0..r7 不占用)
|
||||||
for (const VarBlock& b : pou.blocks) {
|
for (const VarBlock& b : pou.blocks) {
|
||||||
// External / Global 走数据区槽,不占帧寄存器
|
|
||||||
if (b.section == VarSection::External || b.section == VarSection::Global) {
|
if (b.section == VarSection::External || b.section == VarSection::Global) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// FUNCTION 输入已分配(r1..r7)
|
|
||||||
if (f->is_function && b.section == VarSection::Input) {
|
if (f->is_function && b.section == VarSection::Input) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const VarDecl& d : b.vars) {
|
for (const VarDecl& d : b.vars) {
|
||||||
// FB 实例走数据区(layout_fb_instances),不占帧寄存器
|
|
||||||
if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) {
|
if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -253,16 +243,10 @@ namespace {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f->code.push_back(isa::enc_ret());
|
f->code.push_back(E_ret());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 编译一条语句(切片 4:赋值 / IF)
|
|
||||||
* @param f 当前函数
|
|
||||||
* @param st 语句 AST
|
|
||||||
* @return true 成功;false(err 已写)
|
|
||||||
*/
|
|
||||||
bool compile_stmt(FuncCtx& f, const Stmt& st) {
|
bool compile_stmt(FuncCtx& f, const Stmt& st) {
|
||||||
if (st.kind == StmtKind::If) {
|
if (st.kind == StmtKind::If) {
|
||||||
return compile_if(f, st);
|
return compile_if(f, st);
|
||||||
@@ -274,7 +258,7 @@ namespace {
|
|||||||
return compile_fb_call(f, st);
|
return compile_fb_call(f, st);
|
||||||
}
|
}
|
||||||
if (st.kind != StmtKind::Assign) {
|
if (st.kind != StmtKind::Assign) {
|
||||||
return fail("statement not supported in slice 7");
|
return fail("statement not supported");
|
||||||
}
|
}
|
||||||
// 内联 FB 体内:左值可能是实例字段(STORE_GLOBAL)
|
// 内联 FB 体内:左值可能是实例字段(STORE_GLOBAL)
|
||||||
if (inline_fields_) {
|
if (inline_fields_) {
|
||||||
@@ -284,32 +268,20 @@ namespace {
|
|||||||
if (!compile_expr(f, *st.value, t)) {
|
if (!compile_expr(f, *st.value, t)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
f.code.push_back(isa::enc_slot(isa::Op::STORE_GLOBAL, t, fit->second));
|
f.code.push_back(E_slot("STORE_GLOBAL", t, fit->second));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto it = f.regs.find(st.target);
|
const auto it = f.regs.find(st.target);
|
||||||
if (it == f.regs.end()) {
|
if (it == f.regs.end()) {
|
||||||
// 全局 / 外部左值 → STORE_* 到数据区
|
|
||||||
return store_target(f, st.target, *st.value);
|
return store_target(f, st.target, *st.value);
|
||||||
}
|
}
|
||||||
// 函数输入只读(调用约定,见 Doc/compiler/寄存器码.md)
|
|
||||||
if (f.is_function && it->second >= 1 && it->second <= 7) {
|
if (f.is_function && it->second >= 1 && it->second <= 7) {
|
||||||
return fail("cannot write function input '" + st.target + "'");
|
return fail("cannot write function input '" + st.target + "'");
|
||||||
}
|
}
|
||||||
const uint8_t rd = it->second;
|
return compile_expr(f, *st.value, it->second);
|
||||||
return compile_expr(f, *st.value, rd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 编译 FB 调用
|
|
||||||
* @details 实参求值到临时 → STORE_GLOBAL 到实例字段;
|
|
||||||
* 内建 TON/TOF/CTU → CAL_* <实例偏移>;
|
|
||||||
* 用户 FB → compile_fb_inline 内联展开
|
|
||||||
* @param f 当前函数
|
|
||||||
* @param st FbCall 语句 AST
|
|
||||||
* @return true 成功;false(err 已写)
|
|
||||||
*/
|
|
||||||
bool compile_fb_call(FuncCtx& f, const Stmt& st) {
|
bool compile_fb_call(FuncCtx& f, const Stmt& st) {
|
||||||
const InstFields* inst = instance_of(f.pou_name, st.instance);
|
const InstFields* inst = instance_of(f.pou_name, st.instance);
|
||||||
if (inst == nullptr) {
|
if (inst == nullptr) {
|
||||||
@@ -324,22 +296,15 @@ namespace {
|
|||||||
if (!compile_expr(f, *a.value, t)) {
|
if (!compile_expr(f, *a.value, t)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
f.code.push_back(isa::enc_slot(isa::Op::STORE_GLOBAL, t, it->second));
|
f.code.push_back(E_slot("STORE_GLOBAL", t, it->second));
|
||||||
}
|
}
|
||||||
if (inst->type_name == "ton" || inst->type_name == "tof" ||
|
// 内建 FB:配置里必须有对应 op 行(强校验保证)→ 直接查配置 opcode
|
||||||
inst->type_name == "tp" || inst->type_name == "ctu" ||
|
const std::string& tn = inst->type_name;
|
||||||
inst->type_name == "ctd" || inst->type_name == "ctud" ||
|
if (cfg_.find_op("CAL_" + uppercase_of(tn)) != nullptr &&
|
||||||
inst->type_name == "r_trig" || inst->type_name == "f_trig") {
|
(tn == "ton" || tn == "tof" || tn == "tp" || tn == "ctu" ||
|
||||||
const isa::Op op =
|
tn == "ctd" || tn == "ctud" || tn == "r_trig" || tn == "f_trig")) {
|
||||||
inst->type_name == "ton" ? isa::Op::CAL_TON
|
const std::string opname = "CAL_" + uppercase_of(tn);
|
||||||
: inst->type_name == "tof" ? isa::Op::CAL_TOF
|
f.code.push_back(E_slot(opname.c_str(), 0, inst->base));
|
||||||
: inst->type_name == "tp" ? isa::Op::CAL_TP
|
|
||||||
: inst->type_name == "ctu" ? isa::Op::CAL_CTU
|
|
||||||
: inst->type_name == "ctd" ? isa::Op::CAL_CTD
|
|
||||||
: inst->type_name == "ctud" ? isa::Op::CAL_CTUD
|
|
||||||
: inst->type_name == "r_trig" ? isa::Op::CAL_R_TRIG
|
|
||||||
: isa::Op::CAL_F_TRIG;
|
|
||||||
f.code.push_back(isa::enc_slot(op, 0, inst->base));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const POU* fb = find_pou(inst->type_name);
|
const POU* fb = find_pou(inst->type_name);
|
||||||
@@ -349,15 +314,14 @@ namespace {
|
|||||||
return compile_fb_inline(f, *fb, *inst);
|
return compile_fb_inline(f, *fb, *inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static std::string uppercase_of(const std::string& s) {
|
||||||
* @brief 内联编译用户 FB 体
|
std::string out = s;
|
||||||
* @details 切到实例字段上下文(VarRef/赋值左值映射到字段地址);
|
for (char& ch : out) {
|
||||||
* 表达式临时与调用方共用 r8+;字段读写全走 LOAD/STORE_GLOBAL
|
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
|
||||||
* @param f 当前函数
|
}
|
||||||
* @param fb FB 类型 POU
|
return out;
|
||||||
* @param inst 本实例字段表
|
}
|
||||||
* @return true 成功;false(err 已写)
|
|
||||||
*/
|
|
||||||
bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) {
|
bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) {
|
||||||
const InstFields* saved = inline_fields_;
|
const InstFields* saved = inline_fields_;
|
||||||
inline_fields_ = &inst;
|
inline_fields_ = &inst;
|
||||||
@@ -371,14 +335,6 @@ namespace {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 编译 WHILE
|
|
||||||
* @details L_loop: <cond→t> JF t, L_end <body> JMP L_loop L_end:
|
|
||||||
* 条件 JF 与回环 JMP 双回填
|
|
||||||
* @param f 当前函数
|
|
||||||
* @param st WHILE 语句 AST
|
|
||||||
* @return true 成功;false(err 已写)
|
|
||||||
*/
|
|
||||||
bool compile_while(FuncCtx& f, const Stmt& st) {
|
bool compile_while(FuncCtx& f, const Stmt& st) {
|
||||||
const size_t loop = f.code.size();
|
const size_t loop = f.code.size();
|
||||||
const uint8_t t = alloc_temp(f);
|
const uint8_t t = alloc_temp(f);
|
||||||
@@ -386,7 +342,7 @@ namespace {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const size_t jf_idx = f.code.size();
|
const size_t jf_idx = f.code.size();
|
||||||
f.code.push_back(isa::enc_jc(isa::Op::JF, t, 0)); // 假 → L_end
|
f.code.push_back(E_jc("JF", t, 0));
|
||||||
for (const Stmt& s : st.body) {
|
for (const Stmt& s : st.body) {
|
||||||
begin_stmt(f);
|
begin_stmt(f);
|
||||||
if (!compile_stmt(f, s)) {
|
if (!compile_stmt(f, s)) {
|
||||||
@@ -394,29 +350,20 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const size_t jmp_idx = f.code.size();
|
const size_t jmp_idx = f.code.size();
|
||||||
f.code.push_back(isa::enc_jmp(0)); // 回环
|
f.code.push_back(E_jmp(0));
|
||||||
patch_jump(f, jmp_idx, loop);
|
patch_jump(f, jmp_idx, loop);
|
||||||
patch_jump(f, jf_idx, f.code.size());
|
patch_jump(f, jf_idx, f.code.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 编译 IF / ELSIF / ELSE
|
|
||||||
* @details 每分支:<cond→t> JF t, L_next → <body> → JMP L_end;
|
|
||||||
* 最后一个分支无 else 时 JF 直接落 L_end(不补 JMP)
|
|
||||||
* @param f 当前函数
|
|
||||||
* @param st IF 语句 AST
|
|
||||||
* @return true 成功;false(err 已写)
|
|
||||||
*/
|
|
||||||
bool compile_if(FuncCtx& f, const Stmt& st) {
|
bool compile_if(FuncCtx& f, const Stmt& st) {
|
||||||
// 条件链:IF 体 + ELSIF 各体;ELSE 体可选
|
|
||||||
std::vector<std::pair<const Expr*, const std::vector<Stmt>*>> branches;
|
std::vector<std::pair<const Expr*, const std::vector<Stmt>*>> branches;
|
||||||
branches.push_back({st.cond.get(), &st.body});
|
branches.push_back({st.cond.get(), &st.body});
|
||||||
for (const IfBranch& b : st.elsifs) {
|
for (const IfBranch& b : st.elsifs) {
|
||||||
branches.push_back({b.cond.get(), &b.body});
|
branches.push_back({b.cond.get(), &b.body});
|
||||||
}
|
}
|
||||||
const bool has_else = !st.else_body.empty();
|
const bool has_else = !st.else_body.empty();
|
||||||
std::vector<size_t> end_jmps; // 各分支尾部 JMP(回填到 L_end)
|
std::vector<size_t> end_jmps;
|
||||||
|
|
||||||
for (size_t i = 0; i < branches.size(); ++i) {
|
for (size_t i = 0; i < branches.size(); ++i) {
|
||||||
const uint8_t t = alloc_temp(f);
|
const uint8_t t = alloc_temp(f);
|
||||||
@@ -424,7 +371,7 @@ namespace {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const size_t jf_idx = f.code.size();
|
const size_t jf_idx = f.code.size();
|
||||||
f.code.push_back(isa::enc_jc(isa::Op::JF, t, 0)); // 假 → 下一分支
|
f.code.push_back(E_jc("JF", t, 0));
|
||||||
for (const Stmt& s : *branches[i].second) {
|
for (const Stmt& s : *branches[i].second) {
|
||||||
begin_stmt(f);
|
begin_stmt(f);
|
||||||
if (!compile_stmt(f, s)) {
|
if (!compile_stmt(f, s)) {
|
||||||
@@ -433,10 +380,10 @@ namespace {
|
|||||||
}
|
}
|
||||||
if (i + 1 < branches.size() || has_else) {
|
if (i + 1 < branches.size() || has_else) {
|
||||||
const size_t jmp_idx = f.code.size();
|
const size_t jmp_idx = f.code.size();
|
||||||
f.code.push_back(isa::enc_jmp(0)); // 分支尾部 → L_end
|
f.code.push_back(E_jmp(0));
|
||||||
end_jmps.push_back(jmp_idx);
|
end_jmps.push_back(jmp_idx);
|
||||||
}
|
}
|
||||||
patch_jump(f, jf_idx, f.code.size()); // 回填 JF 到下一分支起点
|
patch_jump(f, jf_idx, f.code.size());
|
||||||
}
|
}
|
||||||
if (has_else) {
|
if (has_else) {
|
||||||
for (const Stmt& s : st.else_body) {
|
for (const Stmt& s : st.else_body) {
|
||||||
@@ -453,13 +400,6 @@ namespace {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 赋值左值:帧寄存器直写或 STORE_* 到全局槽
|
|
||||||
* @param f 当前函数
|
|
||||||
* @param target 左值名(全局 / 外部)
|
|
||||||
* @param value 右值表达式
|
|
||||||
* @return true 成功;false(err 已写)
|
|
||||||
*/
|
|
||||||
bool store_target(FuncCtx& f, const std::string& target, const Expr& value) {
|
bool store_target(FuncCtx& f, const std::string& target, const Expr& value) {
|
||||||
const auto git = link_.global_index.find(target);
|
const auto git = link_.global_index.find(target);
|
||||||
if (git == link_.global_index.end()) {
|
if (git == link_.global_index.end()) {
|
||||||
@@ -472,49 +412,36 @@ namespace {
|
|||||||
if (!compile_expr(f, value, tmp)) {
|
if (!compile_expr(f, value, tmp)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const uint16_t slot = global_offset(git->second);
|
f.code.push_back(E_slot(store_name(target), tmp, static_cast<uint16_t>(git->second)));
|
||||||
f.code.push_back(isa::enc_slot(store_op(target), tmp, slot));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 表达式编译到目标寄存器
|
|
||||||
* @details 字面量 → LOADK;帧变量 → MOVE;全局/外部 → LOAD_*(io.input 用 LOAD_I);
|
|
||||||
* NOT → 求值后 NOT rd, t;AND/OR → 短路跳转(JF/JT 跳过右侧)
|
|
||||||
* @param f 当前函数
|
|
||||||
* @param e 表达式 AST
|
|
||||||
* @param rd 目标寄存器
|
|
||||||
* @return true 成功;false(err 已写)
|
|
||||||
*/
|
|
||||||
bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) {
|
bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) {
|
||||||
if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt ||
|
if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt ||
|
||||||
e.kind == ExprKind::LitTime) {
|
e.kind == ExprKind::LitTime) {
|
||||||
const isa::types::TypeTag tag =
|
const char* type_name = e.kind == ExprKind::LitBool ? "BOOL"
|
||||||
e.kind == ExprKind::LitBool ? isa::types::Bool
|
: e.kind == ExprKind::LitInt ? "INT"
|
||||||
: e.kind == ExprKind::LitInt ? isa::types::Int
|
: "TIME";
|
||||||
: isa::types::Time;
|
f.code.push_back(E_imm("LOADK", rd, const_id(type_name, e.int_value)));
|
||||||
f.code.push_back(
|
|
||||||
isa::enc_imm(isa::Op::LOADK, rd, const_id(tag, e.int_value)));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (e.kind == ExprKind::VarRef) {
|
if (e.kind == ExprKind::VarRef) {
|
||||||
// 内联 FB 体内:字段优先(实例字段为变量)
|
|
||||||
if (inline_fields_) {
|
if (inline_fields_) {
|
||||||
const auto fit = inline_fields_->field_addr.find(e.name);
|
const auto fit = inline_fields_->field_addr.find(e.name);
|
||||||
if (fit != inline_fields_->field_addr.end()) {
|
if (fit != inline_fields_->field_addr.end()) {
|
||||||
f.code.push_back(isa::enc_slot(isa::Op::LOAD_GLOBAL, rd, fit->second));
|
f.code.push_back(E_slot("LOAD_GLOBAL", rd, fit->second));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto sit = f.regs.find(e.name);
|
const auto sit = f.regs.find(e.name);
|
||||||
if (sit != f.regs.end()) {
|
if (sit != f.regs.end()) {
|
||||||
f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, sit->second));
|
f.code.push_back(E_rr("MOVE", rd, sit->second));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const auto git = link_.global_index.find(e.name);
|
const auto git = link_.global_index.find(e.name);
|
||||||
if (git != link_.global_index.end()) {
|
if (git != link_.global_index.end()) {
|
||||||
const uint16_t slot = global_offset(git->second);
|
f.code.push_back(
|
||||||
f.code.push_back(isa::enc_slot(load_op(e.name), rd, slot));
|
E_slot(load_name(e.name), rd, static_cast<uint16_t>(git->second)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return fail("no register or slot for '" + e.name + "'");
|
return fail("no register or slot for '" + e.name + "'");
|
||||||
@@ -528,7 +455,7 @@ namespace {
|
|||||||
if (fit == inst->field_addr.end()) {
|
if (fit == inst->field_addr.end()) {
|
||||||
return fail("unknown field '" + e.field + "' for '" + e.name + "'");
|
return fail("unknown field '" + e.field + "' for '" + e.name + "'");
|
||||||
}
|
}
|
||||||
f.code.push_back(isa::enc_slot(isa::Op::LOAD_GLOBAL, rd, fit->second));
|
f.code.push_back(E_slot("LOAD_GLOBAL", rd, fit->second));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (e.kind == ExprKind::Add || e.kind == ExprKind::Sub ||
|
if (e.kind == ExprKind::Add || e.kind == ExprKind::Sub ||
|
||||||
@@ -541,7 +468,7 @@ namespace {
|
|||||||
if (!compile_expr(f, *e.rhs, r)) {
|
if (!compile_expr(f, *e.rhs, r)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
f.code.push_back(isa::enc_rrr(arith_op(e.kind), rd, l, r));
|
f.code.push_back(E_rrr(arith_name(e.kind), rd, l, r));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (e.kind == ExprKind::Cmp) {
|
if (e.kind == ExprKind::Cmp) {
|
||||||
@@ -553,7 +480,7 @@ namespace {
|
|||||||
if (!compile_expr(f, *e.rhs, r)) {
|
if (!compile_expr(f, *e.rhs, r)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
f.code.push_back(isa::enc_rrr(cmp_op(e.op), rd, l, r));
|
f.code.push_back(E_rrr(cmp_name(e.op), rd, l, r));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (e.kind == ExprKind::Not) {
|
if (e.kind == ExprKind::Not) {
|
||||||
@@ -561,27 +488,25 @@ namespace {
|
|||||||
if (!compile_expr(f, *e.operand, t)) {
|
if (!compile_expr(f, *e.operand, t)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
f.code.push_back(isa::enc_rr(isa::Op::NOT, rd, t));
|
f.code.push_back(E_rr("NOT", rd, t));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (e.kind == ExprKind::And || e.kind == ExprKind::Or) {
|
if (e.kind == ExprKind::And || e.kind == ExprKind::Or) {
|
||||||
// 短路:左侧结果在 rd;AND 为假 / OR 为真时跳过右侧
|
|
||||||
if (!compile_expr(f, *e.lhs, rd)) {
|
if (!compile_expr(f, *e.lhs, rd)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const isa::Op jop = (e.kind == ExprKind::And) ? isa::Op::JF : isa::Op::JT;
|
const char* jname = (e.kind == ExprKind::And) ? "JF" : "JT";
|
||||||
const size_t jmp_idx = f.code.size();
|
const size_t jmp_idx = f.code.size();
|
||||||
f.code.push_back(isa::enc_jc(jop, rd, 0)); // 偏移留洞,稍后回填
|
f.code.push_back(E_jc(jname, rd, 0));
|
||||||
const uint8_t t = alloc_temp(f);
|
const uint8_t t = alloc_temp(f);
|
||||||
if (!compile_expr(f, *e.rhs, t)) {
|
if (!compile_expr(f, *e.rhs, t)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, t));
|
f.code.push_back(E_rr("MOVE", rd, t));
|
||||||
patch_jump(f, jmp_idx, f.code.size());
|
patch_jump(f, jmp_idx, f.code.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (e.kind == ExprKind::Call) {
|
if (e.kind == ExprKind::Call) {
|
||||||
// 调用约定:实参求值到 r8+ 临时 → MOVE 到 r1..r7 → CALL → 结果 r0
|
|
||||||
if (e.args.size() > 7) {
|
if (e.args.size() > 7) {
|
||||||
return fail("too many arguments (max 7)");
|
return fail("too many arguments (max 7)");
|
||||||
}
|
}
|
||||||
@@ -598,21 +523,15 @@ namespace {
|
|||||||
return fail("no fn_id for '" + e.name + "'");
|
return fail("no fn_id for '" + e.name + "'");
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < args.size(); ++i) {
|
for (size_t i = 0; i < args.size(); ++i) {
|
||||||
f.code.push_back(
|
f.code.push_back(E_rr("MOVE", static_cast<uint8_t>(1 + i), args[i]));
|
||||||
isa::enc_rr(isa::Op::MOVE, static_cast<uint8_t>(1 + i), args[i]));
|
|
||||||
}
|
}
|
||||||
f.code.push_back(isa::enc_call(static_cast<uint16_t>(fn_id)));
|
f.code.push_back(E_call(static_cast<uint16_t>(fn_id)));
|
||||||
f.code.push_back(isa::enc_rr(isa::Op::MOVE, rd, 0));
|
f.code.push_back(E_rr("MOVE", rd, 0));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return fail("expression not supported in slice 6");
|
return fail("expression not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 按名查 fn_id(POU 收集序下标)
|
|
||||||
* @param name POU 名(小写)
|
|
||||||
* @return fn_id;未找到返回 -1
|
|
||||||
*/
|
|
||||||
int find_fn_id(const std::string& name) const {
|
int find_fn_id(const std::string& name) const {
|
||||||
for (size_t i = 0; i < link_.scopes.size(); ++i) {
|
for (size_t i = 0; i < link_.scopes.size(); ++i) {
|
||||||
if (link_.scopes[i].name == name) {
|
if (link_.scopes[i].name == name) {
|
||||||
@@ -622,87 +541,43 @@ namespace {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 回填跳转偏移(相对下一条指令)
|
|
||||||
* @param f 当前函数
|
|
||||||
* @param idx 跳转指令下标(占位)
|
|
||||||
* @param target_idx 目标指令下标
|
|
||||||
*/
|
|
||||||
void patch_jump(FuncCtx& f, size_t idx, size_t target_idx) {
|
void patch_jump(FuncCtx& f, size_t idx, size_t target_idx) {
|
||||||
const int16_t off = static_cast<int16_t>(
|
const int16_t off = static_cast<int16_t>(
|
||||||
static_cast<int64_t>(target_idx) - (static_cast<int64_t>(idx) + 1));
|
static_cast<int64_t>(target_idx) - (static_cast<int64_t>(idx) + 1));
|
||||||
const isa::Instr w = f.code[idx];
|
const Instr w = f.code[idx];
|
||||||
f.code[idx] = isa::pack(isa::op(w), isa::rd(w),
|
const uint16_t u = static_cast<uint16_t>(off);
|
||||||
static_cast<uint8_t>(static_cast<uint16_t>(off) & 0xFFu),
|
f.code[idx] = pack(op_of(w), rd_of(w), static_cast<uint8_t>(u & 0xFFu),
|
||||||
static_cast<uint8_t>((static_cast<uint16_t>(off) >> 8) & 0xFFu));
|
static_cast<uint8_t>((u >> 8) & 0xFFu));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ---- 操作码名(MachineConfig 已强校验存在)----
|
||||||
* @brief 算术运算 → 操作码
|
static const char* arith_name(ExprKind k) {
|
||||||
* @param k 表达式种类(Add/Sub/Mul/Div)
|
|
||||||
* @return 对应操作码
|
|
||||||
*/
|
|
||||||
isa::Op arith_op(ExprKind k) const {
|
|
||||||
switch (k) {
|
switch (k) {
|
||||||
case ExprKind::Add: return isa::Op::ADD;
|
case ExprKind::Add: return "ADD";
|
||||||
case ExprKind::Sub: return isa::Op::SUB;
|
case ExprKind::Sub: return "SUB";
|
||||||
case ExprKind::Mul: return isa::Op::MUL;
|
case ExprKind::Mul: return "MUL";
|
||||||
case ExprKind::Div: return isa::Op::DIV;
|
case ExprKind::Div: return "DIV";
|
||||||
default: return isa::Op::ADD;
|
default: return "ADD";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static const char* cmp_name(BinOp op) {
|
||||||
/**
|
|
||||||
* @brief 比较符 → CMP_xx 操作码
|
|
||||||
* @param op 比较运算
|
|
||||||
* @return 对应操作码(CMP_EQ / CMP_NE / CMP_LT / CMP_LE / CMP_GT / CMP_GE)
|
|
||||||
*/
|
|
||||||
isa::Op cmp_op(BinOp op) const {
|
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case BinOp::Eq: return isa::Op::CMP_EQ;
|
case BinOp::Eq: return "CMP_EQ";
|
||||||
case BinOp::Ne: return isa::Op::CMP_NE;
|
case BinOp::Ne: return "CMP_NE";
|
||||||
case BinOp::Lt: return isa::Op::CMP_LT;
|
case BinOp::Lt: return "CMP_LT";
|
||||||
case BinOp::Le: return isa::Op::CMP_LE;
|
case BinOp::Le: return "CMP_LE";
|
||||||
case BinOp::Gt: return isa::Op::CMP_GT;
|
case BinOp::Gt: return "CMP_GT";
|
||||||
case BinOp::Ge: return isa::Op::CMP_GE;
|
case BinOp::Ge: return "CMP_GE";
|
||||||
default: return isa::Op::CMP_EQ; // Add/Sub/Mul/Div 不经此函数
|
default: return "CMP_EQ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const char* load_name(const std::string& name) const {
|
||||||
/**
|
return io_input_.count(name) ? "LOAD_I" : "LOAD_GLOBAL";
|
||||||
* @brief 按 io 绑定选读取操作码
|
}
|
||||||
* @param name 变量名(小写)
|
const char* store_name(const std::string& name) const {
|
||||||
* @return io.input 绑定 → LOAD_I;否则 LOAD_GLOBAL
|
return io_output_.count(name) ? "STORE_Q" : "STORE_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 逻辑槽号(符号表 address)即指令 slot(方案 a:8 字节定宽,无映射)
|
|
||||||
* @param slot 全局逻辑槽号
|
|
||||||
* @return 同值(u16 可容,layout_data 已校验)
|
|
||||||
*/
|
|
||||||
uint16_t global_offset(uint32_t slot) const {
|
|
||||||
return static_cast<uint16_t>(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 布局全局数据区:每槽 8 字节定宽(方案 a)
|
|
||||||
* @details 槽号 = 符号表 address(声明序,12.6);
|
|
||||||
* BOOL 用低 1 字节、INT 用低 2 字节(小端)、TIME 全 8 字节;
|
|
||||||
* 初值取自 GVL 声明的 has_init(无则 0)
|
|
||||||
* @return true 成功;false(槽号越界,err 已写)
|
|
||||||
*/
|
|
||||||
bool layout_data() {
|
bool layout_data() {
|
||||||
for (const Symbol& s : link_.globals) {
|
for (const Symbol& s : link_.globals) {
|
||||||
if (s.address > 0xFFFF) {
|
if (s.address > 0xFFFF) {
|
||||||
@@ -714,30 +589,33 @@ namespace {
|
|||||||
const int64_t v = has_init ? init : 0;
|
const int64_t v = has_init ? init : 0;
|
||||||
const size_t off = static_cast<size_t>(s.address) * 8;
|
const size_t off = static_cast<size_t>(s.address) * 8;
|
||||||
data_.resize(off + 8, 0);
|
data_.resize(off + 8, 0);
|
||||||
if (s.type_name == "int") {
|
// 初值按基元宽度写(类型元数据来自配置别名 → base)
|
||||||
|
const ConfigType* ct = cfg_.find_type(uppercase_of(s.type_name));
|
||||||
|
if (ct == nullptr) {
|
||||||
|
return fail("no type in machine.toml for '" + s.type_name + "'");
|
||||||
|
}
|
||||||
|
const PrimType* prim = MachineConfig::find_prim(ct->base);
|
||||||
|
if (prim == nullptr) {
|
||||||
|
return fail("no prim '" + ct->base + "' for type '" + ct->name + "'");
|
||||||
|
}
|
||||||
|
if (prim->width == 2 && !prim->is_float) {
|
||||||
const uint16_t iv = static_cast<uint16_t>(v);
|
const uint16_t iv = static_cast<uint16_t>(v);
|
||||||
data_[off] = static_cast<uint8_t>(iv & 0xFFu);
|
data_[off] = static_cast<uint8_t>(iv & 0xFFu);
|
||||||
data_[off + 1] = static_cast<uint8_t>((iv >> 8) & 0xFFu);
|
data_[off + 1] = static_cast<uint8_t>((iv >> 8) & 0xFFu);
|
||||||
} else if (s.type_name == "time") {
|
} else if (prim->width >= 8 && !prim->is_float) {
|
||||||
const uint64_t tv = static_cast<uint64_t>(v);
|
const uint64_t tv = static_cast<uint64_t>(v);
|
||||||
for (int i = 0; i < 8; ++i) {
|
for (int i = 0; i < 8; ++i) {
|
||||||
data_[off + i] = static_cast<uint8_t>((tv >> (8 * i)) & 0xFFu);
|
data_[off + i] = static_cast<uint8_t>((tv >> (8 * i)) & 0xFFu);
|
||||||
}
|
}
|
||||||
} else { // bool
|
} else {
|
||||||
data_[off] = v ? 1 : 0;
|
data_[off] = v ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 布局 FB 实例块:数据区全局之后,按 POU 收集序 / 实例声明序
|
|
||||||
* @details 每实例:字段按布局段序各占一槽(8 字节定宽),
|
|
||||||
* 字段地址 = 实例基槽 + 字段序号;实例基槽 = 累计槽数
|
|
||||||
* @return true 成功;false(err 已写)
|
|
||||||
*/
|
|
||||||
bool layout_fb_instances() {
|
bool layout_fb_instances() {
|
||||||
uint32_t cur = static_cast<uint32_t>(data_.size() / 8); // 槽号起点
|
uint32_t cur = static_cast<uint32_t>(data_.size() / 8);
|
||||||
bool any = false;
|
bool any = false;
|
||||||
for (const LinkResult::PouScope& sc : link_.scopes) {
|
for (const LinkResult::PouScope& sc : link_.scopes) {
|
||||||
for (const Symbol& s : sc.syms) {
|
for (const Symbol& s : sc.syms) {
|
||||||
@@ -749,7 +627,7 @@ namespace {
|
|||||||
return fail("no layout for instance '" + s.name + "'");
|
return fail("no layout for instance '" + s.name + "'");
|
||||||
}
|
}
|
||||||
InstFields inst;
|
InstFields inst;
|
||||||
inst.base = cur; // 实例基槽(CAL_* 操作数)
|
inst.base = cur;
|
||||||
inst.type_name = s.type_name;
|
inst.type_name = s.type_name;
|
||||||
for (size_t i = 0; i < lay->second.fields.size(); ++i) {
|
for (size_t i = 0; i < lay->second.fields.size(); ++i) {
|
||||||
inst.field_addr[lay->second.fields[i].name] = cur + i;
|
inst.field_addr[lay->second.fields[i].name] = cur + i;
|
||||||
@@ -759,31 +637,18 @@ namespace {
|
|||||||
any = true;
|
any = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 仅在有实例时把数据区补到实例区终点(无实例则保持全局区原样)
|
|
||||||
if (any) {
|
if (any) {
|
||||||
data_.resize(static_cast<size_t>(cur) * 8, 0);
|
data_.resize(static_cast<size_t>(cur) * 8, 0);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 按 POU + 实例名查实例字段表
|
|
||||||
* @param pou POU 名
|
|
||||||
* @param name 实例名
|
|
||||||
* @return 实例字段表指针;未找到返回 nullptr
|
|
||||||
*/
|
|
||||||
const InstFields* instance_of(const std::string& pou,
|
const InstFields* instance_of(const std::string& pou,
|
||||||
const std::string& name) const {
|
const std::string& name) const {
|
||||||
const auto it = instances_.find(pou + "/" + name);
|
const auto it = instances_.find(pou + "/" + name);
|
||||||
return it == instances_.end() ? nullptr : &it->second;
|
return it == instances_.end() ? nullptr : &it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 查全局声明的初值(AST;gvl 文件顶层段,声明序 = 槽号序)
|
|
||||||
* @param name 全局名(小写)
|
|
||||||
* @param has_init 输出是否有初值
|
|
||||||
* @param init 输出初值
|
|
||||||
*/
|
|
||||||
void init_of(const std::string& name, bool* has_init, int64_t* init) const {
|
void init_of(const std::string& name, bool* has_init, int64_t* init) const {
|
||||||
for (const SourceUnit& u : units_) {
|
for (const SourceUnit& u : units_) {
|
||||||
for (const VarBlock& b : u.ast.globals) {
|
for (const VarBlock& b : u.ast.globals) {
|
||||||
@@ -802,11 +667,13 @@ namespace {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 取常量表 id(无则追加)
|
* @brief 取常量表 id(无则追加)
|
||||||
* @param tag 类型标记(BOOL/INT/TIME)
|
* @param type_name 语言类型名("BOOL"/"INT"/"TIME")→ 配置 tag(契约)
|
||||||
* @param value 常量值
|
* @param value 常量值
|
||||||
* @return const_id(u16)
|
* @return const_id(u16)
|
||||||
*/
|
*/
|
||||||
uint16_t const_id(isa::types::TypeTag tag, int64_t value) {
|
uint16_t const_id(const char* type_name, int64_t value) {
|
||||||
|
const ConfigType* ct = cfg_.find_type(type_name);
|
||||||
|
const uint32_t tag = ct ? ct->tag : 0;
|
||||||
for (size_t i = 0; i < consts_.size(); ++i) {
|
for (size_t i = 0; i < consts_.size(); ++i) {
|
||||||
if (consts_[i].tag == tag && consts_[i].value == static_cast<uint64_t>(value)) {
|
if (consts_[i].tag == tag && consts_[i].value == static_cast<uint64_t>(value)) {
|
||||||
return static_cast<uint16_t>(i);
|
return static_cast<uint16_t>(i);
|
||||||
@@ -816,18 +683,12 @@ namespace {
|
|||||||
return static_cast<uint16_t>(consts_.size() - 1);
|
return static_cast<uint16_t>(consts_.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 拼映像:头 + 常量表 + 函数表 + 字节码 + 数据段
|
|
||||||
* @details 段序:const → funcs → code → fb(空)→ data;
|
|
||||||
* 数据段放全局初值(layout_data 已生成字节)
|
|
||||||
* @return true 成功;false(err 已写)
|
|
||||||
*/
|
|
||||||
bool assemble_image() {
|
bool assemble_image() {
|
||||||
const uint32_t off_const = isa::kHeaderSize;
|
const uint32_t off_const = static_cast<uint32_t>(kHeaderSize);
|
||||||
const uint32_t off_funcs = off_const +
|
const uint32_t off_funcs = off_const +
|
||||||
static_cast<uint32_t>(consts_.size()) * isa::kConstEntrySize;
|
static_cast<uint32_t>(consts_.size()) * static_cast<uint32_t>(kConstEntrySize);
|
||||||
uint32_t off_code = off_funcs +
|
uint32_t off_code = off_funcs +
|
||||||
static_cast<uint32_t>(funcs_.size()) * isa::kFuncRowSize;
|
static_cast<uint32_t>(funcs_.size()) * static_cast<uint32_t>(kFuncRowSize);
|
||||||
uint32_t code_total = 0;
|
uint32_t code_total = 0;
|
||||||
for (const FuncCtx& f : funcs_) {
|
for (const FuncCtx& f : funcs_) {
|
||||||
code_total += static_cast<uint32_t>(f.code.size()) * 4;
|
code_total += static_cast<uint32_t>(f.code.size()) * 4;
|
||||||
@@ -837,12 +698,12 @@ namespace {
|
|||||||
|
|
||||||
std::vector<uint8_t>& b = *image_;
|
std::vector<uint8_t>& b = *image_;
|
||||||
b.assign(off_end, 0);
|
b.assign(off_end, 0);
|
||||||
put_le32(b, 0, isa::kMagic);
|
put_le32(b, 0, kMagic);
|
||||||
put_le32(b, 4, isa::kVersion);
|
put_le32(b, 4, kVersion);
|
||||||
put_le32(b, 8, proj_.cycle_limit);
|
put_le32(b, 8, proj_.cycle_limit);
|
||||||
put_le32(b, 12, proj_.dt_ms);
|
put_le32(b, 12, proj_.dt_ms);
|
||||||
|
|
||||||
uint64_t hash = isa::kFnvBasis;
|
uint64_t hash = kFnvBasis;
|
||||||
if (!compute_project_hash(proj_, &hash, err_)) {
|
if (!compute_project_hash(proj_, &hash, err_)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -855,80 +716,82 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
put_le32(b, 24, entry);
|
put_le32(b, 24, entry);
|
||||||
put_le32(b, 28, static_cast<uint32_t>(link_.globals.size())); // n_globals
|
put_le32(b, 28, static_cast<uint32_t>(link_.globals.size()));
|
||||||
put_le32(b, 32, 0); // n_i
|
put_le32(b, 32, 0);
|
||||||
put_le32(b, 36, 0); // n_q
|
put_le32(b, 36, 0);
|
||||||
put_le32(b, 40, 0); // n_m
|
put_le32(b, 40, 0);
|
||||||
put_le32(b, 44, static_cast<uint32_t>(consts_.size()));
|
put_le32(b, 44, static_cast<uint32_t>(consts_.size()));
|
||||||
put_le32(b, 48, static_cast<uint32_t>(funcs_.size()));
|
put_le32(b, 48, static_cast<uint32_t>(funcs_.size()));
|
||||||
put_le32(b, 52, off_const);
|
put_le32(b, 52, off_const);
|
||||||
put_le32(b, 56, off_funcs);
|
put_le32(b, 56, off_funcs);
|
||||||
put_le32(b, 60, off_code);
|
put_le32(b, 60, off_code);
|
||||||
put_le32(b, 64, off_data); // offset_fb(空,与数据段起点相同)
|
put_le32(b, 64, off_data);
|
||||||
put_le32(b, 68, off_data); // offset_data
|
put_le32(b, 68, off_data);
|
||||||
|
|
||||||
for (size_t i = 0; i < consts_.size(); ++i) {
|
for (size_t i = 0; i < consts_.size(); ++i) {
|
||||||
const size_t o = off_const + i * isa::kConstEntrySize;
|
const size_t o = off_const + i * kConstEntrySize;
|
||||||
put_le32(b, o, static_cast<uint32_t>(consts_[i].tag));
|
put_le32(b, o, consts_[i].tag);
|
||||||
put_le64(b, o + 4, consts_[i].value);
|
put_le64(b, o + 4, consts_[i].value);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t c = off_code;
|
uint32_t c = off_code;
|
||||||
for (size_t i = 0; i < funcs_.size(); ++i) {
|
for (size_t i = 0; i < funcs_.size(); ++i) {
|
||||||
const FuncCtx& f = funcs_[i];
|
const FuncCtx& f = funcs_[i];
|
||||||
const size_t o = off_funcs + i * isa::kFuncRowSize;
|
const size_t o = off_funcs + i * kFuncRowSize;
|
||||||
put_le32(b, o, f.nregs);
|
put_le32(b, o, f.nregs);
|
||||||
put_le32(b, o + 8, static_cast<uint32_t>(f.code.size()));
|
put_le32(b, o + 8, static_cast<uint32_t>(f.code.size()));
|
||||||
for (const isa::Instr in : f.code) {
|
for (const Instr in : f.code) {
|
||||||
put_le32(b, c, in);
|
put_le32(b, c, in);
|
||||||
c += 4;
|
c += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// code_offset 回填(相对字节码段起点)
|
|
||||||
uint32_t acc = 0;
|
uint32_t acc = 0;
|
||||||
for (size_t i = 0; i < funcs_.size(); ++i) {
|
for (size_t i = 0; i < funcs_.size(); ++i) {
|
||||||
const size_t o = off_funcs + i * isa::kFuncRowSize;
|
const size_t o = off_funcs + i * kFuncRowSize;
|
||||||
put_le32(b, o + 4, acc);
|
put_le32(b, o + 4, acc);
|
||||||
acc += static_cast<uint32_t>(funcs_[i].code.size()) * 4;
|
acc += static_cast<uint32_t>(funcs_[i].code.size()) * 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数据段:全局初值字节
|
|
||||||
for (size_t i = 0; i < data_.size(); ++i) {
|
for (size_t i = 0; i < data_.size(); ++i) {
|
||||||
b[off_data + i] = data_[i];
|
b[off_data + i] = data_[i];
|
||||||
}
|
}
|
||||||
return true;
|
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 Project& proj_;
|
||||||
const std::vector<SourceUnit>& units_;
|
const std::vector<SourceUnit>& units_;
|
||||||
const LinkResult& link_;
|
const LinkResult& link_;
|
||||||
|
const MachineConfig& cfg_;
|
||||||
std::vector<uint8_t>* image_;
|
std::vector<uint8_t>* image_;
|
||||||
std::string* err_;
|
std::string* err_;
|
||||||
std::vector<FuncCtx> funcs_;
|
std::vector<FuncCtx> funcs_;
|
||||||
std::vector<isa::ConstEntry> consts_;
|
std::vector<ConstEntry> consts_;
|
||||||
std::map<std::string, bool> io_input_; // io.input 绑定名(小写)
|
std::map<std::string, bool> io_input_;
|
||||||
std::map<std::string, bool> io_output_; // io.output 绑定名(小写)
|
std::map<std::string, bool> io_output_;
|
||||||
std::vector<uint8_t> data_; // 数据区(8 字节定宽槽:全局 + FB 实例块)
|
std::vector<uint8_t> data_;
|
||||||
std::map<std::string, InstFields> instances_; // "POU/实例名" → 字段槽号
|
std::map<std::string, InstFields> instances_;
|
||||||
const InstFields* inline_fields_ = nullptr; // 内联 FB 体字段上下文(可空)
|
const InstFields* inline_fields_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // 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,
|
bool codegen_project(const Project& proj, const std::vector<SourceUnit>& units,
|
||||||
const LinkResult& link, std::vector<uint8_t>* image,
|
const LinkResult& link, const MachineConfig& cfg,
|
||||||
std::string* err) {
|
std::vector<uint8_t>* image, std::string* err) {
|
||||||
Builder b(proj, units, link, image, err);
|
Builder b(proj, units, link, cfg, image, err);
|
||||||
return b.run();
|
return b.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -662,7 +662,7 @@ namespace {
|
|||||||
* @return true 全部绑定合法;false(err 已写)
|
* @return true 全部绑定合法;false(err 已写)
|
||||||
*/
|
*/
|
||||||
bool check_io() {
|
bool check_io() {
|
||||||
for (const isa::IoBinding& b : proj_.io) {
|
for (const IoBinding& b : proj_.io) {
|
||||||
std::string key = b.var;
|
std::string key = b.var;
|
||||||
for (char& ch : key) {
|
for (char& ch : key) {
|
||||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "isa/Image.h"
|
#include "compiler/Stb.h"
|
||||||
|
|
||||||
namespace compiler {
|
namespace compiler {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -241,7 +241,7 @@ namespace {
|
|||||||
if (!check_keys(sec, is_input ? "io.input" : "io.output", allowed, 3, err)) {
|
if (!check_keys(sec, is_input ? "io.input" : "io.output", allowed, 3, err)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
isa::IoBinding b;
|
IoBinding b;
|
||||||
b.is_input = is_input;
|
b.is_input = is_input;
|
||||||
b.slot = 0; // 12.6 链接阶段再解析
|
b.slot = 0; // 12.6 链接阶段再解析
|
||||||
if (!req_string(sec, is_input ? "io.input" : "io.output", "var", &b.var, err)) {
|
if (!req_string(sec, is_input ? "io.input" : "io.output", "var", &b.var, err)) {
|
||||||
@@ -353,7 +353,7 @@ bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err) {
|
|||||||
std::sort(sorted.begin(), sorted.end());
|
std::sort(sorted.begin(), sorted.end());
|
||||||
|
|
||||||
std::filesystem::path base(p.base_dir);
|
std::filesystem::path base(p.base_dir);
|
||||||
uint64_t h = isa::kFnvBasis;
|
uint64_t h = kFnvBasis;
|
||||||
for (const std::string& f : sorted) {
|
for (const std::string& f : sorted) {
|
||||||
std::ifstream in(base / f, std::ios::binary);
|
std::ifstream in(base / f, std::ios::binary);
|
||||||
if (!in) {
|
if (!in) {
|
||||||
@@ -365,7 +365,7 @@ bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err) {
|
|||||||
in.read(buf, sizeof buf);
|
in.read(buf, sizeof buf);
|
||||||
const std::streamsize n = in.gcount();
|
const std::streamsize n = in.gcount();
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
h = isa::fnv1a64_update(h, reinterpret_cast<const uint8_t*>(buf),
|
h = fnv1a64_update(h, reinterpret_cast<const uint8_t*>(buf),
|
||||||
static_cast<size_t>(n));
|
static_cast<size_t>(n));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
/**
|
||||||
|
* @file Stb.cpp
|
||||||
|
* @brief 编译器自带的 .stb 映像规范(写侧)+ 只读视图 + FNV-1a + sidecar
|
||||||
|
* @author
|
||||||
|
* @date 2026-08-21
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "compiler/Stb.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "compiler/Project.h"
|
||||||
|
|
||||||
|
namespace compiler {
|
||||||
|
|
||||||
|
uint64_t fnv1a64_update(uint64_t h, const uint8_t* data, size_t len) {
|
||||||
|
for (size_t i = 0; i < len; ++i) {
|
||||||
|
h ^= data[i];
|
||||||
|
h *= kFnvPrime;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t fnv1a64(const uint8_t* data, size_t len) {
|
||||||
|
return fnv1a64_update(kFnvBasis, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
uint32_t get_le32(const uint8_t* p) {
|
||||||
|
return static_cast<uint32_t>(p[0])
|
||||||
|
| (static_cast<uint32_t>(p[1]) << 8)
|
||||||
|
| (static_cast<uint32_t>(p[2]) << 16)
|
||||||
|
| (static_cast<uint32_t>(p[3]) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t get_le64(const uint8_t* p) {
|
||||||
|
uint64_t v = 0;
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
v |= static_cast<uint64_t>(p[i]) << (8 * i);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
StbView StbView::from(const uint8_t* buf, size_t len) {
|
||||||
|
StbView v;
|
||||||
|
v.buf_ = buf;
|
||||||
|
v.len_ = len;
|
||||||
|
if (buf == nullptr) {
|
||||||
|
v.err_ = "null buffer";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
if (len < kHeaderSize) {
|
||||||
|
v.err_ = "image too short";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
if (get_le32(buf + 0) != kMagic) {
|
||||||
|
v.err_ = "bad magic";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
if (get_le32(buf + 4) != kVersion) {
|
||||||
|
v.err_ = "bad version";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
v.cycle_limit_ = get_le32(buf + 8);
|
||||||
|
v.dt_ms_ = get_le32(buf + 12);
|
||||||
|
v.project_hash_ = get_le64(buf + 16);
|
||||||
|
v.entry_fn_id_ = get_le32(buf + 24);
|
||||||
|
v.n_globals_ = get_le32(buf + 28);
|
||||||
|
v.n_consts_ = get_le32(buf + 44);
|
||||||
|
v.n_funcs_ = get_le32(buf + 48);
|
||||||
|
v.offset_code_ = get_le32(buf + 60);
|
||||||
|
v.offset_fb_ = get_le32(buf + 64);
|
||||||
|
v.offset_data_ = get_le32(buf + 68);
|
||||||
|
|
||||||
|
// 段校验
|
||||||
|
const uint64_t offs[5] = {get_le32(buf + 52), get_le32(buf + 56), v.offset_code_,
|
||||||
|
v.offset_fb_, v.offset_data_};
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
if (offs[i] < kHeaderSize || offs[i] > len) {
|
||||||
|
v.err_ = "segment offset out of range";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
if (i > 0 && offs[i] < offs[i - 1]) {
|
||||||
|
v.err_ = "segment offsets not monotonic";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (offs[1] - offs[0] != static_cast<uint64_t>(v.n_consts_) * kConstEntrySize) {
|
||||||
|
v.err_ = "const table size mismatch";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
if (offs[2] - offs[1] != static_cast<uint64_t>(v.n_funcs_) * kFuncRowSize) {
|
||||||
|
v.err_ = "function table size mismatch";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
if ((v.offset_fb_ - v.offset_code_) % 4 != 0) {
|
||||||
|
v.err_ = "code segment not 4-byte aligned";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
if (v.entry_fn_id_ >= v.n_funcs_ && v.n_funcs_ != 0) {
|
||||||
|
v.err_ = "entry fn_id out of range";
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
v.ok_ = true;
|
||||||
|
v.err_.clear();
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
StbView StbView::from(const std::vector<uint8_t>& buf) {
|
||||||
|
return from(buf.data(), buf.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstEntry StbView::const_entry(size_t i) const {
|
||||||
|
ConstEntry e;
|
||||||
|
if (ok_ && i < n_consts_) {
|
||||||
|
const uint8_t* p = buf_ + offs_of_const() + i * kConstEntrySize;
|
||||||
|
e.tag = get_le32(p);
|
||||||
|
e.value = get_le64(p + 4);
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t StbView::offs_of_const() const {
|
||||||
|
// offset_const = offs[0],由 from() 已校验的段起点
|
||||||
|
return static_cast<uint32_t>(get_le32(buf_ + 52));
|
||||||
|
}
|
||||||
|
|
||||||
|
StbView::FuncRow StbView::func_row(size_t i) const {
|
||||||
|
FuncRow r;
|
||||||
|
if (ok_ && i < n_funcs_) {
|
||||||
|
const uint8_t* p = buf_ + get_le32(buf_ + 56) + i * kFuncRowSize;
|
||||||
|
r.nregs = get_le32(p + 0);
|
||||||
|
r.code_offset = get_le32(p + 4);
|
||||||
|
r.code_len = get_le32(p + 8);
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* StbView::code_bytes() const {
|
||||||
|
return ok_ ? buf_ + offset_code_ : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t StbView::code_len() const {
|
||||||
|
return ok_ ? offset_fb_ - offset_code_ : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* StbView::data_bytes() const {
|
||||||
|
return ok_ ? buf_ + offset_data_ : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t StbView::data_len() const {
|
||||||
|
return ok_ ? len_ - offset_data_ : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool read_stb_file(const char* path, std::vector<uint8_t>* out, std::string* err) {
|
||||||
|
std::ifstream in(path, std::ios::binary);
|
||||||
|
if (!in) {
|
||||||
|
if (err) {
|
||||||
|
*err = "cannot open for read: " + std::string(path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out->assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool write_stb_file(const char* path, const std::vector<uint8_t>& img, std::string* err) {
|
||||||
|
std::FILE* f = std::fopen(path, "wb");
|
||||||
|
if (f == nullptr) {
|
||||||
|
if (err) {
|
||||||
|
*err = "cannot open for write: " + std::string(path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bool ok = img.empty() || std::fwrite(&img[0], 1, img.size(), f) == img.size();
|
||||||
|
std::fclose(f);
|
||||||
|
if (!ok && err) {
|
||||||
|
*err = "write failed: " + std::string(path);
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string make_sidecar(const std::vector<IoBinding>& bindings) {
|
||||||
|
std::string out;
|
||||||
|
for (const IoBinding& b : bindings) {
|
||||||
|
char buf[64];
|
||||||
|
out += b.is_input ? "[[io.input]]\n" : "[[io.output]]\n";
|
||||||
|
out += "var = \"";
|
||||||
|
out += b.var;
|
||||||
|
out += "\"\n";
|
||||||
|
std::snprintf(buf, sizeof buf, "slot = %u\n", static_cast<unsigned>(b.slot));
|
||||||
|
out += buf;
|
||||||
|
std::snprintf(buf, sizeof buf, "channel = %u\n", static_cast<unsigned>(b.channel));
|
||||||
|
out += buf;
|
||||||
|
std::snprintf(buf, sizeof buf, "bit = %u\n", static_cast<unsigned>(b.bit));
|
||||||
|
out += buf;
|
||||||
|
out += "\n";
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool write_sidecar_file(const char* path, const std::vector<IoBinding>& bindings,
|
||||||
|
std::string* err) {
|
||||||
|
std::FILE* f = std::fopen(path, "wb");
|
||||||
|
if (f == nullptr) {
|
||||||
|
if (err) {
|
||||||
|
*err = "cannot open for write: " + std::string(path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::string s = make_sidecar(bindings);
|
||||||
|
const bool ok = s.empty() || std::fwrite(s.data(), 1, s.size(), f) == s.size();
|
||||||
|
std::fclose(f);
|
||||||
|
if (!ok && err) {
|
||||||
|
*err = "write failed: " + std::string(path);
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace compiler
|
||||||
+57
-35
@@ -12,67 +12,66 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "compiler/Codec.h"
|
||||||
#include "compiler/Codegen.h"
|
#include "compiler/Codegen.h"
|
||||||
#include "compiler/Linker.h"
|
#include "compiler/Linker.h"
|
||||||
|
#include "compiler/MachineConfig.h"
|
||||||
#include "compiler/Project.h"
|
#include "compiler/Project.h"
|
||||||
|
#include "compiler/Stb.h"
|
||||||
#include "compiler/Typecheck.h"
|
#include "compiler/Typecheck.h"
|
||||||
#include "isa/Encode.h"
|
|
||||||
#include "isa/Image.h"
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
void usage() {
|
void usage() {
|
||||||
std::printf("usage: STCompiler <project.toml> [-o <name>.stb]\n"
|
std::printf("usage: STCompiler <project.toml> [-o <name>.stb] --machine <machine.toml>\n"
|
||||||
" STCompiler <name>.stb --disasm\n"
|
" STCompiler <name>.stb --disasm --machine <machine.toml>\n"
|
||||||
" 解析工程并编译(词法 → 语法 → 链接 → 类型 → 寄存器码)\n"
|
" 解析工程并编译(词法 → 语法 → 链接 → 类型 → 寄存器码)\n"
|
||||||
" -o <name>.stb 编译并写出映像文件\n"
|
" -o <name>.stb 编译并写出映像文件\n"
|
||||||
" 无 -o 只打印文件集合与工程哈希(12.3 阶段)\n"
|
" --machine <path> 机器定义(machine.toml,编译路径必填)\n"
|
||||||
" --disasm 反汇编一个已编译的 .stb 映像\n"
|
" --disasm 反汇编一个已编译的 .stb 映像(需要 --machine)\n"
|
||||||
" --help 打印本帮助\n");
|
" --help 打印本帮助\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 反汇编一个已编译映像(函数表逐条 + 常量表 + 数据段摘要)。
|
// 反汇编一个已编译映像(函数表逐条 + 常量表 + 数据段摘要)。
|
||||||
// 映像无符号表,按 fn_id / 槽号显示;指令偏移为文件绝对字节偏移。
|
// 指令偏移为文件绝对字节偏移;反汇编按 machine.toml 的 format 输出。
|
||||||
int dump_image(const char* path) {
|
int dump_image(const char* path, const compiler::MachineConfig& cfg) {
|
||||||
std::vector<uint8_t> bytes;
|
std::vector<uint8_t> bytes;
|
||||||
std::string err;
|
std::string err;
|
||||||
if (!isa::read_stb_file(path, &bytes, &err)) {
|
if (!compiler::read_stb_file(path, &bytes, &err)) {
|
||||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
const isa::ImageView v = isa::ImageView::from(bytes);
|
const compiler::StbView v = compiler::StbView::from(bytes);
|
||||||
if (!v.ok()) {
|
if (!v.ok()) {
|
||||||
std::fprintf(stderr, "error: %s\n", v.error().c_str());
|
std::fprintf(stderr, "error: %s\n", v.error().c_str());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
const isa::ImageHeader& h = v.header();
|
|
||||||
std::printf("image: %s (%zu bytes, %u functions, %u globals, entry fn %u)\n",
|
std::printf("image: %s (%zu bytes, %u functions, %u globals, entry fn %u)\n",
|
||||||
path, bytes.size(), h.n_funcs, h.n_globals, h.entry_fn_id);
|
path, bytes.size(), v.n_funcs(), v.n_globals(), v.entry_fn_id());
|
||||||
std::printf(" dt_ms=%u cycle_limit=%u hash=0x%016llx\n", h.dt_ms,
|
std::printf(" dt_ms=%u cycle_limit=%u hash=0x%016llx\n", v.dt_ms(),
|
||||||
h.cycle_limit, static_cast<unsigned long long>(h.project_hash));
|
v.cycle_limit(), static_cast<unsigned long long>(v.project_hash()));
|
||||||
|
|
||||||
if (h.n_consts) {
|
if (v.n_consts()) {
|
||||||
std::printf("constants:\n");
|
std::printf("constants:\n");
|
||||||
for (uint32_t i = 0; i < h.n_consts; ++i) {
|
for (uint32_t i = 0; i < v.n_consts(); ++i) {
|
||||||
const isa::ConstEntry c = v.const_entry(i);
|
const compiler::ConstEntry c = v.const_entry(i);
|
||||||
const char* tag = c.tag == isa::types::Bool ? "BOOL"
|
const char* tag = c.tag == 0 ? "BOOL" : c.tag == 1 ? "INT" : "TIME";
|
||||||
: c.tag == isa::types::Int ? "INT"
|
|
||||||
: "TIME";
|
|
||||||
std::printf(" [%u] %s %llu\n", i, tag,
|
std::printf(" [%u] %s %llu\n", i, tag,
|
||||||
static_cast<unsigned long long>(c.value));
|
static_cast<unsigned long long>(c.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::printf("functions:\n");
|
std::printf("functions:\n");
|
||||||
for (uint32_t i = 0; i < h.n_funcs; ++i) {
|
for (uint32_t i = 0; i < v.n_funcs(); ++i) {
|
||||||
const isa::FuncRow r = v.func_row(i);
|
const compiler::StbView::FuncRow r = v.func_row(i);
|
||||||
std::printf(" fn %u: nregs=%u, offset=%u, len=%u\n", i, r.nregs,
|
std::printf(" fn %u: nregs=%u, offset=%u, len=%u\n", i, r.nregs,
|
||||||
r.code_offset, r.code_len);
|
r.code_offset, r.code_len);
|
||||||
const uint8_t* base = v.code_bytes() + r.code_offset;
|
const uint8_t* base = v.code_bytes() + r.code_offset;
|
||||||
for (uint32_t j = 0; j < r.code_len; ++j) {
|
for (uint32_t j = 0; j < r.code_len; ++j) {
|
||||||
char buf[64];
|
char buf[64];
|
||||||
isa::disasm(reinterpret_cast<const uint32_t*>(base)[j], buf, sizeof buf);
|
compiler::disasm(cfg, reinterpret_cast<const uint32_t*>(base)[j], buf,
|
||||||
|
sizeof buf);
|
||||||
std::printf(" 0x%04x %s\n",
|
std::printf(" 0x%04x %s\n",
|
||||||
h.offset_code + r.code_offset + j * 4, buf);
|
v.offset_code() + r.code_offset + j * 4, buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,13 +101,37 @@ int main(int argc, char** argv) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --disasm:第一参数是 .stb 路径,不重新编译
|
// 参数收集
|
||||||
|
std::string machine_path;
|
||||||
|
bool disasm_mode = false;
|
||||||
|
for (int i = 2; i + 1 < argc; ++i) {
|
||||||
|
if (std::strcmp(argv[i], "--machine") == 0) {
|
||||||
|
machine_path = argv[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
if (std::strcmp(argv[i], "--disasm") == 0) {
|
if (std::strcmp(argv[i], "--disasm") == 0) {
|
||||||
return dump_image(argv[1]);
|
disasm_mode = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 机器定义(编译与反汇编都需要;加载/校验失败报错退出)
|
||||||
|
if (machine_path.empty()) {
|
||||||
|
std::fprintf(stderr, "error: missing --machine <machine.toml>\n");
|
||||||
|
usage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
compiler::MachineConfig cfg;
|
||||||
|
std::string err;
|
||||||
|
if (!cfg.load(machine_path, &err)) {
|
||||||
|
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disasm_mode) {
|
||||||
|
return dump_image(argv[1], cfg);
|
||||||
|
}
|
||||||
|
|
||||||
const std::string toml_path = argv[1];
|
const std::string toml_path = argv[1];
|
||||||
std::string out_path;
|
std::string out_path;
|
||||||
for (int i = 2; i + 1 < argc; ++i) {
|
for (int i = 2; i + 1 < argc; ++i) {
|
||||||
@@ -118,7 +141,6 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compiler::Project proj;
|
compiler::Project proj;
|
||||||
std::string err;
|
|
||||||
if (!compiler::parse_project(toml_path, &proj, &err)) {
|
if (!compiler::parse_project(toml_path, &proj, &err)) {
|
||||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||||
return 1;
|
return 1;
|
||||||
@@ -160,19 +182,19 @@ int main(int argc, char** argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
std::vector<uint8_t> image;
|
std::vector<uint8_t> image;
|
||||||
if (!compiler::codegen_project(proj, units, link, &image, &err)) {
|
if (!compiler::codegen_project(proj, units, link, cfg, &image, &err)) {
|
||||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (!isa::write_stb_file(out_path.c_str(), image, &err)) {
|
if (!compiler::write_stb_file(out_path.c_str(), image, &err)) {
|
||||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sidecar:I/O 绑定 var → 槽号 → channel/bit(不进映像,执行器采样用)
|
// sidecar:I/O 绑定 var → 槽号 → channel/bit(不进映像,执行器采样用)
|
||||||
std::vector<isa::IoBinding> bindings;
|
std::vector<compiler::IoBinding> bindings;
|
||||||
for (const isa::IoBinding& b : proj.io) {
|
for (const compiler::IoBinding& b : proj.io) {
|
||||||
isa::IoBinding out_b = b;
|
compiler::IoBinding out_b = b;
|
||||||
std::string key = b.var;
|
std::string key = b.var;
|
||||||
for (char& ch : key) {
|
for (char& ch : key) {
|
||||||
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
|
||||||
@@ -185,13 +207,13 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
std::filesystem::path sidecar = std::filesystem::path(out_path);
|
std::filesystem::path sidecar = std::filesystem::path(out_path);
|
||||||
sidecar.replace_extension(".runtime.toml");
|
sidecar.replace_extension(".runtime.toml");
|
||||||
if (!isa::write_sidecar_file(sidecar.string().c_str(), bindings, &err)) {
|
if (!compiler::write_sidecar_file(sidecar.string().c_str(), bindings, &err)) {
|
||||||
std::fprintf(stderr, "error: %s\n", err.c_str());
|
std::fprintf(stderr, "error: %s\n", err.c_str());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isa::ImageView v = isa::ImageView::from(image);
|
const compiler::StbView v = compiler::StbView::from(image);
|
||||||
std::printf("compiled: %s (%zu bytes, %u functions, %u globals)\n",
|
std::printf("compiled: %s (%zu bytes, %u functions, %u globals)\n",
|
||||||
out_path.c_str(), image.size(), v.header().n_funcs, v.header().n_globals);
|
out_path.c_str(), image.size(), v.n_funcs(), v.n_globals());
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ add_test(NAME typecheck_types
|
|||||||
add_executable(codegen_test
|
add_executable(codegen_test
|
||||||
./src/codegen_test.cpp)
|
./src/codegen_test.cpp)
|
||||||
|
|
||||||
target_link_libraries(codegen_test PRIVATE compiler)
|
target_link_libraries(codegen_test PRIVATE compiler isa)
|
||||||
target_compile_definitions(codegen_test PRIVATE
|
target_compile_definitions(codegen_test PRIVATE
|
||||||
REPO_ROOT="${CMAKE_SOURCE_DIR}")
|
REPO_ROOT="${CMAKE_SOURCE_DIR}")
|
||||||
|
|
||||||
|
|||||||
@@ -119,8 +119,13 @@ namespace {
|
|||||||
std::printf("FAIL %s type: %s\n", dir, err.c_str());
|
std::printf("FAIL %s type: %s\n", dir, err.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
MachineConfig cfg;
|
||||||
|
if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) {
|
||||||
|
std::printf("FAIL %s machine load\n", dir);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
std::vector<uint8_t> img;
|
std::vector<uint8_t> img;
|
||||||
if (!codegen_project(p, units, link, &img, &err)) {
|
if (!codegen_project(p, units, link, cfg, &img, &err)) {
|
||||||
if (want == Outcome::CompileError) {
|
if (want == Outcome::CompileError) {
|
||||||
++g_checks;
|
++g_checks;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -34,8 +34,19 @@ static int g_checks = 0;
|
|||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
// 从用例目录走完整编译管线(解析 → 链接 → 类型检查 → 代码生成)
|
// 从用例目录走完整编译管线(解析 → 链接 → 类型检查 → 代码生成)
|
||||||
|
static bool load_cfg(compiler::MachineConfig* cfg) {
|
||||||
|
std::string err;
|
||||||
|
if (!cfg->load(std::string(REPO_ROOT) + "/compiler/machine.toml", &err)) {
|
||||||
|
std::printf("FAIL load machine.toml: %s\n", err.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool compile_case(const char* dir, std::vector<uint8_t>* image, std::string* err) {
|
static bool compile_case(const char* dir, std::vector<uint8_t>* image, std::string* err) {
|
||||||
using namespace compiler;
|
using namespace compiler;
|
||||||
|
compiler::MachineConfig cfg;
|
||||||
|
if (!load_cfg(&cfg)) return false;
|
||||||
const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml";
|
const std::string toml = std::string(REPO_ROOT) + "/tests/cases/" + dir + "/project.toml";
|
||||||
Project p;
|
Project p;
|
||||||
if (!parse_project(toml, &p, err)) {
|
if (!parse_project(toml, &p, err)) {
|
||||||
@@ -56,7 +67,7 @@ static bool compile_case(const char* dir, std::vector<uint8_t>* image, std::stri
|
|||||||
if (!check_project(p, units, link, err)) {
|
if (!check_project(p, units, link, err)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return codegen_project(p, units, link, image, err);
|
return codegen_project(p, units, link, cfg, image, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从临时工程(globals.st + main.st + 可含 io 绑定)走完整管线
|
// 从临时工程(globals.st + main.st + 可含 io 绑定)走完整管线
|
||||||
@@ -64,6 +75,8 @@ static bool compile_src(const char* name, const char* io_extra, const char* glob
|
|||||||
const char* main_st, std::vector<uint8_t>* image,
|
const char* main_st, std::vector<uint8_t>* image,
|
||||||
std::string* err) {
|
std::string* err) {
|
||||||
using namespace compiler;
|
using namespace compiler;
|
||||||
|
compiler::MachineConfig cfg;
|
||||||
|
if (!load_cfg(&cfg)) return false;
|
||||||
const std::string dir = std::string(REPO_ROOT) + "/build/cg_tmp_" + name;
|
const std::string dir = std::string(REPO_ROOT) + "/build/cg_tmp_" + name;
|
||||||
std::filesystem::remove_all(dir);
|
std::filesystem::remove_all(dir);
|
||||||
std::filesystem::create_directories(dir);
|
std::filesystem::create_directories(dir);
|
||||||
@@ -100,7 +113,7 @@ static bool compile_src(const char* name, const char* io_extra, const char* glob
|
|||||||
if (!check_project(p, units, link, err)) {
|
if (!check_project(p, units, link, err)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return codegen_project(p, units, link, image, err);
|
return codegen_project(p, units, link, cfg, image, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 1. 用例 01:空 MAIN + RET ----
|
// ---- 1. 用例 01:空 MAIN + RET ----
|
||||||
@@ -653,7 +666,9 @@ static bool test_line1() {
|
|||||||
LinkResult link;
|
LinkResult link;
|
||||||
CHECK(link_project(p, units, &link, &err));
|
CHECK(link_project(p, units, &link, &err));
|
||||||
CHECK(check_project(p, units, link, &err));
|
CHECK(check_project(p, units, link, &err));
|
||||||
CHECK(codegen_project(p, units, link, &img, &err));
|
compiler::MachineConfig cfg;
|
||||||
|
CHECK(load_cfg(&cfg));
|
||||||
|
CHECK(codegen_project(p, units, link, cfg, &img, &err));
|
||||||
|
|
||||||
const isa::ImageView v = isa::ImageView::from(img);
|
const isa::ImageView v = isa::ImageView::from(img);
|
||||||
CHECK(v.ok());
|
CHECK(v.ok());
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ static void wslot(vm::Machine& m, int s, int64_t v) {
|
|||||||
|
|
||||||
static bool make_machine(const char* dir, vm::Machine* m, std::string* err) {
|
static bool make_machine(const char* dir, vm::Machine* m, std::string* err) {
|
||||||
using namespace compiler;
|
using namespace compiler;
|
||||||
|
MachineConfig cfg;
|
||||||
|
if (!cfg.load(std::string(REPO_ROOT) + "/compiler/machine.toml", err)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const std::string toml = std::string(REPO_ROOT) + "/" + dir + "/project.toml";
|
const std::string toml = std::string(REPO_ROOT) + "/" + dir + "/project.toml";
|
||||||
Project p;
|
Project p;
|
||||||
if (!parse_project(toml, &p, err)) {
|
if (!parse_project(toml, &p, err)) {
|
||||||
@@ -76,7 +80,7 @@ static bool make_machine(const char* dir, vm::Machine* m, std::string* err) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
std::vector<uint8_t> img;
|
std::vector<uint8_t> img;
|
||||||
if (!codegen_project(p, units, link, &img, err)) {
|
if (!codegen_project(p, units, link, cfg, &img, err)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return vm::Machine::create(img, m, err);
|
return vm::Machine::create(img, m, err);
|
||||||
|
|||||||
Reference in New Issue
Block a user