diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..c0d7466d --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,3 @@ +[build] +rustflags = ["-L", "/home/peng.li24/github.com/array2d/kvspace-c/build"] + diff --git a/CLAUDE.md b/.claude/CLAUDE.md similarity index 100% rename from CLAUDE.md rename to .claude/CLAUDE.md diff --git a/.claude/runtime-rust.md b/.claude/runtime-rust.md new file mode 100644 index 00000000..1bed993b --- /dev/null +++ b/.claude/runtime-rust.md @@ -0,0 +1,116 @@ +# kvlang Rust Runtime 开发原则 + +## 零、总体原则 + +**kvlang Rust runtime 是 Go kvlang runtime 的像素级 C ABI 移植。** + +Go 负责 toolchain(parse → lower → layout → 写入 kvspace),Rust/C++ 负责 runtime(从 kvspace 读取指令 → kvcpu 执行)。 + +## 一、零拷贝原则 + +XValue 的 kind 解析后,body 字节直接 reinterpret_cast,不做任何拷贝或中间类型转换。 + +``` +SHM sbo_data 指针 → read_tlv() 返回直接指针(无 malloc) + ↓ +Rust: *const u8 → *(ptr as *const i64).read_unaligned() → i64 +C++: const uint8_t* → *reinterpret_cast(ptr) +``` + +- 禁止 `to_vec()`, `memcpy`, `copy_from_slice`, `malloc` 在 hot path +- 禁止创建中间 Vec/u8 对象装载 body 字节 +- `display()` 做格式化输出时从 raw pointer 直接读,不拷 +- 只有 kind 字符串(<16 bytes)允许拷贝 + +## 二、先读 Go 代码原则 + +实现任何 runtime 功能前,必须先读 Go 对应文件理解逻辑: + +| 功能 | Go 文件 | +|------|--------| +| 执行循环 | `kvcpu/execute.go` | +| 控制流 | `kvcpu/controlflow.go` | +| 算术 | `rwir/builtin/arith.go` | +| 比较 | `rwir/builtin/cmp.go` | +| IO | `rwir/builtin/io.go` | +| 时间 | `rwir/builtin/time.go` | +| 分发 | `rwir/builtin/ops.go` | +| 虚线程 | `vthread/vthread.go` | +| 路径生成 | `keytree/const.go` + `keytree/vthread.go` | + +禁止不读 Go 代码直接凭想象写 Rust 实现。 + +## 三、禁止 hardcode 原则 + +所有路径常量、kind 字符串、opcode 字符串必须引用模块常量,禁止裸字符串。 + +- 路径 → `keytree/const.rs`(对齐 `keytree/const.go`) +- kind → 引用 XValue kind 常量 +- opcode → `rwir/builtin/ops.rs` dispatch table 中定义 + +禁止在 execute.rs / controlflow.rs 等文件中出现 `"/lib/"`, `"/vthread/"`, `".pc"`, `"main"` 等裸字符串。 + +## 四、像素级对齐 Go 原则 + +Rust 项目的文件路径、文件名、函数名、模块名必须与 Go 源码一对一对应: + +``` +Go: kvlang/kvcpu/execute.go → Rust: kvlang/kvcpu/execute.rs +Go: kvlang/kvcpu/controlflow.go → Rust: kvlang/kvcpu/controlflow.rs +Go: kvlang/kvcpu/cpu.go → Rust: kvlang/kvcpu/cpu.rs +Go: kvlang/rwir/rwir.go → Rust: kvlang/rwir/rwir.rs +Go: kvlang/rwir/builtin/arith.go → Rust: kvlang/rwir/builtin/arith.rs +Go: kvlang/rwir/builtin/cmp.go → Rust: kvlang/rwir/builtin/cmp.rs +Go: kvlang/rwir/builtin/io.go → Rust: kvlang/rwir/builtin/io.rs +Go: kvlang/rwir/builtin/time.go → Rust: kvlang/rwir/builtin/time.rs +Go: kvlang/rwir/builtin/ops.go → Rust: kvlang/rwir/builtin/ops.rs +Go: kvlang/rwir/builtin/logic.go → Rust: kvlang/rwir/builtin/logic.rs +Go: kvlang/rwir/builtin/math.go → Rust: kvlang/rwir/builtin/math.rs +Go: kvlang/rwir/builtin/string.go → Rust: kvlang/rwir/builtin/string.rs +Go: kvlang/vthread/vthread.go → Rust: kvlang/vthread/vthread.rs +Go: kvlang/keytree/const.go → Rust: kvlang/keytree/const.rs +Go: kvlang/logx/logx.go → Rust: kvlang/logx/logx.rs +``` + +Go 中的公开函数名在 Rust 中保持一致。例如 Go 的 `handle_goto` → Rust 的 `handle_goto`,Go 的 `exec_add` → Rust 的 `exec_add`。 + +## 五、模块结构 + +```rust +// lib.rs +pub mod kvcpu; +pub mod rwir; +pub mod keytree; +pub mod logx; +pub mod vthread; + +// kvcpu/mod.rs +pub mod cpu; +pub mod execute; +pub mod controlflow; + +// rwir/mod.rs +pub mod rwir; +pub mod builtin; + +// rwir/builtin/mod.rs +pub mod ops; pub mod arith; pub mod cmp; pub mod io; +pub mod time; pub mod logic; pub mod math; pub mod bit; +pub mod cast; pub mod string; pub mod kvop; +``` + +## 六、编译与测试 + +```bash +# C 构建 +make -C kvspace-c/build -j4 + +# Rust 构建 +cargo build --manifest-path kvlang/Cargo.toml + +# 单文件测试 +KVSPACE_SHM=/tmp/t kvlang-rust main + +# 全量测试 +python3 kvlang/tutorial/test.py --runtime=rust +``` diff --git a/.gitignore b/.gitignore index 1f2dc128..6e26fcc6 100644 --- a/.gitignore +++ b/.gitignore @@ -49,7 +49,8 @@ appendonly.aof # ── Project-specific ───────────────────────────────── /tmp/ /post/ -kvlang +/kvlang +/json # Tool config .omc/ diff --git a/Cargo.lock b/Cargo.lock index 9bc3d48a..9ed0b4df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,12 @@ version = 4 [[package]] name = "kvlang" version = "0.1.0" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" diff --git a/Cargo.toml b/Cargo.toml index 0edfcc7c..a1b9cd8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "kvlang" version = "0.1.0" -edition = "2024" +edition = "2021" description = "kvlang VM — Rust runtime (kvcpu executor)" license = "MIT" @@ -13,5 +13,4 @@ name = "kvlang" path = "cmd/kvlang/main.rs" [dependencies] -# kvspace-rdma client (future) -# serde = { version = "1", features = ["derive"] } +libc = "0.2" diff --git a/Makefile b/Makefile index f374e7a9..e583a709 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test vet clean kvspace install +.PHONY: build test vet clean kvspace install rust rust-test export GOPROXY ?= https://goproxy.cn,direct PREFIX ?= ~/.local @@ -9,9 +9,16 @@ build: install -d $(PREFIX)/bin install kvlang $(PREFIX)/bin/kvlang +rust: + cargo build --manifest-path $(CURDIR)/Cargo.toml + +rust-test: + python3 tutorial/test.py --runtime=rust + vet: go vet ./... clean: go clean + cargo clean --manifest-path $(CURDIR)/Cargo.toml rm -f kvlang diff --git a/ast/ast.go b/ast/ast.go index f9562116..7ec674c5 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -81,13 +81,6 @@ func (s FuncSig) NumReads() int32 { return int32(len(s.Params)) } func (s FuncSig) NumWrites() int32 { return int32(len(s.Returns)) } // ReturnNames 返回输出参数名列表。 -func (s FuncSig) ReturnNames() []string { - names := make([]string, len(s.Returns)) - for i, p := range s.Returns { - names[i] = p.Name - } - return names -} // ── 函数 ────────────────────────────────────────────────────── diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..c53ffd89 --- /dev/null +++ b/build.rs @@ -0,0 +1 @@ +fn main() { println!("cargo:rustc-link-search=native=/home/peng.li24/github.com/array2d/kvspace-c/build"); println!("cargo:rustc-link-lib=kvspace-c"); } diff --git a/cmd/kvlang/layout.go b/cmd/kvlang/layout.go index 1ab92cdb..587595e1 100644 --- a/cmd/kvlang/layout.go +++ b/cmd/kvlang/layout.go @@ -61,6 +61,9 @@ func cmdLayout(args []string) { layout.WriteFunc(kv, fpkg, lower.Func(&df.Funcs[i])) anyCode = true } + for i := range df.RwirDecls { + layout.WriteRwirDecl(kv, &df.RwirDecls[i]) + } body := df.InitBody for _, c := range df.TopLevelCalls { body = append(body, c) } if len(body) > 0 { @@ -121,16 +124,11 @@ func _loadFile(kv kvspace.KVSpace, f string, anyCode *bool, loaded map[string]bo layout.WriteFunc(kv, fpkg, lower.Func(&df.Funcs[i])) *anyCode = true } + for i := range df.RwirDecls { + layout.WriteRwirDecl(kv, &df.RwirDecls[i]) + } for _, st := range df.InitBody { *initBody = append(*initBody, st) } for _, c := range df.TopLevelCalls { *initBody = append(*initBody, c) } if len(df.InitBody) > 0 || len(df.TopLevelCalls) > 0 { *anyCode = true } } -func makeInitFunc(calls []*ast.Instruction) *ast.Func { - body := make([]ast.Stmt, len(calls)) - for i, inst := range calls { - body[i] = inst - } - initFn := ast.Func{Sig: ast.FuncSig{Name: "init"}, Body: body} - return lower.Func(&initFn) -} diff --git a/cmd/kvlang/layoutandrun.go b/cmd/kvlang/layoutandrun.go index ce55be12..a30046b3 100644 --- a/cmd/kvlang/layoutandrun.go +++ b/cmd/kvlang/layoutandrun.go @@ -49,7 +49,7 @@ func findEntryPrefix(kv kvspace.KVSpace, prefix string) string { func runFiles(dsn string, paths []string, debug bool) { kv := kvspace.Conn(dsn) defer kv.DisConn() - registerDefaultTerm(kv) + initDirs(kv) var files []string for _, p := range paths { @@ -59,6 +59,7 @@ func runFiles(dsn string, paths []string, debug bool) { } if len(files) == 0 { logx.Fatal("no .kv files found") } + layoutAndRunStdlib(kv) if !loadFunctions(kv, files) { return } executeEntry(kv, findEntry(dsn), debug) } @@ -66,7 +67,8 @@ func runFiles(dsn string, paths []string, debug bool) { func runCode(name string, rc io.Reader, dsn string, debug bool) { kv := kvspace.Conn(dsn) defer kv.DisConn() - registerDefaultTerm(kv) + initDirs(kv) + layoutAndRunStdlib(kv) df, diags, err := parser.ParseCode(rc) if err != nil { logx.Fatal("parse: %v", err) } @@ -78,6 +80,9 @@ func runCode(name string, rc io.Reader, dsn string, debug bool) { if fpkg == "" { fpkg = df.Package } layout.WriteFunc(kv, fpkg, lower.Func(&df.Funcs[i])) } + for i := range df.RwirDecls { + layout.WriteRwirDecl(kv, &df.RwirDecls[i]) + } body := df.InitBody for _, c := range df.TopLevelCalls { body = append(body, c) } if len(body) > 0 { diff --git a/cmd/kvlang/main.go b/cmd/kvlang/main.go index 12b1e688..86d47e4c 100644 --- a/cmd/kvlang/main.go +++ b/cmd/kvlang/main.go @@ -17,7 +17,7 @@ import ( // 注册 KVSpace 实现;--kvspace DSN 的 scheme 选择后端(默认 shm://)。 _ "github.com/array2d/kvspace-go/redis" _ "github.com/array2d/kvspace-go/goheap" - _ "github.com/array2d/kvspace-go/shm" + // _ "github.com/array2d/kvspace-go/shm" // requires libkvspace-c.so ) func main() { diff --git a/cmd/kvlang/main.rs b/cmd/kvlang/main.rs new file mode 100644 index 00000000..a590f3ed --- /dev/null +++ b/cmd/kvlang/main.rs @@ -0,0 +1,15 @@ +use std::env; +use kvlang::kvcpu::cpu::KVCpu; +use kvlang::kvcpu::execute; + +fn main() { + let func = env::args().nth(1).unwrap_or_else(|| "main".into()); + let shm = env::var("KVSPACE_SHM").expect("KVSPACE_SHM not set"); + let kv_ptr = KVCpu::open(&shm).expect("open shm"); + let cpu = KVCpu::new(kv_ptr, "rust"); + if let Err(e) = execute::execute(&cpu, &func) { + kvlang::logx::logx::error(format_args!("{}", e)); + std::process::exit(1); + } + KVCpu::close(kv_ptr); +} diff --git a/cmd/kvlang/run.go b/cmd/kvlang/run.go index c706c96f..cb81de89 100644 --- a/cmd/kvlang/run.go +++ b/cmd/kvlang/run.go @@ -5,9 +5,11 @@ import ( "flag" "fmt" "os" + "path/filepath" "strings" "time" + "kvlang/rwirext/term" "kvlang/keytree" "kvlang/kvcpu" "github.com/array2d/kvspace-go" @@ -18,11 +20,14 @@ import ( ) // cmdRun 解析参数并路由:内联 / {lib}.{func} / 文件 / 管道。 +var noterm bool + func cmdRun(args []string) { fs := flag.NewFlagSet("run", flag.ExitOnError) dsn := fs.String("kvspace", defaultKVSpace(), kvspaceFlagDesc) code := fs.String("c", "", "内联代码(直接执行字符串)") debug := fs.Bool("debug", false, "单步调试模式(交互式,每条指令暂停)") + fs.BoolVar(¬erm, "noterm", false, "禁用内置终端 daemon(print/input 不再可用)") fs.Usage = func() { fmt.Fprintln(os.Stderr, "usage: kvlang run [--debug] [-c code | {lib}.{func} | ]") fs.PrintDefaults() @@ -56,7 +61,8 @@ func runLib(lib, fn string, debug bool) { if lib == "" { name = fn } kv := kvspace.Conn(defaultKVSpace()) defer kv.DisConn() - registerDefaultTerm(kv) + initDirs(kv) + layoutAndRunStdlib(kv) executeEntry(kv, name, debug) } @@ -66,28 +72,31 @@ func executeEntry(kv kvspace.KVSpace, entryName string, debug bool) { vtid := vthread.AllocVtid(kv) kv.DelTree(keytree.VThread(vtid)) kvspace.MkIndexRecursive(kv, keytree.VThread(vtid)+"/") - builtin.WriteSysRwir(kv) + builtin.WriteRwir(kv, filepath.Base(os.Args[0])) + if !noterm { + term.Register(kv) + go term.Serve(kv) + } firstPC := layout.Bootstrap(ctx, kv, vtid, entryName, nil) if firstPC == "" { logx.Fatal("[single] Bootstrap %s failed", entryName) } vthread.Set(ctx, kv, vtid, firstPC, "init") kv.Set([]kvspace.KVPair{ - {keytree.VThreadCtime(vtid), kvspace.NewTime(time.Now().UnixNano())}, - {keytree.VThreadTerm(vtid), kvspace.NewChar("kvlangrun")}, + {Key: keytree.VThreadCtime(vtid), Val: kvspace.NewTime(time.Now().UnixNano())}, }) if debug { - kv.Set([]kvspace.KVPair{{keytree.VThreadDebugger(vtid), kvspace.NewChar("break")}}) + kv.Set([]kvspace.KVPair{{Key: keytree.VThreadDebugger(vtid), Val: kvspace.NewCharByte([]byte("break")...)}}) logx.Info("[single] debug mode: executing %s", firstPC) - cpu := kvcpu.New(kv, "single") + cpu := kvcpu.New(kv) cpu.Execute(firstPC) logx.Info("[dbg] execution finished") return } logx.Info("[single] executing %s", firstPC) - cpu := kvcpu.New(kv, "single") + cpu := kvcpu.New(kv) cpu.Execute(firstPC) reportRunError(kv, vtid) } @@ -96,7 +105,7 @@ func reportRunError(kv kvspace.KVSpace, vtid string) { msgVal := kvspace.GetOne(kv, keytree.VThreadStatusMsg(vtid, "error")) if !kvspace.IsNone(msgVal) { pcVal := kvspace.GetOne(kv, keytree.VThreadPC(vtid)) - logx.Error("%s at %s", msgVal.String(), pcVal.String()) + logx.Error("%s at %s", msgVal.ValueString(), pcVal.ValueString()) os.Exit(1) } } diff --git a/cmd/kvlang/stdlib.go b/cmd/kvlang/stdlib.go new file mode 100644 index 00000000..9d3c1397 --- /dev/null +++ b/cmd/kvlang/stdlib.go @@ -0,0 +1,79 @@ +package main + +import ( + "context" + "strings" + + "kvlang/keytree" + "kvlang/kvcpu" + "github.com/array2d/kvspace-go" + "kvlang/layout" + "kvlang/logx" + "kvlang/lower" + "kvlang/parser" + "kvlang/stdlib" + "kvlang/vthread" +) + +// layoutAndRunStdlib 在 runtime 启动时 layout 内置 lib 源码到 /lib/,并 run 各 lib 的 init。 +// 每个 init 在独立 vthread 执行,完成后回收 /vthread//,vtid 永远递增。 +func layoutAndRunStdlib(kv kvspace.KVSpace) { + entries, err := stdlib.FS.ReadDir(".") + if err != nil { + return + } + var inits []string + for _, e := range entries { + if e.IsDir() || !strings.HasSuffix(e.Name(), ".kv") { + continue + } + src, err := stdlib.FS.ReadFile(e.Name()) + if err != nil { + continue + } + df, diags, err := parser.ParseCode(strings.NewReader(string(src))) + if err != nil { + logx.Fatal("stdlib parse %s: %v", e.Name(), err) + } + for _, d := range diags { + d.SrcName = "stdlib/" + e.Name() + logx.Diag(d) + } + if parser.HasErrors(diags) { + logx.Fatal("stdlib parse %s: error-level diagnostics", e.Name()) + } + for i := range df.Funcs { + fpkg := df.Funcs[i].Pkg + if fpkg == "" { + fpkg = df.Package + } + layout.WriteFunc(kv, fpkg, lower.Func(&df.Funcs[i])) + if df.Funcs[i].Sig.Name == "init" && fpkg != "" { + inits = append(inits, fpkg+keytree.MemberSep+"init") + } + } + } + for _, fn := range inits { + runStdlibInit(kv, fn) + } +} + +// runStdlibInit 在独立 vthread 执行 init,完成后回收 /vthread//(vtid 不回收)。 +func runStdlibInit(kv kvspace.KVSpace, funcName string) { + ctx := context.Background() + vtid := vthread.AllocVtid(kv) + kvspace.MkIndexRecursive(kv, keytree.VThread(vtid)+"/") + firstPC := layout.Bootstrap(ctx, kv, vtid, funcName, nil) + if firstPC == "" { + kv.DelTree(keytree.VThread(vtid)) + return + } + vthread.Set(ctx, kv, vtid, firstPC, "init") + cpu := kvcpu.New(kv) + cpu.Execute(firstPC) + kv.DelTree(keytree.VThread(vtid)) + // 删除已执行的 init 函数树,避免 findEntry 把 stdlib 的 lib init 误当用户入口 + if dot := strings.LastIndex(funcName, keytree.MemberSep); dot > 0 { + kv.DelTree(keytree.LibFunc(funcName[:dot], funcName[dot+len(keytree.MemberSep):])) + } +} diff --git a/cmd/kvlang/term.go b/cmd/kvlang/term.go deleted file mode 100644 index 217aeb96..00000000 --- a/cmd/kvlang/term.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "kvlang/keytree" - "github.com/array2d/kvspace-go" -) - -func initDirs(kv kvspace.KVSpace) { - kvspace.MkIndexRecursive(kv, "/lib/") - kvspace.MkIndexRecursive(kv, "/vthread/") -} - -func registerDefaultTerm(kv kvspace.KVSpace) { - initDirs(kv) - h := keytree.DevTTY("kvlangrun", "") - kvspace.MkIndexRecursive(kv, h+"stdout/") - kvspace.MkIndexRecursive(kv, h+"stderr/") - kvspace.MkIndexRecursive(kv, h+"stdin/") - kv.Set([]kvspace.KVPair{ - {h + "stdout/type", kvspace.NewChar("file")}, - {h + "stdout/detail", kvspace.NewChar("/dev/stdout")}, - {h + "stderr/type", kvspace.NewChar("file")}, - {h + "stderr/detail", kvspace.NewChar("/dev/stderr")}, - {h + "stdin/type", kvspace.NewChar("file")}, - {h + "stdin/detail", kvspace.NewChar("/dev/stdin")}, - }) -} diff --git a/cmd/kvlang/util.go b/cmd/kvlang/util.go index 6ee792f8..bfb24127 100644 --- a/cmd/kvlang/util.go +++ b/cmd/kvlang/util.go @@ -2,8 +2,16 @@ package main import ( "os" + + "github.com/array2d/kvspace-go" ) +// initDirs 创建基础目录 /lib/ 与 /vthread/(layout/run 前必须存在)。 +func initDirs(kv kvspace.KVSpace) { + kvspace.MkIndexRecursive(kv, "/lib/") + kvspace.MkIndexRecursive(kv, "/vthread/") +} + // defaultKVSpace 返回 kvspace DSN 默认值:KVLANG_KVSPACE 环境变量覆盖,否则本机 redis。 func defaultKVSpace() string { if v := os.Getenv("KVLANG_KVSPACE"); v != "" { diff --git a/deepx-design b/deepx-design index 64baff1d..f520f377 160000 --- a/deepx-design +++ b/deepx-design @@ -1 +1 @@ -Subproject commit 64baff1d12ed7af3227fb94da726ded5b67853a6 +Subproject commit f520f377aeef0b95ad16880b438d03d15dbdbb9e diff --git a/device/device.h b/device/device.h deleted file mode 100644 index 2e8fa3a2..00000000 --- a/device/device.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include -#include -#include - -// I/O device abstraction — identical API to device/*.go and device/*.rs. - -namespace kvlang::device { - -// File I/O -std::string read_file(std::string_view path); -void write_file(std::string_view path, std::string_view text); - -// Terminal I/O (stdin/stdout) -std::optional read_term(std::string_view prompt); -void write_term(std::string_view text); - -// WebSocket -class WSConn { -public: - virtual ~WSConn() = default; - virtual void write(std::string_view msg) = 0; - virtual std::string read() = 0; -}; - -} // namespace kvlang::device diff --git a/device/device_stub.cppx b/device/device_stub.cppx deleted file mode 100644 index 155c9111..00000000 --- a/device/device_stub.cppx +++ /dev/null @@ -1 +0,0 @@ -#include "device.h" diff --git a/device/file.rs b/device/file.rs deleted file mode 100644 index a20ddb8f..00000000 --- a/device/file.rs +++ /dev/null @@ -1 +0,0 @@ -// device::file diff --git a/device/mod.rs b/device/mod.rs deleted file mode 100644 index ed32badd..00000000 --- a/device/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod file; -pub mod term; -pub mod term_ws; -pub mod ws; diff --git a/device/term.go b/device/term.go deleted file mode 100644 index b736166a..00000000 --- a/device/term.go +++ /dev/null @@ -1,10 +0,0 @@ -package device - -// TermStream 表示一个已解析的终端流配置。 -type TermStream struct { - Type string // "websocket" | "file" | "" - Detail string // ws://url 或文件路径 -} - -// IsZero 终端未配置时返回 true。 -func (s TermStream) IsZero() bool { return s.Type == "" } diff --git a/device/term.rs b/device/term.rs deleted file mode 100644 index bc9cac52..00000000 --- a/device/term.rs +++ /dev/null @@ -1 +0,0 @@ -// device::term diff --git a/device/term_ws.go b/device/term_ws.go deleted file mode 100644 index 4763c02a..00000000 --- a/device/term_ws.go +++ /dev/null @@ -1,67 +0,0 @@ -// Package device 提供终端 I/O 传输层 终端 I/O 传输层(WebSocket + 文件)。 -// -// 终端发现流程: -// /vthread//term → 终端名称 $name(默认空字符串,空则无终端) -// /sys/term/${name}/stdout → HASH {type, detail} -// /sys/term/${name}/stderr → HASH {type, detail} -// /sys/term/${name}/stdin → HASH {type, detail} -// -// type 取值: "websocket" | "file" -// detail: ws://url 或文件路径 -// -// 不做任何序列化,直接传原始字节流。 -package device - -import ( - "context" - - "github.com/array2d/kvspace-go" - "kvlang/keytree" -) - -// ResolveTerm 通过 /vthread//term → /sys/term/${name}/${stream} 解析终端流配置。 -func ResolveTerm(ctx context.Context, kv kvspace.KVSpace, vtid, stream string) TermStream { - nameVal := kvspace.GetOne(kv, keytree.VThreadTerm(vtid)) - name := nameVal.String() - if name == "" { return TermStream{} } - base := keytree.DevTTY(name, stream) - tVal := kvspace.GetOne(kv, base+"/type") - dVal := kvspace.GetOne(kv, base+"/detail") - return TermStream{Type: tVal.String(), Detail: dVal.String()} -} - -// WriteTerm 根据 TermStream 类型将文本写入终端(追加换行)。 -func WriteTerm(ctx context.Context, s TermStream, text string) error { - switch s.Type { - case "websocket": - return writeWS(ctx, s.Detail, text) - case "file": - return writeFile(s.Detail, text) - default: - return nil // 无终端,静默丢弃 - } -} - -// WriteTermRaw 同 WriteTerm 但不追加换行。 -func WriteTermRaw(ctx context.Context, s TermStream, text string) error { - switch s.Type { - case "websocket": - return writeWS(ctx, s.Detail, text) - case "file": - return writeFileRaw(s.Detail, text) - default: - return nil - } -} - -// ReadTerm 根据 TermStream 类型从终端读取一行文本。 -func ReadTerm(ctx context.Context, s TermStream) (string, error) { - switch s.Type { - case "websocket": - return readWS(ctx, s.Detail) - case "file": - return readFile(s.Detail) - default: - return "", nil // 无终端,返回空 - } -} diff --git a/device/term_ws.rs b/device/term_ws.rs deleted file mode 100644 index 831c2d32..00000000 --- a/device/term_ws.rs +++ /dev/null @@ -1 +0,0 @@ -// device::term_ws diff --git a/device/ws.go b/device/ws.go deleted file mode 100644 index 254c5e68..00000000 --- a/device/ws.go +++ /dev/null @@ -1,74 +0,0 @@ -package device - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/gorilla/websocket" -) - -type wsConn struct { - conn *websocket.Conn - mu sync.Mutex - wsURL string -} - -var ( - conns = map[string]*wsConn{} - connsMu sync.Mutex -) - -// getConn 获取或创建到 wsURL 的 WebSocket 连接(按 URL 缓存复用)。 -func getConn(ctx context.Context, wsURL string) (*wsConn, error) { - connsMu.Lock() - defer connsMu.Unlock() - - if c, ok := conns[wsURL]; ok { - if err := c.conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(3*time.Second)); err == nil { - return c, nil - } - c.conn.Close() - delete(conns, wsURL) - } - - conn, _, err := websocket.DefaultDialer.DialContext(ctx, wsURL, nil) - if err != nil { - return nil, fmt.Errorf("dial %s: %w", wsURL, err) - } - - c := &wsConn{conn: conn, wsURL: wsURL} - conns[wsURL] = c - return c, nil -} - -// writeWS 发送原始文本到 WebSocket。 -func writeWS(ctx context.Context, wsURL, text string) error { - c, err := getConn(ctx, wsURL) - if err != nil { - return err - } - c.mu.Lock() - defer c.mu.Unlock() - return c.conn.WriteMessage(websocket.TextMessage, []byte(text)) -} - -// readWS 从 WebSocket 读取一行原始文本(阻塞,超时 30s)。 -func readWS(ctx context.Context, wsURL string) (string, error) { - c, err := getConn(ctx, wsURL) - if err != nil { - return "", err - } - c.mu.Lock() - defer c.mu.Unlock() - - c.conn.SetReadDeadline(time.Now().Add(30 * time.Second)) - defer c.conn.SetReadDeadline(time.Time{}) - - _, data, err := c.conn.ReadMessage() - if err != nil { - return "", fmt.Errorf("read: %w", err) - } - return string(data), nil -} diff --git a/device/ws.rs b/device/ws.rs deleted file mode 100644 index 951035c2..00000000 --- a/device/ws.rs +++ /dev/null @@ -1 +0,0 @@ -// device::ws diff --git a/extensions/kvlang/syntaxes/kvlang.tmLanguage.json b/extensions/kvlang/syntaxes/kvlang.tmLanguage.json index f0bd1d64..8fc44b3c 100644 --- a/extensions/kvlang/syntaxes/kvlang.tmLanguage.json +++ b/extensions/kvlang/syntaxes/kvlang.tmLanguage.json @@ -38,7 +38,7 @@ }, "type": { "name": "storage.type.kvlang", - "match": "\\b(int8|int16|int32|int64|uint8|uint16|uint32|uint64|float32|float64|bool|string)\\b" + "match": "\\b(int8|int16|int32|int64|uint8|uint16|uint32|uint64|float32|float64|bool|charbyte)\\b" }, "number": { "name": "constant.numeric.kvlang", diff --git a/go.mod b/go.mod index 50473d18..ed50c20b 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,7 @@ module kvlang go 1.24.4 -require ( - github.com/array2d/kvspace-go v0.1.0 - github.com/gorilla/websocket v1.5.3 -) +require github.com/array2d/kvspace-go v0.1.0 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect @@ -13,3 +10,4 @@ require ( github.com/redis/go-redis/v9 v9.7.0 // indirect ) +replace github.com/array2d/kvspace-go v0.1.0 => ../kvspace-go diff --git a/go.sum b/go.sum index 7c3976f1..f11d99f0 100644 --- a/go.sum +++ b/go.sum @@ -6,7 +6,5 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= diff --git a/hardcode_check.py b/hardcode_check.py new file mode 100644 index 00000000..d4eac4b0 --- /dev/null +++ b/hardcode_check.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +"""检查 symbol.Table 之外的算子 hardcode。语法 token(arrow、成员访问)除外。""" +import argparse, os, re, sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +SYMBOLS = {s.strip("\"'") for s in ''' +"+" "-" "*" "×" "/" "÷" "%" "=" +"==" "!=" "<" ">" "<=" ">=" "≠" "≤" "≥" +"&&" "||" "!" +"√" "⊗" +"&" "|" "^" "<<" ">>" +'''.split()} + +ALLOW = ["symbol/symbol.go", "symbol/check_hardcoded.py"] + +# ── Go patterns ──────────────────────────────────────────── +GO_SYNTAX_PATTERNS = [ + r'arrowVal\s*==', r'Arrow.*"="', r'Arrow.*"<-', r'Arrow.*"->"', + r'Token\{Kind: Arrow', r'\.Value\s*==\s*"\*"', + r'dict literal.*"="', r'\.Value\s*==\s*"="', +] + +# ── Rust patterns ────────────────────────────────────────── +RUST_SYNTAX_PATTERNS = [ + # match arms: "add" | "+" => ..., "print" | "println" => { ... } + # Allow any symbol used in a match arm + r'match\b', # entire match block is allowed +] + +def has_go_violation(line, sym): + q = re.escape(sym) + if re.search(r'"' + q + r'"\s*[:=]\s*(\d+|true|false)', line): + return "map attr" + if re.search(r'==\s*"' + q + r'"', line): + if any(re.search(p, line) for p in GO_SYNTAX_PATTERNS): + return "" + return "compare" + if re.search(r'case\s+.*"' + q + r'"', line): + return "switch case" + return "" + +def has_rs_violation(line, sym): + q = re.escape(sym) + # match arm: '"+" | "-" =>' or '"add" => {' + if re.search(r'"' + q + r'"\s*(?:\|[^"]*"[^"]*")?\s*=>', line): + return "" + # string compare: opcode == "!" + if re.search(r'==\s*"' + q + r'"', line): + return "compare" + return "" + +def check_file(fpath, violations, lang): + rel = os.path.relpath(fpath, ROOT) + if any(rel == a for a in ALLOW): + return + with open(fpath) as f: + for lineno, line in enumerate(f, 1): + s = line.strip() + if s.startswith('//') or s.startswith('*') or s.startswith('import') or s.startswith('use '): + continue + if s.startswith('#'): # Rust attributes (#[derive], #![...]) + continue + if s.startswith("//!") or s.startswith("///"): # Rust doc comments + continue + for sym in SYMBOLS: + has_fn = has_rs_violation if lang == 'rs' else has_go_violation + t = has_fn(line, sym) + if t: + violations.append(f"{rel}:{lineno}: {sym!r} {t}") + break + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--lang", default=".go", choices=(".go", ".rs"), + help="language to check (default: .go)") + args = ap.parse_args() + ext = args.lang + + violations = [] + for dirpath, dirnames, filenames in os.walk(ROOT): + dirnames[:] = [d for d in dirnames if d not in ('.git', 'vendor', 'deepx-design', 'target')] + for fn in filenames: + if not fn.endswith(ext) or fn.endswith('_test' + ext): + continue + check_file(os.path.join(dirpath, fn), violations, ext.lstrip('.')) + + if violations: + print(f"{len(violations)} hardcoded:") + for v in violations: print(f" {v}") + sys.exit(1) + print("0 hardcoded — clean.") + sys.exit(0) + +if __name__ == '__main__': main() diff --git a/keytree/const.go b/keytree/const.go index 391b1550..6f2b7392 100644 --- a/keytree/const.go +++ b/keytree/const.go @@ -24,16 +24,9 @@ const ( SegPause = "pause" // 暂停事件 SegResume = "resume" // 恢复命令 SegMsg = "msg" // 终态附加描述 - SegTerm = "term" // 绑定终端名 SegSeq = "seq" // vtid 自增序列 - SegDev = "dev" // /dev - SegTTY = "tty" // /dev/tty - SegSys = "sys" // /sys - SegOp = "op" // /sys/op - SegRwir = "rwir" // /sys/rwir - SegCmd = "cmd" // 命令队列 - SegFunc = "func" // 算子函数定义 + SegRwir = "rwir" // /rwir ) diff --git a/keytree/const.rs b/keytree/const.rs index f783cc6c..8ad704d3 100644 --- a/keytree/const.rs +++ b/keytree/const.rs @@ -1,21 +1,89 @@ -//! KV path constants — identical to keytree/const.go and keytree/const.h. - -pub const SYS_ROOT: &str = "/sys"; -pub const SYS_VM: &str = "/sys/vm"; -pub const SYS_VT: &str = "/sys/vthread"; -pub const SYS_LIB: &str = "/sys/lib"; -pub const LIB_ROOT: &str = "/lib"; -pub const VT_ROOT: &str = "/vthread"; -pub const FRAME_PC: &str = ".pc"; -pub const FRAME_STATUS: &str = ".status"; -pub const FRAME_RETVAL: &str = ".retval"; -pub const FRAME_ERR: &str = ".err"; -pub const FRAME_DEBUG: &str = ".debugger"; -pub const FRAME_X: &str = ".x"; -pub const FRAME_RPARAM: &str = ".rparam"; -pub const FRAME_WPARAM: &str = ".wparam"; - -pub fn vt_path(vtid: &str) -> String { format!("/vthread/{vtid}") } -pub fn vt_pc(vtid: &str) -> String { format!("/vthread/{vtid}/.pc") } -pub fn lib_func(pkg: &str, name: &str) -> String { format!("/lib/{pkg}.{name}") } -pub fn frame_local(root: &str, slot: &str) -> String { format!("{root}.x/{slot}") } +//! Path constants — matching keytree/const.go, keytree/frame.go, keytree/vthread.go. + +pub const PATH_SEP: &str = "/"; +pub const PATH_SEG_LIB: &str = "lib"; +pub const PATH_SEG_VTHREAD: &str = "vthread"; +pub const RUNTIME_MEMBER_SEP: &str = "\u{2025}"; // U+2025, matching Go RuntimeMemberSep +pub const MEMBER_SEP: &str = "."; + +// runtime reserved segments (keytree/const.go) +const SEG_LIB: &str = "\u{2025}lib"; // frame marker +const SEG_PC: &str = "pc"; +const SEG_STATUS: &str = "status"; +const SEG_CALLPC: &str = "callpc"; +const SEG_RETURNPC: &str = "returnpc"; + +// ── path builders ── + +pub fn lib_path(name: &str) -> String { format!("/{}/{}", PATH_SEG_LIB, name) } + +pub fn lib_func_dir(pkg: &str, name: &str) -> String { + if pkg.is_empty() { format!("/{}/{}", PATH_SEG_LIB, name) } + else { format!("/{}/{}.{}", PATH_SEG_LIB, pkg, name) } +} + +pub fn vthread_root(vtid: &str) -> String { format!("/{}/{}", PATH_SEG_VTHREAD, vtid) } + +fn vt_member(vtid: &str, seg: &str) -> String { + format!("{}/{}{}", vthread_root(vtid), RUNTIME_MEMBER_SEP, seg) +} + +pub fn vthread_pc(vtid: &str) -> String { vt_member(vtid, SEG_PC) } +pub fn vthread_status(vtid: &str) -> String { vt_member(vtid, SEG_STATUS) } + +// frame-level keys (keytree/frame.go) +pub fn frame_root(pc: &str) -> &str { + if let Some(idx) = pc.rfind(&format!("{}{}", PATH_SEP, "[")) { + &pc[..idx] + } else { + pc + } +} + +pub fn entry_pc(root: &str) -> String { + let trimmed = root.trim_end_matches(PATH_SEP); + format!("{}/[1,0]", trimmed) +} + +pub fn frame_stack(root: &str) -> String { + let trimmed = root.trim_end_matches(PATH_SEP); + format!("{}/", trimmed) +} + +fn frame_member(root: &str, seg: &str) -> String { + format!("{}{}{}", frame_stack(root), RUNTIME_MEMBER_SEP, seg) +} + +pub fn frame_lib(root: &str) -> String { frame_member(root, "lib") } +pub fn frame_callpc(root: &str) -> String { frame_member(root, SEG_CALLPC) } +pub fn frame_returnpc(root: &str) -> String { frame_member(root, SEG_RETURNPC) } + +// ── XValue kind constants (matching kvspace-go/const.go) ── + +pub const KIND_NONE: &str = ""; +pub const KIND_BOOL: &str = "bool"; +pub const KIND_INT8: &str = "int8"; +pub const KIND_INT16: &str = "int16"; +pub const KIND_INT32: &str = "int32"; +pub const KIND_INT64: &str = "int64"; +pub const KIND_UINT8: &str = "uint8"; +pub const KIND_UINT16: &str = "uint16"; +pub const KIND_UINT32: &str = "uint32"; +pub const KIND_UINT64: &str = "uint64"; +pub const KIND_FLOAT32: &str = "float32"; +pub const KIND_FLOAT64: &str = "float64"; +pub const KIND_STRING: &str = "string"; +pub const KIND_BYTES: &str = "bytes"; +pub const KIND_INDEX: &str = "index"; +pub const KIND_LINKINDEX: &str = "linkindex"; +pub const KIND_EXTINDEX: &str = "extindex"; +pub const KIND_RWIR: &str = "rwir"; +pub const KIND_RWFUNC: &str = "rwfunc"; +pub const KIND_SCOPE: &str = "scope"; + +// ── opcode constants (matching rwir/rwir.go OpCall etc.) ── + +pub const OP_CALL: &str = "call"; +pub const OP_RETURN: &str = "return"; +pub const OP_GOTO: &str = "goto"; +pub const OP_BR: &str = "br"; diff --git a/keytree/dev.go b/keytree/dev.go deleted file mode 100644 index 638b9715..00000000 --- a/keytree/dev.go +++ /dev/null @@ -1,5 +0,0 @@ -package keytree - -func DevTTY(name, stream string) string { - return PathSegSep + SegDev + PathSegSep + SegTTY + PathSegSep + name + PathSegSep + stream -} diff --git a/keytree/frame.go b/keytree/frame.go index 0fbc4d0c..135feac8 100644 --- a/keytree/frame.go +++ b/keytree/frame.go @@ -1,8 +1,8 @@ package keytree import ( - "fmt" "strings" + "fmt" ) // ── 帧路径工具 ──────────────────────────────────────────────────── @@ -19,9 +19,6 @@ func frameMember(root, seg string) string { return Stack(root) + RuntimeMemberSe func Stack(root string) string { return strings.TrimRight(root, PathSegSep) + PathSegSep } func FrameRO(root string) string { return frameMember(root, SegRO) } -func RParam(root, name string) string { return frameMember(root, SegRParam) + PathSegSep + name } -func WParam(root, name string) string { return frameMember(root, SegWParam) + PathSegSep + name } - func CallPC(root string) string { return frameMember(root, SegCallPC) } func ReturnPC(root string) string { return frameMember(root, SegReturnPC) } @@ -41,9 +38,16 @@ func FrameRoot(pc string) string { func EntryPC(root string) string { root = strings.TrimRight(root, PathSegSep) - return root + PathSegSep + "[0,0]" + return root + PathSegSep + "[1,0]" } func IsEntryPC(pc string) bool { idx := strings.LastIndex(pc, PathSegSep+"[") - return idx >= 0 && pc[idx:] == PathSegSep+"[0,0]" + return idx >= 0 && pc[idx:] == PathSegSep+"[1,0]" +} + +// ScopeEntryPC returns entry for scope/label frames: root/[0,0]. +// Scope frames don't have a function sig at [0,0], so first instruction is [0,0]. +func ScopeEntryPC(root string) string { + root = strings.TrimRight(root, PathSegSep) + return root + PathSegSep + "[0,0]" } diff --git a/keytree/mod.rs b/keytree/mod.rs index 63dd090d..5173f07a 100644 --- a/keytree/mod.rs +++ b/keytree/mod.rs @@ -1,7 +1 @@ -pub mod r#const; // "const" is a Rust keyword -pub mod dev; -pub mod entry; -pub mod frame; -pub mod member; -pub mod sys; -pub mod vthread; +pub mod r#const; diff --git a/keytree/sys.go b/keytree/sys.go index 00e941aa..49a39491 100644 --- a/keytree/sys.go +++ b/keytree/sys.go @@ -1,14 +1,10 @@ package keytree -const SysRoot = PathSegSep + SegSys +const RwirRoot = PathSegSep + SegRwir -func SysOp(backend, n string) string { return SysRoot + PathSegSep + SegOp + PathSegSep + backend + PathSegSep + n } +func Rwir(opcode string) string { return RwirRoot + PathSegSep + opcode } -func SysOpCmd(backend, n string) string { return SysOp(backend, n) + PathSegSep + SegCmd } - -func SysOpFunc(backend, name string) string { return SysRoot + PathSegSep + SegOp + PathSegSep + backend + PathSegSep + SegFunc + PathSegSep + name } - -const SysOpRoot = PathSegSep + SegSys + PathSegSep + SegOp -const SysRwirRoot = PathSegSep + SegSys + PathSegSep + SegRwir - -func SysRwir(opcode string) string { return SysRwirRoot + PathSegSep + opcode } +// RwirRuntime 返回 /rwir/{runtime}/{opcode}。{runtime} 反射自可执行文件名。 +func RwirRuntime(runtime, opcode string) string { + return RwirRoot + PathSegSep + runtime + PathSegSep + opcode +} diff --git a/keytree/vthread.go b/keytree/vthread.go index 60096504..46b0017f 100644 --- a/keytree/vthread.go +++ b/keytree/vthread.go @@ -18,7 +18,6 @@ func VThreadPC(vtid string) string { return vtMember(vtid, SegPC) } func VThreadStatus(vtid string) string { return vtMember(vtid, SegStatus) } func VThreadCtime(vtid string) string { return vtMember(vtid, SegCtime) } func VThreadDebugger(vtid string) string { return vtMember(vtid, SegDebugger) } -func VThreadTerm(vtid string) string { return vtMember(vtid, SegTerm) } func VThreadStatusMsg(vtid, statusVal string) string { return vtMember(vtid, statusVal) + PathSegSep + SegMsg @@ -45,14 +44,10 @@ func VtidFromPC(pc string) string { return rest } + func VThreadSlot(vtid, frame string, i, j int) string { if frame == "" { - return fmt.Sprintf(VthreadRoot+PathSegSep+"%s/[%d,%d]", vtid, i, j) + return fmt.Sprintf("/vthread/%s/[%d,%d]", vtid, i, j) } - return fmt.Sprintf(VthreadRoot+PathSegSep+"%s/%s/[%d,%d]", vtid, frame, i, j) -} - -func VThreadFrame(vtid, frame string) string { - if frame == "" { return VThread(vtid) } - return VThread(vtid) + PathSegSep + frame + return fmt.Sprintf("/vthread/%s/%s/[%d,%d]", vtid, frame, i, j) } diff --git a/kvcpu/controlflow.rs b/kvcpu/controlflow.rs index f6b4c078..4b31afa3 100644 --- a/kvcpu/controlflow.rs +++ b/kvcpu/controlflow.rs @@ -1,3 +1,179 @@ -//! Control flow handling (br, goto, label). -pub fn handle_br() -> String { todo!("handle_br") } -pub fn handle_goto() -> String { todo!("handle_goto") } +//! Control flow — matching kvcpu/controlflow.go. +use std::collections::HashMap; +use super::cpu::KVCpu; +use crate::rwir::rwir::{Rwir, decode}; +use crate::rwir::builtin::ops; +use crate::keytree::r#const; + +use super::execute::{SlotValue, parse_func_dir, execute_inner, build_name_slot_map, build_param_names, build_return_names, resolve_read}; + +/// handle_call: read callee params, pass args, execute, propagate return values. +pub fn handle_call( + cpu: &KVCpu, + _caller_dir: &str, + inst: &Rwir, + caller_name_slot: &HashMap, + caller_slot_vals: &HashMap, + vars: &mut HashMap)>, +) -> Result<(), String> { + if inst.reads.is_empty() { return Err("call needs func name".into()); } + let func_name = &inst.reads[0].name; + + let callee_dir = if func_name.starts_with("/lib/") { + func_name["/lib/".len()..].to_string() + } else { + func_name.clone() + }; + let callee_dir = parse_func_dir(&callee_dir); + + let sig_rv = cpu.get(&format!("{}/[0,0]", callee_dir)) + .ok_or_else(|| format!("call: func not found: {}", func_name))?; + if sig_rv.kind != r#const::KIND_RWFUNC { + return Err(format!("call: {} has no rwfunc", func_name)); + } + let nr = unsafe { sig_rv.u16_le(0) } as usize; + + let callee_names = build_param_names(cpu, &callee_dir); + + // Build args by position + let mut args: HashMap = HashMap::new(); + for i in 0..nr { + let slot = format!("[0,-{}]", i + 1); + if let Some(arg) = inst.reads.get(i) { + let sv = resolve_read(cpu, caller_name_slot, caller_slot_vals, vars, arg); + args.insert(slot, sv); + } + } + + let callee_returns = build_return_names(cpu, &callee_dir); + let ret = execute_inner(cpu, &callee_dir, &callee_names, &args, &callee_returns)?; + for (i, ret_name) in callee_returns.iter().enumerate() { + if let Some((k, v)) = ret.get(ret_name) { + if let Some(w) = inst.writes.get(i) { + vars.insert(w.name.clone(), (k.clone(), v.clone())); + } + } + } + Ok(()) +} + +pub fn handle_return() -> Result<(), String> { Ok(()) } + +/// handle_br(cond, trueLabel, falseLabel): evaluate resolved cond → goto label. +pub fn handle_br( + cpu: &KVCpu, func_dir: &str, inst: &Rwir, + name_slot: &HashMap, + slot_vals: &HashMap, + vars: &mut HashMap)>, +) -> Result<(), String> { + if inst.reads.len() < 3 { return Err("br needs 3 args".into()); } + let cond = unsafe { inst.reads[0].first_byte() != 0 }; + let label_idx = if cond { 1 } else { 2 }; + let label_name = unsafe { + String::from_utf8_lossy(inst.reads[label_idx].bytes()).to_string() + }; + execute_scope(cpu, func_dir, &label_name, name_slot, slot_vals, vars) +} + +/// handle_goto(label): execute label scope. +pub fn handle_goto( + cpu: &KVCpu, func_dir: &str, inst: &Rwir, + name_slot: &HashMap, + slot_vals: &HashMap, + vars: &mut HashMap)>, +) -> Result<(), String> { + if inst.reads.is_empty() { return Err("goto needs label".into()); } + let label = unsafe { String::from_utf8_lossy(inst.reads[0].bytes()).to_string() }; + execute_scope(cpu, func_dir, &label, name_slot, slot_vals, vars) +} + +/// execute_scope: run instructions under func_dir/label[0,0], [1,0], ... +/// Supports br within scopes for while loops. +fn execute_scope( + cpu: &KVCpu, func_dir: &str, label: &str, + name_slot: &HashMap, + slot_vals: &HashMap, + vars: &mut HashMap)>, +) -> Result<(), String> { + // Go layout: func_dir/label[0,0], func_dir/label[1,0], ... + let scope_prefix = format!("{}/{}", func_dir, label); + let scope_base = if cpu.get(&format!("{}[0,0]", scope_prefix)).is_some() { + scope_prefix + } else { + format!("{}{}", func_dir, label) + }; + + // Scope keys: func_dir/label[0,0], func_dir/label[1,0] — no "/" before "[" + let mut slot: i32 = 0; + loop { + let mut inst = decode_scope(cpu, &scope_base, slot) + .ok_or_else(|| format!("scope decode failed at {}", slot))?; + if inst.opcode.is_empty() { break; } + + // Resolve variable references in-place + for p in &mut inst.reads { + if p.val_kind == r#const::KIND_RWIR { + let sv = resolve_read(cpu, name_slot, slot_vals, vars, p); + if sv.body_ptr.is_null() { continue; } + p.val_kind = sv.kind; + p.body_ptr = sv.body_ptr; + p.body_len = sv.body_len; + } + } + + let op = inst.opcode.clone(); + if ops::native(cpu, &op, &inst.reads, &inst.writes, vars) { + // handled by builtin + } else if op == "br" { + // Conditional branch within scope → jump to different scope + if inst.reads.len() < 3 { continue; } + let cond = unsafe { inst.reads[0].first_byte() != 0 }; + let label_idx = if cond { 1 } else { 2 }; + let next_label = unsafe { String::from_utf8_lossy(inst.reads[label_idx].bytes()).to_string() }; + return execute_scope(cpu, func_dir, &next_label, name_slot, slot_vals, vars); + } else if op == "goto" { + if let Some(p) = inst.reads.first() { + let next_label = unsafe { String::from_utf8_lossy(p.bytes()).to_string() }; + return execute_scope(cpu, func_dir, &next_label, name_slot, slot_vals, vars); + } + } + // Other opcodes in scope (like "return") — skip + slot += 1; + } + Ok(()) +} + +/// Decode scope instruction: scope_base[slot,0], scope_base[slot,-j], scope_base[slot,+j]. +fn decode_scope(cpu: &KVCpu, scope_base: &str, slot: i32) -> Option { + use crate::rwir::rwir::Param; + let base = format!("{}[{}", scope_base, slot); + let mut r = Rwir { opcode: String::new(), reads: Vec::new(), writes: Vec::new() }; + if let Some(rv) = cpu.get(&format!("{},0]", base)) { + let raw = unsafe { std::slice::from_raw_parts(rv.body_ptr, rv.body_len as usize) }; + let skip = if rv.kind == r#const::KIND_RWIR && raw.len() >= 4 { 4 } else { 0 }; + r.opcode = String::from_utf8_lossy(&raw[skip..]).to_string(); + } + for i in 1..=32 { + if let Some(rv) = cpu.get(&format!("{},-{}]", base, i)) { + let name = if rv.kind == r#const::KIND_RWIR && rv.body_len >= 4 { + let n = unsafe { std::slice::from_raw_parts(rv.body_ptr, rv.body_len as usize) }; + String::from_utf8_lossy(&n[4..]).to_string() + } else { + let n = unsafe { std::slice::from_raw_parts(rv.body_ptr, rv.body_len as usize) }; + String::from_utf8_lossy(n).to_string() + }; + r.reads.push(Param { name, val_kind: rv.kind.clone(), body_ptr: rv.body_ptr, body_len: rv.body_len }); + } + if let Some(rv) = cpu.get(&format!("{},{}]", base, i)) { + let name = if rv.kind == r#const::KIND_RWIR && rv.body_len >= 4 { + let n = unsafe { std::slice::from_raw_parts(rv.body_ptr, rv.body_len as usize) }; + String::from_utf8_lossy(&n[4..]).to_string() + } else { + let n = unsafe { std::slice::from_raw_parts(rv.body_ptr, rv.body_len as usize) }; + String::from_utf8_lossy(n).to_string() + }; + r.writes.push(Param { name, val_kind: rv.kind.clone(), body_ptr: rv.body_ptr, body_len: rv.body_len }); + } + } + Some(r) +} diff --git a/kvcpu/cpu.go b/kvcpu/cpu.go index e30edd4e..7968db12 100644 --- a/kvcpu/cpu.go +++ b/kvcpu/cpu.go @@ -1,6 +1,6 @@ // Package kvcpu 提供 KV 虚拟 CPU 执行引擎。 // -// c := kvcpu.New(kv, vmID) +// c := kvcpu.New(kv) // c.Execute(pc) // // CPU 通过 kvspace 作为统一内存,Fetch-Decode-Execute 循环执行 vthread。 @@ -13,15 +13,13 @@ type CPU interface { Execute(pc string) error } -// cpu 是 CPU 接口的实现,持有 kvspace 引用和 vmID。 +// cpu 是 CPU 接口的实现,持有 kvspace 引用。 type cpu struct { - kv kvspace.KVSpace - vmID string + kv kvspace.KVSpace } // New 创建一个与 kv 绑定的 CPU 实例。 -// vmID 用于系统级通知(/sys/vm//err)。 // 所有 CPU 实例均内置调试支持:通过 /vthread//.debugger 键激活。 -func New(kv kvspace.KVSpace, vmID string) CPU { - return &cpu{kv: kv, vmID: vmID} +func New(kv kvspace.KVSpace) CPU { + return &cpu{kv: kv} } diff --git a/kvcpu/cpu.rs b/kvcpu/cpu.rs index 05f3659b..7f139750 100644 --- a/kvcpu/cpu.rs +++ b/kvcpu/cpu.rs @@ -1,9 +1,130 @@ -//! KV Virtual CPU — identical to kvcpu/cpu.go and kvcpu/cpu.h. -//! Primary implementation language: Rust. - -/// CPU is the KV virtual CPU interface. -pub trait Cpu { - fn execute(&mut self, pc: &str) -> Result<(), String>; - fn step(&mut self, pc: &str) -> Result<(), String>; - fn debugger_active(&self) -> bool; +//! KVCpu — zero-copy FFI wrapper over kvspace-c. + +use std::ffi::{c_char, c_int, c_void, CString}; + +#[link(name = "kvspace-c")] +extern "C" { + fn kvspace_open(path: *const c_char, data_size: usize) -> *mut c_void; + fn kvspace_close(kv: *mut c_void); + fn kvspace_get(kv: *mut c_void, key: *const c_char, resolve: c_int, out_len: *mut i32) -> *mut u8; + fn kvspace_set(kv: *mut c_void, key: *const c_char, val: *const u8, val_len: i32) -> c_int; + fn kvspace_list(kv: *mut c_void, prefix: *const c_char, expand_ext: c_int, resolve: c_int, names: *mut *mut *mut c_char, count: *mut i32) -> c_int; + fn kvspace_mkindex(kv: *mut c_void, path: *const c_char) -> c_int; + fn kvspace_del(kv: *mut c_void, key: *const c_char) -> c_int; + fn kvspace_notify(kv: *mut c_void, key: *const c_char, val: *const u8, val_len: i32) -> c_int; + fn kvspace_watch(kv: *mut c_void, key: *const c_char, timeout_ms: i32, out_len: *mut i32) -> *mut u8; + fn kvspace_link(kv: *mut c_void, target: *const c_char, linkpath: *const c_char) -> c_int; +} + +/// Raw XValue reference: points directly into SHM, zero-copy. +/// TLV header: [1B: b7=isptr|b6-0=kind_len][N B kind][4B al LE][4B raw_len LE][M B raw_body] +pub struct RawValue { + pub is_ptr: bool, // bit7 of header byte + pub kind: String, // small, copied from TLV header + pub array_len: i32, + pub body_len: i32, // raw body length + pub body_ptr: *const u8, // direct pointer into SHM; zero-copy +} + +impl RawValue { + pub unsafe fn bytes(&self) -> &[u8] { std::slice::from_raw_parts(self.body_ptr, self.body_len as usize) } + pub unsafe fn i64(&self) -> i64 { (self.body_ptr as *const i64).read_unaligned() } + pub unsafe fn f64(&self) -> f64 { (self.body_ptr as *const f64).read_unaligned() } + pub unsafe fn u16_le(&self, offset: usize) -> u16 { + let ptr = self.body_ptr.add(offset); + (ptr as *const u16).read_unaligned() + } + /// body as &str (zero-copy) + pub unsafe fn body_str(&self) -> &str { + let s = std::slice::from_raw_parts(self.body_ptr, self.body_len as usize); + std::str::from_utf8_unchecked(s) + } + /// ptr target path: body is the target key + pub unsafe fn ptr_target(&self) -> &str { self.body_str() } +} + +pub struct KVCpu { + pub kv: *mut c_void, + pub vm_id: String, +} + +impl KVCpu { + pub fn open(shm_path: &str) -> Option<*mut c_void> { + let cp = CString::new(shm_path).ok()?; + let kv = unsafe { kvspace_open(cp.as_ptr(), 2097152) }; + if kv.is_null() { None } else { Some(kv) } + } + + pub fn close(kv: *mut c_void) { unsafe { kvspace_close(kv); } } + + pub fn new(kv: *mut c_void, vm_id: &str) -> Self { + KVCpu { kv, vm_id: vm_id.to_string() } + } + + /// Zero-copy get: parses TLV, returns RawValue with SHM body pointer. + /// is_ptr is bit7 of first header byte. + pub fn get(&self, key: &str) -> Option { + let ck = CString::new(key).ok()?; + let mut len: i32 = 0; + let ptr = unsafe { kvspace_get(self.kv, ck.as_ptr(), 1, &mut len) }; + if ptr.is_null() || len == 0 { return None; } + let data = unsafe { std::slice::from_raw_parts(ptr, len as usize) }; + if data.len() < 10 { return None; } + + let is_ptr = data[0] & 0x80 != 0; + let kl = (data[0] & 0x7F) as usize; + let p = 1 + kl; + if data.len() < p + 8 { return None; } + + let kind = String::from_utf8_lossy(&data[1..p]).to_string(); + let al = i32::from_le_bytes([data[p], data[p+1], data[p+2], data[p+3]]); + let rl = i32::from_le_bytes([data[p+4], data[p+5], data[p+6], data[p+7]]); + if (rl as usize) + p + 8 > data.len() { return None; } + + Some(RawValue { + is_ptr, + kind, + array_len: al, + body_len: rl, + body_ptr: unsafe { ptr.add(p + 8) }, + }) + } + + pub fn set(&self, key: &str, kind: &str, raw: &[u8], al: i32, is_ptr: bool) { + let ck = CString::new(key).unwrap(); + let first_byte = if is_ptr { kind.len() as u8 | 0x80 } else { kind.len() as u8 }; + let mut tlv = vec![first_byte]; + tlv.extend_from_slice(kind.as_bytes()); + tlv.extend_from_slice(&al.to_le_bytes()); + tlv.extend_from_slice(&(raw.len() as i32).to_le_bytes()); + tlv.extend_from_slice(raw); + unsafe { kvspace_set(self.kv, ck.as_ptr(), tlv.as_ptr(), tlv.len() as i32) }; + } + + /// List children of a directory prefix, filtering by prefix. + pub fn list(&self, prefix: &str) -> Vec { + let cp = CString::new(prefix).ok(); + if cp.is_none() { return vec![]; } + let mut names: *mut *mut c_char = std::ptr::null_mut(); + let mut count: i32 = 0; + let rc = unsafe { kvspace_list(self.kv, cp.unwrap().as_ptr(), 0, 0, &mut names, &mut count) }; + if rc != 0 || count <= 0 || names.is_null() { return vec![]; } + let mut result = Vec::with_capacity(count as usize); + for i in 0..count as isize { + let s = unsafe { std::ffi::CStr::from_ptr(*names.offset(i)) }; + if let Ok(s) = s.to_str() { result.push(s.to_string()); } + } + result + } + + pub fn mkindex(&self, path: &str) { + let cp = CString::new(path).unwrap(); + unsafe { kvspace_mkindex(self.kv, cp.as_ptr()) }; + } + + pub fn del(&self, key: &str) { + if let Ok(ck) = CString::new(key) { + unsafe { kvspace_del(self.kv, ck.as_ptr()) }; + } + } } diff --git a/kvcpu/debug.go b/kvcpu/debug.go index 386c66b8..ecd13ba6 100644 --- a/kvcpu/debug.go +++ b/kvcpu/debug.go @@ -22,14 +22,14 @@ func debugFuncName(kv kvspace.KVSpace, frameRoot string) string { parent, dirName := kvspace.SepPath(frameRoot) if parent != "/" { parent += kvspace.DirIndexSuf } v := kv.Get(parent, []string{dirName + kvspace.DirIndexSuf}, true)[0] - extHead := kvspace.DecodeXValueHead(v.Encode()); extTarget := kvspace.DecodeExtIndex(extHead.Raw).ExtPath() + extTarget := kvspace.DecodeExtIndex(kvspace.BodyBytes(v)).ExtPath() if extTarget == "" { return "?" } name := strings.TrimPrefix(extTarget, keytree.LibRoot+"/") return strings.TrimSuffix(name, "/") } -// debugNotifyPause 向 /vthread//.debugger.pause 投递暂停事件(JSON)。 -// CPU 命中断点后调用,agent 通过 kvspace watch 接收。 +// debugNotifyPause 向 /vthread//.debugger.pause 写暂停事件(JSON)。 +// CPU 命中断点后调用,agent 通过 kvspace 轮询读取。 func debugNotifyPause(_ context.Context, kv kvspace.KVSpace, vtid, pc string, inst *rwir.Rwir) { frameRoot := keytree.FrameRoot(pc) event, _ := json.Marshal(map[string]any{ @@ -38,16 +38,19 @@ func debugNotifyPause(_ context.Context, kv kvspace.KVSpace, vtid, pc string, in "frame": frameRoot, "op": inst.Opcode, }) - kv.Notify(keytree.VThreadDebuggerPause(vtid), kvspace.NewChar(string(event))) + kv.Set([]kvspace.KVPair{{Key: keytree.VThreadDebuggerPause(vtid), Val: kvspace.NewCharByte(event...)}}) } -// debugWaitResume 阻塞等待 /vthread//.debugger.resume 上的 Notify, +// debugWaitResume 轮询 /vthread//.debugger.resume, // 返回 agent 发送的命令字符串("step" / "continue" / "abort")。 -// 使用超时重试,与 vthread.WaitDone 保持一致的模式。 func debugWaitResume(kv kvspace.KVSpace, vtid string) string { + resumeKey := keytree.VThreadDebuggerResume(vtid) for { - val := kv.Watch(keytree.VThreadDebuggerResume(vtid), 30*time.Second) - if !kvspace.IsNone(val) { return val.String() } - // 超时 → 继续等待 + cmd := kvspace.GetOne(kv, resumeKey).ValueString() + if cmd != "" { + kv.Del(resumeKey) // 消费命令 + return cmd + } + time.Sleep(time.Millisecond) } } diff --git a/kvcpu/debug.rs b/kvcpu/debug.rs index 050f0e89..c46d706c 100644 --- a/kvcpu/debug.rs +++ b/kvcpu/debug.rs @@ -1,2 +1 @@ -//! Debugger support. -pub fn debugger_step() { todo!("debugger_step") } +pub fn debugger_active() -> bool { false } diff --git a/kvcpu/execute.go b/kvcpu/execute.go index 47ddf925..b9d012a7 100644 --- a/kvcpu/execute.go +++ b/kvcpu/execute.go @@ -4,6 +4,9 @@ import ( "strings" "context" "fmt" + "strconv" + "sync/atomic" + "time" "github.com/array2d/kvspace-go" "kvlang/keytree" @@ -12,7 +15,6 @@ import ( "kvlang/logx" "kvlang/rwir" "kvlang/rwir/builtin" - "kvlang/rwir/dispatch" "kvlang/vthread" ) @@ -22,11 +24,12 @@ const MaxStackDepth = 256 // Execute 从绝对 PC 开始执行 vthread,直到完成、出错或 ctx 取消。 // -// Dispatch 优先级(全静态,无 KV 分类查询): +// Dispatch 优先级: // 1. IsControlOp — call/return/br/goto 控制流原语 -// 2. IsNativeOp — +/-/*/print/sqrt 等标量内建算子 -// 3. tensor.* — tensor 命名空间算子(op/dispatch) -// 4. default — 用户定义函数(rewrite as call) +// 2. IsNativeRwir — +/-/*/print/sqrt 等标量内建算子 +// 3. isCopyOp — 路径/字面量复制 +// 4. isUserRwir — 用户声明的 rwir(查 /rwir/)→ 外部执行器 handoff +// 5. default — 用户定义函数(rewrite as call) // ↓ HandleCall 内查 FuncIdx;未找到 → SetError // // 调试支持(内置,无需特殊启动): @@ -110,7 +113,7 @@ func (c *cpu) Execute(pc string) error { // - 单步模式:每条指令读取一次 .debug(已在调试中,overhead 可接受) if stepping || keytree.IsEntryPC(pc) { v := kvspace.GetOne(c.kv, keytree.VThreadDebugger(vtid)) - switch mode := v.String(); { + switch mode := v.ValueString(); { case mode == "" && stepping: // Agent 清除了 .debugger 标志 → 退出单步模式 stepping = false @@ -148,19 +151,19 @@ func (c *cpu) Execute(pc string) error { execErr = handleControl(ctx, c.kv, vtid, pc, inst) // ── 2. 标量内建算子(静态 map,零 KV 查询)────────────────────── - case builtin.IsNativeOp(inst.Opcode): + case builtin.IsNativeRwir(inst.Opcode): execErr = builtin.Native(ctx, c.kv, vtid, pc, inst) - // ── 3. tensor 命名空间算子(op/dispatch)──────────────────────── - case strings.HasPrefix(inst.Opcode, "tensor."): - execErr = dispatch.Compute(ctx, c.kv, vtid, pc, inst) - - // ── 4. 路径/变量复制( ./x -> dst 或 /abs -> dst 或 a -> b)────── + // ── 3. 路径/变量复制( ./x -> dst 或 /abs -> dst 或 a -> b)────── // 当 opcode 为路径或字面量且有写槽时,视为 copy 操作。 // 裸标识符由 Flat() 归一化为 ./ident,此处通过路径检查统一识别。 case isCopyOp(inst.Opcode, inst.Writes): execErr = builtin.ExecuteCopy(c.kv, vtid, pc, inst) + // ── 4. 用户声明的 rwir(读写码):/rwir/ 存在 → 外部执行器 handoff ── + case isUserRwir(c.kv, inst.Opcode): + execErr = handoffExternalRwir(ctx, c.kv, vtid, pc, inst) + // ── 5. 用户定义函数(default → rewrite as call)───────────────── // 不含 dot、不在任何静态集合 → 必然是用户 func // HandleCall 负责 FuncIdx 查找;未找到 → SetError @@ -178,7 +181,7 @@ func (c *cpu) Execute(pc string) error { // 读取指令执行后更新的 PC newPCVal := kvspace.GetOne(c.kv, keytree.VThreadPC(vtid)) - newPC := newPCVal.String() + newPC := newPCVal.ValueString() if newPC == "" { break } @@ -195,6 +198,45 @@ func isCopyOp(opcode string, writes []rwir.Param) bool { return symbol.Lookup(opcode).Word == "assign" && len(writes) > 0 } +// externalRwirTimeout 外部执行器完成 rwir 的等待超时。 +const externalRwirTimeout = 30 * time.Second + +// isUserRwir 判断 opcode 是否为扩展 rwir(读写码):/rwir/ 的 kind == "rwir"。 +// rwir 的唯一标准是 kind;不在 rwirregistry(native builtin)内的 rwir 即扩展 rwir。 +func isUserRwir(kv kvspace.KVSpace, opcode string) bool { + // 全路径 opcode(如 /lib/math.sum)是函数调用,不是 rwir + if strings.HasPrefix(opcode, "/") { + return false + } + v := kvspace.GetOne(kv, keytree.Rwir(opcode)) + return !kvspace.IsNone(v) && v.Kind() == kvspace.KindRwir +} + +// handoffSeq 全局递增的 handoff 请求号,保证每次 .todo 的 id 唯一(循环里同一 pc 也唯一)。 +var handoffSeq int64 + +// handoffExternalRwir 把当前 rwir 交给外部执行器: +// 写 "pc|id" 到 /rwir//.todo,再 watch /rwir//.done == id。 +func handoffExternalRwir(ctx context.Context, kv kvspace.KVSpace, vtid, pc string, inst *rwir.Rwir) error { + base := keytree.Rwir(inst.Opcode) + todoKey := base + "/.todo<" + vtid + ">" + doneKey := base + "/.done<" + vtid + ">" + id := strconv.FormatInt(atomic.AddInt64(&handoffSeq, 1), 10) + if err := kv.Set([]kvspace.KVPair{{Key: todoKey, Val: kvspace.NewCharByte([]byte(pc + "|" + id)...)}}); err != nil { + msg := fmt.Sprintf("RuntimeError: external rwir %s: todo write failed: %v", inst.Opcode, err) + vthread.SetError(ctx, kv, vtid, pc, msg) + return fmt.Errorf("%s", msg) + } + doneVal := kv.Watch(doneKey, kvspace.NewCharByte([]byte(id)...), externalRwirTimeout) + if kvspace.IsNone(doneVal) || doneVal.ValueString() != id { + msg := fmt.Sprintf("RuntimeError: external rwir %s timeout", inst.Opcode) + vthread.SetError(ctx, kv, vtid, pc, msg) + return fmt.Errorf("%s", msg) + } + // 不在此推进 PC:cpuext 批量执行己方 rwir 后,已把最终 PC 写回 /vthread//pc。 + return nil +} + // checkReadOnlyWrites 读参只读公理的运行期防线(fix-027): // 带写槽的指令,裸名写槽(无 / . [ 形态)命中当前帧 .ro 名单(Bootstrap/HandleCall 写入) // → SetError 异常终止。set 的 `-> base` 本体回写(写回原值,fix-013)豁免。 @@ -204,7 +246,7 @@ func (c *cpu) checkReadOnlyWrites(ctx context.Context, vtid, pc string, inst *rw return nil } roVal := kvspace.GetOne(c.kv, keytree.FrameRO(keytree.FrameRoot(pc))) - ro := roVal.String() + ro := roVal.ValueString() if ro == "" { return nil } diff --git a/kvcpu/execute.rs b/kvcpu/execute.rs index b0e11cbd..7436794c 100644 --- a/kvcpu/execute.rs +++ b/kvcpu/execute.rs @@ -1,11 +1,182 @@ -//! Fetch-Decode-Execute loop — identical to kvcpu/execute.go. +use std::collections::HashMap; +use super::cpu::KVCpu; +use crate::rwir::rwir::{Rwir, Param, decode}; +use crate::rwir::builtin::ops; +use crate::keytree::r#const; -use crate::op; +/// SlotValue references SHM directly — zero-copy. +/// kind is copied (small), body_ptr points into SHM. +#[derive(Clone)] +pub struct SlotValue { + pub kind: String, + pub body_ptr: *const u8, + pub body_len: i32, +} + +/// Execute a function from Go-layout kvspace SHM. +pub fn execute(cpu: &KVCpu, func_name: &str) -> Result<(), String> { + let func_dir = parse_func_dir(func_name); + execute_inner(cpu, &func_dir, &[], &HashMap::new(), &[])?; + Ok(()) +} + +pub fn parse_func_dir(name: &str) -> String { + let (pkg, fname) = if let Some(dot) = name.rfind('.') { + (&name[..dot], &name[dot+1..]) + } else { + ("", name) + }; + r#const::lib_func_dir(pkg, fname) +} + +/// Execute with pre-resolved args. Returns the callee's local vars (for return values). +pub fn execute_inner( + cpu: &KVCpu, + func_dir: &str, + _arg_names: &[String], + caller_args: &HashMap, + return_names: &[String], // ordered write-param names to return +) -> Result)>, String> { + let sig_rv = cpu.get(&format!("{}/[0,0]", func_dir)) + .ok_or_else(|| format!("func not found: {}", func_dir))?; + if sig_rv.kind != r#const::KIND_RWFUNC { + return Err(format!("{} has no rwfunc at [0,0]", func_dir)); + } + let nr = unsafe { sig_rv.u16_le(0) } as i32; + let nw = unsafe { sig_rv.u16_le(2) } as i32; + + let name_slot = build_name_slot_map(cpu, func_dir); + + let mut slot_vals: HashMap = HashMap::new(); + for (slot, val) in caller_args { + slot_vals.insert(slot.clone(), val.clone()); + } + + let mut vars: HashMap)> = HashMap::new(); + + let mut slot_idx: i32 = 1; + loop { + let inst = decode(cpu, func_dir, slot_idx, 128, 128) + .ok_or_else(|| format!("decode failed at slot {}", slot_idx))?; + if inst.opcode.is_empty() { break; } + + let mut resolved_inst = inst.clone(); + for p in &mut resolved_inst.reads { + if p.val_kind == r#const::KIND_RWIR { + let sv = resolve_read(cpu, &name_slot, &slot_vals, &vars, p); + if sv.body_ptr.is_null() { continue; } + p.val_kind = sv.kind; + p.body_ptr = sv.body_ptr; + p.body_len = sv.body_len; + } + } + + let op = resolved_inst.opcode.clone(); + if ops::native(cpu, &op, &resolved_inst.reads, &resolved_inst.writes, &mut vars) { + // handled by builtin + } else if matches!(op.as_str(), "call" | "goto" | "br" | "return") { + match op.as_str() { + "call" => super::controlflow::handle_call(cpu, func_dir, &resolved_inst, &name_slot, &slot_vals, &mut vars)?, + "return" => break, // exit loop, return vars + "goto" => super::controlflow::handle_goto(cpu, func_dir, &resolved_inst, &name_slot, &slot_vals, &mut vars)?, + "br" => super::controlflow::handle_br(cpu, func_dir, &resolved_inst, &name_slot, &slot_vals, &mut vars)?, + _ => {} + }; + } else { + let callee_dir = parse_func_dir(&op); + let callee_returns = build_return_names(cpu, &callee_dir); + let mut args: HashMap = HashMap::new(); + for (i, read) in resolved_inst.reads.iter().enumerate() { + args.insert(format!("[0,-{}]", i+1), SlotValue { + kind: read.val_kind.clone(), + body_ptr: read.body_ptr, + body_len: read.body_len, + }); + } + let ret_vars = execute_inner(cpu, &callee_dir, &[], &args, &callee_returns)?; + for (i, ret_name) in callee_returns.iter().enumerate() { + if let Some((k, v)) = ret_vars.get(ret_name) { + if let Some(w) = resolved_inst.writes.get(i) { + vars.insert(w.name.clone(), (k.clone(), v.clone())); + } + } + } + } + slot_idx += 1; + } + + // Collect return values + let mut ret = HashMap::new(); + for name in return_names { + if let Some(v) = vars.get(name) { + ret.insert(name.clone(), v.clone()); + } + } + Ok(ret) +} + +pub fn build_name_slot_map(cpu: &KVCpu, func_dir: &str) -> HashMap { + let mut map = HashMap::new(); + let children = cpu.list(&format!("{}/", func_dir)); + for child in children { + if child.starts_with('[') || child.starts_with('.') { continue; } + if let Some(rv) = cpu.get(&format!("{}/{}", func_dir, child)) { + if rv.is_ptr { + let target = unsafe { rv.ptr_target() }.to_string(); + map.insert(child, target); + } + } + } + map +} + +pub fn build_param_names(cpu: &KVCpu, func_dir: &str) -> Vec { + let name_slot = build_name_slot_map(cpu, func_dir); + let mut pairs: Vec<(i32, String)> = name_slot.iter() + .filter_map(|(name, slot)| { + if slot.starts_with("[0,-") { + let idx: i32 = slot.trim_start_matches("[0,-").trim_end_matches(']').parse().ok()?; + Some((idx, name.clone())) + } else { None } + }) + .collect(); + pairs.sort_by_key(|(idx, _)| *idx); + pairs.into_iter().map(|(_, name)| name).collect() +} -pub fn fetch_decode(_link_base: &str, _pc: &str) -> op::Instruction { - todo!("fetch_decode") +pub fn build_return_names(cpu: &KVCpu, func_dir: &str) -> Vec { + let name_slot = build_name_slot_map(cpu, func_dir); + let mut pairs: Vec<(i32, String)> = name_slot.iter() + .filter_map(|(name, slot)| { + if slot.starts_with("[0,") && !slot.starts_with("[0,-") { + let idx: i32 = slot.trim_start_matches("[0,").trim_end_matches(']').parse().ok()?; + Some((idx, name.clone())) + } else { None } + }) + .collect(); + pairs.sort_by_key(|(idx, _)| *idx); + pairs.into_iter().map(|(_, name)| name).collect() } -pub fn execute_inst(_inst: &op::Instruction) -> String { - todo!("execute_inst") +pub fn resolve_read( + _cpu: &KVCpu, + name_slot: &HashMap, + slot_vals: &HashMap, + vars: &HashMap)>, + p: &Param, +) -> SlotValue { + if p.val_kind != r#const::KIND_RWIR && p.val_kind != r#const::KIND_RWFUNC { + return SlotValue { kind: p.val_kind.clone(), body_ptr: p.body_ptr, body_len: p.body_len }; + } + let name = &p.name; + if name.is_empty() { return SlotValue { kind: r#const::KIND_NONE.to_string(), body_ptr: std::ptr::null(), body_len: 0 }; } + if let Some(slot) = name_slot.get(name) { + if let Some(sv) = slot_vals.get(slot) { + return sv.clone(); + } + } + if let Some((k, v)) = vars.get(name) { + return SlotValue { kind: k.clone(), body_ptr: v.as_ptr(), body_len: v.len() as i32 }; + } + SlotValue { kind: r#const::KIND_NONE.to_string(), body_ptr: std::ptr::null(), body_len: 0 } } diff --git a/kvcpu/kvcpu_stub.cppx b/kvcpu/kvcpu_stub.cppx index 91d391f8..ac8093cc 100644 --- a/kvcpu/kvcpu_stub.cppx +++ b/kvcpu/kvcpu_stub.cppx @@ -1 +1,181 @@ +// kvlang::kvcpu — C++ virtual CPU implementation +// Links against libkvspace-c.so. Mounts SHM that Go layout wrote. #include "cpu.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kvlang::kvcpu { + +// ── KVSpace thin wrapper ──────────────────────────────────────────────────── +class KVSpace { + kvspace_t *kv; +public: + KVSpace(kvspace_t *k) : kv(k) {} + xvalue_head_t get(std::string_view key) { + int32_t len; uint8_t *d = kvspace_get(kv, key.data(), 1, &len); + if (!d) return {}; + auto h = xvalue_decode_head(d, len); free(d); return h; + } + void set(std::string_view key, const char *kind, const uint8_t *raw, int32_t rl) { + int32_t kl = strlen(kind); + int32_t tl = 1 + kl + 8 + rl; + auto buf = std::make_unique(tl); + buf[0] = kl; memcpy(&buf[1], kind, kl); + int32_t al = 1; + memcpy(&buf[1+kl], &al, 4); memcpy(&buf[1+kl+4], &rl, 4); + memcpy(&buf[1+kl+8], raw, rl); + kvspace_set(kv, key.data(), buf.get(), tl); + } + void setStr(std::string_view key, std::string_view val) { + set(key, "string", (const uint8_t *)val.data(), val.size()); + } + static void close(kvspace_t *k) { kvspace_close(k); } +}; + +// ── Instruction ───────────────────────────────────────────────────────────── +struct Inst { + std::string opcode; + std::vector> reads, writes; // (name, raw_val) +}; + +static int extractAddr0(std::string_view seg) { + auto s = seg.substr(1, seg.size() - 2); // strip [ ] + auto c = s.find(','); return c == s.npos ? 0 : std::stoi(std::string(s.substr(0, c))); +} +static std::string nextPC(std::string_view pc) { + auto lb = pc.rfind('['); + if (lb == pc.npos) return std::string(pc); + auto rb = pc.find(']', lb); + if (rb == pc.npos) return std::string(pc); + auto inner = pc.substr(lb + 1, rb - lb - 1); + auto comma = inner.find(','); + if (comma == std::string_view::npos) return std::string(pc); + int off = std::stoi(std::string(inner.substr(comma + 1))); + return std::string(pc.substr(0, lb)) + "[" + std::string(inner.substr(0, comma)) + "," + std::to_string(off + 1) + "]"; +} + +static Inst decode(KVSpace &kv, std::string_view pc) { + Inst inst; + auto last = pc.rfind("/["); + if (last == pc.npos) return inst; + int addr0 = extractAddr0(pc.substr(last + 1)); + auto prefix = pc.substr(0, last); + + // opcode: [addr0, 0] + auto opk = std::string(prefix) + "[" + std::to_string(addr0) + ",0]"; + auto op = kv.get(opk); + inst.opcode = op.kind_len > 0 ? std::string(op.kind, op.kind_len) : ""; + if (inst.opcode == "string" && op.raw_len > 0) + inst.opcode = std::string((const char *)op.raw, op.raw_len); + + // params: [addr0, -1], [-2], ... and [addr0, 1], [2], ... + for (int i = 1; i <= 128; i++) { + auto rk = std::string(prefix) + "[" + std::to_string(addr0) + ",-" + std::to_string(i) + "]"; + auto rv = kv.get(rk); + if (rv.kind_len > 0) + inst.reads.push_back({rk, std::string((const char *)rv.raw, rv.raw_len)}); + auto wk = std::string(prefix) + "[" + std::to_string(addr0) + "," + std::to_string(i) + "]"; + auto wv = kv.get(wk); + if (wv.kind_len > 0) + inst.writes.push_back({wk, std::string((const char *)wv.raw, wv.raw_len)}); + } + return inst; +} + +// ── CPU Impl ──────────────────────────────────────────────────────────────── +class CPUImpl : public CPU { + KVSpace kv; + std::string vm_id; + + std::string vtidFromPC(std::string_view pc) { + auto s = pc.substr(1); // skip / + auto v = s.find("vthread/"); + if (v == s.npos) return ""; + s = s.substr(v + 8); + auto slash = s.find('/'); + return slash == s.npos ? std::string(s) : std::string(s.substr(0, slash)); + } + + // ── native ops ─────────────────────────────────────────────────────── + int execArith(const Inst &inst, int64_t (*fn)(int64_t, int64_t)) { + if (inst.reads.size() < 2 || inst.writes.empty()) return -1; + int64_t a = 0, b = 0; + memcpy(&a, inst.reads[0].second.data(), std::min(sizeof(a), inst.reads[0].second.size())); + memcpy(&b, inst.reads[1].second.data(), std::min(sizeof(b), inst.reads[1].second.size())); + int64_t r = fn(a, b); + kv.set(inst.writes[0].first, "int64", (const uint8_t *)&r, 8); + return 0; + } + int execCmp(const Inst &inst, bool (*fn)(int64_t, int64_t)) { + if (inst.reads.size() < 2 || inst.writes.empty()) return -1; + int64_t a = 0, b = 0; + memcpy(&a, inst.reads[0].second.data(), std::min(sizeof(a), inst.reads[0].second.size())); + memcpy(&b, inst.reads[1].second.data(), std::min(sizeof(b), inst.reads[1].second.size())); + uint8_t r = fn(a, b); + kv.set(inst.writes[0].first, "bool", &r, 1); + return 0; + } + int execPrint(const Inst &inst, bool nl) { + for (auto &r : inst.reads) { printf("%s", r.second.c_str()); if (nl) printf("\n"); } + return 0; + } + +public: + CPUImpl(kvspace_t *k, std::string_view vid) : kv(k), vm_id(vid) {} + + void execute(std::string_view pc) override { + std::string cur(pc); + auto vtid = vtidFromPC(cur); + auto vt_root = "/vthread/" + vtid; + for (;;) { + auto sv = kv.get(vt_root + "/status"); + std::string status((const char *)sv.raw, sv.raw_len); + if (status == "done" || status == "error") break; + auto pv = kv.get(vt_root + "/pc"); + if (pv.kind_len == 0) break; + cur = std::string((const char *)pv.raw, pv.raw_len); + try { step(cur); } catch (...) { break; } + } + } + + void step(std::string_view pc) override { + auto vtid = vtidFromPC(pc); + auto vt_root = "/vthread/" + vtid; + auto inst = decode(kv, pc); + if (inst.opcode.empty()) { kv.setStr(vt_root + "/status", "done"); return; } + + if (inst.opcode == "add") execArith(inst, [](auto a,auto b){return a+b;}); + else if (inst.opcode == "sub") execArith(inst, [](auto a,auto b){return a-b;}); + else if (inst.opcode == "mul") execArith(inst, [](auto a,auto b){return a*b;}); + else if (inst.opcode == "div") execArith(inst, [](auto a,auto b){return b? a/b : 0;}); + else if (inst.opcode == "mod") execArith(inst, [](auto a,auto b){return b? a%b : 0;}); + else if (inst.opcode == "eq") execCmp(inst, [](auto a,auto b){return a==b;}); + else if (inst.opcode == "neq") execCmp(inst, [](auto a,auto b){return a!=b;}); + else if (inst.opcode == "lt") execCmp(inst, [](auto a,auto b){return ab;}); + else if (inst.opcode == "le") execCmp(inst, [](auto a,auto b){return a<=b;}); + else if (inst.opcode == "ge") execCmp(inst, [](auto a,auto b){return a>=b;}); + else if (inst.opcode == "print" || inst.opcode == "println") execPrint(inst, inst.opcode == "println"); + + kv.setStr(vt_root + "/pc", nextPC(pc)); + } + + bool debugger_active() const override { return false; } +}; + +// ── Factory ───────────────────────────────────────────────────────────────── +std::unique_ptr create_cpu(KVSpace &kv, std::string_view vm_id) { + (void)kv; (void)vm_id; + throw std::runtime_error("not implemented — use raw kvspace_t* overload"); +} +std::unique_ptr create_cpu(kvspace_t *kv, std::string_view vm_id) { + return std::make_unique(kv, vm_id); +} + +} // namespace kvlang::kvcpu diff --git a/kvcpu/sched.rs b/kvcpu/sched.rs index fdffe82b..07232870 100644 --- a/kvcpu/sched.rs +++ b/kvcpu/sched.rs @@ -1,2 +1 @@ -//! VThread scheduler. -pub fn schedule() { todo!("schedule") } +pub struct Scheduler; impl Scheduler { pub fn new() -> Self { Scheduler } } diff --git a/layout/layout.go b/layout/layout.go index 1793da4f..c3a52c02 100644 --- a/layout/layout.go +++ b/layout/layout.go @@ -2,19 +2,22 @@ // // 存储约定: // -// /lib/. 编译后签名(XValue kind=rwfunc) -// /lib/./[i,j] 编译后指令(XValue kind=rwir) +// /lib/./[0,0] 编译后签名(XValue kind=rwfunc, body=[nr|nw]) +// /lib/./ 命名参数→slot 指针(XValue kind=string, isptr=1) +// /lib/./[i,j] 编译后指令(XValue kind=rwir),i 从 1 开始 // /lib/./