diff --git a/cmd/kvlang/help.go b/cmd/kvlang/help.go index 8fe89633..7b9384db 100644 --- a/cmd/kvlang/help.go +++ b/cmd/kvlang/help.go @@ -23,7 +23,7 @@ KV 空间操作已迁至独立 CLI(kvlang-go 仓 cmd/kvspace): kvspace [--kvspace dsn] 选项: - --kvspace kvspace 地址(默认 redis://127.0.0.1:6379;KVLANG_KVSPACE 可覆盖) + --kvspace kvspace 地址(默认 redis://127.0.0.1:6379;KVSPACE 可覆盖) --debug 单步调试模式(设 .debugger="step",agent 通过 kvspace 协议控制) 示例: diff --git a/cmd/kvlang/util.go b/cmd/kvlang/util.go index bfb24127..96870739 100644 --- a/cmd/kvlang/util.go +++ b/cmd/kvlang/util.go @@ -12,14 +12,14 @@ func initDirs(kv kvspace.KVSpace) { kvspace.MkIndexRecursive(kv, "/vthread/") } -// defaultKVSpace 返回 kvspace DSN 默认值:KVLANG_KVSPACE 环境变量覆盖,否则本机 redis。 +// defaultKVSpace 返回 kvspace DSN 默认值:KVSPACE 环境变量覆盖,否则本机 redis。 func defaultKVSpace() string { - if v := os.Getenv("KVLANG_KVSPACE"); v != "" { + if v := os.Getenv("KVSPACE"); v != "" { return v } return "redis://127.0.0.1:6379" } -const kvspaceFlagDesc = "kvspace 地址(DSN,如 redis://host:port、art:// 进程内 ART;默认可由 KVLANG_KVSPACE 覆盖)" +const kvspaceFlagDesc = "kvspace 地址(DSN,如 redis://host:port、art:// 进程内 ART;默认可由 KVSPACE 覆盖)" diff --git a/deepx-design b/deepx-design index 45b76a70..81641980 160000 --- a/deepx-design +++ b/deepx-design @@ -1 +1 @@ -Subproject commit 45b76a7030a944164377c2624a0075b559032404 +Subproject commit 816419809687034050b67375688c7f30137baf31 diff --git a/layout/build.rs b/layout/build.rs index 70640d5a..fce9492c 100644 --- a/layout/build.rs +++ b/layout/build.rs @@ -1,9 +1,17 @@ -// 链接 kvspace-durable 的动态库(cdylib)。 -// 布局侧只通过 extern "C" ABI 调用 kvspace_durable,不依赖其 Rust API。 -// 前置:先在 ../kvspace-durable 执行 `cargo build --release`,产出 libkvspace_durable.so。 +// 链接 kvspace 动态库(cdylib)。布局侧只通过 extern "C" ABI 调用。 +// 默认链接 kvspace-durable;KVLANG_KVSPACE_LIB=kvspace-c 时链接 kvspace-c(SHM,durable 兼容 ABI)。 fn main() { - let lib_dir = "/home/peng.li24/github.com/array2d/kvspace-durable/target/release"; + let lib = std::env::var("KVLANG_KVSPACE_LIB").unwrap_or_else(|_| "kvspace_durable".into()); + let (lib_dir, extra_rpath) = if lib == "kvspace-c" { + ("/home/peng.li24/github.com/array2d/kvspace/build", true) + } else { + ("/home/peng.li24/github.com/array2d/kvspace-durable/target/release", false) + }; println!("cargo:rustc-link-search=native={lib_dir}"); - println!("cargo:rustc-link-lib=dylib=kvspace_durable"); + println!("cargo:rustc-link-lib=dylib={lib}"); println!("cargo:rustc-link-arg=-Wl,-rpath,{lib_dir}"); + if extra_rpath { + println!("cargo:rustc-link-arg=-Wl,-rpath,/home/peng.li24/github.com/array2d/blockmalloc/build"); + println!("cargo:rustc-link-arg=-Wl,-rpath,/home/peng.li24/github.com/array2d/slotsboxmalloc/build"); + } } diff --git a/runtime-rwirext/.gitignore b/runtime-rwirext/.gitignore new file mode 100644 index 00000000..b83d2226 --- /dev/null +++ b/runtime-rwirext/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/runtime-rwirext/Cargo.lock b/runtime-rwirext/Cargo.lock new file mode 100644 index 00000000..45b256e8 --- /dev/null +++ b/runtime-rwirext/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "kvlang-rwext-term" +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/runtime-rwirext/Cargo.toml b/runtime-rwirext/Cargo.toml new file mode 100644 index 00000000..2f73ec92 --- /dev/null +++ b/runtime-rwirext/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "kvlang-rwext-term" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +libc = "0.2" diff --git a/runtime-rwirext/Makefile b/runtime-rwirext/Makefile new file mode 100644 index 00000000..78b2b655 --- /dev/null +++ b/runtime-rwirext/Makefile @@ -0,0 +1,5 @@ +all: + cargo build --release + +clean: + cargo clean diff --git a/runtime-rwirext/build.rs b/runtime-rwirext/build.rs new file mode 100644 index 00000000..88d8180e --- /dev/null +++ b/runtime-rwirext/build.rs @@ -0,0 +1,8 @@ +fn main() { + let rt = "/home/peng.li24/github.com/array2d/kvlang/runtime/build"; + let dual = "/home/peng.li24/github.com/array2d/kvspace-durable/target/release"; + println!("cargo:rustc-link-search=native={rt}"); + println!("cargo:rustc-link-lib=dylib=kvlang_runtime"); + println!("cargo:rustc-link-arg=-Wl,-rpath,{rt}"); + println!("cargo:rustc-link-arg=-Wl,-rpath,{dual}"); +} diff --git a/runtime-rwirext/src/lib.rs b/runtime-rwirext/src/lib.rs new file mode 100644 index 00000000..95530e76 --- /dev/null +++ b/runtime-rwirext/src/lib.rs @@ -0,0 +1,135 @@ +//! term 扩展 runtime:第一个通过 C ABI(kvlang_rwext)嵌入 C runtime 的扩展。 +//! 用 Rust 实现,注册 print/println/cerr,常驻 serve 循环执行外部 rwir。 + +use std::ffi::{c_char, c_int, CStr, CString}; +use std::time::Duration; + +#[repr(C)] +struct rwext_conn { + _p: [u8; 0], +} + +unsafe impl Send for rwext_conn {} + +unsafe extern "C" { + fn rwext_connect(dsn: *const c_char) -> *mut rwext_conn; + fn rwext_disconnect(c: *mut rwext_conn); + fn rwext_register(c: *mut rwext_conn, opcode: *const c_char, nr: i32, nw: i32, sig: *const c_char) -> c_int; + fn rwext_list(c: *mut rwext_conn, prefix: *const c_char) -> *mut c_char; + fn rwext_get(c: *mut rwext_conn, key: *const c_char) -> *mut c_char; + fn rwext_set(c: *mut rwext_conn, key: *const c_char, val: *const c_char) -> c_int; + fn rwext_del(c: *mut rwext_conn, key: *const c_char) -> c_int; + fn rwext_print_line(c: *mut rwext_conn, pc: *const c_char, rawnl: *mut c_int, cerr: *mut c_int) -> *mut c_char; + fn rwext_next_pc(pc: *const c_char) -> *mut c_char; +} + +struct Op { + name: &'static str, + sig: &'static str, + nr: i32, + nw: i32, +} + +const OPS: &[Op] = &[ + Op { name: "print", sig: "rwir print(A:any, ...) -> ()", nr: 1, nw: 0 }, + Op { name: "println", sig: "rwir println(A:any, ...) -> ()", nr: 1, nw: 0 }, + Op { name: "cerr", sig: "rwir cerr(A:any, ...) -> ()", nr: 1, nw: 0 }, +]; + +fn cs(s: &str) -> CString { + CString::new(s).unwrap() +} + +fn take(p: *mut c_char) -> String { + if p.is_null() { + String::new() + } else { + let s = unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned(); + unsafe { libc::free(p as *mut libc::c_void) }; + s + } +} + +fn register(c: *mut rwext_conn) { + for op in OPS { + unsafe { + rwext_register(c, cs(op.name).as_ptr(), op.nr, op.nw, cs(op.sig).as_ptr()); + } + } +} + +fn serve_op(c: *mut rwext_conn, op: &Op) { + let base = format!("/lib/{}", op.name); + let children = take(unsafe { rwext_list(c, cs(&format!("{base}/")).as_ptr()) }); + for child in children.split('\n') { + if !child.starts_with(".todo<") || !child.ends_with('>') { + continue; + } + let vid = &child[6..child.len() - 1]; + let todo_key = format!("{base}/{child}"); + let pcid = take(unsafe { rwext_get(c, cs(&todo_key).as_ptr()) }); + let (pc, id) = match pcid.rfind('|') { + Some(i) => (&pcid[..i], &pcid[i + 1..]), + None => (pcid.as_str(), ""), + }; + + let mut cur = pc.to_string(); + loop { + let mut rawnl = 0i32; + let mut is_cerr = 0i32; + let p = unsafe { rwext_print_line(c, cs(&cur).as_ptr(), &mut rawnl, &mut is_cerr) }; + if p.is_null() { + break; + } + let line = take(p); + if is_cerr != 0 { + eprint!("{line}"); + if rawnl == 0 { + eprintln!(); + } + } else { + print!("{line}"); + if rawnl == 0 { + println!(); + } + } + cur = take(unsafe { rwext_next_pc(cs(&cur).as_ptr()) }); + } + + let vt_pc = format!("/vthread/{vid}/\u{2025}pc"); + unsafe { + rwext_set(c, cs(&vt_pc).as_ptr(), cs(&cur).as_ptr()); + let done_key = format!("{base}/.done<{vid}>"); + rwext_set(c, cs(&done_key).as_ptr(), cs(id).as_ptr()); + rwext_del(c, cs(&todo_key).as_ptr()); + } + } +} + +struct Conn(*mut rwext_conn); +unsafe impl Send for Conn {} + +fn serve(conn: Conn) { + register(conn.0); + loop { + for op in OPS { + serve_op(conn.0, op); + } + std::thread::sleep(Duration::from_millis(500)); + } +} + +#[no_mangle] +pub extern "C" fn rwext_term_start(dsn: *const c_char) { + let dsn = if dsn.is_null() { + "redis://127.0.0.1:6379".to_string() + } else { + unsafe { CStr::from_ptr(dsn) }.to_string_lossy().into_owned() + }; + let c = unsafe { rwext_connect(cs(&dsn).as_ptr()) }; + if c.is_null() { + return; + } + let conn = Conn(c); + std::thread::spawn(move || serve(conn)); +} diff --git a/runtime/.gitignore b/runtime/.gitignore new file mode 100644 index 00000000..8880c6b6 --- /dev/null +++ b/runtime/.gitignore @@ -0,0 +1,3 @@ +build/ +*.o +test/run diff --git a/runtime/Makefile b/runtime/Makefile new file mode 100644 index 00000000..5073c9d0 --- /dev/null +++ b/runtime/Makefile @@ -0,0 +1,31 @@ +CC = cc +CFLAGS = -std=gnu11 -O2 -Wall -fPIC -D_GNU_SOURCE -Iinclude -Isrc +DUAL = /home/peng.li24/github.com/array2d/kvspace-durable/target/release +KVS = /home/peng.li24/github.com/array2d/kvspace/build +BLK = /home/peng.li24/github.com/array2d/blockmalloc/build +SBO = /home/peng.li24/github.com/array2d/slotsboxmalloc/build +SRCS = src/strbuf.c src/xvalue.c src/kv.c src/keytree.c src/rwir.c \ + src/vthread.c src/logx.c src/builtin.c src/kvcpu.c src/runtime.c src/rwext.c +OBJS = $(SRCS:.c=.o) +TARGET = build/libkvlang_runtime.so + +# 链接哪个 kvspace 库由 KVLANG_KVSPACE_LIB 控制(kvspace-c 或 kvspace_durable,均导出同一 ABI) +KVLANG_KVSPACE_LIB ?= kvspace_durable +ifeq ($(KVLANG_KVSPACE_LIB), kvspace-c) + KVS_LINK = -L$(KVS) -lkvspace-c -L$(BLK) -lblockmalloc -L$(SBO) -lslotsboxmalloc \ + -Wl,-rpath,$(KVS) -Wl,-rpath,$(BLK) -Wl,-rpath,$(SBO) +else + KVS_LINK = -L$(DUAL) -lkvspace_durable -Wl,-rpath,$(DUAL) +endif + +all: $(TARGET) + +$(TARGET): $(OBJS) + mkdir -p build + $(CC) -shared -o $@ $(OBJS) $(KVS_LINK) -lm -pthread -Wl,--export-dynamic + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJS) $(TARGET) diff --git a/runtime/include/kvlang_runtime.h b/runtime/include/kvlang_runtime.h new file mode 100644 index 00000000..ee9efc4e --- /dev/null +++ b/runtime/include/kvlang_runtime.h @@ -0,0 +1,14 @@ +#pragma once +#include + +typedef struct kvlang_rt kvlang_rt; + +kvlang_rt *kvlang_rt_connect(const char *dsn); +void kvlang_rt_disconnect(kvlang_rt *rt); +void *kvlang_rt_kv(kvlang_rt *rt); /* 内部 kv 句柄,供 rwirext 扩展用 */ + +int kvlang_rt_execute_pc(kvlang_rt *rt, const char *pc); + +int kvlang_rt_execute(kvlang_rt *rt, const char *funcname, + const char *const *args, int nargs, + char **ret, char *err, uint32_t err_cap); diff --git a/runtime/include/kvlang_rwext.h b/runtime/include/kvlang_rwext.h new file mode 100644 index 00000000..ae4ac174 --- /dev/null +++ b/runtime/include/kvlang_rwext.h @@ -0,0 +1,35 @@ +#pragma once +#include + +/* kvlang 扩展 runtime ABI:供第三方语言(Rust/Python/Go)通过 C ABI 嵌入 C runtime, + * 实现自定义 rwirext(如 term 的 print)。opaque handle + C 字符串,不暴露内部结构。 */ + +typedef struct rwext_conn rwext_conn; + +rwext_conn *rwext_connect(const char *dsn); +void rwext_disconnect(rwext_conn *c); + +/* 写 /lib/ = rwir 签名(幂等) */ +int rwext_register(rwext_conn *c, const char *opcode, int32_t nr, int32_t nw, const char *sig); + +/* 列目录子项,\n 连接返回(malloc,调用方 free);空目录返回 "" */ +char *rwext_list(rwext_conn *c, const char *prefix); + +/* 读 key 的 value_string(malloc);None 返回 "" */ +char *rwext_get(rwext_conn *c, const char *key); + +/* 写 char/utf8 值 */ +int rwext_set(rwext_conn *c, const char *key, const char *val); + +/* 删 key */ +int rwext_del(rwext_conn *c, const char *key); + +/* 从 pc 解码指令;若 opcode ∈ {print,println,cerr},resolve 全部 reads 并 display, + * 以自身 sep(print 无分隔、println/cerr 空格分隔)连接返回(malloc); + * 非己方指令返回 NULL(调用方应停止 RunSeq)。 + * rawnl/cerr 输出该指令的换行/流属性:print→rawnl=1(不换行,stdout); + * println→rawnl=0(换行,stdout);cerr→rawnl=0,cerr=1(换行,stderr)。 */ +char *rwext_print_line(rwext_conn *c, const char *pc, int *rawnl, int *cerr); + +/* 当前指令的下一条 PC(malloc) */ +char *rwext_next_pc(const char *pc); diff --git a/runtime/src/builtin.c b/runtime/src/builtin.c new file mode 100644 index 00000000..43e7f1fb --- /dev/null +++ b/runtime/src/builtin.c @@ -0,0 +1,1437 @@ +#include "runtime_internal.h" +#include +#include + +typedef int (*bi_fn)(frame_t *f); + +/* collection 模块 handler(builtin_coll.c) */ +int bi_array(frame_t *f), bi_len(frame_t *f), bi_at(frame_t *f), bi_set(frame_t *f), + bi_has(frame_t *f), bi_sort(frame_t *f), bi_scatter(frame_t *f), bi_compact(frame_t *f), + bi_append(frame_t *f), bi_slice(frame_t *f), bi_dict(frame_t *f), bi_string_set(frame_t *f), + bi_string_char(frame_t *f), bi_string_ord(frame_t *f), bi_string_cmp(frame_t *f), + bi_string_find(frame_t *f), bi_string_len(frame_t *f), bi_string_slice(frame_t *f), + bi_string_concat(frame_t *f), bi_time_now(frame_t *f), bi_time_sub(frame_t *f), + bi_time_add(frame_t *f), bi_dur_from(frame_t *f), bi_dur_to(frame_t *f), + bi_dur_arith(frame_t *f), bi_dur_cmp(frame_t *f), bi_time_cmp(frame_t *f), + bi_rand_uint64(frame_t *f), bi_rand_int63(frame_t *f), bi_rand_intn(frame_t *f), + bi_kvhas(frame_t *f), bi_kvat(frame_t *f), bi_debugger(frame_t *f); + +/* ── 类型 helper(对齐 Go isIntKind 含 uint)────────────────────── */ + +static bool is_int_kind(const char *k) { return xv_is_int_kind(k) || xv_is_uint_kind(k); } +static bool is_float_kind(const char *k) { return xv_is_float_kind(k); } +static bool is_unsigned_kind(const char *k) { return xv_is_uint_kind(k); } +static bool is_numeric(const xval_t *v) { return xv_is_num_kind(xv_kind(v)); } + +static int int_width(const char *k) { + if (strcmp(k, K_INT8) == 0 || strcmp(k, K_UINT8) == 0) return 8; + if (strcmp(k, K_INT16) == 0 || strcmp(k, K_UINT16) == 0) return 16; + if (strcmp(k, K_INT32) == 0 || strcmp(k, K_UINT32) == 0) return 32; + if (strcmp(k, K_INT64) == 0 || strcmp(k, K_UINT64) == 0) return 64; + return 0; +} + +static const char *wider_int_kind(const char *a, const char *b) { + int aw = int_width(a), bw = int_width(b); + bool au = is_unsigned_kind(a), bu = is_unsigned_kind(b); + if (au && bu) return aw >= bw ? a : b; + if (!au && !bu) return aw >= bw ? a : b; + int w = aw > bw ? aw : bw; + switch (w) { + case 8: return K_INT16; + case 16: return K_INT32; + case 32: return K_INT64; + default: return K_INT64; + } +} + +static const char *wider_float_kind(const char *a, const char *b) { + if (strcmp(a, K_FLOAT64) == 0 || strcmp(b, K_FLOAT64) == 0) return K_FLOAT64; + if (strcmp(a, K_FLOAT32) == 0 || strcmp(b, K_FLOAT32) == 0) return K_FLOAT32; + return K_FLOAT64; +} + +static void narrow_int(const char *a, const char *b, int64_t v, xval_t *out) { + const char *k = wider_int_kind(a, b); + if (strcmp(k, K_INT8) == 0) { int8_t x = (int8_t)v; xv_new_tlv(out, K_INT8, (uint8_t *)&x, 1, 1); return; } + if (strcmp(k, K_INT16) == 0) { int16_t x = (int16_t)v; uint8_t r[2] = { x & 0xFF, (x >> 8) & 0xFF }; xv_new_tlv(out, K_INT16, r, 2, 1); return; } + if (strcmp(k, K_INT32) == 0) { int32_t x = (int32_t)v; uint8_t r[4]; memcpy(r, &x, 4); xv_new_tlv(out, K_INT32, r, 4, 1); return; } + if (strcmp(k, K_UINT8) == 0) { uint8_t x = (uint8_t)v; xv_new_tlv(out, K_UINT8, &x, 1, 1); return; } + if (strcmp(k, K_UINT16) == 0) { uint16_t x = (uint16_t)v; uint8_t r[2] = { x & 0xFF, (x >> 8) & 0xFF }; xv_new_tlv(out, K_UINT16, r, 2, 1); return; } + if (strcmp(k, K_UINT32) == 0) { uint32_t x = (uint32_t)v; uint8_t r[4]; memcpy(r, &x, 4); xv_new_tlv(out, K_UINT32, r, 4, 1); return; } + if (strcmp(k, K_UINT64) == 0) { uint64_t x = (uint64_t)v; uint8_t r[8]; memcpy(r, &x, 8); xv_new_tlv(out, K_UINT64, r, 8, 1); return; } + xv_new_int64(out, v); +} + +static void narrow_float(const char *a, const char *b, double v, xval_t *out) { + if (strcmp(wider_float_kind(a, b), K_FLOAT32) == 0) { + float f = (float)v; uint8_t r[4]; memcpy(r, &f, 4); + xv_new_tlv(out, K_FLOAT32, r, 4, 1); + } else xv_new_float64(out, v); +} + +static int cmp_int(const xval_t *a, const xval_t *b) { + bool au = is_unsigned_kind(xv_kind(a)), bu = is_unsigned_kind(xv_kind(b)); + if (!au && !bu) { int64_t ai = xv_as_int64(a), bi = xv_as_int64(b); return ai < bi ? -1 : ai > bi ? 1 : 0; } + if (au && bu) { uint64_t x = xv_as_uint64(a), y = xv_as_uint64(b); return x < y ? -1 : x > y ? 1 : 0; } + if (au && !bu) { int64_t bi = xv_as_int64(b); if (bi < 0) return 1; uint64_t x = xv_as_uint64(a); return x < (uint64_t)bi ? -1 : x > (uint64_t)bi ? 1 : 0; } + int64_t ai = xv_as_int64(a); if (ai < 0) return -1; uint64_t y = xv_as_uint64(b); + return (uint64_t)ai < y ? -1 : (uint64_t)ai > y ? 1 : 0; +} + +/* ── resolve ───────────────────────────────────────────────────────── */ + +char *bi_func_frame_root(kv_t *kv, const char *frame_root) { + char *cur = strdup(frame_root); + for (;;) { + sbuf_t k; sb_init(&k); + char *stk = kt_stack(cur); + sb_puts(&k, stk); free(stk); + sb_puts(&k, SEG_LIB); + xval_t v; xv_zero(&v); + kv_get_one(kv, k.p, &v); + bool found = !xv_none(&v); + xv_free(&v); sb_free(&k); + if (found) return cur; + char *parent = kt_parent_frame(cur); + if (parent[0] == 0) { free(parent); return cur; } + free(cur); cur = parent; + } +} + +void bi_resolve_read_value(kv_t *kv, const char *frame_path, const char *name, + const xval_t *val, xval_t *out) { + xv_zero(out); + if (val && !xv_none(val) && !xv_kind_is(val, K_RWIR) && !xv_kind_is(val, K_RWFUNC)) { + kvhead_t h; xv_head(val, &h); + int32_t blen; const uint8_t *body = xv_body(val, &h, &blen); + xv_new_tlv(out, xv_kind(val), body, (uint32_t)blen, h.array_len); + return; + } + if (!name || !name[0]) return; + if (name[0] == '/') { kv_get_one(kv, name, out); return; } + char *rw = bi_func_frame_root(kv, frame_path); + sbuf_t key; sb_init(&key); + char *stk = kt_stack(rw); + sb_puts(&key, stk); free(stk); sb_puts(&key, name); + xval_t pv; xv_zero(&pv); + kv_get_one(kv, key.p, &pv); + if (xv_is_ptr(&pv)) { + char *target = xv_ptr_target(&pv); + sbuf_t ak; sb_init(&ak); + char *stk2 = kt_stack(rw); + sb_puts(&ak, stk2); free(stk2); sb_puts(&ak, target); + free(target); + xval_t av; xv_zero(&av); + kv_get_one(kv, ak.p, &av); + if (!xv_none(&av)) { + char *path = xv_value_string(&av); + kv_get_one(kv, path, out); + free(path); + } + xv_free(&av); sb_free(&ak); + } else if (!xv_none(&pv)) { + *out = pv; pv.data = NULL; pv.len = 0; + } + xv_free(&pv); sb_free(&key); free(rw); +} + +char *bi_resolve_write_slot(kv_t *kv, const char *frame_path, const char *name) { + if (name[0] == '/') return strdup(name); + char *rw = bi_func_frame_root(kv, frame_path); + sbuf_t key; sb_init(&key); + char *stk = kt_stack(rw); + sb_puts(&key, stk); free(stk); sb_puts(&key, name); + xval_t pv; xv_zero(&pv); + kv_get_one(kv, key.p, &pv); + char *result = NULL; + if (xv_is_ptr(&pv)) { + char *target = xv_ptr_target(&pv); + sbuf_t ak; sb_init(&ak); + char *stk2 = kt_stack(rw); + sb_puts(&ak, stk2); free(stk2); sb_puts(&ak, target); + free(target); + xval_t av; xv_zero(&av); + kv_get_one(kv, ak.p, &av); + if (!xv_none(&av)) { + xval_t v = av; av.data = NULL; av.len = 0; + for (;;) { + if (!xv_is_char_kind(xv_kind(&v))) break; + char *p = xv_value_string(&v); + xval_t next; xv_zero(&next); + kv_get_one(kv, p, &next); + if (xv_none(&next) || !xv_is_char_kind(xv_kind(&next))) { result = p; break; } + free(p); + xv_free(&v); v = next; + } + xv_free(&v); + } + xv_free(&av); sb_free(&ak); + } + xv_free(&pv); sb_free(&key); free(rw); + if (result) return result; + /* fallback: Stack(rw) + name */ + char *rw2 = bi_func_frame_root(kv, frame_path); + sbuf_t o; sb_init(&o); + char *stk3 = kt_stack(rw2); + sb_puts(&o, stk3); free(stk3); sb_puts(&o, name); + free(rw2); + return sb_detach(&o); +} + +/* ── coerce / display ─────────────────────────────────────────────── */ + +static bool try_parse_int(const char *s, int64_t *out) { + if (!s || !s[0]) return false; + char *end; long long v = strtoll(s, &end, 10); + if (end == s || *end != 0) return false; + *out = v; return true; +} + +bool bi_try_parse_number(const char *s, xval_t *out) { + xv_zero(out); + if (!s || !s[0]) return false; + char c0 = s[0]; + bool num = (c0 >= '0' && c0 <= '9') || (c0 == '-' && s[1] >= '0' && s[1] <= '9'); + if (!num) return false; + int64_t iv; + if (try_parse_int(s, &iv)) { xv_new_int64(out, iv); return true; } + if (c0 != '-' && !strpbrk(s, ".eE")) { + char *end; unsigned long long uv = strtoull(s, &end, 10); + if (end != s && *end == 0) { + uint8_t r[8]; memcpy(r, &uv, 8); + xv_new_tlv(out, K_UINT64, r, 8, 1); return true; + } + } + char *end; double f = strtod(s, &end); + if (end != s && *end == 0) { xv_new_float64(out, f); return true; } + return false; +} + +static void xvalue_at(const xval_t *v, int i, xval_t *out) { + xv_zero(out); + int n = xv_array_len(v); + if (i < 0 || i >= n) return; + const char *k = xv_kind(v); + int sz = xv_elem_size(k); + if (sz <= 0) return; + kvhead_t h; kvspace_decode_head(v->data, v->len, &h); + const uint8_t *body = v->data + h.body_offset; + xv_new_tlv(out, k, body + i * sz, (uint32_t)sz, 1); +} + +void display(const xval_t *v, char **out); + +static void format_array(const xval_t *v, char **out) { + int n = xv_array_len(v); + sbuf_t b; sb_init(&b); + sb_putc(&b, '['); + for (int i = 0; i < n; i++) { + if (i) sb_puts(&b, ", "); + xval_t e; xvalue_at(v, i, &e); + char *s; display(&e, &s); + sb_puts(&b, s); free(s); xv_free(&e); + } + sb_putc(&b, ']'); + *out = sb_detach(&b); +} + +void display(const xval_t *v, char **out) { + if (xv_is_char_kind(xv_kind(v))) { *out = xv_value_string(v); return; } + if (xv_array_len(v) > 1) { format_array(v, out); return; } + *out = xv_value_string(v); +} + +/* ── frame helper ─────────────────────────────────────────────────── */ + +static int read_inputs(frame_t *f, xval_t *out, int cap) { + char *fr = kt_frame_root(f->pc); + char *ff = bi_func_frame_root(f->kv, fr); + free(fr); + int n = 0; + for (int i = 0; i < f->inst->nr && n < cap; i++) { + bi_resolve_read_value(f->kv, ff, f->inst->reads[i].name, &f->inst->reads[i].val, &out[n]); + n++; + } + free(ff); + return n; +} + +static void free_inputs(xval_t *in, int n) { for (int i = 0; i < n; i++) xv_free(&in[i]); } + +static void next_pc(frame_t *f) { + sbuf_t npc; sb_init(&npc); + rwir_next_pc(f->pc, &npc); + vt_set(f->kv, f->vtid, npc.p, "running"); + sb_free(&npc); +} + +static int write_result(frame_t *f, const xval_t *result) { + if (f->inst->nw > 0) { + char *fr = kt_frame_root(f->pc); + char *key = bi_resolve_write_slot(f->kv, fr, f->inst->writes[0].name); + free(fr); + kv_pair_t pair = { key, *result }; + char err[256]; + kv_set(f->kv, &pair, 1, err, sizeof err); + free(key); + } + next_pc(f); + return 0; +} + +static int set_err(frame_t *f, const char *fmt, ...) { + char msg[512]; + va_list ap; va_start(ap, fmt); + vsnprintf(msg, sizeof msg, fmt, ap); + va_end(ap); + vt_set_error(f->kv, f->vtid, f->pc, msg); + return -1; +} + +/* ── 数值算子 ─────────────────────────────────────────────────────── */ + +static int bi_add(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + int rc; + if (n == 2 && xv_is_char_kind(xv_kind(&in[0])) && xv_is_char_kind(xv_kind(&in[1]))) { + char *a = xv_value_string(&in[0]), *b = xv_value_string(&in[1]); + sbuf_t s; sb_init(&s); sb_puts(&s, a); sb_puts(&s, b); + xval_t r; xv_new_char_utf32(&r, s.p); + rc = write_result(f, &r); + xv_free(&r); free(a); free(b); sb_free(&s); + } else if (n >= 2 && is_numeric(&in[0]) && is_numeric(&in[1])) { + if (is_int_kind(xv_kind(&in[0])) && is_int_kind(xv_kind(&in[1]))) { + xval_t r; narrow_int(xv_kind(&in[0]), xv_kind(&in[1]), xv_as_int64(&in[0]) + xv_as_int64(&in[1]), &r); + rc = write_result(f, &r); xv_free(&r); + } else { + xval_t r; narrow_float(xv_kind(&in[0]), xv_kind(&in[1]), xv_as_float64(&in[0]) + xv_as_float64(&in[1]), &r); + rc = write_result(f, &r); xv_free(&r); + } + } else rc = set_err(f, "TypeError: expected numeric, got %s", n ? xv_kind(&in[0]) : "none"); + free_inputs(in, n); + return rc; +} + +static int bi_sub(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + int rc; + if (n == 1) { + xval_t r; xv_new_int64(&r, -xv_as_int64(&in[0])); + rc = write_result(f, &r); xv_free(&r); + } else if (n >= 2 && is_int_kind(xv_kind(&in[0])) && is_int_kind(xv_kind(&in[1]))) { + xval_t r; narrow_int(xv_kind(&in[0]), xv_kind(&in[1]), xv_as_int64(&in[0]) - xv_as_int64(&in[1]), &r); + rc = write_result(f, &r); xv_free(&r); + } else if (n >= 2 && is_numeric(&in[0]) && is_numeric(&in[1])) { + xval_t r; narrow_float(xv_kind(&in[0]), xv_kind(&in[1]), xv_as_float64(&in[0]) - xv_as_float64(&in[1]), &r); + rc = write_result(f, &r); xv_free(&r); + } else rc = set_err(f, "TypeError: expected numeric, got %s", n ? xv_kind(&in[0]) : "none"); + free_inputs(in, n); + return rc; +} + +static int bi_mul(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + int rc; + if (n >= 2 && is_int_kind(xv_kind(&in[0])) && is_int_kind(xv_kind(&in[1]))) { + xval_t r; narrow_int(xv_kind(&in[0]), xv_kind(&in[1]), xv_as_int64(&in[0]) * xv_as_int64(&in[1]), &r); + rc = write_result(f, &r); xv_free(&r); + } else if (n >= 2 && is_numeric(&in[0]) && is_numeric(&in[1])) { + xval_t r; narrow_float(xv_kind(&in[0]), xv_kind(&in[1]), xv_as_float64(&in[0]) * xv_as_float64(&in[1]), &r); + rc = write_result(f, &r); xv_free(&r); + } else rc = set_err(f, "TypeError: expected numeric, got %s", n ? xv_kind(&in[0]) : "none"); + free_inputs(in, n); + return rc; +} + +static int bi_div(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + int rc; + if (n < 2) { rc = set_err(f, "TypeError: binary op requires 2 inputs, got %d", n); free_inputs(in, n); return rc; } + if (xv_as_float64(&in[1]) == 0) { rc = set_err(f, "ZeroDivisionError: division by zero"); free_inputs(in, n); return rc; } + if (is_int_kind(xv_kind(&in[0])) && is_int_kind(xv_kind(&in[1]))) { + xval_t r; narrow_int(xv_kind(&in[0]), xv_kind(&in[1]), xv_as_int64(&in[0]) / xv_as_int64(&in[1]), &r); + rc = write_result(f, &r); xv_free(&r); + } else { + xval_t r; narrow_float(xv_kind(&in[0]), xv_kind(&in[1]), xv_as_float64(&in[0]) / xv_as_float64(&in[1]), &r); + rc = write_result(f, &r); xv_free(&r); + } + free_inputs(in, n); + return rc; +} + +static int bi_mod(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + int rc; + if (n < 2 || !is_int_kind(xv_kind(&in[0])) || !is_int_kind(xv_kind(&in[1]))) { + rc = set_err(f, "TypeError: expected integer, got %s", n ? xv_kind(&in[0]) : "none"); + free_inputs(in, n); return rc; + } + int64_t b = xv_as_int64(&in[1]); + if (b == 0) { rc = set_err(f, "ZeroDivisionError: modulo by zero"); free_inputs(in, n); return rc; } + xval_t r; narrow_int(xv_kind(&in[0]), xv_kind(&in[1]), xv_as_int64(&in[0]) % b, &r); + rc = write_result(f, &r); xv_free(&r); + free_inputs(in, n); + return rc; +} + +typedef enum { CMP_EQ, CMP_NEQ, CMP_LT, CMP_GT, CMP_LE, CMP_GE } cmp_op; + +static int bi_cmp(frame_t *f, cmp_op op) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { set_err(f, "TypeError: binary op requires 2 inputs, got %d", n); free_inputs(in, n); return -1; } + bool allow_null = (op == CMP_EQ || op == CMP_NEQ); + if (xv_none(&in[0]) || xv_none(&in[1])) { + if (!allow_null) { set_err(f, "TypeError: None in comparison"); free_inputs(in, n); return -1; } + bool eq = xv_none(&in[0]) == xv_none(&in[1]); + bool r = (op == CMP_EQ) ? eq : (op == CMP_NEQ) ? !eq : false; + xval_t rv; xv_new_bool(&rv, r); + int rc = write_result(f, &rv); xv_free(&rv); free_inputs(in, n); return rc; + } + bool r; + const char *ka = xv_kind(&in[0]), *kb = xv_kind(&in[1]); + if (is_int_kind(ka) && is_int_kind(kb)) { + int c = cmp_int(&in[0], &in[1]); + r = op == CMP_EQ ? c == 0 : op == CMP_NEQ ? c != 0 : op == CMP_LT ? c < 0 : op == CMP_GT ? c > 0 : op == CMP_LE ? c <= 0 : c >= 0; + } else if (is_numeric(&in[0]) && is_numeric(&in[1])) { + double a = xv_as_float64(&in[0]), b = xv_as_float64(&in[1]); + r = op == CMP_EQ ? a == b : op == CMP_NEQ ? a != b : op == CMP_LT ? a < b : op == CMP_GT ? a > b : op == CMP_LE ? a <= b : a >= b; + } else if (xv_is_char_kind(ka) && xv_is_char_kind(kb)) { + char *a = xv_value_string(&in[0]), *b = xv_value_string(&in[1]); + int c = strcmp(a, b); + r = op == CMP_EQ ? c == 0 : op == CMP_NEQ ? c != 0 : op == CMP_LT ? c < 0 : op == CMP_GT ? c > 0 : op == CMP_LE ? c <= 0 : c >= 0; + free(a); free(b); + } else if (strcmp(ka, K_BOOL) == 0 && strcmp(kb, K_BOOL) == 0) { + bool a = xv_as_bool(&in[0]), b = xv_as_bool(&in[1]); + r = op == CMP_EQ ? a == b : op == CMP_NEQ ? a != b : op == CMP_LT ? a < b : op == CMP_GT ? a > b : op == CMP_LE ? a <= b : a >= b; + } else { + set_err(f, "TypeError: cannot compare %s with %s", ka, kb); free_inputs(in, n); return -1; + } + xval_t rv; xv_new_bool(&rv, r); + int rc = write_result(f, &rv); xv_free(&rv); free_inputs(in, n); + return rc; +} + +static int bi_eq(frame_t *f) { return bi_cmp(f, CMP_EQ); } +static int bi_neq(frame_t *f) { return bi_cmp(f, CMP_NEQ); } +static int bi_lt(frame_t *f) { return bi_cmp(f, CMP_LT); } +static int bi_gt(frame_t *f) { return bi_cmp(f, CMP_GT); } +static int bi_le(frame_t *f) { return bi_cmp(f, CMP_LE); } +static int bi_ge(frame_t *f) { return bi_cmp(f, CMP_GE); } + +static int bi_and(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + xval_t r; xv_new_bool(&r, n >= 2 && xv_as_bool(&in[0]) && xv_as_bool(&in[1])); + int rc = write_result(f, &r); xv_free(&r); free_inputs(in, n); + return rc; +} +static int bi_or(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + xval_t r; xv_new_bool(&r, n >= 2 && (xv_as_bool(&in[0]) || xv_as_bool(&in[1]))); + int rc = write_result(f, &r); xv_free(&r); free_inputs(in, n); + return rc; +} +static int bi_not(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + xval_t r; xv_new_bool(&r, !(n >= 1 && xv_as_bool(&in[0]))); + int rc = write_result(f, &r); xv_free(&r); free_inputs(in, n); + return rc; +} + +static int bi_bit(frame_t *f, int op) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2 || !is_int_kind(xv_kind(&in[0])) || !is_int_kind(xv_kind(&in[1]))) { + set_err(f, "TypeError: expected integer, got %s", n ? xv_kind(&in[0]) : "none"); + free_inputs(in, n); return -1; + } + int64_t a = xv_as_int64(&in[0]), b = xv_as_int64(&in[1]); + int64_t v = op == 0 ? a & b : op == 1 ? a | b : op == 2 ? a ^ b : op == 3 ? (a << (uint64_t)b) : (a >> (uint64_t)b); + xval_t r; narrow_int(xv_kind(&in[0]), xv_kind(&in[1]), v, &r); + int rc = write_result(f, &r); xv_free(&r); free_inputs(in, n); + return rc; +} +static int bi_bitand(frame_t *f) { return bi_bit(f, 0); } +static int bi_bitor(frame_t *f) { return bi_bit(f, 1); } +static int bi_bitxor(frame_t *f) { return bi_bit(f, 2); } +static int bi_shl(frame_t *f) { return bi_bit(f, 3); } +static int bi_shr(frame_t *f) { return bi_bit(f, 4); } + +/* math */ +static int bi_math_unary(frame_t *f, int op) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 1 || !is_numeric(&in[0])) { set_err(f, "TypeError: expected numeric, got %s", n ? xv_kind(&in[0]) : "none"); free_inputs(in, n); return -1; } + xval_t r; + double x = xv_as_float64(&in[0]); + switch (op) { + case 0: xv_new_float64(&r, sqrt(x)); break; + case 1: xv_new_float64(&r, exp(x)); break; + case 2: xv_new_float64(&r, log(x)); break; + case 3: /* neg */ if (is_float_kind(xv_kind(&in[0]))) { narrow_float(xv_kind(&in[0]), xv_kind(&in[0]), -x, &r); } else { narrow_int(xv_kind(&in[0]), xv_kind(&in[0]), -xv_as_int64(&in[0]), &r); } break; + case 4: /* abs */ if (is_float_kind(xv_kind(&in[0]))) { narrow_float(xv_kind(&in[0]), xv_kind(&in[0]), fabs(x), &r); } else { narrow_int(xv_kind(&in[0]), xv_kind(&in[0]), xv_as_int64(&in[0]) < 0 ? -xv_as_int64(&in[0]) : xv_as_int64(&in[0]), &r); } break; + case 5: xv_new_int64(&r, x < 0 ? -1 : x > 0 ? 1 : 0); break; + } + int rc = write_result(f, &r); xv_free(&r); free_inputs(in, n); + return rc; +} +static int bi_sqrt(frame_t *f) { return bi_math_unary(f, 0); } +static int bi_exp(frame_t *f) { return bi_math_unary(f, 1); } +static int bi_log(frame_t *f) { return bi_math_unary(f, 2); } +static int bi_neg(frame_t *f) { return bi_math_unary(f, 3); } +static int bi_abs(frame_t *f) { return bi_math_unary(f, 4); } +static int bi_sign(frame_t *f) { return bi_math_unary(f, 5); } + +static int bi_pow(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2 || !is_numeric(&in[0]) || !is_numeric(&in[1])) { set_err(f, "TypeError: expected numeric"); free_inputs(in, n); return -1; } + xval_t r; xv_new_float64(&r, pow(xv_as_float64(&in[0]), xv_as_float64(&in[1]))); + int rc = write_result(f, &r); xv_free(&r); free_inputs(in, n); + return rc; +} + +static int bi_maxmin(frame_t *f, bool is_max) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { set_err(f, "TypeError: binary op requires 2 inputs, got %d", n); free_inputs(in, n); return -1; } + xval_t r; + if (is_int_kind(xv_kind(&in[0])) && is_int_kind(xv_kind(&in[1]))) { + int c = cmp_int(&in[0], &in[1]); + bool take_a = (is_max && c >= 0) || (!is_max && c <= 0); + narrow_int(xv_kind(&in[0]), xv_kind(&in[1]), take_a ? xv_as_int64(&in[0]) : xv_as_int64(&in[1]), &r); + } else if (is_numeric(&in[0]) && is_numeric(&in[1])) { + double a = xv_as_float64(&in[0]), b = xv_as_float64(&in[1]); + bool take_a = (is_max && a >= b) || (!is_max && a <= b); + narrow_float(xv_kind(&in[0]), xv_kind(&in[1]), take_a ? a : b, &r); + } else { set_err(f, "TypeError: max/min requires numeric, got %s and %s", xv_kind(&in[0]), xv_kind(&in[1])); free_inputs(in, n); return -1; } + int rc = write_result(f, &r); xv_free(&r); free_inputs(in, n); + return rc; +} +static int bi_max(frame_t *f) { return bi_maxmin(f, true); } +static int bi_min(frame_t *f) { return bi_maxmin(f, false); } + +/* cast */ +static int bi_cast_num(frame_t *f, const char *kind) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 1 || xv_none(&in[0])) { set_err(f, "TypeError: cannot cast None"); free_inputs(in, n); return -1; } + xval_t r; + if (strcmp(kind, K_BOOL) == 0) xv_new_bool(&r, xv_as_bool(&in[0])); + else if (strcmp(kind, K_FLOAT32) == 0) { float fv = (float)xv_as_float64(&in[0]); uint8_t b[4]; memcpy(b, &fv, 4); xv_new_tlv(&r, K_FLOAT32, b, 4, 1); } + else if (strcmp(kind, K_FLOAT64) == 0) xv_new_float64(&r, xv_as_float64(&in[0])); + else { int64_t v = xv_as_int64(&in[0]); narrow_int(kind, kind, v, &r); } + int rc = write_result(f, &r); xv_free(&r); free_inputs(in, n); + return rc; +} +static int bi_cast_bool(frame_t *f) { return bi_cast_num(f, K_BOOL); } +static int bi_cast_int8(frame_t *f) { return bi_cast_num(f, K_INT8); } +static int bi_cast_int16(frame_t *f) { return bi_cast_num(f, K_INT16); } +static int bi_cast_int32(frame_t *f) { return bi_cast_num(f, K_INT32); } +static int bi_cast_int64(frame_t *f) { return bi_cast_num(f, K_INT64); } +static int bi_cast_uint8(frame_t *f) { return bi_cast_num(f, K_UINT8); } +static int bi_cast_uint16(frame_t *f) { return bi_cast_num(f, K_UINT16); } +static int bi_cast_uint32(frame_t *f) { return bi_cast_num(f, K_UINT32); } +static int bi_cast_uint64(frame_t *f) { return bi_cast_num(f, K_UINT64); } +static int bi_cast_f32(frame_t *f) { return bi_cast_num(f, K_FLOAT32); } +static int bi_cast_f64(frame_t *f) { return bi_cast_num(f, K_FLOAT64); } + +static int bi_cast_char(frame_t *f, const char *kind) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 1 || xv_none(&in[0])) { set_err(f, "TypeError: char conversion requires a value"); free_inputs(in, n); return -1; } + char *s = xv_value_string(&in[0]); + xval_t r; + if (strcmp(kind, K_CHAR) == 0) xv_new_char_utf32(&r, s); + else xv_new_char_kind(&r, kind, s); + int rc = write_result(f, &r); xv_free(&r); free(s); free_inputs(in, n); + return rc; +} +static int bi_cast_char32(frame_t *f) { return bi_cast_char(f, K_CHAR); } +static int bi_cast_char8(frame_t *f) { return bi_cast_char(f, K_CHAR_UTF8); } +static int bi_cast_char_ascii(frame_t *f) { return bi_cast_char(f, K_CHAR_ASCII); } + +/* ── 注册表 ───────────────────────────────────────────────────────── */ + +static const struct { const char *op; bi_fn fn; } builtins[] = { + {"add", bi_add}, {"+", bi_add}, + {"int8.add", bi_add}, {"int16.add", bi_add}, {"int32.add", bi_add}, {"int64.add", bi_add}, + {"uint8.add", bi_add}, {"uint16.add", bi_add}, {"uint32.add", bi_add}, {"uint64.add", bi_add}, + {"float32.add", bi_add}, {"float64.add", bi_add}, + {"sub", bi_sub}, {"-", bi_sub}, + {"int8.sub", bi_sub}, {"int16.sub", bi_sub}, {"int32.sub", bi_sub}, {"int64.sub", bi_sub}, + {"uint8.sub", bi_sub}, {"uint16.sub", bi_sub}, {"uint32.sub", bi_sub}, {"uint64.sub", bi_sub}, + {"float32.sub", bi_sub}, {"float64.sub", bi_sub}, + {"mul", bi_mul}, {"×", bi_mul}, + {"int8.mul", bi_mul}, {"int16.mul", bi_mul}, {"int32.mul", bi_mul}, {"int64.mul", bi_mul}, + {"uint8.mul", bi_mul}, {"uint16.mul", bi_mul}, {"uint32.mul", bi_mul}, {"uint64.mul", bi_mul}, + {"float32.mul", bi_mul}, {"float64.mul", bi_mul}, + {"div", bi_div}, {"÷", bi_div}, + {"int8.div", bi_div}, {"int16.div", bi_div}, {"int32.div", bi_div}, {"int64.div", bi_div}, + {"uint8.div", bi_div}, {"uint16.div", bi_div}, {"uint32.div", bi_div}, {"uint64.div", bi_div}, + {"float32.div", bi_div}, {"float64.div", bi_div}, + {"mod", bi_mod}, {"%", bi_mod}, + {"int8.mod", bi_mod}, {"int16.mod", bi_mod}, {"int32.mod", bi_mod}, {"int64.mod", bi_mod}, + {"uint8.mod", bi_mod}, {"uint16.mod", bi_mod}, {"uint32.mod", bi_mod}, {"uint64.mod", bi_mod}, + {"eq", bi_eq}, {"==", bi_eq}, + {"int8.eq", bi_eq}, {"int16.eq", bi_eq}, {"int32.eq", bi_eq}, {"int64.eq", bi_eq}, + {"uint8.eq", bi_eq}, {"uint16.eq", bi_eq}, {"uint32.eq", bi_eq}, {"uint64.eq", bi_eq}, + {"float32.eq", bi_eq}, {"float64.eq", bi_eq}, + {"neq", bi_neq}, {"!=", bi_neq}, {"≠", bi_neq}, + {"int8.neq", bi_neq}, {"int16.neq", bi_neq}, {"int32.neq", bi_neq}, {"int64.neq", bi_neq}, + {"uint8.neq", bi_neq}, {"uint16.neq", bi_neq}, {"uint32.neq", bi_neq}, {"uint64.neq", bi_neq}, + {"float32.neq", bi_neq}, {"float64.neq", bi_neq}, + {"lt", bi_lt}, {"<", bi_lt}, + {"int8.lt", bi_lt}, {"int16.lt", bi_lt}, {"int32.lt", bi_lt}, {"int64.lt", bi_lt}, + {"uint8.lt", bi_lt}, {"uint16.lt", bi_lt}, {"uint32.lt", bi_lt}, {"uint64.lt", bi_lt}, + {"float32.lt", bi_lt}, {"float64.lt", bi_lt}, + {"gt", bi_gt}, {">", bi_gt}, + {"int8.gt", bi_gt}, {"int16.gt", bi_gt}, {"int32.gt", bi_gt}, {"int64.gt", bi_gt}, + {"uint8.gt", bi_gt}, {"uint16.gt", bi_gt}, {"uint32.gt", bi_gt}, {"uint64.gt", bi_gt}, + {"float32.gt", bi_gt}, {"float64.gt", bi_gt}, + {"le", bi_le}, {"<=", bi_le}, {"≤", bi_le}, + {"int8.le", bi_le}, {"int16.le", bi_le}, {"int32.le", bi_le}, {"int64.le", bi_le}, + {"uint8.le", bi_le}, {"uint16.le", bi_le}, {"uint32.le", bi_le}, {"uint64.le", bi_le}, + {"float32.le", bi_le}, {"float64.le", bi_le}, + {"ge", bi_ge}, {">=", bi_ge}, {"≥", bi_ge}, + {"int8.ge", bi_ge}, {"int16.ge", bi_ge}, {"int32.ge", bi_ge}, {"int64.ge", bi_ge}, + {"uint8.ge", bi_ge}, {"uint16.ge", bi_ge}, {"uint32.ge", bi_ge}, {"uint64.ge", bi_ge}, + {"float32.ge", bi_ge}, {"float64.ge", bi_ge}, + {"and", bi_and}, {"&&", bi_and}, + {"or", bi_or}, {"||", bi_or}, + {"not", bi_not}, {"!", bi_not}, + {"bitand", bi_bitand}, {"&", bi_bitand}, + {"int64.bitand", bi_bitand}, {"uint64.bitand", bi_bitand}, + {"bitor", bi_bitor}, {"|", bi_bitor}, + {"int64.bitor", bi_bitor}, {"uint64.bitor", bi_bitor}, + {"bitxor", bi_bitxor}, {"^", bi_bitxor}, + {"int64.bitxor", bi_bitxor}, {"uint64.bitxor", bi_bitxor}, + {"shl", bi_shl}, {"<<", bi_shl}, + {"int64.shl", bi_shl}, {"uint64.shl", bi_shl}, + {"shr", bi_shr}, {">>", bi_shr}, + {"int64.shr", bi_shr}, {"uint64.shr", bi_shr}, + {"pow", bi_pow}, {"float32.pow", bi_pow}, {"float64.pow", bi_pow}, + {"sqrt", bi_sqrt}, {"√", bi_sqrt}, {"float32.sqrt", bi_sqrt}, {"float64.sqrt", bi_sqrt}, + {"exp", bi_exp}, {"float32.exp", bi_exp}, {"float64.exp", bi_exp}, + {"log", bi_log}, {"float32.log", bi_log}, {"float64.log", bi_log}, + {"neg", bi_neg}, + {"int8.neg", bi_neg}, {"int16.neg", bi_neg}, {"int32.neg", bi_neg}, {"int64.neg", bi_neg}, + {"float32.neg", bi_neg}, {"float64.neg", bi_neg}, + {"abs", bi_abs}, + {"int8.abs", bi_abs}, {"int16.abs", bi_abs}, {"int32.abs", bi_abs}, {"int64.abs", bi_abs}, + {"float32.abs", bi_abs}, {"float64.abs", bi_abs}, + {"sign", bi_sign}, + {"int8.sign", bi_sign}, {"int16.sign", bi_sign}, {"int32.sign", bi_sign}, {"int64.sign", bi_sign}, + {"uint8.sign", bi_sign}, {"uint16.sign", bi_sign}, {"uint32.sign", bi_sign}, {"uint64.sign", bi_sign}, + {"float32.sign", bi_sign}, {"float64.sign", bi_sign}, + {"max", bi_max}, {"min", bi_min}, + {"int8.max", bi_max}, {"int16.max", bi_max}, {"int32.max", bi_max}, {"int64.max", bi_max}, + {"uint8.max", bi_max}, {"uint16.max", bi_max}, {"uint32.max", bi_max}, {"uint64.max", bi_max}, + {"float32.max", bi_max}, {"float64.max", bi_max}, + {"int8.min", bi_min}, {"int16.min", bi_min}, {"int32.min", bi_min}, {"int64.min", bi_min}, + {"uint8.min", bi_min}, {"uint16.min", bi_min}, {"uint32.min", bi_min}, {"uint64.min", bi_min}, + {"float32.min", bi_min}, {"float64.min", bi_min}, + /* cast */ + {"bool", bi_cast_bool}, {"int8", bi_cast_int8}, {"int16", bi_cast_int16}, + {"int32", bi_cast_int32}, {"int64", bi_cast_int64}, {"uint8", bi_cast_uint8}, + {"uint16", bi_cast_uint16}, {"uint32", bi_cast_uint32}, {"uint64", bi_cast_uint64}, + {"float32", bi_cast_f32}, {"float64", bi_cast_f64}, + {"char/utf32", bi_cast_char32}, {"char/utf8", bi_cast_char8}, {"char/ascii", bi_cast_char_ascii}, + /* collection */ + {"array", bi_array}, {"len", bi_len}, {"at", bi_at}, {"set", bi_set}, {"has", bi_has}, + {"array.sort", bi_sort}, {"array.scatter", bi_scatter}, {"array.compact", bi_compact}, + {"array.append", bi_append}, {"array.slice", bi_slice}, + {"dict", bi_dict}, + {"string.set", bi_string_set}, {"string.char", bi_string_char}, {"string.ord", bi_string_ord}, + {"string.cmp", bi_string_cmp}, {"string.find", bi_string_find}, {"string.len", bi_string_len}, + {"string.slice", bi_string_slice}, {"string.concat", bi_string_concat}, + {"time.now", bi_time_now}, {"time.sub", bi_time_sub}, {"time.add", bi_time_add}, + {"time/duration.nanos", bi_dur_from}, {"time/duration.millis", bi_dur_from}, + {"time/duration.seconds", bi_dur_from}, {"time/duration.minutes", bi_dur_from}, + {"time/duration.hours", bi_dur_from}, + {"time/duration.as_nanos", bi_dur_to}, {"time/duration.as_millis", bi_dur_to}, + {"time/duration.as_seconds", bi_dur_to}, {"time/duration.as_minutes", bi_dur_to}, + {"time/duration.as_hours", bi_dur_to}, + {"time/duration.add", bi_dur_arith}, {"time/duration.sub", bi_dur_arith}, + {"time/duration.before", bi_dur_cmp}, {"time/duration.after", bi_dur_cmp}, + {"time.before", bi_time_cmp}, {"time.after", bi_time_cmp}, + {"random.uint64", bi_rand_uint64}, {"random.int63", bi_rand_int63}, {"random.intn", bi_rand_intn}, + {"kvhas", bi_kvhas}, {"kvat", bi_kvat}, + {"debugger", bi_debugger}, +}; + +static const size_t builtins_n = sizeof(builtins) / sizeof(builtins[0]); + +bool bi_is_native(const char *opcode) { + for (size_t i = 0; i < builtins_n; i++) if (strcmp(builtins[i].op, opcode) == 0) return true; + return false; +} + +bool bi_num_op(const char *opcode) { + switch (opcode[0]) { + case 'a': return strcmp(opcode, "add") == 0 || strcmp(opcode, "abs") == 0; + case 'b': return strcmp(opcode, "bitand") == 0 || strcmp(opcode, "bitor") == 0 || strcmp(opcode, "bitxor") == 0; + case 'd': return strcmp(opcode, "div") == 0; + case 'e': return strcmp(opcode, "eq") == 0 || strcmp(opcode, "exp") == 0; + case 'g': return strcmp(opcode, "gt") == 0 || strcmp(opcode, "ge") == 0; + case 'l': return strcmp(opcode, "lt") == 0 || strcmp(opcode, "le") == 0 || strcmp(opcode, "log") == 0; + case 'm': return strcmp(opcode, "mod") == 0 || strcmp(opcode, "mul") == 0 || strcmp(opcode, "max") == 0 || strcmp(opcode, "min") == 0; + case 'n': return strcmp(opcode, "neq") == 0 || strcmp(opcode, "neg") == 0; + case 'p': return strcmp(opcode, "pow") == 0; + case 's': return strcmp(opcode, "sub") == 0 || strcmp(opcode, "sqrt") == 0 || strcmp(opcode, "shl") == 0 || strcmp(opcode, "shr") == 0 || strcmp(opcode, "sign") == 0; + } + return false; +} + +int bi_native(frame_t *f) { + for (size_t i = 0; i < builtins_n; i++) { + if (strcmp(builtins[i].op, f->inst->opcode) == 0) return builtins[i].fn(f); + } + return set_err(f, "unknown builtin op: %s", f->inst->opcode); +} + +/* ── copy ─────────────────────────────────────────────────────────── */ + +int bi_execute_copy(kv_t *kv, const char *vtid, const char *pc, rwir_inst_t *inst) { + char *fr = kt_frame_root(pc); + frame_t f = { kv, vtid, pc, inst }; + if (inst->nr == 0) { free(fr); next_pc(&f); return 0; } + char *ff = bi_func_frame_root(kv, fr); + xval_t v; xv_zero(&v); + bi_resolve_read_value(kv, ff, inst->reads[0].name, &inst->reads[0].val, &v); + free(ff); + for (int i = 0; i < inst->nw; i++) { + char *key = bi_resolve_write_slot(kv, fr, inst->writes[i].name); + kv_pair_t pair = { key, v }; + char err[256]; + kv_set(kv, &pair, 1, err, sizeof err); + free(key); + } + xv_free(&v); + free(fr); + next_pc(&f); + return 0; +} + +/* ── collection helper ────────────────────────────────────────────── */ + +static uint32_t utf8_decode_next(const char *s, size_t *i, size_t len) { + const unsigned char *p = (const unsigned char *)s; + uint32_t cp = p[*i]; + if (cp < 0x80) { (*i)++; return cp; } + int n = 0; + if ((cp & 0xE0) == 0xC0) { n = 1; cp &= 0x1F; } + else if ((cp & 0xF0) == 0xE0) { n = 2; cp &= 0x0F; } + else if ((cp & 0xF8) == 0xF0) { n = 3; cp &= 0x07; } + else { (*i)++; return 0xFFFD; } + (*i)++; + for (int j = 0; j < n && *i < len; j++, (*i)++) cp = (cp << 6) | (p[*i] & 0x3F); + return cp; +} + +static uint32_t *string_runes(const xval_t *v, int *out_n) { + if (xv_kind_is(v, K_CHAR)) { + int n = xv_array_len(v); + uint32_t *r = malloc(sizeof(uint32_t) * (n > 0 ? n : 1)); + for (int i = 0; i < n; i++) r[i] = xv_char32_at(v, i); + *out_n = n; + return r; + } + char *s = xv_value_string(v); + size_t len = strlen(s); + int cap = 16, n = 0; + uint32_t *r = malloc(sizeof(uint32_t) * cap); + size_t i = 0; + while (i < len) { + if (n == cap) { cap *= 2; r = realloc(r, sizeof(uint32_t) * cap); } + r[n++] = utf8_decode_next(s, &i, len); + } + free(s); + *out_n = n; + return r; +} + +static void new_char32_cp(xval_t *out, uint32_t cp) { + uint8_t le[4] = { cp & 0xFF, (cp >> 8) & 0xFF, (cp >> 16) & 0xFF, (cp >> 24) & 0xFF }; + uint8_t *o; uint32_t l; + kvspace_tlv_encode(K_CHAR, le, 4, 1, &o, &l); + out->data = o; out->len = l; +} + +static int write_char32(frame_t *f, const uint32_t *r, int n) { + xval_t e; + if (n > 0) { + sbuf_t raw; sb_init(&raw); + for (int i = 0; i < n; i++) { + uint8_t le[4] = { r[i] & 0xFF, (r[i] >> 8) & 0xFF, (r[i] >> 16) & 0xFF, (r[i] >> 24) & 0xFF }; + sb_putn(&raw, (const char *)le, 4); + } + uint8_t *out; uint32_t len; + kvspace_tlv_encode(K_CHAR, (const uint8_t *)raw.p, (uint32_t)raw.len, n, &out, &len); + sb_free(&raw); + e.data = out; e.len = len; + } else { + uint8_t *out; uint32_t len; + kvspace_tlv_encode(K_CHAR, (const uint8_t *)"", 0, 0, &out, &len); + e.data = out; e.len = len; + } + int rc = write_result(f, &e); + xv_free(&e); + return rc; +} + +static char *kv_key(const xval_t *v) { + if (xv_is_char_kind(xv_kind(v))) return xv_value_string(v); + if (is_int_kind(xv_kind(v))) { char buf[32]; snprintf(buf, sizeof buf, "%lld", (long long)xv_as_int64(v)); return strdup(buf); } + return strdup(""); +} + +static const char *var_len_char_err(const char *kind) { + return strcmp(kind, K_CHAR_UTF8) == 0 ? "TypeError: char/utf8 is variable-width; index/code-point ops require char/utf32 or char/ascii" : NULL; +} + +static void pack_typed_array(const char *kind, const xval_t *elems, int n, xval_t *out) { + int sz = xv_elem_size(kind); + uint8_t *raw = malloc((size_t)sz * (n > 0 ? n : 1)); + for (int i = 0; i < n; i++) { + const uint8_t *b; int32_t blen; + kvhead_t h; kvspace_decode_head(elems[i].data, elems[i].len, &h); + b = elems[i].data + h.body_offset; blen = h.body_len; + int c = blen < sz ? blen : sz; + memcpy(raw + i * sz, b, (size_t)c); + for (int j = c; j < sz; j++) raw[i * sz + j] = 0; + } + xv_new_tlv(out, kind, raw, (uint32_t)(sz * n), n); + free(raw); +} + +static char *separated_base(kv_t *kv, const char *fp, const char *name) { + char *base = bi_resolve_write_slot(kv, fp, name); + sbuf_t k; sb_init(&k); + sb_puts(&k, base); sb_puts(&k, "[0]"); + xval_t v; xv_zero(&v); + kv_get_one(kv, k.p, &v); + bool exists = !xv_none(&v); + xv_free(&v); sb_free(&k); + if (!exists) { free(base); return NULL; } + return base; +} + +static int separated_len(kv_t *kv, const char *base) { + for (int i = 0; ; i++) { + sbuf_t k; sb_init(&k); + sb_printf(&k, "%s[%d]", base, i); + xval_t v; xv_zero(&v); + kv_get_one(kv, k.p, &v); + bool none = xv_none(&v); + xv_free(&v); sb_free(&k); + if (none) return i; + } +} + +/* ── array 系列 ───────────────────────────────────────────────────── */ + +static void ensure_scattered(frame_t *f, const char *base) { + xval_t arr; xv_zero(&arr); + kv_get_one(f->kv, base, &arr); + if (xv_none(&arr) || xv_elem_size(xv_kind(&arr)) <= 0) { xv_free(&arr); return; } + int n = xv_array_len(&arr); + for (int i = 0; i < n; i++) { + sbuf_t k; sb_init(&k); sb_printf(&k, "%s[%d]", base, i); + xval_t e; xvalue_at(&arr, i, &e); + kv_pair_t p = { k.p, e }; + char err[256]; kv_set(f->kv, &p, 1, err, sizeof err); + xv_free(&e); sb_free(&k); + } + xv_free(&arr); + char err[256]; kv_del(f->kv, base, err, sizeof err); +} + +int bi_array(frame_t *f) { + xval_t in[64]; int n = read_inputs(f, in, 64); + if (f->inst->nw == 0 || n == 0) { next_pc(f); free_inputs(in, n); return 0; } + const char *kind = xv_kind(&in[0]); + if (xv_elem_size(kind) <= 0) { free_inputs(in, n); return set_err(f, "array: unsupported element kind %s", kind); } + for (int i = 1; i < n; i++) if (strcmp(xv_kind(&in[i]), kind) != 0) { free_inputs(in, n); return set_err(f, "array: mixed kinds %s and %s", kind, xv_kind(&in[i])); } + xval_t arr; pack_typed_array(kind, in, n, &arr); + char *fr = kt_frame_root(f->pc); + char *key = bi_resolve_write_slot(f->kv, fr, f->inst->writes[0].name); + free(fr); + kv_pair_t p = { key, arr }; + char err[256]; kv_set(f->kv, &p, 1, err, sizeof err); + free(key); xv_free(&arr); + next_pc(f); + free_inputs(in, n); + return 0; +} + +int bi_len(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + int len = 0; + if (n > 0 && xv_none(&in[0]) && f->inst->nr > 0) { + char *fr = kt_frame_root(f->pc); + char *base = separated_base(f->kv, fr, f->inst->reads[0].name); + if (base) { len = separated_len(f->kv, base); free(base); } + free(fr); + } else if (n > 0) len = xv_array_len(&in[0]); + xval_t r; xv_new_int64(&r, len); + int rc = write_result(f, &r); xv_free(&r); free_inputs(in, n); + return rc; +} + +int bi_at(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { free_inputs(in, n); return set_err(f, "TypeError: at requires array and index"); } + const char *k0 = xv_kind(&in[0]), *k1 = xv_kind(&in[1]); + /* 字符串非路径 → 码点索引 */ + if (xv_is_char_kind(k0)) { + char *s = xv_value_string(&in[0]); + bool is_path = s[0] == '/'; + free(s); + if (!is_path) { + if (var_len_char_err(k0)) { free_inputs(in, n); return set_err(f, "%s", var_len_char_err(k0)); } + if (!is_int_kind(k1)) { xval_t e; xv_new_char_utf32(&e, ""); int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); return rc; } + int idx = (int)xv_as_int64(&in[1]); + int rn; uint32_t *r = string_runes(&in[0], &rn); + if (idx < 0 || idx >= rn) { free(r); xval_t e; xv_new_char_utf32(&e, ""); int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); return rc; } + xval_t e; new_char32_cp(&e, r[idx]); + int rc = write_result(f, &e); xv_free(&e); free(r); free_inputs(in, n); return rc; + } + } + /* 路径访问 */ + bool path_access = strcmp(k0, K_DICT) == 0 || xv_is_char_kind(k0) || xv_is_char_kind(k1) || + (f->inst->nr > 0 && (f->inst->reads[0].name[0] == '/' || (f->inst->reads[0].name[0] == '"' && f->inst->reads[0].name[1] == '/'))); + if (path_access) { + char *fr = kt_frame_root(f->pc); + char *ff = bi_func_frame_root(f->kv, fr); + xval_t base; xv_zero(&base); + bi_resolve_read_value(f->kv, ff, f->inst->reads[0].name, &f->inst->reads[0].val, &base); + char *bp = xv_none(&base) || (strcmp(xv_kind(&base), K_DICT) == 0 || strcmp(xv_kind(&base), K_INDEX) == 0 || strcmp(xv_kind(&base), K_EXT_INDEX) == 0) ? bi_resolve_write_slot(f->kv, ff, f->inst->reads[0].name) : xv_value_string(&base); + char *kk = kv_key(&in[1]); + char *path = kt_member(bp, kk); + xval_t v; xv_zero(&v); + kv_get_one(f->kv, path, &v); + int rc = write_result(f, &v); xv_free(&v); + free(path); free(kk); free(bp); xv_free(&base); free(ff); free(fr); + free_inputs(in, n); return rc; + } + if (xv_elem_size(k0) > 0 && xv_is_char_kind(k1)) { free_inputs(in, n); return set_err(f, "IndexError: at: index must be integer for typed array"); } + if (xv_none(&in[0]) && f->inst->nr > 0) { + char *fr = kt_frame_root(f->pc); + char *base = separated_base(f->kv, fr, f->inst->reads[0].name); + if (base) { + int idx = (int)xv_as_int64(&in[1]); + sbuf_t k; sb_init(&k); sb_printf(&k, "%s[%d]", base, idx); + xval_t v; xv_zero(&v); kv_get_one(f->kv, k.p, &v); + int rc = write_result(f, &v); xv_free(&v); sb_free(&k); + free(base); free(fr); free_inputs(in, n); return rc; + } + free(fr); + } + if (xv_none(&in[0])) { free_inputs(in, n); return set_err(f, "IndexError: at: base is None; help: declare a key-family first or pass a path string"); } + int idx = (int)xv_as_int64(&in[1]); + xval_t e; xvalue_at(&in[0], idx, &e); + int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); + return rc; +} + +int bi_has(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { xval_t r; xv_new_bool(&r, false); int rc = write_result(f, &r); xv_free(&r); free_inputs(in, n); return rc; } + char *fr = kt_frame_root(f->pc); + char *ff = bi_func_frame_root(f->kv, fr); + xval_t base; xv_zero(&base); + bi_resolve_read_value(f->kv, ff, f->inst->reads[0].name, &f->inst->reads[0].val, &base); + char *bp = xv_none(&base) || (strcmp(xv_kind(&base), K_DICT) == 0 || strcmp(xv_kind(&base), K_INDEX) == 0) ? bi_resolve_write_slot(f->kv, ff, f->inst->reads[0].name) : xv_value_string(&base); + char *kk = kv_key(&in[1]); + char *path = kt_member(bp, kk); + xval_t v; xv_zero(&v); kv_get_one(f->kv, path, &v); + bool exists = !xv_none(&v); + xval_t r; xv_new_bool(&r, exists); + int rc = write_result(f, &r); xv_free(&r); xv_free(&v); + free(path); free(kk); free(bp); xv_free(&base); free(ff); free(fr); + free_inputs(in, n); return rc; +} + +int bi_set(frame_t *f) { + xval_t in[3]; int n = read_inputs(f, in, 3); + if (n < 3) { free_inputs(in, n); return set_err(f, "TypeError: set requires array, index, value"); } + const char *k0 = xv_kind(&in[0]); + /* 字符串非路径 → 替换码点 */ + if (xv_is_char_kind(k0)) { + char *s = xv_value_string(&in[0]); bool is_path = s[0] == '/'; free(s); + if (!is_path) { + if (var_len_char_err(k0)) { free_inputs(in, n); return set_err(f, "%s", var_len_char_err(k0)); } + int idx = (int)xv_as_int64(&in[1]); + int rn; uint32_t *r = string_runes(&in[0], &rn); + if (idx < 0 || idx >= rn) { free(r); free_inputs(in, n); return set_err(f, "IndexError: set: string index %d out of bounds (len=%d)", idx, rn); } + int rn2; uint32_t *repl = string_runes(&in[2], &rn2); + if (rn2 == 0) { free(repl); free(r); free_inputs(in, n); return set_err(f, "TypeError: set: replacement char is empty"); } + r[idx] = repl[0]; + int rc = write_char32(f, r, rn); + free(repl); free(r); free_inputs(in, n); + return rc; + } + } + /* 路径写入 */ + bool path_access = strcmp(k0, K_DICT) == 0 || xv_is_char_kind(k0) || xv_is_char_kind(xv_kind(&in[1])) || + (f->inst->nr > 0 && (f->inst->reads[0].name[0] == '/' || (f->inst->reads[0].name[0] == '"' && f->inst->reads[0].name[1] == '/'))); + if (path_access) { + char *fr = kt_frame_root(f->pc); + char *ff = bi_func_frame_root(f->kv, fr); + xval_t base; xv_zero(&base); + bi_resolve_read_value(f->kv, ff, f->inst->reads[0].name, &f->inst->reads[0].val, &base); + char *bp = xv_none(&base) || (strcmp(xv_kind(&base), K_DICT) == 0 || strcmp(xv_kind(&base), K_INDEX) == 0) ? bi_resolve_write_slot(f->kv, ff, f->inst->reads[0].name) : xv_value_string(&base); + char *kk = kv_key(&in[1]); + char *path = kt_member(bp, kk); + kv_pair_t p = { path, in[2] }; + char err[256]; kv_set(f->kv, &p, 1, err, sizeof err); + if (f->inst->nw > 0 && !xv_none(&in[0])) { + char *ok = bi_resolve_write_slot(f->kv, fr, f->inst->writes[0].name); + kv_pair_t p2 = { ok, in[0] }; kv_set(f->kv, &p2, 1, err, sizeof err); + free(ok); + } + free(path); free(kk); free(bp); xv_free(&base); free(ff); free(fr); + next_pc(f); free_inputs(in, n); return 0; + } + if (xv_elem_size(k0) > 0 && xv_is_char_kind(xv_kind(&in[1]))) { free_inputs(in, n); return set_err(f, "IndexError: set: index must be integer for typed array"); } + if (xv_none(&in[0]) && f->inst->nr > 0) { + char *fr = kt_frame_root(f->pc); + char *base = separated_base(f->kv, fr, f->inst->reads[0].name); + if (base) { + int idx = (int)xv_as_int64(&in[1]); + sbuf_t k; sb_init(&k); sb_printf(&k, "%s[%d]", base, idx); + kv_pair_t p = { k.p, in[2] }; + char err[256]; kv_set(f->kv, &p, 1, err, sizeof err); + sb_free(&k); free(base); free(fr); + next_pc(f); free_inputs(in, n); return 0; + } + free(fr); + } + if (xv_none(&in[0])) { free_inputs(in, n); return set_err(f, "IndexError: set: base is None"); } + if (xv_elem_size(k0) > 0) { + /* 整存整取改元素 */ + char *fr = kt_frame_root(f->pc); + char *key = bi_resolve_write_slot(f->kv, fr, f->inst->reads[0].name); + xval_t arr; xv_zero(&arr); kv_get_one(f->kv, key, &arr); + if (!xv_none(&arr)) { + int idx = (int)xv_as_int64(&in[1]); + int sz = xv_elem_size(k0); + kvhead_t h; kvspace_decode_head(arr.data, arr.len, &h); + const uint8_t *body = arr.data + h.body_offset; + int al = xv_array_len(&arr); + if (idx >= 0 && idx < al) { + uint8_t *nb = malloc((size_t)h.body_len); + memcpy(nb, body, (size_t)h.body_len); + const uint8_t *vb; int32_t vbl; kvhead_t vh; kvspace_decode_head(in[2].data, in[2].len, &vh); vb = in[2].data + vh.body_offset; vbl = vh.body_len; + int c = vbl < sz ? vbl : sz; + memcpy(nb + idx * sz, vb, (size_t)c); + xval_t nv; xv_new_tlv(&nv, k0, nb, (uint32_t)h.body_len, al); + kv_pair_t p = { key, nv }; + char err[256]; kv_set(f->kv, &p, 1, err, sizeof err); + xv_free(&nv); free(nb); + } + } + xv_free(&arr); free(key); free(fr); + next_pc(f); free_inputs(in, n); return 0; + } + free_inputs(in, n); return set_err(f, "IndexError: set: unsupported array kind %s", k0); +} + +int bi_sort(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 1) { xval_t r; xv_zero(&r); int rc = write_result(f, &r); free_inputs(in, n); return rc; } + int al = xv_array_len(&in[0]); + if (al <= 1) { int rc = write_result(f, &in[0]); free_inputs(in, n); return rc; } + xval_t *elems = malloc(sizeof(xval_t) * al); + for (int i = 0; i < al; i++) { xvalue_at(&in[0], i, &elems[i]); } + for (int i = 0; i < al - 1; i++) + for (int j = 0; j < al - i - 1; j++) + if (xv_as_float64(&elems[j]) > xv_as_float64(&elems[j + 1])) { xval_t t = elems[j]; elems[j] = elems[j + 1]; elems[j + 1] = t; } + xval_t r; pack_typed_array(xv_kind(&in[0]), elems, al, &r); + int rc = write_result(f, &r); xv_free(&r); + for (int i = 0; i < al; i++) xv_free(&elems[i]); + free(elems); free_inputs(in, n); return rc; +} + +int bi_scatter(frame_t *f) { + if (f->inst->nw == 0) return set_err(f, "TypeError: array.scatter requires a write param"); + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n == 0 || xv_none(&in[0])) { next_pc(f); free_inputs(in, n); return 0; } + if (xv_elem_size(xv_kind(&in[0])) <= 0) { free_inputs(in, n); return set_err(f, "TypeError: array.scatter requires a compact array ([]T), got %s", xv_kind(&in[0])); } + char *fr = kt_frame_root(f->pc); + char *dst = bi_resolve_write_slot(f->kv, fr, f->inst->writes[0].name); + int al = xv_array_len(&in[0]); + for (int i = 0; i < al; i++) { + sbuf_t k; sb_init(&k); sb_printf(&k, "%s[%d]", dst, i); + xval_t e; xvalue_at(&in[0], i, &e); + kv_pair_t p = { k.p, e }; char err[256]; kv_set(f->kv, &p, 1, err, sizeof err); + xv_free(&e); sb_free(&k); + } + free(dst); free(fr); + next_pc(f); free_inputs(in, n); return 0; +} + +int bi_compact(frame_t *f) { + if (f->inst->nr == 0 || f->inst->nw == 0) return set_err(f, "TypeError: array.compact requires read and write params"); + char *fr = kt_frame_root(f->pc); + char *src = bi_resolve_write_slot(f->kv, fr, f->inst->reads[0].name); + xval_t elems[1024]; int n = 0; + for (int i = 0; ; i++) { + sbuf_t k; sb_init(&k); sb_printf(&k, "%s[%d]", src, i); + xval_t v; xv_zero(&v); kv_get_one(f->kv, k.p, &v); + bool none = xv_none(&v); sb_free(&k); + if (none) break; + elems[n++] = v; + } + if (n == 0) { free(src); free(fr); next_pc(f); return 0; } + xval_t arr; pack_typed_array(xv_kind(&elems[0]), elems, n, &arr); + char *dst = bi_resolve_write_slot(f->kv, fr, f->inst->writes[0].name); + kv_pair_t p = { dst, arr }; char err[256]; kv_set(f->kv, &p, 1, err, sizeof err); + for (int i = 0; i < n; i++) xv_free(&elems[i]); + xv_free(&arr); + free(dst); free(src); free(fr); + next_pc(f); return 0; +} + +int bi_append(frame_t *f) { + if (f->inst->nr < 2) return set_err(f, "TypeError: array.append requires array and element"); + xval_t in[2]; int n = read_inputs(f, in, 2); + char *fr = kt_frame_root(f->pc); + char *base = bi_resolve_write_slot(f->kv, fr, f->inst->reads[0].name); + ensure_scattered(f, base); + int len = separated_len(f->kv, base); + sbuf_t k; sb_init(&k); sb_printf(&k, "%s[%d]", base, len); + kv_pair_t p = { k.p, n >= 2 ? in[1] : in[0] }; + char err[256]; kv_set(f->kv, &p, 1, err, sizeof err); + sb_free(&k); free(base); free(fr); + next_pc(f); free_inputs(in, n); return 0; +} + +int bi_slice(frame_t *f) { + if (f->inst->nr < 3) return set_err(f, "TypeError: array.slice requires array, start, end"); + xval_t in[3]; int n = read_inputs(f, in, 3); + char *fr = kt_frame_root(f->pc); + char *base = bi_resolve_write_slot(f->kv, fr, f->inst->reads[0].name); + ensure_scattered(f, base); + int al = separated_len(f->kv, base); + int lo = (int)xv_as_int64(&in[1]), hi = (int)xv_as_int64(&in[2]); + if (lo < 0 || hi < lo || hi > al) { free(base); free(fr); free_inputs(in, n); return set_err(f, "IndexError: array.slice: bounds [%d:%d] out of range (len=%d)", lo, hi, al); } + for (int i = lo; i < hi; i++) { + sbuf_t sk; sb_init(&sk); sb_printf(&sk, "%s[%d]", base, i); + xval_t v; xv_zero(&v); kv_get_one(f->kv, sk.p, &v); + sbuf_t dk; sb_init(&dk); sb_printf(&dk, "%s[%d]", base, i - lo); + kv_pair_t p = { dk.p, v }; char err[256]; kv_set(f->kv, &p, 1, err, sizeof err); + xv_free(&v); sb_free(&sk); sb_free(&dk); + } + for (int i = hi - lo; i < al; i++) { + sbuf_t dk; sb_init(&dk); sb_printf(&dk, "%s[%d]", base, i); + char err[256]; kv_del(f->kv, dk.p, err, sizeof err); sb_free(&dk); + } + free(base); free(fr); + next_pc(f); free_inputs(in, n); return 0; +} + +/* ── dict ─────────────────────────────────────────────────────────── */ + +int bi_dict(frame_t *f) { + xval_t in[64]; int n = read_inputs(f, in, 64); + char *fr = kt_frame_root(f->pc); + for (int w = 0; w < f->inst->nw; w++) { + char *ok = bi_resolve_write_slot(f->kv, fr, f->inst->writes[w].name); + xval_t mark; xv_new_tlv(&mark, K_DICT, (const uint8_t *)"", 0, 1); + kv_pair_t p0 = { ok, mark }; + char err[256]; kv_set(f->kv, &p0, 1, err, sizeof err); + xv_free(&mark); + for (int i = 0; i + 1 < n; i += 2) { + if (xv_none(&in[i + 1])) continue; + char *k = xv_value_string(&in[i]); + char *mk = kt_member(ok, k); + kv_pair_t p = { mk, in[i + 1] }; + kv_set(f->kv, &p, 1, err, sizeof err); + free(k); free(mk); + } + free(ok); + } + free(fr); + next_pc(f); free_inputs(in, n); return 0; +} + +/* ── string 系列 ──────────────────────────────────────────────────── */ + +int bi_string_set(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + char *s = NULL; + if (n > 0) display(&in[0], &s); + else s = strdup(""); + if (f->inst->nw > 0) { + char *fr = kt_frame_root(f->pc); + char *key = bi_resolve_write_slot(f->kv, fr, f->inst->writes[0].name); + xval_t v; xv_new_char_utf32(&v, s); + kv_pair_t p = { key, v }; + char err[256]; kv_set(f->kv, &p, 1, err, sizeof err); + xv_free(&v); free(key); free(fr); + } + free(s); + next_pc(f); free_inputs(in, n); return 0; +} + +int bi_string_char(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { free_inputs(in, n); return set_err(f, "TypeError: string.char requires string and index"); } + if (var_len_char_err(xv_kind(&in[0]))) { free_inputs(in, n); return set_err(f, "%s", var_len_char_err(xv_kind(&in[0]))); } + int idx = (int)xv_as_int64(&in[1]); + int rn; uint32_t *r = string_runes(&in[0], &rn); + int rc; + if (idx < 0 || idx >= rn) rc = set_err(f, "IndexError: at: index %d out of bounds (char count=%d)", idx, rn); + else { uint32_t one = r[idx]; rc = write_char32(f, &one, 1); } + free(r); free_inputs(in, n); + return rc; +} + +int bi_string_ord(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 1) { free_inputs(in, n); return set_err(f, "TypeError: string.ord requires a string"); } + if (var_len_char_err(xv_kind(&in[0]))) { free_inputs(in, n); return set_err(f, "%s", var_len_char_err(xv_kind(&in[0]))); } + int rn; uint32_t *r = string_runes(&in[0], &rn); + xval_t e; xv_new_int64(&e, rn == 0 ? -1 : (int64_t)r[0]); + int rc = write_result(f, &e); xv_free(&e); free(r); free_inputs(in, n); + return rc; +} + +int bi_string_cmp(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { free_inputs(in, n); return set_err(f, "TypeError: string.cmp requires two strings"); } + char *a = xv_value_string(&in[0]), *b = xv_value_string(&in[1]); + int64_t r = strcmp(a, b) < 0 ? -1 : strcmp(a, b) > 0 ? 1 : 0; + xval_t e; xv_new_int64(&e, r); + int rc = write_result(f, &e); xv_free(&e); free(a); free(b); free_inputs(in, n); + return rc; +} + +int bi_string_find(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { free_inputs(in, n); return set_err(f, "TypeError: string.find requires two strings"); } + if (var_len_char_err(xv_kind(&in[0]))) { free_inputs(in, n); return set_err(f, "%s", var_len_char_err(xv_kind(&in[0]))); } + int hn; uint32_t *hay = string_runes(&in[0], &hn); + int nn; uint32_t *needle = string_runes(&in[1], &nn); + int64_t r = -1; + if (nn == 0) r = 0; + else for (int i = 0; i + nn <= hn; i++) { bool m = true; for (int j = 0; j < nn; j++) if (hay[i + j] != needle[j]) { m = false; break; } if (m) { r = i; break; } } + xval_t e; xv_new_int64(&e, r); + int rc = write_result(f, &e); xv_free(&e); free(hay); free(needle); free_inputs(in, n); + return rc; +} + +int bi_string_len(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + int len = 0; + if (n > 0) { + if (var_len_char_err(xv_kind(&in[0]))) { free_inputs(in, n); return set_err(f, "%s", var_len_char_err(xv_kind(&in[0]))); } + if (xv_kind_is(&in[0], K_CHAR)) len = xv_array_len(&in[0]); + else { uint32_t *r = string_runes(&in[0], &len); free(r); } + } + xval_t e; xv_new_int64(&e, len); + int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); + return rc; +} + +int bi_string_slice(frame_t *f) { + xval_t in[3]; int n = read_inputs(f, in, 3); + if (n < 3) { free_inputs(in, n); return set_err(f, "TypeError: string.slice requires string, start, end"); } + if (var_len_char_err(xv_kind(&in[0]))) { free_inputs(in, n); return set_err(f, "%s", var_len_char_err(xv_kind(&in[0]))); } + int lo = (int)xv_as_int64(&in[1]), hi = (int)xv_as_int64(&in[2]); + int rn; uint32_t *r = string_runes(&in[0], &rn); + if (lo < 0 || hi > rn || lo > hi) { free(r); free_inputs(in, n); return set_err(f, "IndexError: at: slice index out of bounds (lo=%d hi=%d char count=%d)", lo, hi, rn); } + write_char32(f, r + lo, hi - lo); + free(r); free_inputs(in, n); + return 0; +} + +int bi_string_concat(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2 || !xv_is_char_kind(xv_kind(&in[0])) || !xv_is_char_kind(xv_kind(&in[1]))) { + if (n >= 2) set_err(f, "TypeError: string.concat requires strings, got %s and %s", xv_kind(&in[0]), xv_kind(&in[1])); + else { xval_t e; xv_new_char_utf32(&e, ""); write_result(f, &e); xv_free(&e); } + free_inputs(in, n); return n < 2 ? 0 : -1; + } + int a, b; uint32_t *ra = string_runes(&in[0], &a); uint32_t *rb = string_runes(&in[1], &b); + uint32_t *r = malloc(sizeof(uint32_t) * (a + b)); + memcpy(r, ra, sizeof(uint32_t) * a); memcpy(r + a, rb, sizeof(uint32_t) * b); + write_char32(f, r, a + b); + free(r); free(ra); free(rb); free_inputs(in, n); + return 0; +} + +/* ── time / duration ──────────────────────────────────────────────── */ + +static int64_t now_nanos(void) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +static void new_time(xval_t *v, int64_t ns) { + uint8_t r[8]; memcpy(r, &ns, 8); + xv_new_tlv(v, K_TIME, r, 8, 1); +} +static void new_duration(xval_t *v, int64_t ns) { + uint8_t r[8]; memcpy(r, &ns, 8); + xv_new_tlv(v, K_DURATION, r, 8, 1); +} + +int bi_time_now(frame_t *f) { + xval_t e; new_time(&e, now_nanos()); + int rc = write_result(f, &e); xv_free(&e); + return rc; +} + +int bi_time_sub(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { free_inputs(in, n); return set_err(f, "TypeError: time.sub requires 2 time args"); } + xval_t e; new_duration(&e, xv_as_int64(&in[0]) - xv_as_int64(&in[1])); + int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); + return rc; +} + +int bi_time_add(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { free_inputs(in, n); return set_err(f, "TypeError: time.add requires time and duration"); } + xval_t e; new_time(&e, xv_as_int64(&in[0]) + xv_as_int64(&in[1])); + int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); + return rc; +} + +static int64_t dur_scale(const char *op) { + if (strstr(op, "nanos")) return 1; + if (strstr(op, "millis")) return 1000000; + if (strstr(op, "seconds")) return 1000000000; + if (strstr(op, "minutes")) return 60000000000LL; + if (strstr(op, "hours")) return 3600000000000LL; + return 1; +} + +int bi_dur_from(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 1) { free_inputs(in, n); return set_err(f, "TypeError: time/duration.from requires 1 int64 arg"); } + xval_t e; new_duration(&e, xv_as_int64(&in[0]) * dur_scale(f->inst->opcode)); + int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); + return rc; +} + +int bi_dur_to(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 1) { free_inputs(in, n); return set_err(f, "TypeError: time/duration.as requires 1 duration arg"); } + xval_t e; xv_new_int64(&e, xv_as_int64(&in[0]) / dur_scale(f->inst->opcode)); + int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); + return rc; +} + +int bi_dur_arith(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { free_inputs(in, n); return set_err(f, "TypeError: time/duration arith requires 2 duration args"); } + int64_t a = xv_as_int64(&in[0]), b = xv_as_int64(&in[1]); + bool sub = strstr(f->inst->opcode, ".sub") != NULL; + xval_t e; new_duration(&e, sub ? a - b : a + b); + int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); + return rc; +} + +int bi_dur_cmp(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { free_inputs(in, n); return set_err(f, "TypeError: time/duration cmp requires 2 duration args"); } + bool before = strstr(f->inst->opcode, "before") != NULL; + int64_t a = xv_as_int64(&in[0]), b = xv_as_int64(&in[1]); + xval_t e; xv_new_bool(&e, before ? a < b : a > b); + int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); + return rc; +} + +int bi_time_cmp(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { free_inputs(in, n); return set_err(f, "TypeError: time cmp requires 2 args"); } + bool before = strstr(f->inst->opcode, "before") != NULL; + int64_t a = xv_as_int64(&in[0]), b = xv_as_int64(&in[1]); + xval_t e; xv_new_bool(&e, before ? a < b : a > b); + int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); + return rc; +} + +/* ── random ───────────────────────────────────────────────────────── */ + +static uint64_t crypto_rand_u64(void) { + uint64_t v = 0; + FILE *fp = fopen("/dev/urandom", "rb"); + if (fp) { fread(&v, 8, 1, fp); fclose(fp); } + return v; +} + +int bi_rand_uint64(frame_t *f) { + uint64_t v = crypto_rand_u64(); + uint8_t r[8]; memcpy(r, &v, 8); + xval_t e; xv_new_tlv(&e, K_UINT64, r, 8, 1); + int rc = write_result(f, &e); xv_free(&e); + return rc; +} + +int bi_rand_int63(frame_t *f) { + int64_t v = (int64_t)(crypto_rand_u64() >> 1); + xval_t e; xv_new_int64(&e, v); + int rc = write_result(f, &e); xv_free(&e); + return rc; +} + +int bi_rand_intn(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 1 || xv_none(&in[0])) { free_inputs(in, n); return set_err(f, "TypeError: random.intn requires 1 int64 arg"); } + uint64_t m = (uint64_t)xv_as_int64(&in[0]); + uint64_t v = m == 0 ? 0 : crypto_rand_u64() % m; + uint8_t r[8]; memcpy(r, &v, 8); + xval_t e; xv_new_tlv(&e, K_UINT64, r, 8, 1); + int rc = write_result(f, &e); xv_free(&e); free_inputs(in, n); + return rc; +} + +/* ── kvop ─────────────────────────────────────────────────────────── */ + +int bi_kvhas(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { free_inputs(in, n); return set_err(f, "TypeError: kvhas requires 2 args"); } + char *fr = kt_frame_root(f->pc); + char *ff = bi_func_frame_root(f->kv, fr); + xval_t base; xv_zero(&base); + bi_resolve_read_value(f->kv, ff, f->inst->reads[0].name, &f->inst->reads[0].val, &base); + char *bp = xv_none(&base) || (strcmp(xv_kind(&base), K_DICT) == 0 || strcmp(xv_kind(&base), K_INDEX) == 0 || strcmp(xv_kind(&base), K_EXT_INDEX) == 0) ? bi_resolve_write_slot(f->kv, ff, f->inst->reads[0].name) : xv_value_string(&base); + char *kk = kv_key(&in[1]); + char *path = kt_member(bp, kk); + xval_t v; xv_zero(&v); kv_get_one(f->kv, path, &v); + xval_t e; xv_new_bool(&e, !xv_none(&v)); + int rc = write_result(f, &e); xv_free(&e); xv_free(&v); + free(path); free(kk); free(bp); xv_free(&base); free(ff); free(fr); + free_inputs(in, n); return rc; +} + +int bi_kvat(frame_t *f) { + xval_t in[2]; int n = read_inputs(f, in, 2); + if (n < 2) { free_inputs(in, n); return set_err(f, "TypeError: kvat requires 2 args"); } + char *fr = kt_frame_root(f->pc); + char *ff = bi_func_frame_root(f->kv, fr); + xval_t base; xv_zero(&base); + bi_resolve_read_value(f->kv, ff, f->inst->reads[0].name, &f->inst->reads[0].val, &base); + char *bp = xv_none(&base) || (strcmp(xv_kind(&base), K_DICT) == 0 || strcmp(xv_kind(&base), K_INDEX) == 0 || strcmp(xv_kind(&base), K_EXT_INDEX) == 0) ? bi_resolve_write_slot(f->kv, ff, f->inst->reads[0].name) : xv_value_string(&base); + char *kk = kv_key(&in[1]); + char *path = kt_member(bp, kk); + xval_t v; xv_zero(&v); kv_get_one(f->kv, path, &v); + if (xv_none(&v)) { free(path); free(kk); free(bp); xv_free(&base); free(ff); free(fr); free_inputs(in, n); return set_err(f, "KeyError: kvat: key not found: %s", path); } + int rc = write_result(f, &v); xv_free(&v); + free(path); free(kk); free(bp); xv_free(&base); free(ff); free(fr); + free_inputs(in, n); return rc; +} + +/* ── debugger ─────────────────────────────────────────────────────── */ + +int bi_debugger(frame_t *f) { + sbuf_t dk; sb_init(&dk); + kt_vthread_debugger(f->vtid, &dk); + xval_t v; xv_zero(&v); kv_get_one(f->kv, dk.p, &v); + bool dbg = !xv_none(&v); + xv_free(&v); + if (!dbg) { sb_free(&dk); next_pc(f); return 0; } + sb_free(&dk); + next_pc(f); + return 0; +} diff --git a/runtime/src/keytree.c b/runtime/src/keytree.c new file mode 100644 index 00000000..0492b6b8 --- /dev/null +++ b/runtime/src/keytree.c @@ -0,0 +1,129 @@ +#include "runtime_internal.h" + +const char *kt_vtid_from_pc(const char *pc, sbuf_t *out) { + sb_clear(out); + const char *pfx = VTHREAD_ROOT PATH_SEP; + size_t pl = strlen(pfx); + if (strncmp(pc, pfx, pl) != 0) return ""; + const char *rest = pc + pl; + const char *slash = strchr(rest, '/'); + if (slash) sb_putn(out, rest, (size_t)(slash - rest)); + else sb_puts(out, rest); + return out->p; +} + +char *kt_stack(const char *root) { + size_t n = strlen(root); + while (n > 0 && root[n - 1] == '/') n--; + char *r = malloc(n + 2); + memcpy(r, root, n); r[n] = '/'; r[n + 1] = 0; + return r; +} + +char *kt_frame_root(const char *pc) { + const char *last = NULL; + for (const char *p = pc; (p = strstr(p, "/[")) != NULL; p += 2) last = p; + if (!last) return NULL; + size_t n = (size_t)(last - pc); + char *r = malloc(n + 1); + memcpy(r, pc, n); r[n] = 0; + return r; +} + +static char *trim_right_join(const char *root, const char *suffix) { + size_t n = strlen(root); + while (n > 0 && root[n - 1] == '/') n--; + size_t sl = strlen(suffix); + char *r = malloc(n + sl + 1); + memcpy(r, root, n); memcpy(r + n, suffix, sl); r[n + sl] = 0; + return r; +} + +char *kt_entry_pc(const char *root) { return trim_right_join(root, "/[1,0]"); } +char *kt_scope_entry_pc(const char *root) { return trim_right_join(root, "/[0,0]"); } + +char *kt_parent_frame(const char *root) { + size_t n = strlen(root); + while (n > 0 && root[n - 1] == '/') n--; + if (n == 0) return strdup(""); + size_t last = (size_t)-1; + for (size_t i = 0; i < n; i++) if (root[i] == '/') last = i; + if (last == (size_t)-1) return strdup(""); + size_t cut = last + 1; + char *r = malloc(cut + 1); + memcpy(r, root, cut); r[cut] = 0; + return r; +} + +char *kt_member(const char *base, const char *name) { + size_t bl = strlen(base), nl = strlen(name); + char *r = malloc(bl + nl + 2); + memcpy(r, base, bl); r[bl] = '.'; memcpy(r + bl + 1, name, nl); r[bl + 1 + nl] = 0; + return r; +} + +char *kt_lib_func(const char *pkg, const char *name) { + sbuf_t b; sb_init(&b); + sb_puts(&b, LIB_ROOT PATH_SEP); + if (pkg && pkg[0]) { sb_puts(&b, pkg); sb_putc(&b, '.'); } + sb_puts(&b, name); + return sb_detach(&b); +} + +char *kt_rwir(const char *opcode) { + sbuf_t b; sb_init(&b); + sb_puts(&b, LIB_ROOT PATH_SEP); + sb_puts(&b, opcode); + return sb_detach(&b); +} + +void kt_vthread(const char *vtid, sbuf_t *out) { + sb_clear(out); + sb_puts(out, VTHREAD_ROOT PATH_SEP); + sb_puts(out, vtid); +} + +void kt_vthread_slot(const char *vtid, const char *frame, int i, int j, sbuf_t *out) { + sb_clear(out); + sb_puts(out, VTHREAD_ROOT PATH_SEP); + sb_puts(out, vtid); + if (frame && frame[0]) { sb_putc(out, '/'); sb_puts(out, frame); } + sb_printf(out, "/[%d,%d]", i, j); +} + +static void vt_member(const char *vtid, const char *seg, sbuf_t *out) { + sb_clear(out); + sb_puts(out, VTHREAD_ROOT PATH_SEP); + sb_puts(out, vtid); + sb_putc(out, '/'); + sb_puts(out, RUNTIME_MEMBER_SEP); + sb_puts(out, seg); +} + +void kt_vthread_pc(const char *vtid, sbuf_t *out) { vt_member(vtid, SEG_PC, out); } +void kt_vthread_status(const char *vtid, sbuf_t *out) { vt_member(vtid, SEG_STATUS, out); } +void kt_vthread_debugger(const char *vtid, sbuf_t *out) { vt_member(vtid, "debugger", out); } + +void kt_vthread_status_msg(const char *vtid, const char *status, sbuf_t *out) { + vt_member(vtid, status, out); + sb_putc(out, '/'); + sb_puts(out, SEG_MSG); +} + +static void frame_member(const char *root, const char *seg, sbuf_t *out) { + sb_clear(out); + char *s = kt_stack(root); + sb_puts(out, s); + free(s); + sb_puts(out, RUNTIME_MEMBER_SEP); + sb_puts(out, seg); +} + +void kt_frame_callpc(const char *root, sbuf_t *out) { frame_member(root, SEG_CALLPC, out); } +void kt_frame_returnpc(const char *root, sbuf_t *out) { frame_member(root, SEG_RETURNPC, out); } +void kt_frame_ro(const char *root, sbuf_t *out) { frame_member(root, SEG_RO, out); } + +bool kt_is_entry_pc(const char *pc) { + const char *slash = strrchr(pc, '/'); + return slash && strcmp(slash, "/[1,0]") == 0; +} diff --git a/runtime/src/kv.c b/runtime/src/kv.c new file mode 100644 index 00000000..9291d2bc --- /dev/null +++ b/runtime/src/kv.c @@ -0,0 +1,128 @@ +#include "runtime_internal.h" + +/* kv 访问统一走 kvspace-durable 兼容 C ABI(kvspace_*)。 + * 后端由链接的 kvspace 库决定(kvspace-durable / kvspace-c 均导出同一 ABI)。 */ + +static void xv_copy_malloc(xval_t *out, const uint8_t *d, uint32_t len) { + if (len > 0) { + out->data = malloc(len); + memcpy(out->data, d, len); + out->len = len; + } +} + +kv_t *kv_connect(const char *dsn) { + kv_t *k = calloc(1, sizeof(*k)); + k->h = kvspace_conn(dsn); + if (!k->h) { free(k); return NULL; } + return k; +} + +void kv_disconnect(kv_t *k) { + if (!k) return; + if (k->h) kvspace_free(k->h); + free(k); +} + +int kv_get_one(kv_t *k, const char *key, xval_t *out) { + xv_zero(out); + uint8_t *d; uint32_t len; + if (kvspace_get_one(k->h, key, &d, &len) != 0) return -1; + xv_copy_malloc(out, d, len); + kvspace_bytes_free(d, len); + return 0; +} + +int kv_get_batch(kv_t *k, const char *prefix, char **names, int n, xval_t *out) { + for (int i = 0; i < n; i++) xv_zero(&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 = kvspace_get_batch(k->h, prefix, ns, (uint32_t)n, &d, &len); + free(ns); + if (rc != 0) return rc; + uint32_t off = 0; + 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) xv_copy_malloc(&out[i], d + off, vl); + off += vl; + } + kvspace_bytes_free(d, len); + return 0; +} + +int kv_set(kv_t *k, const kv_pair_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; + } + int rc = kvspace_set(k->h, keys, vals, lens, (uint32_t)n, err, err_cap); + free(keys); free(lens); free(vals); + return rc; +} + +int kv_del(kv_t *k, const char *key, char *err, uint32_t err_cap) { + const char *keys[1] = { key }; + return kvspace_del(k->h, keys, 1, err, err_cap); +} + +int kv_del_tree(kv_t *k, const char *prefix, char *err, uint32_t err_cap) { + return kvspace_del_tree(k->h, prefix, err, err_cap); +} + +int kv_mkindex(kv_t *k, const char *path, char *err, uint32_t err_cap) { + return kvspace_mkindex(k->h, path, err, err_cap); +} + +int kv_ext_index(kv_t *k, const char *path, const char *ext, char *err, uint32_t err_cap) { + return kvspace_ext_index(k->h, path, ext, err, err_cap); +} + +int kv_del_ext_index(kv_t *k, const char *path, char *err, uint32_t err_cap) { + return kvspace_del_ext_index(k->h, path, err, err_cap); +} + +int kv_list(kv_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 (kvspace_list(k->h, prefix, expand_ext ? 1 : 0, resolve ? 1 : 0, &d, &len) != 0) return -1; + if (len == 0) { if (d) kvspace_bytes_free(d, len); return 0; } + char *s = malloc((size_t)len + 1); + memcpy(s, d, len); s[len] = 0; + kvspace_bytes_free(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); + 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; + return 0; +} + +int kv_watch(kv_t *k, const char *key, const xval_t *target, uint64_t tick_ns, xval_t *out) { + xv_zero(out); + const uint8_t *t = target->data ? target->data : (const uint8_t *)""; + uint32_t tl = target->len; + uint8_t *d; uint32_t len; + if (kvspace_watch(k->h, key, t, tl, tick_ns, &d, &len) != 0) return -1; + xv_copy_malloc(out, d, len); + kvspace_bytes_free(d, len); + return 0; +} diff --git a/runtime/src/kvcpu.c b/runtime/src/kvcpu.c new file mode 100644 index 00000000..a088455e --- /dev/null +++ b/runtime/src/kvcpu.c @@ -0,0 +1,537 @@ +#include "runtime_internal.h" + +static int stack_depth(const char *pc) { + int d = 0; + for (; *pc; pc++) if (*pc == '[') d++; + return d; +} + +/* ExtKind:有 .lib → rwfunc,否则空 */ +static const char *ext_kind(kv_t *kv, const char *frame_root) { + sbuf_t k; sb_init(&k); + char *stk = kt_stack(frame_root); + sb_puts(&k, stk); free(stk); + sb_puts(&k, SEG_LIB); + xval_t v; xv_zero(&v); + kv_get_one(kv, k.p, &v); + bool has = !xv_none(&v); + xv_free(&v); sb_free(&k); + return has ? K_RWFUNC : ""; +} + +static bool is_literal(const char *s) { + if (!s || !s[0]) return false; + return s[0] == '"' || s[0] == '/' || strcmp(s, "true") == 0 || strcmp(s, "false") == 0 || + strcmp(s, "null") == 0 || (s[0] >= '0' && s[0] <= '9') || (s[0] == '-' && s[1]); +} + +static char *frame_slot_key(const char *frame_root, const char *slot) { + if (!slot || !slot[0]) return NULL; + if (slot[0] == '/') return strdup(slot); + if (slot[0] == '.') return NULL; + sbuf_t b; sb_init(&b); + char *stk = kt_stack(frame_root); + sb_puts(&b, stk); free(stk); + sb_puts(&b, slot); + return sb_detach(&b); +} + +static char *resolve_read_path(kv_t *kv, const char *frame_path, const char *name) { + if (is_literal(name)) return NULL; + char *func_frame = bi_func_frame_root(kv, frame_path); + sbuf_t k; sb_init(&k); + char *stk = kt_stack(func_frame); + sb_puts(&k, stk); free(stk); + sb_puts(&k, name); + xval_t v; xv_zero(&v); + kv_get_one(kv, k.p, &v); + char *result = NULL; + if (xv_none(&v)) { + result = frame_slot_key(func_frame, name); + } else if (xv_is_ptr(&v)) { + char *target = xv_ptr_target(&v); + sbuf_t path; sb_init(&path); + char *stk2 = kt_stack(func_frame); + sb_puts(&path, stk2); free(stk2); + sb_puts(&path, target); + free(target); + for (;;) { + xval_t nv; xv_zero(&nv); + kv_get_one(kv, path.p, &nv); + if (xv_none(&nv) || !xv_is_char_kind(xv_kind(&nv))) { result = sb_detach(&path); xv_free(&nv); break; } + char *p2 = xv_value_string(&nv); + xv_free(&nv); + sb_clear(&path); sb_puts(&path, p2); + free(p2); + } + } else { + sbuf_t b; sb_init(&b); + char *stk3 = kt_stack(func_frame); + sb_puts(&b, stk3); free(stk3); + sb_puts(&b, name); + result = sb_detach(&b); + } + xv_free(&v); sb_free(&k); free(func_frame); + return result; +} + +static char *handle_scope_return(kv_t *kv, const char *pc) { + char *fr = kt_frame_root(pc); + sbuf_t rk; sb_init(&rk); + kt_frame_returnpc(fr, &rk); + xval_t v; xv_zero(&v); + kv_get_one(kv, rk.p, &v); + char *parent = xv_none(&v) ? NULL : xv_value_string(&v); + xv_free(&v); + char *stk = kt_stack(fr); + char err[256]; + kv_del_ext_index(kv, stk, err, sizeof err); + kv_del_tree(kv, fr, err, sizeof err); + free(stk); free(fr); sb_free(&rk); + return parent; +} + +static char *handle_return(kv_t *kv, const char *pc) { + sbuf_t vtid_b; sb_init(&vtid_b); + const char *vtid = kt_vtid_from_pc(pc, &vtid_b); + sbuf_t vtroot; sb_init(&vtroot); + kt_vthread(vtid, &vtroot); + char *fr = kt_frame_root(pc); + if (strcmp(fr, vtroot.p) == 0) { free(fr); sb_free(&vtid_b); sb_free(&vtroot); return NULL; } + sbuf_t rk; sb_init(&rk); + kt_frame_returnpc(fr, &rk); + xval_t v; xv_zero(&v); + kv_get_one(kv, rk.p, &v); + char *next = xv_none(&v) ? strdup("") : xv_value_string(&v); + xv_free(&v); + char *stk = kt_stack(fr); + char err[256]; + kv_del_ext_index(kv, stk, err, sizeof err); + kv_del_tree(kv, fr, err, sizeof err); + free(stk); free(fr); sb_free(&rk); + sb_free(&vtid_b); sb_free(&vtroot); + return next; +} + +static char *handle_scope(kv_t *kv, const char *pc, const char *scope_name) { + char *fr = kt_frame_root(pc); + char *rw_root = bi_func_frame_root(kv, fr); + free(fr); + size_t n = strlen(rw_root); + while (n > 0 && rw_root[n - 1] == '/') n--; + sbuf_t scope_frame; sb_init(&scope_frame); + sb_putn(&scope_frame, rw_root, n); + sb_putc(&scope_frame, '/'); + sb_puts(&scope_frame, scope_name); + sb_putc(&scope_frame, '/'); + free(rw_root); + + sbuf_t callpc; sb_init(&callpc); + kt_frame_callpc(scope_frame.p, &callpc); + xval_t v; xv_zero(&v); + kv_get_one(kv, callpc.p, &v); + bool exists = !xv_none(&v); + xv_free(&v); + + char err[256]; + if (!exists) { + kv_mkindex(kv, scope_frame.p, err, sizeof err); + sbuf_t npc; sb_init(&npc); + rwir_next_pc(pc, &npc); + sbuf_t retpc; sb_init(&retpc); + kt_frame_returnpc(scope_frame.p, &retpc); + xval_t rv; xv_new_char_utf8(&rv, npc.p); + kv_pair_t p = { retpc.p, rv }; + kv_set(kv, &p, 1, err, sizeof err); + xv_free(&rv); sb_free(&npc); sb_free(&retpc); + } + char *sep = kt_scope_entry_pc(scope_frame.p); + sbuf_t callpc2; sb_init(&callpc2); + kt_frame_callpc(scope_frame.p, &callpc2); + xval_t cv; xv_new_char_utf8(&cv, sep); + kv_pair_t p = { callpc2.p, cv }; + kv_set(kv, &p, 1, err, sizeof err); + xv_free(&cv); + + sb_free(&callpc); sb_free(&callpc2); sb_free(&scope_frame); + return sep; +} + +/* HandleCall:创建子帧。返回 EntryPC(frameRoot),失败 NULL */ +static char *handle_call(kv_t *kv, const char *pc, rwir_inst_t *inst) { + sbuf_t vtid_b; sb_init(&vtid_b); + const char *vtid = kt_vtid_from_pc(pc, &vtid_b); + const char *fn = inst->reads[0].name; + char *pkg = strdup(""); + char *name = strdup(fn); + const char *lp = "/lib/"; + if (strncmp(fn, lp, 5) == 0) { + const char *rest = fn + 5; + const char *dot = strrchr(rest, '.'); + if (dot) { free(pkg); pkg = strndup(rest, (size_t)(dot - rest)); free(name); name = strdup(dot + 1); } + else { free(name); name = strdup(rest); } + } else { + const char *dot = strrchr(fn, '.'); + if (dot) { free(pkg); pkg = strndup(fn, (size_t)(dot - fn)); free(name); name = strdup(dot + 1); } + } + char *func_key = kt_lib_func(pkg, name); + sbuf_t func_dir; sb_init(&func_dir); + sb_puts(&func_dir, func_key); sb_putc(&func_dir, '/'); + + sbuf_t sig_key; sb_init(&sig_key); + sb_printf(&sig_key, "%s[0,0]", func_dir.p); + xval_t sig; xv_zero(&sig); + kv_get_one(kv, sig_key.p, &sig); + if (xv_none(&sig) || !xv_kind_is(&sig, K_RWFUNC)) { + sbuf_t vtroot; sb_init(&vtroot); kt_vthread(vtid, &vtroot); + char msg[256]; snprintf(msg, sizeof msg, "NameError: rwir/rwfunc not found: %s", fn); + vt_set_error(kv, vtid, pc, msg); + sb_free(&vtroot); + goto fail; + } + kvhead_t h; kvspace_decode_head(sig.data, sig.len, &h); + const uint8_t *sbody = sig.data + h.body_offset; + int nr = sbody[0] | (sbody[1] << 8); + int nw = sbody[2] | (sbody[3] << 8); + + char *caller_fr = kt_frame_root(pc); + char *frame_root = strdup(pc); + + char *stack_fr = kt_stack(frame_root); + char err[256]; + kv_mkindex(kv, stack_fr, err, sizeof err); + kv_ext_index(kv, stack_fr, func_dir.p, err, sizeof err); + + /* 系统变量 */ + sbuf_t npc; sb_init(&npc); rwir_next_pc(pc, &npc); + sbuf_t retpc; sb_init(&retpc); kt_frame_returnpc(frame_root, &retpc); + sbuf_t callpc; sb_init(&callpc); kt_frame_callpc(frame_root, &callpc); + char *ep = kt_entry_pc(frame_root); + sbuf_t seglib; sb_init(&seglib); sb_puts(&seglib, stack_fr); sb_puts(&seglib, SEG_LIB); + xval_t v_npc, v_ep, v_fn; xv_zero(&v_npc); xv_zero(&v_ep); xv_zero(&v_fn); + xv_new_char_utf8(&v_npc, npc.p); + xv_new_char_utf8(&v_ep, ep); + xv_new_char_utf8(&v_fn, func_key); + kv_pair_t sys[3] = { { retpc.p, v_npc }, { callpc.p, v_ep }, { seglib.p, v_fn } }; + kv_set(kv, sys, 3, err, sizeof err); + xv_free(&v_npc); xv_free(&v_ep); xv_free(&v_fn); + + /* 读参 + 写参 */ + kv_pair_t pairs[512]; int np = 0; + int lit_seq = 0; + for (int i = 0; i < nr; i++) { + sbuf_t slot; sb_init(&slot); + sb_printf(&slot, "%s/[0,-%d]", frame_root, i + 1); + if (i + 1 < inst->nr) { + param_t *arg = &inst->reads[i + 1]; + char *rk = resolve_read_path(kv, caller_fr, arg->name); + bool concrete = !xv_none(&arg->val) && !xv_kind_is(&arg->val, K_RWIR) && !xv_kind_is(&arg->val, K_RWFUNC); + if (concrete) { + if (!rk) { + sbuf_t lk; sb_init(&lk); + sb_printf(&lk, "%s/._lit%d", caller_fr, lit_seq++); + rk = sb_detach(&lk); + } + /* 写字面量到 rk(拷贝,避免 double-free) */ + kvhead_t ah; kvspace_decode_head(arg->val.data, arg->val.len, &ah); + int32_t abl; const uint8_t *ab = xv_body(&arg->val, &ah, &abl); + pairs[np].key = strdup(rk); + kvspace_tlv_encode(xv_kind(&arg->val), ab, (uint32_t)abl, ah.array_len, + &pairs[np].val.data, &pairs[np].val.len); + np++; + } + if (rk) { + xval_t rv; xv_new_char_utf8(&rv, rk); + pairs[np].key = sb_detach(&slot); + pairs[np].val = rv; + np++; + free(rk); + } + } + sb_free(&slot); + } + for (int i = 0; i < nw; i++) { + sbuf_t slot; sb_init(&slot); + sb_printf(&slot, "%s/[0,%d]", frame_root, i + 1); + if (i < inst->nw) { + char *wk = resolve_read_path(kv, caller_fr, inst->writes[i].name); + if (wk) { + xval_t wv; xv_new_char_utf8(&wv, wk); + pairs[np].key = sb_detach(&slot); + pairs[np].val = wv; + np++; + free(wk); + } + } + sb_free(&slot); + } + if (np > 0) kv_set(kv, pairs, np, err, sizeof err); + for (int i = 0; i < np; i++) { free(pairs[i].key); xv_free(&pairs[i].val); } + + free(caller_fr); free(stack_fr); + sb_free(&npc); sb_free(&retpc); sb_free(&callpc); sb_free(&seglib); + sb_free(&func_dir); sb_free(&sig_key); sb_free(&vtid_b); + xv_free(&sig); free(func_key); free(pkg); free(name); + free(frame_root); + return ep; + +fail: + sb_free(&func_dir); sb_free(&sig_key); sb_free(&vtid_b); + xv_free(&sig); free(func_key); free(pkg); free(name); + return NULL; +} + +static int handle_control(kv_t *kv, const char *vtid, const char *pc, rwir_inst_t *inst) { + if (strcmp(inst->opcode, OP_CALL) == 0) { + char *sub = handle_call(kv, pc, inst); + if (!sub) return -1; + vt_set(kv, vtid, sub, "running"); + free(sub); + return 0; + } + if (strcmp(inst->opcode, OP_RETURN) == 0) { + char *parent = handle_return(kv, pc); + if (!parent) { vt_set_done(kv, vtid, "ok"); return 0; } + vt_set(kv, vtid, parent, "running"); + free(parent); + return 0; + } + if (strcmp(inst->opcode, OP_GOTO) == 0) { + if (inst->nr == 0) return -1; + char *np = handle_scope(kv, pc, inst->reads[0].name); + if (!np) { vt_set_error(kv, vtid, pc, "RuntimeError: goto failed"); return -1; } + vt_set(kv, vtid, np, "running"); + free(np); + return 0; + } + if (strcmp(inst->opcode, OP_BR) == 0) { + if (inst->nr < 3) return -1; + char *fr = kt_frame_root(pc); + xval_t cond; xv_zero(&cond); + bi_resolve_read_value(kv, fr, inst->reads[0].name, &inst->reads[0].val, &cond); + free(fr); + if (xv_none(&cond)) { vt_set_error(kv, vtid, pc, "TypeError: None in branch condition"); xv_free(&cond); return -1; } + const char *label = inst->reads[2].name; + if (xv_as_bool(&cond)) label = inst->reads[1].name; + xv_free(&cond); + char *np = handle_scope(kv, pc, label); + if (!np) { vt_set_error(kv, vtid, pc, "RuntimeError: br failed"); return -1; } + vt_set(kv, vtid, np, "running"); + free(np); + return 0; + } + return -1; +} + +static bool is_copy_op(const char *opcode) { + return strcmp(opcode, "=") == 0; +} + +static bool is_user_rwir(kv_t *kv, const char *opcode) { + if (opcode[0] == '/') return false; + char *rk = kt_rwir(opcode); + xval_t v; xv_zero(&v); + kv_get_one(kv, rk, &v); + bool r = !xv_none(&v) && xv_kind_is(&v, K_RWIR); + xv_free(&v); free(rk); + return r; +} + +static int64_t handoff_seq = 0; + +static int handoff_external_rwir(kv_t *kv, const char *vtid, const char *pc, rwir_inst_t *inst) { + char *base = kt_rwir(inst->opcode); + sbuf_t todo, done; sb_init(&todo); sb_init(&done); + sb_printf(&todo, "%s/.todo<%s>", base, vtid); + sb_printf(&done, "%s/.done<%s>", base, vtid); + int64_t id = ++handoff_seq; + sbuf_t payload; sb_init(&payload); + sb_printf(&payload, "%s|%lld", pc, (long long)id); + xval_t pv; xv_new_char_utf8(&pv, payload.p); + kv_pair_t p = { todo.p, pv }; + char err[256]; + kv_set(kv, &p, 1, err, sizeof err); + xv_free(&pv); + + char id_str[32]; snprintf(id_str, sizeof id_str, "%lld", (long long)id); + xval_t want; xv_new_char_utf8(&want, id_str); + xval_t got; xv_zero(&got); + kv_watch(kv, done.p, &want, 30000000000ULL, &got); + char *got_s = xv_none(&got) ? strdup("") : xv_value_string(&got); + bool ok = strcmp(got_s, id_str) == 0; + free(got_s); xv_free(&got); xv_free(&want); + sb_free(&todo); sb_free(&done); sb_free(&payload); free(base); + if (!ok) { + char msg[256]; snprintf(msg, sizeof msg, "RuntimeError: external rwir %s timeout", inst->opcode); + vt_set_error(kv, vtid, pc, msg); + return -1; + } + return 0; +} + +char *kvcpu_bootstrap(kv_t *kv, const char *vtid, const char *funcname, + const char *const *args, int nargs) { + char *pkg = strdup(""); + char *name = strdup(funcname); + const char *dot = strrchr(funcname, '.'); + if (dot) { free(pkg); pkg = strndup(funcname, (size_t)(dot - funcname)); free(name); name = strdup(dot + 1); } + char *func_key = kt_lib_func(pkg, name); + sbuf_t func_dir; sb_init(&func_dir); + sb_puts(&func_dir, func_key); sb_putc(&func_dir, '/'); + + sbuf_t sig_key; sb_init(&sig_key); + sb_printf(&sig_key, "%s[0,0]", func_dir.p); + xval_t sig; xv_zero(&sig); + kv_get_one(kv, sig_key.p, &sig); + if (xv_none(&sig) || !xv_kind_is(&sig, K_RWFUNC)) { + char msg[256]; snprintf(msg, sizeof msg, "Bootstrap: rwir/rwfunc not found: %s", funcname); + vt_set_error(kv, vtid, "", msg); + xv_free(&sig); sb_free(&sig_key); sb_free(&func_dir); + free(func_key); free(pkg); free(name); + return NULL; + } + kvhead_t h; kvspace_decode_head(sig.data, sig.len, &h); + const uint8_t *sbody = sig.data + h.body_offset; + int nr = sbody[0] | (sbody[1] << 8); + + sbuf_t vtroot; sb_init(&vtroot); kt_vthread(vtid, &vtroot); + char *stack_vt = kt_stack(vtroot.p); + char err[256]; + kv_mkindex(kv, stack_vt, err, sizeof err); + kv_ext_index(kv, stack_vt, func_dir.p, err, sizeof err); + + char *ep = kt_entry_pc(vtroot.p); + sbuf_t callpc; sb_init(&callpc); kt_frame_callpc(vtroot.p, &callpc); + sbuf_t seglib; sb_init(&seglib); sb_puts(&seglib, stack_vt); sb_puts(&seglib, SEG_LIB); + xval_t v_ep, v_fn; xv_zero(&v_ep); xv_zero(&v_fn); + xv_new_char_utf8(&v_ep, ep); + xv_new_char_utf8(&v_fn, func_key); + kv_pair_t sys[2] = { { callpc.p, v_ep }, { seglib.p, v_fn } }; + kv_set(kv, sys, 2, err, sizeof err); + xv_free(&v_ep); xv_free(&v_fn); + + if (nargs > 0) { + kv_pair_t pairs[128]; int np = 0; + for (int i = 0; i < nr && i < nargs; i++) { + sbuf_t slot; sb_init(&slot); + sb_printf(&slot, "%s/[0,-%d]", vtroot.p, i + 1); + xval_t av; xv_zero(&av); + bi_resolve_read_value(kv, "", args[i], NULL, &av); + pairs[np].key = sb_detach(&slot); + pairs[np].val = av; + np++; + } + if (np > 0) kv_set(kv, pairs, np, err, sizeof err); + for (int i = 0; i < np; i++) { free(pairs[i].key); xv_free(&pairs[i].val); } + } + + xv_free(&sig); sb_free(&sig_key); sb_free(&func_dir); + sb_free(&vtroot); sb_free(&callpc); sb_free(&seglib); + free(stack_vt); free(func_key); free(pkg); free(name); + return ep; +} + +int kvcpu_execute(kv_t *kv, const char *pc) { + sbuf_t vtid_b; sb_init(&vtid_b); + const char *vtid = kt_vtid_from_pc(pc, &vtid_b); + if (vtid[0] == 0) { sb_free(&vtid_b); return -1; } + + char *cur = strdup(pc); + int rc = 0; + for (;;) { + char *pcv = NULL, *status = NULL; + vt_get(kv, vtid, &pcv, &status); + if (!status || (strcmp(status, "init") != 0 && strcmp(status, "running") != 0 && strcmp(status, "wait") != 0)) { + free(pcv); free(status); + break; + } + free(pcv); free(status); + + int depth = stack_depth(cur); + if (depth > MAX_STACK_DEPTH) { + char msg[256]; + snprintf(msg, sizeof msg, "RecursionError: stack overflow: depth=%d pc=%s", depth, cur); + vt_set_error(kv, vtid, cur, msg); + rc = -1; + break; + } + + char *fr = kt_frame_root(cur); + char *link_base = kt_stack(fr); + rwir_inst_t inst; + char err[256]; + if (rwir_decode(kv, link_base, cur, &inst, err, sizeof err) != 0) { + char msg[256]; snprintf(msg, sizeof msg, "decode: %s", err); + vt_set_error(kv, vtid, cur, msg); + free(fr); free(link_base); + rc = -1; + break; + } + free(link_base); + + log_debug("[%s] PC=%s OP=%s R=%d W=%d", vtid, cur, inst.opcode ? inst.opcode : "(end)", inst.nr, inst.nw); + + if (!inst.opcode || !inst.opcode[0]) { + /* 帧结束 */ + sbuf_t vtroot; sb_init(&vtroot); kt_vthread(vtid, &vtroot); + if (strcmp(fr, vtroot.p) == 0) { + vt_set_done(kv, vtid, "ok"); + sb_free(&vtroot); free(fr); rwir_inst_free(&inst); + break; + } + if (strcmp(ext_kind(kv, fr), K_RWFUNC) != 0) { + char *parent = handle_scope_return(kv, cur); + if (!parent || !parent[0]) { free(parent); vt_set_done(kv, vtid, "ok"); sb_free(&vtroot); free(fr); rwir_inst_free(&inst); break; } + vt_set(kv, vtid, parent, "running"); + free(cur); cur = parent; + } else { + char *parent = handle_return(kv, cur); + if (!parent || !parent[0]) { free(parent); vt_set_done(kv, vtid, "ok"); sb_free(&vtroot); free(fr); rwir_inst_free(&inst); break; } + vt_set(kv, vtid, parent, "running"); + free(cur); cur = parent; + } + sb_free(&vtroot); free(fr); rwir_inst_free(&inst); + continue; + } + + int exec_err = 0; + if (op_is_control(inst.opcode)) { + exec_err = handle_control(kv, vtid, cur, &inst); + } else if (bi_is_native(inst.opcode)) { + frame_t f = { kv, vtid, cur, &inst }; + exec_err = bi_native(&f); + } else if (is_copy_op(inst.opcode)) { + exec_err = bi_execute_copy(kv, vtid, cur, &inst); + } else if (is_user_rwir(kv, inst.opcode)) { + exec_err = handoff_external_rwir(kv, vtid, cur, &inst); + } else { + /* 用户函数 → call */ + rwir_inst_t ci; + ci.opcode = strdup(OP_CALL); + ci.nr = inst.nr + 1; + ci.nw = inst.nw; + ci.reads = malloc(sizeof(param_t) * (size_t)ci.nr); + ci.reads[0].name = strdup(inst.opcode); + ci.reads[0].val.data = NULL; ci.reads[0].val.len = 0; + for (int i = 0; i < inst.nr; i++) { ci.reads[i + 1] = inst.reads[i]; } + ci.writes = inst.writes; + exec_err = handle_control(kv, vtid, cur, &ci); + free(ci.opcode); free(ci.reads[0].name); free(ci.reads); + } + + if (exec_err != 0) { free(fr); rwir_inst_free(&inst); rc = -1; break; } + + char *newpc = NULL, *st = NULL; + vt_get(kv, vtid, &newpc, &st); + free(st); + free(fr); + rwir_inst_free(&inst); + if (!newpc || !newpc[0]) { free(newpc); break; } + free(cur); + cur = newpc; + } + + free(cur); + sb_free(&vtid_b); + return rc; +} diff --git a/runtime/src/logx.c b/runtime/src/logx.c new file mode 100644 index 00000000..0fa7f08a --- /dev/null +++ b/runtime/src/logx.c @@ -0,0 +1,35 @@ +#include "runtime_internal.h" + +static int log_level(void) { + const char *lv = getenv("LOG_LEVEL"); + if (!lv || !lv[0] || strcmp(lv, "warn") == 0) return 2; + if (strcmp(lv, "debug") == 0) return 0; + if (strcmp(lv, "info") == 0) return 1; + if (strcmp(lv, "error") == 0) return 3; + return 2; +} + +void log_debug(const char *fmt, ...) { + if (log_level() > 0) return; + va_list ap; va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + va_end(ap); +} + +void log_info(const char *fmt, ...) { + if (log_level() > 1) return; + va_list ap; va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + va_end(ap); +} + +void log_error(const char *fmt, ...) { + if (log_level() > 3) return; + fputs("error: ", stderr); + va_list ap; va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + va_end(ap); +} diff --git a/runtime/src/runtime.c b/runtime/src/runtime.c new file mode 100644 index 00000000..9e54748a --- /dev/null +++ b/runtime/src/runtime.c @@ -0,0 +1,92 @@ +#include "runtime_internal.h" +#include "kvlang_runtime.h" + +struct kvlang_rt { kv_t *kv; }; + +kvlang_rt *kvlang_rt_connect(const char *dsn) { + kv_t *kv = kv_connect(dsn); + if (!kv) return NULL; + kvlang_rt *rt = malloc(sizeof(*rt)); + rt->kv = kv; + return rt; +} + +void kvlang_rt_disconnect(kvlang_rt *rt) { + if (!rt) return; + kv_disconnect(rt->kv); + free(rt); +} + +void *kvlang_rt_kv(kvlang_rt *rt) { + return rt ? rt->kv : NULL; +} + +int kvlang_rt_execute_pc(kvlang_rt *rt, const char *pc) { + return kvcpu_execute(rt->kv, pc); +} + +static char *alloc_vtid(kv_t *kv) { + sbuf_t seq; sb_init(&seq); + sb_puts(&seq, VTHREAD_ROOT "/" RUNTIME_MEMBER_SEP "seq"); + xval_t v; xv_zero(&v); + kv_get_one(kv, seq.p, &v); + char *s = xv_none(&v) ? strdup("") : xv_value_string(&v); + xv_free(&v); + int64_t n = atoll(s) + 1; + free(s); + char buf[32]; snprintf(buf, sizeof buf, "%lld", (long long)n); + xval_t nv; xv_new_char_utf8(&nv, buf); + kv_pair_t p = { seq.p, nv }; + char err[256]; + kv_set(kv, &p, 1, err, sizeof err); + xv_free(&nv); sb_free(&seq); + return strdup(buf); +} + +int kvlang_rt_execute(kvlang_rt *rt, const char *funcname, + const char *const *args, int nargs, + char **ret, char *err, uint32_t err_cap) { + kv_t *kv = rt->kv; + char *vtid = alloc_vtid(kv); + + sbuf_t vtroot; sb_init(&vtroot); kt_vthread(vtid, &vtroot); + char *stack_vt = kt_stack(vtroot.p); + char e[256]; + kv_mkindex(kv, stack_vt, e, sizeof e); + + char *first_pc = kvcpu_bootstrap(kv, vtid, funcname, args, nargs); + if (!first_pc) { + if (err && err_cap) snprintf(err, err_cap, "Bootstrap %s failed", funcname); + free(stack_vt); free(vtid); sb_free(&vtroot); + return -1; + } + vt_set(kv, vtid, first_pc, "init"); + int rc = kvcpu_execute(kv, first_pc); + free(first_pc); + + /* 读终态 */ + sbuf_t st; sb_init(&st); kt_vthread_status(vtid, &st); + xval_t sv; xv_zero(&sv); kv_get_one(kv, st.p, &sv); + char *status = xv_none(&sv) ? strdup("") : xv_value_string(&sv); + xv_free(&sv); + + if (ret) { + if (strcmp(status, "error") == 0) { + sbuf_t mk; sb_init(&mk); kt_vthread_status_msg(vtid, "error", &mk); + xval_t mv; xv_zero(&mv); kv_get_one(kv, mk.p, &mv); + char *msg = xv_none(&mv) ? strdup("error") : xv_value_string(&mv); + xv_free(&mv); sb_free(&mk); + if (err && err_cap) snprintf(err, err_cap, "%s", msg); + *ret = strdup("error"); + free(msg); + rc = -1; + } else { + *ret = status; + } + } else { + free(status); + } + + sb_free(&st); sb_free(&vtroot); free(stack_vt); free(vtid); + return rc; +} diff --git a/runtime/src/runtime_internal.h b/runtime/src/runtime_internal.h new file mode 100644 index 00000000..c17c6d75 --- /dev/null +++ b/runtime/src/runtime_internal.h @@ -0,0 +1,252 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +/* ── kvspace-durable C ABI ─────────────────────────────────────────── */ + +typedef struct { + uint8_t kind[32]; + uint8_t is_ptr; + int32_t array_len; + int32_t body_len; + int32_t body_offset; +} kvhead_t; + +extern void *kvspace_conn(const char *dsn); +extern void kvspace_free(void *h); +extern void kvspace_bytes_free(uint8_t *p, uint32_t len); +extern int kvspace_set(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 kvspace_get_one(void *h, const char *key, uint8_t **out, uint32_t *out_len); +extern int kvspace_get_batch(void *h, const char *prefix, const char *const *names, + uint32_t nnames, uint8_t **out, uint32_t *out_len); +extern int kvspace_list(void *h, const char *prefix, int expand_ext, int resolve, + uint8_t **out, uint32_t *out_len); +extern int kvspace_del(void *h, const char *const *keys, uint32_t nkeys, char *err, uint32_t err_cap); +extern int kvspace_del_tree(void *h, const char *prefix, char *err, uint32_t err_cap); +extern int kvspace_mkindex(void *h, const char *path, char *err, uint32_t err_cap); +extern int kvspace_ext_index(void *h, const char *path, const char *ext_path, char *err, uint32_t err_cap); +extern int kvspace_del_ext_index(void *h, const char *path, char *err, uint32_t err_cap); +extern int kvspace_watch(void *h, const char *key, const uint8_t *target, uint32_t target_len, + uint64_t tick_ns, uint8_t **out, uint32_t *out_len); +extern int kvspace_tlv_encode(const char *kind, const uint8_t *raw, uint32_t raw_len, + int32_t array_len, uint8_t **out, uint32_t *out_len); +extern int kvspace_tlv_encode_ptr(const char *kind, const uint8_t *raw, uint32_t raw_len, + int32_t array_len, uint8_t **out, uint32_t *out_len); +extern int kvspace_decode_head(const uint8_t *data, uint32_t data_len, kvhead_t *out); +extern int kvspace_new_ptr(const char *kind, const char *target, int32_t array_len, + uint8_t **out, uint32_t *out_len); +extern int kvspace_new_char(const char *kind, const char *s, uint8_t **out, uint32_t *out_len); +extern int kvspace_new_char_byte(const uint8_t *bytes, uint32_t len, uint8_t **out, uint32_t *out_len); +extern int kvspace_new_bool(uint8_t v, uint8_t **out, uint32_t *out_len); +extern int kvspace_new_int64(int64_t v, uint8_t **out, uint32_t *out_len); +extern int kvspace_new_float64(double v, uint8_t **out, uint32_t *out_len); + +/* ── kind 常量 ─────────────────────────────────────────────────────── */ + +#define K_NONE "None" +#define K_BOOL "bool" +#define K_INT8 "int8" +#define K_INT16 "int16" +#define K_INT32 "int32" +#define K_INT64 "int64" +#define K_UINT8 "uint8" +#define K_UINT16 "uint16" +#define K_UINT32 "uint32" +#define K_UINT64 "uint64" +#define K_FLOAT32 "float32" +#define K_FLOAT64 "float64" +#define K_CHAR "char/utf32" +#define K_CHAR_UTF8 "char/utf8" +#define K_CHAR_ASCII "char/ascii" +#define K_DICT "dict" +#define K_INDEX "index" +#define K_EXT_INDEX "extindex" +#define K_RWIR "rwir" +#define K_RWFUNC "rwfunc" +#define K_SCOPE "scope" +#define K_TIME "time" +#define K_DURATION "duration" + +/* ── 路径/成员常量 ─────────────────────────────────────────────────── */ + +#define PATH_SEP "/" +#define DIR_INDEX_SUF "/" +#define MEMBER_SEP "." +#define INDEX_VALUE_SEP "\n" +#define RUNTIME_MEMBER_SEP "\xE2\x80\xA5" /* ‥ U+2025 */ +#define EXT_INDEX_HEAD "\xE2\x80\xA6" /* … U+2026 */ + +#define MAX_PARAMS 128 +#define MAX_STACK_DEPTH 256 + +/* ── 基础类型 ──────────────────────────────────────────────────────── */ + +typedef struct { uint8_t *data; uint32_t len; } xval_t; + +typedef struct { char *key; xval_t val; } kv_pair_t; + +typedef struct { void *h; } kv_t; + +/* growable string buffer */ +typedef struct { char *p; size_t len, cap; } sbuf_t; + +static inline void sb_init(sbuf_t *b) { b->p = NULL; b->len = 0; b->cap = 0; } +void sb_putc(sbuf_t *b, char c); +void sb_putn(sbuf_t *b, const char *s, size_t n); +static inline void sb_puts(sbuf_t *b, const char *s) { sb_putn(b, s, strlen(s)); } +void sb_printf(sbuf_t *b, const char *fmt, ...); +char *sb_detach(sbuf_t *b); /* malloc,调用方 free */ +static inline void sb_free(sbuf_t *b) { free(b->p); b->p = NULL; b->len = b->cap = 0; } + +/* ── XValue 操作 ───────────────────────────────────────────────────── */ + +static inline bool xv_none(const xval_t *v) { return v->data == NULL || v->len == 0; } +static inline void xv_zero(xval_t *v) { v->data = NULL; v->len = 0; } +void xv_free(xval_t *v); /* kvspace_bytes_free */ +void xv_set_bytes(xval_t *v, uint8_t *data, uint32_t len); /* 接管内存 */ +int xv_head(const xval_t *v, kvhead_t *h); /* decode head */ +const char *xv_kind(const xval_t *v); /* 返回 kind,None="" */ +bool xv_kind_is(const xval_t *v, const char *kind); +bool xv_is_ptr(const xval_t *v); +int32_t xv_array_len(const xval_t *v); +const uint8_t *xv_body(const xval_t *v, const kvhead_t *h, int32_t *out_len); +char *xv_ptr_target(const xval_t *v); /* malloc */ +char *xv_value_string(const xval_t *v); /* malloc,对齐 Go ValueString */ +bool xv_is_char_kind(const char *kind); +bool xv_is_int_kind(const char *kind); +bool xv_is_uint_kind(const char *kind); +bool xv_is_float_kind(const char *kind); +bool xv_is_num_kind(const char *kind); +int64_t xv_as_int64(const xval_t *v); +double xv_as_float64(const xval_t *v); +uint64_t xv_as_uint64(const xval_t *v); +bool xv_as_bool(const xval_t *v); +uint32_t xv_char32_at(const xval_t *v, int32_t idx); +int32_t xv_elem_size(const char *kind); + +void xv_new_int64(xval_t *v, int64_t n); +void xv_new_float64(xval_t *v, double f); +void xv_new_bool(xval_t *v, bool b); +void xv_new_char_utf8(xval_t *v, const char *s); +void xv_new_char_utf32(xval_t *v, const char *s); /* UTF-8 → UTF-32 LE body */ +void xv_new_char_kind(xval_t *v, const char *kind, const char *s); +void xv_new_ptr(xval_t *v, const char *kind, const char *target, int32_t al); +void xv_new_rwir(xval_t *v, int32_t nr, int32_t nw, const char *sig); +void xv_new_tlv(xval_t *v, const char *kind, const uint8_t *raw, uint32_t raw_len, int32_t al); + +void fmt_float(char *out, size_t cap, double v); + +/* ── KV 操作(封装 durable ABI)────────────────────────────────────── */ + +kv_t *kv_connect(const char *dsn); +void kv_disconnect(kv_t *k); +int kv_get_one(kv_t *k, const char *key, xval_t *out); /* None → out len=0 */ +int kv_get_batch(kv_t *k, const char *prefix, char **names, int n, xval_t *out); +int kv_set(kv_t *k, const kv_pair_t *pairs, int n, char *err, uint32_t err_cap); +int kv_del(kv_t *k, const char *key, char *err, uint32_t err_cap); +int kv_del_tree(kv_t *k, const char *prefix, char *err, uint32_t err_cap); +int kv_mkindex(kv_t *k, const char *path, char *err, uint32_t err_cap); +int kv_ext_index(kv_t *k, const char *path, const char *ext, char *err, uint32_t err_cap); +int kv_del_ext_index(kv_t *k, const char *path, char *err, uint32_t err_cap); +int kv_list(kv_t *k, const char *prefix, bool expand_ext, bool resolve, + char ***out_names, int *out_count); /* split \n */ +int kv_watch(kv_t *k, const char *key, const xval_t *target, uint64_t tick_ns, xval_t *out); + +/* ── keytree ───────────────────────────────────────────────────────── */ + +#define SEG_LIB RUNTIME_MEMBER_SEP "lib" +#define SEG_PC "pc" +#define SEG_STATUS "status" +#define SEG_CALLPC "callpc" +#define SEG_RETURNPC "returnpc" +#define SEG_RO "ro" +#define SEG_MSG "msg" +#define LIB_ROOT "/lib" +#define VTHREAD_ROOT "/vthread" + +static inline void sb_clear(sbuf_t *b) { b->len = 0; if (b->p) b->p[0] = 0; } + +const char *kt_vtid_from_pc(const char *pc, sbuf_t *out); /* "" 无效 */ +char *kt_stack(const char *root); /* malloc */ +char *kt_frame_root(const char *pc); /* malloc,无效 NULL */ +char *kt_entry_pc(const char *root); /* malloc */ +char *kt_scope_entry_pc(const char *root); /* malloc */ +char *kt_parent_frame(const char *root); /* malloc,"" 顶层 */ +char *kt_member(const char *base, const char *name); /* malloc */ +char *kt_lib_func(const char *pkg, const char *name); /* malloc */ +char *kt_rwir(const char *opcode); /* malloc */ +void kt_vthread(const char *vtid, sbuf_t *out); +void kt_vthread_slot(const char *vtid, const char *frame, int i, int j, sbuf_t *out); +void kt_vthread_pc(const char *vtid, sbuf_t *out); +void kt_vthread_status(const char *vtid, sbuf_t *out); +void kt_vthread_status_msg(const char *vtid, const char *status, sbuf_t *out); +void kt_vthread_debugger(const char *vtid, sbuf_t *out); +void kt_frame_callpc(const char *root, sbuf_t *out); +void kt_frame_returnpc(const char *root, sbuf_t *out); +void kt_frame_ro(const char *root, sbuf_t *out); +bool kt_is_entry_pc(const char *pc); + +/* ── rwir ──────────────────────────────────────────────────────────── */ + +#define OP_CALL "call" +#define OP_RETURN "return" +#define OP_BR "br" +#define OP_GOTO "goto" +#define OP_ASSIGN "assign" + +static inline bool op_is_control(const char *op) { + return strcmp(op, OP_CALL) == 0 || strcmp(op, OP_RETURN) == 0 || + strcmp(op, OP_BR) == 0 || strcmp(op, OP_GOTO) == 0; +} + +typedef struct { char *name; xval_t val; } param_t; + +typedef struct { + char *opcode; + param_t *reads; int nr; + param_t *writes; int nw; +} rwir_inst_t; + +int rwir_next_pc(const char *pc, sbuf_t *out); +int rwir_extract_addr0(const char *coord); +int rwir_decode(kv_t *kv, const char *link_base, const char *pc, rwir_inst_t *out, char *err, uint32_t err_cap); +void rwir_inst_free(rwir_inst_t *inst); + +/* ── vthread ───────────────────────────────────────────────────────── */ + +void vt_get(kv_t *kv, const char *vtid, char **pc, char **status); +void vt_set(kv_t *kv, const char *vtid, const char *pc, const char *status); +void vt_set_done(kv_t *kv, const char *vtid, const char *ret); +void vt_set_error(kv_t *kv, const char *vtid, const char *pc, const char *msg); + +/* ── builtin ───────────────────────────────────────────────────────── */ + +typedef struct { kv_t *kv; const char *vtid; const char *pc; rwir_inst_t *inst; } frame_t; + +bool bi_is_native(const char *opcode); +bool bi_num_op(const char *opcode); +int bi_native(frame_t *f); /* dispatch + call,0 成功 */ +int bi_execute_copy(kv_t *kv, const char *vtid, const char *pc, rwir_inst_t *inst); +void bi_resolve_read_value(kv_t *kv, const char *frame_path, const char *name, + const xval_t *val, xval_t *out); +char *bi_resolve_write_slot(kv_t *kv, const char *frame_path, const char *name); +char *bi_func_frame_root(kv_t *kv, const char *frame_root); /* malloc */ +bool bi_try_parse_number(const char *s, xval_t *out); /* 成功 out 接管 */ +void display(const xval_t *v, char **out); /* malloc,对齐 Go Display */ + +/* ── kvcpu ─────────────────────────────────────────────────────────── */ + +int kvcpu_execute(kv_t *kv, const char *pc); +char *kvcpu_bootstrap(kv_t *kv, const char *vtid, const char *funcname, const char *const *args, int nargs); + +/* ── logx ──────────────────────────────────────────────────────────── */ + +void log_debug(const char *fmt, ...); +void log_info(const char *fmt, ...); +void log_error(const char *fmt, ...); diff --git a/runtime/src/rwext.c b/runtime/src/rwext.c new file mode 100644 index 00000000..708b92ef --- /dev/null +++ b/runtime/src/rwext.c @@ -0,0 +1,104 @@ +#include "runtime_internal.h" +#include "kvlang_rwext.h" + +struct rwext_conn { kv_t *kv; }; + +rwext_conn *rwext_connect(const char *dsn) { + kv_t *kv = kv_connect(dsn); + if (!kv) return NULL; + rwext_conn *c = malloc(sizeof(*c)); + c->kv = kv; + return c; +} + +void rwext_disconnect(rwext_conn *c) { + if (!c) return; + kv_disconnect(c->kv); + free(c); +} + +int rwext_register(rwext_conn *c, const char *opcode, int32_t nr, int32_t nw, const char *sig) { + char *key = kt_rwir(opcode); + xval_t v; xv_new_rwir(&v, nr, nw, sig); + kv_pair_t p = { key, v }; + char err[256]; + int rc = kv_set(c->kv, &p, 1, err, sizeof err); + xv_free(&v); free(key); + return rc; +} + +char *rwext_list(rwext_conn *c, const char *prefix) { + char **names = NULL; int count = 0; + if (kv_list(c->kv, prefix, false, false, &names, &count) != 0) return strdup(""); + sbuf_t b; sb_init(&b); + for (int i = 0; i < count; i++) { + if (i) sb_putc(&b, '\n'); + sb_puts(&b, names[i]); + free(names[i]); + } + free(names); + return sb_detach(&b); +} + +char *rwext_get(rwext_conn *c, const char *key) { + xval_t v; xv_zero(&v); + kv_get_one(c->kv, key, &v); + char *s = xv_none(&v) ? strdup("") : xv_value_string(&v); + xv_free(&v); + return s; +} + +int rwext_set(rwext_conn *c, const char *key, const char *val) { + xval_t v; xv_new_char_utf8(&v, val); + kv_pair_t p = { (char *)key, v }; + char err[256]; + int rc = kv_set(c->kv, &p, 1, err, sizeof err); + xv_free(&v); + return rc; +} + +int rwext_del(rwext_conn *c, const char *key) { + char err[256]; + return kv_del(c->kv, key, err, sizeof err); +} + +char *rwext_print_line(rwext_conn *c, const char *pc, int *rawnl, int *cerr) { + if (rawnl) *rawnl = 0; + if (cerr) *cerr = 0; + char *fr = kt_frame_root(pc); + if (!fr) return NULL; + char *lb = kt_stack(fr); + rwir_inst_t inst; + char err[256]; + if (rwir_decode(c->kv, lb, pc, &inst, err, sizeof err) != 0) { + free(fr); free(lb); return NULL; + } + free(lb); + if (!inst.opcode || + (strcmp(inst.opcode, "print") != 0 && strcmp(inst.opcode, "println") != 0 && + strcmp(inst.opcode, "cerr") != 0)) { + free(fr); rwir_inst_free(&inst); return NULL; + } + const char *sep = strcmp(inst.opcode, "print") == 0 ? "" : " "; + if (rawnl) *rawnl = (strcmp(inst.opcode, "print") == 0); + if (cerr) *cerr = (strcmp(inst.opcode, "cerr") == 0); + + sbuf_t line; sb_init(&line); + for (int i = 0; i < inst.nr; i++) { + if (i > 0) sb_puts(&line, sep); + xval_t v; xv_zero(&v); + bi_resolve_read_value(c->kv, fr, inst.reads[i].name, &inst.reads[i].val, &v); + char *s = NULL; display(&v, &s); + sb_puts(&line, s); free(s); + xv_free(&v); + } + free(fr); + rwir_inst_free(&inst); + return sb_detach(&line); +} + +char *rwext_next_pc(const char *pc) { + sbuf_t b; sb_init(&b); + rwir_next_pc(pc, &b); + return sb_detach(&b); +} diff --git a/runtime/src/rwir.c b/runtime/src/rwir.c new file mode 100644 index 00000000..e353690e --- /dev/null +++ b/runtime/src/rwir.c @@ -0,0 +1,130 @@ +#include "runtime_internal.h" + +int rwir_extract_addr0(const char *coord) { + const char *p = coord; + while (*p == '[' || *p == ' ' || *p == '\t') p++; + char *end; + long n = strtol(p, &end, 10); + return end == p ? 0 : (int)n; +} + +int rwir_next_pc(const char *pc, sbuf_t *out) { + sb_clear(out); + const char *slash = strrchr(pc, '/'); + size_t plen = slash ? (size_t)(slash - pc) + 1 : 0; + int num = rwir_extract_addr0(slash ? slash + 1 : pc); + sb_putn(out, pc, plen); + sb_printf(out, "[%d,0]", num + 1); + return 0; +} + +/* scopePrefixAndBase:从 linkBase 提取 scope 链与 rwfunc 帧 lookupBase。 */ +static void scope_prefix_and_base(const char *link_base, sbuf_t *sp, sbuf_t *lb) { + sb_clear(sp); + sb_clear(lb); + sb_puts(sp, ""); + sb_puts(lb, link_base); + + size_t n = strlen(link_base); + while (n > 0 && link_base[n - 1] == '/') n--; + if (n == 0) return; + bool has_bracket = false; + for (size_t i = 0; i < n; i++) if (link_base[i] == '[') { has_bracket = true; break; } + if (!has_bracket) return; + + char *scopes[MAX_STACK_DEPTH]; + int nscopes = 0; + size_t rest = n; + while (rest > 0) { + size_t sep = (size_t)-1; + for (size_t i = 0; i < rest; i++) if (link_base[i] == '/') sep = i; + if (sep == (size_t)-1) break; + size_t start = sep + 1, len = rest - start; + rest = sep; + if (len > 0 && link_base[start] == '[') { + sb_clear(lb); + sb_putn(lb, link_base, rest); + sb_putc(lb, '/'); + sb_putn(lb, link_base + start, len); + sb_putc(lb, '/'); + break; + } + if (nscopes < MAX_STACK_DEPTH) { + scopes[nscopes] = malloc(len + 1); + memcpy(scopes[nscopes], link_base + start, len); + scopes[nscopes][len] = 0; + nscopes++; + } + } + for (int i = nscopes - 1; i >= 0; i--) { + if (sp->len > 0) sb_putc(sp, '.'); + sb_puts(sp, scopes[i]); + free(scopes[i]); + } +} + +void rwir_inst_free(rwir_inst_t *inst) { + free(inst->opcode); + for (int i = 0; i < inst->nr; i++) { free(inst->reads[i].name); xv_free(&inst->reads[i].val); } + for (int i = 0; i < inst->nw; i++) { free(inst->writes[i].name); xv_free(&inst->writes[i].val); } + free(inst->reads); + free(inst->writes); + inst->opcode = NULL; inst->reads = NULL; inst->writes = NULL; inst->nr = inst->nw = 0; +} + +int rwir_decode(kv_t *kv, const char *link_base, const char *pc, rwir_inst_t *out, + char *err, uint32_t err_cap) { + memset(out, 0, sizeof(*out)); + const char *last = NULL; + for (const char *p = pc; (p = strstr(p, "/[")) != NULL; p += 2) last = p; + if (!last) { snprintf(err, err_cap, "Decode: invalid pc (no /[coord]): %s", pc); return -1; } + int addr0 = rwir_extract_addr0(last + 1); + + sbuf_t sp, lb, key; + sb_init(&sp); sb_init(&lb); sb_init(&key); + scope_prefix_and_base(link_base, &sp, &lb); + + int nslots = 1 + 2 * MAX_PARAMS; + char **names = malloc(sizeof(char *) * (size_t)nslots); + sb_printf(&key, "%s[%d,0]", sp.p, addr0); + names[0] = sb_detach(&key); + for (int i = 1; i <= MAX_PARAMS; i++) { + sb_printf(&key, "%s[%d,-%d]", sp.p, addr0, i); + names[(i - 1) * 2 + 1] = sb_detach(&key); + sb_printf(&key, "%s[%d,%d]", sp.p, addr0, i); + names[(i - 1) * 2 + 2] = sb_detach(&key); + } + sb_free(&key); + + xval_t *vals = malloc(sizeof(xval_t) * (size_t)nslots); + kv_get_batch(kv, lb.p, names, nslots, vals); + + if (!xv_none(&vals[0])) out->opcode = xv_value_string(&vals[0]); + + out->reads = malloc(sizeof(param_t) * MAX_PARAMS); + out->writes = malloc(sizeof(param_t) * MAX_PARAMS); + out->nr = out->nw = 0; + for (int i = 1; i <= MAX_PARAMS; i++) { + xval_t *rv = &vals[(i - 1) * 2 + 1]; + if (!xv_none(rv)) { + out->reads[out->nr].name = xv_value_string(rv); + out->reads[out->nr].val = *rv; + rv->data = NULL; rv->len = 0; + out->nr++; + } + xval_t *wv = &vals[(i - 1) * 2 + 2]; + if (!xv_none(wv)) { + out->writes[out->nw].name = xv_value_string(wv); + out->writes[out->nw].val = *wv; + wv->data = NULL; wv->len = 0; + out->nw++; + } + } + + for (int i = 0; i < nslots; i++) xv_free(&vals[i]); + free(vals); + for (int i = 0; i < nslots; i++) free(names[i]); + free(names); + sb_free(&sp); sb_free(&lb); + return 0; +} diff --git a/runtime/src/strbuf.c b/runtime/src/strbuf.c new file mode 100644 index 00000000..1498ad93 --- /dev/null +++ b/runtime/src/strbuf.c @@ -0,0 +1,46 @@ +#include "runtime_internal.h" + +static void sb_reserve(sbuf_t *b, size_t need) { + if (b->cap >= need) return; + size_t cap = b->cap ? b->cap * 2 : 32; + while (cap < need) cap *= 2; + b->p = realloc(b->p, cap); + b->cap = cap; +} + +void sb_putc(sbuf_t *b, char c) { + sb_reserve(b, b->len + 2); + b->p[b->len++] = c; + b->p[b->len] = '\0'; +} + +void sb_putn(sbuf_t *b, const char *s, size_t n) { + if (n == 0) { + if (!b->p) { b->p = calloc(1, 1); b->cap = 1; } + return; + } + sb_reserve(b, b->len + n + 1); + memcpy(b->p + b->len, s, n); + b->len += n; + b->p[b->len] = '\0'; +} + +void sb_printf(sbuf_t *b, const char *fmt, ...) { + va_list ap, ap2; + va_start(ap, fmt); + va_copy(ap2, ap); + int n = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + if (n < 0) { va_end(ap2); return; } + sb_reserve(b, b->len + (size_t)n + 1); + vsnprintf(b->p + b->len, (size_t)n + 1, fmt, ap2); + va_end(ap2); + b->len += (size_t)n; +} + +char *sb_detach(sbuf_t *b) { + if (b->p == NULL) return strdup(""); + char *p = b->p; + b->p = NULL; b->len = b->cap = 0; + return p; +} diff --git a/runtime/src/vthread.c b/runtime/src/vthread.c new file mode 100644 index 00000000..161edcb9 --- /dev/null +++ b/runtime/src/vthread.c @@ -0,0 +1,69 @@ +#include "runtime_internal.h" + +void vt_get(kv_t *kv, const char *vtid, char **pc, char **status) { + *pc = NULL; *status = NULL; + sbuf_t k; sb_init(&k); + kt_vthread_pc(vtid, &k); + xval_t pv; xv_zero(&pv); + kv_get_one(kv, k.p, &pv); + if (!xv_none(&pv)) *pc = xv_value_string(&pv); + xv_free(&pv); + kt_vthread_status(vtid, &k); + xval_t sv; xv_zero(&sv); + kv_get_one(kv, k.p, &sv); + if (!xv_none(&sv)) *status = xv_value_string(&sv); + xv_free(&sv); + sb_free(&k); +} + +void vt_set(kv_t *kv, const char *vtid, const char *pc, const char *status) { + sbuf_t k1, k2; sb_init(&k1); sb_init(&k2); + kt_vthread_pc(vtid, &k1); + kt_vthread_status(vtid, &k2); + xval_t v1, v2; xv_zero(&v1); xv_zero(&v2); + xv_new_char_utf8(&v1, pc); + xv_new_char_utf8(&v2, status); + kv_pair_t pairs[2] = { { k1.p, v1 }, { k2.p, v2 } }; + char err[256]; + kv_set(kv, pairs, 2, err, sizeof err); + xv_free(&v1); xv_free(&v2); sb_free(&k1); sb_free(&k2); +} + +void vt_set_done(kv_t *kv, const char *vtid, const char *ret) { + if (ret == NULL || ret[0] == 0) ret = "ok"; + sbuf_t k; sb_init(&k); + kt_vthread_status(vtid, &k); + xval_t v; xv_zero(&v); + xv_new_char_utf8(&v, ret); + kv_pair_t pair = { k.p, v }; + char err[256]; + kv_set(kv, &pair, 1, err, sizeof err); + xv_free(&v); sb_free(&k); +} + +void vt_set_error(kv_t *kv, const char *vtid, const char *pc, const char *msg) { + sbuf_t msg_path, pc_key, st_key; sb_init(&msg_path); sb_init(&pc_key); sb_init(&st_key); + kt_vthread_status_msg(vtid, "error", &msg_path); + kt_vthread_pc(vtid, &pc_key); + kt_vthread_status(vtid, &st_key); + + /* 确保 .error/ 父目录存在 */ + char *sep = strrchr(msg_path.p, '/'); + if (sep) { + sbuf_t dir; sb_init(&dir); + sb_putn(&dir, msg_path.p, (size_t)(sep - msg_path.p) + 1); + char err[256]; + kv_mkindex(kv, dir.p, err, sizeof err); + sb_free(&dir); + } + + xval_t vpc, vmsg, vst; xv_zero(&vpc); xv_zero(&vmsg); xv_zero(&vst); + xv_new_char_utf8(&vpc, pc); + xv_new_char_utf8(&vmsg, msg); + xv_new_char_utf8(&vst, "error"); + kv_pair_t pairs[3] = { { pc_key.p, vpc }, { msg_path.p, vmsg }, { st_key.p, vst } }; + char err[256]; + kv_set(kv, pairs, 3, err, sizeof err); + xv_free(&vpc); xv_free(&vmsg); xv_free(&vst); + sb_free(&msg_path); sb_free(&pc_key); sb_free(&st_key); +} diff --git a/runtime/src/xvalue.c b/runtime/src/xvalue.c new file mode 100644 index 00000000..539fbc1f --- /dev/null +++ b/runtime/src/xvalue.c @@ -0,0 +1,375 @@ +#include "runtime_internal.h" + +/* 后端无关的 XValue TLV 编解码(对齐 kvspace-durable/kvspace-c 的 kindexp TLV)。 + * xval.data 一律 malloc(free 释放),后端在 kv.c 层负责拷贝。 */ + +static uint32_t rd32(const uint8_t *p) { return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); } +static uint16_t rd16(const uint8_t *p) { return (uint16_t)(p[0] | (p[1] << 8)); } +static uint64_t rd64(const uint8_t *p) { return (uint64_t)rd32(p) | ((uint64_t)rd32(p + 4) << 32); } +static void wr32(uint8_t *p, uint32_t v) { p[0] = v & 0xFF; p[1] = (v >> 8) & 0xFF; p[2] = (v >> 16) & 0xFF; p[3] = (v >> 24) & 0xFF; } + +void xv_free(xval_t *v) { + free(v->data); + v->data = NULL; v->len = 0; +} + +void xv_set_bytes(xval_t *v, uint8_t *data, uint32_t len) { + v->data = data; v->len = len; +} + +static int xv_decode_head_raw(const uint8_t *d, uint32_t len, kvhead_t *h) { + memset(h, 0, sizeof(*h)); + if (!d || len < 4) return -1; + uint8_t kl = d[0]; + uint32_t o = 1 + kl; + if (len < o + 3 + 4) return -1; + uint8_t ref = d[o], arr_flag = d[o + 1], ndim = d[o + 2]; + if (len < o + 3 + 4u * ndim + 4) return -1; + uint32_t k = kl > 31 ? 31 : kl; + memcpy(h->kind, d + 1, k); h->kind[k] = 0; + h->is_ptr = (ref == 1); + uint32_t raw_off = o + 3 + 4u * ndim; + uint32_t raw_len = rd32(d + raw_off); + h->body_offset = (int32_t)(raw_off + 4); + h->body_len = (int32_t)raw_len; + if (arr_flag == 0) h->array_len = 1; + else if (ndim > 0) { + int32_t n = 1; + for (uint32_t i = 0; i < ndim; i++) n *= (int32_t)rd32(d + o + 3 + 4 * i); + h->array_len = n; + } else { + int32_t es = xv_elem_size((const char *)h->kind); + h->array_len = es > 0 ? (int32_t)raw_len / es : 0; + } + return 0; +} + +static uint8_t *xv_encode_tlv(const char *kind, int32_t ref, const uint8_t *raw, + uint32_t raw_len, int32_t array_len, uint32_t *out_len) { + int32_t al = array_len <= 0 ? 1 : array_len; + int32_t ndim = al > 1 ? 1 : 0; + uint32_t kl = (uint32_t)strlen(kind); + uint32_t head_len = 1 + kl + 1 + 1 + 1 + 4u * ndim + 4; + uint32_t total = head_len + raw_len; + uint8_t *buf = malloc(total); + buf[0] = (uint8_t)kl; + memcpy(buf + 1, kind, kl); + uint32_t o = 1 + kl; + buf[o++] = (uint8_t)ref; + buf[o++] = (uint8_t)(al > 1 ? 1 : 0); + buf[o++] = (uint8_t)ndim; + if (ndim) { wr32(buf + o, (uint32_t)al); o += 4; } + wr32(buf + o, raw_len); o += 4; + if (raw_len) memcpy(buf + o, raw, raw_len); + *out_len = total; + return buf; +} + +int xv_head(const xval_t *v, kvhead_t *h) { + memset(h, 0, sizeof(*h)); + if (xv_none(v)) return -1; + return xv_decode_head_raw(v->data, v->len, h); +} + +const char *xv_kind(const xval_t *v) { + static __thread char buf[16][33]; + static __thread int idx = 0; + char *b = buf[idx]; + idx = (idx + 1) & 15; + if (xv_none(v)) { b[0] = 0; return b; } + uint8_t kl = v->data[0]; + if (kl > 32) kl = 32; + memcpy(b, v->data + 1, kl); + b[kl] = 0; + return b; +} + +bool xv_kind_is(const xval_t *v, const char *kind) { + if (xv_none(v)) return kind[0] == 0; + uint8_t kl = v->data[0]; + return (size_t)kl == strlen(kind) && memcmp(v->data + 1, kind, kl) == 0; +} + +bool xv_is_ptr(const xval_t *v) { + if (xv_none(v)) return false; + return v->len > 2 + v->data[0] && v->data[1 + v->data[0]] == 1; +} + +int32_t xv_array_len(const xval_t *v) { + if (xv_none(v)) return 0; + kvhead_t h; xv_decode_head_raw(v->data, v->len, &h); + return h.array_len; +} + +const uint8_t *xv_body(const xval_t *v, const kvhead_t *h, int32_t *out_len) { + int32_t off = h->body_offset, len = h->body_len; + if (off < 0 || len < 0 || off + len > (int32_t)v->len) { + if (out_len) *out_len = 0; + return NULL; + } + if (out_len) *out_len = len; + return v->data + off; +} + +bool xv_is_char_kind(const char *kind) { return strncmp(kind, "char/", 5) == 0; } +bool xv_is_int_kind(const char *kind) { + return strcmp(kind, K_INT8) == 0 || strcmp(kind, K_INT16) == 0 || + strcmp(kind, K_INT32) == 0 || strcmp(kind, K_INT64) == 0; +} +bool xv_is_uint_kind(const char *kind) { + return strcmp(kind, K_UINT8) == 0 || strcmp(kind, K_UINT16) == 0 || + strcmp(kind, K_UINT32) == 0 || strcmp(kind, K_UINT64) == 0; +} +bool xv_is_float_kind(const char *kind) { + return strcmp(kind, K_FLOAT32) == 0 || strcmp(kind, K_FLOAT64) == 0; +} +bool xv_is_num_kind(const char *kind) { + return xv_is_int_kind(kind) || xv_is_uint_kind(kind) || xv_is_float_kind(kind); +} + +int32_t xv_elem_size(const char *kind) { + if (strcmp(kind, K_INT8) == 0 || strcmp(kind, K_UINT8) == 0 || + strcmp(kind, K_CHAR_UTF8) == 0 || strcmp(kind, K_CHAR_ASCII) == 0 || + strcmp(kind, K_BOOL) == 0) return 1; + if (strcmp(kind, K_INT16) == 0 || strcmp(kind, K_UINT16) == 0) return 2; + if (strcmp(kind, K_INT32) == 0 || strcmp(kind, K_UINT32) == 0 || + strcmp(kind, K_FLOAT32) == 0 || strcmp(kind, K_CHAR) == 0) return 4; + if (strcmp(kind, K_INT64) == 0 || strcmp(kind, K_UINT64) == 0 || + strcmp(kind, K_FLOAT64) == 0 || strcmp(kind, K_TIME) == 0 || + strcmp(kind, K_DURATION) == 0) return 8; + return 0; +} + +static const uint8_t *v_body(const xval_t *v, kvhead_t *h) { + if (xv_decode_head_raw(v->data, v->len, h) < 0) return NULL; + return v->data + h->body_offset; +} + +int64_t xv_as_int64(const xval_t *v) { + if (xv_none(v)) return 0; + kvhead_t h; const uint8_t *b = v_body(v, &h); + if (!b) return 0; + const char *k = xv_kind(v); + if (strcmp(k, K_BOOL) == 0) return b[0] != 0; + if (strcmp(k, K_INT8) == 0) return (int8_t)b[0]; + if (strcmp(k, K_INT16) == 0) return (int16_t)rd16(b); + if (strcmp(k, K_INT32) == 0) return (int32_t)rd32(b); + if (strcmp(k, K_INT64) == 0) return (int64_t)rd64(b); + if (strcmp(k, K_UINT8) == 0) return b[0]; + if (strcmp(k, K_UINT16) == 0) return rd16(b); + if (strcmp(k, K_UINT32) == 0) return rd32(b); + if (strcmp(k, K_UINT64) == 0) return (int64_t)rd64(b); + if (strcmp(k, K_FLOAT32) == 0) { float f; uint32_t u = rd32(b); memcpy(&f, &u, 4); return (int64_t)f; } + if (strcmp(k, K_FLOAT64) == 0) { double d; uint64_t u = rd64(b); memcpy(&d, &u, 8); return (int64_t)d; } + if (strcmp(k, K_TIME) == 0 || strcmp(k, K_DURATION) == 0) return (int64_t)rd64(b); + return 0; +} + +double xv_as_float64(const xval_t *v) { + if (xv_none(v)) return 0; + kvhead_t h; const uint8_t *b = v_body(v, &h); + if (!b) return 0; + const char *k = xv_kind(v); + if (strcmp(k, K_FLOAT32) == 0) { float f; uint32_t u = rd32(b); memcpy(&f, &u, 4); return f; } + if (strcmp(k, K_FLOAT64) == 0) { double d; uint64_t u = rd64(b); memcpy(&d, &u, 8); return d; } + return (double)xv_as_int64(v); +} + +uint64_t xv_as_uint64(const xval_t *v) { + if (xv_none(v)) return 0; + kvhead_t h; const uint8_t *b = v_body(v, &h); + if (!b) return 0; + const char *k = xv_kind(v); + if (strcmp(k, K_UINT8) == 0) return b[0]; + if (strcmp(k, K_UINT16) == 0) return rd16(b); + if (strcmp(k, K_UINT32) == 0) return rd32(b); + if (strcmp(k, K_UINT64) == 0) return rd64(b); + return (uint64_t)xv_as_int64(v); +} + +bool xv_as_bool(const xval_t *v) { + if (xv_none(v)) return false; + kvhead_t h; const uint8_t *b = v_body(v, &h); + return b && h.body_len > 0 && b[0] != 0; +} + +uint32_t xv_char32_at(const xval_t *v, int32_t idx) { + kvhead_t h; const uint8_t *b = v_body(v, &h); + if (!b || idx < 0 || idx >= h.array_len) return 0; + return rd32(b + idx * 4); +} + +/* ── UTF-8 ↔ UTF-32 ────────────────────────────────────────────────── */ + +static void utf8_putc(sbuf_t *b, uint32_t cp) { + if (cp < 0x80) sb_putc(b, (char)cp); + else if (cp < 0x800) { sb_putc(b, (char)(0xC0 | (cp >> 6))); sb_putc(b, (char)(0x80 | (cp & 0x3F))); } + else if (cp < 0x10000) { + sb_putc(b, (char)(0xE0 | (cp >> 12))); sb_putc(b, (char)(0x80 | ((cp >> 6) & 0x3F))); + sb_putc(b, (char)(0x80 | (cp & 0x3F))); + } else { + sb_putc(b, (char)(0xF0 | (cp >> 18))); sb_putc(b, (char)(0x80 | ((cp >> 12) & 0x3F))); + sb_putc(b, (char)(0x80 | ((cp >> 6) & 0x3F))); sb_putc(b, (char)(0x80 | (cp & 0x3F))); + } +} + +static uint32_t utf8_next(const char *s, size_t *i, size_t len) { + const unsigned char *p = (const unsigned char *)s; + uint32_t cp = p[*i]; + if (cp < 0x80) { (*i)++; return cp; } + int n = 0; + if ((cp & 0xE0) == 0xC0) { n = 1; cp &= 0x1F; } + else if ((cp & 0xF0) == 0xE0) { n = 2; cp &= 0x0F; } + else if ((cp & 0xF8) == 0xF0) { n = 3; cp &= 0x07; } + else { (*i)++; return 0xFFFD; } + (*i)++; + for (int j = 0; j < n && *i < len; j++, (*i)++) cp = (cp << 6) | (p[*i] & 0x3F); + return cp; +} + +static char *utf32_to_utf8(const uint8_t *body, int32_t blen) { + sbuf_t b; sb_init(&b); + for (int32_t i = 0; i + 4 <= blen; i += 4) utf8_putc(&b, rd32(body + i)); + return sb_detach(&b); +} + +static char *strndup2(const uint8_t *p, int32_t n) { + char *s = malloc((size_t)n + 1); + if (s) { memcpy(s, p, (size_t)n); s[n] = 0; } + return s; +} + +char *xv_ptr_target(const xval_t *v) { + kvhead_t h; const uint8_t *b = v_body(v, &h); + if (!b) return strdup(""); + return strndup2(b, h.body_len); +} + +/* ── value_string(对齐 Go ValueString)────────────────────────────── */ + +static void append_num_int(sbuf_t *b, int64_t n) { sb_printf(b, "%lld", (long long)n); } +static void append_num_uint(sbuf_t *b, uint64_t n) { sb_printf(b, "%llu", (unsigned long long)n); } + +char *xv_value_string(const xval_t *v) { + if (xv_none(v)) return strdup(K_NONE); + kvhead_t h; const uint8_t *body = v_body(v, &h); + if (!body) return strdup(K_NONE); + int32_t blen = h.body_len; + const char *k = xv_kind(v); + + if (h.is_ptr) { + sbuf_t b; sb_init(&b); + sb_putn(&b, "\xE2\x86\x92", 3); + sb_putn(&b, (const char *)body, (size_t)blen); + return sb_detach(&b); + } + if (strcmp(k, K_BOOL) == 0) return strdup(body[0] ? "true" : "false"); + if (strcmp(k, K_INT8) == 0) { sbuf_t b; sb_init(&b); append_num_int(&b, (int8_t)body[0]); return sb_detach(&b); } + if (strcmp(k, K_INT16) == 0) { sbuf_t b; sb_init(&b); append_num_int(&b, (int16_t)rd16(body)); return sb_detach(&b); } + if (strcmp(k, K_INT32) == 0) { sbuf_t b; sb_init(&b); append_num_int(&b, (int32_t)rd32(body)); return sb_detach(&b); } + if (strcmp(k, K_INT64) == 0) { sbuf_t b; sb_init(&b); append_num_int(&b, (int64_t)rd64(body)); return sb_detach(&b); } + if (strcmp(k, K_UINT8) == 0) { sbuf_t b; sb_init(&b); append_num_uint(&b, body[0]); return sb_detach(&b); } + if (strcmp(k, K_UINT16) == 0) { sbuf_t b; sb_init(&b); append_num_uint(&b, rd16(body)); return sb_detach(&b); } + if (strcmp(k, K_UINT32) == 0) { sbuf_t b; sb_init(&b); append_num_uint(&b, rd32(body)); return sb_detach(&b); } + if (strcmp(k, K_UINT64) == 0) { sbuf_t b; sb_init(&b); append_num_uint(&b, rd64(body)); return sb_detach(&b); } + if (strcmp(k, K_FLOAT32) == 0 || strcmp(k, K_FLOAT64) == 0) { + char tmp[64]; fmt_float(tmp, sizeof tmp, xv_as_float64(v)); return strdup(tmp); + } + if (strcmp(k, K_CHAR_UTF8) == 0 || strcmp(k, K_CHAR_ASCII) == 0) return strndup2(body, blen); + if (strcmp(k, K_CHAR) == 0) return utf32_to_utf8(body, blen); + if (strcmp(k, K_RWIR) == 0) return strndup2(body + (blen >= 4 ? 4 : 0), blen >= 4 ? blen - 4 : 0); + if (strcmp(k, K_RWFUNC) == 0) { + sbuf_t b; sb_init(&b); + sb_printf(&b, "r%d/w%d", (blen >= 2 ? rd16(body) : 0), (blen >= 4 ? rd16(body + 2) : 0)); + return sb_detach(&b); + } + if (strcmp(k, K_INDEX) == 0) { + int n = 0; + for (int32_t i = 0; i < blen; i++) if (body[i] == '\n') n++; + if (blen > 0) n++; + sbuf_t b; sb_init(&b); sb_printf(&b, "(%d)", n); return sb_detach(&b); + } + if (strcmp(k, K_DICT) == 0) { + sbuf_t b; sb_init(&b); + if (blen == 0) sb_puts(&b, "dict"); + else { int n = 1; for (int32_t i = 0; i < blen; i++) if (body[i] == '\n') n++; sb_printf(&b, "{%d}", n); } + return sb_detach(&b); + } + return strndup2(body, blen); +} + +/* ── 构造 ──────────────────────────────────────────────────────────── */ + +void xv_new_tlv(xval_t *v, const char *kind, const uint8_t *raw, uint32_t raw_len, int32_t al) { + uint32_t len; + v->data = xv_encode_tlv(kind, 0, raw, raw_len, al, &len); + v->len = len; +} + +void xv_new_int64(xval_t *v, int64_t n) { + uint8_t r[8]; + r[0] = n & 0xFF; r[1] = (n >> 8) & 0xFF; r[2] = (n >> 16) & 0xFF; r[3] = (n >> 24) & 0xFF; + r[4] = (n >> 32) & 0xFF; r[5] = (n >> 40) & 0xFF; r[6] = (n >> 48) & 0xFF; r[7] = (n >> 56) & 0xFF; + xv_new_tlv(v, K_INT64, r, 8, 1); +} +void xv_new_float64(xval_t *v, double f) { + uint64_t u; memcpy(&u, &f, 8); + uint8_t r[8]; + for (int i = 0; i < 8; i++) r[i] = (u >> (i * 8)) & 0xFF; + xv_new_tlv(v, K_FLOAT64, r, 8, 1); +} +void xv_new_bool(xval_t *v, bool b) { + uint8_t r = b ? 1 : 0; + xv_new_tlv(v, K_BOOL, &r, 1, 1); +} +void xv_new_char_utf8(xval_t *v, const char *s) { + uint32_t sl = (uint32_t)strlen(s); + xv_new_tlv(v, K_CHAR_UTF8, (const uint8_t *)s, sl, (int32_t)sl); +} +void xv_new_char_kind(xval_t *v, const char *kind, const char *s) { + uint32_t sl = (uint32_t)strlen(s); + xv_new_tlv(v, kind, (const uint8_t *)s, sl, (int32_t)sl); +} +void xv_new_char_utf32(xval_t *v, const char *s) { + size_t len = strlen(s); + sbuf_t raw; sb_init(&raw); + size_t i = 0; + while (i < len) { + uint32_t cp = utf8_next(s, &i, len); + uint8_t le[4] = { cp & 0xFF, (cp >> 8) & 0xFF, (cp >> 16) & 0xFF, (cp >> 24) & 0xFF }; + sb_putn(&raw, (const char *)le, 4); + } + xv_new_tlv(v, K_CHAR, (const uint8_t *)raw.p, (uint32_t)raw.len, (int32_t)(raw.len / 4)); + sb_free(&raw); +} +void xv_new_ptr(xval_t *v, const char *kind, const char *target, int32_t al) { + uint32_t len; + v->data = xv_encode_tlv(kind, 1, (const uint8_t *)target, (uint32_t)strlen(target), al, &len); + v->len = len; +} +void xv_new_rwir(xval_t *v, int32_t nr, int32_t nw, const char *sig) { + size_t sl = strlen(sig); + uint8_t *raw = malloc(4 + sl); + raw[0] = nr & 0xFF; raw[1] = (nr >> 8) & 0xFF; + raw[2] = nw & 0xFF; raw[3] = (nw >> 8) & 0xFF; + memcpy(raw + 4, sig, sl); + xv_new_tlv(v, K_RWIR, raw, (uint32_t)(4 + sl), 1); + free(raw); +} + +void fmt_float(char *out, size_t cap, double v) { + char s[64]; + snprintf(s, sizeof s, "%.16g", v); + if (strtod(s, NULL) != v) snprintf(s, sizeof s, "%.17g", v); + snprintf(out, cap, "%s", s); + if (strchr(out, 'e')) return; + char *dot = strchr(out, '.'); + if (!dot) { + size_t l = strlen(out); + if (l + 2 < cap) { out[l] = '.'; out[l + 1] = '0'; out[l + 2] = 0; } + return; + } + char *p = out + strlen(out) - 1; + while (p > dot && *p == '0') *p-- = 0; + if (p == dot) { p[1] = '0'; p[2] = 0; } +} diff --git a/runtime/test/main.c b/runtime/test/main.c new file mode 100644 index 00000000..56978d4c --- /dev/null +++ b/runtime/test/main.c @@ -0,0 +1,25 @@ +#include "kvlang_runtime.h" +#include +#include +#include + +extern void rwext_term_start(const char *dsn); + +int main(int argc, char **argv) { + if (argc < 2) { fprintf(stderr, "usage: %s funcname [arg...]\n", argv[0]); return 2; } + const char *dsn = getenv("KVSPACE") ? getenv("KVSPACE") : "redis://127.0.0.1:6379"; + kvlang_rt *rt = kvlang_rt_connect(dsn); + if (!rt) { fprintf(stderr, "connect failed\n"); return 1; } + if (!getenv("KVLANG_NOTERM")) { + rwext_term_start(dsn); + usleep(200000); + } + char *ret = NULL; + char err[512]; + int rc = kvlang_rt_execute(rt, argv[1], (const char *const *)(argv + 2), argc - 2, &ret, err, sizeof err); + if (rc != 0) fprintf(stderr, "error: %s\n", err); + else if (ret) printf("%s\n", ret); + if (ret) free(ret); + kvlang_rt_disconnect(rt); + return rc; +} diff --git a/tutorial/test.py b/tutorial/test.py index f8ab5328..911752bb 100755 --- a/tutorial/test.py +++ b/tutorial/test.py @@ -20,13 +20,17 @@ ROOT = Path(__file__).resolve().parent.parent KV = str(ROOT / "kvlang") RUST_BIN = str(ROOT / "target" / "debug" / "kvlang") +LAYOUT_BIN = os.environ.get("KVLANG_LAYOUT_BIN", + str(ROOT / "layout" / "target" / "debug" / "examples" / "layout_file")) +CRUN_BIN = os.environ.get("KVBIN", str(ROOT / "runtime" / "test" / "run")) +_C_DSN = os.environ.get("KVSPACE", "redis://127.0.0.1:6379") SHM_PATH = "/tmp/kvlang_rust_test" FAIL_CSV = (ROOT / "tutorial" / "test_failures.csv").resolve() BENCH_CSV = (ROOT / "tutorial" / "benchmark.csv").resolve() MODULE = sys.modules[__name__] -_KV_ENV = {**os.environ, "KVLANG_KVSPACE": os.environ.get("KVLANG_KVSPACE", "goheap://")} -_SHM_ENV = {**os.environ, "KVLANG_KVSPACE": f"shm://{SHM_PATH}", +_KV_ENV = {**os.environ, "KVSPACE": os.environ.get("KVSPACE", "goheap://")} +_SHM_ENV = {**os.environ, "KVSPACE": f"shm://{SHM_PATH}", "LD_LIBRARY_PATH": str(ROOT.parent / "kvspace-c" / "build"), "KVSPACE_SHM": SHM_PATH} @@ -237,15 +241,52 @@ def _rust_test_file(f: Path, expects: list[str], env: dict) -> tuple[bool, str]: return True, rust.stdout[:200] +def _detect_entry(out: str) -> str: + m = re.search(r"ENTRY=(\S+)", out) + return m.group(1) if m else "init" + + +def _c_test_file(f: Path, expects: list[str], env: dict) -> tuple[bool, str]: + """Rust layout → kvspace(redis/shm) → C runtime,检查输出。""" + rel = str(f.relative_to(ROOT)) + if _C_DSN.startswith("shm://"): + try: + os.unlink(_C_DSN[len("shm://"):]) + except OSError: + pass + elif _C_DSN.startswith("fs://"): + shutil.rmtree(_C_DSN[len("fs://"):], ignore_errors=True) + else: + _flush_redis() + layout = subprocess.run([LAYOUT_BIN, rel, _C_DSN], capture_output=True, text=True, + timeout=60, cwd=str(ROOT), env=env) + if layout.returncode != 0: + return False, f"layout failed: {layout.stderr.strip()[:100]}" + entry = _detect_entry(layout.stdout) + try: + crun = subprocess.run([CRUN_BIN, entry], capture_output=True, text=True, + timeout=120, cwd=str(ROOT), env={**env, "KVSPACE": _C_DSN}) + except subprocess.TimeoutExpired: + return False, "timeout" + if crun.returncode != 0: + return False, f"c runtime exit {crun.returncode}: {crun.stderr.strip()[:100]}" + for pat in expects: + if pat not in crun.stdout: + return False, f"want {pat!r}" + return True, crun.stdout[:200] + + def main(): ap = argparse.ArgumentParser(description="tutorial test") ap.add_argument("--filter", default="", help="filter by name") ap.add_argument("--no-build", action="store_true", help="skip make build") ap.add_argument("--errorexit", action="store_true", help="exit on first error") ap.add_argument("--bench", action="store_true", help="benchmark matching .kv/.py/.c files") - ap.add_argument("--runtime", default="go", choices=("go", "rust"), - help="runtime to test (default: go)") + ap.add_argument("--runtime", default="go", choices=("go", "rust", "c"), + help="runtime to test (default: go; env KVLANG_RUNTIME=c 等价 --runtime c)") args = ap.parse_args() + if os.environ.get("KVLANG_RUNTIME") == "c": + args.runtime = "c" if not args.no_build and args.runtime == "go": r = subprocess.run(["make", "build"], capture_output=True, text=True, @@ -289,6 +330,15 @@ def main(): print(f"{RED}❌ {prefix} {rel}: {detail}{NC}") failures.append({"file": rel, "reason": detail, "expected": "", "stdout": ""}) failed += 1 + elif args.runtime == "c": + ok, detail = _c_test_file(f, expects, _KV_ENV) + if ok: + print(f"{GREEN}✅ {prefix} {rel}{NC}") + passed += 1 + else: + print(f"{RED}❌ {prefix} {rel}: {detail}{NC}") + failures.append({"file": rel, "reason": detail, "expected": "", "stdout": ""}) + failed += 1 else: _flush_redis() r = subprocess.run([KV, rel], capture_output=True, text=True, diff --git a/tutorial/test_c_runtime.py b/tutorial/test_c_runtime.py new file mode 100644 index 00000000..b1b4bcb1 --- /dev/null +++ b/tutorial/test_c_runtime.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""C runtime 验证:Rust layout → redis → C runtime,对比 .kv 头注释期望输出。 + +用法:python3 tutorial/test_c_runtime.py [filter] +前置:redis 运行中;layout 示例 + C runtime 已构建。 +""" +import re +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +LAYOUT = str(ROOT / "layout" / "target" / "debug" / "examples" / "layout_file") +CRUN = str(ROOT / "runtime" / "test" / "run") + + +def parse_expects(f: Path) -> list[str]: + pats = [] + in_block = False + for line in f.read_text().splitlines(): + if line.startswith("# 期望输出"): + in_block = True + continue + if in_block: + if line.startswith("# ") or line.startswith("# \t"): + p = line[2:].strip() + p = re.sub(r"\s*\(.*\)\s*$", "", p) + if p: + pats.append(p) + elif not line.startswith("#"): + break + return pats + + +def needs_skip(f: Path) -> bool: + for line in f.read_text().splitlines(): + if line.startswith("# extern") or line.startswith("# wip"): + return True + if line and not line.startswith("#"): + return False + return False + + +def detect_entry(out: str) -> str: + m = re.search(r"ENTRY=(\S+)", out) + return m.group(1) if m else "init" + + +def flush(): + subprocess.run(["redis-cli", "-p", "6379", "FLUSHALL"], capture_output=True, timeout=5) + + +def layout(f: Path): + return subprocess.run([LAYOUT, str(f)], capture_output=True, text=True, timeout=60) + + +def crun(entry: str): + cmd = [CRUN, entry] + return subprocess.run(cmd, capture_output=True, text=True, timeout=30) + + +def main() -> int: + filt = sys.argv[1] if len(sys.argv) > 1 else "" + files = sorted(ROOT.glob("tutorial/**/*.kv")) + passed = failed = skipped = 0 + fails = [] + for f in files: + if filt and filt not in str(f): + continue + if needs_skip(f): + skipped += 1 + continue + expects = parse_expects(f) + if not expects: + continue + print(f" {f.name}", file=sys.stderr, flush=True) + + flush() + lr = layout(f) + if lr.returncode != 0: + failed += 1 + fails.append((f.name, f"[layout] {lr.stderr.strip()[-160:]}")) + continue + entry = detect_entry(lr.stdout) + try: + cr = crun(entry) + except subprocess.TimeoutExpired: + failed += 1 + fails.append((f.name, "[timeout]")) + continue + if cr.returncode != 0: + failed += 1 + fails.append((f.name, f"[run rc={cr.returncode}] {cr.stderr.strip()[-160:]}")) + continue + ok = all(e in cr.stdout for e in expects) + if ok: + passed += 1 + else: + failed += 1 + fails.append((f.name, f"[entry={entry}] expect={expects} got={cr.stdout.strip()[-160:]!r}")) + + print(f"\nPASS {passed} FAIL {failed} SKIP {skipped}") + for name, why in fails: + print(f" FAIL {name}: {why}") + return 1 if failed else 0 + + +if __name__ == "__main__": + sys.exit(main())