From c378411637532167ab0f4ad67bf8872a733fb9bf Mon Sep 17 00:00:00 2001 From: chentianya Date: Wed, 19 Aug 2026 14:40:38 +0800 Subject: [PATCH] =?UTF-8?q?=E8=90=BD=E5=9C=B0=E6=98=A0=E5=83=8F=E8=AF=BB?= =?UTF-8?q?=E5=86=99=EF=BC=9A72=20=E5=AD=97=E8=8A=82=E5=A4=B4=E3=80=81?= =?UTF-8?q?=E6=AE=B5=E5=B8=83=E5=B1=80=E3=80=81FNV-1a=E3=80=81.stb=20?= =?UTF-8?q?=E4=B8=8E=20sidecar=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 指令与映像.md 冻结:头字段表、常量表/函数表行、段序、数据段对齐、sidecar 格式 - Types.h 补 TypeTag;Image.h/cpp:ImageView 校验、write_ret_main、文件与 sidecar - isa CMake 改 target_include_directories PUBLIC --- Doc/isa/指令与映像.md | 64 +++++- {host => executor}/CMakeLists.txt | 0 {host => executor}/README.md | 0 {host => executor}/src/main.cpp | 0 isa/CMakeLists.txt | 7 +- isa/include/isa/Image.h | 123 ++++++++++++ isa/include/isa/Types.h | 7 + isa/src/Image.cpp | 321 ++++++++++++++++++++++++++++++ 8 files changed, 509 insertions(+), 13 deletions(-) rename {host => executor}/CMakeLists.txt (100%) rename {host => executor}/README.md (100%) rename {host => executor}/src/main.cpp (100%) create mode 100644 isa/include/isa/Image.h create mode 100644 isa/src/Image.cpp diff --git a/Doc/isa/指令与映像.md b/Doc/isa/指令与映像.md index 311ea1b..a36fb46 100644 --- a/Doc/isa/指令与映像.md +++ b/Doc/isa/指令与映像.md @@ -130,19 +130,51 @@ FB 实例固定布局,字段偏移编译期算死。 ## 映像 -魔数 `STSC`,版本 `1`。 +魔数 `STSC`,版本 `1`。全部整数小端。文件 = 头 + 五个段,段序:常量表 → 函数表 → 字节码 → FB 布局 → 数据。偏移记在头里,段必须连续、偏移单调不减,末段终点不超过文件长度。 -头: +### 头(72 字节) -- `cycle_limit`、`dt_ms` -- 工程哈希 -- 全局槽数、I/Q/M 槽数 -- 常量表 / 函数表 / 字节码 / FB 布局的偏移 -- 入口 `MAIN` 的 `fn_id` +| 偏移 | 宽 | 字段 | +|---|---|---| +| 0 | 4 | 魔数 `STSC` | +| 4 | 4 | 版本 `1` | +| 8 | 4 | `cycle_limit` | +| 12 | 4 | `dt_ms` | +| 16 | 8 | 工程哈希(FNV-1a 64) | +| 24 | 4 | 入口 `entry_fn_id` | +| 28 | 4 | 全局槽数 `n_globals` | +| 32 | 4 | I 槽数 `n_i` | +| 36 | 4 | Q 槽数 `n_q` | +| 40 | 4 | M 槽数 `n_m` | +| 44 | 4 | 常量数 `n_consts` | +| 48 | 4 | 函数数 `n_funcs` | +| 52 | 4 | `offset_const` | +| 56 | 4 | `offset_funcs` | +| 60 | 4 | `offset_code` | +| 64 | 4 | `offset_fb` | +| 68 | 4 | `offset_data` | -函数表一行:`fn_id`、`nregs`、字节码偏移与长度。 +### 常量表一行(12 字节) -工程哈希:收录源文件路径排序后对内容做 **FNV-1a 64**,只保证回放槽位一致,不是密码学哈希。 +`[tag:u32][value:u64]`;`tag`:0=BOOL、1=INT、2=TIME。`value` 按 tag 解释,未用高位为 0。 + +### 函数表一行(12 字节) + +`[nregs:u32][code_offset:u32][code_len:u32]`;行下标即 `fn_id`,`code_offset` 相对字节码段起点,`code_len` 为指令条数。 + +### 字节码段 + +指令按 `u32` 连续排;`CALL` 的 `fn_id` 是函数表行下标。 + +### FB 布局段 + +第一版为空;每类型一行 `[field_count:u32][field_count × (tag:u32, offset:u32)]`,offset 相对实例基址,12.8 落地 codegen 时冻精确表。 + +### 数据段 + +槽初值按序排:全局(`n_globals`)→ I → Q → M。槽宽与对齐:`BOOL` 1 字节 / `INT` 2 字节 / `TIME` 8 字节,每个槽起点对齐到自身宽度(需要时插 padding)。 + +工程哈希:收录源文件路径排序后对内容做 **FNV-1a 64**,只保证回放槽位一致,不是密码学哈希。FNV-1a 64:basis `0xcbf29ce484222325`,prime `0x100000001b3`。 `[[io.input]]` / `[[io.output]]` 不进映像。 @@ -153,7 +185,19 @@ FB 实例固定布局,字段偏移编译期算死。 编译产物是单文件 `.stb`:映像头 + 各段按本文布局连续排,魔数 `STSC`、版本 `1`、小端。`STCompiler` 写,`BytecodeExecutor` 读。 - `dt_ms` / `cycle_limit` 在映像头,执行器只认映像,不重复配置。 -- I/O 绑定不进映像:`STCompiler` 另写 sidecar `.runtime.toml`,内容是 `var`(已解析槽号)→ `channel` / `bit`;`BytecodeExecutor` 读 sidecar 做采样与写回,不创造变量。 +- I/O 绑定不进映像:`STCompiler` 另写 sidecar `.runtime.toml`,`BytecodeExecutor` 读 sidecar 做采样与写回,不创造变量。 + +sidecar 格式(TOML 子集,冻结): + +```toml +[[io.input]] # 或 [[io.output]] +var = "I0_0" # 已在 GVL 声明的名字 +slot = 1 # 全局槽号(编译期已解析) +channel = 0 +bit = 0 +``` + +每行一个绑定;`var` 顺序不要求与映像槽序一致(`slot` 为准)。 --- diff --git a/host/CMakeLists.txt b/executor/CMakeLists.txt similarity index 100% rename from host/CMakeLists.txt rename to executor/CMakeLists.txt diff --git a/host/README.md b/executor/README.md similarity index 100% rename from host/README.md rename to executor/README.md diff --git a/host/src/main.cpp b/executor/src/main.cpp similarity index 100% rename from host/src/main.cpp rename to executor/src/main.cpp diff --git a/isa/CMakeLists.txt b/isa/CMakeLists.txt index 22cae7c..2f42c49 100644 --- a/isa/CMakeLists.txt +++ b/isa/CMakeLists.txt @@ -10,7 +10,8 @@ project(ISA set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 11) -include_directories(./include) - add_library(isa STATIC - ./src/Encode.cpp) \ No newline at end of file + ./src/Encode.cpp + ./src/Image.cpp) + +target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) \ No newline at end of file diff --git a/isa/include/isa/Image.h b/isa/include/isa/Image.h new file mode 100644 index 0000000..d1f1289 --- /dev/null +++ b/isa/include/isa/Image.h @@ -0,0 +1,123 @@ +/** + * @file Image.h + * @brief 映像头、段、FNV-1a 哈希、.stb 文件与 sidecar + * @author + * @date 2026-08-19 + */ + +#pragma once + +#include +#include +#include +#include + +#include "isa/Types.h" + +namespace isa { + + // 魔数 "STSC"(小端 u32)、版本 + static const uint32_t kMagic = 0x43545353u; + static const uint32_t kVersion = 1; + + // 头与表行宽(见 Doc/isa/指令与映像.md) + static const size_t kHeaderSize = 72; + static const size_t kConstEntrySize = 12; + static const size_t kFuncRowSize = 12; + + // FNV-1a 64:basis / prime(见 Doc/isa/指令与映像.md) + static const uint64_t kFnvBasis = 0xcbf29ce484222325ull; + static const uint64_t kFnvPrime = 0x100000001b3ull; + + // 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); + + // 映像头(字节布局见 Doc/isa/指令与映像.md) + struct ImageHeader { + uint32_t cycle_limit; + uint32_t dt_ms; + uint64_t project_hash; + uint32_t entry_fn_id; + uint32_t n_globals; + uint32_t n_i; + uint32_t n_q; + uint32_t n_m; + uint32_t n_consts; + uint32_t n_funcs; + uint32_t offset_const; + uint32_t offset_funcs; + uint32_t offset_code; + uint32_t offset_fb; + uint32_t offset_data; + }; + + // 常量表一行 + struct ConstEntry { + types::TypeTag tag; + uint64_t value; + }; + + // 函数表一行 + struct FuncRow { + uint32_t nregs; + uint32_t code_offset; // 相对字节码段起点 + uint32_t code_len; // 指令条数 + }; + + // I/O 绑定(sidecar 一行) + struct IoBinding { + std::string var; + uint32_t slot; + uint32_t channel; + uint32_t bit; + bool is_input; // true = [[io.input]],false = [[io.output]] + }; + + // 只读视图:校验过的映像 + class ImageView { + public: + static ImageView from(const uint8_t* buf, size_t len); + static ImageView from(const std::vector& buf); + + bool ok() const; + const std::string& error() const; + const ImageHeader& header() const; + + const uint8_t* raw() const; + size_t raw_len() const; + + 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: + ImageView(); + + const uint8_t* buf_; + size_t len_; + bool ok_; + std::string err_; + ImageHeader hdr_; + }; + + // 写最小映像:空槽 + MAIN(fn_id=0)+ 一条 RET + std::vector write_ret_main(uint32_t cycle_limit, uint32_t dt_ms, + uint64_t project_hash); + + // .stb 文件读写(纯字节,校验交给 read_image / ImageView) + bool write_stb_file(const char* path, const std::vector& image, + std::string* err); + bool read_stb_file(const char* path, std::vector* image, + std::string* err); + + // sidecar 生成与写文件(格式见 Doc/isa/指令与映像.md) + std::string make_sidecar(const std::vector& bindings); + bool write_sidecar_file(const char* path, + const std::vector& bindings, + std::string* err); +} diff --git a/isa/include/isa/Types.h b/isa/include/isa/Types.h index fd74a9f..2fa1efb 100644 --- a/isa/include/isa/Types.h +++ b/isa/include/isa/Types.h @@ -20,6 +20,13 @@ namespace isa { // 差值可负、为 DATE_AND_TIME 与 64 位定时器预留。槽位按 8 字节对齐。 using TIME = int64_t; + // 槽 / 常量表类型标记,数值冻结:0=BOOL、1=INT、2=TIME(见映像规范) + enum TypeTag : uint8_t { + Bool = 0, + Int = 1, + Time = 2, + }; + // 饱和算术:夹到 INT 最小/最大,编译期与 VM 共用同一规则。 inline INT sat_add(INT a, INT b) { diff --git a/isa/src/Image.cpp b/isa/src/Image.cpp new file mode 100644 index 0000000..1d2fd7b --- /dev/null +++ b/isa/src/Image.cpp @@ -0,0 +1,321 @@ +/** + * @file Image.cpp + * @brief 映像头、段、FNV-1a 哈希、.stb 文件与 sidecar + * @author + * @date 2026-08-19 + */ + +#include "isa/Image.h" + +#include +#include + +#include "isa/Instr.h" + +namespace isa { + +// ---- 小端读写(只放 .cpp,不把 packed struct 当 ABI)---- + +static void put_le32(std::vector& b, size_t off, uint32_t v) { + b[off + 0] = static_cast(v & 0xFFu); + b[off + 1] = static_cast((v >> 8) & 0xFFu); + b[off + 2] = static_cast((v >> 16) & 0xFFu); + b[off + 3] = static_cast((v >> 24) & 0xFFu); +} + +static uint32_t get_le32(const uint8_t* p) { + return static_cast(p[0]) + | (static_cast(p[1]) << 8) + | (static_cast(p[2]) << 16) + | (static_cast(p[3]) << 24); +} + +static void put_le64(std::vector& b, size_t off, uint64_t v) { + for (int i = 0; i < 8; ++i) { + b[off + i] = static_cast((v >> (8 * i)) & 0xFFu); + } +} + +static uint64_t get_le64(const uint8_t* p) { + uint64_t v = 0; + for (int i = 0; i < 8; ++i) { + v |= static_cast(p[i]) << (8 * i); + } + return v; +} + +// ---- FNV-1a 64 ---- + +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); +} + +// ---- 最小映像:空槽 + MAIN + 一条 RET ---- + +std::vector write_ret_main(uint32_t cycle_limit, uint32_t dt_ms, + uint64_t project_hash) { + std::vector b(kHeaderSize + kFuncRowSize + 4, 0); + + put_le32(b, 0, kMagic); + put_le32(b, 4, kVersion); + put_le32(b, 8, cycle_limit); + put_le32(b, 12, dt_ms); + put_le64(b, 16, project_hash); + put_le32(b, 24, 0); // entry_fn_id = MAIN = 0 + // n_globals / n_i / n_q / n_m = 0 + // n_consts = 0 + put_le32(b, 48, 1); // n_funcs = 1 + + const size_t off_funcs = kHeaderSize; // 72 + const size_t off_code = off_funcs + kFuncRowSize; // 84 + const size_t off_end = off_code + 4; // 88 + + put_le32(b, 52, static_cast(off_funcs)); // offset_const + put_le32(b, 56, static_cast(off_funcs)); // offset_funcs + put_le32(b, 60, static_cast(off_code)); // offset_code + put_le32(b, 64, static_cast(off_end)); // offset_fb + put_le32(b, 68, static_cast(off_end)); // offset_data + + // 函数表一行:MAIN,无寄存器,代码 0 起 1 条 + put_le32(b, off_funcs + 0, 0); // nregs + put_le32(b, off_funcs + 4, 0); // code_offset + put_le32(b, off_funcs + 8, 1); // code_len + + // 字节码:RET + put_le32(b, off_code, pack(Op::RET, 0, 0, 0)); + + return b; +} + +// ---- 只读视图 ---- + +ImageView::ImageView() + : buf_(0), len_(0), ok_(false), err_("uninitialized"), hdr_() {} + +ImageView ImageView::from(const uint8_t* buf, size_t len) { + ImageView v; + v.buf_ = buf; + v.len_ = len; + + if (buf == 0) { + 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; + } + + ImageHeader& h = v.hdr_; + h.cycle_limit = get_le32(buf + 8); + h.dt_ms = get_le32(buf + 12); + h.project_hash = get_le64(buf + 16); + h.entry_fn_id = get_le32(buf + 24); + h.n_globals = get_le32(buf + 28); + h.n_i = get_le32(buf + 32); + h.n_q = get_le32(buf + 36); + h.n_m = get_le32(buf + 40); + h.n_consts = get_le32(buf + 44); + h.n_funcs = get_le32(buf + 48); + h.offset_const = get_le32(buf + 52); + h.offset_funcs = get_le32(buf + 56); + h.offset_code = get_le32(buf + 60); + h.offset_fb = get_le32(buf + 64); + h.offset_data = get_le32(buf + 68); + + // 段校验:连续、单调不减、末段终点不越界 + const uint64_t offs[5] = { + h.offset_const, h.offset_funcs, h.offset_code, h.offset_fb, h.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; + } + } + + const uint64_t const_len = h.offset_funcs - h.offset_const; + const uint64_t funcs_len = h.offset_code - h.offset_funcs; + if (const_len != static_cast(h.n_consts) * kConstEntrySize) { + v.err_ = "const table size mismatch"; + return v; + } + if (funcs_len != static_cast(h.n_funcs) * kFuncRowSize) { + v.err_ = "function table size mismatch"; + return v; + } + if ((h.offset_fb - h.offset_code) % 4 != 0) { + v.err_ = "code segment not 4-byte aligned"; + return v; + } + if (h.entry_fn_id >= h.n_funcs && h.n_funcs != 0) { + v.err_ = "entry fn_id out of range"; + return v; + } + + v.ok_ = true; + v.err_.clear(); + return v; +} + +ImageView ImageView::from(const std::vector& buf) { + return from(buf.data(), buf.size()); +} + +bool ImageView::ok() const { return ok_; } +const std::string& ImageView::error() const { return err_; } +const ImageHeader& ImageView::header() const { return hdr_; } +const uint8_t* ImageView::raw() const { return buf_; } +size_t ImageView::raw_len() const { return len_; } + +ConstEntry ImageView::const_entry(size_t i) const { + ConstEntry e; + e.tag = types::Bool; + e.value = 0; + if (ok_ && i < hdr_.n_consts) { + const uint8_t* p = buf_ + hdr_.offset_const + i * kConstEntrySize; + const uint32_t tag = get_le32(p); + if (tag <= static_cast(types::Time)) { + e.tag = static_cast(tag); + } + e.value = get_le64(p + 4); + } + return e; +} + +FuncRow ImageView::func_row(size_t i) const { + FuncRow r = {0, 0, 0}; + if (ok_ && i < hdr_.n_funcs) { + const uint8_t* p = buf_ + hdr_.offset_funcs + 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* ImageView::code_bytes() const { + return ok_ ? buf_ + hdr_.offset_code : 0; +} + +size_t ImageView::code_len() const { + return ok_ ? hdr_.offset_fb - hdr_.offset_code : 0; +} + +const uint8_t* ImageView::data_bytes() const { + return ok_ ? buf_ + hdr_.offset_data : 0; +} + +size_t ImageView::data_len() const { + return ok_ ? len_ - hdr_.offset_data : 0; +} + +// ---- .stb 文件读写 ---- + +bool write_stb_file(const char* path, const std::vector& image, + std::string* err) { + FILE* f = std::fopen(path, "wb"); + if (f == 0) { + if (err) { + *err = std::string("cannot open for write: ") + path; + } + return false; + } + const bool ok = image.empty() || std::fwrite(&image[0], 1, image.size(), f) == image.size(); + std::fclose(f); + if (!ok && err) { + *err = std::string("write failed: ") + path; + } + return ok; +} + +bool read_stb_file(const char* path, std::vector* image, + std::string* err) { + FILE* f = std::fopen(path, "rb"); + if (f == 0) { + if (err) { + *err = std::string("cannot open for read: ") + path; + } + return false; + } + std::fseek(f, 0, SEEK_END); + const long size = std::ftell(f); + std::fseek(f, 0, SEEK_SET); + if (size < 0) { + std::fclose(f); + if (err) { + *err = "tell failed"; + } + return false; + } + image->resize(static_cast(size)); + const bool ok = size == 0 || std::fread(&(*image)[0], 1, static_cast(size), f) == static_cast(size); + std::fclose(f); + if (!ok && err) { + *err = std::string("read failed: ") + path; + } + return ok; +} + +// ---- sidecar ---- + +std::string make_sidecar(const std::vector& bindings) { + std::string out; + for (size_t i = 0; i < bindings.size(); ++i) { + const IoBinding& b = bindings[i]; + 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(b.slot)); + out += buf; + std::snprintf(buf, sizeof buf, "channel = %u\n", static_cast(b.channel)); + out += buf; + std::snprintf(buf, sizeof buf, "bit = %u\n", static_cast(b.bit)); + out += buf; + out += "\n"; + } + return out; +} + +bool write_sidecar_file(const char* path, + const std::vector& bindings, + std::string* err) { + FILE* f = std::fopen(path, "wb"); + if (f == 0) { + if (err) { + *err = std::string("cannot open for write: ") + 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 = std::string("write failed: ") + path; + } + return ok; +} + +} // namespace isa