Files
Viper_Wiki/16-repl.md
T

6.4 KiB
Raw Blame History

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 类注册「缺存根 / 注入失败 / 超预算」只告警,会话继续。

相关文件