12.10 双可执行接线:编译产出 sidecar,BytecodeExecutor 跑周期。

- STCompiler -o:另写 <name>.runtime.toml(io 绑定 var→槽号→channel/bit,槽号来自链接结果)
- BytecodeExecutor <name>.stb [--cycles N] [--replay <file>]:加载 .stb + sidecar,
  逐周期采样 I(回放行按 io.input 顺序)→ 跑周期 → 打印 I/Q;故障打印 FAULT 退出码 1
- 手写 sidecar 解析(冻结 TOML 子集);Machine 暴露 header()(dt_ms/cycle_limit)
- 验证:line1 回放 0/1 组合 Q 正确(急停/start 语义)、用例 06 CycleLimit FAULT;ctest 10/10
This commit is contained in:
2026-08-21 20:26:28 +08:00
parent 8c0fc4fe98
commit 00565f035d
5 changed files with 267 additions and 4 deletions
+10
View File
@@ -4,6 +4,16 @@ BytecodeExecutor 模块:可执行入口。CMake 目标:`BytecodeExecutor`
第一版两个可执行:`STCompiler` 编译出 `<name>.stb``<name>.runtime.toml``BytecodeExecutor` 加载映像按扫描周期跑。总顺序见 [`初步计划.md`](../初步计划.md) 第 12.10 节。
## 用法
```text
BytecodeExecutor <name>.stb [--cycles N] [--replay <file>]
```
- `--cycles N`:跑 N 个周期(缺省 10
- `--replay <file>`:读录制的 I 序列(每行空格分隔的 0/1,按 sidecar `io.input` 绑定顺序),逐周期喂入;读尽后保持最后一行
- 每周期打印 `cycle N: I=[...] Q=[...]`(按 sidecar 绑定顺序);故障打印 `FAULT(n)` 并退出码 1
## 职责
- 加载 `.stb` 映像与 `<name>.runtime.toml` sidecarI/O 绑定 var → 槽号 → channel/bit
+23
View File
@@ -5,8 +5,10 @@
* @date 2026-08-21
*/
#include <cctype>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <string>
#include <vector>
@@ -167,6 +169,27 @@ int main(int argc, char** argv) {
return 1;
}
// sidecarI/O 绑定 var → 槽号 → channel/bit(不进映像,执行器采样用)
std::vector<isa::IoBinding> bindings;
for (const isa::IoBinding& b : proj.io) {
isa::IoBinding out_b = b;
std::string key = b.var;
for (char& ch : key) {
ch = static_cast<char>(std::tolower(static_cast<unsigned char>(ch)));
}
const auto it = link.global_index.find(key);
if (it != link.global_index.end()) {
out_b.slot = it->second; // 12.6 已校验存在
}
bindings.push_back(out_b);
}
std::filesystem::path sidecar = std::filesystem::path(out_path);
sidecar.replace_extension(".runtime.toml");
if (!isa::write_sidecar_file(sidecar.string().c_str(), bindings, &err)) {
std::fprintf(stderr, "error: %s\n", err.c_str());
return 1;
}
const isa::ImageView v = isa::ImageView::from(image);
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);
+229 -3
View File
@@ -2,14 +2,240 @@
* @file main.cpp
* @brief BytecodeExecutor 可执行入口
* @author
* @date 2026-08-19
* @date 2026-08-21
*
* @details 12.10:加载 .stb + sidecar,按扫描周期执行。
* - `BytecodeExecutor <name>.stb [--cycles N] [--replay <file>]`
* - 只链 vm + isa,不链 compilerdt_ms / cycle_limit 从映像头取
* - I 采样 / Q 写回按 sidecarvar → 槽号 → channel/bit),不创造变量
* - --replay:读录制文本(每行空格分隔的 0/1,按 io.input 绑定顺序),
* 逐周期喂入;文件读尽后保持最后一行
* - 每周期打印 I/Q(最小可观测)
*/
#include <cctype>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <string>
#include <vector>
#include "isa/Image.h"
#include "vm/Machine.h"
namespace {
void usage() {
std::printf("usage: BytecodeExecutor <name>.stb [--cycles N] [--replay <file>]\n"
" 加载 .stb + <name>.runtime.toml,按扫描周期执行\n"
" --cycles N 跑 N 个周期(缺省 10)\n"
" --replay <file> 读录制的 I 序列(每行按 io.input 顺序的 0/1\n"
" --help 打印本帮助\n");
}
// sidecar 绑定(TOML 子集,格式冻结于 Doc/isa/指令与映像.md
struct Binding {
bool is_input = true;
std::string var;
uint32_t slot = 0;
uint32_t channel = 0;
uint32_t bit = 0;
};
// 解析 <name>.runtime.toml(逐行手写解析,仅冻结子集)
bool parse_sidecar(const std::string& path, std::vector<Binding>* out,
std::string* err) {
std::ifstream in(path);
if (!in) {
*err = "cannot open sidecar: " + path;
return false;
}
std::string line;
bool is_input = true;
bool in_entry = false;
Binding cur;
while (std::getline(in, line)) {
// 去首尾空白
size_t b = line.find_first_not_of(" \t\r");
if (b == std::string::npos) {
continue;
}
line = line.substr(b);
if (line.empty() || line[0] == '#') {
continue;
}
if (line == "[[io.input]]") {
if (in_entry) out->push_back(cur);
cur = Binding();
is_input = true;
in_entry = true;
continue;
}
if (line == "[[io.output]]") {
if (in_entry) out->push_back(cur);
cur = Binding();
is_input = false;
in_entry = true;
continue;
}
const size_t eq = line.find('=');
if (eq == std::string::npos) {
continue;
}
const std::string key = line.substr(0, eq);
std::string val = line.substr(eq + 1);
const size_t b2 = key.find_first_not_of(" \t");
const size_t e2 = key.find_last_not_of(" \t");
const size_t b3 = val.find_first_not_of(" \t");
const size_t e3 = val.find_last_not_of(" \t\r");
const std::string k = key.substr(b2, e2 - b2 + 1);
const std::string v = val.substr(b3, e3 - b3 + 1);
if (k == "var") {
cur.var = v.substr(1, v.size() - 2); // 去引号
} else if (k == "slot") {
cur.slot = static_cast<uint32_t>(std::stoul(v));
} else if (k == "channel") {
cur.channel = static_cast<uint32_t>(std::stoul(v));
} else if (k == "bit") {
cur.bit = static_cast<uint32_t>(std::stoul(v));
}
cur.is_input = is_input;
}
if (in_entry) {
out->push_back(cur);
}
return true;
}
// 读一个槽的 BOOL 值(低字节)
int slot_bool(vm::Machine& m, uint32_t slot) {
return m.data()[static_cast<size_t>(slot) * 8] ? 1 : 0;
}
void put_bool(vm::Machine& m, uint32_t slot, int v) {
m.data()[static_cast<size_t>(slot) * 8] = v ? 1 : 0;
}
} // namespace
int main(int argc, char** argv) {
(void)argc;
(void)argv;
if (argc < 2) {
std::printf("BytecodeExecutor 0.1\n");
usage();
return 0;
}
if (std::strcmp(argv[1], "--help") == 0) {
usage();
return 0;
}
const std::string stb_path = argv[1];
uint32_t cycles = 10;
std::string replay;
for (int i = 2; i < argc; ++i) {
if (std::strcmp(argv[i], "--cycles") == 0 && i + 1 < argc) {
cycles = static_cast<uint32_t>(std::stoul(argv[i + 1]));
} else if (std::strcmp(argv[i], "--replay") == 0 && i + 1 < argc) {
replay = argv[i + 1];
}
}
std::string err;
std::vector<uint8_t> bytes;
if (!isa::read_stb_file(stb_path.c_str(), &bytes, &err)) {
std::fprintf(stderr, "error: %s\n", err.c_str());
return 1;
}
vm::Machine machine;
if (!vm::Machine::create(bytes, &machine, &err)) {
std::fprintf(stderr, "error: %s\n", err.c_str());
return 1;
}
// sidecar<name>.runtime.toml
std::string sidecar_path = stb_path;
const size_t dot = sidecar_path.find_last_of('.');
if (dot != std::string::npos) {
sidecar_path = sidecar_path.substr(0, dot);
}
sidecar_path += ".runtime.toml";
std::vector<Binding> bindings;
if (!parse_sidecar(sidecar_path, &bindings, &err)) {
std::fprintf(stderr, "error: %s\n", err.c_str());
return 1;
}
// 回放缓冲
std::vector<int> replay_line;
std::ifstream replay_in;
if (!replay.empty()) {
replay_in.open(replay);
if (!replay_in) {
std::fprintf(stderr, "error: cannot open replay: %s\n", replay.c_str());
return 1;
}
}
const isa::ImageHeader& h = machine.header();
std::printf("image: %s (%zu bytes, dt_ms=%u, cycle_limit=%u)\n", stb_path.c_str(),
bytes.size(), h.dt_ms, h.cycle_limit);
for (uint32_t c = 1; c <= cycles; ++c) {
// 采样:回放行按 io.input 顺序填,无回放则全 0
if (replay_in.is_open()) {
std::string line;
if (std::getline(replay_in, line)) {
replay_line.clear();
size_t pos = 0;
while (pos < line.size()) {
while (pos < line.size() &&
std::isspace(static_cast<unsigned char>(line[pos]))) {
++pos;
}
if (pos >= line.size()) break;
const size_t s = pos;
while (pos < line.size() &&
!std::isspace(static_cast<unsigned char>(line[pos]))) {
++pos;
}
replay_line.push_back(std::stoi(line.substr(s, pos - s)));
}
}
size_t idx = 0;
for (const Binding& b : bindings) {
if (!b.is_input) continue;
put_bool(machine, b.slot, idx < replay_line.size() ? replay_line[idx] : 0);
++idx;
}
} else {
for (const Binding& b : bindings) {
if (b.is_input) {
put_bool(machine, b.slot, 0);
}
}
}
const vm::Fault f = machine.run_cycle();
// 打印 I/Q(按 sidecar 绑定顺序)
std::string is, qs;
for (const Binding& b : bindings) {
if (b.is_input) {
if (!is.empty()) is += " ";
is += std::to_string(slot_bool(machine, b.slot));
} else {
if (!qs.empty()) qs += " ";
qs += std::to_string(slot_bool(machine, b.slot));
}
}
std::printf("cycle %u: I=[%s] Q=[%s]", c, is.c_str(), qs.c_str());
if (f != vm::Fault::None) {
std::printf(" FAULT(%d)", static_cast<int>(f));
}
std::printf("\n");
if (f != vm::Fault::None) {
return 1;
}
}
return 0;
}
+3
View File
@@ -62,6 +62,9 @@ namespace vm {
/** @brief 最近一次故障(step 返回 false 后查询) */
Fault fault() const;
/** @brief 映像头(dt_ms / cycle_limit 等) */
const isa::ImageHeader& header() const;
/** @brief 当前指令下标(相对当前函数字节码段) */
uint32_t pc() const;
+1
View File
@@ -78,6 +78,7 @@ bool Machine::step() {
}
Fault Machine::fault() const { return fault_; }
const isa::ImageHeader& Machine::header() const { return image_.header(); }
uint32_t Machine::pc() const { return pc_; }
uint32_t Machine::fn_id() const { return cur_frame().fn_id; }
uint32_t Machine::cycle_count() const { return cycle_count_; }