6.4 KiB
6.4 KiB
16 - 交互式 REPL
TransPyV 内置一个交互式 REPL:把每一行/每一块 Viper 片段即时编译成 DLL 并加载调用,把结果回显出来。它支持会话变量(M2)与跨片段会话函数(M3),适合快速试验语法、验证表达式与类型行为。
实现位置:TransPyV/App/lib/core/Repl.vp;入口开关在 TransPyV/App/main.vp。
启动
TransPyV.exe --repl
:: 也可显式指定工程(REPL 的编译/链接参数取自工程配置)
TransPyV.exe --repl --project TransPyV\repl.vpj
--repl是布尔开关;未给--project时,按 当前工作目录 →TransPyV.exe同目录 的顺序查找repl.vpj;两处都没有则 fail-fast,并在诊断里打印已查找位置与模板位置。- 模板工程 TransPyV/repl.vpj 刻意不含
source_dir(让 REPL 走「全量扫描 + 注册」而非 Phase1 的可达性分析),sha1_slice_level = 1。 - REPL 的片段临时目录是
%TEMP%\TransPyV_repl\<pid>,与repl.vpj的build_dir无关。
与其它模式互斥
--repl 与 --phase2command、--asan、--traceback 互斥,命中即打印原因并返回错误码:
--phase2command下llc不执行,片段.obj不会生成,clang++ -shared必然失败;--asan会先给片段.ll插桩,而片段产物是运行期LoadLibraryA载入的 DLL,宿主未挂 ASan 运行库;- 片段 DLL 没有
<exe>.stackmap旁文件,--traceback无从符号化。
输入形态
启动横幅自述支持范围:
TransPyV REPL (M2/M3/M4) - 会话变量 + 会话函数(跨片段)+ ctx 回传结果;
支持表达式(1+2)、定义(x = 1 / x: int = 5)、引用(x + 1)、
增强赋值(x += 2)、取址(p = &x)等;空行跳过,Ctrl+Z 或 EOF 退出
多行块:行尾 ':' 进入续行(提示符 ...),空行结束并整块执行一次
限制:签名须单行、不支持 async def、函数体内不支持 raise、不支持偏函数
- 单行片段:任意顶层语句(表达式、定义、引用、增强赋值、取址……)。
- 多行块:整行去尾空白后以
:结尾,或首个非空白字符为@(装饰器行)时进入续行;空行或 EOF 结束,整块作为一个片段执行一次。REPL 刻意不做缩进/关键字分析,只认这两个判据(与 Python REPL 一致)。 - 提示符:单行
>>>,续行...。 - 退出:
Ctrl+Z/ EOF(连续读到 EOF)。
执行流程
每个片段走一遍完整的小型流水线(ReplExecFragment):
片段源码
│ ① 包裹为导出函数:export def __repl_frag_<n>(__repl_ctx__: bytes) -> int: ...
│ ② 落盘为 <tmp>/<n>.vp(供 fatal_error 回显真实源行)
│ ③ 重置模块级翻译状态 + 基线回灌 → 解析 → 翻译为完整模块 IR
│ ④ 校验导出符号签名 `define i32 @__repl_frag_<n>(i8* %__repl_ctx__)`
│ ⑤ 写 .ll → llc → .obj → clang++ -shared → .dll
│ ⑥ LoadLibraryA + GetProcAddress
│ ⑦ 经 t.Callable 间接调用(传入 ctx 基址)
└ ⑧ 从 ctx 结果区读回并回显
要点:
- 片段链接命令用工程配置的
linker.cmd(repl.vpj里是clang++),并追加-Wl,--out-implib=<...>.dll.a;编译器 flags 取compiler.flags。 - 宿主可导出符号(
project.vpj里的-Wl,--export-all-symbols)生成的 import lib 会被传给片段,使片段能调用 includes/宿主符号;找不到 import lib 时给出提示但不终止会话。 - 会话函数(顶层
def)会被提升为会话级符号,跨片段可见;提升受限类型集合(void/i1/i8/i16/i32/i64/float/double/i8*),超出即拒绝并打印。
会话上下文(ctx)
宿主与片段同进程共享一段 ctx 内存,用于回传会话变量与结果。布局由 TransPyV/App/lib/core/Gen/GenTypes.vp 定义:
| 常量 | 值 | 说明 |
|---|---|---|
REPL_CTX_MAGIC |
0x5245504C('REPL') |
魔数 |
REPL_CTX_VERSION |
2 |
版本 |
REPL_SLOT_BASE |
64 |
变量槽起始偏移(每槽 16 字节) |
REPL_VAR_CAP |
64 |
变量槽数量上限 |
REPL_FUNC_BASE |
1088 |
函数槽起始偏移 |
REPL_FUNC_CAP |
32 |
会话函数数量上限 |
REPL_CTX_SIZE |
1600 |
ctx 总大小(字节) |
REPL_SESSION_SHA1 |
71f67b7cb405f0e4 |
会话模块固定 SHA1 |
内存池与错误隔离
REPL 用两个 MemBuddy 池:
- 主池
mb:每行mark(),行末reset_to()回收瞬时分配(.ll/.obj/.dll路径、命令行等)。 - 永久池
_perm_pool:会话级常驻(jmp_buf、解析表、行/块/IR/ctx 缓冲、会话变量表与函数表、基线)。main.vp通过Repl._perm_pool = perm_pool注入;解析表ast._init_tables(_perm_pool)必须建在永久池,否则会被每行回收导致 use-after-free。
jmp_buf 错误隔离:_REPL_JMPBUF_SIZE = 256(mingw-w64 x86_64 上 jmp_buf 实测大小:_JBLEN=16 × SETJMP_FLOAT128 16B),分配在永久池。VLogger.error / fatal_error 经 repl_abort(longjmp)回到 REPL 的恢复点,从而进程存活、提示符继续,而不是终止进程。每行执行前 _setjmp;longjmp 返回分支会归零块状态、回滚会话表水位并 mb.reset_to(mark)。
已知限制
- 签名须单行;不支持
async def(协程 ABI 与 M3 的签名 token 不相容)。 - 函数体内不支持
raise(EH 出参会让间接调用 arity 不匹配,M2 不支持)。 - 不支持偏函数;会话函数的参数/返回类型受上面列出的类型集合限制(结构体值/数组/多级指针请改用指针参数或等 M4)。
- 容量上限:块输入 8 KiB、会话变量 64、会话函数 32、单片段提升上限 16、片段 import lib 上限 128;超限均明确报错,不静默。
- includes 类注册「缺存根 / 注入失败 / 超预算」只告警,会话继续。
相关文件
- TransPyV/App/lib/core/Repl.vp
- TransPyV/App/main.vp(
--repl/ReplFindProject) - TransPyV/repl.vpj(模板工程)
- TransPyV/App/lib/core/Gen/GenTypes.vp(ctx 布局常量)