Compare commits

...
3 Commits
Author SHA1 Message Date
Admin dd7f7bcba7 双可执行落地:STCompiler 与 BytecodeExecutor。
- STCompiler 入口在 compiler/src/main.cpp,链 compiler+isa
- host/ 改名 executor/,BytecodeExecutor 链 vm+isa 不链 compiler
- compiler/vm 补 PUBLIC 链 isa;顶层加入 executor
- CTest:版本冒烟 2 例 + isa_roundtrip(89 断言),3/3 通过
2026-08-19 14:40:44 +08:00
Admin c378411637 落地映像读写:72 字节头、段布局、FNV-1a、.stb 与 sidecar。
- 指令与映像.md 冻结:头字段表、常量表/函数表行、段序、数据段对齐、sidecar 格式
- Types.h 补 TypeTag;Image.h/cpp:ImageView 校验、write_ret_main、文件与 sidecar
- isa CMake 改 target_include_directories PUBLIC
2026-08-19 14:40:38 +08:00
Admin eba48f852e 实现指令编解码与反汇编,冻结 disasm 格式。
- Encode.h/Encode.cpp:encode/decode 往返 + disasm 一行文本
- 指令与映像.md 新增反汇编格式表(9 种形态 + 未知操作码)
- Op.h STORE_* 注释对齐 disasm(rs, slot 寄存器在前)
2026-08-19 14:06:20 +08:00
19 changed files with 957 additions and 41 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ include("${CMAKE_SOURCE_DIR}/cmake/Standards.cmake")
add_subdirectory(isa) add_subdirectory(isa)
add_subdirectory(compiler) add_subdirectory(compiler)
add_subdirectory(vm) add_subdirectory(vm)
add_subdirectory(host) add_subdirectory(executor)
# 自动化用例 # 自动化用例
enable_testing() enable_testing()
+73 -10
View File
@@ -107,23 +107,74 @@ RET
`I`/`Q`/`M` 走映像指令,不是通用寄存器。 `I`/`Q`/`M` 走映像指令,不是通用寄存器。
FB 实例固定布局,字段偏移编译期算死。 FB 实例固定布局,字段偏移编译期算死。
### 反汇编格式(disasm
`disasm` 输出一条指令的一行文本,格式冻结:
| 形态 | 格式 | 示例 |
|---|---|---|
| 二寄存器 | `MOVE rd, rs` / `NOT rd, rs` | `MOVE r1, r2` |
| 三寄存器 | `OP rd, ra, rb`(逻辑 / 算术 / 比较) | `ADD r1, r2, r3` |
| 常量 | `LOADK rd, imm` | `LOADK r0, 3` |
| 无条件跳转 | `JMP off` | `JMP +4` |
| 条件跳转 | `JT rd, off` / `JF rd, off` | `JT r0, -1` |
| I/Q/M/全局 | `LOAD_I rd, slot` 等;STORE 同形 `STORE_Q rs, slot` | `LOAD_I r0, 2` |
| FB 内置 | `CAL_TON instance` 等 | `CAL_TON 0` |
| 调用 | `CALL fn_id` | `CALL 1` |
| 返回 | `RET` | `RET` |
| 未知操作码 | `??? 0x<hex>` | — |
跳转偏移带符号、恒显正负号(`%+d`);寄存器一律 `r<n>`;常量/槽号/`fn_id` 无符号十进制。
--- ---
## 映像 ## 映像
魔数 `STSC`,版本 `1` 魔数 `STSC`,版本 `1`全部整数小端。文件 = 头 + 五个段,段序:常量表 → 函数表 → 字节码 → FB 布局 → 数据。偏移记在头里,段必须连续、偏移单调不减,末段终点不超过文件长度。
头: ### 头(72 字节)
- `cycle_limit``dt_ms` | 偏移 | 宽 | 字段 |
- 工程哈希 |---|---|---|
- 全局槽数、I/Q/M 槽数 | 0 | 4 | 魔数 `STSC` |
- 常量表 / 函数表 / 字节码 / FB 布局的偏移 | 4 | 4 | 版本 `1` |
- 入口 `MAIN``fn_id` | 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 64basis `0xcbf29ce484222325`prime `0x100000001b3`
`[[io.input]]` / `[[io.output]]` 不进映像。 `[[io.input]]` / `[[io.output]]` 不进映像。
@@ -134,7 +185,19 @@ FB 实例固定布局,字段偏移编译期算死。
编译产物是单文件 `<name>.stb`:映像头 + 各段按本文布局连续排,魔数 `STSC`、版本 `1`、小端。`STCompiler` 写,`BytecodeExecutor` 读。 编译产物是单文件 `<name>.stb`:映像头 + 各段按本文布局连续排,魔数 `STSC`、版本 `1`、小端。`STCompiler` 写,`BytecodeExecutor` 读。
- `dt_ms` / `cycle_limit` 在映像头,执行器只认映像,不重复配置。 - `dt_ms` / `cycle_limit` 在映像头,执行器只认映像,不重复配置。
- I/O 绑定不进映像:`STCompiler` 另写 sidecar `<name>.runtime.toml`内容是 `var`(已解析槽号)→ `channel` / `bit``BytecodeExecutor` 读 sidecar 做采样与写回,不创造变量。 - I/O 绑定不进映像:`STCompiler` 另写 sidecar `<name>.runtime.toml``BytecodeExecutor` 读 sidecar 做采样与写回,不创造变量。
sidecar 格式(TOML 子集,冻结):
```toml
[[io.input]] # 或 [[io.output]]
var = "I0_0" # 已在 GVL 声明的名字
slot = 1 # 全局槽号(编译期已解析)
channel = 0
bit = 0
```
每行一个绑定;`var` 顺序不要求与映像槽序一致(`slot` 为准)。
--- ---
+9 -2
View File
@@ -6,8 +6,15 @@ project(Compiler
VERSION 0.1 VERSION 0.1
DESCRIPTION "ST / toml 编译器") DESCRIPTION "ST / toml 编译器")
include_directories(./include)
# 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里 # 词法、语法、符号表、类型检查、codegen、链接都留在 compiler 一个库里
add_library(compiler STATIC add_library(compiler STATIC
./src/Lexer.cpp) ./src/Lexer.cpp)
target_include_directories(compiler PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(compiler PUBLIC isa)
# 可执行入口:STCompiler <project.toml> -o <name>.stb
add_executable(STCompiler
./src/main.cpp)
target_link_libraries(STCompiler PRIVATE compiler)
+3 -2
View File
@@ -1,6 +1,7 @@
/** /**
* @file main.cpp * @file main.cpp
* @brief host * @brief STCompiler
* @author
* @date 2026-08-19 * @date 2026-08-19
*/ */
@@ -9,6 +10,6 @@
int main(int argc, char** argv) { int main(int argc, char** argv) {
(void)argc; (void)argc;
(void)argv; (void)argv;
std::printf("Stator host 0.1\n"); std::printf("STCompiler 0.1\n");
return 0; return 0;
} }
+13
View File
@@ -0,0 +1,13 @@
# CMake最低版本
cmake_minimum_required(VERSION 3.21)
# 工程名、版本号、工程描述
project(Executor
VERSION 0.1
DESCRIPTION "字节码执行器:加载 .stb + sidecar 跑扫描周期")
add_executable(BytecodeExecutor
./src/main.cpp)
# 只链 vm + isa,不链 compiler
target_link_libraries(BytecodeExecutor PRIVATE vm)
+5
View File
@@ -0,0 +1,5 @@
# executor
BytecodeExecutor 模块:可执行入口。CMake 目标:`BytecodeExecutor``EXECUTABLE`),链接 `vm``isa`**不链 `compiler`**。
职责与边界见 [`doc/executor/执行器入口.md`](../doc/executor/执行器入口.md)。
+15
View File
@@ -0,0 +1,15 @@
/**
* @file main.cpp
* @brief BytecodeExecutor 可执行入口
* @author
* @date 2026-08-19
*/
#include <cstdio>
int main(int argc, char** argv) {
(void)argc;
(void)argv;
std::printf("BytecodeExecutor 0.1\n");
return 0;
}
-10
View File
@@ -1,10 +0,0 @@
# CMake最低版本
cmake_minimum_required(VERSION 3.21)
# 工程名、版本号、工程描述
project(Host
VERSION 0.1
DESCRIPTION "可执行入口:编译并跑扫描周期")
add_executable(host
./src/main.cpp)
-5
View File
@@ -1,5 +0,0 @@
# host
可执行入口。CMake 目标:`host``EXECUTABLE`),链接 `compiler``vm`
职责与边界见 [`doc/host/宿主入口.md`](../doc/host/宿主入口.md)。
+4 -3
View File
@@ -10,7 +10,8 @@ project(ISA
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
include_directories(./include)
add_library(isa STATIC add_library(isa STATIC
./src/Encode.cpp) ./src/Encode.cpp
./src/Image.cpp)
target_include_directories(isa PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
+31
View File
@@ -0,0 +1,31 @@
/**
* @file Encode.h
* @brief 指令编解码与反汇编
* @author
* @date 2026-08-19
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include "isa/Instr.h"
#include "isa/Op.h"
namespace isa {
// 解码后的字段视图
struct Decoded {
Op op;
uint8_t rd;
uint8_t a;
uint8_t b;
};
// 编解码往返:encode(decode(w)) == w
Instr encode(Decoded d);
Decoded decode(Instr w);
// 反汇编一行文本,写入 out,至多 cap 字节(含 '\0')。格式见 Doc/isa/指令与映像.md。
void disasm(Instr w, char* out, size_t cap);
}
+123
View File
@@ -0,0 +1,123 @@
/**
* @file Image.h
* @brief 映像头、段、FNV-1a 哈希、.stb 文件与 sidecar
* @author
* @date 2026-08-19
*/
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#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 64basis / 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<uint8_t>& 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_;
};
// 写最小映像:空槽 + MAINfn_id=0+ 一条 RET
std::vector<uint8_t> 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<uint8_t>& image,
std::string* err);
bool read_stb_file(const char* path, std::vector<uint8_t>* image,
std::string* err);
// sidecar 生成与写文件(格式见 Doc/isa/指令与映像.md
std::string make_sidecar(const std::vector<IoBinding>& bindings);
bool write_sidecar_file(const char* path,
const std::vector<IoBinding>& bindings,
std::string* err);
}
+3 -3
View File
@@ -32,11 +32,11 @@ namespace isa {
JT = 16, // JT r, offset JT = 16, // JT r, offset
JF = 17, // JF r, offset JF = 17, // JF r, offset
LOAD_I = 18, // LOAD_I rd, slot LOAD_I = 18, // LOAD_I rd, slot
STORE_Q = 19, // STORE_Q slot, rs STORE_Q = 19, // STORE_Q rs, slot
LOAD_M = 20, // LOAD_M rd, slot LOAD_M = 20, // LOAD_M rd, slot
STORE_M = 21, // STORE_M slot, rs STORE_M = 21, // STORE_M rs, slot
LOAD_GLOBAL = 22, // LOAD_GLOBAL rd, slot LOAD_GLOBAL = 22, // LOAD_GLOBAL rd, slot
STORE_GLOBAL = 23, // STORE_GLOBAL slot, rs STORE_GLOBAL = 23, // STORE_GLOBAL rs, slot
CAL_TON = 24, // CAL_TON instance_slot CAL_TON = 24, // CAL_TON instance_slot
CAL_TOF = 25, // CAL_TOF instance_slot CAL_TOF = 25, // CAL_TOF instance_slot
CAL_CTU = 26, // CAL_CTU instance_slot CAL_CTU = 26, // CAL_CTU instance_slot
+7
View File
@@ -20,6 +20,13 @@ namespace isa {
// 差值可负、为 DATE_AND_TIME 与 64 位定时器预留。槽位按 8 字节对齐。 // 差值可负、为 DATE_AND_TIME 与 64 位定时器预留。槽位按 8 字节对齐。
using TIME = int64_t; using TIME = int64_t;
// 槽 / 常量表类型标记,数值冻结:0=BOOL、1=INT、2=TIME(见映像规范)
enum TypeTag : uint8_t {
Bool = 0,
Int = 1,
Time = 2,
};
// 饱和算术:夹到 INT 最小/最大,编译期与 VM 共用同一规则。 // 饱和算术:夹到 INT 最小/最大,编译期与 VM 共用同一规则。
inline INT sat_add(INT a, INT b) { inline INT sat_add(INT a, INT b) {
+96
View File
@@ -0,0 +1,96 @@
/**
* @file Encode.cpp
* @brief 指令编解码与反汇编
* @author
* @date 2026-08-19
*/
#include "isa/Encode.h"
#include <cstdio>
namespace isa {
Instr encode(Decoded d) {
return pack(d.op, d.rd, d.a, d.b);
}
Decoded decode(Instr w) {
Decoded d;
d.op = op(w);
d.rd = rd(w);
d.a = a(w);
d.b = b(w);
return d;
}
void disasm(Instr w, char* out, size_t cap) {
if (cap == 0) {
return;
}
out[0] = '\0';
const Op o = op(w);
const int idx = static_cast<int>(o);
if (idx < 0 || idx >= kOpCount) {
snprintf(out, cap, "??? 0x%08x", static_cast<unsigned>(w));
return;
}
const char* m = mnemonic(o);
switch (o) {
case Op::MOVE:
case Op::NOT:
snprintf(out, cap, "%s r%u, r%u", m,
static_cast<unsigned>(rd(w)), static_cast<unsigned>(a(w)));
break;
case Op::AND:
case Op::OR:
case Op::ADD:
case Op::SUB:
case Op::MUL:
case Op::DIV:
case Op::CMP_EQ:
case Op::CMP_NE:
case Op::CMP_LT:
case Op::CMP_LE:
case Op::CMP_GT:
case Op::CMP_GE:
snprintf(out, cap, "%s r%u, r%u, r%u", m,
static_cast<unsigned>(rd(w)), static_cast<unsigned>(a(w)),
static_cast<unsigned>(b(w)));
break;
case Op::LOADK:
snprintf(out, cap, "%s r%u, %u", m,
static_cast<unsigned>(rd(w)), static_cast<unsigned>(imm16(w)));
break;
case Op::JMP:
snprintf(out, cap, "%s %+d", m, static_cast<int>(off16(w)));
break;
case Op::JT:
case Op::JF:
snprintf(out, cap, "%s r%u, %+d", m,
static_cast<unsigned>(rd(w)), static_cast<int>(off16(w)));
break;
case Op::LOAD_I:
case Op::STORE_Q:
case Op::LOAD_M:
case Op::STORE_M:
case Op::LOAD_GLOBAL:
case Op::STORE_GLOBAL:
snprintf(out, cap, "%s r%u, %u", m,
static_cast<unsigned>(rd(w)), static_cast<unsigned>(imm16(w)));
break;
case Op::CAL_TON:
case Op::CAL_TOF:
case Op::CAL_CTU:
case Op::CALL:
snprintf(out, cap, "%s %u", m, static_cast<unsigned>(imm16(w)));
break;
case Op::RET:
snprintf(out, cap, "%s", m);
break;
}
}
} // namespace isa
+321
View File
@@ -0,0 +1,321 @@
/**
* @file Image.cpp
* @brief 映像头、段、FNV-1a 哈希、.stb 文件与 sidecar
* @author
* @date 2026-08-19
*/
#include "isa/Image.h"
#include <cstdio>
#include <cstring>
#include "isa/Instr.h"
namespace isa {
// ---- 小端读写(只放 .cpp,不把 packed struct 当 ABI----
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 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);
}
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);
}
}
static 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;
}
// ---- 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<uint8_t> write_ret_main(uint32_t cycle_limit, uint32_t dt_ms,
uint64_t project_hash) {
std::vector<uint8_t> 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<uint32_t>(off_funcs)); // offset_const
put_le32(b, 56, static_cast<uint32_t>(off_funcs)); // offset_funcs
put_le32(b, 60, static_cast<uint32_t>(off_code)); // offset_code
put_le32(b, 64, static_cast<uint32_t>(off_end)); // offset_fb
put_le32(b, 68, static_cast<uint32_t>(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<uint64_t>(h.n_consts) * kConstEntrySize) {
v.err_ = "const table size mismatch";
return v;
}
if (funcs_len != static_cast<uint64_t>(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<uint8_t>& 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<uint32_t>(types::Time)) {
e.tag = static_cast<types::TypeTag>(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<uint8_t>& 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<uint8_t>* 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_t>(size));
const bool ok = size == 0 || std::fread(&(*image)[0], 1, static_cast<size_t>(size), f) == static_cast<size_t>(size);
std::fclose(f);
if (!ok && err) {
*err = std::string("read failed: ") + path;
}
return ok;
}
// ---- sidecar ----
std::string make_sidecar(const std::vector<IoBinding>& 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<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) {
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
+14 -3
View File
@@ -1,3 +1,14 @@
# 自动化用例:先放一个必过的空测试,验证 CTest 接线 # 自动化用例
add_test(NAME host_version add_test(NAME stcompiler_version
COMMAND host) COMMAND STCompiler)
add_test(NAME bytecode_executor_version
COMMAND BytecodeExecutor)
# isa 合同测试:链接 isa 库,断言覆盖饱和/编解码/disasm/哈希/映像/sidecar
add_executable(isa_test
./src/isa_test.cpp)
target_link_libraries(isa_test PRIVATE isa)
add_test(NAME isa_roundtrip
COMMAND isa_test)
+236
View File
@@ -0,0 +1,236 @@
/**
* @file isa_test.cpp
* @brief isa 合同测试:饱和、编解码、disasm、哈希、映像、sidecar
* @author
* @date 2026-08-19
*/
#include <cassert>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include "isa/Encode.h"
#include "isa/Image.h"
#include "isa/Instr.h"
#include "isa/Op.h"
#include "isa/Types.h"
static int g_checks = 0;
#define CHECK(cond) \
do { \
if (!(cond)) { \
std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
return false; \
} \
++g_checks; \
} while (0)
// ---- 1. 饱和算术 ----
static bool test_sat() {
using namespace isa::types;
CHECK(sat_add(30000, 30000) == 32767);
CHECK(sat_add(-30000, -30000) == -32768);
CHECK(sat_add(10, 20) == 30);
CHECK(sat_sub(-30000, 30000) == -32768);
CHECK(sat_sub(5, 3) == 2);
CHECK(sat_mul(300, 200) == 32767);
CHECK(sat_mul(-300, 200) == -32768);
CHECK(sat_mul(6, 7) == 42);
CHECK(sat_div(10, 3) == 3);
CHECK(sat_div(-32768, -1) == 32767); // INT_MIN / -1 饱和
CHECK(sat_div(7, 0) == 7); // 除零返回被除数
CHECK(static_cast<int>(TypeTag::Bool) == 0);
CHECK(static_cast<int>(TypeTag::Int) == 1);
CHECK(static_cast<int>(TypeTag::Time) == 2);
return true;
}
// ---- 2. 操作码与助记符 ----
static bool test_op() {
using namespace isa;
CHECK(static_cast<int>(Op::MOVE) == 0);
CHECK(static_cast<int>(Op::RET) == 28);
CHECK(kOpCount == 29);
CHECK(std::strcmp(mnemonic(Op::CAL_CTU), "CAL_CTU") == 0);
CHECK(std::strcmp(mnemonic(static_cast<Op>(255)), "???") == 0);
return true;
}
// ---- 3. 指令打包 / 拆字段 ----
static bool test_instr() {
using namespace isa;
const Instr w = pack(Op::ADD, 5, 3, 4);
CHECK(op(w) == Op::ADD);
CHECK(rd(w) == 5);
CHECK(a(w) == 3);
CHECK(b(w) == 4);
CHECK(imm16(enc_imm(Op::LOADK, 0, 0x1234)) == 0x1234);
CHECK(off16(enc_jmp(-1)) == -1);
CHECK(off16(enc_jmp(4)) == 4);
CHECK(rd(enc_jc(Op::JT, 2, -3)) == 2);
CHECK(off16(enc_jc(Op::JT, 2, -3)) == -3);
CHECK(imm16(enc_call(0xABCD)) == 0xABCD);
CHECK(imm16(enc_slot(Op::LOAD_I, 7, 300)) == 300);
CHECK(rd(enc_slot(Op::LOAD_I, 7, 300)) == 7);
CHECK(rd(enc_rr(Op::MOVE, 1, 2)) == 1);
CHECK(a(enc_rr(Op::MOVE, 1, 2)) == 2);
CHECK(op(enc_ret()) == Op::RET);
return true;
}
// ---- 4. encode / decode / disasm ----
static bool test_encode() {
using namespace isa;
const Instr w = enc_rrr(Op::ADD, 5, 3, 4);
CHECK(encode(decode(w)) == w);
CHECK(decode(w).op == Op::ADD);
CHECK(decode(w).rd == 5);
CHECK(decode(w).a == 3);
CHECK(decode(w).b == 4);
char buf[64];
disasm(w, buf, sizeof buf);
CHECK(std::strcmp(buf, "ADD r5, r3, r4") == 0);
disasm(enc_rr(Op::MOVE, 1, 2), buf, sizeof buf);
CHECK(std::strcmp(buf, "MOVE r1, r2") == 0);
disasm(enc_imm(Op::LOADK, 0, 3), buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r0, 3") == 0);
disasm(enc_jmp(4), buf, sizeof buf);
CHECK(std::strcmp(buf, "JMP +4") == 0);
disasm(enc_jmp(-1), buf, sizeof buf);
CHECK(std::strcmp(buf, "JMP -1") == 0);
disasm(enc_jc(Op::JT, 0, -1), buf, sizeof buf);
CHECK(std::strcmp(buf, "JT r0, -1") == 0);
disasm(enc_call(1), buf, sizeof buf);
CHECK(std::strcmp(buf, "CALL 1") == 0);
disasm(enc_slot(Op::LOAD_I, 0, 2), buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_I r0, 2") == 0);
disasm(enc_slot(Op::STORE_Q, 4, 0), buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_Q r4, 0") == 0);
disasm(enc_ret(), buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
disasm(enc_imm(Op::CAL_TON, 0, 0), buf, sizeof buf);
CHECK(std::strcmp(buf, "CAL_TON 0") == 0);
disasm(0xFFu, buf, sizeof buf); // 未知操作码
CHECK(std::strcmp(buf, "??? 0x000000ff") == 0);
return true;
}
// ---- 5. FNV-1a 64 ----
static bool test_fnv() {
using namespace isa;
CHECK(fnv1a64(0, 0) == kFnvBasis);
const uint8_t a1[] = {'a'};
CHECK(fnv1a64(a1, 1) == 0xaf63dc4c8601ec8cull);
return true;
}
// ---- 6. 映像:write_ret_main → ImageView ----
static bool test_image() {
using namespace isa;
const uint64_t hash = 0x123456789abcdef0ull;
std::vector<uint8_t> img = write_ret_main(100000, 10, hash);
ImageView v = ImageView::from(img);
CHECK(v.ok());
CHECK(v.error().empty());
CHECK(v.header().cycle_limit == 100000);
CHECK(v.header().dt_ms == 10);
CHECK(v.header().project_hash == hash);
CHECK(v.header().entry_fn_id == 0);
CHECK(v.header().n_funcs == 1);
CHECK(v.header().n_globals == 0);
CHECK(v.header().n_i == 0);
CHECK(v.header().n_q == 0);
CHECK(v.header().n_m == 0);
CHECK(v.header().offset_const == kHeaderSize);
CHECK(v.header().offset_data == kHeaderSize + kFuncRowSize + 4);
CHECK(v.code_len() == 4);
CHECK(v.data_len() == 0);
const FuncRow r = v.func_row(0);
CHECK(r.nregs == 0);
CHECK(r.code_offset == 0);
CHECK(r.code_len == 1);
char buf[32];
disasm(v.code_bytes()[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "RET") == 0);
// 损坏映像必须拒绝
std::vector<uint8_t> bad = img;
bad[0] = 'X';
CHECK(!ImageView::from(bad).ok());
std::vector<uint8_t> bad2 = img;
bad2[60] = 255; // offset_code 越界
CHECK(!ImageView::from(bad2).ok());
std::vector<uint8_t> bad3 = img;
bad3[48] = 2; // n_funcs=2 但表只有 1 行
CHECK(!ImageView::from(bad3).ok());
std::vector<uint8_t> bad4 = img;
bad4[24] = 1; // entry_fn_id 越界
CHECK(!ImageView::from(bad4).ok());
std::vector<uint8_t> short_img = img;
short_img.resize(kHeaderSize - 1);
CHECK(!ImageView::from(short_img).ok());
return true;
}
// ---- 7. .stb 文件与 sidecar ----
static bool test_files() {
using namespace isa;
std::vector<uint8_t> img = write_ret_main(100000, 10, 0);
const char* stb_path = "isa_test_tmp.stb";
std::string err;
CHECK(write_stb_file(stb_path, img, &err));
std::vector<uint8_t> back;
CHECK(read_stb_file(stb_path, &back, &err));
CHECK(back == img);
CHECK(ImageView::from(back).ok());
std::remove(stb_path);
std::vector<IoBinding> bs;
IoBinding b1;
b1.var = "I0_0"; b1.slot = 1; b1.channel = 0; b1.bit = 0; b1.is_input = true;
IoBinding b2;
b2.var = "Q0_0"; b2.slot = 3; b2.channel = 0; b2.bit = 0; b2.is_input = false;
bs.push_back(b1);
bs.push_back(b2);
const std::string sc = make_sidecar(bs);
CHECK(sc.find("[[io.input]]") == 0);
CHECK(sc.find("var = \"I0_0\"") != std::string::npos);
CHECK(sc.find("slot = 1") != std::string::npos);
CHECK(sc.find("channel = 0") != std::string::npos);
CHECK(sc.find("[[io.output]]") != std::string::npos);
CHECK(sc.find("var = \"Q0_0\"") != std::string::npos);
CHECK(sc.find("slot = 3") != std::string::npos);
const char* sc_path = "isa_test_tmp.runtime.toml";
CHECK(write_sidecar_file(sc_path, bs, &err));
std::remove(sc_path);
return true;
}
int main() {
if (!test_sat()) return 1;
if (!test_op()) return 1;
if (!test_instr()) return 1;
if (!test_encode()) return 1;
if (!test_fnv()) return 1;
if (!test_image()) return 1;
if (!test_files()) return 1;
std::printf("isa_test: %d checks passed\n", g_checks);
return 0;
}
+3 -2
View File
@@ -6,7 +6,8 @@ project(VM
VERSION 0.1 VERSION 0.1
DESCRIPTION "寄存器虚拟机") DESCRIPTION "寄存器虚拟机")
include_directories(./include)
add_library(vm STATIC add_library(vm STATIC
./src/Machine.cpp) ./src/Machine.cpp)
target_include_directories(vm PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(vm PUBLIC isa)