Skip to content

Commit 40c6e21

Browse files
committed
refactor: update keytree/kvcpu/kvspace/layoutcode/op
1 parent 3bc7cc0 commit 40c6e21

7 files changed

Lines changed: 101 additions & 65 deletions

File tree

internal/keytree/frame.go

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,59 +10,54 @@ import (
1010
// 链接帧结构(P4 Link 模型):
1111
//
1212
// callPC 调用指令绝对路径(帧唯一标识符)
13-
// callPC + "/_fn" 软链接 → /func/<pkg>/<name>(只读指令区)
13+
// callPC + "/.fn" 软链接 → /func/<pkg>/<name>(只读指令区)
1414
// callPC + "/<param>" 参数(本帧局部变量)
1515
// callPC + "/.callpc" 存储 callPC 自身,供 HandleReturn 恢复
16-
// callPC + "/.ret0" 调用方的写槽(return 时写入父帧)
16+
// callPC + "/.w0" 调用方的第 0 个写槽路径(return 时写入父帧)
1717
//
1818
// 层级嵌套(路径深度 = 调用栈深度):
1919
//
20-
// /vthread/42 L0 vthread 根
21-
// /vthread/42/_fn/[i,j] main 的指令(通过 L0 链接读取)
22-
// /vthread/42/[3,0] add 的帧根(callPC = /vthread/42/_fn/[3,0])
23-
// /vthread/42/[3,0]/_fn/[i,j] add 的指令
24-
// /vthread/42/[3,0]/[2,0] sub 的帧根
25-
// /vthread/42/[3,0]/[2,0]/_fn/[i,j] sub 的指令
20+
// /vthread/42 L0 vthread 根
21+
// /vthread/42/.fn/[i,j] main 的指令(通过 L0 链接读取)
22+
// /vthread/42/[3,0] add 的帧根(callPC = /vthread/42/.fn/[3,0])
23+
// /vthread/42/[3,0]/.fn/[i,j] add 的指令
24+
// /vthread/42/[3,0]/[2,0] sub 的帧根
25+
// /vthread/42/[3,0]/[2,0]/.fn/[i,j] sub 的指令
2626

27-
// FnCode 返回帧的指令链接路径:frameRoot + "/_fn"
28-
func FnCode(frameRoot string) string { return frameRoot + "/_fn" }
27+
// FnCode 返回帧的指令链接路径:frameRoot + "/.fn"
28+
//
29+
// ".fn" 以 "." 开头,遵循引擎保留键的统一约定(用户代码禁止写 "." 前缀键)。
30+
func FnCode(frameRoot string) string { return frameRoot + "/.fn" }
2931

3032
// FrameRoot 从任意指令绝对 PC 提取帧根(即 callPC)。
3133
//
32-
// /vthread/42/_fn/[2,0] → /vthread/42 (main 的帧根)
33-
// /vthread/42/[3,0]/_fn/[0,0] → /vthread/42/[3,0] (add 的帧根)
34-
// /vthread/42/[3,0]/[2,0]/_fn/[1,0] → /vthread/42/[3,0]/[2,0]
35-
// FrameRoot 从任意指令绝对 PC 提取帧根(即 callPC)。
36-
// 所有合法执行 PC 均由 Bootstrap / HandleCall 产生,格式保证含 "/_fn/"。
34+
// 所有合法执行 PC 均由 Bootstrap / HandleCall 产生,格式保证含 "/.fn/"。
3735
//
38-
// /vthread/42/_fn/[2,0] → /vthread/42
39-
// /vthread/42/[3,0]/_fn/[0,0] → /vthread/42/[3,0]
36+
// /vthread/42/.fn/[2,0] → /vthread/42
37+
// /vthread/42/[3,0]/.fn/[0,0] → /vthread/42/[3,0]
38+
// /vthread/42/[3,0]/[2,0]/.fn/[1,0] → /vthread/42/[3,0]/[2,0]
4039
func FrameRoot(pc string) string {
41-
if idx := strings.LastIndex(pc, "/_fn/"); idx >= 0 {
40+
if idx := strings.LastIndex(pc, "/.fn/"); idx >= 0 {
4241
return pc[:idx]
4342
}
44-
panic(fmt.Sprintf("FrameRoot: pc has no /_fn/ segment: %q", pc))
43+
panic(fmt.Sprintf("FrameRoot: pc has no /.fn/ segment: %q", pc))
4544
}
4645

4746
// ChildFrameRoot 从 callPC 推导被调方帧根。
4847
//
49-
// callPC = parentFrameRoot + "/_fn/" + "[coord]"
48+
// callPC = parentFrameRoot + "/.fn/" + "[coord]"
5049
// childFrameRoot = parentFrameRoot + "/" + "[coord]"
5150
//
52-
// 对顶层调用(callPC = /vthread/vtid/_fn/[0,0],无嵌套 /_fn/):
53-
// parentFrameRoot = /vthread/vtid(vthread 根即 pre_main 帧根)
54-
// childFrameRoot = parentFrameRoot + "/" + "[coord]" = /vthread/vtid/[0,0]
55-
// ChildFrameRoot 从 callPC 推导被调方帧根。
56-
//
57-
// callPC = parentFrameRoot + "/_fn/" + "[coord]"
58-
// childFrameRoot = parentFrameRoot + "/" + "[coord]"
51+
// 对顶层调用(callPC = /vthread/vtid/.fn/[0,0]):
52+
// parentFrameRoot = /vthread/vtid
53+
// childFrameRoot = /vthread/vtid/[0,0]
5954
//
60-
// 例:/vthread/42/_fn/[3,0] → /vthread/42/[3,0]
55+
// 例:/vthread/42/.fn/[3,0] → /vthread/42/[3,0]
6156
func ChildFrameRoot(callPC string) string {
62-
idx := strings.LastIndex(callPC, "/_fn/")
57+
idx := strings.LastIndex(callPC, "/.fn/")
6358
if idx < 0 {
64-
panic(fmt.Sprintf("ChildFrameRoot: callPC has no /_fn/ segment: %q", callPC))
59+
panic(fmt.Sprintf("ChildFrameRoot: callPC has no /.fn/ segment: %q", callPC))
6560
}
66-
// callPC[:idx] = parentFrameRoot, callPC[idx+5:] = "[coord]"
67-
return callPC[:idx] + "/" + callPC[idx+5:]
61+
// callPC[:idx] = parentFrameRoot, callPC[idx+6:] = "[coord]"
62+
return callPC[:idx] + "/" + callPC[idx+6:]
6863
}

internal/kvcpu/debug.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import (
1616
"kvlang/internal/op"
1717
)
1818

19-
// isFuncEntryPC 判断 pc 是否为函数帧入口指令(frameRoot/_fn/[0,0])。
19+
// isFuncEntryPC 判断 pc 是否为函数帧入口指令(frameRoot/.fn/[0,0])。
2020
// Bootstrap 和 HandleCall 均将第一条 PC 设为此形式。
2121
func isFuncEntryPC(pc string) bool {
22-
return strings.HasSuffix(pc, "/_fn/[0,0]")
22+
return strings.HasSuffix(pc, "/.fn/[0,0]")
2323
}
2424

2525
// debugFuncName 从帧根读取 .rootfunc 字段获取函数名。

internal/kvcpu/debugger_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func decodeAt(kv kvspace.KVSpace, pc string) *op.Instruction {
165165
// ─── 测试 1:事件 JSON 格式 ──────────────────────────────────────────────────
166166

167167
// TestDebugger_EventFormat 验证 .debug.pause 事件的 JSON 字段完整且语义正确:
168-
// - pc 必须以 /_fn/[0,0] 结尾(函数入口)
168+
// - pc 必须以 /.fn/[0,0] 结尾(函数入口)
169169
// - func 与源码函数名一致
170170
// - frame 非空(帧根路径)
171171
// - op 非空(被暂停指令的 opcode)
@@ -186,8 +186,8 @@ def idplus(x: int) -> (r: int) {
186186
if !gotFirst {
187187
gotFirst = true
188188
// 第一个暂停必定是函数入口
189-
if !strings.HasSuffix(ev.PC, "/_fn/[0,0]") {
190-
t.Errorf("first pause pc %q should end with /_fn/[0,0]", ev.PC)
189+
if !strings.HasSuffix(ev.PC, "/.fn/[0,0]") {
190+
t.Errorf("first pause pc %q should end with /.fn/[0,0]", ev.PC)
191191
}
192192
if ev.Func != "idplus" {
193193
t.Errorf("func=%q, want %q", ev.Func, "idplus")

internal/kvspace/redis/redis_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@ func TestResolveCore_NegativeCache(t *testing.T) {
9898
}
9999
}
100100

101-
// TestResolveCore_FnLink 模拟 VM 帧链接(/_fn 是唯一实际使用的链接形式)
101+
// TestResolveCore_FnLink 模拟 VM 帧链接(/.fn 是唯一实际使用的链接形式)
102102
func TestResolveCore_FnLink(t *testing.T) {
103103
lk := testLookup(map[string]string{
104-
"/vthread/42/[3,0]/_fn": "/func/main/add",
104+
"/vthread/42/[3,0]/.fn": "/func/main/add",
105105
})
106106
cases := []struct{ in, want string }{
107-
{"/vthread/42/[3,0]/_fn/[0,0]", "/func/main/add/[0,0]"},
108-
{"/vthread/42/[3,0]/_fn/[2,-1]", "/func/main/add/[2,-1]"},
109-
{"/vthread/42/[3,0]/_fn/[5,1]", "/func/main/add/[5,1]"},
107+
{"/vthread/42/[3,0]/.fn/[0,0]", "/func/main/add/[0,0]"},
108+
{"/vthread/42/[3,0]/.fn/[2,-1]", "/func/main/add/[2,-1]"},
109+
{"/vthread/42/[3,0]/.fn/[5,1]", "/func/main/add/[5,1]"},
110110
{"/func/main/add/[0,0]", "/func/main/add/[0,0]"}, // 已解析路径不变
111111
}
112112
for _, c := range cases {

internal/layoutcode/layoutcode.go

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@
1111
// 帧模型(Link P4):
1212
//
1313
// callPC 调用指令绝对路径,作为帧根
14-
// callPC/_fn 软链接 → /func/<pkg>/<name>(只读指令)
14+
// callPC/.fn 软链接 → /func/<pkg>/<name>(只读指令)
1515
// callPC/<param> 参数(本帧局部变量,不经过链接)
1616
// callPC/.callpc 存储 callPC 自身,供 HandleReturn 恢复
17-
// callPC/.ret0 调用方的写槽(return 时写入父帧)
1817
// callPC/.rootfunc 根函数名(TCO 不更新,供 resolveLabel 使用)
18+
//
19+
// 写槽(读写码语义):
20+
//
21+
// kvlang 只有读写码,函数调用 `add(x,y) -> ./s` 的 `-> ./s` 是**调用方指定的写目标**。
22+
// 写槽路径已存在调用指令 [addr0,1], [addr0,2],... 中,HandleReturn 直接从 .callpc
23+
// 推导路径读取,无需在子帧额外存储——没有"返回值",只有写槽绑定。
1924
package layoutcode
2025

2126
import (
@@ -67,10 +72,10 @@ func writeStmt(kv kvspace.KVSpace, st ast.Stmt, prefix string, idx *int) {
6772
}
6873
}
6974

70-
// HandleCall 执行 CALL:链接函数指令树,绑定参数,存储返回槽
75+
// HandleCall 执行 CALL:链接函数指令树,绑定参数,存储写槽
7176
//
72-
// pc 为调用指令的绝对路径(如 /vthread/42/_fn/[3,0])。
73-
// tail=true 时执行 TCO:复用当前帧,仅重链 _fn(br/goto 路径)。
77+
// pc 为调用指令的绝对路径(如 /vthread/42/.fn/[3,0])。
78+
// tail=true 时执行 TCO:复用当前帧,仅重链 .fn(br/goto 路径)。
7479
// 返回被调帧第一条指令的绝对 PC;失败时返回 ""(不再返回 pc,避免"PC == 失败"歧义)。
7580
func HandleCall(ctx context.Context, kv kvspace.KVSpace, pc string, inst *op.Instruction, tail bool) string {
7681
vtid := keytree.VtidFromPC(pc)
@@ -91,7 +96,7 @@ func HandleCall(ctx context.Context, kv kvspace.KVSpace, pc string, inst *op.Ins
9196
}
9297
funcSig := parser.ParseFuncSig(sigVal.Str())
9398

94-
// TCO:复用当前帧,仅重链 _fn 到目标块代码区(.rootfunc 不更新,保持根函数名)
99+
// TCO:复用当前帧,仅重链 .fn 到目标块代码区(.rootfunc 不更新,保持根函数名)
95100
if tail {
96101
frameRoot := keytree.FrameRoot(pc)
97102
kv.Unlink(keytree.FnCode(frameRoot))
@@ -106,7 +111,7 @@ func HandleCall(ctx context.Context, kv kvspace.KVSpace, pc string, inst *op.Ins
106111
callerFrameRoot := keytree.FrameRoot(pc)
107112
frameRoot := keytree.ChildFrameRoot(pc)
108113

109-
// 链接只读指令区:frameRoot/_fn → /func/<pkg>/<name>
114+
// 链接只读指令区:frameRoot/.fn → /func/<pkg>/<name>
110115
if err := kv.Link(funcKey, keytree.FnCode(frameRoot)); err != nil {
111116
vthread.SetError(ctx, kv, vtid, pc, "link failed: "+err.Error())
112117
return ""
@@ -123,19 +128,15 @@ func HandleCall(ctx context.Context, kv kvspace.KVSpace, pc string, inst *op.Ins
123128
}
124129
}
125130

126-
// 存储调用方写槽(return 时写入父帧)
127-
for i := range funcSig.ReturnNames() {
128-
if i < len(inst.Writes) {
129-
kv.Set(fmt.Sprintf("%s/.ret%d", frameRoot, i), kvspace.Str(inst.Writes[i]))
130-
}
131-
}
131+
// 写槽路径已在调用指令 [addr0,1], [addr0,2], ... 中,HandleReturn 从 .callpc 直接读,
132+
// 无需在子帧额外存 .w{N} 冗余键。
132133

133134
return keytree.FnCode(frameRoot) + "/[0,0]"
134135
}
135136

136-
// HandleReturn 处理 RETURN:回传值,清理帧,恢复父帧 PC。
137+
// HandleReturn 处理 RETURN:将被调方输出写入父帧写槽,清理帧,恢复父帧 PC。
137138
//
138-
// pc 为 return 指令的绝对路径(如 /vthread/42/[3,0]/_fn/[1,0])。
139+
// pc 为 return 指令的绝对路径(如 /vthread/42/[3,0]/.fn/[1,0])。
139140
// 返回值:
140141
//
141142
// ("", retVal) — 顶层 return(frameRoot == vthreadRoot);retVal 供 vthread.SetDone
@@ -162,21 +163,24 @@ func HandleReturn(ctx context.Context, kv kvspace.KVSpace, pc string, inst *op.I
162163
callPCVal, _ := kv.Get(frameRoot + "/.callpc")
163164
callPC := callPCVal.Str()
164165

165-
// 将所有返回值写入父帧的对应写槽(.ret0, .ret1, ...)
166+
// 将被调方输出写入父帧的写槽。
167+
// 读写码语义:写槽路径直接从调用指令 [callPC addr0, i+1] 读取——
168+
// callPC = /vthread/42/.fn/[3,0] → 第 i 个写槽在 /vthread/42/.fn/[3, i+1]。
169+
// 父帧在子帧执行期间保持挂起,父帧 .fn 链接不变,路径可靠解析。
166170
if callPC != "" {
167171
parentFrameRoot := keytree.FrameRoot(callPC)
168172
for i, read := range inst.Reads {
169-
retKey := fmt.Sprintf("%s/.ret%d", frameRoot, i)
170-
retTargetVal, _ := kv.Get(retKey)
171-
retTarget := retTargetVal.Str()
172-
if strings.HasPrefix(retTarget, "./") {
173+
wSlotPath := op.WriteSlotPC(callPC, i)
174+
wTargetVal, _ := kv.Get(wSlotPath)
175+
wTarget := wTargetVal.Str()
176+
if strings.HasPrefix(wTarget, "./") {
173177
v, _ := kv.Get(frameRoot + "/" + read[2:])
174-
kv.Set(parentFrameRoot+"/"+retTarget[2:], v)
178+
kv.Set(parentFrameRoot+"/"+wTarget[2:], v)
175179
}
176180
}
177181
}
178182

179-
// 清理帧:先 Unlink 代码区,再 DelTree 帧根(params / .ret0 / .callpc / .rootfunc)
183+
// 清理帧:先 Unlink 代码区,再 DelTree 帧根(params / .callpc / .rootfunc)
180184
kv.Unlink(keytree.FnCode(frameRoot))
181185
kv.DelTree(frameRoot)
182186

@@ -205,7 +209,7 @@ func RegisterBlocks(kv kvspace.KVSpace, pkg, parent string, body []ast.Stmt) {
205209
// 顶层帧的特征:frameRoot == vthreadRoot → HandleReturn 识别为顶层 return → SetDone。
206210
//
207211
// args 为按序传入的参数值(对应 funcSig.ParamNames()),可为空。
208-
// 成功返回第一条指令的绝对 PC(vthreadRoot/_fn/[0,0]);失败返回 ""。
212+
// 成功返回第一条指令的绝对 PC(vthreadRoot/.fn/[0,0]);失败返回 ""。
209213
func Bootstrap(ctx context.Context, kv kvspace.KVSpace, vtid, funcName string, args []string) string {
210214
pkgVal, err := kv.Get(keytree.FuncIdx(funcName))
211215
pkg := pkgVal.Str()

internal/op/op_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,28 @@ func TestNextPC(t *testing.T) {
3030
}
3131
}
3232

33+
func TestWriteSlotPC(t *testing.T) {
34+
tests := []struct {
35+
pc string
36+
slot int
37+
want string
38+
}{
39+
// 调用指令 [3,0],写槽 0 → [3,1],写槽 1 → [3,2]
40+
{"/vthread/42/.fn/[3,0]", 0, "/vthread/42/.fn/[3,1]"},
41+
{"/vthread/42/.fn/[3,0]", 1, "/vthread/42/.fn/[3,2]"},
42+
// 嵌套帧(子帧的调用指令)
43+
{"/vthread/42/[0,0]/.fn/[5,0]", 0, "/vthread/42/[0,0]/.fn/[5,1]"},
44+
// 指令 0
45+
{"/vthread/42/.fn/[0,0]", 0, "/vthread/42/.fn/[0,1]"},
46+
}
47+
48+
for _, tc := range tests {
49+
if got := op.WriteSlotPC(tc.pc, tc.slot); got != tc.want {
50+
t.Errorf("WriteSlotPC(%q, %d) = %q, want %q", tc.pc, tc.slot, got, tc.want)
51+
}
52+
}
53+
}
54+
3355
func TestParentPC(t *testing.T) {
3456
tests := []struct {
3557
pc string

internal/op/pc.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ import (
66
"strings"
77
)
88

9+
// WriteSlotPC 从指令 opcode 路径推导第 i 个写槽路径(i 从 0 起)。
10+
//
11+
// 调用指令 opcode 存在 [addr0,0],写槽存在 [addr0,1], [addr0,2], ...
12+
// 例:WriteSlotPC("/vthread/42/.fn/[3,0]", 0) → "/vthread/42/.fn/[3,1]"
13+
//
14+
// 用于 HandleReturn 直接从 .callpc 指令读写目标,无需额外存 .w{N} 键。
15+
func WriteSlotPC(pc string, i int) string {
16+
idx := strings.LastIndex(pc, "/[")
17+
if idx < 0 {
18+
return pc
19+
}
20+
addr0 := extractAddr0(pc[idx+1:])
21+
return fmt.Sprintf("%s/[%d,%d]", pc[:idx], addr0, i+1)
22+
}
23+
924
// NextPC 返回当前指令的下一条指令坐标。
1025
func NextPC(pc string) string {
1126
parts := strings.Split(pc, "/")

0 commit comments

Comments
 (0)