diff --git a/README.md b/README.md index 666bd056..8c528473 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ make all # runtime(C) + layout(Rust) + runti ./bin/kvlang vet my.kv # parse + lower only ./bin/kvlang format my.kv # format to stdout ./bin/kvlang layout my.kv # print the entry point -./bin/kvlang dump my.kv # reconstruct /lib as runnable kvlang +./bin/kvlang printlib my.kv # reconstruct /lib as runnable kvlang (layout result, no .src) ``` `make` targets: `all` · `runtime` · `layout` · `runtime-rs` · `json` · `oldhero` · `test` · `install` · `clean`. `make install` places the libraries in `/usr/lib`, the CLIs (`kvlang`, `kvlanglayout`) in `/usr/bin`, and headers in `/usr/include/kvlang`. @@ -279,7 +279,7 @@ Map traversal uses `while` + `kvspace·listlen` + `kvspace·listn` (`for`-`in` i **`time·` / `time/duration·` / `random·`:** `now` `sub` `add` `before` `after`; `nanos` `millis` `seconds` `minutes` `hours` and the `as_*` forms; `uint64` `int63` `intn` **`vthread·` / debug:** `create` `run` `call` `sleep` `setstatus`; `debugger` (≡ `vthread·setstatus("paused")`) -`print` / `println` / `cerr` / `printf` / `input` are **not** builtins. In the KV world there is no terminal — only keys and values — so I/O is not a core-language primitive. They are opcodes of the `term` runtime, reached through the `def rwir` route-header mechanism described above. The same mechanism covers `json·to` / `json·from`, `http·call` / `http·get|post|put|del`, `networld/proc·exec`, `networld/fs·size|read|write|append|list|del|mkdir|exists`, and the self-hosting `kvlang·vet|format|layout|dump`. +`print` / `println` / `cerr` / `printf` / `input` are **not** builtins. In the KV world there is no terminal — only keys and values — so I/O is not a core-language primitive. They are opcodes of the `term` runtime, reached through the `def rwir` route-header mechanism described above. The same mechanism covers `json·to` / `json·from`, `http·call` / `http·get|post|put|del`, `networld/proc·exec`, `networld/fs·size|read|write|append|list|del|mkdir|exists`, and the self-hosting `kvlang·vet|format|layout|printlib|printstack`. ```kv print(x,…) // no spaces, no newline diff --git a/README_CN.md b/README_CN.md index ab52d3ba..58d1a9e9 100644 --- a/README_CN.md +++ b/README_CN.md @@ -100,7 +100,7 @@ make all # runtime(C) + layout(Rust) + runti ./bin/kvlang vet my.kv # 只 parse + lower ./bin/kvlang format my.kv # 格式化到 stdout ./bin/kvlang layout my.kv # 打印入口点 -./bin/kvlang dump my.kv # 把 /lib 逆向重建为可运行 kvlang +./bin/kvlang printlib my.kv # 把 /lib 重建为可运行 kvlang(看 layout 结果,不读 .src) ``` `make` 目标:`all` · `runtime` · `layout` · `runtime-rs` · `json` · `oldhero` · `test` · `install` · `clean`。`make install` 把库装到 `/usr/lib`、CLI(`kvlang`、`kvlanglayout`)装到 `/usr/bin`、头文件装到 `/usr/include/kvlang`。 @@ -279,7 +279,7 @@ map 遍历用 `while` + `kvspace·listlen` + `kvspace·listn`(`for`-`in` 对 m **`time·` / `time/duration·` / `random·`:** `now` `sub` `add` `before` `after`;`nanos` `millis` `seconds` `minutes` `hours` 及各 `as_*` 形式;`uint64` `int63` `intn` **`vthread·` / 调试:** `create` `run` `call` `sleep` `setstatus`;`debugger`(≡ `vthread·setstatus("paused")`) -`print` / `println` / `cerr` / `printf` / `input` **不是**内建。KV 世界里没有终端,只有 key 和 value——I/O 不是核心语言原语。它们是 `term` runtime 的操作码,经上文所述的 `def rwir` 路由头机制到达。同一机制覆盖 `json·to` / `json·from`、`http·call` / `http·get|post|put|del`、`networld/proc·exec`、`networld/fs·size|read|write|append|list|del|mkdir|exists`,以及自举的 `kvlang·vet|format|layout|dump`。 +`print` / `println` / `cerr` / `printf` / `input` **不是**内建。KV 世界里没有终端,只有 key 和 value——I/O 不是核心语言原语。它们是 `term` runtime 的操作码,经上文所述的 `def rwir` 路由头机制到达。同一机制覆盖 `json·to` / `json·from`、`http·call` / `http·get|post|put|del`、`networld/proc·exec`、`networld/fs·size|read|write|append|list|del|mkdir|exists`,以及自举的 `kvlang·vet|format|layout|printlib|printstack`。 ```kv print(x,…) // 无空格、无换行 diff --git a/layout/src/bin/kvlanglayout.rs b/layout/src/bin/kvlanglayout.rs index a4134eb2..96a4cfa8 100644 --- a/layout/src/bin/kvlanglayout.rs +++ b/layout/src/bin/kvlanglayout.rs @@ -4,12 +4,12 @@ //! kvlanglayout [dsn] 仅 layout,打印 ENTRY=(默认子命令) //! kvlanglayout vet 仅校验(parse+lower),打印 ok 或错误 //! kvlanglayout format 格式化输出到 stdout -//! kvlanglayout dump [prefix] [dsn] layout 后把 /lib(或 prefix)子树 dump 为可运行 kvlang + 槽位注释 +//! kvlanglayout printlib [prefix] [dsn] layout 后把 /lib(或 prefix)子树重建为可运行 kvlang + 槽位注释(不读 .src) use std::env; use std::fs; -use kvlanglayout::{compile, dump, format, init_dirs, vet, Kv}; +use kvlanglayout::{compile, format, init_dirs, printlib, vet, Kv}; /// 复刻 Go runtime 的 findEntry:DFS /lib/ 找首个 init(顶层 `init` 或 lib 块内 `pkg·init`)。 fn find_entry(kv: &mut Kv, prefix: &str, pkg: &str) -> String { @@ -69,7 +69,7 @@ fn main() { } return; } - if args.len() >= 3 && args[1] == "dump" { + if args.len() >= 3 && args[1] == "printlib" { let prefix = args.get(3).map(String::as_str).unwrap_or("/lib"); let dsn = args .get(4) @@ -79,11 +79,11 @@ fn main() { let mut kv = Kv::conn(dsn); init_dirs(&mut kv).expect("init_dirs"); compile(&mut kv, &src).expect("compile"); - print!("{}", dump(&mut kv, prefix)); + print!("{}", printlib(&mut kv, prefix)); return; } if args.len() < 2 { - eprintln!("usage: kvlanglayout [dsn] | kvlanglayout {{vet|format}} | kvlanglayout dump [prefix] [dsn]"); + eprintln!("usage: kvlanglayout [dsn] | kvlanglayout {{vet|format}} | kvlanglayout printlib [prefix] [dsn]"); std::process::exit(1); } let dsn = args diff --git a/layout/src/capi.rs b/layout/src/capi.rs index 47c0255f..9632ba0b 100644 --- a/layout/src/capi.rs +++ b/layout/src/capi.rs @@ -5,7 +5,8 @@ //! kvlangLayoutVet(src,…) 只校验(parse+lower),不写 kvspace —— 自造代码闸门 //! kvlangLayoutFormat(src,…) 格式化(parse → 规范化源码),不写 kvspace //! kvlangLayoutCode(src,dsn,…) 从源码串 layout 进 kvspace(LLM 生成即插入,不落盘) -//! kvlangLayoutDump(lib,dsn,…) 把 /lib 子树递归导出为可读文本(审查 lower 后的 code) +//! kvlangLayoutPrintlib(lib,dsn,…) 把 /lib 子树逆向重建为可运行 kvlang(审查 layout 结果,不读 .src) +//! kvlangLayoutPrintstack(vid,dsn,…) 把 /vthread 活动栈渲染成可读文本(帧链 + 实参 + 当前指令) //! kvlangLayoutFile(path,…) 是 Code 的薄封装(读文件后走同一 core)。源码读回(`.src`) //! 是纯 KV 读(/lib/.src),不在此 ABI。 //! @@ -17,7 +18,7 @@ use std::fs; use std::os::raw::c_char; use std::panic::catch_unwind; -use crate::{compile, dump, format, init_dirs, kvkind, vet, Kv}; +use crate::{compile, format, init_dirs, kvkind, printlib, printstack, vet, Kv}; fn cstr<'a>(p: *const c_char) -> &'a str { if p.is_null() { @@ -149,11 +150,12 @@ pub extern "C" fn kvlangLayoutFormat( } } -/// dump:把 lib 前缀下的整棵子树重构为可运行的 kvlang 源码(还原 `lib {}` 与 `rwfunc`), +/// printlib:把 lib 前缀下的整棵子树重构为可运行的 kvlang 源码(还原 `lib {}` 与 `rwfunc`), /// lower 后的原始槽位以 `#` 注释附在各自函数后,供审查。 -/// 成功返回 0(out=dump 文本),失败返回 -1(err_out=错误)。 +/// **只看 layout 结果**:签名读参数定义键、体读线性指令槽,不读 `.src` 源码副本。 +/// 成功返回 0(out=printlib 文本),失败返回 -1(err_out=错误)。 #[no_mangle] -pub extern "C" fn kvlangLayoutDump( +pub extern "C" fn kvlangLayoutPrintlib( lib: *const c_char, dsn: *const c_char, out: *mut c_char, @@ -165,7 +167,7 @@ pub extern "C" fn kvlangLayoutDump( let dsn = cstr(dsn).to_string(); let r = catch_unwind(|| -> Result { let mut kv = Kv::conn(&dsn); - Ok(dump(&mut kv, &lib)) + Ok(printlib(&mut kv, &lib)) }); match r { Ok(Ok(s)) => { @@ -177,7 +179,41 @@ pub extern "C" fn kvlangLayoutDump( -1 } Err(_) => { - write_out(err_out, err_cap, "dump panicked"); + write_out(err_out, err_cap, "printlib panicked"); + -1 + } + } +} + +/// printstack:把 /vthread/ 的活动栈渲染成可读文本(帧链 + 每帧实参 + 顶帧当前指令)。 +/// 只读,不改 ‥pc/‥status;暂停/恢复由调用方(harness)负责。 +/// 成功返回 0(out=printstack 文本),失败返回 -1(err_out=错误)。 +#[no_mangle] +pub extern "C" fn kvlangLayoutPrintstack( + vid: *const c_char, + dsn: *const c_char, + out: *mut c_char, + out_cap: u32, + err_out: *mut c_char, + err_cap: u32, +) -> i32 { + let vid = cstr(vid).to_string(); + let dsn = cstr(dsn).to_string(); + let r = catch_unwind(|| -> Result { + let mut kv = Kv::conn(&dsn); + Ok(printstack(&mut kv, &vid)) + }); + match r { + Ok(Ok(s)) => { + write_out(out, out_cap, &s); + 0 + } + Ok(Err(e)) => { + write_out(err_out, err_cap, &e); + -1 + } + Err(_) => { + write_out(err_out, err_cap, "printstack panicked"); -1 } } diff --git a/layout/src/code.rs b/layout/src/code.rs index b71523a5..9ec53372 100644 --- a/layout/src/code.rs +++ b/layout/src/code.rs @@ -5,11 +5,11 @@ //! /lib/·.[0,±k] 参数定义键(langtype=def langtype, body=名字\x00类型) //! /lib/·/[i,j] 编译后指令(kind=rwir),i 从 1 开始 //! /lib/·/‥labels/ label → irseq -//! /lib/·.src 源码副本(仅 write_func 保留写入,dump 不再依赖) +//! /lib/·.src 源码副本(仅 write_func 保留写入,printlib 不再依赖) //! //! WriteBody: DFS-number insts (incl. ScopeStmt), emit [i,j], rewrite goto/br labels to irseq. -//! dump: 反向——严格读 /lib/ 子树重建 AST(签名读参数定义键 .[0,±k]、体读线性槽+‥labels), -//! 不读 .src、不依赖签名行 [0,x] 静态槽。 +//! printlib: 反向——严格读 /lib/ 子树重建 AST(签名读参数定义键 .[0,±k]、体读线性槽+‥labels), +//! 不读 .src、不依赖签名行 [0,x] 静态槽。printstack: /vthread/ 活动栈渲染(只读)。 use std::collections::HashMap; @@ -165,9 +165,10 @@ pub fn vet(src: &str) -> Result<(), String> { Ok(()) } -/// dump:把 /lib 子树重构为可运行的 kvlang 源码(还原 `lib {}` 与 `rwfunc`), -/// lower 后的原始槽位(`key kind:value`)以 `#` 注释附在各自函数后,供审查。 -pub fn dump(kv: &mut Kv, lib: &str) -> String { +/// printlib:把 /lib 子树重构为可运行的 kvlang 源码(**只看 layout 结果,不读 `.src`**: +/// 签名读参数定义键、函数体读线性指令槽),lower 后的原始槽位(`key kind:value`) +/// 以 `#` 注释附在各自函数后,供审查。 +pub fn printlib(kv: &mut Kv, lib: &str) -> String { // lib 是 /lib 下任意 prefix,只 dump 该子树。三种情形: // /lib → 全量 // /lib/foo → 虚拟 pkg(func 存于 /lib/foo·* 扁平目录):走全树后过滤 @@ -369,7 +370,7 @@ fn emit_func(out: &mut String, f: &DumpFunc, indent: &str) { out.push('\n'); } -// ── dump 重建:严格从 /lib 子树反出可运行 kvlang(不读 .src)───────────── +// ── printlib 重建:严格从 /lib 子树反出可运行 kvlang(**不读 .src**)─────── // // 数据来源(对齐 write_func / spec「指令布局格式」): // 签名 ← [0,0] 计数头(nr,nw,dyn) + 命名参数 Ptr 键(langtype=类型、body=[0,±k] 定位读/写与序) @@ -991,6 +992,136 @@ fn is_literal(s: &str) -> bool { || (b[0] == b'-' && s.len() > 1) } +// ── printstack:/vthread 活动栈渲染(帧链 + 每帧实参 + 顶帧当前指令)────────── +// +// 实测帧布局(2026-09): +// /vthread//‥pc / ‥status / ‥error/msg vthread 头(仅根) +// /vthread//[k]/‥lib / ‥callpc / ‥returnpc 第 k 帧(帧目录,k 为帧号) +// /vthread//[k]/[0,±j] 该帧绑定的实参 / 写参 +// /vthread//[k]/[s0,s1] 帧内指令槽;PC 指向当前那一条 +// 只读:不碰 ‥pc/‥status,不写任何槽——暂停/恢复是调用方(harness)的事。 + +/// 帧目录按帧号升序:`/vthread//[k]/`(k 为整数)。 +fn frame_dirs(kv: &mut Kv, root: &str) -> Vec { + let mut v: Vec<(i64, String)> = kv + .list(&format!("{root}/"), false, false) + .into_iter() + .filter_map(|n| { + let t = n.trim_end_matches('/'); + if !(t.starts_with('[') && t.ends_with(']')) { + return None; + } + t[1..t.len() - 1] + .parse::() + .ok() + .map(|k| (k, format!("{root}/{t}"))) + }) + .collect(); + v.sort(); + v.into_iter().map(|(_, p)| p).collect() +} + +/// 值截断到 200 字符(整份文件内容躺在槽里时不该刷屏)。 +fn clip(s: String) -> String { + let n = s.chars().count(); + if n <= 200 { + return s; + } + let mut out: String = s.chars().take(200).collect(); + out.push('…'); + out +} + +/// 字符串键的纯值(char 类给内容,其余给 kind:value / 空串)。 +fn plain_of(kv: &mut Kv, key: &str) -> String { + let d = kv.get_one(key); + if d.is_empty() { + return String::new(); + } + let k = kvkind::kind(&d); + if kvkind::is_char_kind(&k) { + kvkind::value_string(&d) + } else { + let s = kvkind::display(&d); + if s == "None" { + String::new() + } else { + s + } + } +} + +/// printstack:把 /vthread/ 的活动栈渲染成可读文本;vid 必填 +/// (「当前 vthread」由调用方从自己的 PC 前缀取,见 runtime 侧 rwir)。 +pub fn printstack(kv: &mut Kv, vid: &str) -> String { + if vid.is_empty() { + return "error: printstack requires vid".to_string(); + } + let root = format!("/vthread/{vid}"); + // 头部字段都是字符串值:给纯值(不带 kind 前缀);槽位值才用 kind:value(类型有意义)。 + let status = plain_of(kv, &format!("{root}/‥status")); + let pc = plain_of(kv, &format!("{root}/‥pc")); + let emsg = plain_of(kv, &format!("{root}/‥error/msg")); + let frames = frame_dirs(kv, &root); + let mut out = String::new(); + out.push_str(&format!( + "vthread {vid} status={status} frames={}\n", + frames.len() + )); + if !emsg.is_empty() { + out.push_str(&format!("error: {emsg}\n")); + } + if !pc.is_empty() { + out.push_str(&format!("pc: {pc}\n")); + } + // 顶帧 = 其路径是 PC 前缀的那个(帧号最大者优先)。帧内**指令槽不是帧成员** + // (帧只存自己的实参/写参),指令经 `‥lib` 软链到函数指令树、由 PC 定位—— + // 所以当前指令直接读 PC 那个键。 + let cur = frames + .iter() + .rev() + .find(|f| pc.contains(f.as_str())) + .cloned(); + for (i, f) in frames.iter().enumerate() { + let lib = plain_of(kv, &format!("{f}/‥lib")); + out.push_str(&format!("frame[{i}] {lib}\n")); + for key in ["‥callpc", "‥returnpc"] { + let v = plain_of(kv, &format!("{f}/{key}")); + if !v.is_empty() { + out.push_str(&format!(" {key} = {v}\n")); + } + } + // 帧成员 = 实参槽 `[0,±j]` + **命名局部**(变量名即成员名,如 text/total/sz) + // + `‥*`。命名局部要用 expand_ext 才列得出来(kvspace·list 默认不展开)。 + let mut names: Vec = kv + .list(&format!("{f}/"), true, true) + .into_iter() + .map(|n| n.trim_end_matches('/').to_string()) + .collect(); + names.sort(); + for n in names { + if n.starts_with('‥') { + continue; + } + if n == "[0,0]" { + continue; // 帧的签名锚点,不是值槽 + } + let is_arg = n.starts_with("[0,"); + let is_local = !is_arg && !n.starts_with('['); + if !(is_arg || is_local) { + continue; + } + let v = kvkind::display(&kv.get_one(&format!("{f}/{n}"))); + out.push_str(&format!(" {n} = {}\n", clip(v))); + } + if cur.as_deref() == Some(f.as_str()) && !pc.is_empty() { + let v = kvkind::display(&kv.get_one(&pc)); + out.push_str(&format!(" cur = {}\n", clip(v))); + } + } + out +} + #[cfg(test)] mod tests { use super::*; diff --git a/layout/src/lib.rs b/layout/src/lib.rs index de86a836..3a26bd2c 100644 --- a/layout/src/lib.rs +++ b/layout/src/lib.rs @@ -16,6 +16,8 @@ pub mod parser; pub mod scanner; pub mod symbol; -pub use code::{compile, dump, format, init_dirs, vet, write_func, write_rwir_decl}; +pub use code::{ + compile, format, init_dirs, printlib, printstack, vet, write_func, write_rwir_decl, +}; pub use ffi::Kv; pub use scanner::Diagnostic; diff --git a/runtime-rs/build.rs b/runtime-rs/build.rs index 61877adc..2f558917 100644 --- a/runtime-rs/build.rs +++ b/runtime-rs/build.rs @@ -24,7 +24,8 @@ fn main() { let bin = format!("{manifest}/../bin"); // kvlang/bin(新构建的 libkvlang_runtime.so) // KVSPACE_LIB_DIR 可覆盖安装目录:macOS 的 /usr 受 SIP 保护,应指向 /lib/kvspace。 - let kvspace_dir = std::env::var("KVSPACE_LIB_DIR").unwrap_or_else(|_| "/usr/lib/kvspace".into()); + let kvspace_dir = + std::env::var("KVSPACE_LIB_DIR").unwrap_or_else(|_| "/usr/lib/kvspace".into()); println!("cargo:rustc-link-search=native={bin}"); println!("cargo:rustc-link-search=native={kvspace_dir}"); diff --git a/runtime-rs/src/ffi.rs b/runtime-rs/src/ffi.rs index d9b2a4db..6361c418 100644 --- a/runtime-rs/src/ffi.rs +++ b/runtime-rs/src/ffi.rs @@ -221,7 +221,7 @@ unsafe extern "C" { err_cap: u32, ) -> c_int; pub fn kvlangLayoutVet(src: *const c_char, err: *mut c_char, err_cap: u32) -> c_int; - pub fn kvlangLayoutDump( + pub fn kvlangLayoutPrintlib( lib: *const c_char, dsn: *const c_char, out: *mut c_char, @@ -229,6 +229,14 @@ unsafe extern "C" { err: *mut c_char, err_cap: u32, ) -> c_int; + pub fn kvlangLayoutPrintstack( + vid: *const c_char, + dsn: *const c_char, + out: *mut c_char, + out_cap: u32, + err: *mut c_char, + err_cap: u32, + ) -> c_int; } pub fn cs(s: &str) -> CString { diff --git a/runtime-rs/src/main.rs b/runtime-rs/src/main.rs index ac5d3e6a..9d0c7eb8 100644 --- a/runtime-rs/src/main.rs +++ b/runtime-rs/src/main.rs @@ -7,7 +7,7 @@ //! kvlang layout 仅 layout,打印 ENTRY= //! kvlang vet 仅校验(parse+lower),打印 ok 或错误 //! kvlang format 格式化输出到 stdout -//! kvlang dump [prefix] layout 该文件后,把 /lib(或 prefix)子树 dump 为可运行 kvlang + 槽位注释 +//! kvlang printlib [prefix] layout 该文件后,把 /lib(或 prefix)子树重建为可运行 kvlang + 槽位注释(不读 .src) //! kvlang(无参,设 KVLANG_LIB=p1:p2:…) layout 各路径下所有 .kv → run 各 lib 的 init(pkg 取自源码 `lib` 声明,同 stdlib) //! 驱动循环:executeVthread 主导执行,遇 rwir 停下;就地 rwir(print/json/http/…) //! 连续批处理 + nextPc;外部 rwir(如 numpy)handoff 给扩展进程;native/控制帧写回 pc。 @@ -100,9 +100,9 @@ fn main() { } print!("{}", cbuf(&out)); } - // ── layout 文件后 dump /lib(或 prefix)子树:审查 lower 产物、调坐标、验 round-trip ── - [cmd, file] if cmd == "dump" => dump_file(file, "/lib", &dsn), - [cmd, file, prefix] if cmd == "dump" => dump_file(file, prefix, &dsn), + // ── layout 文件后 printlib /lib(或 prefix)子树:审查 layout 结果、调坐标、验 round-trip ── + [cmd, file] if cmd == "printlib" => printlib_file(file, "/lib", &dsn), + [cmd, file, prefix] if cmd == "printlib" => printlib_file(file, prefix, &dsn), // ── stdlib 先 layout&run → 再 layout 内存源码 → 运行(散语句合成 init)── [cmd, src] if cmd == "-c" => { @@ -154,14 +154,14 @@ fn main() { } } -/// layout 进 kvspace 后 dump `prefix` 子树到 stdout(审查 lower 产物用)。 -fn dump_file(path: &str, prefix: &str, dsn: &str) { +// layout 进 kvspace 后 printlib `prefix` 子树到 stdout(审查 layout 结果用,不读 .src)。 +fn printlib_file(path: &str, prefix: &str, dsn: &str) { layout_file_or_die(path, dsn); let src = read_file(path); let mut out = vec![0u8; src.len() * 16 + 65536]; let mut err = [0u8; 4096]; let rc = unsafe { - kvlangLayoutDump( + kvlangLayoutPrintlib( cs(prefix).as_ptr(), cs(dsn).as_ptr(), out.as_mut_ptr() as *mut c_char, @@ -171,7 +171,7 @@ fn dump_file(path: &str, prefix: &str, dsn: &str) { ) }; if rc != 0 { - kvlang_rs::elog!("dump 失败: {}", cbuf(&err)); + kvlang_rs::elog!("printlib 失败: {}", cbuf(&err)); std::process::exit(1); } print!("{}", cbuf(&out)); diff --git a/runtime-rs/src/rwir/kvlanglayout.rs b/runtime-rs/src/rwir/kvlanglayout.rs index bf8bfd24..b350205a 100644 --- a/runtime-rs/src/rwir/kvlanglayout.rs +++ b/runtime-rs/src/rwir/kvlanglayout.rs @@ -2,8 +2,9 @@ //! kvlang·vet(src) -> "ok" | 错误信息 只校验(parse+lower),不写 kvspace //! kvlang·format(src) -> 规范化源码 | "error: …" 格式化(parse→规范化),不写 kvspace //! kvlang·layout(src) -> entry | "error: …" 把内存源码 layout 进 kvspace -//! kvlang·dump(lib) -> dump 文本 | "error: …" 把 /lib 子树重构为可运行 kvlang 源码 -//! 四者都在 C 边界 catch_unwind:坏代码返回 -1,绝不打崩宿主进程。 +//! kvlang·printlib(lib) -> 源码 | "error: …" 把 /lib 子树重建为可运行 kvlang(不读 .src) +//! kvlang·printstack(vid)-> 文本 | "error: …" 把 /vthread/ 活动栈渲染为文本 +//! 五者都在 C 边界 catch_unwind:坏代码返回 -1,绝不打崩宿主进程。 use std::ffi::c_char; @@ -68,11 +69,11 @@ pub fn layout(eng: &Engine, code: &str) -> String { } } -pub fn dump(eng: &Engine, lib: &str) -> String { +pub fn printlib(eng: &Engine, lib: &str) -> String { let mut out = vec![0u8; 65536]; let mut err = buf(); let rc = unsafe { - kvlangLayoutDump( + kvlangLayoutPrintlib( cs(lib).as_ptr(), cs(&eng.dsn).as_ptr(), out.as_mut_ptr() as *mut c_char, @@ -87,3 +88,39 @@ pub fn dump(eng: &Engine, lib: &str) -> String { format!("error: {}", cbuf(&err)) } } + +/// printstack(vid):把 /vthread/ 的活动栈渲染成文本。 +/// vid 空串 = 当前 vthread —— 从本 rwir 自己的 PC 前缀 `/vthread//…` 取。 +pub fn printstack(eng: &Engine, pc: &str, vid: &str) -> String { + let vid = if vid.is_empty() { + current_vid(pc) + } else { + vid.to_string() + }; + if vid.is_empty() { + return "error: printstack 无法确定 vthread(传 vid 或从 vthread 内调用)".to_string(); + } + let mut out = vec![0u8; 65536]; + let mut err = buf(); + let rc = unsafe { + kvlangLayoutPrintstack( + cs(&vid).as_ptr(), + cs(&eng.dsn).as_ptr(), + out.as_mut_ptr() as *mut c_char, + out.len() as u32, + err.as_mut_ptr() as *mut c_char, + err.len() as u32, + ) + }; + if rc == 0 { + cbuf(&out) + } else { + format!("error: {}", cbuf(&err)) + } +} + +/// 从 PC 路径取 vid:`/vthread//…` → ``(不是 vthread 内则空串)。 +fn current_vid(pc: &str) -> String { + let rest = pc.strip_prefix("/vthread/").unwrap_or(""); + rest.split('/').next().unwrap_or("").to_string() +} diff --git a/runtime-rs/src/rwir/mod.rs b/runtime-rs/src/rwir/mod.rs index 01ec5cda..7b87d3e0 100644 --- a/runtime-rs/src/rwir/mod.rs +++ b/runtime-rs/src/rwir/mod.rs @@ -111,7 +111,14 @@ pub const MYRWIRCAPS: &[(&str, Rwir)] = &[ }, ), ( - "kvlang·dump", + "kvlang·printlib", + Rwir { + rp: &["[]char/utf32"], + wp: &["[]char/utf32"], + }, + ), + ( + "kvlang·printstack", Rwir { rp: &["[]char/utf32"], wp: &["[]char/utf32"], @@ -241,7 +248,8 @@ pub fn is_inproc(op: &str) -> bool { | "kvlang·vet" | "kvlang·format" | "kvlang·layout" - | "kvlang·dump" + | "kvlang·printlib" + | "kvlang·printstack" | "networld/proc·exec" | "networld/fs·size" | "networld/fs·read" @@ -293,8 +301,12 @@ pub fn dispatch(eng: &Engine, op: &str, pc: &str) { let out = kvlanglayout::layout(eng, &eng.read0(pc)); eng.set_kv(&eng.write0(pc), &out); } - "kvlang·dump" => { - let out = kvlanglayout::dump(eng, &eng.read0(pc)); + "kvlang·printlib" => { + let out = kvlanglayout::printlib(eng, &eng.read0(pc)); + eng.set_kv(&eng.write0(pc), &out); + } + "kvlang·printstack" => { + let out = kvlanglayout::printstack(eng, pc, &eng.read0(pc)); eng.set_kv(&eng.write0(pc), &out); } other => crate::elog!("未知 rwir: {other} @ {pc}"), diff --git a/stdlib/kvlang/kvlangbrief.kv b/stdlib/kvlang/kvlangbrief.kv index ae86c15f..4a91490d 100644 --- a/stdlib/kvlang/kvlangbrief.kv +++ b/stdlib/kvlang/kvlangbrief.kv @@ -195,7 +195,8 @@ rwfunc main() -> () { - `kvlang·vet(src) -> "ok"/err`:只校验,不写库。 - `kvlang·format(src) -> 规范化源码`。 - `kvlang·layout(src) -> entry`:源码 layout 入库,返回入口。 -- `kvlang·dump("/lib") -> 源码`:/lib 子树导出为可运行源码。 +- `kvlang·printlib("/lib") -> 源码`:把 /lib 子树重建为可运行源码(**只看 layout 结果,不读 `.src`**)。 +- `kvlang·printstack(vid) -> 文本`:把 /vthread 活动栈渲染成文本(帧链 + 实参 + 当前指令;vid 空 = 当前 vthread)。 ### 外部进程/文件系统(networld) - `networld/proc·exec(args, envs) -> code[, out, err]`:args/envs 是 `{...}` 数组字面量,args 首元素=可执行文件;out/err 是 `@[]uint8` 捕获句柄(绑定即捕获)。 diff --git "a/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/08-layout-abi.kv" "b/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/08-layout-abi.kv" index e14342da..a79ab09f 100644 --- "a/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/08-layout-abi.kv" +++ "b/stdlib/kvlang/spec/04-layout\350\257\255\344\271\211/08-layout-abi.kv" @@ -13,7 +13,8 @@ layout 能力经 cdylib 导出,第三方无需 fork 子进程即可布局( | `kvlangLayoutCode(src, dsn, …)` | 内存源码串 layout 进 kvspace | | `kvlangLayoutVet(src, …)` | 只校验(parse + lower),不写 kvspace | | `kvlangLayoutFormat(src, …)` | 格式化(parse → 规范化源码),不写 kvspace | -| `kvlangLayoutDump(lib, dsn, …)` | 把 `/lib` 子树逆向重建为可运行 kvlang + 槽位注释 | +| `kvlangLayoutPrintlib(lib, dsn, …)` | 把 `/lib` 子树重建为可运行 kvlang + 槽位注释(只看 layout 结果,不读 `.src`) | +| `kvlangLayoutPrintstack(vid, dsn, …)` | 把 `/vthread/` 活动栈渲染为文本(帧链 + 实参 + 当前指令) | | `kvlangKindexprParse(langtype, out)` | 解析一条 langtype(kindexpr)串为 `(kind, ndim, dims, 元素宽)`;与 runtime 侧 `kvlangLangtypeParse` 同一文法、同一语义(两个 ABI 名待统一) | 成功返回 0(`entry_out` 为 init 入口名列表,`\n` 分隔),失败返回 -1(`err_out` 为错误)。 diff --git a/tutorial/13-stdlib/kvlang/01-reflect.kv b/tutorial/13-stdlib/kvlang/01-reflect.kv index 1fa26d80..477863ee 100644 --- a/tutorial/13-stdlib/kvlang/01-reflect.kv +++ b/tutorial/13-stdlib/kvlang/01-reflect.kv @@ -5,7 +5,7 @@ // 与 init 入口都保留 —— fnlist 是客观自省,是否过滤由调用方决定 // 期望输出: // math= E, Pi, Tau -// kvlang= dump, format, layout, vet +// kvlang= format, layout, printlib, printstack, vet // has-allinset= true // has-param= false // string-nonempty= true diff --git a/tutorial/13-stdlib/kvlang/02-printlib.kv b/tutorial/13-stdlib/kvlang/02-printlib.kv new file mode 100644 index 00000000..7c9d8ec3 --- /dev/null +++ b/tutorial/13-stdlib/kvlang/02-printlib.kv @@ -0,0 +1,23 @@ +// kvlang·printlib:把 /lib 子树重建为可运行 kvlang —— **只看 layout 结果,不读 `.src`** +// 验法:dump 一次 → 删掉该函数的源码副本 `.src` → 再 dump,两次输出必须一致 +// 期望输出: +// before-has-fn= true +// after-has-fn= true +// stable= 0 +lib pl_probe { + rwfunc add(a:int64, b:int64) -> (c:int64) { + a + b -> c + } +} + +rwfunc test() -> () { + kvlang·printlib("/lib/pl_probe") -> before + string·contains(before, "rwfunc add") -> h1 + println("before-has-fn=", h1) + kvspace·del("/lib/pl_probe·add.src") -> _ + kvlang·printlib("/lib/pl_probe") -> after + string·contains(after, "rwfunc add") -> h2 + println("after-has-fn=", h2) + string·cmp(before, after) -> same + println("stable=", same) +} diff --git a/tutorial/15-vthread/05-printstack.kv b/tutorial/15-vthread/05-printstack.kv new file mode 100644 index 00000000..7d384316 --- /dev/null +++ b/tutorial/15-vthread/05-printstack.kv @@ -0,0 +1,29 @@ +// kvlang·printstack(vid):把 /vthread 的活动栈渲染成文本(帧链 + 实参 + 当前指令) +// 只读:调用后 ‥pc/‥status 不变;vid 传空串 = 当前 vthread +// 期望输出: +// status= true +// frames= true +// outer-lib= true +// inner-lib= true +// arg= true +// cur= true +rwfunc ps_inner(n:int64) -> (r:int64) { + no_such·thing(n) -> x +} + +rwfunc ps_outer() -> () { + ps_inner(3) -> out +} + +rwfunc test() -> () { + vthread·create("ps_outer") -> vid + char/utf32(vid) -> vid + vthread·run(vid) + kvlang·printstack(vid) -> st + println("status=", string·contains(st, "status=error")) + println("frames=", string·contains(st, "frames=2")) + println("outer-lib=", string·contains(st, "/lib/ps_outer")) + println("inner-lib=", string·contains(st, "/lib/ps_inner")) + println("arg=", string·contains(st, "int64:3")) + println("cur=", string·contains(st, "cur = rwir|rwfunc:no_such·thing")) +}