From 8f13298252ab9912989b717ebea3128d1c4b28a0 Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Sat, 5 Sep 2026 12:47:25 +0800 Subject: [PATCH 1/4] =?UTF-8?q?runtime:=20=E5=80=9F=E7=94=A8=E8=AF=BB?= =?UTF-8?q?=E9=80=82=E9=85=8D=E2=80=94=E2=80=94=E8=AF=BB=E5=8F=82=E9=9B=B6?= =?UTF-8?q?=E6=8B=B7=E8=B4=9D=E5=8F=96=E6=8C=87=E9=92=88=E5=90=8E=E6=8B=B7?= =?UTF-8?q?=E8=B4=9D=E8=87=AA=E6=8C=81=20+=20=E9=80=90=E5=90=8D=E5=8D=95?= =?UTF-8?q?=E5=80=BC=E8=AF=BB=E5=8F=96=E4=BB=A3=20batch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - xvalue/kv/runtime:适配 kvspace 借用读(resolve=0 raw 返指针 → runtime 拷贝自持, 空值 len=0);frame member 逐名单值借用读(resolve=1 穿透 link),取代已删 kvspaceGetBatch。 - runtime-rs:ffi/engine/main/json/rwir 适配借用读;新增 networld rwir 模块。 --- layout/src/ffi.rs | 119 +++++++++++++++++++---------- runtime-rs/src/engine.rs | 126 +++++++++++-------------------- runtime-rs/src/ffi.rs | 78 ++++++++++++------- runtime-rs/src/main.rs | 11 +-- runtime-rs/src/rwir/json.rs | 2 +- runtime-rs/src/rwir/mod.rs | 2 +- runtime/include/kvlang_runtime.h | 4 + runtime/src/kv.c | 69 ++++++++--------- runtime/src/runtime.c | 16 +++- runtime/src/runtime_internal.h | 17 +++-- runtime/src/rwir.c | 69 ++++++++--------- runtime/src/xvalue.c | 4 +- 12 files changed, 272 insertions(+), 245 deletions(-) diff --git a/layout/src/ffi.rs b/layout/src/ffi.rs index 1c3d892c..5c66a634 100644 --- a/layout/src/ffi.rs +++ b/layout/src/ffi.rs @@ -17,18 +17,38 @@ pub type Handle = *mut c_void; extern "C" { fn kvspaceConnect(dsn: *const c_char) -> Handle; fn kvspaceClose(h: Handle); - fn kvspaceBytesFree(p: *mut u8, len: u32); + /// codec 产出为 frontend malloc 缓冲,调用方以 libc free 释放(无 kvspaceBytesFree)。 + fn free(p: *mut c_void); - fn kvspaceSet( + /// 借用读:*out 指向后端常驻/回收空间,调用方不得 free。resolve=1 穿透 link。 + fn kvspaceGet( h: Handle, - keys: *const *const c_char, - vals: *const u8, - lens: *const u32, - n: u32, + key: *const c_char, + resolve: c_int, + out: *mut *mut u8, + out_len: *mut u32, + ) -> c_int; + /// 就地写:key 已存在、body_len==原 body_len → 返回原 box body 偏移指针;否则非 0。 + fn kvspaceWriteInPlace( + h: Handle, + key: *const c_char, + resolve: c_int, + body_len: u32, + body: *mut *mut u8, + err: *mut c_char, + err_cap: u32, + ) -> c_int; + /// 新位置写:按 (kindexpr, body_len) 分配新 box、写 head,返回 body 偏移指针。 + fn kvspaceWriteNewPlace( + h: Handle, + key: *const c_char, + kindexpr: *const c_char, + body_len: u32, + body: *mut *mut u8, err: *mut c_char, err_cap: u32, ) -> c_int; - fn kvspaceGet(h: Handle, key: *const c_char, out: *mut *mut u8, out_len: *mut u32) -> c_int; + /// 借用枚举:*out 指向后端常驻/回收缓冲(\n 连接名),调用方不得 free。 fn kvspaceList( h: Handle, prefix: *const c_char, @@ -106,8 +126,8 @@ fn err_ret(buf: &mut [c_char; 256], ret: c_int) -> Result<(), String> { }) } -/// 调用带 (out, out_len) 输出参数的 extern fn,返回分配的字节并释放。 -fn call_alloc(f: impl FnOnce(*mut *mut u8, *mut u32) -> c_int) -> Vec { +/// codec 调用:产出 frontend malloc 缓冲,拷出后以 libc free 释放。 +fn call_codec(f: impl FnOnce(*mut *mut u8, *mut u32) -> c_int) -> Vec { let mut out: *mut u8 = std::ptr::null_mut(); let mut out_len: u32 = 0; f(&mut out, &mut out_len); @@ -115,17 +135,19 @@ fn call_alloc(f: impl FnOnce(*mut *mut u8, *mut u32) -> c_int) -> Vec { return Vec::new(); } let bytes = unsafe { std::slice::from_raw_parts(out, out_len as usize) }.to_vec(); - unsafe { kvspaceBytesFree(out, out_len) }; + unsafe { free(out as *mut c_void) }; bytes } -fn to_cstrings(ss: &[String]) -> (Vec, Vec<*const c_char>) { - let cs: Vec = ss - .iter() - .map(|s| CString::new(s.as_str()).expect("no NUL in key")) - .collect(); - let ptrs: Vec<*const c_char> = cs.iter().map(|c| c.as_ptr()).collect(); - (cs, ptrs) +/// 借用调用:*out 指向后端常驻/回收空间,拷出自持(借用只需活到本次拷贝),不 free。 +fn call_borrow(f: impl FnOnce(*mut *mut u8, *mut u32) -> c_int) -> Vec { + let mut out: *mut u8 = std::ptr::null_mut(); + let mut out_len: u32 = 0; + f(&mut out, &mut out_len); + if out.is_null() || out_len == 0 { + return Vec::new(); + } + unsafe { std::slice::from_raw_parts(out, out_len as usize) }.to_vec() } // ── 安全句柄 ───────────────────────────────────────────────────────── @@ -142,39 +164,56 @@ impl Kv { Kv { h } } + /// 写:pairs 的值为预编码 TLV;逐条解 head 取 (kindexpr, body),经 WriteNewPlace + /// 向 kvspace 要 body 偏移指针后直接写入 body 字节(新建/换 kind/换尺寸唯一原语)。 pub fn set(&mut self, pairs: &[(String, Vec)]) -> Result<(), String> { - let keys: Vec = pairs.iter().map(|(k, _)| k.clone()).collect(); - let (_cs, key_ptrs) = to_cstrings(&keys); - let mut vals: Vec = Vec::new(); - let mut lens: Vec = Vec::new(); - for (_, v) in pairs { - vals.extend_from_slice(v); - lens.push(v.len() as u32); + for (key, tlv) in pairs { + self.write_new_place(key, tlv)?; } + Ok(()) + } + + fn write_new_place(&mut self, key: &str, tlv: &[u8]) -> Result<(), String> { + let h = decode_head(tlv); + let klen = h.kindexpr.iter().position(|&b| b == 0).unwrap_or(0); + let kindexpr = CString::new(&h.kindexpr[..klen]).expect("no NUL in kindexpr"); + let ck = CString::new(key).expect("no NUL in key"); + let body_off = h.body_offset as usize; + let body_len = h.body_len.max(0) as usize; + let mut body: *mut u8 = std::ptr::null_mut(); let mut err: [c_char; 256] = [0; 256]; let ret = unsafe { - kvspaceSet( + kvspaceWriteNewPlace( self.h, - key_ptrs.as_ptr(), - vals.as_ptr(), - lens.as_ptr(), - lens.len() as u32, + ck.as_ptr(), + kindexpr.as_ptr(), + body_len as u32, + &mut body, err.as_mut_ptr(), err.len() as u32, ) }; - err_ret(&mut err, ret) + err_ret(&mut err, ret)?; + if body_len > 0 { + if body.is_null() { + return Err(format!("kvspace: WriteNewPlace null body at {key}")); + } + unsafe { + std::ptr::copy_nonoverlapping(tlv[body_off..].as_ptr(), body, body_len); + } + } + Ok(()) } - /// 单点读:None 返回空字节。 + /// 单点读(借用后拷出自持):None 返回空字节。resolve=1 穿透 link。 pub fn get_one(&mut self, key: &str) -> Vec { let c = CString::new(key).expect("no NUL in key"); - call_alloc(|out, out_len| unsafe { kvspaceGet(self.h, c.as_ptr(), out, out_len) }) + call_borrow(|out, out_len| unsafe { kvspaceGet(self.h, c.as_ptr(), 0, out, out_len) }) } pub fn list(&mut self, prefix: &str, expand_ext: bool, resolve: bool) -> Vec { let c = CString::new(prefix).expect("no NUL in prefix"); - let bytes = call_alloc(|out, out_len| unsafe { + let bytes = call_borrow(|out, out_len| unsafe { kvspaceList( self.h, c.as_ptr(), @@ -255,7 +294,7 @@ fn al_to_dims(kind: &str, array_len: i32) -> Vec { pub fn tlv_encode(kind: &str, raw: &[u8], array_len: i32) -> Vec { let ck = CString::new(kind).expect("no NUL in kind"); let dims = al_to_dims(kind, array_len); - call_alloc(|out, out_len| unsafe { + call_codec(|out, out_len| unsafe { kvspaceTlvEncode( ck.as_ptr(), raw.as_ptr(), @@ -288,7 +327,7 @@ pub fn decode_head(data: &[u8]) -> kvspaceHead_t { pub fn new_ptr(target_kindexpr: &str, target: &str) -> Vec { let ck = CString::new(target_kindexpr).expect("no NUL"); let ct = CString::new(target).expect("no NUL"); - call_alloc(|out, out_len| unsafe { kvspaceNewPtr(ck.as_ptr(), ct.as_ptr(), out, out_len) }) + call_codec(|out, out_len| unsafe { kvspaceNewPtr(ck.as_ptr(), ct.as_ptr(), out, out_len) }) } pub fn new_char(kind: &str, s: &str) -> Vec { @@ -305,7 +344,7 @@ pub fn new_char(kind: &str, s: &str) -> Vec { }; let ck = CString::new(kind).expect("no NUL"); let dims = [n]; - call_alloc(|out, out_len| unsafe { + call_codec(|out, out_len| unsafe { kvspaceTlvEncode( ck.as_ptr(), raw.as_ptr(), @@ -319,19 +358,19 @@ pub fn new_char(kind: &str, s: &str) -> Vec { } pub fn new_char_byte(bytes: &[u8]) -> Vec { - call_alloc(|out, out_len| unsafe { + call_codec(|out, out_len| unsafe { kvspaceNewChar(bytes.as_ptr(), bytes.len() as u32, out, out_len) }) } pub fn new_bool(v: bool) -> Vec { - call_alloc(|out, out_len| unsafe { kvspaceNewBool(v as u8, out, out_len) }) + call_codec(|out, out_len| unsafe { kvspaceNewBool(v as u8, out, out_len) }) } pub fn new_int64(v: i64) -> Vec { - call_alloc(|out, out_len| unsafe { kvspaceNewInt64(v, out, out_len) }) + call_codec(|out, out_len| unsafe { kvspaceNewInt64(v, out, out_len) }) } pub fn new_float64(v: f64) -> Vec { - call_alloc(|out, out_len| unsafe { kvspaceNewFloat64(v, out, out_len) }) + call_codec(|out, out_len| unsafe { kvspaceNewFloat64(v, out, out_len) }) } diff --git a/runtime-rs/src/engine.rs b/runtime-rs/src/engine.rs index 51e5e3f7..c0cfee31 100644 --- a/runtime-rs/src/engine.rs +++ b/runtime-rs/src/engine.rs @@ -17,41 +17,44 @@ pub struct Engine { impl Engine { // ── kvspace 读写(绝对路径,char/utf8 与 char/utf32 编解码)───────── - /// 通用 TLV 编码写入:任意 kind + body 字节 + dims(空=标量 ndim0)。 - /// set_kv 与各 rwir 的类型化输出共用,避免每种 kind 一个专用写函数。 - pub fn set_tlv_encoded(&self, key: &str, kind: &str, raw: &[u8], dims: &[i32]) { + /// 写即构造:按 (kindexpr, body) 向 kvspace 要偏移指针后直接写 body 字节—— + /// key 已存在且同 body_len → WriteInPlace(原 box 就地);否则 WriteNewPlace(新 box)。 + /// 两分支各调唯一原语、无预 encode 整条 TLV、无中转 buffer、无 free。 + fn write_construct(&self, key: &str, kindexpr: &str, body: &[u8]) { unsafe { - let (dptr, ndim) = if dims.is_empty() { - (std::ptr::null(), 0) - } else { - (dims.as_ptr(), dims.len() as i32) - }; - let (mut buf, mut len) = (null_mut(), 0u32); - kvspaceTlvEncode( - cs(kind).as_ptr(), - raw.as_ptr(), - raw.len() as u32, - dptr, - ndim, - &mut buf, - &mut len, - ); let ck = cs(key); - let keys = [ck.as_ptr()]; - let lens = [len]; + let mut bp: *mut u8 = null_mut(); let mut err = [0u8; 256]; - kvspaceSet( + let rc = kvspaceWriteInPlace( self.kv, - keys.as_ptr(), - buf, - lens.as_ptr(), + ck.as_ptr(), 1, + body.len() as u32, + &mut bp, err.as_mut_ptr() as *mut c_char, 256, ); - kvspaceBytesFree(buf, len); + if rc != 0 { + kvspaceWriteNewPlace( + self.kv, + ck.as_ptr(), + cs(kindexpr).as_ptr(), + body.len() as u32, + &mut bp, + err.as_mut_ptr() as *mut c_char, + 256, + ); + } + if !body.is_empty() && !bp.is_null() { + std::ptr::copy_nonoverlapping(body.as_ptr(), bp, body.len()); + } } } + /// 通用类型化写入:任意 kind + body 字节 + dims(空=标量 ndim0)。 + /// set_kv 与各 rwir 的类型化输出共用,避免每种 kind 一个专用写函数。 + pub fn set_tlv_encoded(&self, key: &str, kind: &str, raw: &[u8], dims: &[i32]) { + self.write_construct(key, &build_kindexpr(kind, 0, dims), raw); + } pub fn set_kv(&self, key: &str, val: &str) { let utf32: Vec = val.chars().map(|c| c as u32).collect(); let raw: Vec = utf32.iter().flat_map(|v| v.to_le_bytes()).collect(); @@ -60,7 +63,7 @@ impl Engine { pub fn get_kv(&self, key: &str) -> String { unsafe { let (mut out, mut olen) = (null_mut(), 0u32); - kvspaceGet(self.kv, cs(key).as_ptr(), &mut out, &mut olen); + kvspaceGet(self.kv, cs(key).as_ptr(), 0, &mut out, &mut olen); if out.is_null() || olen == 0 { return String::new(); } @@ -71,7 +74,7 @@ impl Engine { .to_string(); let (_, _, kind) = parse_kindexpr(&kx); let (bo, bl) = (head.body_offset as usize, head.body_len.max(0) as usize); - let s = if kind == "char/utf32" { + if kind == "char/utf32" { std::slice::from_raw_parts(out.add(bo), bl) .chunks_exact(4) .map(|c| { @@ -81,44 +84,14 @@ impl Engine { .collect() } else { String::from_utf8_lossy(std::slice::from_raw_parts(out.add(bo), bl)).into_owned() - }; - kvspaceBytesFree(out, olen); - s + } } } /// 扩展世界(@ ref=2)句柄编码写入:kind=目标完整 kindexpr(如 "[]uint8"),body=定位串。 /// 读取该 key 时由 read_at 按 body 前缀路由给对应 /lib/networld/* 兑现器还原真实字节。 pub fn set_ext_handle(&self, key: &str, target_kindexpr: &str, locator: &str) { - unsafe { - let (mut buf, mut len) = (null_mut(), 0u32); - kvspaceTlvEncodeMode( - cs(target_kindexpr).as_ptr(), - locator.as_ptr(), - locator.len() as u32, - std::ptr::null(), - 0, - 2, - 0, - 0, - &mut buf, - &mut len, - ); - let ck = cs(key); - let keys = [ck.as_ptr()]; - let lens = [len]; - let mut err = [0u8; 256]; - kvspaceSet( - self.kv, - keys.as_ptr(), - buf, - lens.as_ptr(), - 1, - err.as_mut_ptr() as *mut c_char, - 256, - ); - kvspaceBytesFree(buf, len); - } + self.write_construct(key, &format!("@{target_kindexpr}"), locator.as_bytes()); } /// 读 key 的 head,返回 (ref, body 串)。仅 ref==2 时 body 有意义(扩展句柄定位串)。 @@ -243,10 +216,8 @@ impl Engine { if out.is_null() || olen == 0 { return Vec::new(); } - let s = String::from_utf8_lossy(std::slice::from_raw_parts(out, olen as usize)) - .into_owned(); - kvspaceBytesFree(out, olen); - s.split('\n') + String::from_utf8_lossy(std::slice::from_raw_parts(out, olen as usize)) + .split('\n') .filter(|x| !x.is_empty()) .map(str::to_string) .collect() @@ -281,33 +252,28 @@ impl Engine { pub fn get_tlv(&self, key: &str) -> Vec { unsafe { let (mut out, mut olen) = (null_mut(), 0u32); - kvspaceGet(self.kv, cs(key).as_ptr(), &mut out, &mut olen); + kvspaceGet(self.kv, cs(key).as_ptr(), 0, &mut out, &mut olen); if out.is_null() || olen == 0 { return Vec::new(); } - let v = std::slice::from_raw_parts(out, olen as usize).to_vec(); - kvspaceBytesFree(out, olen); - v + std::slice::from_raw_parts(out, olen as usize).to_vec() } } + /// 写预编码 TLV:解 head 取 (kindexpr, body) 后走写即构造(新建/换 kind/换尺寸唯一原语)。 pub fn set_tlv(&self, key: &str, tlv: &[u8]) { if tlv.is_empty() { return; } unsafe { - let ck = cs(key); - let keys = [ck.as_ptr()]; - let lens = [tlv.len() as u32]; - let mut err = [0u8; 256]; - kvspaceSet( - self.kv, - keys.as_ptr(), - tlv.as_ptr(), - lens.as_ptr(), - 1, - err.as_mut_ptr() as *mut c_char, - 256, - ); + let mut head = KvspaceHead::default(); + if kvspaceDecodeHead(tlv.as_ptr(), tlv.len() as u32, &mut head) != 0 { + return; + } + let kx = String::from_utf8_lossy(&head.kindexpr) + .trim_end_matches('\0') + .to_string(); + let (bo, bl) = (head.body_offset as usize, head.body_len.max(0) as usize); + self.write_construct(key, &kx, &tlv[bo..bo + bl]); } } diff --git a/runtime-rs/src/ffi.rs b/runtime-rs/src/ffi.rs index 5384144f..ecd96431 100644 --- a/runtime-rs/src/ffi.rs +++ b/runtime-rs/src/ffi.rs @@ -27,6 +27,30 @@ impl Default for KvspaceHead { } } +/// 构造 kindexpr(parse_kindexpr 的逆):ref 前缀(1→'*'/2→'@')+ [dims] + base kind。 +/// 与 frontend.c::build_kindexpr byte-identical。 +pub fn build_kindexpr(kind: &str, r: i32, dims: &[i32]) -> String { + let mut s = String::new(); + match r { + 1 => s.push('*'), + 2 => s.push('@'), + _ => {} + } + if !dims.is_empty() { + s.push('['); + s.push_str( + &dims + .iter() + .map(|d| d.to_string()) + .collect::>() + .join(","), + ); + s.push(']'); + } + s.push_str(kind); + s +} + /// 解析 kindexpr 内容 → (ref, dims, base kind)。 pub fn parse_kindexpr(kx: &str) -> (i32, Vec, String) { let (r, rest) = match kx.as_bytes().first() { @@ -71,27 +95,33 @@ unsafe extern "C" { err: *mut c_char, err_cap: u32, ) -> c_int; - pub fn kvspaceBytesFree(p: *mut u8, len: u32); - pub fn kvspaceSet( - h: *mut c_void, - keys: *const *const c_char, - vals: *const u8, - lens: *const u32, - n: u32, - err: *mut c_char, - err_cap: u32, - ) -> c_int; + /// 借用读:*out 指向后端常驻/回收空间,调用方不得 free。resolve=1 穿透 link。 pub fn kvspaceGet( h: *mut c_void, key: *const c_char, + resolve: c_int, out: *mut *mut u8, out_len: *mut u32, ) -> c_int; - pub fn kvspaceNewChar( - bytes: *const u8, - len: u32, - out: *mut *mut u8, - out_len: *mut u32, + /// 就地写:key 已存在、body_len==原 body_len → 返回原 box body 偏移指针;否则非 0。 + pub fn kvspaceWriteInPlace( + h: *mut c_void, + key: *const c_char, + resolve: c_int, + body_len: u32, + body: *mut *mut u8, + err: *mut c_char, + err_cap: u32, + ) -> c_int; + /// 新位置写:按 (kindexpr, body_len) 分配新 box、写 head,返回 body 偏移指针。 + pub fn kvspaceWriteNewPlace( + h: *mut c_void, + key: *const c_char, + kindexpr: *const c_char, + body_len: u32, + body: *mut *mut u8, + err: *mut c_char, + err_cap: u32, ) -> c_int; pub fn kvspaceDecodeHead(data: *const u8, data_len: u32, out: *mut KvspaceHead) -> c_int; pub fn kvspaceList( @@ -117,22 +147,12 @@ unsafe extern "C" { out: *mut *mut u8, out_len: *mut u32, ) -> c_int; - pub fn kvspaceTlvEncodeMode( - kind: *const c_char, - raw: *const u8, - raw_len: u32, - dims: *const i32, - ndim: i32, - r#ref: i32, - ro: u8, - vid: u32, - out: *mut *mut u8, - out_len: *mut u32, - ) -> c_int; // ── kvlang runtime:模式2 执行 ─────────────────────────────────── pub fn kvlangRuntimeConnect(dsn: *const c_char) -> *mut c_void; pub fn kvlangRuntimeDisconnect(rt: *mut c_void); + /// runtime 内部 kvspace 句柄——复用它而非另开连接(durable 惰性 flush 仅同句柄内相干)。 + pub fn kvlangRuntimeKvspaceHandle(rt: *mut c_void) -> *mut c_void; pub fn kvlangRuntimeBootstrap( rt: *mut c_void, funcname: *const c_char, @@ -216,8 +236,8 @@ pub fn cs(s: &str) -> CString { CString::new(s).unwrap_or_else(|_| CString::new("").unwrap()) } -/// 接管 C runtime(libc malloc)的字符串(读出后 libc::free)。 -/// 仅用于 kvlang runtime / rwirext 的返回值——kvspace(Rust)缓冲区必须走 kvspaceBytesFree。 +/// 接管 C runtime / rwirext 返回的字符串(libc malloc,读出后 libc::free)。 +/// kvspace 读为借用偏移指针(常驻空间,不 free);codec 产出为 frontend malloc(libc::free)。 pub fn take(p: *mut c_char) -> String { if p.is_null() { return String::new(); diff --git a/runtime-rs/src/main.rs b/runtime-rs/src/main.rs index 6ad37b80..176a0fef 100644 --- a/runtime-rs/src/main.rs +++ b/runtime-rs/src/main.rs @@ -261,11 +261,9 @@ fn boot(dsn: &str) -> Engine { kvlang_rs::elog!("kvlangRuntimeConnect 失败: {dsn}"); std::process::exit(1); } - let kv = unsafe { kvspaceConnect(cs(dsn).as_ptr()) }; - if kv.is_null() { - kvlang_rs::elog!("kvspaceConnect 失败: {dsn}"); - std::process::exit(1); - } + // 复用 rt 内部 kvspace 句柄,绝不另开连接——durable 惰性 flush 只在同句柄内相干, + // 跨句柄写不可见(曾致 fs/redis 外部 rwir 后 pc 陈旧、整段重跑;shm 共享 mmap 掩盖)。 + let kv = unsafe { kvlangRuntimeKvspaceHandle(rt) }; let eng = Engine { rt, kv, @@ -331,6 +329,5 @@ fn drive_vid(eng: &Engine, vid: &str) { eng.set_kv(&format!("/vthread/{sub}/\u{2025}pc"), &c); } } - - unsafe { kvspaceClose(kv) }; + // kv 属 rt(kvlangRuntimeKvspaceHandle),进程退出时随 rt 回收,此处不 close。 } diff --git a/runtime-rs/src/rwir/json.rs b/runtime-rs/src/rwir/json.rs index 45d8b81e..3da33804 100644 --- a/runtime-rs/src/rwir/json.rs +++ b/runtime-rs/src/rwir/json.rs @@ -297,7 +297,7 @@ fn tlv_encode_dims(kind: &str, raw: &[u8], dims: &[i32]) -> Vec { return Vec::new(); } let v = std::slice::from_raw_parts(out, olen as usize).to_vec(); - kvspaceBytesFree(out, olen); + libc::free(out as *mut std::ffi::c_void); v } } diff --git a/runtime-rs/src/rwir/mod.rs b/runtime-rs/src/rwir/mod.rs index b4653a4e..87c971b6 100644 --- a/runtime-rs/src/rwir/mod.rs +++ b/runtime-rs/src/rwir/mod.rs @@ -8,9 +8,9 @@ //! 不纯 rwir(llm/shell/python/byteseek·run 等)留在 byteseek,依赖本库后自行叠加。 pub mod http; -pub mod networld; pub mod json; pub mod kvlanglayout; +pub mod networld; pub mod term; use crate::engine::Engine; diff --git a/runtime/include/kvlang_runtime.h b/runtime/include/kvlang_runtime.h index 460bcfe6..8376c3c7 100644 --- a/runtime/include/kvlang_runtime.h +++ b/runtime/include/kvlang_runtime.h @@ -8,6 +8,10 @@ void kvlangRuntimeDisconnect(kvlangRuntime_t *rt); int kvlangRuntimeExecutePc(kvlangRuntime_t *rt, const char *pc); +/* runtime 内部 kvspace 句柄——runtime-rs 须复用它而非另开连接(durable 惰性 + * flush 只在同句柄内相干)。返回句柄生命周期同 rt,调用方不得 close。 */ +void *kvlangRuntimeKvspaceHandle(kvlangRuntime_t *rt); + /* 模式2(runtime 主导 + term 嵌入):分配 vthread 并 bootstrap,返回 * vid(malloc)。 term 专注这一个 vid 的 ext rwir 处理。 */ char *kvlangRuntimeBootstrap(kvlangRuntime_t *rt, const char *funcname, diff --git a/runtime/src/kv.c b/runtime/src/kv.c index 0a694a5f..2eefd031 100644 --- a/runtime/src/kv.c +++ b/runtime/src/kv.c @@ -24,16 +24,16 @@ void kvlangKvDisconnect(kvlangKv_t *k) { free(k); } +/* 借用读(resolve=0,raw)→ 拷贝为 runtime 自持。空值 → out len=0。 */ int kvlangKvGetOne(kvlangKv_t *k, const char *key, kvlangXvalue_t *out) { kvlangXvalueZero(out); uint8_t *d; uint32_t len; - if (kvspaceGet(k->h, key, &d, &len) != 0) return -1; + if (kvspaceGet(k->h, key, 0, &d, &len) != 0) return -1; kvlangXvalueCopyMalloc(out, d, len); - kvspaceBytesFree(d, len); return 0; } -/* Frame member: GetBatch(dir, name). Full-path Get does not ext-fallback on [d] frames. */ +/* Frame member: prefix 直连 name 组键,穿透 link(resolve=1)——全路径 Get(resolve=0) 不穿透 [d] 帧。 */ int kvlangKvGetMember(kvlangKv_t *k, const char *dir, const char *name, kvlangXvalue_t *out) { kvlangXvalueZero(out); if (!name || !name[0]) return 0; @@ -41,45 +41,44 @@ int kvlangKvGetMember(kvlangKv_t *k, const char *dir, const char *name, kvlangXv return kvlangKvGetBatch(k, dir, &nm, 1, out); } +/* 逐名单值借用读(key=prefix+name,resolve=1 穿透 link)→ 拷贝自持。取代已删的 kvspaceGetBatch。 */ int kvlangKvGetBatch(kvlangKv_t *k, const char *prefix, char **names, int n, kvlangXvalue_t *out) { - for (int i = 0; i < n; i++) kvlangXvalueZero(&out[i]); - const char **ns = malloc(sizeof(char *) * (size_t)n); - for (int i = 0; i < n; i++) ns[i] = names[i]; - uint8_t *d; uint32_t len; - int rc = kvspaceGetBatch(k->h, prefix, ns, (uint32_t)n, &d, &len); - free(ns); - if (rc != 0) return rc; - uint32_t off = 0; + size_t pl = strlen(prefix); for (int i = 0; i < n; i++) { - if (off + 4 > len) break; - uint32_t vl = (uint32_t)d[off] | ((uint32_t)d[off + 1] << 8) | ((uint32_t)d[off + 2] << 16) | ((uint32_t)d[off + 3] << 24); - off += 4; - if (vl > 0 && off + vl <= len) kvlangXvalueCopyMalloc(&out[i], d + off, vl); - off += vl; + kvlangXvalueZero(&out[i]); + size_t nl = strlen(names[i]); + char *key = malloc(pl + nl + 1); + memcpy(key, prefix, pl); memcpy(key + pl, names[i], nl); key[pl + nl] = 0; + uint8_t *d; uint32_t len; + if (kvspaceGet(k->h, key, 1, &d, &len) == 0 && d && len > 0) + kvlangXvalueCopyMalloc(&out[i], d, len); + free(key); } - kvspaceBytesFree(d, len); return 0; } +/* 写即构造:逐条解 head 取 (kindexpr, body)——同 body_len 就地(WriteInPlace),否则新位置 + * (WriteNewPlace)——向 kvspace 要 body 偏移指针后直接写字节,无预合并缓冲。 */ int kvlangKvSet(kvlangKv_t *k, const kvlangKvPair_t *pairs, int n, char *err, uint32_t err_cap) { - if (n <= 0) return 0; - const char **keys = malloc(sizeof(char *) * (size_t)n); - uint32_t *lens = malloc(sizeof(uint32_t) * (size_t)n); - size_t total = 0; - for (int i = 0; i < n; i++) { - keys[i] = pairs[i].key; - lens[i] = pairs[i].val.len; - total += pairs[i].val.len; - } - uint8_t *vals = malloc(total ? total : 1); - size_t off = 0; for (int i = 0; i < n; i++) { - if (pairs[i].val.len) memcpy(vals + off, pairs[i].val.data, pairs[i].val.len); - off += pairs[i].val.len; + const kvlangXvalue_t *v = &pairs[i].val; + if (!v->data || v->len == 0) { /* None → 删键,令该槽读回 None(不可静默跳过留旧值) */ + const char *dk[1] = { pairs[i].key }; + kvspaceDel(k->h, dk, 1, err, err_cap); + continue; + } + kvspaceHead_t h; + if (kvspaceDecodeHead(v->data, v->len, &h) != 0 || !h.kindexpr[0]) continue; + uint32_t body_len = h.body_len < 0 ? 0 : (uint32_t)h.body_len; + const uint8_t *body = v->data + h.body_offset; + uint8_t *dst = NULL; + if (kvspaceWriteInPlace(k->h, pairs[i].key, 1, body_len, &dst, err, err_cap) != 0) { + if (kvspaceWriteNewPlace(k->h, pairs[i].key, (const char *)h.kindexpr, body_len, &dst, err, err_cap) != 0) + return -1; + } + if (body_len > 0 && dst) memcpy(dst, body, body_len); } - int rc = kvspaceSet(k->h, keys, vals, lens, (uint32_t)n, err, err_cap); - free(keys); free(lens); free(vals); - return rc; + return 0; } int kvlangKvDel(kvlangKv_t *k, const char *key, char *err, uint32_t err_cap) { @@ -108,10 +107,9 @@ int kvlangKvList(kvlangKv_t *k, const char *prefix, bool expand_ext, bool resolv *out_names = NULL; *out_count = 0; uint8_t *d; uint32_t len; if (kvspaceList(k->h, prefix, expand_ext ? 1 : 0, resolve ? 1 : 0, &d, &len) != 0) return -1; - if (len == 0) { if (d) kvspaceBytesFree(d, len); return 0; } + if (!d || len == 0) return 0; char *s = malloc((size_t)len + 1); memcpy(s, d, len); s[len] = 0; - kvspaceBytesFree(d, len); int cnt = 1; for (uint32_t i = 0; i < len; i++) if (s[i] == '\n') cnt++; char **names = malloc(sizeof(char *) * (size_t)cnt); @@ -131,6 +129,5 @@ int kvlangKvWatch(kvlangKv_t *k, const char *key, const kvlangXvalue_t *target, uint8_t *d; uint32_t len; if (kvspaceWatch(k->h, key, t, tl, tick_ns, &d, &len) != 0) return -1; kvlangXvalueCopyMalloc(out, d, len); - kvspaceBytesFree(d, len); return 0; } diff --git a/runtime/src/runtime.c b/runtime/src/runtime.c index a83cc5ea..ba2a2192 100644 --- a/runtime/src/runtime.c +++ b/runtime/src/runtime.c @@ -25,6 +25,11 @@ int kvlangRuntimeExecutePc(kvlangRuntime_t *rt, const char *pc) { return kvlangKvcpuExecute(rt->kv, pc); } +/* runtime 与 runtime-rs 必须共用同一 kvspace 句柄——durable 惰性 flush + * 仅在同句柄内相干, 跨句柄写不可见(shm 共享 mmap + * 掩盖了此点)。暴露内部句柄,禁止调用方另开连接。 */ +void *kvlangRuntimeKvspaceHandle(kvlangRuntime_t *rt) { return rt->kv->h; } + static char *read_vthread_pc(kvlangKv_t *kv, const char *vid) { kvlangStrbuf_t key; kvlangStrbufInit(&key); @@ -69,8 +74,9 @@ static char *alloc_vtid(kvlangKv_t *kv) { return strdup(buf); } -/* 创建一个 vthread(分配 vid + 建栈索引 + bootstrap 首指令 + 置 init),返回 vid(调用方 free)。 - * 只创建不运行——运行由 kvlangRuntimeRunVid 按 vid 承接。RuntimeBootstrap/ExecuteKv/vthread·create 共用。 */ +/* 创建一个 vthread(分配 vid + 建栈索引 + bootstrap 首指令 + 置 init),返回 + * vid(调用方 free)。 只创建不运行——运行由 kvlangRuntimeRunVid 按 vid + * 承接。RuntimeBootstrap/ExecuteKv/vthread·create 共用。 */ char *kvlangVthreadSpawn(kvlangKv_t *kv, const char *funcname, const char *const *args, int nargs) { char *vtid = alloc_vtid(kv); @@ -99,7 +105,8 @@ char *kvlangRuntimeBootstrap(kvlangRuntime_t *rt, const char *funcname, return kvlangVthreadSpawn(rt->kv, funcname, args, nargs); } -/* 按 vid 从其持久化 pc 跑到结束(WATCH 模式,阻塞)。读终态:error 时把消息写 err、*ret="error"。 */ +/* 按 vid 从其持久化 pc 跑到结束(WATCH 模式,阻塞)。读终态:error 时把消息写 + * err、*ret="error"。 */ int kvlangRuntimeRunVid(kvlangKv_t *kv, const char *vid, char **ret, char *err, uint32_t err_cap) { char *pc = read_vthread_pc(kv, vid); @@ -167,5 +174,6 @@ int kvlangRuntimeExecuteKv(kvlangKv_t *kv, const char *funcname, int kvlangRuntimeExecute(kvlangRuntime_t *rt, const char *funcname, const char *const *args, int nargs, char **ret, char *err, uint32_t err_cap) { - return kvlangRuntimeExecuteKv(rt->kv, funcname, args, nargs, ret, err, err_cap); + return kvlangRuntimeExecuteKv(rt->kv, funcname, args, nargs, ret, err, + err_cap); } diff --git a/runtime/src/runtime_internal.h b/runtime/src/runtime_internal.h index 4a3e6890..7a2b7b77 100644 --- a/runtime/src/runtime_internal.h +++ b/runtime/src/runtime_internal.h @@ -20,12 +20,15 @@ typedef struct { extern void *kvspaceConnect(const char *dsn); extern void kvspaceClose(void *h); -extern void kvspaceBytesFree(uint8_t *p, uint32_t len); -extern int kvspaceSet(void *h, const char *const *keys, const uint8_t *vals, - const uint32_t *lens, uint32_t n, char *err, uint32_t err_cap); -extern int kvspaceGet(void *h, const char *key, uint8_t **out, uint32_t *out_len); -extern int kvspaceGetBatch(void *h, const char *prefix, const char *const *names, - uint32_t nnames, uint8_t **out, uint32_t *out_len); +/* 借用读:*out 指向后端常驻/回收空间,调用方不得 free。resolve=1 穿透 link。 */ +extern int kvspaceGet(void *h, const char *key, int resolve, uint8_t **out, uint32_t *out_len); +/* 就地写:key 已存在、body_len==原 body_len → 返回原 box body 偏移指针;否则非 0 + err。 */ +extern int kvspaceWriteInPlace(void *h, const char *key, int resolve, uint32_t body_len, + uint8_t **body, char *err, uint32_t err_cap); +/* 新位置写:按 (kindexpr, body_len) 分配新 box、写 head,返回 body 偏移指针。 */ +extern int kvspaceWriteNewPlace(void *h, const char *key, const char *kindexpr, uint32_t body_len, + uint8_t **body, char *err, uint32_t err_cap); +/* 借用枚举:*out 指向后端常驻/回收缓冲(\n 连接名),调用方不得 free。 */ extern int kvspaceList(void *h, const char *prefix, int expand_ext, int resolve, uint8_t **out, uint32_t *out_len); extern int kvspaceDel(void *h, const char *const *keys, uint32_t nkeys, char *err, uint32_t err_cap); @@ -87,7 +90,7 @@ static inline void kvlangStrbufFree(kvlangStrbuf_t *b) { free(b->p); b->p = NULL static inline bool kvlangXvalueNone(const kvlangXvalue_t *v) { return v->data == NULL || v->len == 0; } static inline void kvlangXvalueZero(kvlangXvalue_t *v) { v->data = NULL; v->len = 0; } -void kvlangXvalueFree(kvlangXvalue_t *v); /* kvspaceBytesFree */ +void kvlangXvalueFree(kvlangXvalue_t *v); /* free 自持 data(借用读已拷贝为自持) */ void kvlangXvalueSetBytes(kvlangXvalue_t *v, uint8_t *data, uint32_t len); /* 接管内存 */ int kvlangXvalueHead(const kvlangXvalue_t *v, kvspaceHead_t *h); /* decode head */ const char *kvlangXvalueKind(const kvlangXvalue_t *v); /* 返回 kind,None="" */ diff --git a/runtime/src/rwir.c b/runtime/src/rwir.c index ad75eb33..21ac9251 100644 --- a/runtime/src/rwir.c +++ b/runtime/src/rwir.c @@ -38,51 +38,44 @@ int kvlangRwirDecode(kvlangKv_t *kv, const char *link_base, const char *pc, kvla kvlangStrbuf_t key; kvlangStrbufInit(&key); - int nslots = 1 + 2 * MAX_PARAMS; - char **names = malloc(sizeof(char *) * (size_t)nslots); + out->reads = malloc(sizeof(kvlangParam_t) * MAX_PARAMS); + out->writes = malloc(sizeof(kvlangParam_t) * MAX_PARAMS); + out->nr = out->nw = 0; + + /* 指令槽是稠密数组:opcode 在 [addr0,0],读参 [addr0,-1..]、写参 [addr0,1..] 各自从 1 连续, + * 首个缺失槽即终止。逐槽读、遇空即停,替代每步固定读满 1+2*MAX_PARAMS 个槽——durable 后端上 + * 那些缺失槽会各触发一次祖先 ext-index 解析,放大成 syscall 风暴(prime_sieve fs/redis 超时根因)。 */ + kvlangXvalue_t v; + char *nm; + kvlangStrbufPrintf(&key, "[%d,0]", addr0); - names[0] = kvlangStrbufDetach(&key); + nm = kvlangStrbufDetach(&key); + kvlangKvGetBatch(kv, link_base, &nm, 1, &v); + free(nm); + if (!kvlangXvalueNone(&v)) out->opcode = kvlangXvalueValueString(&v); + kvlangXvalueFree(&v); + for (int i = 1; i <= MAX_PARAMS; i++) { kvlangStrbufPrintf(&key, "[%d,-%d]", addr0, i); - names[(i - 1) * 2 + 1] = kvlangStrbufDetach(&key); - kvlangStrbufPrintf(&key, "[%d,%d]", addr0, i); - names[(i - 1) * 2 + 2] = kvlangStrbufDetach(&key); + nm = kvlangStrbufDetach(&key); + kvlangKvGetBatch(kv, link_base, &nm, 1, &v); + free(nm); + if (kvlangXvalueNone(&v)) { kvlangXvalueFree(&v); break; } + out->reads[out->nr].name = kvlangXvalueValueString(&v); + out->reads[out->nr].val = v; + out->nr++; } - - kvlangXvalue_t *vals = malloc(sizeof(kvlangXvalue_t) * (size_t)nslots); - if (kvlangKvGetBatch(kv, link_base, names, nslots, vals) != 0) { - snprintf(err, err_cap, "Decode: GetBatch failed at %s", pc); - for (int i = 0; i < nslots; i++) free(names[i]); - free(names); free(vals); kvlangStrbufFree(&key); - return -1; - } - - if (!kvlangXvalueNone(&vals[0])) out->opcode = kvlangXvalueValueString(&vals[0]); - - out->reads = malloc(sizeof(kvlangParam_t) * MAX_PARAMS); - out->writes = malloc(sizeof(kvlangParam_t) * MAX_PARAMS); - out->nr = out->nw = 0; for (int i = 1; i <= MAX_PARAMS; i++) { - kvlangXvalue_t *rv = &vals[(i - 1) * 2 + 1]; - if (!kvlangXvalueNone(rv)) { - out->reads[out->nr].name = kvlangXvalueValueString(rv); - out->reads[out->nr].val = *rv; - rv->data = NULL; rv->len = 0; - out->nr++; - } - kvlangXvalue_t *wv = &vals[(i - 1) * 2 + 2]; - if (!kvlangXvalueNone(wv)) { - out->writes[out->nw].name = kvlangXvalueValueString(wv); - out->writes[out->nw].val = *wv; - wv->data = NULL; wv->len = 0; - out->nw++; - } + kvlangStrbufPrintf(&key, "[%d,%d]", addr0, i); + nm = kvlangStrbufDetach(&key); + kvlangKvGetBatch(kv, link_base, &nm, 1, &v); + free(nm); + if (kvlangXvalueNone(&v)) { kvlangXvalueFree(&v); break; } + out->writes[out->nw].name = kvlangXvalueValueString(&v); + out->writes[out->nw].val = v; + out->nw++; } - for (int i = 0; i < nslots; i++) kvlangXvalueFree(&vals[i]); - free(vals); - for (int i = 0; i < nslots; i++) free(names[i]); - free(names); kvlangStrbufFree(&key); return 0; } diff --git a/runtime/src/xvalue.c b/runtime/src/xvalue.c index d03d716c..aea7687d 100644 --- a/runtime/src/xvalue.c +++ b/runtime/src/xvalue.c @@ -60,7 +60,7 @@ static uint8_t *kvlangXvalueOwn(uint8_t *tmp, uint32_t tl, uint32_t *out_len) { if (!tmp) { *out_len = 0; return NULL; } uint8_t *buf = malloc(tl); memcpy(buf, tmp, tl); - kvspaceBytesFree(tmp, tl); + free(tmp); *out_len = tl; return buf; } @@ -338,7 +338,7 @@ void kvlangXvalueNewTlvDims(kvlangXvalue_t *v, const char *kind, const uint8_t * } uint8_t *buf = malloc(tl); memcpy(buf, tmp, tl); - kvspaceBytesFree(tmp, tl); + free(tmp); v->data = buf; v->len = tl; } From c732e0397e2108c7f6c50e1d5ea278f9599da9e0 Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Sat, 5 Sep 2026 14:45:41 +0800 Subject: [PATCH 2/4] =?UTF-8?q?runtime/layout:=200-copy=20=E8=BF=AD?= =?UTF-8?q?=E4=BB=A3=202=E2=80=94=E2=80=94listlen/listat=20=E5=8F=96?= =?UTF-8?q?=E4=BB=A3=20list=E3=80=81=E5=8E=BB=20GetBatch=20=E6=AE=8B?= =?UTF-8?q?=E7=95=99=E3=80=81in-proc=20rwir=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - layout ffi:kvspaceList → listlen+listat(前缀遍历,不一次性返整段);lower/code 格式。 - runtime:frame member 借用读走 dir 直连,删 kvlangKvGetBatch 残留;rwir 适配。 - runtime-rs:engine/ffi/json 适配 list 新 API;in-proc rwir 派发(is_inproc→dispatch)。 - deepdive runtime 05/06:runtime-rs 就地 rwir 语义 + 借用读文档同步。 --- layout/src/code.rs | 17 +- layout/src/ffi.rs | 47 +++-- layout/src/lower.rs | 7 +- layout/tests/issue116.rs | 170 ++++++++++++++---- layout/tests/pipeline_test.rs | 36 +++- runtime-rs/src/engine.rs | 50 ++++-- runtime-rs/src/ffi.rs | 83 ++++----- runtime-rs/src/rwir/json.rs | 67 ++++--- runtime/src/kv.c | 54 +++--- runtime/src/runtime_internal.h | 8 +- runtime/src/rwir.c | 6 +- .../runtime/05-rwirext-extension-runtime.kv | 25 ++- .../06-c-runtime-and-backend-abstraction.kv | 39 +++- 13 files changed, 400 insertions(+), 209 deletions(-) diff --git a/layout/src/code.rs b/layout/src/code.rs index 10f3caad..0c2d7647 100644 --- a/layout/src/code.rs +++ b/layout/src/code.rs @@ -420,7 +420,12 @@ pub fn write_func(kv: &mut Kv, pkg: &str, fn_: &mut Func) { let _ = kv.mkindex(&keytree::lib_labels_dir(pkg, &fn_.sig.name)); let lpairs: Vec<(String, Vec)> = labels .iter() - .map(|(label, irseq)| (keytree::lib_label(pkg, &fn_.sig.name, label), ffi::new_int64(*irseq as i64))) + .map(|(label, irseq)| { + ( + keytree::lib_label(pkg, &fn_.sig.name, label), + ffi::new_int64(*irseq as i64), + ) + }) .collect(); let _ = kv.set(&lpairs); } @@ -497,7 +502,10 @@ fn write_linear_inst( } _ => {} } - let target_char = if s.writes.len() == 1 && !s.write_types.is_empty() && kvkind::is_char_kind(&s.write_types[0]) { + let target_char = if s.writes.len() == 1 + && !s.write_types.is_empty() + && kvkind::is_char_kind(&s.write_types[0]) + { s.write_types[0].as_str() } else { "" @@ -508,7 +516,10 @@ fn write_linear_inst( pairs.push((format!("{prefix}/[{n},0]"), opcode_value(&opcode))); } for (j, r) in reads.iter().enumerate() { - pairs.push((format!("{prefix}/[{n},-{}]", j + 1), slot_value(r, target_char))); + pairs.push(( + format!("{prefix}/[{n},-{}]", j + 1), + slot_value(r, target_char), + )); } for (j, w) in s.writes.iter().enumerate() { pairs.push((format!("{prefix}/[{n},{}]", j + 1), slot_value(w, ""))); diff --git a/layout/src/ffi.rs b/layout/src/ffi.rs index 5c66a634..10236a17 100644 --- a/layout/src/ffi.rs +++ b/layout/src/ffi.rs @@ -48,12 +48,20 @@ extern "C" { err: *mut c_char, err_cap: u32, ) -> c_int; - /// 借用枚举:*out 指向后端常驻/回收缓冲(\n 连接名),调用方不得 free。 - fn kvspaceList( + /// 前缀遍历:listlen 定计数,逐 idx 取名(借用回收缓冲,不得 free),不一次性返回整段名单。 + fn kvspaceListLen( h: Handle, prefix: *const c_char, expand_ext: c_int, resolve: c_int, + out_count: *mut i32, + ) -> c_int; + fn kvspaceListAt( + h: Handle, + prefix: *const c_char, + expand_ext: c_int, + resolve: c_int, + idx: i32, out: *mut *mut u8, out_len: *mut u32, ) -> c_int; @@ -213,23 +221,38 @@ impl Kv { pub fn list(&mut self, prefix: &str, expand_ext: bool, resolve: bool) -> Vec { let c = CString::new(prefix).expect("no NUL in prefix"); - let bytes = call_borrow(|out, out_len| unsafe { - kvspaceList( + let mut count: i32 = 0; + if unsafe { + kvspaceListLen( self.h, c.as_ptr(), expand_ext as c_int, resolve as c_int, - out, - out_len, + &mut count, ) - }); - if bytes.is_empty() { + } != 0 + || count <= 0 + { return Vec::new(); } - String::from_utf8_lossy(&bytes) - .split('\n') - .map(|s| s.to_string()) - .collect() + let mut v = Vec::with_capacity(count as usize); + for i in 0..count { + let bytes = call_borrow(|out, out_len| unsafe { + kvspaceListAt( + self.h, + c.as_ptr(), + expand_ext as c_int, + resolve as c_int, + i, + out, + out_len, + ) + }); + if !bytes.is_empty() { + v.push(String::from_utf8_lossy(&bytes).into_owned()); + } + } + v } pub fn del_tree(&mut self, prefix: &str) -> Result<(), String> { diff --git a/layout/src/lower.rs b/layout/src/lower.rs index 0bc793b6..c562d82c 100644 --- a/layout/src/lower.rs +++ b/layout/src/lower.rs @@ -111,7 +111,12 @@ pub fn lower_func(fn_: &Func) -> Func { let mut targets = HashSet::new(); collect_goto_targets(&fn_.body, &mut targets); let body = terminate(lower_body(&fn_.body, &mut lg, None, &tm, &targets)); - Func { comments: Vec::new(), sig: fn_.sig.clone(), body, pkg: String::new() } + Func { + comments: Vec::new(), + sig: fn_.sig.clone(), + body, + pkg: String::new(), + } } fn return_inst() -> Stmt { diff --git a/layout/tests/issue116.rs b/layout/tests/issue116.rs index 01de704f..248e8525 100644 --- a/layout/tests/issue116.rs +++ b/layout/tests/issue116.rs @@ -16,7 +16,11 @@ fn sig(data: &[u8]) -> String { fn slot_text(data: &[u8]) -> String { let k = kvkind::kind(data); if k == "int64" || k == "bool" || k == "float64" { - kvkind::display(data).rsplit(':').next().unwrap_or("").to_string() + kvkind::display(data) + .rsplit(':') + .next() + .unwrap_or("") + .to_string() } else { sig(data) } @@ -161,14 +165,23 @@ fn assert_single_plane(kv: &mut Kv, fn_: &str) { }) .cloned() .collect(); - assert!(nested.is_empty(), "{fn_} nested/scope keys: {nested:?} all={children:?}"); + assert!( + nested.is_empty(), + "{fn_} nested/scope keys: {nested:?} all={children:?}" + ); let dirs: Vec<_> = children .iter() .filter(|c| c.ends_with('/') && !c.contains("labels") && parse_coord(c).is_none()) .cloned() .collect(); - assert!(dirs.is_empty(), "{fn_} extra dirs: {dirs:?} all={children:?}"); - assert!(children.iter().any(|c| c.starts_with('[')), "{fn_} has no [i,j]: {children:?}"); + assert!( + dirs.is_empty(), + "{fn_} extra dirs: {dirs:?} all={children:?}" + ); + assert!( + children.iter().any(|c| c.starts_with('[')), + "{fn_} has no [i,j]: {children:?}" + ); } fn assert_jumps_are_int64(kv: &mut Kv, fn_: &str, p: &BTreeMap) { @@ -191,7 +204,11 @@ fn assert_jumps_are_int64(kv: &mut Kv, fn_: &str, p: &BTreeMap) { ); let irseq: i32 = slot_text(&v).parse().unwrap(); assert!(irseq >= 1, "{fn_} [{n}] {} target {irseq} < 1", inst.op); - assert!(p.contains_key(&irseq), "{fn_} [{n}] {} -> missing {irseq}", inst.op); + assert!( + p.contains_key(&irseq), + "{fn_} [{n}] {} -> missing {irseq}", + inst.op + ); } } assert!(saw, "{fn_}: no goto/br in\n{}", dump_ir(p)); @@ -247,9 +264,18 @@ fn lib_plane_has_no_scope_flat_keys() { }) .cloned() .collect(); - assert!(bad.is_empty(), "{fn_} still has scope flat keys: {bad:?} all={children:?}"); - assert!(children.iter().any(|c| c.starts_with('[')), "{fn_} has no [i,j]: {children:?}"); - assert!(children.iter().any(|c| c.contains("labels")), "{fn_} missing ‥labels: {children:?}"); + assert!( + bad.is_empty(), + "{fn_} still has scope flat keys: {bad:?} all={children:?}" + ); + assert!( + children.iter().any(|c| c.starts_with('[')), + "{fn_} has no [i,j]: {children:?}" + ); + assert!( + children.iter().any(|c| c.contains("labels")), + "{fn_} missing ‥labels: {children:?}" + ); } } @@ -293,15 +319,24 @@ fn labeled_irseq_is_not_fallen_into() { continue; } let prev = irseq - 1; - let op = ops.get(&prev).unwrap_or_else(|| panic!("no opcode at [{prev},0] before label {irseq}")); - assert!(is_terminator_op(op), "irseq {prev} ({op}) falls through into labeled {irseq}"); + let op = ops + .get(&prev) + .unwrap_or_else(|| panic!("no opcode at [{prev},0] before label {irseq}")); + assert!( + is_terminator_op(op), + "irseq {prev} ({op}) falls through into labeled {irseq}" + ); } } #[test] fn br_true_false_are_int64() { let mut kv = fresh_kv(); - compile(&mut kv, "rwfunc f(X:int64) -> (Y:int64) {\n if (X > 0) { X -> Y } else { 0 -> Y }\n}\n").unwrap(); + compile( + &mut kv, + "rwfunc f(X:int64) -> (Y:int64) {\n if (X > 0) { X -> Y } else { 0 -> Y }\n}\n", + ) + .unwrap(); let children = children_of(&mut kv, "/lib/f/"); let mut saw_br = false; for c in &children { @@ -312,7 +347,12 @@ fn br_true_false_are_int64() { let t = kv.get_one(&format!("/lib/f/[{row},-2]")); let f = kv.get_one(&format!("/lib/f/[{row},-3]")); assert_eq!(kvkind::kind(&t), "int64", "br true {}", kvkind::display(&t)); - assert_eq!(kvkind::kind(&f), "int64", "br false {}", kvkind::display(&f)); + assert_eq!( + kvkind::kind(&f), + "int64", + "br false {}", + kvkind::display(&f) + ); saw_br = true; } assert!(saw_br, "no br in {children:?}"); @@ -329,18 +369,36 @@ fn abs_if_is_numbered_jumps() { eprintln!("abs IR:\n{ir}"); assert_jumps_are_int64(&mut kv, "abs", &p); assert_no_scope_opcode(&p, "abs"); - let ops: Vec<_> = p.iter().filter(|(n, _)| **n > 0).map(|(_, i)| i.op.as_str()).collect(); + let ops: Vec<_> = p + .iter() + .filter(|(n, _)| **n > 0) + .map(|(_, i)| i.op.as_str()) + .collect(); assert!(ops.contains(&"br"), "abs missing br:\n{ir}"); assert!(ops.contains(&"goto"), "abs missing goto:\n{ir}"); assert!(ops.contains(&"return"), "abs missing return:\n{ir}"); assert!(!ops.contains(&"call"), "abs if lowered to call:\n{ir}"); let labs = labels_of(&mut kv, "abs"); - assert!(labs.keys().any(|k| k.contains("_if_")), "abs labels={labs:?}"); - assert!(labs.keys().any(|k| k.contains("_then_")), "abs labels={labs:?}"); - assert!(labs.keys().any(|k| k.contains("_else_")), "abs labels={labs:?}"); - assert!(labs.keys().any(|k| k.contains("_merge_")), "abs labels={labs:?}"); + assert!( + labs.keys().any(|k| k.contains("_if_")), + "abs labels={labs:?}" + ); + assert!( + labs.keys().any(|k| k.contains("_then_")), + "abs labels={labs:?}" + ); + assert!( + labs.keys().any(|k| k.contains("_else_")), + "abs labels={labs:?}" + ); + assert!( + labs.keys().any(|k| k.contains("_merge_")), + "abs labels={labs:?}" + ); for (name, irseq) in &labs { - let inst = p.get(irseq).unwrap_or_else(|| panic!("label {name} -> {irseq} missing")); + let inst = p + .get(irseq) + .unwrap_or_else(|| panic!("label {name} -> {irseq} missing")); assert!(!inst.op.is_empty(), "label {name} -> empty op at {irseq}"); } } @@ -363,10 +421,22 @@ fn while_back_edge_is_goto_int64() { .map(|(_, v)| *v) .unwrap_or_else(|| panic!("no _while_ label: {labs:?}")); let back = p.iter().any(|(n, inst)| { - *n > 0 && inst.op == "goto" && inst.reads.first().map(|s| s.parse() == Ok(cond)).unwrap_or(false) + *n > 0 + && inst.op == "goto" + && inst + .reads + .first() + .map(|s| s.parse() == Ok(cond)) + .unwrap_or(false) }); - assert!(back, "while body has no goto {cond}:\n{ir}\nlabels={labs:?}"); - assert!(!p.values().any(|i| i.op == "call"), "while lowered to call:\n{ir}"); + assert!( + back, + "while body has no goto {cond}:\n{ir}\nlabels={labs:?}" + ); + assert!( + !p.values().any(|i| i.op == "call"), + "while lowered to call:\n{ir}" + ); } #[test] @@ -386,10 +456,21 @@ fn break_is_goto_exit() { .find(|(k, _)| k.contains("_exit_")) .map(|(_, v)| *v) .unwrap_or_else(|| panic!("no _exit_ label: {labs:?}")); - let to_exit = p.iter().filter(|(_, inst)| { - inst.op == "goto" && inst.reads.first().map(|s| s.parse() == Ok(exit)).unwrap_or(false) - }).count(); - assert!(to_exit >= 1, "break did not emit goto {exit}:\n{ir}\nlabels={labs:?}"); + let to_exit = p + .iter() + .filter(|(_, inst)| { + inst.op == "goto" + && inst + .reads + .first() + .map(|s| s.parse() == Ok(exit)) + .unwrap_or(false) + }) + .count(); + assert!( + to_exit >= 1, + "break did not emit goto {exit}:\n{ir}\nlabels={labs:?}" + ); } #[test] @@ -409,9 +490,17 @@ fn continue_is_goto_cond() { .find(|(k, _)| k.contains("_while_")) .map(|(_, v)| *v) .unwrap_or_else(|| panic!("no _while_ label: {labs:?}")); - let to_cond = p.iter().filter(|(_, inst)| { - inst.op == "goto" && inst.reads.first().map(|s| s.parse() == Ok(cond)).unwrap_or(false) - }).count(); + let to_cond = p + .iter() + .filter(|(_, inst)| { + inst.op == "goto" + && inst + .reads + .first() + .map(|s| s.parse() == Ok(cond)) + .unwrap_or(false) + }) + .count(); assert!(to_cond >= 2, "continue+latch should goto cond {cond} at least twice, got {to_cond}:\n{ir}\nlabels={labs:?}"); } @@ -427,9 +516,19 @@ fn for_is_br_goto_single_plane() { assert_jumps_are_int64(&mut kv, "f", &p); assert_no_scope_opcode(&p, "f"); let labs = labels_of(&mut kv, "f"); - assert!(labs.keys().any(|k| k.contains("_for_")), "for labels={labs:?}"); + assert!( + labs.keys().any(|k| k.contains("_for_")), + "for labels={labs:?}" + ); assert!(p.values().any(|i| i.op == "br"), "for missing br:\n{ir}"); - assert!(!p.values().any(|i| i.op == "call" && i.reads.first().map(|s| s.contains("_for_")).unwrap_or(false)), "for still calls a scope:\n{ir}"); + assert!( + !p.values().any(|i| i.op == "call" + && i.reads + .first() + .map(|s| s.contains("_for_")) + .unwrap_or(false)), + "for still calls a scope:\n{ir}" + ); } #[test] @@ -442,14 +541,21 @@ fn nested_if_while_single_plane() { assert_jumps_are_int64(&mut kv, "f", &p); assert_no_scope_opcode(&p, "f"); let brs = p.values().filter(|i| i.op == "br").count(); - assert!(brs >= 2, "nested if+while should have >=2 br, got {brs}:\n{}", dump_ir(&p)); + assert!( + brs >= 2, + "nested if+while should have >=2 br, got {brs}:\n{}", + dump_ir(&p) + ); } #[test] fn tutorial_control_files_single_plane() { for (file, fns) in [ ("../tutorial/03-control/if.kv", &["my·abs"][..]), - ("../tutorial/03-control/while.kv", &["sum_to", "first_div7", "sum_odds"][..]), + ( + "../tutorial/03-control/while.kv", + &["sum_to", "first_div7", "sum_odds"][..], + ), ("../tutorial/03-control/guess.kv", &["guess_number"][..]), ("../tutorial/03-control/classify.kv", &["classify"][..]), ("../tutorial/03-control/for.kv", &["test"][..]), diff --git a/layout/tests/pipeline_test.rs b/layout/tests/pipeline_test.rs index eab8894b..dcec290d 100644 --- a/layout/tests/pipeline_test.rs +++ b/layout/tests/pipeline_test.rs @@ -109,19 +109,35 @@ fn compile_control_flow_is_single_plane() { b.contains("_if_") || b.contains("_then_") || b.contains("_else_") || b.contains("_merge_") }); assert!(!old_scope, "scope keys still present: {children:?}"); - assert!(children.iter().any(|c| c.contains("labels")), "missing ‥labels: {children:?}"); + assert!( + children.iter().any(|c| c.contains("labels")), + "missing ‥labels: {children:?}" + ); let labels = kv.list("/lib/f/\u{2025}labels/", false, false); - let if_name = labels.iter().map(|c| c.trim_end_matches('/')).find(|c| c.contains("_if_")); + let if_name = labels + .iter() + .map(|c| c.trim_end_matches('/')) + .find(|c| c.contains("_if_")); assert!(if_name.is_some(), "‥labels={labels:?}"); let if_irseq = kv.get_one(&format!("/lib/f/\u{2025}labels/{}", if_name.unwrap())); - assert_eq!(kvkind::kind(&if_irseq), "int64", "label kind={} labels={labels:?}", kvkind::display(&if_irseq)); + assert_eq!( + kvkind::kind(&if_irseq), + "int64", + "label kind={} labels={labels:?}", + kvkind::display(&if_irseq) + ); // irseq 1 is preamble goto; target is int64 let goto_op = kv.get_one("/lib/f/[1,0]"); assert_eq!(sig(&goto_op), "goto"); let tgt = kv.get_one("/lib/f/[1,-1]"); - assert_eq!(kvkind::kind(&tgt), "int64", "goto target {}", kvkind::display(&tgt)); + assert_eq!( + kvkind::kind(&tgt), + "int64", + "goto target {}", + kvkind::display(&tgt) + ); } #[test] @@ -131,7 +147,9 @@ fn compile_while_goto_targets_are_int64() { compile(&mut kv, src).unwrap(); let children = kv.list("/lib/sum_to/", false, false); assert!( - !children.iter().any(|c| c.contains("_while_") || c.contains("_do_")), + !children + .iter() + .any(|c| c.contains("_while_") || c.contains("_do_")), "while still has scope keys: {children:?}" ); let mut saw_int_goto = false; @@ -143,7 +161,13 @@ fn compile_while_goto_targets_are_int64() { if sig(&kv.get_one(&format!("/lib/sum_to/{t}"))) != "goto" { continue; } - let row: i32 = t.trim_start_matches('[').split(',').next().unwrap().parse().unwrap(); + let row: i32 = t + .trim_start_matches('[') + .split(',') + .next() + .unwrap() + .parse() + .unwrap(); let tgt = kv.get_one(&format!("/lib/sum_to/[{row},-1]")); assert_eq!(kvkind::kind(&tgt), "int64"); saw_int_goto = true; diff --git a/runtime-rs/src/engine.rs b/runtime-rs/src/engine.rs index c0cfee31..2b1aba2a 100644 --- a/runtime-rs/src/engine.rs +++ b/runtime-rs/src/engine.rs @@ -53,7 +53,7 @@ impl Engine { /// 通用类型化写入:任意 kind + body 字节 + dims(空=标量 ndim0)。 /// set_kv 与各 rwir 的类型化输出共用,避免每种 kind 一个专用写函数。 pub fn set_tlv_encoded(&self, key: &str, kind: &str, raw: &[u8], dims: &[i32]) { - self.write_construct(key, &build_kindexpr(kind, 0, dims), raw); + self.set_tlv(key, &tlv_encode(kind, raw, dims)); } pub fn set_kv(&self, key: &str, val: &str) { let utf32: Vec = val.chars().map(|c| c as u32).collect(); @@ -72,9 +72,8 @@ impl Engine { let kx = String::from_utf8_lossy(&head.kindexpr) .trim_end_matches('\0') .to_string(); - let (_, _, kind) = parse_kindexpr(&kx); let (bo, bl) = (head.body_offset as usize, head.body_len.max(0) as usize); - if kind == "char/utf32" { + if kx.ends_with("char/utf32") { std::slice::from_raw_parts(out.add(bo), bl) .chunks_exact(4) .map(|c| { @@ -103,10 +102,11 @@ impl Engine { unsafe { let mut head = KvspaceHead::default(); kvspaceDecodeHead(tlv.as_ptr(), tlv.len() as u32, &mut head); - let kx = String::from_utf8_lossy(&head.kindexpr) - .trim_end_matches('\0') - .to_string(); - let (r, _, _) = parse_kindexpr(&kx); + let r = match head.kindexpr[0] { + b'@' => 2, + b'*' => 1, + _ => 0, + }; if r != 2 { return (r, String::new()); } @@ -172,10 +172,11 @@ impl Engine { if kvspaceDecodeHead(tlv.as_ptr(), tlv.len() as u32, &mut h) != 0 { return Vec::new(); } - let kx = String::from_utf8_lossy(&h.kindexpr) - .trim_end_matches('\0') - .to_string(); - let (r, _, _) = parse_kindexpr(&kx); + let r = match h.kindexpr[0] { + b'@' => 2, + b'*' => 1, + _ => 0, + }; let (bo, bl) = (h.body_offset as usize, h.body_len.max(0) as usize); if bo + bl > tlv.len() { return Vec::new(); @@ -209,18 +210,29 @@ impl Engine { } // ── kvspace 结构操作(json/http 扩展遍历子树用)──────────────────── + /// 前缀遍历:先 ListLen 定计数,再逐 idx ListAt 取名(借用回收缓冲,读出即自持), + /// 不经一次性整段名单缓冲。 pub fn list_kv(&self, prefix: &str) -> Vec { unsafe { - let (mut out, mut olen) = (null_mut(), 0u32); - kvspaceList(self.kv, cs(prefix).as_ptr(), 0, 0, &mut out, &mut olen); - if out.is_null() || olen == 0 { + let cp = cs(prefix); + let mut count = 0i32; + if kvspaceListLen(self.kv, cp.as_ptr(), 0, 0, &mut count) != 0 || count <= 0 { return Vec::new(); } - String::from_utf8_lossy(std::slice::from_raw_parts(out, olen as usize)) - .split('\n') - .filter(|x| !x.is_empty()) - .map(str::to_string) - .collect() + let mut v = Vec::with_capacity(count as usize); + for i in 0..count { + let (mut out, mut olen) = (null_mut(), 0u32); + if kvspaceListAt(self.kv, cp.as_ptr(), 0, 0, i, &mut out, &mut olen) == 0 + && !out.is_null() + && olen > 0 + { + v.push( + String::from_utf8_lossy(std::slice::from_raw_parts(out, olen as usize)) + .into_owned(), + ); + } + } + v } } pub fn del_tree(&self, prefix: &str) { diff --git a/runtime-rs/src/ffi.rs b/runtime-rs/src/ffi.rs index ecd96431..e6b1f77e 100644 --- a/runtime-rs/src/ffi.rs +++ b/runtime-rs/src/ffi.rs @@ -27,52 +27,30 @@ impl Default for KvspaceHead { } } -/// 构造 kindexpr(parse_kindexpr 的逆):ref 前缀(1→'*'/2→'@')+ [dims] + base kind。 -/// 与 frontend.c::build_kindexpr byte-identical。 -pub fn build_kindexpr(kind: &str, r: i32, dims: &[i32]) -> String { - let mut s = String::new(); - match r { - 1 => s.push('*'), - 2 => s.push('@'), - _ => {} - } - if !dims.is_empty() { - s.push('['); - s.push_str( - &dims - .iter() - .map(|d| d.to_string()) - .collect::>() - .join(","), +/// 类型化 TLV 编码——唯一编码入口,直委托 kvspace 正典 codec kvspaceTlvEncode(不在 Rust +/// 侧复刻 kindexpr 构造)。dims 空=标量。返回 frontend malloc 的 TLV 拷贝,随即 free。 +pub fn tlv_encode(kind: &str, raw: &[u8], dims: &[i32]) -> Vec { + unsafe { + let (mut out, mut olen) = (std::ptr::null_mut(), 0u32); + kvspaceTlvEncode( + cs(kind).as_ptr(), + raw.as_ptr(), + raw.len() as u32, + if dims.is_empty() { + std::ptr::null() + } else { + dims.as_ptr() + }, + dims.len() as i32, + &mut out, + &mut olen, ); - s.push(']'); - } - s.push_str(kind); - s -} - -/// 解析 kindexpr 内容 → (ref, dims, base kind)。 -pub fn parse_kindexpr(kx: &str) -> (i32, Vec, String) { - let (r, rest) = match kx.as_bytes().first() { - Some(b'*') => (1, &kx[1..]), - Some(b'@') => (2, &kx[1..]), - _ => (0, kx), - }; - if rest.starts_with('[') { - match rest.find(']') { - Some(end) => ( - r, - rest[1..end] - .split(',') - .filter(|d| !d.is_empty()) - .map(|d| d.parse().unwrap_or(0)) - .collect(), - rest[end + 1..].to_string(), - ), - None => (r, Vec::new(), rest.to_string()), + if out.is_null() || olen == 0 { + return Vec::new(); } - } else { - (r, Vec::new(), rest.to_string()) + let v = std::slice::from_raw_parts(out, olen as usize).to_vec(); + libc::free(out as *mut c_void); + v } } @@ -124,19 +102,22 @@ unsafe extern "C" { err_cap: u32, ) -> c_int; pub fn kvspaceDecodeHead(data: *const u8, data_len: u32, out: *mut KvspaceHead) -> c_int; - pub fn kvspaceList( + // 前缀遍历:listlen 定计数,逐 idx 取名(借用回收缓冲,不得 free),不一次性返回整段名单。 + pub fn kvspaceListLen( h: *mut c_void, prefix: *const c_char, expand_ext: c_int, resolve: c_int, - out: *mut *mut u8, - out_len: *mut u32, + out_count: *mut i32, ) -> c_int; - pub fn kvspaceMkindex( + pub fn kvspaceListAt( h: *mut c_void, - path: *const c_char, - err: *mut c_char, - err_cap: u32, + prefix: *const c_char, + expand_ext: c_int, + resolve: c_int, + idx: i32, + out: *mut *mut u8, + out_len: *mut u32, ) -> c_int; pub fn kvspaceTlvEncode( kind: *const c_char, diff --git a/runtime-rs/src/rwir/json.rs b/runtime-rs/src/rwir/json.rs index 3da33804..c467fa73 100644 --- a/runtime-rs/src/rwir/json.rs +++ b/runtime-rs/src/rwir/json.rs @@ -239,12 +239,12 @@ fn value_to_tlv(v: &Value) -> Vec { match v { Value::Number(n) => { if let Some(i) = n.as_i64() { - tlv_encode("int64", &i.to_le_bytes(), 1) + tlv_encode("int64", &i.to_le_bytes(), &[]) } else { - tlv_encode("float64", &n.as_f64().unwrap_or(0.0).to_le_bytes(), 1) + tlv_encode("float64", &n.as_f64().unwrap_or(0.0).to_le_bytes(), &[]) } } - Value::Bool(b) => tlv_encode("bool", &[*b as u8], 1), + Value::Bool(b) => tlv_encode("bool", &[*b as u8], &[]), Value::String(s) => new_char_byte(s.as_bytes()), _ => Vec::new(), } @@ -256,49 +256,42 @@ fn value_to_tlv(v: &Value) -> Vec { fn mk_mem_index(names: &[String]) -> Vec { let mut body = (names.len() as u32).to_le_bytes().to_vec(); body.extend_from_slice(names.join("\n").as_bytes()); - tlv_encode(KIND_INDEX, &body, 1) + tlv_encode(KIND_INDEX, &body, &[]) } // object 容器值 p:body 空。 fn mk_obj_value() -> Vec { - tlv_encode(KIND_OBJ, &[], 1) + tlv_encode(KIND_OBJ, &[], &[]) } // stringkeymap 容器值 p:body 空,dims=[n](恒一维坐标段)。 fn mk_map_value(n: usize) -> Vec { - tlv_encode_dims(KIND_MAP, &[], &[n as i32]) + tlv_encode(KIND_MAP, &[], &[n as i32]) } -// ── TLV 编码(权威 kvspace ABI)─────────────────────────────────── +// ── kindexpr 串解析(反序列化按类型/形状分发用;kvspace 未导出串解析器)───────── -fn tlv_encode(kind: &str, raw: &[u8], arr_len: usize) -> Vec { - let dims = [arr_len as i32]; - let ds: &[i32] = if arr_len > 1 { &dims } else { &[] }; - tlv_encode_dims(kind, raw, ds) -} - -fn tlv_encode_dims(kind: &str, raw: &[u8], dims: &[i32]) -> Vec { - unsafe { - let (mut out, mut olen) = (std::ptr::null_mut(), 0u32); - kvspaceTlvEncode( - cs(kind).as_ptr(), - raw.as_ptr(), - raw.len() as u32, - if dims.is_empty() { - std::ptr::null() - } else { - dims.as_ptr() - }, - dims.len() as i32, - &mut out, - &mut olen, - ); - if out.is_null() || olen == 0 { - return Vec::new(); +fn parse_kindexpr(kx: &str) -> (i32, Vec, String) { + let (r, rest) = match kx.as_bytes().first() { + Some(b'*') => (1, &kx[1..]), + Some(b'@') => (2, &kx[1..]), + _ => (0, kx), + }; + if rest.starts_with('[') { + match rest.find(']') { + Some(end) => ( + r, + rest[1..end] + .split(',') + .filter(|d| !d.is_empty()) + .map(|d| d.parse().unwrap_or(0)) + .collect(), + rest[end + 1..].to_string(), + ), + None => (r, Vec::new(), rest.to_string()), } - let v = std::slice::from_raw_parts(out, olen as usize).to_vec(); - libc::free(out as *mut std::ffi::c_void); - v + } else { + (r, Vec::new(), rest.to_string()) } } @@ -308,7 +301,7 @@ fn new_char_byte(bytes: &[u8]) -> Vec { .map(|c| c as u32) .collect(); let raw: Vec = utf32.iter().flat_map(|v| v.to_le_bytes()).collect(); - tlv_encode_dims("char/utf32", &raw, &[utf32.len() as i32]) + tlv_encode("char/utf32", &raw, &[utf32.len() as i32]) } #[cfg(test)] @@ -321,7 +314,9 @@ mod tests { for v in vals { raw.extend_from_slice(&v.to_le_bytes()); } - tlv_encode("int64", &raw, vals.len()) + let d = [vals.len() as i32]; + let ds: &[i32] = if vals.len() > 1 { &d } else { &[] }; + tlv_encode("int64", &raw, ds) } fn test_engine() -> Engine { diff --git a/runtime/src/kv.c b/runtime/src/kv.c index 2eefd031..91122dd1 100644 --- a/runtime/src/kv.c +++ b/runtime/src/kv.c @@ -33,27 +33,18 @@ int kvlangKvGetOne(kvlangKv_t *k, const char *key, kvlangXvalue_t *out) { return 0; } -/* Frame member: prefix 直连 name 组键,穿透 link(resolve=1)——全路径 Get(resolve=0) 不穿透 [d] 帧。 */ +/* Frame member: dir 直连 name 组键,借用读(resolve=1 穿透 link,全路径 Get(resolve=0) 不穿透 [d] 帧) + * → 拷贝自持。空值 → out len=0。 */ int kvlangKvGetMember(kvlangKv_t *k, const char *dir, const char *name, kvlangXvalue_t *out) { kvlangXvalueZero(out); if (!name || !name[0]) return 0; - char *nm = (char *)name; - return kvlangKvGetBatch(k, dir, &nm, 1, out); -} - -/* 逐名单值借用读(key=prefix+name,resolve=1 穿透 link)→ 拷贝自持。取代已删的 kvspaceGetBatch。 */ -int kvlangKvGetBatch(kvlangKv_t *k, const char *prefix, char **names, int n, kvlangXvalue_t *out) { - size_t pl = strlen(prefix); - for (int i = 0; i < n; i++) { - kvlangXvalueZero(&out[i]); - size_t nl = strlen(names[i]); - char *key = malloc(pl + nl + 1); - memcpy(key, prefix, pl); memcpy(key + pl, names[i], nl); key[pl + nl] = 0; - uint8_t *d; uint32_t len; - if (kvspaceGet(k->h, key, 1, &d, &len) == 0 && d && len > 0) - kvlangXvalueCopyMalloc(&out[i], d, len); - free(key); - } + size_t dl = strlen(dir), nl = strlen(name); + char *key = malloc(dl + nl + 1); + memcpy(key, dir, dl); memcpy(key + dl, name, nl); key[dl + nl] = 0; + uint8_t *d; uint32_t len; + if (kvspaceGet(k->h, key, 1, &d, &len) == 0 && d && len > 0) + kvlangXvalueCopyMalloc(out, d, len); + free(key); return 0; } @@ -105,20 +96,19 @@ int kvlangKvDelExtIndex(kvlangKv_t *k, const char *path, char *err, uint32_t err int kvlangKvList(kvlangKv_t *k, const char *prefix, bool expand_ext, bool resolve, char ***out_names, int *out_count) { *out_names = NULL; *out_count = 0; - uint8_t *d; uint32_t len; - if (kvspaceList(k->h, prefix, expand_ext ? 1 : 0, resolve ? 1 : 0, &d, &len) != 0) return -1; - if (!d || len == 0) return 0; - char *s = malloc((size_t)len + 1); - memcpy(s, d, len); s[len] = 0; - int cnt = 1; - for (uint32_t i = 0; i < len; i++) if (s[i] == '\n') cnt++; - char **names = malloc(sizeof(char *) * (size_t)cnt); - int idx = 0; - char *save = NULL; - for (char *tok = strtok_r(s, "\n", &save); tok; tok = strtok_r(NULL, "\n", &save)) - names[idx++] = strdup(tok); - free(s); - *out_names = names; *out_count = idx; + int ex = expand_ext ? 1 : 0, rs = resolve ? 1 : 0; + int32_t count = 0; + if (kvspaceListLen(k->h, prefix, ex, rs, &count) != 0) return -1; + if (count <= 0) return 0; + char **names = malloc(sizeof(char *) * (size_t)count); + for (int32_t i = 0; i < count; i++) { + uint8_t *d = NULL; uint32_t len = 0; + if (kvspaceListAt(k->h, prefix, ex, rs, i, &d, &len) == 0 && d) + names[i] = strndup((const char *)d, len); + else + names[i] = strdup(""); + } + *out_names = names; *out_count = (int)count; return 0; } diff --git a/runtime/src/runtime_internal.h b/runtime/src/runtime_internal.h index 7a2b7b77..6ad89548 100644 --- a/runtime/src/runtime_internal.h +++ b/runtime/src/runtime_internal.h @@ -28,9 +28,10 @@ extern int kvspaceWriteInPlace(void *h, const char *key, int resolve, uint32_t /* 新位置写:按 (kindexpr, body_len) 分配新 box、写 head,返回 body 偏移指针。 */ extern int kvspaceWriteNewPlace(void *h, const char *key, const char *kindexpr, uint32_t body_len, uint8_t **body, char *err, uint32_t err_cap); -/* 借用枚举:*out 指向后端常驻/回收缓冲(\n 连接名),调用方不得 free。 */ -extern int kvspaceList(void *h, const char *prefix, int expand_ext, int resolve, - uint8_t **out, uint32_t *out_len); +/* 前缀遍历:listlen 定计数,逐 idx 取名(借用回收缓冲,不得 free),不一次性返回整段名单。 */ +extern int kvspaceListLen(void *h, const char *prefix, int expand_ext, int resolve, int32_t *out_count); +extern int kvspaceListAt(void *h, const char *prefix, int expand_ext, int resolve, int32_t idx, + uint8_t **out, uint32_t *out_len); extern int kvspaceDel(void *h, const char *const *keys, uint32_t nkeys, char *err, uint32_t err_cap); extern int kvspaceDelTree(void *h, const char *prefix, char *err, uint32_t err_cap); extern int kvspaceMkindex(void *h, const char *path, char *err, uint32_t err_cap); @@ -135,7 +136,6 @@ void kvlangFormatFloat(char *out, size_t cap, double v); kvlangKv_t *kvlangKvConnect(const char *dsn); void kvlangKvDisconnect(kvlangKv_t *k); int kvlangKvGetOne(kvlangKv_t *k, const char *key, kvlangXvalue_t *out); /* None → out len=0 */ -int kvlangKvGetBatch(kvlangKv_t *k, const char *prefix, char **names, int n, kvlangXvalue_t *out); int kvlangKvGetMember(kvlangKv_t *k, const char *dir, const char *name, kvlangXvalue_t *out); int kvlangKvSet(kvlangKv_t *k, const kvlangKvPair_t *pairs, int n, char *err, uint32_t err_cap); int kvlangKvDel(kvlangKv_t *k, const char *key, char *err, uint32_t err_cap); diff --git a/runtime/src/rwir.c b/runtime/src/rwir.c index 21ac9251..b34dd79f 100644 --- a/runtime/src/rwir.c +++ b/runtime/src/rwir.c @@ -50,7 +50,7 @@ int kvlangRwirDecode(kvlangKv_t *kv, const char *link_base, const char *pc, kvla kvlangStrbufPrintf(&key, "[%d,0]", addr0); nm = kvlangStrbufDetach(&key); - kvlangKvGetBatch(kv, link_base, &nm, 1, &v); + kvlangKvGetMember(kv, link_base, nm, &v); free(nm); if (!kvlangXvalueNone(&v)) out->opcode = kvlangXvalueValueString(&v); kvlangXvalueFree(&v); @@ -58,7 +58,7 @@ int kvlangRwirDecode(kvlangKv_t *kv, const char *link_base, const char *pc, kvla for (int i = 1; i <= MAX_PARAMS; i++) { kvlangStrbufPrintf(&key, "[%d,-%d]", addr0, i); nm = kvlangStrbufDetach(&key); - kvlangKvGetBatch(kv, link_base, &nm, 1, &v); + kvlangKvGetMember(kv, link_base, nm, &v); free(nm); if (kvlangXvalueNone(&v)) { kvlangXvalueFree(&v); break; } out->reads[out->nr].name = kvlangXvalueValueString(&v); @@ -68,7 +68,7 @@ int kvlangRwirDecode(kvlangKv_t *kv, const char *link_base, const char *pc, kvla for (int i = 1; i <= MAX_PARAMS; i++) { kvlangStrbufPrintf(&key, "[%d,%d]", addr0, i); nm = kvlangStrbufDetach(&key); - kvlangKvGetBatch(kv, link_base, &nm, 1, &v); + kvlangKvGetMember(kv, link_base, nm, &v); free(nm); if (kvlangXvalueNone(&v)) { kvlangXvalueFree(&v); break; } out->writes[out->nw].name = kvlangXvalueValueString(&v); diff --git a/stdlib/kvlang/deepdive/runtime/05-rwirext-extension-runtime.kv b/stdlib/kvlang/deepdive/runtime/05-rwirext-extension-runtime.kv index ba6d1ff9..c20f0668 100644 --- a/stdlib/kvlang/deepdive/runtime/05-rwirext-extension-runtime.kv +++ b/stdlib/kvlang/deepdive/runtime/05-rwirext-extension-runtime.kv @@ -95,7 +95,18 @@ kvlang runtime(C) 扩展运行时 `isothersrwir(kv, opcode)`:`opcode[0]=='/' → false`;否则读 `/lib/`,kind==`defrwir` → true。 `handoff_external_rwir`:Set 共享队列、Watch 直到消费完成(30s 超时),不推进 PC。 -Rust runtime(runtime-rs)中,就地 rwir(print/json/http)直接在 `drive_vid` 驱动循环里批处理,外部 rwir(如 numpy)通过 `kvlang_rwirextHandoff` 交给独立进程。 +Rust runtime(runtime-rs)中,**就地 rwir**直接在 `run_vid` 驱动循环里派发(`rwir::is_inproc(op)` 命中 → `rwir::dispatch`),无 handoff、无 watch、与中央 runtime 同进程。当前就地集合(`rwir/mod.rs::is_inproc`): + +``` +term print / println / cerr / input +json json·to / json·from +http http·call +layout kvlanglayout·vet / ·format / ·layout / ·dump +networld networld/proc·exec + networld/fs·size / ·read / ·write / ·append / ·list / ·del / ·mkdir / ·exists +``` + +非就地 rwir(如 numpy tensor 计算)经 `kvlang_rwirextHandoff` 移交独立进程扩展。**注意**:就地与独立进程只是**执行位置**之别,职责线一致——两者都只经 `kvlang_rwirext*` 解析参数、只经 `kvspace*` 正典 ABI 读写与编解码(详见 [[runtime篇-06-C运行时与后端抽象]] §七)。 ## 五、标杆:term 与 json @@ -115,5 +126,17 @@ Rust runtime(runtime-rs)中,就地 rwir(print/json/http)直接在 `dri | `/ext/` | 在哪、怎么执行(拓扑) | 存储/计算/通信注册 | 无状态扩展运行时(term/json)只写 `/lib/`;有状态扩展引擎(op-gpu/heap-plat)写 `/lib/` + `/ext/`。handoff 协议一致,后者多一次 `/ext/` 查表定位引擎。 + +## 七、扩展的职责与不可越界 + +扩展回答的唯一问题是「**算什么、怎么算**」——tensor 怎么乘、LLM 怎么调、JSON 怎么按类型分发写回子树。它**不回答**「值长什么样、指令怎么解码、类型怎么校验」——这些是 runtime-c 的值语义,扩展只**调用**、不**复刻**。 + +扩展只有三个合法动作,一步都不许多做: + +1. **拿 PC**:从 handoff 队列 / drive 循环取当前 PC,跑己方 rwir,`NextPc` 求后继。 +2. **解析参数**:**只经** `kvlang_rwirext*`(`Params` / `ResolveRead` / `ResolveReadPath` / `ResolveWrite`)。不自行解析 rwir 槽位。 +3. **读写 kvspace**:**只经** `kvspace*` 正典 ABI(`Get` 借用读、`WriteInPlace`/`WriteNewPlace` 写、`TlvEncode`/`DecodeHead` 编解码)。**绝不自造 kindexpr 构造器、绝不手写 TLV/head。** + +**曾越界、已拉回**:runtime-rs 一度有 Rust 版 `build_kindexpr`(与 `frontend.c` 逐字节等价)——对编解码器的复刻,违反职责线,**已删**,编码统一走 `kvspaceTlvEncode`。唯一保留的例外是 json 反序列化里的 kindexpr 串**解析器**(`json.rs::parse_kindexpr`):kvspace 只导出编码器不导出串解析器,而「按类型/形状把外部文本分发写回」是扩展**自身的反序列化职责**,故留在扩展侧,且只读 head、不造 head。runtime-c 侧的权威边界见 [[runtime篇-06-C运行时与后端抽象]] §七。 "# -> /lib/kvlang/deepdive/runtime/05_rwirext_extension_runtime } diff --git a/stdlib/kvlang/deepdive/runtime/06-c-runtime-and-backend-abstraction.kv b/stdlib/kvlang/deepdive/runtime/06-c-runtime-and-backend-abstraction.kv index 75ce4dda..c9ae8418 100644 --- a/stdlib/kvlang/deepdive/runtime/06-c-runtime-and-backend-abstraction.kv +++ b/stdlib/kvlang/deepdive/runtime/06-c-runtime-and-backend-abstraction.kv @@ -16,9 +16,9 @@ kvlang 是**小 runtime 大扩展**架构。中央 runtime 只实现最核心的 **两组对外 C ABI**: - `kvlang_runtime.h`(`kvlangRuntimeConnect/Disconnect/Execute/Bootstrap/...`):核心执行器 -- `kvlang_rwirext.h`:扩展 runtime 语义,**只暴露 kvspace 不提供的部分**。KV 存取(connect/get/set/del/list/tlv)**不在此**——扩展宿主自己连 kvspace ABI,把 kvspace 句柄(`void *`)传进带句柄的 rwirext 函数。 +- `kvlang_rwirext.h`:扩展 runtime 语义,**只暴露 kvspace 不提供的部分**。KV 存取(connect/get/write/del/list/tlv)**不在此**——扩展宿主自己连 kvspace ABI,把 kvspace 句柄(`void *`)传进带句柄的 rwirext 函数。 -`kvlang_rwirext.h` 现有 9 个符号:`Register`(写 `/lib/` 签名)、`PrintLine`(print/println/cerr resolve+display)、`NextPc`(纯函数,下一条 PC)、`Params`(opcode + 读/写参名,\n 分隔)、`ResolveRead`/`ResolveReadPath`/`ResolveWrite`(读/写参解析,`ResolveReadPath` 供零拷贝读整块 ndarray)、`TypeValid`/`TypeMatch`(签名类型表达式校验与值判定)。 +`kvlang_rwirext.h` 现有 9 个符号:`Register`(写 `/lib/` 签名)、`Handoff`(把非己方 rwir 移交外部执行器)、`NextPc`(纯函数,下一条 PC)、`Params`(opcode + 读/写参名,\n 分隔)、`ResolveRead`/`ResolveReadPath`/`ResolveWrite`(读/写参解析,`ResolveReadPath` 供零拷贝读整块 ndarray)、`KindexprValid`/`KindexprMatch`(签名类型表达式校验与值判定)。**print/println/cerr 不在此**——term 扩展与中央 runtime 同进程,在 drive 循环里就地 `ResolveRead` + display,直写宿主 stdout/stderr,无专用打印 ABI。 **模块**(内部实现统一 `kvlang`/`kvspace` 前缀):`xvalue.c`(TLV 编解码)、`kv.c`(kvspace ABI 封装)、`keytree.c`(路径构造)、`rwir.c`(PC 解析 + Decode)、`vthread.c`、`builtin.c`(~50 native 算子 + 静态注册表)、`kvcpu.c`(Execute 循环)、`runtime.c`(ABI 层)、`rwirext.c`(扩展 ABI,含 `isothersrwir`)。 @@ -33,16 +33,21 @@ kvlang 是**小 runtime 大扩展**架构。中央 runtime 只实现最核心的 layout 与 runtime **都不对 kvspace 做特化处理**:不 dlopen、不双后端、不解析 scheme。二者只走一套 `kvspace*` 兼容 C ABI(无下划线 camelCase): ``` -kvspaceConnect / Close / BytesFree -kvspaceSet / Get / GetBatch / List / Del / DelTree +kvspaceConnect / Close +kvspaceGet // 借用读:*out 指向后端常驻/回收空间,调用方不得 free +kvspaceWriteInPlace // key 已存在且 body_len 相等 → 返回原 box body 偏移指针 +kvspaceWriteNewPlace // 否则按 (kindexpr, body_len) 分配新 box、写 head,返回 body 指针 +kvspaceListLen / ListAt / Del / DelTree / Cp / CpTree kvspaceMkindex / MkindexExt / RmindexExt / Watch kvspaceTlvEncode / TlvEncodeMode / DecodeHead kvspaceNewPtr / NewChar / NewBool / NewInt64 / NewFloat64 ``` +**零拷贝写读模型**:读走 `kvspaceGet`——返回后端常驻内存的**借用偏移指针**,调用方只读、**不 free**(旧 `kvspaceBytesFree` 已删)。写不再有「先 encode 整条 TLV 再交后端」的 `kvspaceSet`(已删):改为向后端**要一个可写 body 偏移指针**再直接写字节——`kvspaceWriteInPlace`(key 已存在且 body 尺寸不变,原 box 就地改)与 `kvspaceWriteNewPlace`(新建 / 换 kind / 换尺寸,分配新 box 并写 head)两条原语,各自唯一、无中转 buffer。前缀枚举同样不再一次性返回整段名单缓冲(旧 `kvspaceList` 已删,连同批量读的 `kvspaceGetBatch`):`kvspaceListLen` 只返子项计数、无缓冲无需释放,`kvspaceListAt(idx)` 借用回收缓冲逐项取名——遍历即「先 ListLen 定计数、再逐 idx ListAt」,与借用读模型一致、绝不外泄大块名单。 + **后端由链接决定**:`KVLANG_KVSPACE_LIB` 环境变量在**构建期**选择链接哪个 kvspace 库(`kvspace-c` 或 `kvspace_durable`),两者导出同一 ABI。runtime 的 `kvlangKv_t` 只保留 `void *h`,代码零后端分支。 -**kvspace-c 补齐 ABI**:`kvspace-c/src/durable_abi.c` 导出完整符号(内部复用 `kvspaceShm*` + `kvspaceXvalue*`),其中 `GetBatch`(前缀批量读)与 `Watch`(轮询到值==target 或超时)为补齐的手工实现。 +**kvspace-c 补齐 ABI**:`kvspace-c/src/durable_abi.c` 导出完整符号(内部复用 `kvspaceShm*` + `kvspaceXvalue*`),其中 `Watch`(轮询到值==target 或超时)为补齐的手工实现。 ## 四、三个后端 @@ -70,12 +75,28 @@ kvspaceNewPtr / NewChar / NewBool / NewInt64 / NewFloat64 | 后端 | 结果 | |------|------| -| shm | **136 PASS / 0 FAIL** | -| redis | 134 PASS(`time.kv` delta ms RTT 时序 + `prime_sieve` 超时,非语义) | -| fs | 133 PASS(`time.kv` 文件 I/O 时序 + `prime_sieve` 超时,非语义)| +| shm | **185 PASS / 0 FAIL / 3 SKIP** | +| redis | **185 PASS / 0 FAIL / 3 SKIP** | +| fs | **185 PASS / 0 FAIL / 3 SKIP** | -Go runtime 为 136/136 baseline(`goheap://` 进程内,无 RTT/文件 I/O)。 +三后端**语义完全一致**——早期 redis/fs 上 `prime_sieve` 的超时与 `time.kv` 的 RTT/文件 I/O 时序偏差已随指令重读稠密探查修复(`RwirDecode` 遇空槽即停,不再每步固定读满 257 槽),不再有后端专属失败。3 个 SKIP 为跨进程扩展 / WIP 用例,三后端一致跳过。 **后端存储布局差异(非语义 bug)**:fs 后端(`kvspace-durable/src/fs`)把成员分隔符 `·`(OBJ_SEP)编码为 `·/`——尾中点目录 + `/` 目录边界,故限定名 `/lib/math·sum` 在 fs 上呈嵌套目录 `lib/math·/sum`,而 redis/shm 后端存为扁平单键 `/lib/math·sum`。读写经对称编解码还原(`·/`↔`·`),三后端语义一致。 + +## 七、runtime-c 的边界与扩展的职责(单一正确路径) + +「小 runtime 大扩展」不是模糊分工,而是一条**不可越界**的职责线。runtime-c 独占三件事,扩展一件都不许重做: + +**runtime-c 拥有(唯一实现,扩展不得复刻)**: +1. **执行核心**:fetch-decode-execute 大循环、call/return/br/goto、vthread 调度、native builtin 派发(~50 基础算子)、`=` 拷贝语义。 +2. **值语义与编解码**:TLV / kindexpr 的**唯一编解码器**在 kvspace 正典 codec(`kvspaceTlvEncode` / `kvspaceDecodeHead` / `kvspaceNewPtr/NewChar/NewBool/NewInt64/NewFloat64`),runtime-c 的 `xvalue.c` 只是其薄封装。kindexpr 串是类型的唯一真相,怎么拼、怎么解,只此一处。 +3. **指令解码与签名校验**:`rwir.c` 的 `RwirDecode`(稠密探查、遇空即停)、`/lib/` 签名的读写码规范、`KindexprValid` / `KindexprMatch` 类型判定。 + +**扩展只许做三件事(无论就地 rwir 还是独立进程)**: +1. **拿 PC**:经 handoff / drive 循环从 runtime 拿到当前 PC 与后继 PC(`NextPc`),连续跑己方 rwir。 +2. **解析参数**:**只经 `kvlang_rwirext*` 宿主 ABI**(`Params` / `ResolveRead` / `ResolveReadPath` / `ResolveWrite`)拿读/写参名与值。不许自行解析 rwir 槽位。 +3. **读写 kvspace**:**只经上文的 `kvspace*` 正典 ABI**。要落一个类型化值,走 `kvspaceTlvEncode` + `WriteInPlace/WriteNewPlace`;**绝不自造 kindexpr 构造器、绝不手写 TLV/head**。 + +**一条曾越界、已拉回的实例**:runtime-rs 早期在 Rust 侧有一份 `build_kindexpr`,与 `frontend.c` 逐字节等价——这是对编解码器的复刻,违反职责线,**已删除**,所有编码统一路由到 `kvspaceTlvEncode`(见 `ffi.rs::tlv_encode` 注释「唯一编码入口,不在 Rust 侧复刻 kindexpr 构造」)。**唯一保留的例外**是 json 扩展反序列化里的 kindexpr 串**解析器**(`json.rs::parse_kindexpr`):kvspace 只导出编码器、不导出串解析器,而「把外部 JSON 文本按类型/形状分发写回子树」是扩展**自己的反序列化职责**,不属于 runtime-c 的值语义——故这份解析留在扩展侧,且只读 head 不造 head。这条例外恰恰印证边界:**能调用 kvspace 的,一律调用;kvspace 没提供、且属扩展自身语义的,才自理**。 "# -> /lib/kvlang/deepdive/runtime/06_c_runtime_and_backend_abstraction } From e7585a185878d2aa76743acdc4c5fef6cb55e4d4 Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Sat, 5 Sep 2026 15:11:46 +0800 Subject: [PATCH 3/4] =?UTF-8?q?numpy:=20=E9=80=82=E9=85=8D=E5=80=9F?= =?UTF-8?q?=E7=94=A8=E8=AF=BB/listlen=20API=EF=BC=880-copy=20=E8=BF=AD?= =?UTF-8?q?=E4=BB=A3=203=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- runtime-rwirext_example/py/numpy/numpy.py | 37 +++++++++++++---------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/runtime-rwirext_example/py/numpy/numpy.py b/runtime-rwirext_example/py/numpy/numpy.py index 028beb2d..f58a981f 100644 --- a/runtime-rwirext_example/py/numpy/numpy.py +++ b/runtime-rwirext_example/py/numpy/numpy.py @@ -65,7 +65,7 @@ class kvlang_kindexpr_t(ctypes.Structure): def _bind(): # kvspace ABI(扩展宿主自连):句柄 = kvspaceConnect(dsn),shm 下即 ShmOpen,可直传 Shm* 零拷贝 _ks.kvspaceConnect.argtypes = [ctypes.c_char_p]; _ks.kvspaceConnect.restype = ctypes.c_void_p - _ks.kvspaceFree.argtypes = [ctypes.c_void_p] + _ks.kvspaceClose.argtypes = [ctypes.c_void_p] _ks.kvspaceShmGet.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.POINTER(ctypes.c_int32)] _ks.kvspaceShmGet.restype = ctypes.POINTER(ctypes.c_uint8) _ks.kvspaceShmSet.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint8), ctypes.c_int32] @@ -75,10 +75,14 @@ def _bind(): ctypes.POINTER(ctypes.c_int32), ctypes.c_int32, ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8)), ctypes.POINTER(ctypes.c_uint32)] _ks.kvspaceTlvEncode.restype = ctypes.c_int - _ks.kvspaceBytesFree.argtypes = [ctypes.POINTER(ctypes.c_uint8), ctypes.c_uint32] - _ks.kvspaceList.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int, - ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8)), ctypes.POINTER(ctypes.c_uint32)] - _ks.kvspaceList.restype = ctypes.c_int + # codec 产出为 frontend malloc 缓冲,拷出后以 libc free 释放(无 kvspaceBytesFree)。 + _ks.kvspaceListLen.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int, + ctypes.POINTER(ctypes.c_int32)] + _ks.kvspaceListLen.restype = ctypes.c_int + _ks.kvspaceListAt.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int, + ctypes.c_int32, ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8)), + ctypes.POINTER(ctypes.c_uint32)] + _ks.kvspaceListAt.restype = ctypes.c_int _ks.kvspaceNewChar.argtypes = [ctypes.POINTER(ctypes.c_uint8), ctypes.c_uint32, ctypes.POINTER(ctypes.POINTER(ctypes.c_uint8)), ctypes.POINTER(ctypes.c_uint32)] _ks.kvspaceNewChar.restype = ctypes.c_int @@ -195,7 +199,7 @@ def __init__(self, dsn): def close(self): if self.kv: - _ks.kvspaceFree(self.kv); self.kv = None + _ks.kvspaceClose(self.kv); self.kv = None # ── 句柄自带的 KV 存取(handoff:list todo / read todo / write pc,done / del todo)── def kv_get(self, key): @@ -215,18 +219,19 @@ def kv_set(self, key, val): if _ks.kvspaceNewChar(b, len(b), ctypes.byref(out), ctypes.byref(ol)) != 0: return _ks.kvspaceShmSet(self.kv, key.encode(), out, ol.value) - _ks.kvspaceBytesFree(out, ol.value) + _libc.free(ctypes.cast(out, ctypes.c_void_p)) + # 前缀枚举:ListLen 定计数 + 逐 idx ListAt 借用取名(借用不 free)。 def kv_list(self, prefix): - out = ctypes.POINTER(ctypes.c_uint8)(); ol = ctypes.c_uint32() - if _ks.kvspaceList(self.kv, prefix.encode(), 0, 0, ctypes.byref(out), ctypes.byref(ol)) != 0 \ - or not out or ol.value == 0: - if out: - _ks.kvspaceBytesFree(out, ol.value) + count = ctypes.c_int32() + if _ks.kvspaceListLen(self.kv, prefix.encode(), 0, 0, ctypes.byref(count)) != 0 or count.value <= 0: return [] - s = ctypes.string_at(out, ol.value).decode("utf-8", "replace") - _ks.kvspaceBytesFree(out, ol.value) - return s.split("\n") if s else [] + names = [] + for i in range(count.value): + out = ctypes.POINTER(ctypes.c_uint8)(); ol = ctypes.c_uint32() + if _ks.kvspaceListAt(self.kv, prefix.encode(), 0, 0, i, ctypes.byref(out), ctypes.byref(ol)) == 0 and out: + names.append(ctypes.string_at(out, ol.value).decode("utf-8", "replace")) + return names def kv_del(self, key): keys = (ctypes.c_char_p * 1)(key.encode()) @@ -270,7 +275,7 @@ def alloc(self, key, arr): dims, ndim, ctypes.byref(out), ctypes.byref(ol)) != 0: raise RuntimeError(f"tlv_encode failed: {key}") _ks.kvspaceShmSet(self.kv, key.encode(), out, ol.value) - _ks.kvspaceBytesFree(out, ol.value) + _libc.free(ctypes.cast(out, ctypes.c_void_p)) # ── 读参:解析为帧槽路径后零拷贝 view;内联字面量回退 resolve_read ── def read_arg(self, pc, i): From 771d7458262173b9407b8e3f6c571f6f3aa28621 Mon Sep 17 00:00:00 2001 From: "peng.li24" <734991033@qq.com> Date: Sat, 5 Sep 2026 15:26:38 +0800 Subject: [PATCH 4/4] =?UTF-8?q?deps:=20=E5=9B=9B=E4=BB=93=20tag=20?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=20v0.2.6=20=E2=86=92=20v0.2.7=EF=BC=880-copy?= =?UTF-8?q?=20=E5=80=9F=E7=94=A8=E8=AF=BB=E5=8F=91=E5=B8=83=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kvlang v0.2.7 = 0-copy(借读零拷贝 + listlen/listat);kvspace-c/durable v0.2.7 同步。 --- deps.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deps.json b/deps.json index b200e65e..bb317f1d 100644 --- a/deps.json +++ b/deps.json @@ -1,7 +1,7 @@ { - "kvspace": "v0.2.6", - "kvspace-c": "v0.2.6", - "kvspace-durable": "v0.2.6", + "kvspace": "v0.2.7", + "kvspace-c": "v0.2.7", + "kvspace-durable": "v0.2.7", "blockmalloc": "v0.1.4", "slotsboxmalloc": "v0.1.5" }