/** * @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