Skip to content

PC/帧寻址与经典字节码的收敛:kvlang [函数轴,指令轴,参数轴] vs CPython 3.12 逐条对比 #203

Description

@miaobyte

观察

#116 把控制流摊平(if/while/for/break/continue → goto/br 到 int64 指令号,runtime 不再建 scope 帧、无 _then_/_else_/_while_ 目录)之后,kvlang 的运行时地址退回成三个数字轴:

PC = /vthread/<vtid>/[d]/[i,j]
                      └┬┘ └┬┘
              函数轴 d   │  └ 指令参数轴 j(读参负 / 写参正 / opcode=0)
      (调用栈深度,栈式)  └ 指令轴 i(函数内平坦指令号,1..N)

这个形态和经典字节码 VM(CPython/Lua/JVM)结构上非常接近。本 issue 用 CPython 3.12 的真实字节码逐条对比,厘清:哪些是已经收敛的经典模型、哪些是 kvlang 承重的本质差异、哪些可以反过来向成熟字节码借鉴。不改代码,先定认知与方向。

一、同一函数,两侧真实产物

源(两侧同构):

absval(x): if x < 0 { r = 0 - x } else { r = x }; return r

CPython 3.12 dis.dis(PC = co_code 里的整数字节偏移):

 2   LOAD_FAST 0(x); LOAD_CONST 1(0); COMPARE_OP <(2); POP_JUMP_IF_FALSE →26
 3   LOAD_CONST 1(0); LOAD_FAST 0(x); BINARY_OP -(10); STORE_FAST 1(r)
 6   LOAD_FAST 1(r); RETURN_VALUE
 5 > LOAD_FAST 0(x); STORE_FAST 1(r)
 6   LOAD_FAST 1(r); RETURN_VALUE
co_varnames=('x','r')  co_consts=(None,0)  # 局部=整数下标, LOAD_FAST 走 co_varnames[i]

kvlang kvlang dump(PC = KV 路径,指令按 [i,j] 逐槽铺开):

[1,0] goto      [1,-1]=2                       # 跳到指令 2
[2,0] int64·lt  [2,-1]=x [2,-2]=0  [2,1]=_1
[3,0] br        [3,-1]=_1 [3,-2]=4 [3,-3]=6    # 真→4 假→6
[4,0] int64·sub [4,-1]=0 [4,-2]=x  [4,1]=r
[5,0] goto      [5,-1]=8
[6,0] =         [6,-1]=x  [6,1]=r
[7,0] goto      [7,-1]=8
[8,0] return
# x →[0,-1](Ptr)  r →[0,1](Ptr)               # 名字→slot 的命名 Ptr

二、已经收敛的部分(和经典字节码一样)

维度 CPython kvlang(#116 后) 收敛?
每函数一段平坦线性指令,源码块结构被摊掉 co_code 字节序列 /lib/<func>/[i,*],i=1..N ✅ 相同
分支 = 跳到一个指令号 POP_JUMP_IF_FALSE/JUMP_BACKWARD 到字节偏移 goto/br 到 int64 irseq(kvcpu.c:32 jump_irseq) ✅ 相同模型
代码模板共享 + 每次调用一帧 一个 code object,每调用一个 frame /lib/<func> + 每帧一次 extindex(kvspace篇-03) ✅ 相同
局部在"每帧命名空间",键由编译/布局期定死 co_varnames 整数下标 名字→slot 的 Ptr,layout 期解析 ✅ 逻辑相同
调用栈 frame 对象链 帧按深度编号 [d](keytree.c:53) ✅ 逻辑相同(栈式)

用户的观察成立:摊平之后,kvlang 的地址骨架就是"经典字节码 + 帧栈"。

三、承重的本质差异(不是退化,是 kvlang 立身之本)

  1. 指令参数轴 j = 三地址,不是栈式。 CPython 是栈机:指令不显式寻址操作数,操作数经隐式求值栈流动(LOAD_FAST 压栈、BINARY_OP 弹二压一,co_stacksize 记录栈深)。kvlang 每条指令显式命名读操作数([i,-1],[i,-2]…)与写操作数([i,1]…),更接近 LLVM IR / Lua 寄存器 / 三地址码。j 轴 = 三地址指令的操作数平面,是"无隐藏栈、每个操作数都是可寻址 KV 路径"的直接后果,也是扩展 rwir 能有任意 nr/nw 的前提。承重,不可去。

  2. 基底是可观测、可崩溃恢复的 KV 树。 CPython 的 PC 是内存整数偏移、co_code 是不透明字节、frame 是堆上 C 结构——进程死即全失。kvlang 的 PC 是 KV 路径字符串,指令是可 kvspace tree/get 的 KV 条目,帧是 KV 子树——外部化、可观测、可重启续跑。逻辑骨架相同,基底完全不同,这正是 kvlang 的核心命题。

  3. layout 不是 codegen。 CPython compile() 产出不透明字节数组(codegen)。kvlang 把同一份三地址 IR 布局成 KV 坐标,不序列化成字节,IR 本身即运行时表示(呼应 kvlang/layout 定位澄清:它是语法检查器和布局工具,不是编译器 #202:layout 只检查+布局,不是编译器)。

  4. 优化/编译在扩展,核心刻意不优化字节码。 CPython 有窥孔优化 + 3.11 起的自适应特化解释器,优化的是字节码本身。kvlang 核心保持朴素解释,把融合/codegen 推给扩展编译器(算子融合,op-gpu)。所以"看着像字节码"的核心是故意不优化的。

四、由收敛引出的可借鉴点(本 issue 要讨论的方向)

既然骨架已和成熟字节码收敛,成熟设计里有一处直接可借:

局部变量寻址:CPython 的整数槽 vs kvlang 的 3 跳 Ptr 链。

讨论点(供定案,本 issue 不改码):

  1. 是否把 name→整数 slot 下标在 layout 期固化,让运行时局部寻址从 3 跳降到 1 跳(对齐 CPython LOAD_FAST/JVM iload/Lua 寄存器),消掉命名 Ptr 中间跳。与 j 轴的负/正坐标如何统一编号。
  2. 三地址(j 轴显式操作数)vs 栈式的定案确认:kvlang 明确站三地址阵营(理由:每操作数须为可寻址 KV 路径),把这条写进设计文档,避免反复。
  3. CPython frame 的紧凑布局是否对 2-runtime/PC 与帧寻址:调用深度导致 key 长度 O(D)、活跃 key 总量 O(D²) —— 评估 [ctlflow,irseq,params] 三维地址 #116 的 O(D²) 活跃键问题有借鉴(如把一帧的定长 slot 段合并寻址)。

复现

# CPython 侧
python3 -c "import dis; ..."   # 见上,Python 3.12.7
# kvlang 侧
kvlang dump cmp_demo.kv        # absval/sum_to(while)/fact(递归)/gcd,dump 见上

关联

#116(三维定稿与摊平实测)、#194(每操作一次 KV 往返的正交成本)、#202(layout ≠ 编译器)、kvspace篇-03(布局格式与 3 跳 Ptr 链)。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions